Initial curated RAGcore source from c16667f0c9397e2bfc8dd0255b114fba23c8d05e
This commit is contained in:
commit
7ca726caf2
659 files changed
+98171
No files matched your search
@@ -0,0 +1,278 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import replace
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ragcore.api.v1.search.dependencies import get_search_application, get_search_principal
|
||||
from ragcore.api.v1.search.service import (
|
||||
AuthorizationInputs,
|
||||
SearchExecutionRequest,
|
||||
SearchExecutionResult,
|
||||
SearchHit,
|
||||
SearchService,
|
||||
)
|
||||
from ragcore.application.retrieval.authorization import RetrievalAuthorizationService
|
||||
from ragcore.domain.authorization import Principal, PrincipalType
|
||||
from ragcore.domain.citations import Citation
|
||||
from ragcore.domain.control import Application, Role, Workspace
|
||||
from ragcore.main import create_app
|
||||
from ragcore.retrieval.ranking import FusedCandidate
|
||||
from ragcore.retrieval.search.models import (
|
||||
Candidate,
|
||||
CandidateBackendKind,
|
||||
CandidateProvenance,
|
||||
)
|
||||
|
||||
|
||||
class FixtureExecutor:
|
||||
def __init__(self, hits: tuple[SearchHit, ...] = (), **metadata: object) -> None:
|
||||
self.hits = hits
|
||||
self.metadata = metadata
|
||||
self.calls: list[SearchExecutionRequest] = []
|
||||
|
||||
async def execute(self, request: SearchExecutionRequest) -> SearchExecutionResult:
|
||||
self.calls.append(request)
|
||||
hits = tuple(
|
||||
replace(
|
||||
hit,
|
||||
citation=Citation.create(
|
||||
workspace_id=hit.citation.workspace_id,
|
||||
space_id=hit.citation.space_id,
|
||||
document_id=hit.citation.document_id,
|
||||
document_version_id=hit.citation.document_version_id,
|
||||
chunk_id=hit.citation.chunk_id,
|
||||
parent_chunk_id=hit.citation.parent_chunk_id,
|
||||
location=hit.citation.location,
|
||||
excerpt=hit.citation.excerpt,
|
||||
source_uri=hit.citation.source_uri,
|
||||
acquired_at=hit.citation.acquired_at,
|
||||
retrieval_run_id=request.retrieval_run_id,
|
||||
index_alias=hit.citation.index_alias,
|
||||
profile_version=hit.citation.profile_version,
|
||||
),
|
||||
)
|
||||
for hit in self.hits
|
||||
)
|
||||
return SearchExecutionResult(hits=hits, **self.metadata)
|
||||
|
||||
|
||||
class FixtureInputs:
|
||||
def __init__(
|
||||
self,
|
||||
workspace: Workspace,
|
||||
principal: Principal,
|
||||
space_id: UUID,
|
||||
*,
|
||||
application: Application | None = None,
|
||||
) -> None:
|
||||
self.workspace = workspace
|
||||
self.principal = principal
|
||||
self.space_id = space_id
|
||||
self.application = application
|
||||
|
||||
async def load(self, principal: Principal) -> AuthorizationInputs:
|
||||
return AuthorizationInputs(
|
||||
spaces=(self.workspace.create_knowledge_space(
|
||||
id=self.space_id,
|
||||
name="Search space",
|
||||
slug="search-space",
|
||||
),),
|
||||
user_grants=(
|
||||
self.workspace.grant_user(
|
||||
id=uuid4(),
|
||||
user_id=principal.id,
|
||||
knowledge_space=self.workspace.create_knowledge_space(
|
||||
id=self.space_id,
|
||||
name="Search space",
|
||||
slug="search-space",
|
||||
),
|
||||
role=Role.VIEWER,
|
||||
),
|
||||
),
|
||||
application=self.application,
|
||||
)
|
||||
|
||||
|
||||
def _fixture(
|
||||
*,
|
||||
text: str = "Exact evidence excerpt.",
|
||||
query: str = "exact",
|
||||
metadata: dict[str, object] | None = None,
|
||||
) -> tuple[object, Principal, UUID, SearchHit, FixtureExecutor]:
|
||||
workspace = Workspace(uuid4(), "Search workspace", "search-workspace")
|
||||
space_id = uuid4()
|
||||
principal = Principal(uuid4(), workspace.id, PrincipalType.HUMAN, frozenset({Role.VIEWER}))
|
||||
document_id, version_id, chunk_id = uuid4(), uuid4(), uuid4()
|
||||
citation = Citation.create(
|
||||
workspace_id=workspace.id,
|
||||
space_id=space_id,
|
||||
document_id=document_id,
|
||||
document_version_id=version_id,
|
||||
chunk_id=chunk_id,
|
||||
parent_chunk_id=None,
|
||||
location={"page_start": 2, "page_end": 2, "section": "Runbook"},
|
||||
excerpt=text,
|
||||
source_uri="https://example.test/runbook.md",
|
||||
acquired_at=datetime(2026, 8, 1, 12, tzinfo=UTC),
|
||||
retrieval_run_id=uuid4(),
|
||||
index_alias="rag_dense_hybrid_active",
|
||||
profile_version="hybrid-v1",
|
||||
)
|
||||
candidate = Candidate(
|
||||
point_id=uuid4(),
|
||||
score=0.91,
|
||||
rank=1,
|
||||
backend=CandidateBackendKind.DENSE,
|
||||
provenance=CandidateProvenance(
|
||||
workspace_id=workspace.id,
|
||||
space_id=space_id,
|
||||
document_id=document_id,
|
||||
document_version_id=version_id,
|
||||
chunk_id=chunk_id,
|
||||
source_id=uuid4(),
|
||||
parent_chunk_id=None,
|
||||
status="published",
|
||||
content_type="text/markdown",
|
||||
language="nl",
|
||||
trust_level="trusted_internal",
|
||||
),
|
||||
)
|
||||
hit = SearchHit(
|
||||
fused=FusedCandidate(candidate, 1 / 61, 1, None),
|
||||
text=text,
|
||||
title="Runbook",
|
||||
citation=citation,
|
||||
dense_score=0.91,
|
||||
)
|
||||
app = Application(
|
||||
id=uuid4(),
|
||||
workspace_id=workspace.id,
|
||||
name="Search application",
|
||||
default_knowledge_space_ids=frozenset({space_id}),
|
||||
)
|
||||
executor = FixtureExecutor((hit,), **(metadata or {}))
|
||||
service = SearchService(
|
||||
authorization=RetrievalAuthorizationService(),
|
||||
inputs=FixtureInputs(workspace, principal, space_id, application=app),
|
||||
executor=executor,
|
||||
)
|
||||
api = create_app()
|
||||
api.dependency_overrides[get_search_principal] = lambda: principal
|
||||
api.dependency_overrides[get_search_application] = lambda: service
|
||||
return api, principal, space_id, hit, executor
|
||||
|
||||
|
||||
def test_openapi_and_runtime_expose_search_operation() -> None:
|
||||
api, _, _, _, _ = _fixture()
|
||||
document = yaml.safe_load(
|
||||
(Path(__file__).parents[4] / "docs" / "contracts" / "openapi.yaml").read_text()
|
||||
)
|
||||
assert set(api.openapi()["paths"]["/v1/search"]) == {"post"}
|
||||
assert "post" in document["paths"]["/v1/search"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("query", ["exact", "semantic"])
|
||||
def test_exact_and_semantic_fixtures_return_citations(query: str) -> None:
|
||||
api, _, space_id, _, executor = _fixture(query=query)
|
||||
response = TestClient(api).post(
|
||||
"/v1/search",
|
||||
json={"query": query, "requested_space_ids": [str(space_id)]},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert body["effective_space_ids"] == [str(space_id)]
|
||||
assert body["results"][0]["citation"]["excerpt_sha256"]
|
||||
assert executor.calls[0].scope.effective_space_ids == frozenset({space_id})
|
||||
|
||||
|
||||
def test_requested_spaces_cannot_expand_effective_scope() -> None:
|
||||
api, _, _, _, executor = _fixture()
|
||||
forbidden_space = uuid4()
|
||||
response = TestClient(api).post(
|
||||
"/v1/search",
|
||||
json={"query": "exact", "requested_space_ids": [str(forbidden_space)]},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["effective_space_ids"] == []
|
||||
assert response.json()["results"] == []
|
||||
assert executor.calls == []
|
||||
|
||||
|
||||
def test_degraded_metadata_is_stable() -> None:
|
||||
api, _, space_id, _, _ = _fixture(
|
||||
metadata={
|
||||
"degraded": True,
|
||||
"degradation_codes": ("backend_timeout",),
|
||||
"timings_ms": {"dense": 12.5, "sparse": 5.0},
|
||||
}
|
||||
)
|
||||
response = TestClient(api).post(
|
||||
"/v1/search",
|
||||
json={"query": "exact", "requested_space_ids": [str(space_id)]},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["degraded"] is True
|
||||
assert response.json()["degradation_codes"] == ["backend_timeout"]
|
||||
assert response.json()["timings_ms"] == {"dense": 12.5, "sparse": 5.0}
|
||||
|
||||
|
||||
def test_invalid_filter_duplicates_return_problem_details() -> None:
|
||||
api, _, _, _, _ = _fixture()
|
||||
response = TestClient(api).post(
|
||||
"/v1/search",
|
||||
json={"query": "exact", "filters": {"languages": ["nl", "nl"]}},
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
assert response.json()["code"] == "VALIDATION_ERROR"
|
||||
|
||||
|
||||
def test_missing_search_service_is_503_problem_details() -> None:
|
||||
api, principal, _, _, _ = _fixture()
|
||||
# create_app() always wires a real search_application now; popping the
|
||||
# dependency override alone would just fall through to that real service
|
||||
# instead of exercising get_search_application's own missing-state
|
||||
# fallback, so the state attribute itself has to be genuinely absent.
|
||||
api.dependency_overrides.pop(get_search_application)
|
||||
del api.state.search_application
|
||||
response = TestClient(api).post("/v1/search", json={"query": "exact"})
|
||||
assert response.status_code == 503
|
||||
assert response.json()["code"] == "SEARCH_UNAVAILABLE"
|
||||
assert principal.workspace_id
|
||||
def test_debug_output_requires_admin_capability() -> None:
|
||||
api, _, space_id, _, _ = _fixture()
|
||||
response = TestClient(api).post(
|
||||
"/v1/search",
|
||||
json={
|
||||
"query": "exact",
|
||||
"requested_space_ids": [str(space_id)],
|
||||
"include_debug": True,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json()["code"] == "DEBUG_NOT_ALLOWED"
|
||||
|
||||
|
||||
def test_mismatched_profile_provenance_is_rejected() -> None:
|
||||
api, _, space_id, _, _ = _fixture()
|
||||
response = TestClient(api).post(
|
||||
"/v1/search",
|
||||
json={
|
||||
"query": "exact",
|
||||
"profile": "other-v1",
|
||||
"requested_space_ids": [str(space_id)],
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 503
|
||||
assert response.json()["code"] == "UNAUTHORIZED_OR_MISMATCHED_RESULT"
|
||||
Reference in new issue
Block a user