Skip to content

Add integration tests for the base website and ticket purchase flow - #50

Open
daedeloth wants to merge 12 commits into
upgrade/php-8.5from
feature/integration-tests
Open

Add integration tests for the base website and ticket purchase flow#50
daedeloth wants to merge 12 commits into
upgrade/php-8.5from
feature/integration-tests

Conversation

@daedeloth

Copy link
Copy Markdown
Member

Summary

HTTP-level integration tests proving the public site renders and the full ticket purchase flow works — free and paid — against a real MySQL 8 database with every external service faked. Runs inside the docker-compose webserver container (production PHP) against a dedicated catlab_events_test database, so dev data is never touched and no local PHP/DB setup is needed:

bin/integration-tests.sh

Stacked on #49 (needs the PHP 8.5 image) — merge that first, then retarget/merge this.

What's covered (10 tests)

  • Smoke: homepage, event page, ticket-selection page render.
  • Free purchase: select → process → order ACCEPTED, thanks page renders, confirmation email requested, no payment API involved, Eukles tracking fired (all via fakes).
  • Paid purchase: order created remotely (faked), redirect to payUrl, PSP callback orders/{id}/sync flips the order to ACCEPTED — exercised both authenticated and as a guest, locking in the unauthenticated-callback contract real PSP calls depend on.
  • Guards: register routes redirect guests to login; no order is created.
  • migrate:fresh on a clean MySQL 8 every run — proves the migration chain end-to-end.

Production changes (2, both deliberate)

  1. CatLabApiClientFactory seam — the three direct new ApiClient(...) sites (EventController, Order, SendEmail) now resolve a factory from the container so tests can bind a fake. Behavior identical in production.
  2. Bug fix (found by these tests): free orders were cancelled by the thanks page. Order::synchronize() cancelled any order lacking a catlab_order_id; free orders never get one, and the thanks page calls synchronize(). It now only cancels orders still PENDING. Side benefit: the unauthenticated GET orders/{id}/sync can no longer flip an accepted or refunded order to CANCELLED (which re-fired cancellation email + UitPAS cancel).

Also: mockery/mockery dev-dependency bumped 0.9 → ^1.6 (0.9 fatals under PHP 8.5 inside Laravel's RefreshDatabase; nothing in the repo uses the Mockery API directly).

Deviations from the spec (documented)

  • The plan's fixture code predated schema drift: events.startDate/endDate moved to the event_dates table (2021), description/event_type are NOT NULL, requires_team defaults true — the fixtures trait adapts, with comments.
  • The free-flow test POSTs /process directly rather than walking select → confirm first; confirmRegister and the PreparingOrder event are not yet exercised.

Known follow-ups (pre-existing, out of scope)

  • Order::synchronize() compares $this->status, which is not a column (always null) — the "don't go cancelled → pending" guard is dead code.
  • Order::getEuklesAttributes() does $data['price'] on a null getOrderData() result for orders without a catlab_order_id.
  • Organisation::getRepresentedOrganisation()'s function-static cache makes some smoke-test outcomes order-dependent within one phpunit process.
  • docker-compose maps MySQL to host port 3312, which can collide with other local projects (tests are unaffected — they use the internal network).

Verification

  • bin/integration-tests.sh: OK (10 tests, 36 assertions)
  • Host vendor/bin/phpunit --testsuite Unit: OK (9 tests) — still DB-free
  • Whole-branch review + fix wave completed; per-task review on every commit

🤖 Generated with Claude Code

https://claude.ai/code/session_013HqprN6hPwwHLXeCRbGdgo

daedeloth and others added 12 commits July 24, 2026 15:21
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HqprN6hPwwHLXeCRbGdgo
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HqprN6hPwwHLXeCRbGdgo
Adds phpunit.integration.xml, bin/integration-tests.sh, and
tests/Integration/IntegrationTestCase.php so integration tests run inside
the docker-compose webserver container against a dedicated
catlab_events_test database, never the dev database. HarnessTest verifies
the DB connection name and that /status responds.

Bumped mockery/mockery from 0.9.* to ^1.6 (composer.json/composer.lock):
Laravel's RefreshDatabase trait mocks console output via Mockery on every
artisan() call, and Mockery 0.9.11 (2015) generates invalid "?mixed"
parameter syntax under PHP 8.5, causing a fatal error before migrate:fresh
even runs. Nothing in the repo uses the Mockery API directly (tests use
PHPUnit's createMock), so the bump is safe. Host Unit suite (7 tests) still
passes after the bump.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HqprN6hPwwHLXeCRbGdgo
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HqprN6hPwwHLXeCRbGdgo
Order::synchronize() unconditionally cancelled any order lacking a
catlab_order_id. Free-ticket orders never receive a catlab_order_id
(no payment API call is made for them), so simply loading or
reloading the thanks page silently flipped every accepted free order
to CANCELLED.

Restrict the cancel-on-missing-id branch to orders still PENDING;
accepted orders are left untouched. Covered by the new
FreeTicketPurchaseTest integration test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HqprN6hPwwHLXeCRbGdgo
Introduces FakeCatLabApiClient, FakeCatLabApiClientFactory and
FakeEuklesClient, bound in IntegrationTestCase::setUp() so tests never
hit the real CatLab Accounts or Eukles services. FreeTicketPurchaseTest
exercises a full free-ticket registration end to end, including
visiting the thanks page, and asserts the order stays ACCEPTED and
exactly one confirmation email is requested.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HqprN6hPwwHLXeCRbGdgo
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HqprN6hPwwHLXeCRbGdgo
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HqprN6hPwwHLXeCRbGdgo
- Use explicit nullable types (?User) for CatLabApiClientFactory::forUser()
  and its test fake, since implicit nullable is deprecated on PHP 8.4+ and
  production runs 8.5.
- Pin external-service env vars in phpunit.integration.xml so the container
  no longer inherits the developer's .env: fixed Eukles key/secret make
  EuklesEventSubscriber register deterministically against the fake client,
  and UiTDatabank credentials are blanked. Add an assertion in
  FreeTicketPurchaseTest that order confirmation is now reliably tracked
  via the faked Eukles client.
- Add a PaidTicketPurchaseTest covering the payment-sync callback when the
  buyer's session/auth has been dropped, since real PSP callbacks arrive
  without one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HqprN6hPwwHLXeCRbGdgo
The default 3312 mapping collides with other local docker projects;
the integration tests themselves use the internal network and are
unaffected, but docker compose up refuses to start the container.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HqprN6hPwwHLXeCRbGdgo
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