78 lines
3.9 KiB
Python
78 lines
3.9 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import sqlite3
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from jarca_ledger_mcp.infrastructure.sqlite import migrations
|
|
from jarca_ledger_mcp.infrastructure.sqlite.migrations import init_db
|
|
|
|
FIXTURE = Path(__file__).parent / "fixtures" / "schema_v14.sql"
|
|
|
|
|
|
def _v14_db(path: Path) -> None:
|
|
with sqlite3.connect(path) as db:
|
|
db.executescript(FIXTURE.read_text())
|
|
db.execute("INSERT INTO accounts VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", ("a1", "Main", "bank", "Bank", "KRW", 1, "memo", "1234", "User", 100000, "2026-01-31", "", "ok", "cash", "2026-01-01", "2026-01-31"))
|
|
db.execute("INSERT INTO transactions (id,transaction_date,type,amount,account_id,created_at,updated_at) VALUES (?,?,?,?,?,?,?)", ("t1", "2026-01-15", "expense", -1200, "a1", "2026-01-15", "2026-01-15"))
|
|
db.execute("INSERT INTO asset_snapshots (id,basis_date,source_type,source_id,name,amount,closed_at,created_at) VALUES (?,?,?,?,?,?,?,?)", ("s1", "2026-01-31", "account", "a1", "Main", 100000, "2026-01-31T10:00:00", "2026-01-31T10:00:00"))
|
|
db.execute("INSERT INTO asset_snapshot_runs (id,basis_date,close_status,created_at) VALUES (?,?,?,?)", ("run1", "2026-01-31", "closed", "2026-01-31T10:00:00"))
|
|
db.execute("INSERT OR REPLACE INTO schema_meta VALUES ('schema_version','14')")
|
|
db.commit()
|
|
|
|
|
|
def _rows(path: Path, table: str):
|
|
with sqlite3.connect(path) as db:
|
|
return db.execute(f"SELECT * FROM {table} ORDER BY 1").fetchall()
|
|
|
|
|
|
def test_actual_v14_fixture_is_tag_ddl_and_migrates_preserving_rows(tmp_path):
|
|
path = tmp_path / "ledger.sqlite3"
|
|
_v14_db(path)
|
|
before_accounts, before_tx = _rows(path, "accounts"), _rows(path, "transactions")
|
|
init_db(str(path))
|
|
with sqlite3.connect(path) as db:
|
|
assert db.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0] == "15"
|
|
assert db.execute("SELECT amount, closed_at FROM account_snapshot WHERE id='s1'").fetchone() == (100000, "2026-01-31T10:00:00")
|
|
assert db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='asset_snapshot_runs'").fetchone() is None
|
|
assert db.execute("PRAGMA foreign_key_check").fetchall() == []
|
|
assert db.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
|
|
assert _rows(path, "accounts") == before_accounts
|
|
assert _rows(path, "transactions") == before_tx
|
|
|
|
|
|
def test_actual_v14_migration_is_idempotent_and_snapshot_unchanged(tmp_path):
|
|
path = tmp_path / "ledger.sqlite3"
|
|
_v14_db(path)
|
|
init_db(str(path))
|
|
first = _rows(path, "account_snapshot")
|
|
init_db(str(path))
|
|
assert _rows(path, "account_snapshot") == first
|
|
|
|
|
|
def test_migration_failure_rolls_back_legacy_renames_and_metadata(tmp_path, monkeypatch):
|
|
path = tmp_path / "ledger.sqlite3"
|
|
_v14_db(path)
|
|
original = migrations.migrate_db
|
|
def fail(conn):
|
|
raise RuntimeError("injected")
|
|
monkeypatch.setattr(migrations, "migrate_db", fail)
|
|
with pytest.raises(RuntimeError, match="injected"):
|
|
init_db(str(path))
|
|
with sqlite3.connect(path) as db:
|
|
assert db.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0] == "14"
|
|
assert db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='asset_snapshots'").fetchone()
|
|
assert not db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='account_snapshot'").fetchone()
|
|
monkeypatch.setattr(migrations, "migrate_db", original)
|
|
|
|
|
|
def test_new_init_has_same_contract(tmp_path):
|
|
path = tmp_path / "new.sqlite3"
|
|
init_db(str(path))
|
|
with sqlite3.connect(path) as db:
|
|
assert db.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0] == "15"
|
|
cols = {r[1] for r in db.execute("PRAGMA table_info(account_snapshot)")}
|
|
assert {"monthly_settlement_id", "verification_status", "evidence_kind"} <= cols
|