test(mine): complete source adapter dispatch coverage

Restore the fresh-palace dry-run regression alongside the existing-empty
and initialized-sqlite_exact cases. Cover explicit failure for unsupported
adapter result types and daemon source-adapter dispatch. Ignore Hypothesis'
test cache so it cannot be committed.
This commit is contained in:
Grace Gettert 2026-08-10 17:19:48 +00:00
parent ba163b1de3
commit b676b2bd2f
3 changed files with 84 additions and 6 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
dist/
build/
__pycache__/
.hypothesis/
*.pyc
.pytest_cache/
mempal.yaml

View File

@ -120,6 +120,16 @@ class _MetadataAdapter(BaseSourceAdapter):
return AdapterSchema(version="1.0", fields={})
class _InvalidResultAdapter(BaseSourceAdapter):
name = "invalid-result"
def ingest(self, *, source, palace):
yield object()
def describe_schema(self):
return AdapterSchema(version="1.0", fields={})
def _hold_palace_lock(palace_path, ready_flag, release_flag):
"""Hold a writer lease in a separate process for contention coverage."""
from mempalace.palace import mine_palace_lock
@ -236,12 +246,11 @@ def test_mine_source_dry_run_prevents_direct_collection_and_kg_mutations(monkeyp
register("direct-mutation", _DirectMutationAdapter)
monkeypatch.setattr(cli, "MempalaceConfig", _FakeConfig)
def read_only_collection(_palace_path, *, create):
assert create is False
raise FileNotFoundError
monkeypatch.setattr(palace, "get_collection", read_only_collection)
# Dry runs must never reach the backend at all (see
# test_mine_source_dry_run_never_opens_existing_collection for the
# dedicated assertion); failing here catches a regression that
# reintroduces a real `get_collection` call in the dry-run path.
monkeypatch.setattr(palace, "get_collection", lambda *_a, **_k: pytest.fail("must not open"))
monkeypatch.setattr(knowledge_graph, "KnowledgeGraph", _FakeKnowledgeGraph)
drawers_written = cli.mine_source_adapter(
@ -323,6 +332,25 @@ def test_mine_source_dry_run_existing_uninitialized_palace_creates_no_chroma_art
assert not (palace / "chroma.sqlite3").exists()
def test_mine_source_dry_run_fresh_palace_creates_no_backend_artifacts(tmp_path, monkeypatch):
"""Dry runs must not materialize a nonexistent Chroma palace."""
register("fixture", _FixtureAdapter)
palace = tmp_path / "fresh-palace"
monkeypatch.setenv("MEMPALACE_BACKEND", "chroma")
assert (
cli.mine_source_adapter(
source_name="fixture",
source_path="/source",
palace_path=str(palace),
dry_run=True,
)
== 1
)
assert not palace.exists()
def test_mine_source_dry_run_preserves_initialized_sqlite_exact_artifacts(tmp_path, monkeypatch):
"""Dry runs must not open or alter an existing sqlite_exact backend."""
from mempalace.backends.base import PalaceRef
@ -387,6 +415,19 @@ def test_mine_source_accepts_non_incremental_metadata(monkeypatch, recwarn):
assert "non-incremental item metadata" in str(recwarn.pop(RuntimeWarning).message)
def test_mine_source_rejects_unsupported_adapter_results():
"""Unexpected adapter yields must fail rather than being silently dropped."""
register("invalid-result", _InvalidResultAdapter)
with pytest.raises(TypeError, match="unsupported result type object"):
cli.mine_source_adapter(
source_name="invalid-result",
source_path="/source",
palace_path="/fake/palace",
dry_run=True,
)
def test_mine_source_holds_writer_lease_before_opening_handles(monkeypatch):
from mempalace import knowledge_graph, palace

View File

@ -1182,6 +1182,42 @@ def test_run_mine_invalid_mode_returns_structured_error(tmp_path):
assert out["exit_code"] == 2
def test_run_mine_source_adapter_dispatches_through_adapter_runner(tmp_path, monkeypatch):
"""Daemon mine jobs preserve the CLI's explicit source-adapter dispatch."""
from mempalace import cli, service
received = {}
def fake_mine_source_adapter(**kwargs):
received.update(kwargs)
return 3
monkeypatch.setattr(cli, "mine_source_adapter", fake_mine_source_adapter)
palace = tmp_path / "palace"
out = service.run_mine(
{
"palace_path": str(palace),
"source": "/source",
"source_adapter": "fixture",
"dry_run": True,
}
)
assert received == {
"source_name": "fixture",
"source_path": "/source",
"palace_path": str(palace.resolve()),
"dry_run": True,
}
assert out == {
"success": True,
"kind": "mine",
"mode": "source",
"dry_run": True,
"exit_code": 0,
}
def test_run_mcp_tool_rejects_non_dict_arguments():
from mempalace import service