Repository navigation
[obvious-fleet] test(backend): cover the device result boundary through the FastAPI app - #256
Conversation
Seed the route with synthetic stored artifacts derived from the committed result fixtures and assert the exact wire envelope: not-ready polling, the ready key set, reviewer-only verdictUrl, recursive hidden-reference stripping, preview presence, 503 on malformed or missing blobs, drifted payloads served as stored, and failed/expired terminal captures.
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Independent review (Obvious, Autobuild + Max) Verdict: ready — good to move out of draft. The PR is exactly what it claims: one new test file, nothing else, driving the real route through the real app with seeded storage. Every number in the body reproduced here, and three mutation checks show the tests bite. Two low, non-blocking findings and two notes below. Findings (ranked by severity)1. Low — the verdict-leak canary can never fire. 2. Low — Minor: What I verifiedAll runs at the PR head
One repo-level observation (not this PR's defect): What I couldn't verify
|
|
Note Review skipped Codemetrics is installed on this repository, but it hasn't been added to your team yet. To enable AI code reviews for this repo, go to your dashboard and add BaseScanning to your team's repositories. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
[Low impact] Adds test coverage for the device result API boundary. The PR appears safe to merge, with two non-blocking improvements to the new tests.
|
| _seed( | ||
| client, | ||
| capture_id, | ||
| {"result/result.json": result_key, "result/result.internal.json": internal_key}, |
There was a problem hiding this comment.
Internal-file check goes untested
This test never reaches the check it names. The route reads verdicts/verdict.C.json, but the artifact map only supplies result/result.internal.json, so verdict_key is absent. The test would pass even if the internal-file check were removed. Map verdicts/verdict.C.json to internal_key so the assertion tests that check.
| {"result/result.json": result_key, "result/result.internal.json": internal_key}, | |
| {"result/result.json": result_key, "verdicts/verdict.C.json": internal_key}, |
Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/server/api/tests/test_result_boundary.py
Line: 293
Comment:
**Internal-file check goes untested**
This test never reaches the check it names. The route reads `verdicts/verdict.C.json`, but the artifact map only supplies `result/result.internal.json`, so `verdict_key` is absent. The test would pass even if the internal-file check were removed. Map `verdicts/verdict.C.json` to `internal_key` so the assertion tests that check.
```suggestion
{"result/result.json": result_key, "verdicts/verdict.C.json": internal_key},
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| assert payload["outcome"]["kind"] == "eligible" | ||
| assert payload["outcome"]["recommendedPlacement"]["wallId"] == "w1" |
There was a problem hiding this comment.
The ready-result test checks only wallId inside recommendedPlacement. Dropping or changing startSM, confidence, or the stored box dimensions would still pass, even though the test seeds those values to check the stored-result boundary. Compare the full returned outcome with _outcome_for("pass") to protect the values the route should preserve.
| assert payload["outcome"]["kind"] == "eligible" | |
| assert payload["outcome"]["recommendedPlacement"]["wallId"] == "w1" | |
| assert payload["outcome"] == _outcome_for("pass") |
Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/server/api/tests/test_result_boundary.py
Line: 233-234
Comment:
**Placement values go unchecked**
The ready-result test checks only `wallId` inside `recommendedPlacement`. Dropping or changing `startSM`, `confidence`, or the stored box dimensions would still pass, even though the test seeds those values to check the stored-result boundary. Compare the full returned `outcome` with `_outcome_for("pass")` to protect the values the route should preserve.
```suggestion
assert payload["outcome"] == _outcome_for("pass")
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
fleet-packet: burst5-20261015-bs-result-contract
What changed and why
backend/server/api/tests/test_result_boundary.pyis new and nothing else changes. It tests the result boundary the capture client consumes (ios/HouseScanKit/Sources/HouseScanKit/Upload/CaptureAPI.swift+CaptureResult.swift) through the FastAPI app: the route inbackend/server/api/routes/result.pyreading synthetic stored artifacts whose content derives from committed fixtures. It does not change the iOS contract, does not add a placement endpoint, and does not touch the route or contracts — the open PRs #249 (reviewer page template, store-sync script) and #251 (orchestrator run records, coordinator) touch disjoint files.Schema-only validation vs. the route and stored-artifact boundary. Validating
ResultResponseagainst a payload in a unit test proves the contract type is self-consistent, but the client consumes neither the contract class nor the stored bytes alone — it consumes what the route returns after it re-reads state, mergesstage_artifacts, overwritesstatus/runId, signs URLs, masks the verdict, strips hidden references, and swallows validation failure. Testing through the app with seeded storage proves the composition of all those steps plus the storage failure paths (malformed bytes, missing blob), which a schema test cannot reach. The trade-off is coupling to route internals (artifact key names, the not-ready envelope's null padding), which is exactly the behavior under test; the suite is one file with shared seed helpers, so that coupling is contained. Both are kept: schema validation still runs inside the route (logged asresult.schema_drift), and one test pins that a drifted payload is served as stored rather than rewritten or rejected.Stored artifacts derive from committed fixtures: the verdict blobs are
server/result/tests/data/{synthetic,reject}/verdicts/verdict.C.jsoncopied verbatim, the preview isserver/result/tests/data/synthetic/points.ply, and the drift case is the committed schema-negativeserver/contracts/tests/negatives/result-eligible-without-placement.json. The deviceresult.jsonmaps the committed verdict's site decision onto the wire outcome (pass→eligible with a placement, reject→masked as manual_review), matching whatserver.result.composeproduces. Storage is the test settings' volume backend; state is memory.How I verified it
Worked in a fresh clone at
~/work/burst5-20261015-bs-result-contracton the BaseScanning repo sandbox (branchobv/burst5-20261015-bs-result-contractofffeat/port-house-scanning-server), commit60d0cf14.uv run --frozen --extra api pytest server/api/tests/test_result_boundary.py→ 13 passeduv run --frozen --extra api pytest server/api/tests→ 136 passed (no regressions; the pre-existing result tests intest_seams.py,test_more.py,test_auth_none.pystill pass)uv run --frozen --extra api pytest ios-kit/tests→ 18 passeduv run --frozen ruff check .→ All checks passeduv run --frozen ruff format --check .→ 460 files already formatteduv run --frozen lint-imports→ Contracts: 5 kept, 0 brokenResult states proven:
runId: null,status: "uploading", emptyviewsNeeded/memberActions, nulloutcome; with a pending run the same body carries therunIdand the capture status, stable across repeated polls.verdictUrl/previewUrl/reviewUrl; the route overwrites the storedrunId/statusmarkers with the live record's; the eligible outcome and itsrecommendedPlacementround-trip byte-for-byte.verdictUrl(/v1/uploads/…); the owner and every device principal getverdictUrl: null; another device's token gets 403forbidden.verdicts/orresult.internal.jsonbecomes null at any nesting depth (site reasons, criteria fields, meter evidence); the committed verdict's contents never appear in a device body, and an artifact namedresult.internal.jsonis masked even from a reviewer.result/preview.plyartifact both device and reviewer get a signed URL; without one the key is absent (not null).errors[0].code == "storage_unavailable",pointer: "/"andRetry-After: 5.status: "failed"/"expired"withoutcome: null, emptyviewsNeededand areviewUrl, the shapeCaptureAPI.statusesWithoutOutcometreats as final.What is not verified
swift test): this sandbox has no Swift toolchain (which swift→ not found). No iOS code changed, and the envelope assertions above were checked against the decoder's contract inCaptureResult.swift(optional fields, unknown-status tolerance,statusesWithoutOutcome) by reading it, not by executing it. TheiosMake target and Xcode builds did not run for the same reason.result.schema_drift) is exercised by the drift test but the log line itself is not asserted; the served body is.Assumptions
backend/server/result/tests/data/andbackend/server/contracts/tests/negatives/; the synthetic deviceresult.jsonin each test is built in-code from those fixtures rather than committed as a new binary blob, since the API tests' storage is per-test temporary volume state._outcome_formirrorsserver.result.compose's documented masking; the test asserts the route preserves whatever the composer stored, and the mask itself is covered by the result package's own tests.ENVELOPE_KEYSreflects the 13 documentedResultResponsefields; the tests assert the not-ready body carries all 13 (null-padded) and the ready body carries only stored-plus-composed keys, which is the observed and client-compatible behavior (CaptureResult.Responsedecodes absent optionals as nil).result.jsonfrom an actual capture run (coverage summaries, scale, meter blocks with real reads, ARKit-world placements) — no production capture data exists in this public repository, so the tests exercise synthetic artifacts shaped from committed verdict fixtures instead; likewise real signed-URL reachability (GCS/R2 backends) is unexercised because the test settings use volume storage, and the amendment/re-run overlay ofresult.result.jsonacross multiple runs for one capture awaits a production-shaped multi-run case.