test(491): run the dedupe fixture against MySql in CI; correct the collation claim

The dedupe DML had zero automated coverage on MySql: the migrations job only
applies migrations to a fresh EMPTY database, so no dedupe row ever executed
there. Two MySql-only collation defects escaped that gate in this session and
were caught only by hand-run containers.

Parameterize LibraryFolderDedupeMigrationTests over both providers from ONE
fixture body - same seeded rows, same expected survivors - rather than adding a
MySql-only copy that would drift and recreate the gap. Assertions no longer use
WHERE Path = '...', which is itself collation-dependent and would quietly mean
something different per provider; rows are read once and compared ordinally in
memory. A new step in the existing migrations job runs it against that job's
mysql:8.4 service, on a per-test database of its own.

Proven red when the collation is wrong: restoring COLLATE utf8mb4_bin fails the
MySql half with survivors [1,4,5,6,7,9] - the trailing-space sibling deleted -
while SQLite stays green. Proven non-skippable: without
ETV_TEST_MYSQL_CONNECTION the fixture ignores visibly, and with
ETV_REQUIRE_MYSQL_TESTS=1 (which CI sets) that skip becomes a hard failure, so
it cannot pass having connected to nothing. Local runs need no MySql.

Also correct an overstated comment. The schema pins only the utf8mb4 charset,
never a collation, so the effective comparison is the server default: always
case-insensitive, but PAD SPACE only on utf8mb4_general_ci - 8.4's default
utf8mb4_0900_ai_ci is NO PAD, verified on the real column. The migration bug was
independent of that because the old code applied an EXPLICIT utf8mb4_bin, which
is PAD SPACE everywhere; the runtime simply tolerates both.

Refs #488 #308
fix #491
This commit is contained in:
2026-07-25 21:13:31 +02:00
parent 48d41b9235
commit 83cd36e0de
4 changed files with 309 additions and 118 deletions
+20
View File
@@ -408,6 +408,26 @@ 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"
functional-e2e:
name: Functional E2E (curl + UI contracts)
runs-on: ubuntu-latest