Skip to content

test: add integration tests with seeded MySQL/Redis and response snapshots - #247

Draft
TwoDCube wants to merge 2 commits into
test/unit-testsfrom
test/integration
Draft

TwoDCube wants to merge 2 commits into
test/unit-testsfrom
test/integration

Conversation

@TwoDCube

@TwoDCube TwoDCube commented Sep 14, 2026 •

Copy link
Copy Markdown
Member

Step 2 of 3 in adding a test suite. Stacked on #246, so the base is test/unit-tests. Once #246 merges and its branch is deleted, GitHub retargets this PR to master.

What changes

New test-utils workspace crate, modelled on matverseny-backend's:

  • TestApp::seeded() / TestApp::empty() serve the real website_backend2::app router, middleware included, on 127.0.0.1:0. The persisted-query cache is the real Redis-backed one.
  • TestDatabase creates a fresh test_<uuid> MySQL database for each test and generates every table from the sea-orm entities (Schema::create_table_from_entity). It drops the database when the test ends, so tests are independent and run in parallel.
  • seed.rs is a small, deterministic data set: published, featured, unpublished and backdated posts; authors and labels with and without posts; a nested menu; soft-deleted rows; canteen days around the ISO year boundary; events around a month's calendar grid; colleague names that need Hungarian collation. Named constants identify the rows tests use.
  • assert_response_snapshot! is insta::assert_json_snapshot! with object keys sorted. The order of sibling fields in responses is not stable between runs, while array order, which the tests rely on, is kept.
  • TEST_MYSQL_URL / TEST_REDIS_URL configure it, with defaults that match compose.yaml. If a service is unreachable, the failure says how to start it.

Entities aligned with production (src/entity/):

  • ids and the type/category columns are now unsigned
  • strings have their Laravel lengths
  • posts_data.date is DATE
  • events_data.color is NOT NULL

Only schema generation and seeding use these types; the resolvers select their own columns into their own structs, so runtime queries are unchanged. The unsigned types matter because sqlx only decodes u32/u16 from UNSIGNED columns. On a signed schema, Maybe swallows the decode error and the resolvers' ids would come back as null.

Integration tests (tests/: 44 tests + 18 insta snapshots, plus 12 unit tests for the harness itself):

File Covers
frontend_queries.rs The operations website_frontend2 sends (copied to tests/frontend/), snapshot-tested
posts.rs Ordering, featured, search (case- and accent-insensitive), unpublished posts never leaking, preview tokens, archive, author/label filters, pagination
calendar.rs Canteen ISO weeks across New Year, events in a calendar grid, invalid arguments
content.rs Colleagues in Hungarian order, pages, menu tree, authors, labels
http.rs Health checks, /metrics counters, CORS, gzip, complexity limit, GraphiQL, introspection, routing
persisted_queries.rs The APQ protocol end to end, plus RedisCache round-trip and eviction of undecodable entries

CI:

  • The Test job starts database and redis with docker compose up --wait, then runs cargo nextest run --workspace.
  • Format and Clippy cover the workspace.
  • Service logs are printed when the job fails.

compose.yaml gains healthchecks and --max-connections=500, and drops the obsolete version key.

Docs: docs/testing.md explains the harness, the seed data, how to update snapshots, and why entity changes must mirror migrations.

Bugs found (ignored tests; each gets its own fix PR)

Test Bug
paging_through_posts_visits_every_published_post_once The cursor filter is date <= d AND id < i, so posts with a higher id but an older date are skipped: paging stops after [1, 2].
page_info_of_a_filtered_list_reflects_the_filter hasPreviousPage/hasNextPage of author, label, featured and search lists are computed over all published posts.
listing_posts_without_published_posts_returns_an_empty_list With no published posts, every post list returns Could not get min value instead of an empty list.
nonexistent_canteen_week_is_a_graphql_error canteen(week: 0) panics on unwrap() and returns HTTP 500.
operation_named_introspection_query_can_select_data Requests with operationName: "IntrospectionQuery" skip the transaction and context data, so selecting data panics (HTTP 500).
trailing_slash_is_ignored trim_trailing_slash is added with Router::layer, which runs after routing, so POST /graphql/ returns 404.
soft_deleted_page_is_not_served page(slug) ignores deleted_at.
soft_deleted_menu_items_are_not_listed menu ignores deleted_at.
menu_slug_does_not_depend_on_selecting_link page_id is only loaded when link is selected, so { menu { slug } } returns null slugs. The frontend always selects link, so the site isn't affected today.

events_include_the_last_day_of_the_calendar_grid covers the events bug from #246 at the API level.

Snapshots record current behaviour, so each fix PR also updates the affected snapshots.

Verification

  • cargo fmt --all --check passes; cargo clippy --locked --workspace --all-targets -- -D warnings is clean.
  • cargo test --workspace against MySQL 8.4.11 and Redis 6.2.24 in local containers: 109 passed, 14 ignored.
  • A second run with CI=true, so insta refuses to write, passes.
  • Every ignored test was run with --ignored and fails the way its description says.
  • No test_* databases are left behind after a run.
  • Every snapshot was read and checked against the seed data. The one snapshot that recorded a bug (the menu slugs) was replaced with the ignored test above.
  • The first CI run failed on canteen_page: CI serialized the query's aliased root fields in a different order than local runs. Snapshots now compare with sorted object keys (ced400b). A jq -S comparison confirmed all 18 regenerated snapshots are semantically unchanged.
  • Container image: podman build -f Containerfile from a clean worktree of this branch succeeds. cargo-chef compiled none of the test-only crates, and the image's binary starts on ubi-micro, exiting with the expected EnvVarMissing { name: "DATABASE_URL" } when unconfigured.
  • Not run locally: nextest and cargo-deny. CI runs both.

🤖 Generated with Claude Code

…shots

Add a test-utils workspace crate that serves the real application router
against a fresh MySQL database per test, with tables generated from the
sea-orm entities and an optional deterministic seed data set.

- Entities now match the production column types (unsigned ids, string
  lengths, DATE post dates). sqlx needs this to decode the resolvers'
  u32/u16 fields, and the generated test schema relies on it.
- Integration tests cover posts, search, preview tokens, the archive,
  canteen, events, pages, the menu, colleagues, authors, labels, HTTP
  behaviour and Apollo persisted queries.
- The operations website_frontend2 sends are copied into tests/frontend/
  and snapshot-tested with insta.
- Nine more bugs are recorded as #[ignore = "bug: ..."] tests.
- compose.yaml gets healthchecks, and CI starts MySQL and Redis for tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@verseghy-prow
verseghy-prow Bot requested a review from smrtrfszm September 14, 2026 18:03
@verseghy-prow

verseghy-prow Bot commented Sep 14, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: TwoDCube

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@verseghy-prow verseghy-prow Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Sep 14, 2026
@TwoDCube
TwoDCube marked this pull request as draft September 14, 2026 18:06
CI serialized the aliased root fields of the canteen query in a different
order than local runs, which failed its snapshot. JSON object key order
carries no meaning, so take response snapshots with
assert_response_snapshot!, which sorts object keys before comparing and
keeps array order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/work-in-progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant