Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion .github/workflows/quality-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ jobs:
- name: Component health check
run: make health

# docs/TEST_CASES.md is generated from the test sources; fail if a test
# was added or changed without regenerating it (make test-catalogue).
- name: Test catalogue is up to date
run: python3 scripts/test_catalogue.py --check

- name: Evals — Tier P (offline validator scenarios)
run: python3 evals/runner.py --tiers p --verbose

Expand Down Expand Up @@ -150,8 +155,14 @@ jobs:
# Prints a final block accounting for every test: PASSED / FAILED /
# ERROR / SKIPPED (with reasons) / NOT RUN (deselected). --strict fails
# the build on any skip.
# The T&E schema (te_dev) is deployed into te_mgmt_dev, so the API under
# test must connect there; with the job-level PGDATABASE=postgres every
# T&E-mode test failed with "relation te_dev.organisations does not exist".
# Accessibility tests need Node + a browser and run in their own job below.
- name: Full test suite — final result with skip accounting
run: python3 scripts/test_report.py --strict
env:
PGDATABASE: te_mgmt_dev
run: python3 scripts/test_report.py --markers "not accessibility" --strict

- name: Upload eval reports
if: always()
Expand All @@ -161,3 +172,57 @@ jobs:
path: evals/reports/
if-no-files-found: ignore
retention-days: 30

# WCAG 2.1 AA checks on the React frontend: Playwright (Chromium) + axe-core.
# The backend is mocked inside the tests, so no database is needed.
accessibility:
name: accessibility (playwright + axe)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Setup Python
uses: actions/setup-python@v7
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: |
requirements-dev.txt
api/requirements.txt

- name: Install dev dependencies
run: pip install -r requirements-dev.txt -r api/requirements.txt

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Setup Bun
uses: oven-sh/setup-bun@v2

- name: Install frontend dependencies
working-directory: csv-table-hub-main
run: bun install --frozen-lockfile

- name: Install Playwright Chromium
run: python -m playwright install --with-deps chromium

- name: Frontend unit tests (vitest)
working-directory: csv-table-hub-main
run: npx vitest run

- name: Accessibility tests — final result with skip accounting
env:
TMPDIR: ${{ runner.temp }}
run: python3 scripts/test_report.py --markers accessibility --strict

- name: Upload Vite dev-server log
if: always()
uses: actions/upload-artifact@v7
with:
name: a11y-vite-log
path: ${{ runner.temp }}/a11y-vite.log
if-no-files-found: ignore
retention-days: 14
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ _norton_/
extend to Oracle dbs/

# Runtime test artifacts (regenerate with scripts/test_report.py)
# tests/snapshots/ holds committed fixtures AND runtime-generated output.
# tests/unit/snapshots/ holds committed fixtures AND runtime-generated output.
# Ignore only the generated file; fixtures must be committed.
tests/snapshots/all_valid_expected_valid.csv
tests/unit/snapshots/all_valid_expected_valid.csv
tfplan
*.tfplan
terraform-provider-*.log
Expand Down
33 changes: 33 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.PHONY: test-free test-gate test-evals test-e2e test-all \
test-unit test-system test-sit test-performance test-accessibility test-uat \
test-catalogue \
lint lint-diff health \
eval-list eval-compare eval-summary select-tests \
csv-load csv-list csv-demo
Expand Down Expand Up @@ -32,6 +34,37 @@ test-all:
pytest tests/ --cov=build/csv --cov=evals --cov-report=term-missing
python3 evals/runner.py --tiers p,i,s --verbose

# ── Test groups (see tests/README.md) ───────────────────────────────────────────

# Unit: no database, no network. Python + frontend (vitest).
test-unit:
pytest -m unit tests/ -v
cd csv-table-hub-main && npx vitest run

# System: whole backend through its public API. Requires PostgreSQL.
test-system:
pytest tests/system -v

# System integration: API <-> frontend contract, API <-> DB, cross-env parity.
test-sit:
pytest tests/system_integration -v

# Performance: throughput / latency budgets. PERF_BUDGET_SCALE=3 on slow machines.
test-performance:
pytest tests/performance -v

# Accessibility: WCAG 2.1 AA with Playwright + axe. Needs csv-table-hub-main/node_modules.
test-accessibility:
pytest tests/accessibility -v

# User acceptance: Given/When/Then user stories. Requires PostgreSQL.
test-uat:
pytest tests/uat -v

# Regenerate docs/TEST_CASES.md from the test sources.
test-catalogue:
python3 scripts/test_catalogue.py

# ── Code quality ──────────────────────────────────────────────────────────────

