blob: 7488ccf9a062e36d25e50f8de433dad5e8a581bb [file] [view]
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Testing & Quality Guide
This guide covers every check that must pass before merging a change: unit tests, dbt model parsing, integration tests, linters, and license compliance verification.
All commands target the repository root unless stated otherwise. Run them in a standard Unix shell (bash/zsh on Linux and macOS; Git Bash on Windows).
---
## Quick Reference
| Check | Command | Needs stack? |
|---|---|---|
| Python unit tests — extractor | `pytest tests/unit/extractor -v` | No |
| Python unit tests — superset | `pytest tests/unit/superset -v` | No |
| dbt model parse | `cd dbt && dbt deps && dbt parse --profiles-dir . --target local` | No |
| dbt schema/data tests | `docker compose exec dbt dbt test` | Yes |
| Integration tests (RLS) | `pytest tests/integration -v` | Yes |
| Python linter | `ruff check .` | No |
| SQL linter | `sqlfluff lint dbt/models dbt/tests --exclude-rules structure.column_order,structure.join_condition_order,references.qualification` | No |
| Shell linter | see full command below | No |
| Dependency license audit | `python scripts/check_licenses.py` | No |
| Apache header audit | `java -jar apache-rat-0.17.jar --input-exclude-file .rat-excludes -- .` (see §6 for the download step) | No |
---
## Environment Setup
Install Python dependencies into a virtual environment once before running any test or lint command locally:
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install \
-r extractor/requirements.txt \
-r dbt/requirements.txt \
-r requirements-dev.txt
```
Activate the environment at the start of each session:
```bash
source .venv/bin/activate
```
> On Windows (Git Bash), `source .venv/bin/activate` works identically. On Windows PowerShell, use `.\.venv\Scripts\Activate.ps1` instead, then run the same commands.
---
## 1. Python Unit Tests
Unit tests run without a database or Docker.
```bash
pytest tests/unit/extractor -v
pytest tests/unit/superset -v
```
### What they cover
**`tests/unit/extractor/`** — watermark tracking, incremental state calculation, source query construction, upsert statement generation, replica lag checks, config parsing.
**`tests/unit/superset/`** — dashboard asset specification builders, RLS Jinja2 template rendering, chart layout construction, metric definitions.
---
## 2. dbt Model Parsing (no database required)
Validate that all SQL models compile and all `ref()` dependencies resolve:
```bash
cd dbt
dbt deps
dbt parse --profiles-dir . --target local
```
`dbt deps` downloads the `dbt-utils` package declared in `packages.yml` — an internet connection is required for this step. Once downloaded, the package is cached in `dbt/dbt_packages/`.
`dbt parse` fails if any model contains a syntax error, unknown macro, or dangling `ref()`. No database connection is made.
---
## 3. dbt Schema & Data Tests (requires running stack)
Ensure `docker compose up -d warehouse dbt` is running, then:
```bash
# Run all schema tests (unique, not_null, relationships, accepted_values)
docker compose exec dbt dbt test
# Run tests for a single model
docker compose exec dbt dbt test --select mart_portfolio_health
# Rebuild all incremental models from scratch, then test
docker compose exec dbt dbt build --full-refresh
```
---
## 4. Integration Tests — Row-Level Security
These tests verify that the Superset RLS policy in `meta.user_office_mapping` isolates data correctly between users. They require a fully running stack with seeded demo data.
### Prerequisites
1. Fineract DB seeded with `warehouse/seed/seed_fineract_source.sql`.
2. BI stack running: `docker compose up -d warehouse superset dbt extractor`.
3. At least one successful pipeline run (check `docker compose logs -f extractor`).
### Run
```bash
pytest tests/integration -v
```
### What is verified
- `north_manager` queries return rows only for `office_id = 101` (North Branch).
- `south_manager` queries return rows only for `office_id = 102` (South Branch).
- `admin` queries return rows across all offices.
- No user can bypass the `meta.user_office_mapping` lookup.
---
## 5. Code Quality — Linters
### Python (Ruff)
```bash
# Check for violations
ruff check .
# Auto-fix safe violations
ruff check --fix .
```
Configuration is in `pyproject.toml` (`line-length = 120`, rules: E, F, W, I, UP, B, SIM).
### SQL (SQLFluff)
```bash
sqlfluff lint dbt/models dbt/tests \
--exclude-rules structure.column_order,structure.join_condition_order,references.qualification
```
Auto-fix:
```bash
sqlfluff fix dbt/models dbt/tests \
--exclude-rules structure.column_order,structure.join_condition_order,references.qualification
```
Configuration is in `.sqlfluff` at the repository root.
### Shell (ShellCheck)
Run exactly the set of files checked in CI (with `--severity=warning`):
```bash
shellcheck --severity=warning \
docker/fineract-postgresql/initdb/001_init_fineract_databases.sh \
docker/postgres-warehouse/initdb/002_create_warehouse_roles.sh \
docker/superset/init_superset.sh \
docker/superset/refresh_superset_assets.sh \
scripts/bootstrap_fineract_source.sh \
scripts/bootstrap_source.sh \
scripts/common.sh \
scripts/run_extractor_backfill.sh \
scripts/run_extractor_incremental.sh \
scripts/run_pipeline.sh \
scripts/stop_fineract_backend.sh \
scripts/verify_extraction.sh
```
Install ShellCheck if needed:
- Linux (Debian/Ubuntu): `sudo apt-get install shellcheck`
- Linux (RHEL/Fedora): `sudo dnf install ShellCheck`
- macOS: `brew install shellcheck`
- Windows: ShellCheck is not available as a standalone native binary. Run it inside WSL (`sudo apt-get install shellcheck` in WSL), or use the [ShellCheck GitHub Action](https://github.com/marketplace/actions/shellcheck) in CI. `ci.yml` runs on `ubuntu-24.04` and `apache-rat.yml` runs on `ubuntu-latest`; both GitHub-hosted runner images ship `shellcheck` pre-installed.
---
## 6. License Compliance
### Python dependency license audit
Checks that every direct Python dependency carries a recognized license category compatible with Apache redistribution policy:
```bash
python scripts/check_licenses.py
```
The script checks installed direct dependencies and exits `0` as long as no `RESTRICTED` (GPL/Copyleft) licenses are found. It prints `[WARN] UNKNOWN` for packages whose metadata does not self-report a recognized license string (e.g., `pytest 9.1.1`). Verify those manually at [https://www.apache.org/legal/resolved.html](https://www.apache.org/legal/resolved.html).
This check runs in a **separate** `license-check.yml` workflow, not in `ci.yml`.
### Apache source header audit
Verifies that every tracked source file contains the Apache License 2.0 header. This check runs in a **separate** `apache-rat.yml` workflow, not in `ci.yml`. Download the RAT jar once (or reuse an existing copy):
```bash
curl -sL https://repo1.maven.org/maven2/org/apache/rat/apache-rat/0.17/apache-rat-0.17.jar -o apache-rat-0.17.jar
```
Then run it from the repository root:
```bash
java -jar apache-rat-0.17.jar --input-exclude-file .rat-excludes -- .
```
The `-E` flag is deprecated; use `--input-exclude-file` as shown above (matching the CI workflow).
The exclusion list is in `.rat-excludes`. Files listed there (auto-generated files, binary assets, third-party dashboards) are intentionally exempt.
---
## 7. Running the Full Pre-Commit Suite
Run all checks that do not require a running stack:
```bash
source .venv/bin/activate
ruff check .
pytest tests/unit/extractor -v
pytest tests/unit/superset -v
cd dbt && dbt deps && dbt parse --profiles-dir . --target local && cd ..
sqlfluff lint dbt/models dbt/tests \
--exclude-rules structure.column_order,structure.join_condition_order,references.qualification
shellcheck --severity=warning \
docker/fineract-postgresql/initdb/001_init_fineract_databases.sh \
docker/postgres-warehouse/initdb/002_create_warehouse_roles.sh \
docker/superset/init_superset.sh \
docker/superset/refresh_superset_assets.sh \
scripts/bootstrap_fineract_source.sh \
scripts/bootstrap_source.sh \
scripts/common.sh \
scripts/run_extractor_backfill.sh \
scripts/run_extractor_incremental.sh \
scripts/run_pipeline.sh \
scripts/stop_fineract_backend.sh \
scripts/verify_extraction.sh
python scripts/check_licenses.py
```
> The `shellcheck` file list above must match both the "Shell (ShellCheck)" subsection in §5 and the `Lint - shellcheck` step in `.github/workflows/ci.yml`. If you add or remove a shell script, update all three.
`ruff`, `pytest`, `dbt parse`, and `sqlfluff` run in the main `ci.yml` workflow on every push. `shellcheck` also runs in `ci.yml`. `python scripts/check_licenses.py` runs in the separate `license-check.yml` workflow. Apache RAT runs in the separate `apache-rat.yml` workflow.
---
## 8. Verifying an Extraction End-to-End
After the stack is running and at least one pipeline run has completed, verify extraction correctness:
```bash
bash scripts/verify_extraction.sh
```
This script checks service health, confirms all five warehouse schemas exist, prints row counts for every raw table, and verifies at least one successful pipeline run is recorded in `meta.pipeline_state`.