fix(e2e): migrate move_toast from browser to API tier (bookshelf-xif3r) #1339
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-xif3r"
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?
Summary
Coverage equivalence: the browser toast was a thin UI layer over the same MoveBooksWorkflow completion. Journey-14 asserts the same terminal workflow state directly at the API/state level (same pattern as Journey-6's EnrichWorkflow/RecalcMatchScoresWorkflow guards); the Vitest tests assert every JS branch of the polling controller.
Test plan
Closes bead bookshelf-xif3r on merge.
Security Review — bookshelf-xif3r / PR #1339
Reviewed per
.claude/rules/review-standard.md.Scope examined
e2e/browser/journey_move_toast_test.godrops any security-relevant assertion not re-covered by the replacement.journey_14_bulk_move_workflow_test.go) exercises the bulk-move endpoint with correct user scoping.Findings
No findings.
Deleted browser test — security coverage audit:
The deleted browser journey made exactly two assertions: (a) an info toast appears immediately after the move POST, and (b) an error toast appears once the workflow completes with failures. Both are DOM/JS-layer assertions about the Stimulus controller toast UI — neither is an authZ, cross-user scoping, CSP, or ownership assertion. There is no security-relevant assertion in the deleted file that is not re-covered.
New API journey — user scoping:
The journey seeds books and libraries mapped to
user_id=1viauser_library_mapping, then issues requests throughauthClient(the standard authenticated session for user 1). ThePOST /books/bulk/movehandler derivesuserIDfrom the authenticated session (not the request body), filters source books viafilterOwnedIDs(userLibraryIDs), and filters destination libraries viaownedLibSet— both keyed off the session userID. TheGET .../statushandler explicitly checksownerID != sessionUserIDand returns not-found on mismatch. The cross-user denial path (user B polling user A instanceID) is covered by the unit test atinternal/bookmove/move_handler_test.go:1001("wrong user — ownership mismatch"). The new e2e journey correctly exercises the happy path; the authZ negative path is the right tier for unit tests and is already there.Wall-clock timing in polling loop:
The
time.Now().Before(deadline)/time.Sleep(300ms)polling pattern mirrors Journey-6 (e2e/api/journey_6_wfengine_test.go:239,258) which established this as the accepted idiom for async wfengine assertions. The assertion itself (Expect(state).To(Equal("completed"))) is on the workflow terminal state, not on elapsed time — the flake-prevention rule for wall-clock-in-assertion-path does not apply.Secrets / PII: None. All fixture data is synthetic (library names
Journey14Src/Journey14Dst, book titleJourney14 Test Book).Production code change: Zero. The diff touches only
e2e/api/,e2e/browser/, andstatic/js/test/— nointernal/,cmd/,templates/,static/css/, orMakefilechanges.E2E policy compliance: The new file is a single
OrderedDescribewith a singleBeforeAllthat boots one fresh DB+app, satisfying the journey policy. It includes the required justification comment.REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Code Review — bookshelf-xif3r (PR #1339)
CI + Mergeability
CI state: success (all 7 jobs green). Mergeable: True.
Phase 1: Spec Compliance
Bead goal: fix an intermittent E2E Browser flake by migrating the racy bulk-move journey from the browser tier to the API tier. The diff does exactly this — deletes the browser journey, adds an API-tier Journey-14, and adds Vitest branch coverage for
_pollMoveStatus. No extra work, no missing requirements.Coverage parity: the browser journey tested two behaviours via CSS selector assertions: an info toast appearing immediately, and an error toast appearing when the workflow completes with failures. Per CLAUDE.md browser policy: "Browser
Itasserting only a CSS selector → API test." The deletion is policy-correct. The API journey covers the wiring (POST 202 + workflow completion + exact BulkMoveResult fields). The_pollMoveStatusVitest tests cover all four JS branches including the success/error toast dispatch. Nothing substantive is lost.Phase 2: Code Quality
e2e/api/journey_14_bulk_move_workflow_test.go — all checks pass:
Ordered+BeforeAllbooting a fresh DB+app once. No per-spec ResetDB() inside It blocks.deadline := time.Now().Add(60s); for time.Now().Before(deadline)) is identical to Journey-6 lines 239/306/364 — established precedent. Assertion is onstate == "completed"(string), not on elapsed time. Not the flake pattern the review standard targets.srcPathID(line 103) is assigned and used at line 107 (INSERT INTO book). No dangling variable.package api_test— black-box compliant.static/js/test/books_organize_controller.test.js — all checks pass:
vi.useFakeTimers()/vi.useRealTimers()in beforeEach/afterEach — correct.vi.advanceTimersByTime(2001)correctly drains the 2000ms MOVE_POLL_INTERVAL_MS setTimeout.for (var i = 0; i < 8; i++) { await Promise.resolve(); }micro-task drain is the project-established pattern for flushing fetch().then().then() chains after fake-timer advance.running → completedacross two timer advances with an intermediate assertion.fetchcalled exactly once — correctly verifies no retry after terminal 4xx.make e2e-policy-checkwill pass: single top-levelvar _ = Describe("...", Ordered, ...).No findings.
REVIEW VERDICT: 0 blocker, 0 major, 0 minor