package recruiting import "testing" func ip(i int) *int { return &i } // TestBucketBoundaries. Every boundary here is an off by one waiting to happen, and each one either // hides overdue work or cries wolf about work that is not due yet. func TestBucketBoundaries(t *testing.T) { for _, c := range []struct { name string days *int want string }{ {"no due date is not overdue", nil, bucketNone}, {"yesterday is overdue", ip(-1), bucketOverdue}, {"long past is overdue", ip(-400), bucketOverdue}, {"today is today, not overdue", ip(0), bucketToday}, {"tomorrow is soon, not today", ip(1), bucketSoon}, {"the last day of the window is still soon", ip(soonWindowDays), bucketSoon}, {"the day after the window is later", ip(soonWindowDays + 1), bucketLater}, {"far future is later", ip(365), bucketLater}, } { t.Run(c.name, func(t *testing.T) { if got := bucketFor(c.days); got != c.want { t.Errorf("bucketFor(%v) = %q, want %q", c.days, got, c.want) } }) } } // TestBucketsPartitionEveryInput. Every possible day offset lands in exactly one bucket, and the // ordering of the buckets is monotone in the offset. A gap would make a task invisible in every view. func TestBucketsPartitionEveryInput(t *testing.T) { rank := map[string]int{bucketOverdue: 0, bucketToday: 1, bucketSoon: 2, bucketLater: 3} prev := -1 for d := -500; d <= 500; d++ { b := bucketFor(ip(d)) r, ok := rank[b] if !ok { t.Fatalf("day offset %d produced bucket %q, which is not a due bucket", d, b) } if r < prev { t.Fatalf("bucket rank went backwards at day %d: %q", d, b) } prev = r } // And only a missing due date yields none, so "none" can never hide a dated task. if bucketFor(nil) != bucketNone { t.Error("a missing due date did not produce the none bucket") } } // TestTaskVocabulariesMatchMigration pins the Go allowlists against the CHECK constraints in 0049. func TestTaskVocabulariesMatchMigration(t *testing.T) { for _, s := range []string{"open", "done", "cancelled"} { if !taskStatuses[s] { t.Errorf("status %q is in migration 0049 but missing here", s) } } if len(taskStatuses) != 3 { t.Errorf("%d statuses, migration 0049 permits 3", len(taskStatuses)) } for _, p := range []string{"low", "normal", "high"} { if !taskPriorities[p] { t.Errorf("priority %q is in migration 0049 but missing here", p) } } if len(taskPriorities) != 3 { t.Errorf("%d priorities, migration 0049 permits 3", len(taskPriorities)) } } // TestUpdateTaskInputCannotSetCompletedAt is a structural guard. completed_at is maintained by the // database from the status, and accepting it from a caller is how a reopened task ends up still // carrying a completion time and reading as finished in every report. func TestUpdateTaskInputCannotSetCompletedAt(t *testing.T) { for _, forbidden := range []string{"completed_at", "created_by", "organization_id"} { if hasJSONField(UpdateTaskInput{}, forbidden) { t.Errorf("UpdateTaskInput exposes %q, which the database owns", forbidden) } if hasJSONField(CreateTaskInput{}, forbidden) { t.Errorf("CreateTaskInput exposes %q, which the database owns", forbidden) } } }