Skip to content

test(query): unit-test MySqlReadOnlySessionEnforcer#118

Merged
daniel3303 merged 1 commit into
mainfrom
test/mysql-enforcer
May 27, 2026
Merged

test(query): unit-test MySqlReadOnlySessionEnforcer#118
daniel3303 merged 1 commit into
mainfrom
test/mysql-enforcer

Conversation

@daniel3303
Copy link
Copy Markdown
Owner

Summary

Fourth in the 5-PR coverage-recovery series (after #115, #116, #117). MySQL has real session-level read-only enforcement (PR #109) but no MySQL Testcontainer in the repo, so every line of the enforcer was unreachable — that accounted for ~1.8pp of the recent codecov drop.

Two halves of the contract get pinned:

(a) Apply / Reset send the expected commands. A minimal RecordingDbConnection / RecordingDbCommand pair (a DbConnection subclass with all the abstract members stubbed) captures every command text the enforcer's Execute helper sends. No real provider needed, no fragile NSubstitute dance — just a directly-callable test double.

(b) IsReadOnlyViolation recognises MySQL's ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION via reflection. The fake exception class mimics MySqlException by exposing a public int Number property, so the reflection-by-name lookup in the enforcer executes exactly as it would in production. Tests cover:

Input Expected
DbException subclass with Number == 1792 true
DbException subclass with Number ∈ {1, 1062, 2006} false
DbException subclass without a Number property false (reflection lookup returns null)
InvalidOperationException (not a DbException) false

Code changes

  • tests/Equibles.AgentQL.UnitTests/Query/ReadOnly/MySqlReadOnlySessionEnforcerTests.cs (new) — 9 test cases plus the minimal DbConnection/DbCommand test doubles.

9 / 9 pass. No source changes — the enforcer is reachable via the InternalsVisibleTo added in PR #115.

MySQL has real session-level read-only enforcement (PR #109), but the
repo has no MySQL Testcontainer so every line of the enforcer was
unreachable — that accounted for ~1.8pp of the recent codecov drop.

Two halves of the contract get pinned:

Apply / Reset send the expected SET SESSION TRANSACTION READ
{ONLY,WRITE} commands. A minimal RecordingDbConnection / RecordingDb-
Command pair captures every command text that the enforcer's Execute
helper sends — no real provider needed.

IsReadOnlyViolation recognises MySQL's
ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION via reflection on the runtime
exception's Number property. The fake mimics MySqlException by exposing
a public int Number, so the reflection path executes exactly as in
production. Tests cover: Number == 1792 → true, Number in {1, 1062,
2006} → false, DbException without a Number property → false (the
property lookup returns null and the predicate falls through), non-
DbException → false.

No source changes — the enforcer is reached via the InternalsVisibleTo
added in PR #115.
@daniel3303 daniel3303 merged commit 9f40c9c into main May 27, 2026
4 checks passed
daniel3303 added a commit that referenced this pull request May 27, 2026
…119)

Oracle's SET TRANSACTION READ ONLY is per-transaction and an embedded
COMMIT escapes it (see PR #110), so the enforcer is intentionally a
no-op that only exposes a Limitation message — same shape as the SQL
Server enforcer. With no Oracle Testcontainer in the repo every line of
the enforcer was unreachable, accounting for ~1.6pp of the recent
codecov drop.

Pins the same contract as the SQL Server tests with one Oracle-specific
twist: the Limitation message must explain why per-transaction read-
only is the wrong layer of defense (the SQL Server message has no
equivalent caveat to assert).

9 cases: Limitation non-null and mentions Oracle, explains the
per-transaction gap (contains "transaction" / "COMMIT" / "session-
level"), recommends a concrete remediation (SELECT, READ ONLY, or
ALTER DATABASE), Apply and Reset send no commands (NSubstitute
DidNotReceive on CreateCommand), IsReadOnlyViolation returns false for
every shape of exception, Instance is a singleton.

Closes the 5-PR coverage-recovery series (#115, #116, #117, #118, this
PR). Combined recovery target ~7pp.

No source changes — the enforcer is reached via the InternalsVisibleTo
added in PR #115.
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