spb/air Public MIT
AIR — The Language of Accounting.
Python 100%
1# =============================================================================2# Projet : AIR — Accounting Intermediate Representation3# Auteur : Simon-Pierre Boucher4# Contact : contact@spboucher.ai5# Fichier : test_workspace.py6# Description : Tests — the AIR home (managed local data) and the standalone CLI flows.7# =============================================================================8"""AIR home + CLI tests: init, compile into the home, archives, status,9incremental recompile, statements, and offline QBO export — all without any10third-party system."""11from __future__ import annotations1213import json14from pathlib import Path1516import pytest1718from kernel.workspace import Workspace, WorkspaceError19from sdk.cli import main2021ROOT = Path(__file__).resolve().parents[1]22POLICY = str(ROOT / "alsl" / "policies" / "ca-qc-2026.yaml")23DEMO = str(ROOT / "tests" / "fixtures" / "demo_document.yaml")242526def test_workspace_init_open_and_guardrails(tmp_path: Path) -> None:27 home = tmp_path / "books"28 ws = Workspace.init(home, name="acme", policies=POLICY)29 assert ws.meta["name"] == "acme"30 with pytest.raises(WorkspaceError, match="already initialized"):31 Workspace.init(home)32 with pytest.raises(WorkspaceError, match="no AIR home"):33 Workspace.open(tmp_path / "elsewhere")343536def test_cli_full_standalone_lifecycle(tmp_path: Path, capsys) -> None:37 home = str(tmp_path / "books")3839 assert main(["init", "--home", home, "--policies", POLICY]) == 040 assert main(["compile", DEMO, "--home", home]) == 041 out = capsys.readouterr().out42 assert "8 entries" in out and "document archived" in out4344 # replaying the same document is idempotent (0 appended)45 assert main(["compile", DEMO, "--home", home]) == 046 assert "+0 appended" in capsys.readouterr().out4748 # data is managed inside the home49 ws = Workspace.open(home)50 status = ws.status()51 assert status["entries"] == 852 assert status["chain_valid"] is True53 assert status["documents_archived"] == 1 # content-addressed, no duplicate5455 # statements straight from the home56 assert main(["report", "trial-balance", "--home", home]) == 057 tb = capsys.readouterr().out58 assert "TOTAL" in tb5960 assert main(["status", "--home", home]) == 061 assert "chain_valid" in capsys.readouterr().out626364def test_cli_incremental_into_home(tmp_path: Path, capsys) -> None:65 home = str(tmp_path / "books")66 main(["init", "--home", home, "--policies", POLICY])67 main(["compile", DEMO, "--home", home])68 capsys.readouterr()6970 corrected = tmp_path / "demo_v2.yaml"71 corrected.write_text(72 Path(DEMO).read_text().replace('amount: "333.33"', 'amount: "349.99"'),73 encoding="utf-8",74 )75 assert main(["recompile", DEMO, str(corrected), "--home", home]) == 076 out = capsys.readouterr().out77 assert "~1 changed" in out and "1 reversal(s)" in out7879 status = Workspace.open(home).status()80 assert status["entries"] == 10 # 8 + reversal + replacement81 assert status["documents_archived"] == 282 assert status["chain_valid"] is True838485def test_cli_qbo_export_needs_no_quickbooks(tmp_path: Path, capsys) -> None:86 home = str(tmp_path / "books")87 main(["init", "--home", home, "--policies", POLICY])88 capsys.readouterr()89 assert main(["compile", DEMO, "--home", home, "--backend", "qbo-export"]) == 090 out = capsys.readouterr().out91 assert "QBO JSON export" in out9293 exports = list(Workspace.open(home).exports_dir.glob("qbo_journal_*.json"))94 assert len(exports) == 195 data = json.loads(exports[0].read_text())96 assert len(data["journal_entries"]) == 897 assert data["journal_entries"][0]["Line"] # QBO JournalEntry shape9899100def test_cli_missing_policies_is_helpful(tmp_path: Path, capsys) -> None:101 home = str(tmp_path / "books")102 main(["init", "--home", home]) # no default policies103 capsys.readouterr()104 assert main(["compile", DEMO, "--home", home]) == 1105 assert "no policy set" in capsys.readouterr().err106