Files
stack/tests/cli/test_zot_deep.py
kert 5c446f832e test(zot): copy zotero.sqlite to tmp before parity check
Verify-parity opens the DB with PRAGMA journal_mode=WAL, which contends
with the running Zotero container's lock and fails the test with
"database is locked". Snapshot via shutil.copy2 first — schema rows
(itemTypes/fields/creatorTypes) are stable so a hot copy is fine for
this test's purpose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:11:47 -04:00

138 lines
4.0 KiB
Python

"""Deep exercising tests for cli/zot.py."""
from __future__ import annotations
import sqlite3
from pathlib import Path
from unittest.mock import patch
from typer.testing import CliRunner
from cli.zot import app
runner = CliRunner()
class TestHoldZotero:
@patch("cli.zot.subprocess.run")
def test_hold_true(self, mc_sub):
from cli.zot import _hold_zotero
result = _hold_zotero(lambda: 42, hold=True)
assert result == 42
assert mc_sub.call_count == 2
def test_hold_false(self):
from cli.zot import _hold_zotero
result = _hold_zotero(lambda: 99, hold=False)
assert result == 99
class TestResolve:
def test_valid_path(self, tmp_path):
from cli.zot import _resolve
db = tmp_path / "z.sqlite"
db.write_text("")
assert _resolve(db) == db
def test_missing_path(self, tmp_path):
import typer
from cli.zot import _resolve
with patch("cli.zot._DEFAULT_DB", tmp_path / "nope.sqlite"):
try:
_resolve(None)
assert False, "should raise"
except typer.BadParameter:
pass
class TestDumpSchema:
@patch("cli.zot.subprocess.run")
@patch(
"zot.ops.dump_schema",
return_value={
"TYPE_MAP": {"doc": 14},
"FIELD_MAP": {"title": 1},
"CREATOR_TYPES": {"author": 10},
},
)
def test_python(self, mc_dump, mc_sub, tmp_path):
db = tmp_path / "z.sqlite"
sqlite3.connect(str(db)).close()
result = runner.invoke(app, ["dump-schema", "--db", str(db)])
assert result.exit_code == 0
assert "TYPE_MAP" in result.output
@patch("cli.zot.subprocess.run")
@patch("zot.ops.dump_schema", return_value={"TYPE_MAP": {"doc": 14}})
def test_json(self, mc_dump, mc_sub, tmp_path):
db = tmp_path / "z.sqlite"
db.write_text("")
result = runner.invoke(app, ["dump-schema", "--db", str(db), "--json"])
assert result.exit_code == 0
class TestFixDates:
@patch("cli.zot.subprocess.run")
@patch("zot.ops.fix_dates", return_value={"normalized": 5, "triggers_installed": 2})
def test_basic(self, mc_fix, mc_sub, tmp_path):
db = tmp_path / "z.sqlite"
db.write_text("")
result = runner.invoke(app, ["fix-dates", "--db", str(db), "--no-hold"])
assert result.exit_code == 0
class TestFixKeys:
@patch("cli.zot.subprocess.run")
@patch(
"zot.ops.fix_keys",
return_value={
"items": 3,
"collections": 0,
"storage_renames": 1,
"remaining": 0,
},
)
def test_basic(self, mc_fix, mc_sub, tmp_path):
db = tmp_path / "z.sqlite"
db.write_text("")
result = runner.invoke(app, ["fix-keys", "--db", str(db), "--no-hold"])
assert result.exit_code == 0
class TestFixFields:
@patch("cli.zot.subprocess.run")
@patch(
"zot.ops.fix_fields",
return_value={"remapped": 2, "deleted_no_mapping": 1, "deleted_conflict": 0},
)
def test_basic(self, mc_fix, mc_sub, tmp_path):
db = tmp_path / "z.sqlite"
db.write_text("")
result = runner.invoke(app, ["fix-fields", "--db", str(db), "--no-hold"])
assert result.exit_code == 0
class TestVerifyParity:
def test_with_real_db(self, tmp_path):
import shutil
db = Path("data/zotero/data/zotero.sqlite")
if not db.exists():
import pytest
pytest.skip("zotero.sqlite not available")
# Copy to tmp so the parity check (which opens WAL) doesn't
# contend with the Zotero container's lock on the live file.
# Schema rows (itemTypes/fields/creatorTypes) are stable so a
# hot copy is fine for this test's purpose.
snap = tmp_path / "zotero.sqlite"
shutil.copy2(db, snap)
result = runner.invoke(app, ["verify-parity", "--db", str(snap)])
assert result.exit_code == 0
assert "parity OK" in result.output