- Removed unnecessary imports from various test files to improve readability and maintainability. - Added new settings for the Generated Feed display in the settingsFeed.js file. - Introduced a new configuration file for Pyright type checking.
261 lines
10 KiB
Python
261 lines
10 KiB
Python
"""
|
|
Additional coverage tests for index_prepare_ops.py.
|
|
Targets: lines 26-29, 43-97, 110-127, 138-147, 157-166, 178.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from mjr_am_backend.features.index import index_prepare_ops as ipo
|
|
from mjr_am_backend.shared import Result
|
|
|
|
# ─── prepare_metadata_for_entry ─────────────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prepare_metadata_for_entry_non_fast_ok():
|
|
async def _get_metadata(filepath, scan_id):
|
|
return Result.Ok({"prompt": "test"})
|
|
|
|
scanner = SimpleNamespace(
|
|
metadata=SimpleNamespace(get_metadata=_get_metadata),
|
|
_current_scan_id=None,
|
|
)
|
|
res, cache_store = await ipo.prepare_metadata_for_entry(scanner, filepath="x.png", fast=False)
|
|
assert res.ok is True
|
|
assert cache_store is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prepare_metadata_for_entry_non_fast_error(monkeypatch):
|
|
async def _get_metadata(filepath, scan_id):
|
|
return Result.Err("FAIL", "extraction failed")
|
|
|
|
def _error_payload(result, filepath):
|
|
return Result.Ok({"error": "extraction failed"})
|
|
|
|
scanner = SimpleNamespace(
|
|
metadata=SimpleNamespace(get_metadata=_get_metadata),
|
|
_current_scan_id=None,
|
|
)
|
|
monkeypatch.setattr(ipo.MetadataHelpers, "metadata_error_payload", _error_payload)
|
|
|
|
res, cache_store = await ipo.prepare_metadata_for_entry(scanner, filepath="x.png", fast=False)
|
|
assert cache_store is False
|
|
|
|
|
|
# ─── prepare_index_entry_context ────────────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prepare_index_entry_context_success(tmp_path):
|
|
p = tmp_path / "img.png"
|
|
p.write_text("x")
|
|
|
|
class _FakeStat:
|
|
st_mtime_ns = 1_000_000_000
|
|
st_mtime = 1.0
|
|
st_size = 1
|
|
|
|
async def _stat_with_retry(*, file_path):
|
|
return True, _FakeStat()
|
|
|
|
async def _get_journal(*, filepath, existing_state, incremental):
|
|
return "hash_j"
|
|
|
|
scanner = SimpleNamespace(
|
|
_stat_with_retry=_stat_with_retry,
|
|
_get_journal_state_hash_for_index_file=_get_journal,
|
|
)
|
|
ctx, err = await ipo.prepare_index_entry_context(
|
|
scanner, file_path=p, existing_state=None, incremental=True
|
|
)
|
|
assert ctx is not None and err is None
|
|
assert "filepath" in ctx and "state_hash" in ctx
|
|
|
|
|
|
# ─── should_skip_by_journal_state ───────────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_should_skip_non_incremental():
|
|
scanner = SimpleNamespace()
|
|
ctx = {"journal_state_hash": "h", "state_hash": "h", "existing_id": 1}
|
|
assert await ipo.should_skip_by_journal_state(scanner, prepare_ctx=ctx, incremental=False, fast=True) is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_should_skip_no_journal_hash():
|
|
scanner = SimpleNamespace()
|
|
ctx = {"journal_state_hash": None, "state_hash": "h", "existing_id": 1}
|
|
assert await ipo.should_skip_by_journal_state(scanner, prepare_ctx=ctx, incremental=True, fast=True) is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_should_skip_hash_mismatch():
|
|
scanner = SimpleNamespace()
|
|
ctx = {"journal_state_hash": "a", "state_hash": "b", "existing_id": 1}
|
|
assert await ipo.should_skip_by_journal_state(scanner, prepare_ctx=ctx, incremental=True, fast=True) is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_should_skip_no_existing_id():
|
|
scanner = SimpleNamespace()
|
|
ctx = {"journal_state_hash": "h", "state_hash": "h", "existing_id": 0}
|
|
assert await ipo.should_skip_by_journal_state(scanner, prepare_ctx=ctx, incremental=True, fast=False) is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_should_skip_fast_false_when_vector_missing():
|
|
async def _asset_has_vector(*, asset_id):
|
|
return False
|
|
|
|
scanner = SimpleNamespace(_asset_has_vector=_asset_has_vector)
|
|
ctx = {"journal_state_hash": "h", "state_hash": "h", "existing_id": 1}
|
|
assert await ipo.should_skip_by_journal_state(scanner, prepare_ctx=ctx, incremental=True, fast=True) is False
|
|
|
|
|
|
# ─── refresh_entry_from_cached_metadata ─────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_refresh_entry_from_cached_metadata_no_cache(monkeypatch):
|
|
async def _cached(db, fp, state_hash):
|
|
return None
|
|
|
|
monkeypatch.setattr(ipo.MetadataHelpers, "retrieve_cached_metadata", _cached)
|
|
scanner = SimpleNamespace(db=object())
|
|
prepare_ctx = {"filepath": "x.png", "state_hash": "h", "existing_id": 1, "mtime": 1, "size": 2}
|
|
out = await ipo.refresh_entry_from_cached_metadata(
|
|
scanner, prepare_ctx=prepare_ctx, file_path=Path("x.png"), fast=False
|
|
)
|
|
assert out is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_refresh_entry_from_cached_metadata_error_result(monkeypatch):
|
|
async def _cached(db, fp, state_hash):
|
|
return Result.Err("MISS", "not found")
|
|
|
|
monkeypatch.setattr(ipo.MetadataHelpers, "retrieve_cached_metadata", _cached)
|
|
scanner = SimpleNamespace(db=object())
|
|
prepare_ctx = {"filepath": "x.png", "state_hash": "h", "existing_id": 1, "mtime": 1, "size": 2}
|
|
out = await ipo.refresh_entry_from_cached_metadata(
|
|
scanner, prepare_ctx=prepare_ctx, file_path=Path("x.png"), fast=False
|
|
)
|
|
assert out is None
|
|
|
|
|
|
# ─── maybe_skip_prepare_for_incremental ─────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_maybe_skip_journal_path(monkeypatch):
|
|
async def _should_skip(scanner, *, prepare_ctx, incremental, fast):
|
|
return True
|
|
|
|
monkeypatch.setattr(ipo, "should_skip_by_journal_state", _should_skip)
|
|
|
|
scanner = SimpleNamespace()
|
|
ctx = {"journal_state_hash": "h", "state_hash": "h", "existing_id": 1, "mtime": 1, "size": 2, "existing_mtime": 1}
|
|
result = await ipo.maybe_skip_prepare_for_incremental(
|
|
scanner, prepare_ctx=ctx, incremental=True, fast=False, file_path=Path("x.png")
|
|
)
|
|
assert result is not None and result.data == {"action": "skipped_journal"}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_maybe_skip_incremental_unchanged_rich_metadata(monkeypatch):
|
|
async def _should_skip(scanner, *, prepare_ctx, incremental, fast):
|
|
return False
|
|
|
|
async def _refresh_cached(scanner, *, prepare_ctx, file_path, fast):
|
|
return None
|
|
|
|
async def _has_rich(*, asset_id):
|
|
return True
|
|
|
|
monkeypatch.setattr(ipo, "should_skip_by_journal_state", _should_skip)
|
|
monkeypatch.setattr(ipo, "refresh_entry_from_cached_metadata", _refresh_cached)
|
|
|
|
scanner = SimpleNamespace(_asset_has_rich_metadata=_has_rich)
|
|
# mtime==existing_mtime → incremental_unchanged = True
|
|
ctx = {"state_hash": "h", "journal_state_hash": "diff", "existing_id": 1, "mtime": 5, "size": 10, "existing_mtime": 5}
|
|
result = await ipo.maybe_skip_prepare_for_incremental(
|
|
scanner, prepare_ctx=ctx, incremental=True, fast=False, file_path=Path("x.png")
|
|
)
|
|
assert result is not None and result.data == {"action": "skipped"}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_maybe_skip_incremental_unchanged_rich_metadata_but_missing_vector(monkeypatch):
|
|
async def _should_skip(scanner, *, prepare_ctx, incremental, fast):
|
|
return False
|
|
|
|
async def _refresh_cached(scanner, *, prepare_ctx, file_path, fast):
|
|
return None
|
|
|
|
async def _has_rich(*, asset_id):
|
|
return True
|
|
|
|
async def _asset_has_vector(*, asset_id):
|
|
return False
|
|
|
|
monkeypatch.setattr(ipo, "should_skip_by_journal_state", _should_skip)
|
|
monkeypatch.setattr(ipo, "refresh_entry_from_cached_metadata", _refresh_cached)
|
|
|
|
scanner = SimpleNamespace(
|
|
_asset_has_rich_metadata=_has_rich,
|
|
_asset_has_vector=_asset_has_vector,
|
|
)
|
|
ctx = {"state_hash": "h", "journal_state_hash": "diff", "existing_id": 1, "mtime": 5, "size": 10, "existing_mtime": 5}
|
|
result = await ipo.maybe_skip_prepare_for_incremental(
|
|
scanner, prepare_ctx=ctx, incremental=True, fast=False, file_path=Path("x.png")
|
|
)
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_maybe_skip_not_incremental_unchanged_returns_none(monkeypatch):
|
|
async def _should_skip(scanner, *, prepare_ctx, incremental, fast):
|
|
return False
|
|
|
|
monkeypatch.setattr(ipo, "should_skip_by_journal_state", _should_skip)
|
|
scanner = SimpleNamespace()
|
|
# different mtime → not unchanged
|
|
ctx = {"state_hash": "h", "journal_state_hash": None, "existing_id": 1, "mtime": 5, "size": 10, "existing_mtime": 3}
|
|
result = await ipo.maybe_skip_prepare_for_incremental(
|
|
scanner, prepare_ctx=ctx, incremental=True, fast=False, file_path=Path("x.png")
|
|
)
|
|
assert result is None
|
|
|
|
|
|
# ─── prepare_index_entry (full flow) ────────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prepare_index_entry_stat_failure(monkeypatch):
|
|
async def _ctx(scanner, *, file_path, existing_state, incremental):
|
|
return None, "stat failed"
|
|
|
|
monkeypatch.setattr(ipo, "prepare_index_entry_context", _ctx)
|
|
scanner = SimpleNamespace()
|
|
result = await ipo.prepare_index_entry(
|
|
scanner, file_path=Path("x.png"), base_dir="C:/", incremental=False
|
|
)
|
|
assert not result.ok and "STAT_FAILED" in result.code
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prepare_index_entry_skip_path(monkeypatch):
|
|
async def _ctx(scanner, *, file_path, existing_state, incremental):
|
|
return {"state_hash": "h", "journal_state_hash": None, "existing_id": 1,
|
|
"existing_mtime": 5, "mtime": 5, "size": 10, "filepath": "x.png"}, None
|
|
|
|
async def _skip(scanner, *, prepare_ctx, incremental, fast, file_path):
|
|
return Result.Ok({"action": "skipped"})
|
|
|
|
monkeypatch.setattr(ipo, "prepare_index_entry_context", _ctx)
|
|
monkeypatch.setattr(ipo, "maybe_skip_prepare_for_incremental", _skip)
|
|
scanner = SimpleNamespace()
|
|
result = await ipo.prepare_index_entry(
|
|
scanner, file_path=Path("x.png"), base_dir="C:/", incremental=True
|
|
)
|
|
assert result.ok and result.data["action"] == "skipped"
|