Run the test suite against MariaDB instead of SQLite #169

Open
opened 2026-09-26 16:07:20 +02:00 by myrmidex · 2 comments
Owner

Problem

phpunit.xml forces DB_CONNECTION=sqlite / DB_DATABASE=:memory:, while dev and production run MariaDB. SQLite silently tolerates schema operations and constraints MariaDB rejects, so the suite produces false positives: a migration can be green in CI and fail on deploy.

This project has already shipped a broken migration chain once (#150), and .claude/PLATFORM.md documents MariaDB-specific constraints (feeds.url UNIQUE vs non-unique feeds.provider) that no test can currently exercise.

It also blocks the routes.id ticket — that migration alters a composite primary key with a dependent foreign key, which is exactly the class of operation SQLite would accept and MariaDB might refuse.

Approach

Point phpunit.xml at a MariaDB test database.

RefreshDatabase migrates once and wraps each test in a rolled-back transaction against a real transactional database, rather than re-running migrations per test as it does for an in-memory database — so the suite may not get slower. One published benchmark found MySQL faster than in-memory SQLite for this reason. Measure before and after rather than assuming either direction.

CI needs a MariaDB service container in .forgejo/workflows/ci.yml, with steps health-gated on it. Sequenced after #148 so the CI image build path is automated first.

Blocked on

The dev MariaDB credentials currently fail — container_db_read (as root) and container_tinker (as ffr) both return Access denied. The dev database was recreated from a fresh mariadb:11 pull during #164 and may no longer match the compose file's expected credentials. This must be resolved first.

Acceptance criteria

  • Suite runs against MariaDB locally and in CI
  • Full suite green (1273 tests as of v1.4.3)
  • Runtime measured before and after, recorded here
  • At least one test demonstrably fails on MariaDB where it passed on SQLite, or a note recording that none did
  • .claude/PLATFORM.md updated — the SQLite note under Dev Environment is now wrong
  • #148 — CI image build path, sequenced first
## Problem `phpunit.xml` forces `DB_CONNECTION=sqlite` / `DB_DATABASE=:memory:`, while dev and production run MariaDB. SQLite silently tolerates schema operations and constraints MariaDB rejects, so the suite produces false positives: a migration can be green in CI and fail on deploy. This project has already shipped a broken migration chain once (#150), and `.claude/PLATFORM.md` documents MariaDB-specific constraints (`feeds.url` UNIQUE vs non-unique `feeds.provider`) that no test can currently exercise. It also blocks the `routes.id` ticket — that migration alters a composite primary key with a dependent foreign key, which is exactly the class of operation SQLite would accept and MariaDB might refuse. ## Approach Point `phpunit.xml` at a MariaDB test database. `RefreshDatabase` migrates once and wraps each test in a rolled-back transaction against a real transactional database, rather than re-running migrations per test as it does for an in-memory database — so the suite may not get slower. [One published benchmark](https://madewithlove.com/blog/using-mysql-for-testing-on-laravel/) found MySQL *faster* than in-memory SQLite for this reason. **Measure before and after rather than assuming either direction.** CI needs a MariaDB service container in `.forgejo/workflows/ci.yml`, with steps health-gated on it. Sequenced after #148 so the CI image build path is automated first. ## Blocked on The dev MariaDB credentials currently fail — `container_db_read` (as `root`) and `container_tinker` (as `ffr`) both return `Access denied`. The dev database was recreated from a fresh `mariadb:11` pull during #164 and may no longer match the compose file's expected credentials. This must be resolved first. ## Acceptance criteria - [ ] Suite runs against MariaDB locally and in CI - [ ] Full suite green (1273 tests as of v1.4.3) - [ ] Runtime measured before and after, recorded here - [ ] At least one test demonstrably fails on MariaDB where it passed on SQLite, **or** a note recording that none did - [ ] `.claude/PLATFORM.md` updated — the SQLite note under Dev Environment is now wrong ## Related - #148 — CI image build path, sequenced first
myrmidex added this to the v1.5.0 milestone 2026-09-26 16:07:20 +02:00
myrmidex added the
testing
label 2026-09-26 16:07:20 +02:00
myrmidex self-assigned this 2026-09-26 16:07:20 +02:00
Author
Owner

Blocker resolved. The dev MariaDB is reachable again.

Cause: the db_data volume predated the current credentials. MariaDB's entrypoint only creates MYSQL_USER/MYSQL_PASSWORD when it initialises an empty data directory — the log showed MariaDB upgrade not required with no Initializing database files, so the compose file's credentials were never applied to that volume. root failed too, so the volume's actual credentials were unrecoverable.

Fixed by recreating the volume (dev data was seeded fixtures only):

docker compose -f docker/dev/docker-compose.yml down
docker volume rm dev_db_data
docker compose -f docker/dev/docker-compose.yml up -d
docker exec ffr_dev_app php artisan migrate --seed

Verified: MariaDB 11.8.8, database ffr_dev, 27 migrations applied, ffr@% present. Both container_tinker and container_db_read work again.

Worth knowing for this ticket: a test database created the same way will have the same property — changing credentials in compose does nothing to an existing volume. If the MariaDB test database is provisioned as a separate volume, that is a trap to document rather than rediscover.

**Blocker resolved.** The dev MariaDB is reachable again. Cause: the `db_data` volume predated the current credentials. MariaDB's entrypoint only creates `MYSQL_USER`/`MYSQL_PASSWORD` when it initialises an **empty** data directory — the log showed `MariaDB upgrade not required` with no `Initializing database files`, so the compose file's credentials were never applied to that volume. `root` failed too, so the volume's actual credentials were unrecoverable. Fixed by recreating the volume (dev data was seeded fixtures only): ``` docker compose -f docker/dev/docker-compose.yml down docker volume rm dev_db_data docker compose -f docker/dev/docker-compose.yml up -d docker exec ffr_dev_app php artisan migrate --seed ``` Verified: MariaDB 11.8.8, database `ffr_dev`, 27 migrations applied, `ffr@%` present. Both `container_tinker` and `container_db_read` work again. Worth knowing for this ticket: a test database created the same way will have the same property — **changing credentials in compose does nothing to an existing volume.** If the MariaDB test database is provisioned as a separate volume, that is a trap to document rather than rediscover.
myrmidex added a new dependency 2026-09-26 18:06:49 +02:00
Author
Owner

Obstacle to solve before starting: the CI image has no pdo_mysql yet

#148 added pdo_mysql to docker/build/Dockerfile.ci and bumped the image version to php8.3-4, but ci.yml still pins php8.3-3, which lacks the driver. That was deliberate — pinning CI to an image that does not exist yet would have failed #148's own PR.

php8.3-4 gets built only when images.yml runs, and images.yml triggers on push to main, which happens when release/v1.5.0 merges at release time. So the image this ticket needs will not exist until the end of the milestone.

Consequence: the moment phpunit.xml points at MariaDB, CI fails with a missing-driver error, and the fix is not available yet. Local work against the dev database is unaffected.

Options, none yet chosen:

  1. Build and push fedi-feed-router-ci:php8.3-4 by hand, once. Gets CI working immediately. Ironic given #148 exists to stop hand-building images, but defensible as a one-off to break the cycle.
  2. Accept red CI on this ticket until release time. Local gates stay green and prove the work; CI goes green when images.yml runs on the main merge. Honest but leaves the branch red for a while, and CI cannot be trusted as a gate for #170 either.
  3. Trigger images.yml on main early — e.g. merge #148's image changes to main ahead of the rest of the milestone. Deviates from the release flow.
  4. Dispatch images.yml manually if Forgejo exposes the workflow_dispatch button. Cleanest if available; unverified whether it is.

Also note ci.yml needs a MariaDB service container regardless — that is separate from the image and part of this ticket's own work.

  • #148 — added pdo_mysql and the php8.3-4 tag; its own verification is also waiting on images.yml running on main
## Obstacle to solve before starting: the CI image has no `pdo_mysql` yet #148 added `pdo_mysql` to `docker/build/Dockerfile.ci` and bumped the image version to `php8.3-4`, but **`ci.yml` still pins `php8.3-3`**, which lacks the driver. That was deliberate — pinning CI to an image that does not exist yet would have failed #148's own PR. `php8.3-4` gets built only when `images.yml` runs, and `images.yml` triggers on **push to `main`**, which happens when `release/v1.5.0` merges at release time. So the image this ticket needs will not exist until the end of the milestone. Consequence: the moment `phpunit.xml` points at MariaDB, CI fails with a missing-driver error, and the fix is not available yet. Local work against the dev database is unaffected. Options, none yet chosen: 1. **Build and push `fedi-feed-router-ci:php8.3-4` by hand, once.** Gets CI working immediately. Ironic given #148 exists to stop hand-building images, but defensible as a one-off to break the cycle. 2. **Accept red CI on this ticket until release time.** Local gates stay green and prove the work; CI goes green when `images.yml` runs on the `main` merge. Honest but leaves the branch red for a while, and CI cannot be trusted as a gate for #170 either. 3. **Trigger `images.yml` on `main` early** — e.g. merge #148's image changes to `main` ahead of the rest of the milestone. Deviates from the release flow. 4. **Dispatch `images.yml` manually** if Forgejo exposes the `workflow_dispatch` button. Cleanest if available; unverified whether it is. Also note `ci.yml` needs a MariaDB **service container** regardless — that is separate from the image and part of this ticket's own work. ### Related - #148 — added `pdo_mysql` and the `php8.3-4` tag; its own verification is also waiting on `images.yml` running on `main`
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference: lvl0/fedi-feed-router#169
No description provided.