Commit Graph

5 Commits

Author SHA1 Message Date
Nora d81b6ed4fc fix(sp27): /api/claims + /api/remittances total counts the full population
Before this fix, the list endpoints computed `total` via
`len(list(store.iter_*(**common)))`. Both `iter_claims` and
`iter_remittances` default to `limit=100`, so the reported total
silently capped at 100 even when the DB held 60k claims or 835
remits. The frontend rendered a `data.total` of 100 in the KPI
tile and a 100-row table, the page looked complete, and the bug
stayed hidden — exactly the Dashboard silent-failure pattern that
59c3275 (server-aggregated KPIs) was meant to retire.

The Remittances page symptom reported on Jun 29 ('REMITS 100',
empty CLAIM column on 100 rows) was this bug. The empty CLAIM is
a separate matching concern (those remits have `claim_id = NULL`
in the DB); the count fix addresses the population-size lie.

Fix:
* `CycloneStore.count_claims` + `count_remittances` reuse the
  iter's filter pipeline (DB filters + in-memory payer/date
  filters) with an effectively-unbounded limit so the count
  reflects the true DB population.
* `list_claims` + `list_remittances` call the new count
  helpers instead of slicing `iter_*` twice.
* 13 new tests in `test_list_endpoint_counts.py` cover the
  empty/filter/per-dimension/cardinality cases plus the HTTP
  regression (seed 150/120, assert total matches).

Bonus fix:
* `conftest._reset_rate_limit_buckets` walks the middleware
  stack and clears `RateLimitMiddleware._buckets` between
  tests. Without this, the full suite tripped the 300 req/60s
  rate limiter mid-run and 9 later tests (4 of mine, 5
  pre-existing in test_inbox_endpoints / test_payer_summary)
  got 429s. All 1167 tests now pass.

Follow-ups noted but out of scope:
* `/api/admin/audit-log`, `/api/admin/backup/list`,
  `/api/admin/scheduler/processed-files` use the same
  `len(rows)`-after-`.limit(limit)` pattern. Admin-only,
  no `has_more` consumption, so impact is bounded.
* `count_*` could short-circuit to `func.count()` to skip
  the UI-dict build, but iter already calls `q.all()` so
  the win is modest.
2026-06-29 13:22:42 -06:00
Nora e2d4a595a4 fix(api): use request.app.state in inbox endpoints; preserve dict HTTPException detail
- inbox_lanes, inbox_dismiss_candidates, inbox_export_csv: switch
  module-level 'app' to per-request 'request.app' so per-endpoint
  state stays consistent with the test's TestClient target across
  importlib.reload() (test_api.py::test_cors_extra_origins_via_env
  reloads the api module to mutate CORS allow-lists; pre-reload
  endpoints then mutate the wrong app instance).

- _http_exc_handler: when HTTPException.detail is a dict, wrap under
  'detail' so the standard envelope stays stable and callers can
  branch on body['detail']['error'].

- conftest._auto_init_db: re-resolve cyclone.api.app each fixture
  invocation (instead of caching at module load) so the reload pattern
  doesn't leave event_bus set on a stale app.

- test_acks.test_migration_latest_idempotent_on_fresh_db: bump
  user_version assertion from 12 to 14 after auth migration renumber
  (0013 users+sessions, 0014 audit_log.user_id).

Also installs sqlcipher3 + paramiko into the backend venv so the
capability tests can run; both modules were optional deps that
test_db_crypto and test_sftp_paramiko assume are present.

Backend test results: 1008 passed, 9 skipped (gitignored prodfile
fixtures), 0 failed.
2026-06-22 16:55:55 -06:00
Nora 8fc3d9adda test(auth): add test_existing_endpoints_require_auth + per-test AUTH_DISABLED flips
- New test_existing_endpoints_require_auth.py: spot-check that existing
  /api/* endpoints now require auth (gated via Depends(matrix_gate)) when
  AUTH_DISABLED is False. Health remains public.

- conftest.py: flip AUTH_DISABLED=True for the suite so the legacy
  pre-auth tests keep passing without login. Auth tests flip it back
  off via their own autouse fixture (now patched to use monkeypatch
  for cleanup).

Verified: 53 auth tests pass; 222 pre-existing non-auth failures are
unchanged.
2026-06-22 15:59:08 -06:00
Tyler 8bcb23c78d feat(api): parse endpoints pass EventBus into store writes
- api.parse_837 / parse_835: pass request.app.state.event_bus into store.add()
- conftest: autouse fixture wires a fresh EventBus onto app.state for every
  test, since TestClient does not invoke the FastAPI lifespan handler
  unless used as a context manager
- test_pubsub: split get_event_bus coverage into a raises-when-missing test
  and a returns-attached-bus test, both save/restore app.state.event_bus
  around the assertion so the autouse fixture's bus is preserved

Phase 2 complete: db init moved to lifespan, EventBus is the process-wide
publish point, and the two ingest endpoints publish claim_written /
remittance_written / activity_recorded events on every store.add().
2026-06-20 15:51:18 -06:00
Tyler c8bc2c73e6 feat(backend): SQLAlchemy-backed CycloneStore (T9)
Replace the in-memory InMemoryStore with a SQLAlchemy-backed
CycloneStore that persists Batch, Claim, Remittance, Match, and
ActivityEvent rows to a configurable DB engine (sqlite by default,
overridable via CYCLONE_DB_URL).

Public API preserved: add / get_batch / iter_claims / iter_remittances
/ distinct_providers / recent_activity. New T10/T12 stubs:
list_unmatched, manual_match, manual_unmatch.

Backward-compat shims for tests that called ._batches.clear() or
acquired ._lock as a context manager.

Idempotency: add() does a per-row session.get(Claim, c.id) (and
s.get(Remittance, ...)) check before each insert; duplicates are
skipped with a warning. This makes re-uploading the same fixture
idempotent instead of raising IntegrityError on the PK.

Auto-init DB fixture (backend/tests/conftest.py) sets
CYCLONE_DB_URL to a per-test sqlite file and calls db.init_db()
once per test, replacing the old module-scoped in-memory fixture.
2026-06-19 22:37:12 -06:00