feat(opdb) --dry-run mode for OPDB sync via OpdbSyncMode enum#71
Merged
Conversation
Phase 2 § Scope item 4 code half (Wave 2). The operational hand-off
half (actual run against deployed Cosmos with env-var setup, dry-run
pass, real run, Cosmos state verification) closes item 4 fully after
this PR merges.
What ships:
- IOpdbSyncService.SyncAsync now takes an OpdbSyncMode enum
{ Apply, DryRun } in place of the previous parameterless signature.
/local-review flagged a `bool dryRun` first draft as a "boolean
trap" — opaque at call sites, leaves no room for a future
Validate/Schema-only mode without another breaking change. Enum
is self-documenting at every call site (sync.SyncAsync(
OpdbSyncMode.DryRun, ct) reads as English).
- OpdbSyncService skips _machines.UpsertAsync calls in DryRun mode
but still runs reads (required to distinguish projected-insert
from projected-update counts) and the in-place merge (which
exercises the mapping against real OPDB data and would surface
any DTO-shape regression even before any write).
- CLI handler for --source opdb threads --dry-run through to the
new mode parameter and emits a distinct output line in dry-run
mode: "OPDB sync (DRY RUN — no writes): fetched X, would-insert
Y, would-update Z, skipped W, duration N.Ns".
Local review summary: 0 🔴 / 2 ⚠️ / 8 categories ✅.
⚠️ — Fixed: bool dryRun replaced with OpdbSyncMode enum (per the
review's "boolean trap" finding; the interface change is breaking
this PR anyway, so making it an enum now costs nothing extra and
prevents another breaking change for a future Validate mode).
⚠️ — Fixed: added Assert.Equal("Stranger Things", existing.Title) +
Assert.Equal(NowFixed, existing.LastSeenAt) to the dry-run-existing
test, asserting the merge actually ran in dry-run mode. The XML doc
on OpdbSyncMode.DryRun documents that reads + merge happen even in
dry-run; the test now pins that contract. If a future refactor wraps
the merge in `if (mode != DryRun)`, this test fails — forcing a
re-read of the contract.
Tests: 518 / 518 passing (2 new dry-run tests added; 3 existing
tests updated to pass OpdbSyncMode.Apply explicitly). Build clean,
zero warnings.
After merge — operational hand-off (closes Phase 2 § Scope item 4):
1. Set Cosmos__AccountEndpoint + Cosmos__AccountResourceId +
Opdb__BaseUrl + Opdb__ApiToken env vars (PowerShell, not
Git-Bash, per the friendly-error guard in ArmCosmosProvisioner)
2. Run: dotnet run --project src/PinballWizard.Cli --
--source opdb --dry-run
Confirm the "OPDB sync (DRY RUN — no writes)" summary line
includes a non-zero fetched count and reasonable inserted/updated
projection.
3. Run: dotnet run --project src/PinballWizard.Cli -- --source opdb
Confirm the "OPDB sync" summary line shows actual writes; spot-
check 5 random machines in the Cosmos `machines` container for
full provenance.
4. Capture the dry-run + real-run output in the Phase 2 retrospective.
| MachineJson("GRBN-MQR4P", manufacturer: "Stern Pinball, Inc.", name: "Stranger Things (Pro)", commonName: "Stranger Things"), | ||
| MachineJson("XYZ", manufacturer: "Jersey Jack Pinball", name: "Wonka", commonName: "Wonka"))); | ||
|
|
||
| _repository.GetByOpdbIdAsync("GRBN-MQR4P", "stern", Arg.Any<CancellationToken>()).Returns((Machine?)null); |
| MachineJson("XYZ", manufacturer: "Jersey Jack Pinball", name: "Wonka", commonName: "Wonka"))); | ||
|
|
||
| _repository.GetByOpdbIdAsync("GRBN-MQR4P", "stern", Arg.Any<CancellationToken>()).Returns((Machine?)null); | ||
| _repository.GetByOpdbIdAsync("XYZ", "jjp", Arg.Any<CancellationToken>()).Returns((Machine?)null); |
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
docs/build-spec.mdPhase 2 § Scope item 4 — code half (Wave 2). The operational hand-off half (actual run against deployed Cosmos with env-var setup, dry-run pass, real run, Cosmos state verification) closes item 4 fully after this PR merges.IOpdbSyncService.SyncAsyncnow takes anOpdbSyncModeenum (Apply/DryRun) in place of the previous parameterless signature. InDryRunmode the service:Applymode)IMachineRepository.GetByOpdbIdAsync(required to distinguish projected-insert from projected-update counts)OpdbMachineMapper.MergeOpdbFieldsIntoon existing machines (in-memory; the mutatedexistingis discarded by the GC, but performing the merge confirms the mapping itself doesn't throw on real OPDB data)_machines.UpsertAsynccalls — no Cosmos writesOpdbSyncResultshape; counters represent projected writes in dry-run modeThe CLI handler for
--source opdbthreads the existing--dry-runflag through and emits a distinct output line in dry-run mode ("OPDB sync (DRY RUN — no writes): fetched X, would-insert Y, would-update Z, skipped W, duration N.Ns").Why an enum, not a
bool/local-reviewflagged abool dryRunfirst draft as a "boolean trap" — opaque at call sites (sync.SyncAsync(true, ct)is mystery-flag), and leaves no room for a futureValidate-only mode without another breaking change. The interface change is breaking this PR regardless, so switching to an enum now is the cheapest moment.Test Plan
dotnet test PinballWizard.slnx --nologo→ 518 / 518 passing (was 516; +2 new dry-run tests). The 3 existing OpdbSyncService tests were updated to passOpdbSyncMode.Applyexplicitly.dotnet build PinballWizard.slnx --nologo→ clean, zero warnings.SyncAsync_DryRunExistingMachine_ProjectsUpdateWithoutUpsertingasserts both_repository.DidNotReceive().UpsertAsync(...)ANDexisting.Title == "Stranger Things"(after the call) — the second assertion pins the documented contract that merge runs in dry-run mode, so a future refactor wrapping the merge inif (mode != DryRun)will break the test and force a re-read.Local review
/local-reviewoutcome: 0 🔴 / 2bool dryRunfirst draft replaced withOpdbSyncModeenum (boolean-trap finding).SyncAsync_DryRunExistingMachine_*(test-quality finding).The reviewer's caller-search verification confirmed only
Program.cs:192andOpdbSyncServiceTests.cs(5 sites) callSyncAsync— all updated. No missed call sites.Operational hand-off (closes Phase 2 § Scope item 4)
After this PR merges:
ArmCosmosProvisioner):dotnet run --project src/PinballWizard.Cli -- --source opdb --dry-runConfirm the
OPDB sync (DRY RUN — no writes)summary line shows non-zerofetchedand reasonablewould-insert/would-updatecounts.dotnet run --project src/PinballWizard.Cli -- --source opdbConfirm the
OPDB syncsummary line shows actual writes. Spot-check 5 random machines in the Cosmosmachinescontainer for full provenance.7-item self-audit
IngestionSourceSeeder(no shared sibling for "discover-then-conditionally-write" except the scraper--dry-runflag, which has the same semantic). Confirmed by/local-reviewreviewer's caller search.catch { }— no new catches added.--source opdb --dry-runexercised by manual trace; existing tests cover the orchestrator dispatch path.git log -1 --format='%an <%ae>'→Jim Keeley <94459922+jkeeley2073@users.noreply.github.com>✅Out of Scope
🤖 Generated with Claude Code