This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
recoll-modern/tests/integration/test_end_to_end.py
T

121 lines
5.9 KiB
Python

"""Exercise the complete HTTP lifecycle with durable state and a deterministic engine."""
import time
from pathlib import Path
from typing import cast
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from recoll_modern.app import create_app
from recoll_modern.config import Settings
from recoll_modern.engine.fake import FakeEngine, FakeRecord
from recoll_modern.storage.database import Database
from recoll_modern.storage.jobs import JobRepository
def _wait(client: TestClient, job_id: str) -> dict[str, object]:
deadline = time.monotonic() + 5
while time.monotonic() < deadline:
job = client.get(f"/recoll/jobs/{job_id}").json()["job"]
if job["state"] not in ("queued", "running"):
return cast(dict[str, object], job)
time.sleep(0.01)
raise AssertionError(f"job {job_id} did not finish")
def test_complete_collection_lifecycle(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
root = tmp_path / "docs"
(root / "a" / ".calibre").mkdir(parents=True)
(root / "b").mkdir()
(root / "a" / "shared.txt").write_text("python duplicate")
(root / "b" / "shared.txt").write_text("python duplicate")
(root / "a" / "other.txt").write_text("python separate")
(root / "a" / ".calibre" / "hidden.txt").write_text("python hidden")
monkeypatch.setenv("RECOLL_MODERN_ADMIN_USERNAME", "admin")
monkeypatch.setenv("RECOLL_MODERN_ADMIN_PASSWORD", "secret-password")
settings = Settings.from_env({
"RECOLL_MODERN_DOCUMENT_ROOT": str(root),
"RECOLL_MODERN_STATE_ROOT": str(tmp_path / "state"),
"RECOLL_MODERN_RECOLL_CONFIG_DIR": str(tmp_path / "config"),
"RECOLL_MODERN_ROOT_PATH": "/recoll",
"RECOLL_MODERN_INLINE_TEXT_LIMIT": "12",
"RECOLL_MODERN_INLINE_TEXT_LIMIT_MAX": "12",
})
engine = FakeEngine([])
with TestClient(create_app(settings, engine), base_url="https://testserver") as client:
login = client.post("/recoll/auth/login", json={
"username": "admin", "password": "secret-password",
})
assert login.status_code == 200
assert "Path=/recoll" in login.headers["set-cookie"]
headers = {"X-CSRF-Token": login.json()["csrf_token"]}
first = client.post("/recoll/collections", json={"name": "First"}, headers=headers)
second = client.post("/recoll/collections", json={"name": "Second"}, headers=headers)
a, b = first.json()["id"], second.json()["id"]
for collection_id, sources in (
(a, [{"type": "directory", "path": "a", "mode": "persistent",
"excluded_directories": ["a/.calibre"]}]),
(b, [{"type": "directory", "path": "b", "mode": "snapshot"}]),
):
response = client.post(
f"/recoll/collections/{collection_id}/memberships",
headers=headers, json={"sources": sources},
)
assert _wait(client, response.json()["job"]["id"])["state"] == "completed"
direct = client.post(
f"/recoll/collections/{a}/memberships", headers=headers,
json={"sources": [
{"type": "file", "path": "a/other.txt"},
{"type": "file", "path": "a/shared.txt"},
]},
)
assert _wait(client, direct.json()["job"]["id"])["state"] == "completed"
members_a = client.get(f"/recoll/collections/{a}/documents").json()["items"]
members_b = client.get(f"/recoll/collections/{b}/documents").json()["items"]
assert len(members_a) == 2 and len(members_b) == 1
shared = next(item for item in members_a if item["path"] == "a/shared.txt")
assert shared["document_id"] == members_b[0]["document_id"]
assert all(item["path"] != "a/.calibre/hidden.txt" for item in members_a)
engine.records = [
FakeRecord(str(root / "a" / "shared.txt"), "Shared", "python duplicate", (a, b)),
FakeRecord(str(root / "a" / "other.txt"), "Other", "python separate", (a,)),
]
result = client.get("/recoll/search", params=[
("q", "python"), ("collections", a), ("collections", b),
("fields", "text"), ("text_limit", "6"),
])
assert result.status_code == 200
assert len(result.json()["hits"]) == 2
assert result.json()["hits"][0]["text"] == "python"
assert client.get(
f"/recoll/documents/{shared['document_id']}/text"
).json()["text"] == "python duplicate"
partial = client.get(
f"/recoll/documents/{shared['document_id']}/file", headers={"Range": "bytes=0-5"}
)
assert partial.status_code == 206 and partial.content == b"python"
(root / "a" / "shared.txt").unlink()
refresh = client.post("/recoll/jobs/reindex", headers=headers, json={})
assert _wait(client, refresh.json()["job"]["id"])["state"] == "completed"
promoted = cast(FastAPI, client.app).state.documents.get(shared["document_id"])
assert promoted.canonical_path == "b/shared.txt"
engine.records = [
FakeRecord(str(root / "b" / "shared.txt"), "Shared", "python duplicate", (a, b))
]
assert client.get("/recoll/search", params={
"q": "python", "collections": a,
}).json()["hits"][0]["document_id"] == shared["document_id"]
jobs = JobRepository(Database(settings.database_path))
interrupted = jobs.enqueue("reindex", {"version": 1, "paths": [], "collections": []}, False)
with Database(settings.database_path).transaction() as connection:
connection.execute("UPDATE jobs SET state = 'running' WHERE id = ?", (interrupted.id,))
with TestClient(create_app(settings, engine), base_url="https://testserver") as client:
client.post("/recoll/auth/login", json={
"username": "admin", "password": "secret-password",
})
assert _wait(client, interrupted.id)["state"] == "completed"