fix: harden the emit idempotency cache and surface silent truncations#37
Conversation
The emitted.json guard protecting against double electronic emission had three silent failure modes, all fixed: - A corrupt or unreadable cache was ignored and read as empty, which would re-emit already-stamped invoices. emit now aborts with a remediation hint. - The cache was persisted once after all batches; a crash mid-run lost every stamped id. It is now saved after each successful batch, and emission stops if the save fails (listing the affected ids). - The cache file was written in place; a crash mid-write could tear it. Writes now go through temp-file + rename. Also addressed from the same review: - output: auto-detected column sets capped at 10 now print a stderr note with the dropped count and a --columns/--output json hint (stdout stays clean for piping). - generic: list filters dropped over a flag collision or empty definition are recorded and asserted empty by a registry test, so a bad resource definition fails CI instead of silently losing the filter. - api: ID decoding tries Int64 before the float64 normalization path, so ids above 2^53 keep full precision. - api: AsAPIError delegates to errors.As, which also walks errors.Join trees the manual unwrap loop missed. - api: removed the unreachable lastErr branch after the retry loop. - api: ListAll's page-cap warning now includes a remediation hint.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
… save atomic Found while hardening the test suite (the new fuzz properties surfaced the first two within seconds of fuzzing): - Money accepted the string "NaN"/"Inf" via ParseFloat; the resulting value poisons any later json.Marshal of the containing document (JSON cannot represent NaN). Both decoders now reject non-finite values. - Int round-tripped bare and quoted integers through float64, losing precision above 2^53 — same bug just fixed in ID; now tries int64 first. - import dry-run and create --draft ignored json.Marshal errors, which could print garbage or send a stale body; both now fail the row/command. - auth status ignored the config.Load error and would dereference a nil config if the file were unreadable; it now propagates the error. - config.Save writes via temp + rename (same crash-safety treatment as emitted.json); country auto-detect's best-effort save is now documented as deliberate.
…, negative inputs The suite was overwhelmingly happy-path (26 error assertions across 241 test functions; integration fake servers only ever returned 200/404). This adds the failure-path coverage a recent review showed was missing: - Fuzzers now assert value-level properties instead of "doesn't panic": integer literals survive ID/Int decoding verbatim, Money matches ParseFloat exactly, StringOrSlice keeps single strings intact. The Money property found the NaN bug within seconds; its counterexample is committed as a permanent corpus regression. - commands: partial import fails loudly with per-row errors and non-zero exit; emit keeps failed batches out of the idempotency cache; a cache save failure stops emission before the next batch; delete without confirmation never sends the DELETE; auth login never persists the token into config.yaml. - internal/api: malformed JSON on 200, HTML error bodies, context cancellation mid-backoff, the 429 throttle/restore cycle, HTTP-date Retry-After fallback, missing rate-limit headers, the subscriptions list wrapper, and empty wrapper arrays. - internal/config: corrupt YAML is an error, save leaves no temp files behind. - internal/auth: a locked keyring backend degrades to env/config fallback. - internal/output: key/value column filtering and ordering; pinned the drop-non-map-rows normalization so changing it is a conscious decision. - CONTRIBUTING: resource tests should cover what is unique to the resource, not re-test generic CRUD; documented the failure-path and fuzzing expectations. TestCurrentProfileName now pins flagProfile: runRoot resets globals before each run, not after, so a prior test's --profile leaked into direct helper calls depending on file order.
|
Second pass on this branch: implemented the test-effectiveness improvements that came out of the suite scan, plus the production bugs they immediately surfaced. Production fixes (
|
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. Five independent review passes (CLAUDE.md adherence, shallow bug scan, git-history context, prior-PR feedback, in-code comment constraints) produced 9 candidate findings; all scored below the confidence threshold after verification. Highest-scoring (50/100, informational): |
Summary
Fixes every actionable finding from a full code review of the project, in one pass. The headline fix is the
invoices emitidempotency cache, where silent failures could lead to double electronic (fiscal) emission.Emission cache (
commands/emit.go) — the serious oneloadEmitCachepreviously did_ = json.Unmarshaland returned an empty map, silently dropping the guard against re-emission.emitnow aborts with the cache path and a remediation hint. A missing file is still a normal fresh start.os.WriteFilecould tearemitted.jsonon a crash mid-write — which, combined with the first bug, produced exactly the empty-guard scenario.Silent truncations
--columns/--output jsonhint. Stdout stays clean for piping. The cap is now the named constantmaxAutoColumns.ListAllpage cap: the warning was already user-visible at the default log level (the review misread this one); it now also carries a remediation hint.Edge cases
generic.gofilter collisions: list filters dropped over a flag collision or empty definition are recorded indroppedListFiltersand asserted empty by a new registry test — a bad resource definition now fails CI instead of silently losing the filter, while keeping the documented never-panic-at-init behavior.api.IDprecision: decoding triesInt64before the float64 normalization path, so ids above 2⁵³ keep full precision.AsAPIErrornow delegates toerrors.As, which also walkserrors.Joinmulti-error trees the manual unwrap loop missed (test added).lastErrbranch after the loop inclient.go; the remaining return is documented as a guard against a negativeMaxRetries.Test plan
errors.Joinunwrap, column-cap dropped count + explicit-columns-never-capped, registry collision assertion.make checkgreen: gofmt -s, go vet, golangci-lint (0 issues), full test suite.