Improve test coverage for cli, filter, registry, and migrations
These files were not touched by the recent performance and correctness PRs but had coverage gaps. Adds tests for: - CLI: serve, queue init/migrate, config loading, cli() entry point including MigrationRequiredError exit path - filter: _default_supported_extensions, __call__ watchfiles callback, FileFilter with supported_extensions=None - registry: resolve_adhoc_fetcher with bucket-less S3 URI - migrations: pragma no-cover on unreachable schema upgrade path (no diff migrations exist until SCHEMA_VERSION > 1)
This commit is contained in:
parent
64f2b7b7d2
commit
1bf2505093
4 changed files with 153 additions and 6 deletions
|
|
@ -36,9 +36,7 @@ async def apply_migrations(conn: aiosqlite.Connection) -> int:
|
|||
)
|
||||
else:
|
||||
current = row[0]
|
||||
if current < SCHEMA_VERSION:
|
||||
# No diff migrations exist yet — future versions add UPDATE/ALTER
|
||||
# statements between here and the version bump.
|
||||
if current < SCHEMA_VERSION: # pragma: no cover - no migrations yet
|
||||
await _exec(conn, "UPDATE schema_version SET version = ?", SCHEMA_VERSION)
|
||||
|
||||
await conn.commit()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
"""haiku-ingester run-batch CLI: echoes the batch report and exits non-zero
|
||||
when any job dead-letters. IngesterApp is patched so no real ingestion runs."""
|
||||
"""haiku-ingester CLI: exercises every subcommand via CliRunner with
|
||||
IngesterApp / open_queue patched out so no real ingestion runs."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from haiku.rag.ingester.app import BatchReport
|
||||
|
|
@ -11,6 +12,9 @@ from haiku.rag.ingester.cli import _cli as cli
|
|||
runner = CliRunner()
|
||||
|
||||
|
||||
# --- helpers ---
|
||||
|
||||
|
||||
def _fake_app(report: BatchReport, monkeypatch) -> AsyncMock:
|
||||
fake = AsyncMock()
|
||||
fake.run_batch.return_value = report
|
||||
|
|
@ -44,3 +48,118 @@ def test_run_batch_exits_nonzero_when_sweep_fails(monkeypatch):
|
|||
|
||||
assert result.exit_code == 1
|
||||
assert "failed to sweep: docs" in result.output
|
||||
|
||||
|
||||
# --- serve ---
|
||||
|
||||
|
||||
def test_serve_invokes_app(monkeypatch):
|
||||
fake = AsyncMock()
|
||||
monkeypatch.setattr("haiku.rag.ingester.cli.IngesterApp", lambda **_: fake)
|
||||
|
||||
result = runner.invoke(cli, ["serve", "--db", "x.lancedb", "--no-api"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
fake.serve.assert_awaited_once_with(api=False)
|
||||
|
||||
|
||||
def test_serve_passes_host_and_port(monkeypatch):
|
||||
fake = AsyncMock()
|
||||
captured = {}
|
||||
|
||||
def _capture(**kwargs):
|
||||
captured.update(kwargs)
|
||||
return fake
|
||||
|
||||
monkeypatch.setattr("haiku.rag.ingester.cli.IngesterApp", _capture)
|
||||
|
||||
result = runner.invoke(
|
||||
cli, ["serve", "--db", "x.lancedb", "--host", "0.0.0.0", "--port", "9999"]
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert captured["config"].ingester.api.host == "0.0.0.0"
|
||||
assert captured["config"].ingester.api.port == 9999
|
||||
|
||||
|
||||
# --- queue init / migrate ---
|
||||
|
||||
|
||||
def test_queue_init(tmp_path, monkeypatch):
|
||||
fake_conn = AsyncMock()
|
||||
monkeypatch.setattr(
|
||||
"haiku.rag.ingester.cli.open_queue", AsyncMock(return_value=fake_conn)
|
||||
)
|
||||
|
||||
db_path = tmp_path / "queue.db"
|
||||
result = runner.invoke(cli, ["queue", "init", "--queue", str(db_path)])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "initialized" in result.output
|
||||
|
||||
|
||||
def test_queue_migrate(tmp_path, monkeypatch):
|
||||
fake_conn = AsyncMock()
|
||||
monkeypatch.setattr(
|
||||
"haiku.rag.ingester.cli.open_queue", AsyncMock(return_value=fake_conn)
|
||||
)
|
||||
|
||||
db_path = tmp_path / "queue.db"
|
||||
result = runner.invoke(cli, ["queue", "migrate", "--queue", str(db_path)])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "up to date" in result.output
|
||||
|
||||
|
||||
# --- config loading ---
|
||||
|
||||
|
||||
def test_load_config_with_explicit_path(tmp_path):
|
||||
from haiku.rag.ingester.cli import _load_config_with_override
|
||||
|
||||
config_file = tmp_path / "test.yaml"
|
||||
config_file.write_text("embeddings:\n model:\n provider: ollama\n")
|
||||
|
||||
config = _load_config_with_override(config_file)
|
||||
assert config is not None
|
||||
|
||||
|
||||
def test_load_config_falls_back_to_default(monkeypatch):
|
||||
from haiku.rag.ingester.cli import _load_config_with_override
|
||||
|
||||
monkeypatch.setattr("haiku.rag.ingester.cli.find_config_file", lambda _: None)
|
||||
|
||||
config = _load_config_with_override(None)
|
||||
assert config is not None
|
||||
|
||||
|
||||
# --- cli() entry point ---
|
||||
|
||||
|
||||
def test_cli_entry_point(monkeypatch):
|
||||
from haiku.rag.ingester.cli import cli as cli_entry
|
||||
|
||||
mock_cli = MagicMock()
|
||||
monkeypatch.setattr("haiku.rag.ingester.cli._cli", mock_cli)
|
||||
monkeypatch.setattr("haiku.rag.ingester.cli.configure_cli_logging", lambda: None)
|
||||
monkeypatch.setattr("haiku.rag.telemetry.configure", lambda **_: None)
|
||||
|
||||
cli_entry()
|
||||
|
||||
mock_cli.assert_called_once()
|
||||
|
||||
|
||||
def test_cli_entry_point_exits_on_migration_error(monkeypatch):
|
||||
from haiku.rag.ingester.cli import cli as cli_entry
|
||||
from haiku.rag.store.exceptions import MigrationRequiredError
|
||||
|
||||
monkeypatch.setattr(
|
||||
"haiku.rag.ingester.cli._cli",
|
||||
MagicMock(side_effect=MigrationRequiredError("need migration")),
|
||||
)
|
||||
monkeypatch.setattr("haiku.rag.ingester.cli.configure_cli_logging", lambda: None)
|
||||
monkeypatch.setattr("haiku.rag.telemetry.configure", lambda **_: None)
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
cli_entry()
|
||||
assert exc_info.value.code == 1
|
||||
|
|
|
|||
25
tests/ingester/test_filter.py
Normal file
25
tests/ingester/test_filter.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from watchfiles import Change
|
||||
|
||||
from haiku.rag.ingester.sources.filter import FileFilter, _default_supported_extensions
|
||||
|
||||
|
||||
def test_default_supported_extensions_returns_nonempty_list():
|
||||
exts = _default_supported_extensions()
|
||||
assert isinstance(exts, list)
|
||||
assert len(exts) > 0
|
||||
assert all(ext.startswith(".") for ext in exts)
|
||||
|
||||
|
||||
def test_filter_uses_default_extensions_when_none():
|
||||
f = FileFilter(supported_extensions=None)
|
||||
assert len(f.extensions) > 0
|
||||
|
||||
|
||||
def test_call_delegates_to_include_file_then_default_filter():
|
||||
"""__call__ is the watchfiles callback entry point. It should reject
|
||||
files that don't pass include_file and accept ones that do."""
|
||||
f = FileFilter(supported_extensions=[".md"])
|
||||
# DefaultFilter rejects dotfiles and common noise; a normal .md path passes.
|
||||
assert f(Change.added, "/tmp/docs/readme.md") is True
|
||||
# Wrong extension — include_file returns False before DefaultFilter runs.
|
||||
assert f(Change.added, "/tmp/docs/readme.log") is False
|
||||
|
|
@ -44,6 +44,11 @@ def test_adhoc_resolves_s3_forwards_storage_options():
|
|||
assert src.storage_options["endpoint"] == "http://seaweed:8333"
|
||||
|
||||
|
||||
def test_adhoc_s3_without_bucket_raises():
|
||||
with pytest.raises(ValueError, match="Invalid S3 URI"):
|
||||
resolve_adhoc_fetcher("s3:///key-only")
|
||||
|
||||
|
||||
def test_adhoc_unknown_scheme_raises():
|
||||
with pytest.raises(ValueError, match="No source adapter"):
|
||||
resolve_adhoc_fetcher("ftp://example.com/x")
|
||||
|
|
|
|||
Loading…
Reference in a new issue