# Full flake8 + bandit scan over all Python source.
Expand Down
41 changes: 25 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[![FastAPI](https://img.shields.io/badge/FastAPI-latest-009688?logo=fastapi&logoColor=white)](https://fastapi.tiangolo.com/)
[![React](https://img.shields.io/badge/React-19-61DAFB?logo=react&logoColor=white)](https://react.dev/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Tests](https://img.shields.io/badge/Tests-76%20vitest%20%7C%20142%20SQL%20assertions-brightgreen)](#test-suite)
[![Tests](https://img.shields.io/badge/Tests-95%20vitest%20%7C%20142%20SQL%20assertions%20%7C%206%20test%20groups-brightgreen)](#test-suite)

---

Expand Down Expand Up @@ -61,7 +61,7 @@ PostgreDataMigrationAppWithCSVLoader/
│ │ ├── export.ts ← CSV / XLSX / PDF export
│ │ ├── mapping-templates.ts ← saved column mapping templates
│ │ └── csv-preview.ts ← CSV parsing utilities
│ ├── src/__tests__/ ← 76 vitest tests
│ ├── src/__tests__/unit/ ← 95 vitest unit tests
│ ├── .env.local ← VITE_API_BASE, VITE_API_KEY (not committed)
│ └── package.json
│
Expand All @@ -83,12 +83,14 @@ PostgreDataMigrationAppWithCSVLoader/
│ ├── start-frontend.ps1 ← Terminal 2: Vite on :8080
│ └── provision_full_test_env.sh ← one-command DB setup
│
├── tests/
│ ├── test_api_unit.py ← 12 unit tests (no DB required)
│ ├── test_api_integration.py ← 19 integration tests
│ ├── test_te_loader_integration.py ← 11 T&E mode integration tests
│ ├── environments/ ← 4 per-environment contract tests
│ └── snapshots/ ← committed CSV test fixtures
├── tests/ ← grouped by test category (tests/README.md)
│ ├── unit/ ← no DB: parser, config, auth, pool, validator
│ ├── system/ ← whole backend through its public API
│ ├── system_integration/ ← API↔UI contract, API↔DB, env parity
│ ├── performance/ ← throughput / latency budgets
│ ├── accessibility/ ← WCAG 2.1 AA (Playwright + axe-core)
│ ├── uat/ ← Given/When/Then user stories
│ └── suites/, framework/ ← 142-assertion SQL suite (psql)
│
├── env.dev.example ← dev environment template
├── docs/ENV_VARIABLES.md ← all environment variables documented
Expand Down Expand Up @@ -184,19 +186,26 @@ Interactive docs: `http://localhost:8000/docs`

### Python tests

Tests are grouped into unit, system, system integration, performance,
accessibility and user acceptance tests. See [`tests/README.md`](tests/README.md)
for what each group covers, and [`docs/TEST_CASES.md`](docs/TEST_CASES.md) for
the full list of test cases.

```powershell
# Unit tests — no database needed (~5 seconds)
python -m pytest tests\ -m "unit or regression or security or snapshot" -q

# Integration tests — requires running API + provisioned database
python -m pytest tests\test_api_integration.py -v
python -m pytest tests\test_te_loader_integration.py -v
# Need a provisioned dev database ($env:PGDATABASE = "te_mgmt_dev")
python -m pytest tests\system -v
python -m pytest tests\system_integration -v
python -m pytest tests\performance -v
python -m pytest tests\uat -v

# Per-environment contract tests
python -m pytest tests\environments\ -v
# Needs the frontend installed (cd csv-table-hub-main; bun install)
python -m pytest tests\accessibility -v
```

### Frontend tests (76 vitest)
### Frontend tests (95 vitest)

```powershell
cd csv-table-hub-main
Expand Down Expand Up @@ -236,7 +245,7 @@ See **`docs/ENV_VARIABLES.md`** for the full reference. Key variables:
| `PGPASSWORD` | _(empty)_ | PostgreSQL password |
| `PGDATABASE` | `te_mgmt_dev` | Target database |
| `API_KEY` | _(empty)_ | Shared secret for `X-API-Key` header (empty = unauthenticated) |
| `CORS_ORIGINS` | `http://localhost:5173,...` | Allowed frontend origins |
| `CORS_ORIGINS` | `http://localhost:8080,...` | Allowed frontend origins |
| `TE_SCHEMA` | `te_dev` | Schema for the 12 T&E tables |
| `API_ALLOW_DESTRUCTIVE` | `false` | Enable `DELETE /api/csv/files/{id}` |

Expand Down Expand Up @@ -283,7 +292,7 @@ organisations ──< personnel
| Connection Limit | 10 | 15 | 25 | 50 |
| Seed Data | ✅ | ✅ | ❌ | ❌ |

Staging and Prod are **never seeded**. The `tests/environments/` contract tests
Staging and Prod are **never seeded**. The `tests/system_integration/environments/` contract tests
verify this automatically.

---
Expand Down
6 changes: 4 additions & 2 deletions api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ class Settings:
# Schema holding the fixed T&E tables (deploy_all.sh dev creates te_dev).
TE_SCHEMA: str = os.environ.get("TE_SCHEMA", "te_dev")

# CORS origins for the frontend dev server.
# CORS origins for the frontend dev server. 8080 is the port the Vite dev
# server (scripts/start-frontend.ps1) actually listens on; 5173/3000 are
# kept for other local setups.
CORS_ORIGINS: list = os.environ.get(
"CORS_ORIGINS", "http://localhost:5173,http://localhost:3000"
"CORS_ORIGINS", "http://localhost:8080,http://localhost:5173,http://localhost:3000"
).split(",")

MAX_UPLOAD_BYTES: int = int(os.environ.get("MAX_UPLOAD_BYTES", str(50 * 1024 * 1024)))
Expand Down
Loading
Loading