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>
138 lines
4.0 KiB
Python
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
|