e2e-nightly has never passed: every seedDatabase() call fails on a UNIQUE constraint #32
Labels
No labels
area/auth
area/backend
area/ci
area/deploy
area/e2e
area/frontend
area/shared
blocked
good-first-issue
needs-info
priority/high
priority/low
priority/medium
type/bug
type/chore
type/docs
type/feature
type/tech-debt
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
celilo/lunacycle#32
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
e2e-nightly.ymlhas run exactly once since it was created, and it failed. lunacycle currently has no working e2e coverage gating anything:release.ymldeliberately does not gate on e2e (it was moved out so a Docker-runner outage couldn't block shipping), so a red nightly blocks nothing and publishes proceed regardless.This matters more than usual right now because registry-poll CD is about to be armed for lunacycle (see #31). Once
auto_upgradeis on, whatever merges tomainreaches production unattended within 15 minutes, with the nightly as the only correctness signal — and it has never been green.Symptom
Run 65 — 11 failed, 12 did not run, 6 passed, 20.4m.
Every failure is a
beforeAlltimeout:and the server log shows the real cause, repeated on every reseed:
So
seedDatabase()returns 500, thebeforeAllhangs to its timeout, and every spec that seeds dies with it.Scope
apps/lunacycle-server/src/test-reseed.ts— the reseed endpoint. Its delete list looks complete and correctly ordered (templateTaskUseris cleared beforetemplateTask), so the residue theory needs proving rather than assuming.apps/lunacycle-server/src/defaultScenario.ts:78— the failing insert, insidecreateCategoryTasksAndAssignments. Note it re-reads the row it just inserted withfindFirst({ where: eq(templateTask.title, task.title) })rather than using the uuid it generated a few lines above. That lookup is by a non-unique column, so any title collision resolves to the wrong (earlier) row and re-inserts an existing(templateTaskId, userId)pair. Using the generated id directly removes the failure mode regardless of whether it's the active one here.playwright.config.tshasworkers: 1andfullyParallel: false. Ruled out.active-cycle-extra→activeCycleWithExtraTaskScenario→activeCycleScenario, which seeds a single template task ("Meditate") for users[bob, monica]. No duplicate titles are visible in the scenario data, so the mechanism is not yet fully explained — reproduce locally before fixing.createUserawaitsfetchRandomAvatar()(a network call) and then never uses the result. Three of those per seed, against an external service, inside a 30sbeforeAllbudget on a CI container. Worth deleting while in here.Acceptance
seedDatabase()succeeds on repeated calls within a single server lifetime.defaultScenario.tsno longer re-queries an inserted row by a non-unique column.bun run test:playwrightrun is green.e2e-nightly.ymlcompletes green at least once on schedule (not just viaworkflow_dispatch).Related
.forgejo/workflows/e2e-nightly.yml— the workflow, and its header comment explaining why e2e stopped gating releases.Fix in #33. e2e-nightly run 71 against that branch: 29 passed, 2 skipped, 2.2m — green.
Corrections to the filing
The diagnosis was right about the site; two of the supporting claims were not.
fetchRandomAvatar()is not a network call any more.avatarUtils.tswas rewritten to generate an SVG data URI locally ("that service went away and there's no good reason for seeding to depend on the network"). So it wasn't eating the 30s budget. Its result was still computed and discarded increateUser, which is now deleted.workers: 1rules out parallel tests, not overlapping requests. That's exactly the mechanism. Playwright abandons thebeforeAllat 30s but the reseed it issued is still in flight server-side; the next spec's reseed clears the tables and starts seeding while the abandoned one is still writing. That's where the second same-titledMeditatecomes from, which is the residue the scenario data doesn't show.titlelookup was the right suspect, and it isn't the only one. All four self-read-backs in the seed used a non-unique column:templateTask.title,category.name,user.email,template.isActive. All four are gone; the seed mints each id up front.Why one collision took the whole run down
A 500 makes Playwright retry the spec, which issues another reseed, which is more overlap — self-amplifying. That accounts for the symptoms that the constraint alone doesn't explain:
bootstrap.spec'sminimalseed (which cannot hit this bug) timing out three times, andsmoke.spec.ts:16's plainrequest.get('http://localhost:3000/api')timing out at 30s. Both are a saturated single-threaded server, not a separate fault. Run 65 took 20.4m; run 71 takes 2.2m.Reproduction
tests/seed/reseed.test.ts(new, runs asbun run test:seedinpr-validate.yml) seeds twice without clearing — the residue condition — and reproduces the CI error byte for byte on pre-fix code, down to the frame numbers:It goes green with the fix and red again when the fix is reverted. It lives outside
test:unitbecause the seed path needsbun:sqlite, which vitest (node) can't load.Acceptance
seedDatabase()succeeds on repeated calls within a single server lifetimedefaultScenario.tsno longer re-queries an inserted row by a non-unique columnbun run test:playwrightrun is green (locally, in the CI image, and on the runner)e2e-nightly.ymlgreen on schedule — run 71 wasworkflow_dispatch; the 07:00 UTC cron after #33 merges is the one that closes this