fix(seed): address seeded rows by their generated uuid, not by title (#32) #33

Merged
forgejo-admin merged 1 commit from fix-e2e-seed-unique into main 2026-08-02 05:15:24 +00:00

Fixes the UNIQUE constraint failed: template_task_user.* that 500-ed every
/api/test-reseed call in run 65#32.

e2e-nightly run 71, dispatched against this branch: 29 passed, 2 skipped, 2.2m — green.
Run 65 was 11 failed / 12 did not run / 6 passed in 20.4m. First green nightly there has ever been.

Root cause

createCategoryTasksAndAssignments minted a uuid for the template task, inserted
it, threw the uuid away, and re-read the row back with
findFirst({ where: eq(templateTask.title, task.title) }). title is not unique
— and neither are the other three columns the seed read itself back by:
category.name, user.email, template.isActive.

With a same-titled row already in the table the lookup resolves to the older
row, and assigning it to the same users again violates the
template_task_user primary key. /api/test-reseed clears the tables before it
seeds, so the residue comes from a reseed the test runner had already abandoned
landing after the next one began — and each 500 makes Playwright retry, which
issues another reseed, which is how one collision became a run-long storm. The
20.4m → 2.2m drop is that storm going away.

Fix

Mint each id up front and use it. All four self-read-backs are gone — the seed
never asks the database for something it already knows. Dropped along the way:
the templateId prop no caller of the helper ever read, and a
fetchRandomAvatar() result that was computed and discarded.

Evidence (watched to fail)

New tests/seed/reseed.test.ts seeds twice without clearing — exactly the
residue condition — and reproduces CI's error byte for byte on the pre-fix code:

$ git checkout apps/lunacycle-server/src/defaultScenario.ts   # pre-fix
$ bun test tests/seed
SQLiteError: UNIQUE constraint failed: template_task_user.template_task_id, template_task_user.user_id
      code: "SQLITE_CONSTRAINT_PRIMARYKEY"
      at createCategoryTasksAndAssignments (.../defaultScenario.ts:83:10)
      at async activeCycleScenario (.../defaultScenario.ts:150:9)
 0 pass / 1 fail

With the fix: 1 pass / 0 fail. Reverting it goes red again with the same trace.

Also run end to end in the CI image (mcr.microsoft.com/playwright:v1.60.0-noble,
same install steps as e2e-nightly.yml): 29 passed / 2 skipped.

The test runs in pr-validate.yml as bun run test:seed — separate from
test:unit because the seed path needs bun:sqlite, which vitest (node) can't
load.

Left for the operator

#32's last acceptance item — "should a red nightly notify anyone" — is a policy
call, not made here. It still fails silently.

Does not touch #30 or #31 (rebased onto #31's merge; the two pr-validate.yml
and package.json overlaps were both pure additions, kept side by side).

Fixes the `UNIQUE constraint failed: template_task_user.*` that 500-ed every `/api/test-reseed` call in [run 65](https://git.celilo.computer/celilo/lunacycle/actions/runs/65) — #32. **[e2e-nightly run 71](https://git.celilo.computer/celilo/lunacycle/actions/runs/71), dispatched against this branch: 29 passed, 2 skipped, 2.2m — green.** Run 65 was 11 failed / 12 did not run / 6 passed in 20.4m. First green nightly there has ever been. ## Root cause `createCategoryTasksAndAssignments` minted a uuid for the template task, inserted it, **threw the uuid away**, and re-read the row back with `findFirst({ where: eq(templateTask.title, task.title) })`. `title` is not unique — and neither are the other three columns the seed read itself back by: `category.name`, `user.email`, `template.isActive`. With a same-titled row already in the table the lookup resolves to the **older** row, and assigning it to the same users again violates the `template_task_user` primary key. `/api/test-reseed` clears the tables before it seeds, so the residue comes from a reseed the test runner had already abandoned landing *after* the next one began — and each 500 makes Playwright retry, which issues another reseed, which is how one collision became a run-long storm. The 20.4m → 2.2m drop is that storm going away. ## Fix Mint each id up front and use it. All four self-read-backs are gone — the seed never asks the database for something it already knows. Dropped along the way: the `templateId` prop no caller of the helper ever read, and a `fetchRandomAvatar()` result that was computed and discarded. ## Evidence (watched to fail) New `tests/seed/reseed.test.ts` seeds twice without clearing — exactly the residue condition — and reproduces CI's error byte for byte on the pre-fix code: ``` $ git checkout apps/lunacycle-server/src/defaultScenario.ts # pre-fix $ bun test tests/seed SQLiteError: UNIQUE constraint failed: template_task_user.template_task_id, template_task_user.user_id code: "SQLITE_CONSTRAINT_PRIMARYKEY" at createCategoryTasksAndAssignments (.../defaultScenario.ts:83:10) at async activeCycleScenario (.../defaultScenario.ts:150:9) 0 pass / 1 fail ``` With the fix: `1 pass / 0 fail`. Reverting it goes red again with the same trace. Also run end to end in the CI image (`mcr.microsoft.com/playwright:v1.60.0-noble`, same install steps as `e2e-nightly.yml`): 29 passed / 2 skipped. The test runs in `pr-validate.yml` as `bun run test:seed` — separate from `test:unit` because the seed path needs `bun:sqlite`, which vitest (node) can't load. ## Left for the operator #32's last acceptance item — "should a red nightly notify anyone" — is a policy call, not made here. It still fails silently. Does not touch #30 or #31 (rebased onto #31's merge; the two `pr-validate.yml` and `package.json` overlaps were both pure additions, kept side by side).
fix(seed): address seeded rows by their generated uuid, not by title
All checks were successful
pr-validate / validate (pull_request) Successful in 17s
82112f9c46
createCategoryTasksAndAssignments inserted a template task with a fresh
uuid, discarded it, then re-read the row by `title` — a non-unique column.
With a same-titled row already present (a reseed the runner abandoned
landing after the next one began) the lookup returned the OLDER row and
re-inserted its existing (templateTaskId, userId) pairs:

    UNIQUE constraint failed: template_task_user.template_task_id,
                              template_task_user.user_id

which 500-ed /api/test-reseed and took every e2e beforeAll with it (#32).

All four self-read-backs are gone (category.name, templateTask.title,
user.email, template.isActive — none unique); the seed mints each id up
front and uses it, which also drops the unused `templateId` prop and a
discarded fetchRandomAvatar() call.

`bun run test:seed` covers the invariant and runs in pr-validate. It lives
outside test:unit because the seed path needs bun:sqlite, which vitest
can't load.
forgejo-admin force-pushed fix-e2e-seed-unique from 82112f9c46
All checks were successful
pr-validate / validate (pull_request) Successful in 17s
to 39529b3113
All checks were successful
pr-validate / validate (pull_request) Successful in 18s
2026-08-02 04:03:01 +00:00
Compare
forgejo-admin force-pushed fix-e2e-seed-unique from 39529b3113
All checks were successful
pr-validate / validate (pull_request) Successful in 18s
to beab6c0d59
All checks were successful
pr-validate / validate (pull_request) Successful in 17s
2026-08-02 04:06:09 +00:00
Compare
Sign in to join this conversation.
No description provided.