diff --git a/README.md b/README.md index 1d6b74d..7fbb70a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Scheduling options are documented in [`docs/scheduling.md`](docs/scheduling.md). ## Current capabilities -- [x] YAML data contracts with column rules and foreign keys +- [x] YAML data contracts with column rules and foreign keys (orders, customers) - [x] Schema validation against contract columns - [x] Null, uniqueness, freshness, and referential-integrity checks - [x] Check run history with SQLite (default) or PostgreSQL @@ -101,7 +101,8 @@ python -m src.dqo.cli history --contract orders ├── dags/ │ └── dqo_orders_checks.py ├── contracts/ -│ └── orders.yml +│ ├── orders.yml +│ └── customers.yml ├── data/samples/ ├── docs/ │ ├── architecture.md diff --git a/contracts/customers.yml b/contracts/customers.yml new file mode 100644 index 0000000..c182685 --- /dev/null +++ b/contracts/customers.yml @@ -0,0 +1,15 @@ +name: customers +version: "1.0" +description: Customer dimension contract for retail analytics + +columns: + customer_id: + type: string + nullable: false + unique: true + full_name: + type: string + nullable: false + region: + type: string + nullable: false diff --git a/data/samples/customers_invalid.csv b/data/samples/customers_invalid.csv new file mode 100644 index 0000000..5852b9a --- /dev/null +++ b/data/samples/customers_invalid.csv @@ -0,0 +1,4 @@ +customer_id,full_name,region +C001,Ada Lovelace,EMEA +C001,Grace Hopper,AMER +,Missing Name,APAC diff --git a/tests/conftest.py b/tests/conftest.py index c0d6522..b69aef3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,6 +14,11 @@ def orders_contract() -> object: return load_contract(ROOT / "contracts" / "orders.yml") +@pytest.fixture +def customers_contract() -> object: + return load_contract(ROOT / "contracts" / "customers.yml") + + @pytest.fixture def valid_orders() -> list[dict[str, str]]: return load_csv(ROOT / "data" / "samples" / "orders.csv") diff --git a/tests/test_contracts.py b/tests/test_contracts.py index 3fe7cd5..971767a 100644 --- a/tests/test_contracts.py +++ b/tests/test_contracts.py @@ -10,3 +10,16 @@ def test_load_orders_contract() -> None: assert contract.freshness is not None assert contract.freshness.column == "updated_at" assert len(contract.foreign_keys) == 1 + + +def test_load_customers_contract() -> None: + contract = load_contract(Path("contracts/customers.yml")) + assert contract.name == "customers" + assert contract.freshness is None + assert {column.name for column in contract.columns} == { + "customer_id", + "full_name", + "region", + } + customer_id_column = next(column for column in contract.columns if column.name == "customer_id") + assert customer_id_column.unique is True diff --git a/tests/test_runner.py b/tests/test_runner.py index a52cad6..ad5bc5f 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -23,3 +23,17 @@ def test_run_contract_file_fails_for_invalid_dataset(tmp_path: Path) -> None: ) assert summary.passed is False assert summary.failed_results + + +def test_run_checks_passes_for_valid_customers(customers_contract, customers) -> None: + results = run_checks(customers_contract, customers) + assert all(result.status.value != "failed" for result in results) + + +def test_run_contract_file_fails_for_invalid_customers() -> None: + summary = run_contract_file( + Path("contracts/customers.yml"), + Path("data/samples/customers_invalid.csv"), + ) + assert summary.passed is False + assert summary.failed_results