feat(guest): guest checkout without login (Swish + QR) #17

Merged
jocke merged 5 commits from feature/guest-checkout into master 2026-06-22 11:16:16 +00:00

5 commits

Author SHA1 Message Date
Hermes Agent
2ef093ba2b test(guest): add frontend unit tests for guest checkout flow
All checks were successful
CI / Lint, type check, unit tests, coverage (pull_request) Successful in 2m51s
CI / E2E browser tests (pull_request) Successful in 1m34s
Add 41 tests covering all four guest checkout modules that had 0%
function coverage in CI, raising overall frontend coverage from
76.69% to 84.64% statements, well above the 70% threshold.

New test files:
- guestOrders.spec.ts: verify createGuestOrder posts correct body,
  fetchGuestOrder GETs /guest-orders/:token, payGuestOrder POSTs
  /guest-orders/:token/pay; error propagation
- GuestCheckoutPage.spec.ts: form validation (plate regex, email
  regex, button disabled state), character counter, successful
  submission navigates to guest-payment with token+plate query,
  API failure shows Swedish error message
- GuestOrderPage.spec.ts: loading state, order detail rendering,
  all six status labels (pending_payment, paid, sent, delivered,
  failed, cancelled), payment link visibility, fetch error handling
- GuestPaymentRedirect.spec.ts: Swish number/amount display, QR
  code rendering, Swish payment link, confirmation dialog flow,
  payGuestOrder call on confirm, navigation on success, error for
  missing token and failed fetch, redirect when already paid,
  magic order link

Also includes a rebase onto origin/master (PR #16 Swish QR fixes,
payment unit tests, e2e retry improvements) which resolves the
'not up to date with master' from the review comment.
2026-06-22 10:44:34 +00:00
Hermes Agent
d16f048630 fix(db): make V12 migration H2-compatible (drop partial-index WHERE clauses)
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).
2026-06-22 10:35:56 +00:00
Hermes Agent
8ff71f8e32 fix(test): remove non-existent setCreatedAt call that broke compileTestJava
GuestOrderControllerTest#shouldGetGuestOrderByToken called
Order.setCreatedAt(Instant.parse(...)), but the Order entity has no
setCreatedAt setter — createdAt is assigned only inside @PrePersist
onCreate(). This was a compile error (cannot find symbol) that made
compileTestJava fail, so the jacocoTestCoverageVerification CI step
aborted before running any tests — hence coverage never improved and
CI stayed red after the previous commit (a0cefb2).

Why E2E passed but lint-and-test failed: the e2e backend image builds
with `./gradlew :backend:bootJar`, which compiles only main sources
and never compiles/runs tests. The lint-and-test job runs
`./gradlew :backend:jacocoTestCoverageVerification`, which depends on
test -> compileTestJava, which is where this failed.

Changes:
- Remove the Order.setCreatedAt(Instant) call (no such method).
- Remove the order.setAmountPaid(BigDecimal) setup line.
- Remove the jsonPath $.amountPaid assertion (depended on that setup;
  the test still asserts id, plate, status, guestToken).
- Drop the now-unused java.math.BigDecimal and java.time.Instant
  imports.

No behavioral change to production code; test-only fix.
2026-06-22 10:35:56 +00:00
Hermes Agent
33ffc8851d test(guest): add backend unit tests for guest checkout to fix CI
The guest checkout PR (#17) added new backend code without any unit
tests, causing the jacocoTestCoverageVerification CI step to fail
(coverage dropped below 70% line / 60% branch thresholds).

OrderServiceTest — 10 new tests covering:
- createGuestOrder: correct fields, plate normalization, email
  normalization (lowercase+trim), null email handling
- getOrderByGuestToken: successful lookup, not-found, security check
  (refuses to serve user-owned orders via guest token)
- confirmGuestPayment: success path (status→PROCESSING, notification),
  non-pending order throws, unknown token throws

GuestOrderControllerTest — new file, 8 tests covering:
- POST /api/guest-orders: create without auth (201), validation
  (bad plate, blank email, invalid email, blank letter text)
- GET /api/guest-orders/{token}: lookup (200), not-found (404)
- POST /api/guest-orders/{token}/pay: confirm (200), conflict (409),
  not-found (404)

All tests follow existing patterns (MockitoExtension for service,
SpringBootTest+MockMvc for controller). Cannot run backend tests
locally (no JDK in agent sandbox) — CI will verify.
2026-06-22 10:35:56 +00:00
Hermes Agent
6381b6fd63 feat(guest): guest checkout without login (Swish + QR)
Adds an anonymous guest checkout flow so a customer can order a bilhälsning
without creating an account. Payment via Swish (QR + payment link).

Backend:
- GuestOrderController: POST /api/guest-orders (public, no auth)
- CreateGuestOrderRequest / GuestOrderResponse DTOs
- Order entity: guest_email, guest_token (UUID), nullable user_id
- OrderRepository: findByGuestToken, findByGuestEmail
- OrderService: createGuestOrder, getGuestOrder by token
- SecurityConfig: /api/guest-orders/** permitAll
- V12 migration: drops user_id NOT NULL, adds guest_email + guest_token
  with partial unique index (backfill-safe for existing user orders)

Frontend:
- GuestCheckoutPage: plate lookup + order form (no login)
- GuestPaymentRedirect: Swish QR + payment link + status polling
- GuestOrderPage: order status by guest token
- guestOrders.ts API client
- router: /guest/* public routes
- vite.config: dev proxy for /api/guest-orders

Verification:
- [x] vue-tsc type-check passes (exit 0)
- [ ] Backend Java compiles (no JDK/docker in agent sandbox)
- [ ] Flyway V12 migration applies cleanly
- [ ] End-to-end POST /api/guest-orders -> 201 -> Swish -> status

Frontend type-checks but backend has NOT been compiled or run yet. This
PR is for review; backend smoke test pending in a docker environment.
2026-06-22 10:35:56 +00:00