diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml index b1e3d78e9..b90794028 100644 --- a/.gitea/workflows/docker-build.yml +++ b/.gitea/workflows/docker-build.yml @@ -408,25 +408,15 @@ jobs: done echo "::endgroup::" - # The two checks above only ever apply migrations to a fresh EMPTY database, so they execute no - # rows of any data-migration logic. The #491 LibraryFolder dedupe DELETES rows irreversibly and its - # correctness depends on MySql string-comparison semantics that SQLite does not share — two - # MySql-only collation defects (a case-insensitive grouping, then a PAD SPACE one) escaped exactly - # this gate and were caught only by hand-run servers. LibraryFolderDedupeMigrationTests is - # parameterized over both providers from ONE fixture, so running it here against the live service - # closes that gap and keeps the two providers from silently diverging. - # ETV_REQUIRE_MYSQL_TESTS turns "no MySql reachable" from a skip into a failure, so this can never - # quietly pass having connected to nothing. It reuses the `mysql` service already declared by this - # job, on its own per-test database, so it does not disturb the fresh-DB apply above. - - name: MySql — data-migration fixture (#491 dedupe, same fixture as SQLite) - if: steps.detect.outputs.docs_only != 'true' && steps.revalidate.outputs.skip != 'true' - env: - ETV_TEST_MYSQL_CONNECTION: "Server=mysql;Port=3306;Uid=root;Pwd=ersatztv;DefaultCommandTimeout=300;" - ETV_REQUIRE_MYSQL_TESTS: "1" - run: | - set -euo pipefail - dotnet test ErsatzTV.Tests/ErsatzTV.Tests.csproj --no-build --configuration Release \ - --filter "FullyQualifiedName~LibraryFolderDedupeMigrationTests" + # NOTE (ersatztv#491 -> #627): running the LibraryFolder dedupe fixture against the live `mysql` + # service was implemented here and then REMOVED. The coverage gap it closes is real — the two + # checks above only ever apply migrations to a fresh EMPTY database, so they execute no rows of any + # data-migration logic, and two MySql-only collation defects escaped exactly this gate. But the + # fixture proved non-deterministic in CI across three attempts (stale pooled session after a drop, + # then lost isolation from a shared database name, then a connect-before-create), and an + # intermittently-red gate is worse than none: it trains everyone to re-run instead of read, which is + # how the original defects escaped. The fixture itself is retained and is opt-in via + # ETV_TEST_MYSQL_CONNECTION (skipped, visibly, without it). Re-arming it here is tracked by #627. functional-e2e: name: Functional E2E (curl + UI contracts) diff --git a/docs/decisions/records/scan/libraryfolder-unique-identity.md b/docs/decisions/records/scan/libraryfolder-unique-identity.md index b273af931..25b7c8d05 100644 --- a/docs/decisions/records/scan/libraryfolder-unique-identity.md +++ b/docs/decisions/records/scan/libraryfolder-unique-identity.md @@ -182,18 +182,23 @@ parameterized body makes "the two providers agree" a checked property. Assertion `WHERE Path = '…'`, which is itself collation-dependent and would silently mean something different per provider; rows are read once and compared ordinally in memory. -This closes a **real** hole rather than a theoretical one. CI's `migrations` job only ever applied -migrations to a fresh **empty** database, so it executed zero rows of dedupe logic — both MySQL-only -collation defects sailed through it and were caught only by hand-run containers that vanish with the -session. A new step in that same job (reusing its existing `mysql:8.4` service, on a per-test database -of its own) runs the fixture against the live server. It is red when the collation is wrong: restoring -`COLLATE utf8mb4_bin` makes the MySQL half fail with survivors `[1,4,5,6,7,9]` — row 10, the -trailing-space sibling, deleted — while the SQLite half stays green. +The MySQL half of this migration has **no automated coverage**, and that is a known, tracked gap +(#627) rather than an oversight. CI's `migrations` job only ever applies migrations to a fresh +**empty** database, so it executes zero rows of dedupe logic — both MySQL-only collation defects +sailed through it and were caught only by hand-run containers that vanish with the session. A CI step +running the fixture against the job's existing `mysql:8.4` service was built and then **removed**: it +was non-deterministic across three attempts (a stale pooled session surviving `DROP DATABASE`, then +lost isolation from a shared database name, then a connect-before-create), and an intermittently-red +gate is worse than none — it trains everyone to re-run instead of read, which is how the original +defects escaped in the first place. -The gate cannot silently no-op. MySQL needs `ETV_TEST_MYSQL_CONNECTION`; without it the fixture -**ignores** (a visible skip, so local runs need no MySQL), but CI also sets `ETV_REQUIRE_MYSQL_TESTS=1`, -which turns that skip into a hard failure — "connected to nothing and passed" is precisely the failure -mode being fixed, so it must not be reachable on the CI path. +What survives is the fixture itself: `LibraryFolderDedupeMigrationTests` is parameterized over both +providers from **one shared body**, so the expectations cannot silently diverge, and the MySQL half is +opt-in via `ETV_TEST_MYSQL_CONNECTION` (a visible skip without it) with `ETV_REQUIRE_MYSQL_TESTS=1` +turning that skip into a hard failure for whoever re-arms it. It is red when the collation is wrong: +restoring `COLLATE utf8mb4_bin` makes the MySQL half fail with survivors `[1,4,5,6,7,9]` — row 10, the +trailing-space sibling, deleted — while the SQLite half stays green. Run it by hand against a real +server before touching this migration. **A MySQL test fixture needs a fresh database per test AND an explicitly cleared connection pool — the two are independent, and fixing only one of them breaks the other way.** This fixture failed in CI twice,