71 lines
2.3 KiB
Python
71 lines
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from ragcore.application.sources import SourceConnector
|
|
from ragcore.domain.sources import (
|
|
Acquisition,
|
|
FolderSourceConfig,
|
|
ScanResult,
|
|
SourceCursor,
|
|
SourceFingerprint,
|
|
SourceItem,
|
|
SourceValidationError,
|
|
WebsiteSourceConfig,
|
|
)
|
|
|
|
|
|
def _item(external_id: str = "item-1") -> SourceItem:
|
|
return SourceItem(
|
|
source_id=uuid4(), external_id=external_id, locator="file:///docs/item.txt", name="item.txt"
|
|
)
|
|
|
|
|
|
def test_connector_contract_has_no_parse_or_publication_responsibility() -> None:
|
|
assert not hasattr(SourceConnector, "parse")
|
|
assert not hasattr(SourceConnector, "publish")
|
|
assert {"scan", "acquire", "fingerprint"} <= set(dir(SourceConnector))
|
|
|
|
|
|
def test_partial_scan_cannot_imply_deletion() -> None:
|
|
result = ScanResult(
|
|
items=(_item("still-present"),), next_cursor=SourceCursor("page-2"), complete=False
|
|
)
|
|
assert result.deleted_item_ids({"still-present", "temporarily-missing"}) == frozenset()
|
|
|
|
complete = ScanResult(items=(_item("still-present"),), next_cursor=None, complete=True)
|
|
assert complete.deleted_item_ids({"still-present", "source-deleted"}) == {"source-deleted"}
|
|
|
|
|
|
def test_configs_are_typed_and_secret_safe(tmp_path: Path) -> None:
|
|
config = FolderSourceConfig(tmp_path.resolve())
|
|
assert config.root == tmp_path.resolve()
|
|
with pytest.raises(SourceValidationError):
|
|
FolderSourceConfig(Path("relative/root"))
|
|
with pytest.raises(SourceValidationError):
|
|
WebsiteSourceConfig(("example.test",), "https://other.test/sitemap.xml")
|
|
|
|
with pytest.raises(SourceValidationError):
|
|
SourceItem(
|
|
source_id=uuid4(),
|
|
external_id="secret-item",
|
|
locator="https://example.test/item",
|
|
name="item",
|
|
metadata={"api_token": "must-not-enter-source-metadata"},
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_acquisition_is_a_stream_and_does_not_parse_or_publish() -> None:
|
|
async def stream():
|
|
yield b"bytes"
|
|
|
|
item = _item()
|
|
acquisition = Acquisition(stream(), item, SourceFingerprint("a" * 64))
|
|
assert [part async for part in acquisition.stream] == [b"bytes"]
|
|
assert not hasattr(acquisition, "parsed_document")
|
|
assert not hasattr(acquisition, "publication")
|