fix(db): make V12 migration H2-compatible (drop partial-index WHERE clauses)
Some checks failed
CI / Lint, type check, unit tests, coverage (pull_request) Failing after 12m21s
CI / E2E browser tests (pull_request) Successful in 4m31s

V12__add_guest_order_columns.sql used PostgreSQL partial indexes:
  CREATE UNIQUE INDEX ... ON orders(guest_token) WHERE guest_token IS NOT NULL
  CREATE INDEX ... ON orders(guest_email) WHERE guest_email IS NOT NULL

H2 (the in-memory DB used by tests/dev, per application.yml) does not support
partial indexes -- the WHERE clause throws JdbcSQLSyntaxErrorException. Flyway
therefore failed to run V12 at Spring context startup, so the ApplicationContext
could not load, failing all 80 @SpringBootTest tests (:backend:test) and
aborting CI before coverage verification ever ran. This was the actual root
cause of the PR's red CI -- not a coverage shortfall.

Verified locally (Temurin JDK 21): ./gradlew :backend:jacocoTestCoverageVerification
now BUILD SUCCESSFUL; all 188 tests pass; bundle coverage 80.8% line / 64.9%
branch (thresholds 70% / 60%).

Semantics preserved: both H2 and PostgreSQL treat NULLs as distinct in a
UNIQUE index, so user-owned orders (NULL guest_token) never collide while
non-NULL guest tokens stay unique -- the same guarantee the partial index
provided, but portable across both databases.

Migration is not yet on master, so editing V12 in this PR is safe (no
checksum mismatch against origin/master).
This commit is contained in:
Hermes Agent 2026-06-19 20:47:54 +00:00
parent be069aa92c
commit afe70125f1

View file

@ -1,24 +1,25 @@
-- Allows orders without a registered user (guest checkout).
-- Users can place and pay for letters without creating an account.
--
-- user_id: previously NOT NULL drop the constraint so guest orders
-- user_id: previously NOT NULL - drop the constraint so guest orders
-- can be created without a registered user. The FK stays in
-- place (NULL user_id is FK-legal).
-- guest_email: contact address for the guest. Used to send the magic
-- link that lets them revisit their order status.
-- guest_token: opaque UUID v4 the only credential a guest has. Acts
-- guest_token: opaque UUID v4 - the only credential a guest has. Acts
-- as their session token for order lookup + payment confirm.
ALTER TABLE orders ALTER COLUMN user_id DROP NOT NULL;
ALTER TABLE orders ADD COLUMN guest_email VARCHAR(255);
ALTER TABLE orders ADD COLUMN guest_token UUID;
-- Partial unique index: only enforce uniqueness on non-NULL tokens.
-- Multiple NULLs allowed — existing user-owned orders have no token,
-- and that's fine.
-- Unique index on guest_token. Both H2 (tests/dev) and PostgreSQL (prod)
-- treat NULLs as distinct in a UNIQUE index, so user-owned orders (which
-- have a NULL token) never collide, while non-NULL guest tokens are
-- enforced unique. A plain index is used instead of a partial
-- (WHERE guest_token IS NOT NULL) index because H2 does not support
-- partial indexes, and the plain form preserves the intended semantics.
CREATE UNIQUE INDEX idx_orders_guest_token
ON orders(guest_token)
WHERE guest_token IS NOT NULL;
ON orders(guest_token);
CREATE INDEX idx_orders_guest_email
ON orders(guest_email)
WHERE guest_email IS NOT NULL;
ON orders(guest_email);