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,539 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import hashlib
|
||||
from collections.abc import AsyncIterable
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ragcore.api.v1.control.dependencies import (
|
||||
get_control_application,
|
||||
get_credential_service,
|
||||
get_principal,
|
||||
)
|
||||
from ragcore.api.v1.uploads.dependencies import get_upload_service
|
||||
from ragcore.application.authorization import AuthorizationService
|
||||
from ragcore.application.control.api import ControlPlaneApplication
|
||||
from ragcore.application.documents import DocumentRecord
|
||||
from ragcore.application.identity.credentials import ServiceAccountCredentialService
|
||||
from ragcore.application.jobs import Job, JobService
|
||||
from ragcore.application.storage import BlobReference
|
||||
from ragcore.domain.authorization import Principal, PrincipalType
|
||||
from ragcore.domain.control import (
|
||||
Application,
|
||||
Grant,
|
||||
KnowledgeSpace,
|
||||
Policy,
|
||||
Role,
|
||||
Scope,
|
||||
ServiceAccount,
|
||||
UserGrant,
|
||||
Workspace,
|
||||
)
|
||||
from ragcore.infrastructure.connectors.upload import (
|
||||
UploadIdempotencyStore,
|
||||
UploadPolicy,
|
||||
UploadService,
|
||||
)
|
||||
from ragcore.infrastructure.database.identity.memory import InMemoryCredentialRepository
|
||||
from ragcore.main import create_app
|
||||
|
||||
|
||||
class RecordingBlobStorage:
|
||||
def __init__(self) -> None:
|
||||
self.calls = 0
|
||||
|
||||
async def put(
|
||||
self,
|
||||
chunks: AsyncIterable[bytes],
|
||||
*,
|
||||
media_type: str | None = None,
|
||||
original_name: str | None = None,
|
||||
expected_sha256: str | None = None,
|
||||
) -> BlobReference:
|
||||
self.calls += 1
|
||||
body = b"".join([chunk async for chunk in chunks])
|
||||
digest = hashlib.sha256(body).hexdigest()
|
||||
return BlobReference(
|
||||
digest=digest,
|
||||
size=len(body),
|
||||
media_type=media_type,
|
||||
original_name=original_name,
|
||||
storage_key=f"blobs/{digest}",
|
||||
)
|
||||
|
||||
|
||||
class RecordingJobRepository:
|
||||
def __init__(self) -> None:
|
||||
self.calls = 0
|
||||
self.enqueued: list[Job] = []
|
||||
|
||||
async def enqueue(self, job: Job) -> Job:
|
||||
self.calls += 1
|
||||
self.enqueued.append(job)
|
||||
return job
|
||||
|
||||
|
||||
class FakeControlRepository:
|
||||
def __init__(self, workspace: Workspace) -> None:
|
||||
self.workspace = workspace
|
||||
self.applications: list[Application] = []
|
||||
self.service_accounts: list[ServiceAccount] = []
|
||||
self.spaces: list[KnowledgeSpace] = []
|
||||
self.grants: list[Grant | UserGrant] = []
|
||||
self.policies: list[Policy] = []
|
||||
|
||||
async def get_workspace(self, workspace_id):
|
||||
return self.workspace if workspace_id == self.workspace.id else None
|
||||
|
||||
async def create_application(self, entity):
|
||||
self.applications.append(entity)
|
||||
return entity
|
||||
|
||||
async def get_application(self, application_id, workspace_id):
|
||||
return next(
|
||||
(
|
||||
item
|
||||
for item in self.applications
|
||||
if item.id == application_id and item.workspace_id == workspace_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
async def list_applications(self, workspace_id):
|
||||
return tuple(item for item in self.applications if item.workspace_id == workspace_id)
|
||||
|
||||
async def create_service_account(self, entity):
|
||||
self.service_accounts.append(entity)
|
||||
return entity
|
||||
|
||||
async def get_service_account_by_id(self, service_account_id):
|
||||
return next((item for item in self.service_accounts if item.id == service_account_id), None)
|
||||
|
||||
async def create_space(self, entity):
|
||||
self.spaces.append(entity)
|
||||
return entity
|
||||
|
||||
async def list_spaces(self, workspace_id):
|
||||
return tuple(item for item in self.spaces if item.workspace_id == workspace_id)
|
||||
|
||||
async def update_space(self, entity):
|
||||
self.spaces = [entity if item.id == entity.id else item for item in self.spaces]
|
||||
return entity
|
||||
|
||||
async def create_grant(self, entity):
|
||||
self.grants.append(entity)
|
||||
return entity
|
||||
|
||||
async def create_user_grant(self, entity):
|
||||
self.grants.append(entity)
|
||||
return entity
|
||||
|
||||
async def create_policy(self, entity):
|
||||
self.policies.append(entity)
|
||||
return entity
|
||||
|
||||
async def list_policies(self, workspace_id):
|
||||
return tuple(item for item in self.policies if item.workspace_id == workspace_id)
|
||||
|
||||
async def list_grants(self, workspace_id):
|
||||
return tuple(
|
||||
item
|
||||
for item in self.grants
|
||||
if isinstance(item, Grant) and item.workspace_id == workspace_id
|
||||
)
|
||||
|
||||
|
||||
class FakeDocumentRepository:
|
||||
def __init__(self) -> None:
|
||||
self.documents: list[DocumentRecord] = []
|
||||
|
||||
async def create_document(self, document: DocumentRecord) -> DocumentRecord:
|
||||
self.documents.append(document)
|
||||
return document
|
||||
|
||||
async def create_version(self, version): # type: ignore[no-untyped-def]
|
||||
raise AssertionError("should not be reached in these tests")
|
||||
|
||||
async def get_version(self, version_id): # type: ignore[no-untyped-def]
|
||||
raise AssertionError("should not be reached in these tests")
|
||||
|
||||
async def publish_validated(self, version_id, artifacts, chunks): # type: ignore[no-untyped-def]
|
||||
raise AssertionError("should not be reached in these tests")
|
||||
|
||||
async def mark_source_deleted(self, document_id): # type: ignore[no-untyped-def]
|
||||
raise AssertionError("should not be reached in these tests")
|
||||
|
||||
async def list_searchable_versions(self, document_id): # type: ignore[no-untyped-def]
|
||||
raise AssertionError("should not be reached in these tests")
|
||||
|
||||
async def list_documents_by_space(self, space_id): # type: ignore[no-untyped-def]
|
||||
return tuple(item for item in self.documents if item.space_id == space_id)
|
||||
|
||||
async def list_documents_by_space_with_status(self, space_id): # type: ignore[no-untyped-def]
|
||||
from ragcore.application.documents import DocumentStatusRecord, DocumentVersionState
|
||||
|
||||
return tuple(
|
||||
DocumentStatusRecord(
|
||||
document=item,
|
||||
latest_state=DocumentVersionState.PUBLISHED,
|
||||
parser_version="text-parser-1",
|
||||
chunk_profile_version="prose-v1",
|
||||
updated_at=None,
|
||||
)
|
||||
for item in self.documents
|
||||
if item.space_id == space_id
|
||||
)
|
||||
|
||||
|
||||
def _fixture():
|
||||
workspace = Workspace(uuid4(), "Web workspace", "web-workspace")
|
||||
principal = Principal(
|
||||
uuid4(), workspace.id, PrincipalType.HUMAN, frozenset({Role.WORKSPACE_ADMIN})
|
||||
)
|
||||
repository = FakeControlRepository(workspace)
|
||||
document_repository = FakeDocumentRepository()
|
||||
repository.documents = document_repository
|
||||
application = workspace.create_application(
|
||||
id=uuid4(),
|
||||
name="Web application",
|
||||
slug="web-application",
|
||||
scopes=frozenset({Scope.SEARCH}),
|
||||
)
|
||||
asyncio.run(repository.create_application(application))
|
||||
service = ControlPlaneApplication(
|
||||
repository, AuthorizationService(), document_repository=document_repository
|
||||
)
|
||||
credentials = InMemoryCredentialRepository()
|
||||
credential_service = ServiceAccountCredentialService(
|
||||
credentials,
|
||||
"x" * 32,
|
||||
repository.get_service_account_by_id,
|
||||
)
|
||||
blob_storage = RecordingBlobStorage()
|
||||
job_repository = RecordingJobRepository()
|
||||
repository.uploads = job_repository
|
||||
upload_service = UploadService(
|
||||
blob_storage,
|
||||
JobService(job_repository),
|
||||
policy=UploadPolicy(),
|
||||
idempotency=UploadIdempotencyStore(),
|
||||
)
|
||||
app = create_app()
|
||||
app.dependency_overrides[get_principal] = lambda: principal
|
||||
app.dependency_overrides[get_control_application] = lambda: service
|
||||
app.dependency_overrides[get_credential_service] = lambda: credential_service
|
||||
app.dependency_overrides[get_upload_service] = lambda: upload_service
|
||||
return app, repository, principal, application
|
||||
|
||||
|
||||
def test_control_screens_have_empty_states_and_accessible_landmarks() -> None:
|
||||
app, _, _, _ = _fixture()
|
||||
client = TestClient(app)
|
||||
for path, heading in (
|
||||
("/admin/control/applications", "Applications"),
|
||||
("/admin/control/spaces", "Knowledge spaces"),
|
||||
("/admin/control/policies", "Policies"),
|
||||
):
|
||||
response = client.get(path)
|
||||
assert response.status_code == 200
|
||||
assert heading in response.text
|
||||
if path != "/admin/control/applications":
|
||||
assert 'role="status"' in response.text
|
||||
html = client.get("/admin/control/applications").text
|
||||
assert 'href="#main-content"' in html
|
||||
assert '<nav aria-label="Primary navigation">' in html
|
||||
assert 'id="main-content"' in html
|
||||
assert client.get("/static/admin.css").status_code == 200
|
||||
assert ":focus-visible" in client.get("/static/admin.css").text
|
||||
|
||||
|
||||
def test_forbidden_control_screen_is_explicit_and_does_not_leak_data() -> None:
|
||||
app, _, principal, _ = _fixture()
|
||||
denied = Principal(
|
||||
principal.id, principal.workspace_id, PrincipalType.HUMAN, frozenset({Role.VIEWER})
|
||||
)
|
||||
app.dependency_overrides[get_principal] = lambda: denied
|
||||
response = TestClient(app).get("/admin/control/applications")
|
||||
assert response.status_code == 403
|
||||
assert "Access denied" in response.text
|
||||
assert "Web application" not in response.text
|
||||
assert "Reference:" in response.text
|
||||
|
||||
|
||||
def test_credential_plaintext_is_rendered_once() -> None:
|
||||
app, _, _, application = _fixture()
|
||||
client = TestClient(app)
|
||||
form = client.get(f"/admin/control/applications/{application.id}/credentials/new")
|
||||
assert form.status_code == 200
|
||||
assert "One-time plaintext token" not in form.text
|
||||
assert '<nav aria-label="Breadcrumb"' in form.text
|
||||
assert "Web application" in form.text
|
||||
issued = client.post(
|
||||
f"/admin/control/applications/{application.id}/credentials",
|
||||
data={"name": "browser credential", "scopes": ["search"]},
|
||||
)
|
||||
assert issued.status_code == 201
|
||||
assert "rc_sa_" in issued.text
|
||||
assert "It will not be shown again" in issued.text
|
||||
refreshed = client.get(f"/admin/control/applications/{application.id}/credentials/new")
|
||||
assert refreshed.status_code == 200
|
||||
assert "rc_sa_" not in refreshed.text
|
||||
|
||||
|
||||
def test_grant_screen_and_error_state_are_authorized() -> None:
|
||||
app, _, _, _ = _fixture()
|
||||
client = TestClient(app)
|
||||
missing = client.get(f"/admin/control/spaces/{uuid4()}/grants")
|
||||
assert missing.status_code == 404
|
||||
assert "Knowledge space not found" in missing.text
|
||||
assert "Reference:" in missing.text
|
||||
|
||||
|
||||
def test_grants_page_lists_real_application_grants_for_the_space() -> None:
|
||||
app, repository, _, application = _fixture()
|
||||
space = repository.workspace.create_knowledge_space(
|
||||
id=uuid4(), name="Runbooks", slug="runbooks"
|
||||
)
|
||||
asyncio.run(repository.create_space(space))
|
||||
grant = repository.workspace.grant(
|
||||
id=uuid4(), application=application, knowledge_space=space, role=Role.VIEWER
|
||||
)
|
||||
asyncio.run(repository.create_grant(grant))
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get(f"/admin/control/spaces/{space.id}/grants")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Web application" in response.text
|
||||
assert "viewer" in response.text
|
||||
assert '<nav aria-label="Breadcrumb"' in response.text
|
||||
assert "Runbooks" in response.text
|
||||
|
||||
|
||||
def test_documents_page_missing_space_is_a_stable_not_found() -> None:
|
||||
app, _, _, _ = _fixture()
|
||||
client = TestClient(app)
|
||||
response = client.get(f"/admin/control/spaces/{uuid4()}/documents")
|
||||
assert response.status_code == 404
|
||||
assert "Knowledge space not found" in response.text
|
||||
|
||||
|
||||
def test_documents_page_has_an_empty_state() -> None:
|
||||
app, repository, _, _ = _fixture()
|
||||
space = repository.workspace.create_knowledge_space(
|
||||
id=uuid4(), name="Fleet Ops Procedures", slug="fleet-ops-procedures"
|
||||
)
|
||||
asyncio.run(repository.create_space(space))
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get(f"/admin/control/spaces/{space.id}/documents")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "No documents yet" in response.text
|
||||
assert 'role="status"' in response.text
|
||||
|
||||
|
||||
def test_documents_page_lists_ingested_documents_for_the_space() -> None:
|
||||
app, repository, _, _ = _fixture()
|
||||
space = repository.workspace.create_knowledge_space(
|
||||
id=uuid4(), name="Fleet Ops Procedures", slug="fleet-ops-procedures"
|
||||
)
|
||||
asyncio.run(repository.create_space(space))
|
||||
document = DocumentRecord(
|
||||
id=uuid4(),
|
||||
workspace_id=repository.workspace.id,
|
||||
space_id=space.id,
|
||||
source_key="a" * 64,
|
||||
external_id="runbook.txt",
|
||||
title="Fleet restart runbook",
|
||||
)
|
||||
asyncio.run(repository.documents.create_document(document))
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get(f"/admin/control/spaces/{space.id}/documents")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Fleet restart runbook" in response.text
|
||||
assert "runbook.txt" in response.text
|
||||
assert '<nav aria-label="Breadcrumb"' in response.text
|
||||
assert "Fleet Ops Procedures" in response.text
|
||||
|
||||
|
||||
def test_new_space_form_renders() -> None:
|
||||
app, _, _, _ = _fixture()
|
||||
client = TestClient(app)
|
||||
response = client.get("/admin/control/spaces/new")
|
||||
assert response.status_code == 200
|
||||
assert "Create a knowledge space" in response.text
|
||||
assert 'action="/admin/control/spaces"' in response.text
|
||||
|
||||
|
||||
def test_create_space_persists_and_redirects_to_the_list() -> None:
|
||||
app, repository, _, _ = _fixture()
|
||||
client = TestClient(app)
|
||||
response = client.post(
|
||||
"/admin/control/spaces",
|
||||
data={"name": "Fleet Ops Procedures", "slug": "fleet-ops-procedures"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert response.status_code == 303
|
||||
assert response.headers["location"] == "/admin/control/spaces"
|
||||
created = asyncio.run(repository.list_spaces(repository.workspace.id))
|
||||
assert any(space.slug == "fleet-ops-procedures" for space in created)
|
||||
|
||||
listing = client.get("/admin/control/spaces")
|
||||
assert "Fleet Ops Procedures" in listing.text
|
||||
|
||||
|
||||
def test_create_space_rejects_an_invalid_slug_without_persisting() -> None:
|
||||
app, repository, _, _ = _fixture()
|
||||
client = TestClient(app)
|
||||
response = client.post(
|
||||
"/admin/control/spaces",
|
||||
data={"name": "Bad Slug", "slug": "Not A Valid Slug!"},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert "lowercase letters, numbers and single hyphens" in response.text
|
||||
assert not asyncio.run(repository.list_spaces(repository.workspace.id))
|
||||
|
||||
|
||||
def test_deactivate_then_activate_space_round_trips() -> None:
|
||||
app, repository, _, _ = _fixture()
|
||||
space = repository.workspace.create_knowledge_space(
|
||||
id=uuid4(), name="Runbooks", slug="runbooks"
|
||||
)
|
||||
asyncio.run(repository.create_space(space))
|
||||
client = TestClient(app)
|
||||
|
||||
deactivated = client.post(
|
||||
f"/admin/control/spaces/{space.id}/deactivate", follow_redirects=False
|
||||
)
|
||||
assert deactivated.status_code == 303
|
||||
stored = asyncio.run(repository.list_spaces(repository.workspace.id))
|
||||
assert stored[0].active is False
|
||||
|
||||
reactivated = client.post(f"/admin/control/spaces/{space.id}/activate", follow_redirects=False)
|
||||
assert reactivated.status_code == 303
|
||||
stored = asyncio.run(repository.list_spaces(repository.workspace.id))
|
||||
assert stored[0].active is True
|
||||
|
||||
|
||||
def test_deactivate_missing_space_is_a_stable_not_found() -> None:
|
||||
app, _, _, _ = _fixture()
|
||||
client = TestClient(app)
|
||||
response = client.post(f"/admin/control/spaces/{uuid4()}/deactivate")
|
||||
assert response.status_code == 404
|
||||
assert "Knowledge space not found" in response.text
|
||||
|
||||
|
||||
def test_space_actions_are_forbidden_for_an_unprivileged_principal() -> None:
|
||||
app, repository, principal, _ = _fixture()
|
||||
space = repository.workspace.create_knowledge_space(
|
||||
id=uuid4(), name="Runbooks", slug="runbooks"
|
||||
)
|
||||
asyncio.run(repository.create_space(space))
|
||||
viewer = Principal(
|
||||
principal.id, principal.workspace_id, PrincipalType.HUMAN, frozenset({Role.VIEWER})
|
||||
)
|
||||
app.dependency_overrides[get_principal] = lambda: viewer
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.post(f"/admin/control/spaces/{space.id}/deactivate")
|
||||
assert response.status_code == 403
|
||||
assert "Access denied" in response.text
|
||||
|
||||
|
||||
def test_upload_form_renders_for_a_real_space() -> None:
|
||||
app, repository, _, _ = _fixture()
|
||||
space = repository.workspace.create_knowledge_space(
|
||||
id=uuid4(), name="Fleet Ops Procedures", slug="fleet-ops-procedures"
|
||||
)
|
||||
asyncio.run(repository.create_space(space))
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get(f"/admin/control/spaces/{space.id}/documents/upload")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Upload a document to Fleet Ops Procedures" in response.text
|
||||
assert 'enctype="multipart/form-data"' in response.text
|
||||
|
||||
|
||||
def test_upload_form_missing_space_is_a_stable_not_found() -> None:
|
||||
app, _, _, _ = _fixture()
|
||||
client = TestClient(app)
|
||||
response = client.get(f"/admin/control/spaces/{uuid4()}/documents/upload")
|
||||
assert response.status_code == 404
|
||||
assert "Knowledge space not found" in response.text
|
||||
|
||||
|
||||
def test_upload_accepts_a_supported_file_and_enqueues_a_job() -> None:
|
||||
app, repository, _, _ = _fixture()
|
||||
space = repository.workspace.create_knowledge_space(
|
||||
id=uuid4(), name="Fleet Ops Procedures", slug="fleet-ops-procedures"
|
||||
)
|
||||
asyncio.run(repository.create_space(space))
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.post(
|
||||
f"/admin/control/spaces/{space.id}/documents/upload",
|
||||
files={"file": ("runbook.txt", b"restart the gateway", "text/plain")},
|
||||
)
|
||||
|
||||
assert response.status_code == 202
|
||||
assert "Upload accepted" in response.text
|
||||
assert "runbook.txt" in response.text
|
||||
assert repository.uploads.calls == 1
|
||||
assert repository.uploads.enqueued[0].operation == "ingest_upload"
|
||||
|
||||
|
||||
def test_upload_rejects_an_unsupported_file_type_without_enqueuing() -> None:
|
||||
app, repository, _, _ = _fixture()
|
||||
space = repository.workspace.create_knowledge_space(
|
||||
id=uuid4(), name="Fleet Ops Procedures", slug="fleet-ops-procedures"
|
||||
)
|
||||
asyncio.run(repository.create_space(space))
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.post(
|
||||
f"/admin/control/spaces/{space.id}/documents/upload",
|
||||
files={"file": ("payload.exe", b"MZ", "application/octet-stream")},
|
||||
)
|
||||
|
||||
assert response.status_code == 415
|
||||
assert "not supported" in response.text
|
||||
assert repository.uploads.calls == 0
|
||||
|
||||
|
||||
def test_upload_missing_space_is_a_stable_not_found() -> None:
|
||||
app, _, _, _ = _fixture()
|
||||
client = TestClient(app)
|
||||
response = client.post(
|
||||
f"/admin/control/spaces/{uuid4()}/documents/upload",
|
||||
files={"file": ("runbook.txt", b"restart the gateway", "text/plain")},
|
||||
)
|
||||
assert response.status_code == 404
|
||||
assert "Knowledge space not found" in response.text
|
||||
|
||||
|
||||
def test_upload_is_forbidden_for_an_unprivileged_principal() -> None:
|
||||
app, repository, principal, _ = _fixture()
|
||||
space = repository.workspace.create_knowledge_space(
|
||||
id=uuid4(), name="Fleet Ops Procedures", slug="fleet-ops-procedures"
|
||||
)
|
||||
asyncio.run(repository.create_space(space))
|
||||
viewer = Principal(
|
||||
principal.id, principal.workspace_id, PrincipalType.HUMAN, frozenset({Role.VIEWER})
|
||||
)
|
||||
app.dependency_overrides[get_principal] = lambda: viewer
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.post(
|
||||
f"/admin/control/spaces/{space.id}/documents/upload",
|
||||
files={"file": ("runbook.txt", b"restart the gateway", "text/plain")},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
assert "Access denied" in response.text
|
||||
assert repository.uploads.calls == 0
|
||||
Reference in new issue
Block a user