Refuse draft_week jobs on the generic jobs endpoint
CI / ci (push) Successful in 3m28s
CI / ci (pull_request) Successful in 3m10s

A kind can now be made service-only: Runner.Enqueue, and so
POST /api/v1/jobs, refuses it with 400 service_only_kind, and only its
typed Kind enqueues it. draft_week is service-only, so StartDraft and
its one-draft-in-flight-per-week rule is the only way to start a draft;
a confirmed draft_week posted to /api/v1/jobs can no longer run beside
the week's unconfirmed draft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 06:57:53 -04:00
co-authored by Claude Opus 5.5
parent 45fcd71d27
commit 1f548c9f1d
5 changed files with 103 additions and 9 deletions
+36
View File
@@ -736,3 +736,39 @@ func TestRunnerEnqueueByKindName(t *testing.T) {
t.Errorf("job = %+v (result %s), want succeeded with {\"done\":3}", job, job.Result)
}
}
func TestServiceOnlyKind(t *testing.T) {
t.Parallel()
st := migratedStore(t)
r := newRunner(t, st, 1)
k := register(t, r, "count", func(_ context.Context, p countPayload, _ func(countResult) error) (countResult, error) {
return countResult{Done: p.Steps}, nil
})
if err := k.ServiceOnly(); err != nil {
t.Fatal(err)
}
ctx := context.Background()
if _, err := r.Enqueue(ctx, "count", json.RawMessage(`{"steps":3}`)); !errors.Is(err, ErrServiceOnly) {
t.Errorf("Runner.Enqueue error = %v, want ErrServiceOnly", err)
}
var n int
if err := st.Reader.QueryRow("SELECT COUNT(*) FROM jobs").Scan(&n); err != nil {
t.Fatal(err)
}
if n != 0 {
t.Errorf("refused enqueue stored %d jobs, want 0", n)
}
id, err := k.Enqueue(ctx, countPayload{Steps: 3})
if err != nil {
t.Fatal(err)
}
start(t, r)
if job := waitFor(t, r, id, finished); job.Status != StatusSucceeded {
t.Errorf("job enqueued through its Kind = %+v, want succeeded", job)
}
if err := k.ServiceOnly(); err == nil {
t.Error("ServiceOnly after Start succeeded, want an error")
}
}