diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml new file mode 100644 index 0000000..a288262 --- /dev/null +++ b/.github/workflows/quality-gate.yml @@ -0,0 +1,109 @@ +name: Quality Gate + +on: + push: + branches: + - main + - master + pull_request: + +jobs: + # Fast, deterministic checks — no database, no network beyond pip. + free-tier: + name: free-tier (unit, lint, health, evals-p) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dev dependencies + run: pip install -r requirements-dev.txt + + - name: Unit / regression / security / snapshot tests + run: make test-free + + - name: Lint (flake8 + bandit) + run: make lint + + - name: Component health check + run: make health + + - name: Evals — Tier P (offline validator scenarios) + run: python3 evals/runner.py --tiers p --verbose + + # Full integration surface: Tier I deploys env_dev.sql twice (idempotency), + # Tier S runs the 85-assertion SQL suite, then the PG-gated pytest tiers run. + integration-postgres: + name: integration (postgres service) + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + env: + PGHOST: localhost + PGPORT: "5432" + PGUSER: postgres + PGPASSWORD: postgres + PGDATABASE: postgres + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dev dependencies + run: pip install -r requirements-dev.txt + + - name: Install PostgreSQL client + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends postgresql-client + + - name: Write CI database config + run: | + cat > build/config.local.env <<'EOF' + DB_ENGINE="postgresql" + PG_HOST="localhost" + PG_PORT="5432" + PG_SUPERUSER="postgres" + PG_SUPERUSER_PASSWORD="postgres" + PG_DB_DEV="te_mgmt_dev" + PG_SCHEMA_DEV="te_dev" + PG_DB_TEST="te_mgmt_test" + PG_SCHEMA_TEST="te_test" + EOF + + # env_*.sql scripts do not create their databases — deploy_all.sh does that + # before invoking them (see build/te_core_schema.sql:41). Mirror it here. + - name: Create environment databases + run: | + for db in te_mgmt_dev te_mgmt_test; do + if [ -z "$(psql -d postgres -tA -c "SELECT 1 FROM pg_database WHERE datname = '$db'")" ]; then + psql -v ON_ERROR_STOP=1 -d postgres -c \ + "CREATE DATABASE \"$db\" WITH OWNER = postgres ENCODING = 'UTF8' TEMPLATE = template0 CONNECTION LIMIT = -1" + fi + done + + - name: Evals — Tiers P, I, S + run: python3 evals/runner.py --tiers p,i,s --verbose + + - name: Deploy test environment (for cross-env parity) + run: psql -v ON_ERROR_STOP=1 -f build/environments/env_test.sql + + - name: Integration / e2e / parity tests + run: pytest -m "e2e or integration or parity" tests/ -v diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c063be5..93895a6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -49,7 +49,15 @@ The three layers can break independently, so we keep them physically separate. T | `tests/run_all_tests.sql` | Master SQL test orchestrator | | `tests/run_tests.sh` | Bash wrapper that sources `config.local.env` | | `tests/run_python_tests.ps1` | Windows runner — invoked by the GitHub Actions workflow | +| `tests/conftest.py` | pytest env-var isolation between tests | | `tests/test_csv_validator.py` | unittest for `build/csv/validator.py` | +| `tests/test_csv_utilise.py` | unit tests for `build/csv_utilise.sh` argument parsing | +| `tests/test_csv_loader_arbitrary_shapes.py` | integration: arbitrary CSV shapes through loader → PG (skips without PG) | +| `tests/test_e2e_pipeline.py` | e2e: CSV → validate → load → verify (DB half skips without PG) | +| `tests/test_parity.py` | cross-environment row-count / schema parity (skips without PG) | +| `tests/test_regression.py` | pinned tests for previously found bug classes | +| `tests/test_security.py` | static credential/SQL-pattern scans | +| `tests/test_snapshot.py` | golden-file output comparisons (`tests/snapshots/`) | | `tests/test_evals_runner.py` | unittest for `evals/runner.py` itself | ### `evals/` — data-driven scenarios diff --git a/SETUP_RUNBOOK.md b/SETUP_RUNBOOK.md new file mode 100644 index 0000000..0e07f0e --- /dev/null +++ b/SETUP_RUNBOOK.md @@ -0,0 +1,119 @@ +# SETUP RUNBOOK — PostgreDataMigrationApp + +Every command block below is labelled with **where to run it**. On Windows there are +two different terminals, and the repo's `.sh` scripts only work in one of them: + +| Label | Prompt looks like | Use for | +|---|---|---| +| **Git Bash** | `user@machine MINGW64 ~` | all `.sh` scripts, `make`, day-to-day operations | +| **PowerShell** | `PS C:\Users\...>` | `preflight.ps1`, `scripts\test.ps1`, git on Windows | + +On Linux / macOS / WSL2 every block runs in your normal shell. +Azure Cloud Shell is **not** used by this runbook — it cannot reach a database on your machine. + +--- + +## Phase 0 — Prerequisites (one-time per machine) + +Install: + +- **Git** (on Windows: [Git for Windows](https://gitforwindows.org/), which includes Git Bash) +- **Python 3.10+** +- **PostgreSQL 13+** — server running, `psql` client on PATH + +**▶ RUN IN: Git Bash** — verify: + +```bash +git --version && python3 --version && psql --version && bash --version | head -1 +``` + +## Phase 1 — Get the code and Python dependencies + +**▶ RUN IN: Git Bash** + +```bash +git clone https://github.com/amar-python/PostgreDataMigrationApp.git +cd PostgreDataMigrationApp +pip install -r requirements-dev.txt +``` + +## Phase 2 — Preflight check + +Catches missing tools and configuration before anything half-deploys. + +**▶ RUN IN: Git Bash** (repo root): + +```bash +bash preflight.sh +``` + +Windows-native alternative — **▶ RUN IN: PowerShell**: `.\preflight.ps1` + +## Phase 3 — Configure the database connection + +**▶ RUN IN: Git Bash** (repo root): + +```bash +cd build +cp config.env.example config.env +./setup.sh # interactive wizard: engine, host, port, credentials per environment +cd .. +``` + +This writes `build/config.local.env` — the file every loader and test script reads. +It is gitignored; never commit it. + +## Phase 4 — Deploy the schema + +PostgreSQL must be running and reachable with the credentials from Phase 3. + +**▶ RUN IN: Git Bash** (repo root): + +```bash +export PGPASSWORD='' + +# Single environment (dev) — idempotent, safe to re-run: +psql -U postgres -f build/environments/env_dev.sql + +# Or all four environments (dev / test / staging / prod): +bash build/deploy_all.sh +``` + +## Phase 5 — Verify the deployment + +**▶ RUN IN: Git Bash** (repo root): + +```bash +make test-free # offline unit / regression / security tests +make lint # flake8 + bandit +make health # structural check — every expected file present +bash tests/run_tests.sh dev # SQL assertion suite against the dev DB +python3 evals/runner.py --tiers p,i,s # evals: offline + idempotency + SQL tiers +``` + +All green means the environment is correctly stood up. + +## Phase 6 — Load and use CSV data (day-to-day) + +**▶ RUN IN: Git Bash** (repo root): + +```bash +make csv-demo # one-shot proof with bundled samples +bash build/csv_loader.sh path/to/anything.csv --env dev # load any CSV +bash build/csv_utilise.sh list --env dev # list CSV-loaded tables +bash build/csv_utilise.sh peek --limit 5 # inspect rows +bash build/csv_utilise.sh export
out.csv # round-trip back to CSV +bash build/csv_utilise.sh drop
--yes # remove a CSV-loaded table +``` + +--- + +## Troubleshooting order + +When a DB-dependent step fails, check in this order — it resolves most setup issues: + +1. Is PostgreSQL running? (`pg_isready` or `psql -c "SELECT 1"`) +2. Is `PGPASSWORD` set **in this terminal**? (environment variables do not survive new windows) +3. Does `build/config.local.env` exist? (Phase 3 creates it; a fresh clone does not have it) +4. Are you in the right terminal? `.sh` scripts require Git Bash / WSL2 — they fail in + PowerShell and cmd with syntax errors. diff --git a/build/csv/validator.py b/build/csv/validator.py index ed27c90..729aa80 100644 --- a/build/csv/validator.py +++ b/build/csv/validator.py @@ -25,10 +25,16 @@ RED = '\033[0;31m' NC = '\033[0m' + def log(msg): print(f"{GREEN} [validator OK]{NC} {msg}") + + def warn(msg): print(f"{YELLOW} [validator WARN]{NC} {msg}") + + def err(msg): print(f"{RED} [validator ERR]{NC} {msg}", file=sys.stderr) + # ── Read environment variables ──────────────────────────────────────────────── CSV_FILE = os.environ.get('CSV_FILE', '') VALID_CSV = os.environ.get('VALID_CSV', '') @@ -36,7 +42,7 @@ def err(msg): print(f"{RED} [validator ERR]{NC} {msg}", file=sys.stderr) TABLE_NAME = os.environ.get('TABLE_NAME', 'unknown') # Validate required vars -missing = [v for v in ('CSV_FILE','VALID_CSV','SKIP_FILE') if not os.environ.get(v)] +missing = [v for v in ('CSV_FILE', 'VALID_CSV', 'SKIP_FILE') if not os.environ.get(v)] if missing: err(f"Missing required environment variables: {', '.join(missing)}") sys.exit(1) diff --git a/evals/runner.py b/evals/runner.py index fa38b5d..5898cdd 100644 --- a/evals/runner.py +++ b/evals/runner.py @@ -305,7 +305,8 @@ def _can_connect_pg() -> bool: def _count_dev_rows() -> Dict[str, Any]: counts: Dict[str, Any] = {} for tbl in _DEV_SEED_TABLES: - query = 'SELECT count(*) FROM te_dev."' + tbl + '";' + # tbl comes from the hardcoded _DEV_SEED_TABLES constant — not injectable + query = 'SELECT count(*) FROM te_dev."' + tbl + '";' # nosec B608 r = subprocess.run( ["psql", "-tA", "-d", "te_mgmt_dev", "-c", query], env=_pg_env(), @@ -456,6 +457,8 @@ def _run_fresh_deploy_then_tests( table_overrides = [ "--set", "schema_name=te_dev", + "--set", "app_user=te_dev_user", + "--set", "conn_limit=10", "--set", "tbl_organisations=organisations", "--set", "tbl_personnel=personnel", "--set", "tbl_test_programs=test_programs", @@ -486,14 +489,18 @@ def _run_fresh_deploy_then_tests( total_assertions = None pass_rate = None for line in tests.stdout.splitlines(): - parts = line.split() - if (len(parts) >= 5 and parts[0].isdigit() and parts[1].isdigit() - and parts[2].isdigit() and parts[3].endswith("%")): - try: - total_assertions = int(parts[0]) - pass_rate = float(parts[3].rstrip("%")) - except ValueError: - pass + # Summary row may be plain ("142 142 0 100.0% ...") or a psql table + # row ("142 | 142 | 0 | 0 | 100.0% | ..."); strip pipes first. + parts = line.replace("|", " ").split() + if (len(parts) >= 4 and parts[0].isdigit() and parts[1].isdigit() + and parts[2].isdigit()): + pct = next((p for p in parts[3:] if p.endswith("%")), None) + if pct is not None: + try: + total_assertions = int(parts[0]) + pass_rate = float(pct.rstrip("%")) + except ValueError: + pass actual["total_assertions"] = total_assertions actual["pass_rate"] = pass_rate result.actual = actual diff --git a/scripts/lint.sh b/scripts/lint.sh index 13391ff..4ced616 100644 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -24,7 +24,7 @@ ALL_PY=("${PY_SRC[@]}" "${PY_TESTS[@]}" "${PY_SCRIPTS[@]}") echo -e "\n${YELLOW}=== flake8 (style + logic) ===${NC}" if python3 -m flake8 "${ALL_PY[@]}" \ --max-line-length=120 \ - --extend-ignore=E501; then + --extend-ignore=E501,E221,E272; then echo -e "${GREEN}✓ flake8 clean${NC}" else echo -e "${RED}✗ flake8 found issues — fix before merging${NC}" diff --git a/tests/suites/test_02_programs_phases.sql b/tests/suites/test_02_programs_phases.sql index 8e57021..7d748ad 100644 --- a/tests/suites/test_02_programs_phases.sql +++ b/tests/suites/test_02_programs_phases.sql @@ -19,75 +19,75 @@ BEGIN -- ───────────────────────────────────────────────────────────────────────────── -- E01: correct row count - SELECT COUNT(*) INTO v_count FROM :"schema_name".:"tbl_test_programs"; - PERFORM :"schema_name".assert_equals( + SELECT COUNT(*) INTO v_count FROM test_programs; + PERFORM assert_equals( 'programs', 'E01 — Row count = 2', 2::BIGINT, v_count ); -- E02: CYB9131 program exists SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_programs" + FROM test_programs WHERE program_code = 'CYB9131'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'programs', 'E02 — CYB9131 program present', 1::BIGINT, v_count ); -- E03: LAND400-P3 program exists SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_programs" + FROM test_programs WHERE program_code = 'LAND400-P3'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'programs', 'E03 — LAND400-P3 program present', 1::BIGINT, v_count ); -- E04: all programs have a program_director assigned SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_programs" + FROM test_programs WHERE program_director_id IS NULL; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'programs', 'E04 — All programs have a director assigned', 0::BIGINT, v_count ); -- E05: program directors exist in personnel table (no dangling FKs) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_programs" tp + FROM test_programs tp WHERE tp.program_director_id IS NOT NULL AND NOT EXISTS ( - SELECT 1 FROM :"schema_name".:"tbl_personnel" p + SELECT 1 FROM personnel p WHERE p.person_id = tp.program_director_id ); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'programs', 'E05 — All program directors exist in personnel table', 0::BIGINT, v_count ); -- E06: all status values are within the allowed set SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_programs" + FROM test_programs WHERE status NOT IN ('planning','active','suspended','completed','cancelled'); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'programs', 'E06 — All program status values are valid', 0::BIGINT, v_count ); -- E07: classification values are all within ISM-compliant set SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_programs" + FROM test_programs WHERE classification NOT IN ('UNCLASSIFIED','PROTECTED','SECRET','TOP SECRET'); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'programs', 'E07 — All classification markings are valid', 0::BIGINT, v_count ); -- E08: end_date is always >= start_date (no inverted dates) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_programs" + FROM test_programs WHERE end_date IS NOT NULL AND end_date < start_date; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'programs', 'E08 — No programs have end_date before start_date', 0::BIGINT, v_count ); @@ -95,19 +95,19 @@ BEGIN -- E09: program_code values are unique SELECT COUNT(*) INTO v_count FROM ( - SELECT program_code FROM :"schema_name".:"tbl_test_programs" + SELECT program_code FROM test_programs GROUP BY program_code HAVING COUNT(*) > 1 ) dups; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'programs', 'E09 — All program_codes are unique', 0::BIGINT, v_count ); -- E10: CYB9131 is classified PROTECTED SELECT classification INTO v_value - FROM :"schema_name".:"tbl_test_programs" + FROM test_programs WHERE program_code = 'CYB9131'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'programs', 'E10 — CYB9131 classification is PROTECTED', 'PROTECTED', v_value ); @@ -117,37 +117,37 @@ BEGIN -- ───────────────────────────────────────────────────────────────────────────── -- F01: reject inverted date range - PERFORM :"schema_name".assert_raises( + PERFORM assert_raises( 'programs', 'F01 — Inverted date range rejected by CHECK constraint', - 'INSERT INTO ' || :'schema_name' || '.' || :'tbl_test_programs' || + 'INSERT INTO ' || current_setting('te.schema_name') || '.' || 'test_programs' || $q$ (org_id, program_code, program_name, classification, status, start_date, end_date) SELECT org_id, 'TEST-BAD-DATES', 'Bad Date Test', 'UNCLASSIFIED', 'planning', '2025-12-31', '2025-01-01' - FROM $q$ || :'schema_name' || '.' || :'tbl_organisations' || + FROM $q$ || current_setting('te.schema_name') || '.' || 'organisations' || ' LIMIT 1' ); -- F02: reject invalid classification marking - PERFORM :"schema_name".assert_raises( + PERFORM assert_raises( 'programs', 'F02 — Invalid classification rejected by CHECK constraint', - 'INSERT INTO ' || :'schema_name' || '.' || :'tbl_test_programs' || + 'INSERT INTO ' || current_setting('te.schema_name') || '.' || 'test_programs' || $q$ (org_id, program_code, program_name, classification, status) SELECT org_id, 'TEST-BADCLASS', 'Bad Class Test', 'CONFIDENTIAL', 'planning' - FROM $q$ || :'schema_name' || '.' || :'tbl_organisations' || + FROM $q$ || current_setting('te.schema_name') || '.' || 'organisations' || ' LIMIT 1' ); -- F03: reject duplicate program_code - PERFORM :"schema_name".assert_raises( + PERFORM assert_raises( 'programs', 'F03 — Duplicate program_code rejected by UNIQUE constraint', - 'INSERT INTO ' || :'schema_name' || '.' || :'tbl_test_programs' || + 'INSERT INTO ' || current_setting('te.schema_name') || '.' || 'test_programs' || $q$ (org_id, program_code, program_name, classification, status) SELECT org_id, 'CYB9131', 'Duplicate Code Test', 'UNCLASSIFIED', 'planning' - FROM $q$ || :'schema_name' || '.' || :'tbl_organisations' || + FROM $q$ || current_setting('te.schema_name') || '.' || 'organisations' || ' LIMIT 1' ); @@ -156,31 +156,31 @@ BEGIN -- ───────────────────────────────────────────────────────────────────────────── -- G01: correct row count - SELECT COUNT(*) INTO v_count FROM :"schema_name".:"tbl_temp_documents"; - PERFORM :"schema_name".assert_equals( + SELECT COUNT(*) INTO v_count FROM temp_documents; + PERFORM assert_equals( 'temp_documents', 'G01 — Row count = 3', 3::BIGINT, v_count ); -- G02: approved TEMP exists for CYB9131 SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_temp_documents" td - JOIN :"schema_name".:"tbl_test_programs" tp ON tp.program_id = td.program_id + FROM temp_documents td + JOIN test_programs tp ON tp.program_id = td.program_id WHERE tp.program_code = 'CYB9131' AND td.status = 'approved'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'temp_documents', 'G02 — CYB9131 has an approved TEMP', 1::BIGINT, v_count ); -- G03: every TEMP has an author in personnel SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_temp_documents" td + FROM temp_documents td WHERE NOT EXISTS ( - SELECT 1 FROM :"schema_name".:"tbl_personnel" p + SELECT 1 FROM personnel p WHERE p.person_id = td.author_id ); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'temp_documents', 'G03 — All TEMP authors exist in personnel', 0::BIGINT, v_count ); @@ -188,28 +188,28 @@ BEGIN -- G04: version + program_id combination is unique SELECT COUNT(*) INTO v_count FROM ( - SELECT program_id, version FROM :"schema_name".:"tbl_temp_documents" + SELECT program_id, version FROM temp_documents GROUP BY program_id, version HAVING COUNT(*) > 1 ) dups; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'temp_documents', 'G04 — No duplicate version per program (UNIQUE enforced)', 0::BIGINT, v_count ); -- G05: status values are all valid SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_temp_documents" + FROM temp_documents WHERE status NOT IN ('draft','in_review','approved','superseded','cancelled'); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'temp_documents', 'G05 — All TEMP status values are valid', 0::BIGINT, v_count ); -- G06: at least one TEMP is in draft or in_review (active development) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_temp_documents" + FROM temp_documents WHERE status IN ('draft','in_review'); - PERFORM :"schema_name".assert_true( + PERFORM assert_true( 'temp_documents', 'G06 — At least one TEMP in active draft/review state', v_count::TEXT || ' >= 1' ); @@ -219,37 +219,37 @@ BEGIN -- ───────────────────────────────────────────────────────────────────────────── -- H01: correct row count - SELECT COUNT(*) INTO v_count FROM :"schema_name".:"tbl_test_phases"; - PERFORM :"schema_name".assert_equals( + SELECT COUNT(*) INTO v_count FROM test_phases; + PERFORM assert_equals( 'test_phases', 'H01 — Row count = 3', 3::BIGINT, v_count ); -- H02: CYB9131 has both a completed DT&E and an active OT&E phase SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_phases" ph - JOIN :"schema_name".:"tbl_test_programs" tp ON tp.program_id = ph.program_id + FROM test_phases ph + JOIN test_programs tp ON tp.program_id = ph.program_id WHERE tp.program_code = 'CYB9131' AND ph.phase_type IN ('DT&E','OT&E'); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_phases', 'H02 — CYB9131 has DT&E and OT&E phases', 2::BIGINT, v_count ); -- H03: completed phases have an actual_start date SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_phases" + FROM test_phases WHERE status = 'completed' AND actual_start IS NULL; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_phases', 'H03 — All completed phases have an actual_start date', 0::BIGINT, v_count ); -- H04: phase_type values are within the allowed set SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_phases" + FROM test_phases WHERE phase_type NOT IN ('DT&E','AT&E','OT&E','IOT&E','LFT&E','FOLLOW_ON'); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_phases', 'H04 — All phase_type values are valid', 0::BIGINT, v_count ); @@ -257,19 +257,19 @@ BEGIN -- H05: phase_code + program_id is unique SELECT COUNT(*) INTO v_count FROM ( - SELECT program_id, phase_code FROM :"schema_name".:"tbl_test_phases" + SELECT program_id, phase_code FROM test_phases GROUP BY program_id, phase_code HAVING COUNT(*) > 1 ) dups; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_phases', 'H05 — No duplicate phase_code per program (UNIQUE enforced)', 0::BIGINT, v_count ); -- H06: planned phases have no actual_start date yet SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_phases" + FROM test_phases WHERE status = 'planned' AND actual_start IS NOT NULL; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_phases', 'H06 — Planned phases have no actual_start date', 0::BIGINT, v_count ); diff --git a/tests/suites/test_03_requirements_vcrm.sql b/tests/suites/test_03_requirements_vcrm.sql index 0da7efd..1e374d7 100644 --- a/tests/suites/test_03_requirements_vcrm.sql +++ b/tests/suites/test_03_requirements_vcrm.sql @@ -18,74 +18,74 @@ BEGIN -- ───────────────────────────────────────────────────────────────────────────── -- I01: correct row count - SELECT COUNT(*) INTO v_count FROM :"schema_name".:"tbl_requirements"; - PERFORM :"schema_name".assert_equals( + SELECT COUNT(*) INTO v_count FROM requirements; + PERFORM assert_equals( 'requirements', 'I01 — Row count = 8', 8::BIGINT, v_count ); -- I02: CYB9131 has 6 requirements SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_requirements" r - JOIN :"schema_name".:"tbl_test_programs" tp ON tp.program_id = r.program_id + FROM requirements r + JOIN test_programs tp ON tp.program_id = r.program_id WHERE tp.program_code = 'CYB9131'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'requirements', 'I02 — CYB9131 has 6 requirements', 6::BIGINT, v_count ); -- I03: LAND400-P3 has 2 requirements SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_requirements" r - JOIN :"schema_name".:"tbl_test_programs" tp ON tp.program_id = r.program_id + FROM requirements r + JOIN test_programs tp ON tp.program_id = r.program_id WHERE tp.program_code = 'LAND400-P3'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'requirements', 'I03 — LAND400-P3 has 2 requirements', 2::BIGINT, v_count ); -- I04: all req_identifiers follow expected non-empty format SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_requirements" + FROM requirements WHERE req_identifier IS NULL OR LENGTH(TRIM(req_identifier)) = 0; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'requirements', 'I04 — All requirements have a non-empty req_identifier', 0::BIGINT, v_count ); -- I05: all mandatory (priority=1) requirements have verification_method set SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_requirements" + FROM requirements WHERE priority = 1 AND verification_method IS NULL; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'requirements', 'I05 — All mandatory requirements have a verification method', 0::BIGINT, v_count ); -- I06: verification_method values are within allowed set SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_requirements" + FROM requirements WHERE verification_method NOT IN ('test','analysis','inspection','demonstration'); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'requirements', 'I06 — All verification_method values are valid', 0::BIGINT, v_count ); -- I07: req_type values are within allowed set SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_requirements" + FROM requirements WHERE req_type NOT IN ('functional','performance','security', 'safety','interface','compliance'); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'requirements', 'I07 — All req_type values are valid', 0::BIGINT, v_count ); -- I08: priority values are between 1 and 3 SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_requirements" + FROM requirements WHERE priority NOT BETWEEN 1 AND 3; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'requirements', 'I08 — All priority values are between 1 and 3', 0::BIGINT, v_count ); @@ -93,31 +93,31 @@ BEGIN -- I09: req_identifier + program_id is unique SELECT COUNT(*) INTO v_count FROM ( - SELECT program_id, req_identifier FROM :"schema_name".:"tbl_requirements" + SELECT program_id, req_identifier FROM requirements GROUP BY program_id, req_identifier HAVING COUNT(*) > 1 ) dups; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'requirements', 'I09 — No duplicate req_identifier per program (UNIQUE enforced)', 0::BIGINT, v_count ); -- I10: all requirements are linked to an existing program (FK integrity) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_requirements" r + FROM requirements r WHERE NOT EXISTS ( - SELECT 1 FROM :"schema_name".:"tbl_test_programs" tp + SELECT 1 FROM test_programs tp WHERE tp.program_id = r.program_id ); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'requirements', 'I10 — All requirements reference a valid program (FK check)', 0::BIGINT, v_count ); -- I11: mandatory security requirement SYS-SEC-001 exists SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_requirements" + FROM requirements WHERE req_identifier = 'SYS-SEC-001'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'requirements', 'I11 — SYS-SEC-001 (MFA Enforcement) requirement exists', 1::BIGINT, v_count ); @@ -127,22 +127,22 @@ BEGIN -- ───────────────────────────────────────────────────────────────────────────── -- J01: reject priority outside 1–3 - PERFORM :"schema_name".assert_raises( + PERFORM assert_raises( 'requirements', 'J01 — Priority outside 1–3 rejected by CHECK constraint', - 'INSERT INTO ' || :'schema_name' || '.' || :'tbl_requirements' || + 'INSERT INTO ' || current_setting('te.schema_name') || '.' || 'requirements' || $q$ (program_id, req_identifier, title, priority, verification_method) SELECT program_id, 'SYS-BAD-999', 'Bad Priority Test', 9, 'test' - FROM $q$ || :'schema_name' || '.' || :'tbl_test_programs' || + FROM $q$ || current_setting('te.schema_name') || '.' || 'test_programs' || ' LIMIT 1' ); -- J02: reject invalid verification_method - PERFORM :"schema_name".assert_raises( + PERFORM assert_raises( 'requirements', 'J02 — Invalid verification_method rejected by CHECK', - 'INSERT INTO ' || :'schema_name' || '.' || :'tbl_requirements' || + 'INSERT INTO ' || current_setting('te.schema_name') || '.' || 'requirements' || $q$ (program_id, req_identifier, title, priority, verification_method) SELECT program_id, 'SYS-BAD-888', 'Bad Method Test', 1, 'guess' - FROM $q$ || :'schema_name' || '.' || :'tbl_test_programs' || + FROM $q$ || current_setting('te.schema_name') || '.' || 'test_programs' || ' LIMIT 1' ); @@ -151,42 +151,42 @@ BEGIN -- ───────────────────────────────────────────────────────────────────────────── -- K01: correct row count in vcrm_entries - SELECT COUNT(*) INTO v_count FROM :"schema_name".:"tbl_vcrm_entries"; - PERFORM :"schema_name".assert_equals( + SELECT COUNT(*) INTO v_count FROM vcrm_entries; + PERFORM assert_equals( 'vcrm', 'K01 — VCRM row count = 8', 8::BIGINT, v_count ); -- K02: all CYB9131 requirements have at least one test case mapped SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_requirements" r - JOIN :"schema_name".:"tbl_test_programs" tp ON tp.program_id = r.program_id + FROM requirements r + JOIN test_programs tp ON tp.program_id = r.program_id WHERE tp.program_code = 'CYB9131' AND NOT EXISTS ( - SELECT 1 FROM :"schema_name".:"tbl_vcrm_entries" v + SELECT 1 FROM vcrm_entries v WHERE v.req_id = r.req_id ); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'vcrm', 'K02 — All CYB9131 requirements have VCRM coverage', 0::BIGINT, v_count ); -- K03: SYS-SEC-001 (MFA) is covered by exactly 2 test cases SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_vcrm_entries" v - JOIN :"schema_name".:"tbl_requirements" r ON r.req_id = v.req_id + FROM vcrm_entries v + JOIN requirements r ON r.req_id = v.req_id WHERE r.req_identifier = 'SYS-SEC-001'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'vcrm', 'K03 — SYS-SEC-001 (MFA) mapped to exactly 2 test cases', 2::BIGINT, v_count ); -- K04: SYS-PERF-001 (Availability SLA) is covered by exactly 1 test case SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_vcrm_entries" v - JOIN :"schema_name".:"tbl_requirements" r ON r.req_id = v.req_id + FROM vcrm_entries v + JOIN requirements r ON r.req_id = v.req_id WHERE r.req_identifier = 'SYS-PERF-001'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'vcrm', 'K04 — SYS-PERF-001 (Availability) mapped to exactly 1 test case', 1::BIGINT, v_count ); @@ -194,57 +194,57 @@ BEGIN -- K05: no duplicate req+tc pairs in VCRM SELECT COUNT(*) INTO v_count FROM ( - SELECT req_id, tc_id FROM :"schema_name".:"tbl_vcrm_entries" + SELECT req_id, tc_id FROM vcrm_entries GROUP BY req_id, tc_id HAVING COUNT(*) > 1 ) dups; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'vcrm', 'K05 — No duplicate req↔tc entries in VCRM (UNIQUE enforced)', 0::BIGINT, v_count ); -- K06: all VCRM entries reference a valid requirement (FK integrity) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_vcrm_entries" v + FROM vcrm_entries v WHERE NOT EXISTS ( - SELECT 1 FROM :"schema_name".:"tbl_requirements" r + SELECT 1 FROM requirements r WHERE r.req_id = v.req_id ); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'vcrm', 'K06 — All VCRM entries reference a valid requirement (FK check)', 0::BIGINT, v_count ); -- K07: all VCRM entries reference a valid test case (FK integrity) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_vcrm_entries" v + FROM vcrm_entries v WHERE NOT EXISTS ( - SELECT 1 FROM :"schema_name".:"tbl_test_cases" tc + SELECT 1 FROM test_cases tc WHERE tc.tc_id = v.tc_id ); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'vcrm', 'K07 — All VCRM entries reference a valid test case (FK check)', 0::BIGINT, v_count ); -- K08: coverage_type values are within the allowed set SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_vcrm_entries" + FROM vcrm_entries WHERE coverage_type NOT IN ('full','partial','conditional'); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'vcrm', 'K08 — All coverage_type values are valid', 0::BIGINT, v_count ); -- K09: LAND400 requirements have NO VCRM coverage yet (expected gap) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_requirements" r - JOIN :"schema_name".:"tbl_test_programs" tp ON tp.program_id = r.program_id + FROM requirements r + JOIN test_programs tp ON tp.program_id = r.program_id WHERE tp.program_code = 'LAND400-P3' AND EXISTS ( - SELECT 1 FROM :"schema_name".:"tbl_vcrm_entries" v + SELECT 1 FROM vcrm_entries v WHERE v.req_id = r.req_id ); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'vcrm', 'K09 — LAND400-P3 requirements correctly have no VCRM entries yet', 0::BIGINT, v_count ); @@ -255,12 +255,12 @@ BEGIN 100.0 * COUNT(DISTINCT v.req_id) / NULLIF(COUNT(DISTINCT r.req_id), 0), 1 ) INTO v_pct - FROM :"schema_name".:"tbl_requirements" r - JOIN :"schema_name".:"tbl_test_programs" tp ON tp.program_id = r.program_id - LEFT JOIN :"schema_name".:"tbl_vcrm_entries" v ON v.req_id = r.req_id + FROM requirements r + JOIN test_programs tp ON tp.program_id = r.program_id + LEFT JOIN vcrm_entries v ON v.req_id = r.req_id WHERE tp.program_code = 'CYB9131'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'vcrm', 'K10 — CYB9131 VCRM coverage is 100%', 100.0::NUMERIC, v_pct ); diff --git a/tests/suites/test_04_execution_defects.sql b/tests/suites/test_04_execution_defects.sql index 2bd7b5a..59d607e 100644 --- a/tests/suites/test_04_execution_defects.sql +++ b/tests/suites/test_04_execution_defects.sql @@ -17,67 +17,67 @@ BEGIN -- ───────────────────────────────────────────────────────────────────────────── -- L01: correct row count - SELECT COUNT(*) INTO v_count FROM :"schema_name".:"tbl_test_cases"; - PERFORM :"schema_name".assert_equals( + SELECT COUNT(*) INTO v_count FROM test_cases; + PERFORM assert_equals( 'test_cases', 'L01 — Row count = 8', 8::BIGINT, v_count ); -- L02: all test cases linked to the CYB9131 OT&E phase SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_cases" tc - JOIN :"schema_name".:"tbl_test_phases" ph ON ph.phase_id = tc.phase_id + FROM test_cases tc + JOIN test_phases ph ON ph.phase_id = tc.phase_id WHERE ph.phase_code = 'CYB9131-OTE'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_cases', 'L02 — All 8 test cases belong to CYB9131 OT&E phase', 8::BIGINT, v_count ); -- L03: all test cases have status = 'approved' SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_cases" + FROM test_cases WHERE status != 'approved'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_cases', 'L03 — All seeded test cases are in approved status', 0::BIGINT, v_count ); -- L04: TC-OTE-001 (MFA positive) exists SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_cases" + FROM test_cases WHERE tc_identifier = 'TC-OTE-001'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_cases', 'L04 — TC-OTE-001 (MFA valid TOTP) exists', 1::BIGINT, v_count ); -- L05: all test cases have a non-empty title SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_cases" + FROM test_cases WHERE title IS NULL OR LENGTH(TRIM(title)) = 0; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_cases', 'L05 — All test cases have a non-empty title', 0::BIGINT, v_count ); -- L06: all tc_type values are within the allowed set SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_cases" + FROM test_cases WHERE tc_type NOT IN ('functional','performance','security', 'regression','integration','acceptance'); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_cases', 'L06 — All tc_type values are valid', 0::BIGINT, v_count ); -- L07: all test cases have an author in the personnel table SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_cases" tc + FROM test_cases tc WHERE NOT EXISTS ( - SELECT 1 FROM :"schema_name".:"tbl_personnel" p + SELECT 1 FROM personnel p WHERE p.person_id = tc.author_id ); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_cases', 'L07 — All test case authors exist in personnel (FK check)', 0::BIGINT, v_count ); @@ -85,19 +85,19 @@ BEGIN -- L08: tc_identifier + phase_id is unique SELECT COUNT(*) INTO v_count FROM ( - SELECT phase_id, tc_identifier FROM :"schema_name".:"tbl_test_cases" + SELECT phase_id, tc_identifier FROM test_cases GROUP BY phase_id, tc_identifier HAVING COUNT(*) > 1 ) dups; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_cases', 'L08 — No duplicate tc_identifier per phase (UNIQUE enforced)', 0::BIGINT, v_count ); -- L09: all test cases have a defined objective SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_cases" + FROM test_cases WHERE objective IS NULL OR LENGTH(TRIM(objective)) = 0; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_cases', 'L09 — All test cases have a defined objective', 0::BIGINT, v_count ); @@ -107,8 +107,8 @@ BEGIN -- ───────────────────────────────────────────────────────────────────────────── -- M01: correct row count - SELECT COUNT(*) INTO v_count FROM :"schema_name".:"tbl_test_events"; - PERFORM :"schema_name".assert_equals( + SELECT COUNT(*) INTO v_count FROM test_events; + PERFORM assert_equals( 'test_events', 'M01 — Row count = 3', 3::BIGINT, v_count ); @@ -116,68 +116,68 @@ BEGIN -- M02: event codes are unique SELECT COUNT(*) INTO v_count FROM ( - SELECT event_code FROM :"schema_name".:"tbl_test_events" + SELECT event_code FROM test_events GROUP BY event_code HAVING COUNT(*) > 1 ) dups; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_events', 'M02 — All event_codes are unique (UNIQUE enforced)', 0::BIGINT, v_count ); -- M03: EV01 is completed SELECT status INTO v_value - FROM :"schema_name".:"tbl_test_events" + FROM test_events WHERE event_code = 'CYB9131-OTE-EV01'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_events', 'M03 — CYB9131-OTE-EV01 status is completed', 'completed', v_value ); -- M04: EV02 is in_progress SELECT status INTO v_value - FROM :"schema_name".:"tbl_test_events" + FROM test_events WHERE event_code = 'CYB9131-OTE-EV02'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_events', 'M04 — CYB9131-OTE-EV02 status is in_progress', 'in_progress', v_value ); -- M05: EV03 is planned (future) SELECT status INTO v_value - FROM :"schema_name".:"tbl_test_events" + FROM test_events WHERE event_code = 'CYB9131-OTE-EV03'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_events', 'M05 — CYB9131-OTE-EV03 status is planned', 'planned', v_value ); -- M06: completed events have both actual_start and actual_end populated SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_events" + FROM test_events WHERE status = 'completed' AND (actual_start IS NULL OR actual_end IS NULL); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_events', 'M06 — All completed events have actual_start and actual_end', 0::BIGINT, v_count ); -- M07: planned events have no actual_end date SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_events" + FROM test_events WHERE status = 'planned' AND actual_end IS NOT NULL; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_events', 'M07 — Planned events have no actual_end date', 0::BIGINT, v_count ); -- M08: all events are linked to a valid phase (FK check) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_events" ev + FROM test_events ev WHERE NOT EXISTS ( - SELECT 1 FROM :"schema_name".:"tbl_test_phases" ph + SELECT 1 FROM test_phases ph WHERE ph.phase_id = ev.phase_id ); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_events', 'M08 — All test events reference a valid phase (FK check)', 0::BIGINT, v_count ); @@ -187,91 +187,91 @@ BEGIN -- ───────────────────────────────────────────────────────────────────────────── -- N01: correct row count - SELECT COUNT(*) INTO v_count FROM :"schema_name".:"tbl_test_results"; - PERFORM :"schema_name".assert_equals( + SELECT COUNT(*) INTO v_count FROM test_results; + PERFORM assert_equals( 'test_results', 'N01 — Row count = 7', 7::BIGINT, v_count ); -- N02: Event 1 has 6 results SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_results" tr - JOIN :"schema_name".:"tbl_test_events" ev ON ev.event_id = tr.event_id + FROM test_results tr + JOIN test_events ev ON ev.event_id = tr.event_id WHERE ev.event_code = 'CYB9131-OTE-EV01'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_results', 'N02 — EV01 has 6 test results', 6::BIGINT, v_count ); -- N03: Event 2 has 1 result (in-progress) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_results" tr - JOIN :"schema_name".:"tbl_test_events" ev ON ev.event_id = tr.event_id + FROM test_results tr + JOIN test_events ev ON ev.event_id = tr.event_id WHERE ev.event_code = 'CYB9131-OTE-EV02'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_results', 'N03 — EV02 has 1 test result so far', 1::BIGINT, v_count ); -- N04: EV01 produced 4 passes SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_results" tr - JOIN :"schema_name".:"tbl_test_events" ev ON ev.event_id = tr.event_id + FROM test_results tr + JOIN test_events ev ON ev.event_id = tr.event_id WHERE ev.event_code = 'CYB9131-OTE-EV01' AND tr.verdict = 'pass'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_results', 'N04 — EV01 has 4 pass verdicts', 4::BIGINT, v_count ); -- N05: EV01 produced 2 fails SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_results" tr - JOIN :"schema_name".:"tbl_test_events" ev ON ev.event_id = tr.event_id + FROM test_results tr + JOIN test_events ev ON ev.event_id = tr.event_id WHERE ev.event_code = 'CYB9131-OTE-EV01' AND tr.verdict = 'fail'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_results', 'N05 — EV01 has 2 fail verdicts', 2::BIGINT, v_count ); -- N06: EV02 result is inconclusive (72-hour test still running) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_results" tr - JOIN :"schema_name".:"tbl_test_events" ev ON ev.event_id = tr.event_id + FROM test_results tr + JOIN test_events ev ON ev.event_id = tr.event_id WHERE ev.event_code = 'CYB9131-OTE-EV02' AND tr.verdict = 'inconclusive'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_results', 'N06 — EV02 result verdict is inconclusive', 1::BIGINT, v_count ); -- N07: verdict values are all within allowed set SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_results" + FROM test_results WHERE verdict NOT IN ('pass','fail','blocked','not_run','inconclusive'); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_results', 'N07 — All verdict values are valid', 0::BIGINT, v_count ); -- N08: all results reference a valid test case (FK check) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_results" tr + FROM test_results tr WHERE NOT EXISTS ( - SELECT 1 FROM :"schema_name".:"tbl_test_cases" tc + SELECT 1 FROM test_cases tc WHERE tc.tc_id = tr.tc_id ); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_results', 'N08 — All test results reference a valid test case (FK check)', 0::BIGINT, v_count ); -- N09: all results have an actual_result or notes documented SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_test_results" + FROM test_results WHERE actual_result IS NULL AND notes IS NULL; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'test_results', 'N09 — All results have actual_result or notes recorded', 0::BIGINT, v_count ); @@ -281,86 +281,86 @@ BEGIN -- ───────────────────────────────────────────────────────────────────────────── -- O01: correct row count - SELECT COUNT(*) INTO v_count FROM :"schema_name".:"tbl_defect_reports"; - PERFORM :"schema_name".assert_equals( + SELECT COUNT(*) INTO v_count FROM defect_reports; + PERFORM assert_equals( 'defect_reports', 'O01 — Row count = 3', 3::BIGINT, v_count ); -- O02: DR-CYB-0001 exists (audit log gap) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_defect_reports" + FROM defect_reports WHERE defect_ref = 'DR-CYB-0001'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'defect_reports', 'O02 — DR-CYB-0001 (Audit Log gap) exists', 1::BIGINT, v_count ); -- O03: DR-CYB-0002 exists (TLS 1.2 gap) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_defect_reports" + FROM defect_reports WHERE defect_ref = 'DR-CYB-0002'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'defect_reports', 'O03 — DR-CYB-0002 (TLS 1.2 gap) exists', 1::BIGINT, v_count ); -- O04: DR-CYB-0003 exists (session timeout) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_defect_reports" + FROM defect_reports WHERE defect_ref = 'DR-CYB-0003'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'defect_reports', 'O04 — DR-CYB-0003 (Session Timeout) exists', 1::BIGINT, v_count ); -- O05: no critical-severity defects (all are major or minor) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_defect_reports" + FROM defect_reports WHERE severity = 'critical'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'defect_reports', 'O05 — No critical-severity defects in seed data', 0::BIGINT, v_count ); -- O06: 2 major defects seeded SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_defect_reports" + FROM defect_reports WHERE severity = 'major'; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'defect_reports', 'O06 — 2 major-severity defects seeded', 2::BIGINT, v_count ); -- O07: no closed or resolved defects (all open or in-progress) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_defect_reports" + FROM defect_reports WHERE status IN ('closed','resolved'); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'defect_reports', 'O07 — No closed/resolved defects (all active)', 0::BIGINT, v_count ); -- O08: all defects reference a valid program (FK check) SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_defect_reports" dr + FROM defect_reports dr WHERE NOT EXISTS ( - SELECT 1 FROM :"schema_name".:"tbl_test_programs" tp + SELECT 1 FROM test_programs tp WHERE tp.program_id = dr.program_id ); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'defect_reports', 'O08 — All defects reference a valid program (FK check)', 0::BIGINT, v_count ); -- O09: all defects have a raiser in personnel SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_defect_reports" dr + FROM defect_reports dr WHERE NOT EXISTS ( - SELECT 1 FROM :"schema_name".:"tbl_personnel" p + SELECT 1 FROM personnel p WHERE p.person_id = dr.raised_by_id ); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'defect_reports', 'O09 — All defect raisers exist in personnel (FK check)', 0::BIGINT, v_count ); @@ -368,28 +368,28 @@ BEGIN -- O10: defect_ref values are unique SELECT COUNT(*) INTO v_count FROM ( - SELECT defect_ref FROM :"schema_name".:"tbl_defect_reports" + SELECT defect_ref FROM defect_reports GROUP BY defect_ref HAVING COUNT(*) > 1 ) dups; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'defect_reports', 'O10 — All defect_ref values are unique (UNIQUE enforced)', 0::BIGINT, v_count ); -- O11: resolved defects must have a resolved_at timestamp SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_defect_reports" + FROM defect_reports WHERE status = 'resolved' AND resolved_at IS NULL; - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'defect_reports', 'O11 — All resolved defects have a resolved_at timestamp', 0::BIGINT, v_count ); -- O12: severity values are within allowed set SELECT COUNT(*) INTO v_count - FROM :"schema_name".:"tbl_defect_reports" + FROM defect_reports WHERE severity NOT IN ('critical','major','minor','observation'); - PERFORM :"schema_name".assert_equals( + PERFORM assert_equals( 'defect_reports', 'O12 — All severity values are valid', 0::BIGINT, v_count ); diff --git a/tests/suites/test_05_schema_and_business_rules.sql b/tests/suites/test_05_schema_and_business_rules.sql index 2bf3182..5864205 100644 --- a/tests/suites/test_05_schema_and_business_rules.sql +++ b/tests/suites/test_05_schema_and_business_rules.sql @@ -124,7 +124,7 @@ BEGIN PERFORM assert_true( 'schema', 'R01 — Trigger fires: organisations.updated_at advances on UPDATE', - $b$ $b$ || v_ts2::TEXT || ' > ' || v_ts1::TEXT + (v_ts2 > v_ts1)::TEXT ); END; @@ -143,7 +143,7 @@ BEGIN PERFORM assert_true( 'schema', 'R02 — Trigger fires: test_programs.updated_at advances on UPDATE', - $b$ $b$ || v_ts2::TEXT || ' > ' || v_ts1::TEXT + (v_ts2 > v_ts1)::TEXT ); END;