Skip to content

Test layout, accessibility job, registry-race fix, ragged/XLSX test deltas (#167, #168, #191, #192, #193) - #214

Draft
amar-python wants to merge 9 commits into
mainfrom
enh/04-167-168-191-192
Draft

amar-python wants to merge 9 commits into
mainfrom
enh/04-167-168-191-192

Conversation

@amar-python

Copy link
Copy Markdown
Owner

Summary

Work on the "04 Enhancements" batch (#167, #168, #169, #191, #192, #193, #194, #195).

While I was building this, main moved. Merged #202 already delivered #169, #193 and #194, and open draft #213 implements #195. So this PR is rebased onto current main and contains only what main lacks, plus one defect the new tests found. Nothing here overrides upstream's #169, #193 or #194 implementations.

Issue Status in this PR
#167 tests into category folders Done here
#168 accessibility CI job Done here (needs a CI run, see below)
#191 registry race Done here, and fixes a real defect
#192 ragged rows Done here, as a delta on upstream's tests
#193 XLSX formula guard Upstream's guard kept. Adds the "stored as text" assertion the issue asks for, plus the spreadsheet check
#169 catalogue, #194 coverage floors Not touched. Already on main and equivalent to what I built; I dropped mine. Catalogue regenerated for the new layout
#195 confirm types Not included. Collides with #213; see "Decision needed"

Changes

#167. Six folders (unit, system, system_integration, performance, accessibility, uat), each a package. tests/conftest.py gives every test its folder's marker, so pytest -m sit and pytest tests/system_integration select the same set, and adds the shared fixtures (live_api, registry_cleanup, te_org_cleanup, destructive_enabled, unique_tag). New markers, make test-* targets, tests/README.md. scripts/lint.sh now collects every test file with find, so the list can't drift again. The three test files that landed flat on main after I branched are moved too.

#168. tests/accessibility/ (Playwright + axe-core, WCAG 2.1 AA) and a new accessibility job. / and /audit redirect to /auth when signed out, so a plain scan would check the sign-in page three times and report a false green. The tests seed the te.session sessionStorage entry, stub /api/**, and assert they landed on the route they asked for. The integration job and the coverage gate step now exclude the marker.

#191. Concurrency test: uploads released together by a barrier, 40 rounds, for identical file, identical content under different names, and same name with different content. It asserts one winner, one registry row, an attributable rejection for every loser, and no orphan tables. Upstream's test tolerated "error" as a valid loser outcome, so it could not catch this: the losing request can get a generic status: "error" ("relation … already exists" / "type … already exists") instead of duplicate_file, because the loader catches only UniqueViolation. It now also catches DuplicateTable and DuplicateObject, and names the winner rather than echoing the loser's own file. This replaces upstream's test_registry_race.py.

#192. Upstream's tests check the accounting identity and a reason per ragged row. Adds what they don't: the reason is also stored (GET /api/csv/uploads/{id}), and the same for T&E mode.

#193. Asserts the XLSX cell is a text cell with no formula, for =cmd, +cmd, -cmd, @cmd and a tab-prefixed value, in the error and audit reports.

Evidence

All run against PostgreSQL 16 with all four environments deployed, on this branch rebased onto main (ad39ddf).

  • Backend, as CI runs it (test_report.py --markers "not accessibility" --strict): 391 passed, 0 failed, 0 skipped
  • Test count: main 388; this branch 391 non-accessibility + 7 accessibility. Reconciles exactly (388 − 2 upstream race tests + 3 mine + 2 ragged), so the reorg lost nothing
  • Upstream's coverage gate, unchanged: csv_parse ≥ 79, dynamic_loader 87.6% (≥ 84), te_loader 97.3% (≥ 96), pass
  • make lint, bandit, make health (89), test_catalogue.py --check: pass. Frontend: vitest 165 passed, tsc clean
  • Registry race: concurrent uploads of the same file need a regression test (F1) #191 on today's main: with the loader fix, 5/5 runs pass. With the fix reverted (upstream behaviour), 3/3 fail
  • Mutation checks (each fails when the guard is removed): dropping UNIQUE(file_hash) fails the different-names race test 10/10 (the same-name test does not catch it, since UNIQUE(file_name) also guards that case; documented in the test); extra_cells() returning [] fails 6 of 9 ragged tests; disabling upstream's XLSX guard fails 11 of 38 export tests
  • Spreadsheet check (Formula injection in exports: guard XLSX as CSV, and test both (F3) #193): the XLSX produced by downloadErrorReport, opened in LibreOffice 24.2.7.2, shows '=1+1, '=cmd, '+cmd, '-cmd, '@cmd literally, not evaluated; -5 and plain untouched

First local run of the accessibility scans against the Vite dev server: 4 pass, 3 fail. The failures are real frontend defects, as #168 expects: an unlabelled select on /audit, and no <main> landmark on /auth and /audit.

Not verified, and decisions needed

Closes #167, #191, #192. Refs #168, #193 (CI run and Excel outstanding).


Generated by Claude Code

Flat tests/*.py made it impossible to run one category, and the M2/M3 tests
landed uncategorised. Six folders, each a package, with the renames PR #41
proposed plus the 17 test files that landed after it was drafted.

- tests/{unit,system,system_integration,performance,accessibility,uat}/
- tests/conftest.py: folder-derived markers, plus the shared live_api,
  registry_cleanup, te_org_cleanup, destructive_enabled and unique_tag fixtures
- pytest.ini: system, sit, performance, accessibility, uat markers
- Makefile: test-unit / test-system / test-sit / test-performance /
  test-accessibility / test-uat
- scripts/lint.sh: every Python test file is now collected by find, so the list
  cannot drift again; tests get E702/E401/E128 relaxed, source does not
- requirements-dev.txt: playwright, axe-playwright-python (for #168)
- health_check.py rglob, select_tests.py / run_qa.ps1 / .gitignore path updates

365 tests collected before and after; the no-database tiers pass unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011T6Wqj8HS5EUEcvTUM24SL
tests/test_header_mapping.py, test_original_bytes.py and test_registry_race.py
landed flat on main; all three need PostgreSQL, so they go to system_integration/.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011T6Wqj8HS5EUEcvTUM24SL
- tests/accessibility/: Playwright fixtures and scans of /auth, / and /audit.
  Signed-out, / and /audit redirect to /auth, so they would have been scanned
  as the sign-in page and reported a false green. The signed_in_page fixture
  seeds the te.session sessionStorage entry and stubs /api/**, and every scan
  asserts it landed on the route it asked for.
- quality-gate.yml: new `accessibility` job on pull_request. The scan step is
  continue-on-error because the frontend has known unfixed defects; results are
  uploaded as an artifact. The integration job now runs with
  --markers "not accessibility".
- The job installs with `npm ci`, not bun: bun.lock is stale (zod ^3 vs ^4) and
  `bun install --frozen-lockfile` fails against package.json.

First local run against the Vite dev server: 4 pass, 3 fail. The failures are
real frontend defects (unlabelled select on /audit; no <main> on /auth and
/audit), which is what the issue says to expect.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011T6Wqj8HS5EUEcvTUM24SL
…191, F1)

Adds tests/system_integration/test_registry_race.py: several uploads released
together by a barrier, 40 rounds each, for (a) the identical file, (b) identical
content under different names, (c) the same name with different content. Asserts
exactly one winner, one registry row, an attributable duplicate_file for every
loser, and no orphan csv_<hash> tables or duplicated rows.

The test found a defect. When identical uploads overlap, the losing request
could get a generic status "error" ("relation ... already exists" /
"type ... already exists") instead of duplicate_file: the #81 handler caught
only UniqueViolation, and these arrive as DuplicateTable / DuplicateObject. The
registry stayed consistent; the loser just was not told why. The handler also
named the loser's own file, not the winner's. upload_dynamic now catches all
three and a small _lost_race() looks up the winner in the registry.

Evidence, PostgreSQL 16:
- fix reverted: 5 of 5 runs fail
- fix in place: 12 of 12 (at 8 rounds) and 6 of 6 (at 40 rounds) pass
- mutation, file_hash UNIQUE dropped: the different-names test fails 10 of 10.
  The same-name test does not detect it, because UNIQUE (file_name) and the
  csv_<hash> table name also stop that case; documented in the test.
- full suite, all four environments deployed: 370 passed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011T6Wqj8HS5EUEcvTUM24SL
…ounting (#192)

Upstream's tests already check the accounting identity and that each ragged row
gets a reason in the response. Adds what they do not: that every reason is also
stored (GET /api/csv/uploads/{id}), and the same checks for T&E mode, with a file
mixing good rows, an in-file duplicate and several ragged rows.

Mutation, extra_cells() forced to return []: 6 of 9 tests in the file fail,
including all the new ones. Passing run against PostgreSQL 16: 9 passed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011T6Wqj8HS5EUEcvTUM24SL
…back (#193)

Upstream's guard and tests already cover the payloads and plain numbers by
reading values back through sheet_to_json. The issue asks for the cell to be
asserted as stored as text, which is the property that stops a spreadsheet from
evaluating it: adds that for =cmd, +cmd, -cmd, @cmd and a tab-prefixed value in
the error report, and for the audit report.

Mutation, upstream's guard disabled: 11 of 38 tests in export.test.ts fail.

Spreadsheet-application check, as the issue asks: the XLSX produced by
downloadErrorReport, opened in LibreOffice 24.2.7.2, shows '=1+1, '=cmd, '+cmd,
'-cmd, '@cmd literally (not evaluated); -5 and plain are untouched. Microsoft
Excel was not available, so it is not covered.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011T6Wqj8HS5EUEcvTUM24SL
Only conflict was the generated docs/TEST_CASES.md, regenerated with
scripts/test_catalogue.py. tests/test_te_reupload_registry.py (#215) landed flat on
main and is moved to tests/system_integration/ to match the #167 layout.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011T6Wqj8HS5EUEcvTUM24SL
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.

Reorganise tests/ into category folders (unit, system, SIT, performance, uat)

2 participants