Skip to content

Fix #24: Enforce ETag IfMatchEtag preconditions in Replace/Upsert/Delete/Patch#29

Draft
McNultyyy wants to merge 1 commit into
mainfrom
agents/issue-24-review-and-fix
Draft

Fix #24: Enforce ETag IfMatchEtag preconditions in Replace/Upsert/Delete/Patch#29
McNultyyy wants to merge 1 commit into
mainfrom
agents/issue-24-review-and-fix

Conversation

@McNultyyy
Copy link
Copy Markdown
Collaborator

@McNultyyy McNultyyy commented Apr 15, 2026

Summary

Fixes #24 — Hardens CheckIfMatch and CheckIfMatchStream in InMemoryContainer.cs to properly enforce ETag preconditions for Replace, Upsert, Delete, and Patch operations.

Problem

Both CheckIfMatch (typed APIs) and CheckIfMatchStream (stream APIs) silently ignored the IfMatchEtag request option — they never threw 412 Precondition Failed even when the provided ETag didn't match the stored one. This allowed callers to overwrite items without optimistic concurrency being enforced.

Fix

InMemoryContainer.cs

  • CheckIfMatch: Now checks _etags for the key and throws InMemoryCosmosException(412 PreconditionFailed) when the provided ETag doesn't match the stored one.
  • CheckIfMatchStream: Same logic — returns false when the stored ETag doesn't match.
  • Upsert INSERT path preserved: Per the REST API spec, IfMatchEtag only applies to update (PUT/DELETE) operations, not inserts (POST). When Upsert creates a new item, CheckIfMatch correctly passes through — the missing-key case is handled by the Upsert create path itself.
  • Wildcard *: Always passes for all operations (no change to existing behaviour).

Tests (Integration project)

  • ~90 ETag tests in Tests.Integration/ETagTests.cs — Replace, Upsert, Delete, Patch with stale/current/wildcard ETags across typed, stream, and batch APIs.
  • ~65 red-team edge case tests in Tests.Integration/ETagRedTeamEdgeCaseTests.cs — adversarial scenarios:
    • Empty/whitespace/unquoted/special-character ETags
    • Cross-partition key ETag scoping
    • Delete-then-upsert with stale ETags
    • Concurrent replace races
    • Patch with ETag checks
    • Batch mixed success/failure scenarios
    • State export/import ETag preservation

Version Bump

Patch version bumped to 4.0.6 (from 4.0.5).

Test Results

  • 7633 unit tests — all passing (124 skipped)
  • All integration tests — all passing

Rebased on latest main

Incorporates all v4.0 changes (internal classes, FakeCosmosHandler pipeline, test project restructuring, issue #18 CosmosException fix).

@McNultyyy McNultyyy requested a review from lemonlion as a code owner April 15, 2026 18:59
@McNultyyy McNultyyy force-pushed the agents/issue-24-review-and-fix branch from 44332dc to c4d66f5 Compare April 21, 2026 22:07
@McNultyyy McNultyyy force-pushed the agents/issue-24-review-and-fix branch from c4d66f5 to 65f3b41 Compare April 23, 2026 13:31
Move ETagTests.cs and ETagRedTeamEdgeCaseTests.cs from the Unit test
project to Integration, converting from InMemoryContainer (internal API)
to InMemoryCosmos.Create() which goes through the full SDK pipeline.

Key changes:
- Replace InMemoryContainer fields with InMemoryCosmosResult + IDisposable
- Use _cosmos.Container for SDK operations, _cosmos.ExportState()/
  ImportState()/RestoreToPointInTime() for state management
- Inline container creation uses 'using var cosmos = InMemoryCosmos.Create()'
- Adapt 5 edge-case tests for SDK pipeline behavior:
  - Empty/whitespace/unquoted ETags are dropped by HTTP header parser
  - Patch handler does not propagate IfNoneMatch headers

Bump version to 4.0.6.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@McNultyyy McNultyyy force-pushed the agents/issue-24-review-and-fix branch from 65f3b41 to 6b52671 Compare April 23, 2026 14:23
@McNultyyy McNultyyy changed the title Fix #24: Harden ETag IfMatchEtag precondition checks Fix #24: Enforce ETag IfMatchEtag preconditions in Replace/Upsert/Delete/Patch Apr 23, 2026
@McNultyyy McNultyyy marked this pull request as draft April 23, 2026 14:58
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.

Bug: ETag preconditions (IfMatchEtag) not enforced — ReplaceItemAsync and UpsertItemAsync accept any ETag

2 participants