State dict injection now works by parameter name (prev/state, annotation not checked) with DictPrev/DictState tags taking precedence; DictPre/DictPost remain as aliases. MigrationResult is renamed to MigrationReport with fields original/version/applied; the old type alias and a deprecated .migrations property remain. Old symbols stay covered by the original tests; new-style tests import from the kanta root. Includes some unrelated ruff formatting.
130 lines
4.3 KiB
Python
130 lines
4.3 KiB
Python
"""Tests for the ``python -m kanta`` CLI output formatting."""
|
|
|
|
import sys
|
|
from datetime import UTC, datetime
|
|
|
|
from kanta.__main__ import (
|
|
_extra_import_paths,
|
|
_format_ts,
|
|
_import_dotted,
|
|
_import_kanta_object,
|
|
main,
|
|
)
|
|
from kanta.serialization import JsonSerializer
|
|
from kanta.serialization.framing import LineFramer
|
|
from kanta.structs import ChangeRecord, Snapshot
|
|
|
|
|
|
def test_format_ts_strips_microseconds():
|
|
"""Timestamps are rendered without microsecond precision."""
|
|
dt = datetime(2026, 8, 12, 10, 6, 52, 375398, tzinfo=UTC)
|
|
assert _format_ts(dt) == "2026-08-12 10:06:52"
|
|
|
|
|
|
def test_extra_import_paths_are_temporary(tmp_path, monkeypatch):
|
|
"""CWD and nearby venv site-packages are added only for the import block."""
|
|
parent_dir = tmp_path / "parent"
|
|
cwd = parent_dir / "child"
|
|
venv_site = (
|
|
cwd
|
|
/ ".venv"
|
|
/ "lib"
|
|
/ f"python{sys.version_info.major}.{sys.version_info.minor}"
|
|
/ "site-packages"
|
|
)
|
|
venv_site.mkdir(parents=True)
|
|
parent_venv_site = (
|
|
parent_dir
|
|
/ ".venv"
|
|
/ "lib"
|
|
/ f"python{sys.version_info.major}.{sys.version_info.minor}"
|
|
/ "site-packages"
|
|
)
|
|
parent_venv_site.mkdir(parents=True)
|
|
|
|
monkeypatch.chdir(cwd)
|
|
cwd_str = str(cwd)
|
|
venv = str(venv_site)
|
|
parent_venv = str(parent_venv_site)
|
|
|
|
before = sys.path.copy()
|
|
with _extra_import_paths():
|
|
during = sys.path.copy()
|
|
assert cwd_str in during
|
|
assert venv in during
|
|
assert parent_venv in during
|
|
assert during.index(cwd_str) < during.index(venv) < during.index(parent_venv)
|
|
assert sys.path == before
|
|
|
|
|
|
def test_extra_import_paths_ignores_other_python_versions(tmp_path, monkeypatch):
|
|
"""Only the site-packages for the running Python version is picked up."""
|
|
current_site = (
|
|
tmp_path
|
|
/ ".venv"
|
|
/ "lib"
|
|
/ f"python{sys.version_info.major}.{sys.version_info.minor}"
|
|
/ "site-packages"
|
|
)
|
|
other_site = tmp_path / ".venv" / "lib" / "python9.9" / "site-packages"
|
|
current_site.mkdir(parents=True)
|
|
other_site.mkdir(parents=True)
|
|
|
|
monkeypatch.chdir(tmp_path)
|
|
with _extra_import_paths():
|
|
assert str(current_site) in sys.path
|
|
assert str(other_site) not in sys.path
|
|
|
|
|
|
def test_cli_snapshot_line_format(tmp_path, capsys):
|
|
"""Snapshot lines are timestamped and colored with metadata."""
|
|
path = tmp_path / "test.kantadb"
|
|
ts = datetime(2026, 8, 12, 10, 6, 52, 375398, tzinfo=UTC)
|
|
mtime = datetime(2026, 8, 12, 9, 0, 0, tzinfo=UTC)
|
|
serializer = JsonSerializer()
|
|
framer = LineFramer()
|
|
|
|
snapshot = Snapshot(ts=ts, v=1, m=mtime, state={"counter": 5})
|
|
change = ChangeRecord(ts=ts, a="inc", v=1, u="user1", diff={"counter": 6})
|
|
data = framer.frame_snapshot(
|
|
serializer.encode(snapshot), record_offset=0
|
|
) + framer.frame_change(serializer.encode(change), record_offset=0)
|
|
path.write_bytes(data)
|
|
|
|
code = main([str(path)])
|
|
assert code == 0
|
|
|
|
err = capsys.readouterr().err
|
|
# No microsecond precision anywhere.
|
|
assert "10:06:52" in err
|
|
assert "10:06:52.375398" not in err
|
|
|
|
# Snapshot line: bright white snapshot/sN, white version/mtime, dark size.
|
|
assert "\x1b[97msnapshot s0" in err
|
|
assert "\x1b[38;5;250m v1 2026-08-12 09:00:00" in err
|
|
assert "\x1b[38;5;242m 13 B" in err
|
|
|
|
|
|
def test_import_dotted_from_file_path(tmp_path):
|
|
"""--data can be a filesystem path with an optional colon-separated symbol."""
|
|
module = tmp_path / "models.py"
|
|
module.write_text("class Data:\n pass\n")
|
|
result = _import_dotted(f"{module}:Data")
|
|
assert result.__name__ == "Data"
|
|
|
|
|
|
def test_import_kanta_object_from_file_path(tmp_path):
|
|
"""--kanta can be a filesystem path; default symbol is ``kanta``."""
|
|
module = tmp_path / "database.py"
|
|
module.write_text("class Kanta:\n pass\nkanta = Kanta()\n")
|
|
result = _import_kanta_object(str(module))
|
|
assert type(result).__name__ == "Kanta"
|
|
|
|
|
|
def test_import_kanta_object_from_file_path_with_symbol(tmp_path):
|
|
"""--kanta can be a filesystem path with an explicit colon-separated symbol."""
|
|
module = tmp_path / "database.py"
|
|
module.write_text("class CustomKanta:\n pass\nmy_kanta = CustomKanta()\n")
|
|
result = _import_kanta_object(f"{module}:my_kanta")
|
|
assert type(result).__name__ == "CustomKanta"
|