"""Unit tests for db.py, files.py, and config.py: DB helpers, name/position counters, settings merge.""" import sqlite3 from collections.abc import Iterator from pathlib import Path import pytest import db import files from config import deep_merge @pytest.fixture(autouse=True) def reset_counters() -> Iterator[None]: db.COUNTERS.clear() yield db.COUNTERS.clear() @pytest.fixture def test_db(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Iterator[sqlite3.Connection]: """Temporary SQLite database with full schema applied.""" monkeypatch.setattr(db, "DB_PATH", tmp_path / "test.db") monkeypatch.setattr(files, "DATA_DIR", tmp_path) monkeypatch.setattr(files, "IMAGES_DIR", tmp_path / "images") files.init_dirs() db.init_db() connection = db.conn() yield connection connection.close() # ── deep_merge ──────────────────────────────────────────────────────────────── def test_deep_merge_basic() -> None: result = deep_merge({"a": 1, "b": 2}, {"b": 3, "c": 4}) assert result == {"a": 1, "b": 3, "c": 4} def test_deep_merge_nested() -> None: base = {"x": {"a": 1, "b": 2}} override = {"x": {"b": 99, "c": 3}} result = deep_merge(base, override) assert result == {"x": {"a": 1, "b": 99, "c": 3}} def test_deep_merge_list_replacement() -> None: base = {"items": [1, 2, 3]} override = {"items": [4, 5]} result = deep_merge(base, override) assert result["items"] == [4, 5] def test_deep_merge_does_not_mutate_base() -> None: base = {"a": {"x": 1}} deep_merge(base, {"a": {"x": 2}}) assert base["a"]["x"] == 1 # ── uid / now ──────────────────────────────────────────────────────────────── def test_uid_unique() -> None: assert db.uid() != db.uid() def test_uid_is_string() -> None: result = db.uid() assert isinstance(result, str) assert len(result) == 36 # UUID4 format def test_now_is_string() -> None: result = db.now() assert isinstance(result, str) assert "T" in result # ISO format # ── next_name ──────────────────────────────────────────────────────────────── def test_next_name_increments() -> None: assert db.next_name("Room") == "Room 1" assert db.next_name("Room") == "Room 2" assert db.next_name("Room") == "Room 3" def test_next_name_independent_prefixes() -> None: assert db.next_name("Room") == "Room 1" assert db.next_name("Shelf") == "Shelf 1" assert db.next_name("Room") == "Room 2" # ── next_pos / next_root_pos ──────────────────────────────────────────────── def test_next_root_pos_empty(test_db: sqlite3.Connection) -> None: pos = db.next_root_pos(test_db, "rooms") assert pos == 1 def test_next_root_pos_with_rows(test_db: sqlite3.Connection) -> None: ts = db.now() test_db.execute("INSERT INTO rooms VALUES (?,?,?,?)", ["r1", "Room 1", 1, ts]) test_db.execute("INSERT INTO rooms VALUES (?,?,?,?)", ["r2", "Room 2", 2, ts]) test_db.commit() assert db.next_root_pos(test_db, "rooms") == 3 def test_next_pos_empty(test_db: sqlite3.Connection) -> None: ts = db.now() test_db.execute("INSERT INTO rooms VALUES (?,?,?,?)", ["r1", "Room", 1, ts]) test_db.commit() pos = db.next_pos(test_db, "cabinets", "room_id", "r1") assert pos == 1 def test_next_pos_with_children(test_db: sqlite3.Connection) -> None: ts = db.now() test_db.execute("INSERT INTO rooms VALUES (?,?,?,?)", ["r1", "Room", 1, ts]) test_db.execute("INSERT INTO cabinets VALUES (?,?,?,?,?,?,?,?)", ["c1", "r1", "C1", None, None, None, 1, ts]) test_db.execute("INSERT INTO cabinets VALUES (?,?,?,?,?,?,?,?)", ["c2", "r1", "C2", None, None, None, 2, ts]) test_db.commit() pos = db.next_pos(test_db, "cabinets", "room_id", "r1") assert pos == 3 # ── init_db ──────────────────────────────────────────────────────────────────── def test_init_db_creates_tables(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(db, "DB_PATH", tmp_path / "test.db") db.init_db() connection = sqlite3.connect(tmp_path / "test.db") tables = {row[0] for row in connection.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()} connection.close() assert {"rooms", "cabinets", "shelves", "books"}.issubset(tables) # ── init_dirs ───────────────────────────────────────────────────────────────── def test_init_dirs_creates_images_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(files, "DATA_DIR", tmp_path) monkeypatch.setattr(files, "IMAGES_DIR", tmp_path / "images") files.init_dirs() assert (tmp_path / "images").is_dir()