Repository navigation
Close test gaps for transaction-scoped CIPHERSTASH settings - #487
Conversation
872203f to
af2b45f
Compare
af2b45f to
09d7c10
Compare
09d7c10 to
7bf6b86
Compare
cipherstash-agent
left a comment
There was a problem hiding this comment.
Review attestation — automated review by Claude Code at head 7bf6b86: /code-review medium, then company-skills:cross-check-review-findings (from cipherstash/skills) against the PR's existing feedback.
Result: no critical or blocking issues. No new findings survived verification.
- Scope: tests only (
settings_rollback_tests.rs,disable_mapping.rs). No production code changed. - Checked: the
Client::pipeline/run/replyharness (reply ordering, the post-failure skip of queued replies, reading replies only whensettings_in_flight), the rollback andROLLBACK TOcases, the twoDISCARD ALLpipeline cases, and the new keyset assertions in the integration test. - Existing feedback: none on this PR (no reviews, threads or comments), so there was nothing to de-duplicate against.
- Not verified here: CI was still in progress at review time. The
Test (PostgreSQL 14–17)jobs run the unit tests, and the integration test needs ZeroKMS credentials, as the PR description says. Treat those results as the gate.
Generated by Claude Code
freshtonic
left a comment
There was a problem hiding this comment.
Reviewed head 7bf6b86d. This PR adds tests only and changes no production code. I found nothing that must change. The tests catch the bugs they are written for, and the DISCARD ALL analysis agrees with the code in settings.rs.
Nits
Client::pipelinedoc: "Like the driver". In a test of client behaviour, "the driver" reads as the client driver, for example tokio-postgres, which does not wait for replies. What the harness copies is the drain in Proxy'spostgresql/driver.rs:214-264: after an Execute (or a Query or Sync) that leaves a settings change in flight, Proxy reads the backend until the outcome settles, and then it accepts more frontend messages. Write "Like Proxy'sdriver.rs", so that no one "fixes" the harness to send the whole batch first.discard_all_rejected_later_in_a_pipeline_keeps_settingspassed under all my mutations. This is expected, because the test guards against a reset applied before PostgreSQL confirms the DISCARD, and none of my mutations did that. It is useful as a regression guard. I note it only so that no one counts it in the mutation claim.
What I checked and found correct
- Mutations in
settings.rs. For each change below, at least one of the new tests failed:- With no
restore_transaction()onROLLBACK,rolled_back_keyset_name_reset…,rolled_back_extended_protocol…andpipelined_batch…fail. - When
ROLLBACK TOdoes not restore the keyset, the same three tests fail. - When
DiscardAlldoes nothing,discard_all_that_opens_a_pipeline…fails. - With no restore after a failed implicit transaction (
ready_for_query),pipelined_batch…anddiscard_all_that_opens_a_pipeline…fail.
- With no
- The
DISCARD ALLcase.reset()clearstransaction_start, so the laterSET … 'after'takes its snapshot from the defaults, and the failure restores the defaults. This agrees with the PostgreSQL behaviour in the PR description: aDISCARD ALLthat opens the batch is committed, and only the statements after it are rolled back. - The guard in
rollback_restores_original_keyset. Theassert_ne!before the rollback proves that each change set really changed a setting. Without it, a rollback test could pass on a no-op. - The skipped messages in the harness. After a rejected Execute, Proxy still sees the rest of the batch but gets no replies for it. This is what PostgreSQL does.
- The integration test.
disable_mappingruns intest:integration:without_multitenant, whereCS_DEFAULT_KEYSET_IDis set, so the refusal ofSET CIPHERSTASH.KEYSET_NAMEand the keyset check are valid there. The INSERT after each rolled-back mapping change fails if mapping stays disabled, because Proxy then does not encrypt the parameter and the write is refused. The test reads the stored ciphertext directly from PostgreSQL, so it checks the keyset that was really used.
What I ran
cargo test -p cipherstash-proxy --lib settings_rollback_tests: 16 of 16 passed.- The four mutations above. I reverted all changes after the runs.
CI
All 9 checks pass, including the integration jobs on PostgreSQL 14 to 17 and the encrypted burn-in.
|
Thanks @freshtonic. I took the doc nit in 52d71aa: the On Generated by Claude Code |
Add message-level tests for rolled-back RESET and SET ... TO DEFAULT of KEYSET_NAME, extended-protocol SET and reset followed by ROLLBACK and ROLLBACK TO, and pipelined batches that change settings and are then rolled back. The pipelined batch test first checks that a batch without a rollback changes the keyset and mapping, so it cannot pass if Proxy ignored them. PostgreSQL 14 to 17 accept DISCARD ALL only as the first Execute of an extended-protocol batch and commit it at once, so a later failure in the batch keeps the defaults. Tests pin that Proxy does the same, and that a DISCARD ALL PostgreSQL rejects later in a batch keeps the settings. The rolled-back mapping integration test now asserts that Proxy runs with a default keyset, and checks the keyset of an encrypted write right after a rolled-back mapping SET. If the rollback were not applied, mapping would stay disabled and the write would be refused.
52d71aa to
2fd4270
Compare
Requested by Toby
Adds tests for how
CIPHERSTASH.*settings behave under rollback, covering cases nothing tested yet.Tests only: every new test passed against the existing behaviour, and breaking rollback,
ROLLBACK TOor theDISCARD ALLreset insettings.rsmakes them fail.What's covered
RESET CIPHERSTASH.KEYSET_NAMEandSET CIPHERSTASH.KEYSET_NAME TO DEFAULT, rolled back fully or to a savepoint, through both the simple and the extended protocol, restore the keyset the next encrypted INSERT uses.ROLLBACKand withROLLBACK TO SAVEPOINT, for keyset, mapping,SET LOCAL,RESET ALLand a mixed set of changes.Client::pipelineharness helper sends a whole batch before PostgreSQL replies. Like the driver, it only reads replies when an Execute leaves a settings change in flight. Covered:BEGIN; SET; SET; ROLLBACK, the same withROLLBACK TO, and an implicit transaction aborted by a later failing statement. A control batch without a rollback checks that the changes do take effect.default_keyset_id.rolled_back_mapping_changes_are_undonenow asserts that Proxy refusesSET CIPHERSTASH.KEYSET_NAMEbecause a default keyset is configured. After each rolled-back mapping SET, it writes a row and reads the stored ciphertext straight from PostgreSQL to check that it was encrypted underCS_DEFAULT_KEYSET_ID.DISCARD ALL in an implicit transaction
I probed PostgreSQL 14, 15, 16 and 17 (the CI matrix) directly over the wire protocol:
DISCARD ALLis accepted only as the first Execute of an extended-protocol batch, and PostgreSQL commits it at once. A later failure in the batch rolls back only what follows it, sowork_memand a customcipherstash.*GUC stay at their defaults.DISCARD ALL cannot be executed within a pipeline.So Proxy keeping the defaults after such a failure is correct, not a gap. Two unit tests pin both cases.
Not run here
The integration test compiles and passes clippy, but it needs ZeroKMS credentials, so CI is its first run.
Acknowledgment
By submitting this pull request, I confirm that CipherStash can use, modify, copy, and redistribute this contribution, under the terms of CipherStash's choice.
🤖 Generated with Claude Code