Skip to content

Commit 619e78e

Browse files
committed
test(add): cover coordinator conflict edge cases
1 parent 9a87b81 commit 619e78e

2 files changed

Lines changed: 95 additions & 0 deletions

File tree

‎tests/test_add_command.py‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,31 @@ def test_add_directory_calls_helper_for_each_file(self, tmp_path):
221221
assert "b.txt" in called_names
222222
assert "ignore.xyz" not in called_names
223223

224+
def test_add_directory_stops_after_dirty_rollback(self, tmp_path):
225+
import pytest
226+
227+
from openkb.add_coordinator import DirtyRollbackError
228+
229+
kb_dir = self._setup_kb(tmp_path)
230+
docs_dir = tmp_path / "docs"
231+
docs_dir.mkdir()
232+
(docs_dir / "a.md").write_text("# A")
233+
(docs_dir / "b.md").write_text("# B")
234+
dirty_error = DirtyRollbackError(
235+
"add",
236+
kb_dir / ".openkb" / "journal" / "retained.json",
237+
)
238+
239+
runner = CliRunner()
240+
with patch("openkb.cli.add_single_file", side_effect=dirty_error) as mock_add, \
241+
patch("openkb.cli._find_kb_dir", return_value=kb_dir):
242+
with pytest.raises(DirtyRollbackError) as exc_info:
243+
runner.invoke(cli, ["add", str(docs_dir)], catch_exceptions=False)
244+
245+
assert exc_info.value is dirty_error
246+
mock_add.assert_called_once()
247+
assert mock_add.call_args.args[0].name == "a.md"
248+
224249
def test_add_unsupported_extension(self, tmp_path):
225250
kb_dir = self._setup_kb(tmp_path)
226251
doc = tmp_path / "file.xyz"
@@ -525,6 +550,52 @@ def race_before_lock(openkb_dir):
525550
assert registry.get(synthetic)["doc_name"] == expected_doc_name
526551
assert (kb_dir / "wiki" / "sources" / f"{expected_doc_name}.json").exists()
527552

553+
def test_cloud_import_skips_if_same_doc_id_registered_after_fetch(self, tmp_path):
554+
import hashlib
555+
from contextlib import contextmanager
556+
557+
from openkb.cli import import_from_pageindex_cloud
558+
from openkb.locks import kb_ingest_lock as real_kb_ingest_lock
559+
from openkb.state import HashRegistry
560+
561+
kb_dir = self._setup_kb(tmp_path)
562+
cloud = self._cloud_data(doc_name="Cloud-Paper")
563+
path_key = "pageindex-cloud:cloud-1"
564+
synthetic = hashlib.sha256(path_key.encode("utf-8")).hexdigest()
565+
lock_calls = {"count": 0}
566+
567+
@contextmanager
568+
def register_same_doc_before_second_lock(openkb_dir):
569+
lock_calls["count"] += 1
570+
if lock_calls["count"] == 2:
571+
HashRegistry(openkb_dir / "hashes.json").add(
572+
synthetic,
573+
{
574+
"name": "Cloud Paper.pdf",
575+
"doc_name": "Cloud-Paper",
576+
"type": "pageindex_cloud",
577+
"origin": "cloud",
578+
"path": path_key,
579+
"source_path": "wiki/sources/Cloud-Paper.json",
580+
"doc_id": "cloud-1",
581+
},
582+
)
583+
with real_kb_ingest_lock(openkb_dir):
584+
yield
585+
586+
with patch("openkb.cli.prepare_cloud_import", return_value=cloud) as mock_prepare, \
587+
patch("openkb.cli.kb_ingest_lock", side_effect=register_same_doc_before_second_lock), \
588+
patch("openkb.cli.compile_long_doc") as mock_compile, \
589+
patch("openkb.add_coordinator.run_add_mutation") as mock_run, \
590+
patch("openkb.cli._setup_llm_key"):
591+
assert import_from_pageindex_cloud("cloud-1", kb_dir) == "skipped"
592+
593+
assert lock_calls["count"] == 2
594+
mock_prepare.assert_called_once()
595+
mock_compile.assert_not_called()
596+
mock_run.assert_not_called()
597+
assert HashRegistry(kb_dir / ".openkb" / "hashes.json").is_known(synthetic)
598+
528599
def test_cloud_import_propagates_dirty_rollback(self, tmp_path):
529600
import pytest
530601

‎tests/test_locks.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
atomic_write_json,
1212
atomic_write_text,
1313
kb_ingest_lock,
14+
kb_ingest_lock_held,
1415
kb_read_lock,
1516
)
1617

@@ -76,6 +77,29 @@ def test_write_lock_can_take_nested_read(tmp_path):
7677
assert (openkb_dir / "ingest.lock").exists()
7778

7879

80+
def test_kb_ingest_lock_held_is_exclusive_and_thread_local(tmp_path):
81+
openkb_dir = tmp_path / ".openkb"
82+
worker_seen = []
83+
84+
assert not kb_ingest_lock_held(openkb_dir)
85+
86+
with kb_read_lock(openkb_dir):
87+
assert not kb_ingest_lock_held(openkb_dir)
88+
89+
with kb_ingest_lock(openkb_dir):
90+
assert kb_ingest_lock_held(openkb_dir)
91+
92+
worker = threading.Thread(
93+
target=lambda: worker_seen.append(kb_ingest_lock_held(openkb_dir))
94+
)
95+
worker.start()
96+
worker.join(timeout=2)
97+
98+
assert not worker.is_alive()
99+
assert worker_seen == [False]
100+
assert not kb_ingest_lock_held(openkb_dir)
101+
102+
79103
def test_atomic_write_text_replaces_file(tmp_path):
80104
target = tmp_path / "nested" / "file.txt"
81105
atomic_write_text(target, "first")

0 commit comments

Comments
 (0)