Skip to content

refactor(backend): build the dev fixture through the API - #240

Merged
sabinem merged 1 commit into
mainfrom
fix/seed-using-services
Aug 25, 2026
Merged

refactor(backend): build the dev fixture through the API#240
sabinem merged 1 commit into
mainfrom
fix/seed-using-services

Conversation

@sabinem

@sabinem sabinem commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

The seed wrote every row with ent and then hand-wrote the casbin rows that should have accompanied them, so the fixture drifted from the handlers it was imitating: the capability→policy switch was copied out of SetCapabilities, votes were written

Seed the dev fixture through the API

As a developer working on Hackagon
I want the dev fixture to be built by calling the backend
So that what I see locally is always a state the app itself could have produced.

Why

The seed wrote rows with ent and hand-copied the casbin rules that should go with
them. Those copies drifted: it duplicated the capability→policy switch out of
SetCapabilities, wrote votes without the checks SubmitVote makes, and kept an
answer's storage format true by hand. When a handler got fixed, the fixture didn't.

What changed

The seed now starts the gRPC server in-process on a bufconn pipe and drives it with
tokens it signs itself — the only way to act as all 106 fixture identities, of which
four exist in Keycloak. All four hackathons go through real RPCs; 24 hand-written
AddRole/AddPolicy calls are gone, and main.go drops from 1962 lines to 208.

What you'll notice

  • Two fixture bugs fixed: public hackathons are finally readable anonymously
    (public-hackathon-read is stale), and H3 gained the member, submission:read
    row the hand-written vote capability had missed.
  • Team Gamma's creator moves from bob to the admin — team:create is Owner-only, so
    a participant-assembled team was never a reachable state.
  • just db::seed logs one upsert answer ERROR per answering participant. That's
    the handler's own log line from answer-upsert-sql; the seed still succeeds.
  • A failed run now refuses to re-seed on top of itself: wipe with just clean::state.

Verification

Row-by-row diff of the old and new fixtures: 90 lines of 864 differ, each one an
explained handler behaviour. check::lint, check::build, check::test and
treefmt --ci pass. Smoke-tested against the running backend as alice, bob and
charles.

The seed wrote every row with ent and then hand-wrote the casbin rows that
should have accompanied them, so the fixture drifted from the handlers it was
imitating: the capability→policy switch was copied out of SetCapabilities, votes
were written without the checks SubmitVote makes, and an answer's storage format
had to be kept true by hand. It now stands the gRPC server up in-process on a
bufconn pipe and drives it with tokens it signs itself. That is the only way to
act as the 106 fixture identities — four of which exist in Keycloak — and the
only way to give anyone a display name, which can come from JWT claims and
nowhere else. All 24 AddRole/AddPolicy call sites are gone; the handlers grant
every role themselves.

Two consequences shape how each hackathon now reads. A capability has to be on
at the moment it is used, so each one switches capabilities on as its history
needs them and ends with a call declaring the set it should be left in. And H3
is created with a live window and backdated last of all, because Join refuses a
hackathon that has already finished — a past hackathon cannot be populated as
one.

Seeding is no longer atomic and cannot be: several handlers open a transaction of
their own, and ent refuses one inside another. Instead of the guarantee the run
checks all four hackathon names — all present skips, none seeds, and some is an
error telling you to wipe, because a half fixture that looks whole is worse than
either. It was never fully atomic anyway, since casbin writes through its own
connection and a rollback always left the policy rows.

Diffed row by row against the old fixture: 90 lines of 864 change. Two are fixes
the fixture had missed. The three public hackathons finally carry
`*, hackathon:read`, so anonymous browsing works and public-hackathon-read is
stale; and H3 gains the `member, submission:read` row that the hand-written vote
capability dropped, without which a voter cannot read what they are voting on.
One deliberate divergence goes the other way: the `owner, project:join` rows the
seed used to add so an owner could express a preference are gone, and seeded
hackathons now behave exactly as the API does. The rest is the fixture no longer
improving on the handlers — page order starts at 0, questions with no options
store null, Approve and Team.Create leave the modifier alone, a single-choice
vote stores 0 — plus Team Gamma passing from bob to the admin, because
team:create is Owner-only and a participant-assembled team is not a state the
API can produce.

Registration answers are the one direct write left, because no answer can be
stored through the API at all (TODO(backend: answer-upsert-sql)). The seed calls
SubmitAnswers anyway so the handler still validates them, falls back to ent on
exactly that failure, and stops falling back the day the ticket lands.
@sabinem
sabinem merged commit 285d1cd into main Aug 25, 2026
2 checks passed
@sabinem
sabinem deleted the fix/seed-using-services branch August 25, 2026 18:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant