sunnyware / tests /test_rag_admin_http.py
shamiur's picture
Part 599: Add /api/rag/clear endpoint
f12c1e1
Raw History Blame Contribute Delete
8.44 kB
# -*- coding: utf-8 -*-
"""
Part 581: HTTP-level tests for RAG admin endpoints.
"""
import os
import sys
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
# Ensure src is on path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from sunnyware.main import app
@pytest.fixture(scope="module")
def client():
"""Start TestClient with lifespan events and auth disabled."""
os.environ.pop("SUNNYWARE_API_KEYS", None)
from sunnyware import auth as _auth
_auth.load_keys(reload=True)
with TestClient(app) as c:
yield c
def test_rag_stats(client):
r = client.get("/api/rag-admin/stats")
assert r.status_code == 200
data = r.json()
assert "documents" in data or "docs" in data or isinstance(data, dict)
def test_rag_list_documents_empty(client):
r = client.get("/api/rag-admin/documents")
assert r.status_code == 200
data = r.json()
assert "count" in data
assert "documents" in data
assert isinstance(data["documents"], list)
def test_rag_add_document_missing_name(client):
r = client.post("/api/rag-admin/documents", json={"content": "hello"})
assert r.status_code == 400
assert "name" in r.json().get("error", "")
def test_rag_add_document_missing_content(client):
r = client.post("/api/rag-admin/documents", json={"name": "test.txt"})
assert r.status_code == 400
assert "content" in r.json().get("error", "")
def test_rag_add_document_success(client):
r = client.post("/api/rag-admin/documents", json={
"name": "hello.txt",
"content": "Hello world. This is a test document. It has multiple sentences.",
})
assert r.status_code == 200
data = r.json()
assert data["ok"] is True
assert "document" in data
doc = data["document"]
assert "id" in doc
# Verify it appears in listing
r2 = client.get("/api/rag-admin/documents")
assert r2.status_code == 200
names = [d["name"] for d in r2.json()["documents"]]
assert "hello.txt" in names
# Cleanup
client.delete(f"/api/rag-admin/documents/{doc['id']}")
def test_rag_add_document_invalid_json(client):
r = client.post("/api/rag-admin/documents", data="not json")
assert r.status_code == 400
def test_rag_add_document_content_too_large(client):
r = client.post("/api/rag-admin/documents", json={
"name": "big.txt",
"content": "x" * 500_001,
})
assert r.status_code == 400
assert "too large" in r.json().get("error", "")
def test_rag_chunks_empty(client):
r = client.get("/api/rag-admin/chunks")
assert r.status_code == 200
data = r.json()
assert "count" in data
assert "chunks" in data
assert isinstance(data["chunks"], list)
def test_rag_search_chunks(client):
r = client.get("/api/rag-admin/chunks?search=hello")
assert r.status_code == 200
data = r.json()
assert "count" in data
assert "chunks" in data
def test_rag_document_chunks(client):
# Create a document first
r = client.post("/api/rag-admin/documents", json={
"name": "chunks_test.txt",
"content": "First sentence. Second sentence. Third sentence. Fourth sentence.",
})
assert r.status_code == 200
doc_id = r.json()["document"]["id"]
# Fetch its chunks
r2 = client.get(f"/api/rag-admin/documents/{doc_id}/chunks")
assert r2.status_code == 200
data = r2.json()
assert "count" in data
assert "chunks" in data
assert data["count"] >= 1
# Cleanup
client.delete(f"/api/rag-admin/documents/{doc_id}")
def test_rag_bulk_delete(client):
# Create two documents
ids = []
for i in range(2):
r = client.post("/api/rag-admin/documents", json={
"name": f"bulk_del_{i}.txt",
"content": f"Content for doc {i}. Another sentence.",
})
assert r.status_code == 200
ids.append(r.json()["document"]["id"])
# Bulk delete them
r = client.post("/api/rag-admin/bulk-delete", json={"doc_ids": ids})
assert r.status_code == 200
data = r.json()
assert data["ok"] is True
assert data["deleted"] >= 1
def test_rag_bulk_delete_invalid_body(client):
r = client.post("/api/rag-admin/bulk-delete", json={"doc_ids": "not-a-list"})
assert r.status_code == 400
assert "list" in r.json().get("error", "").lower()
def test_rag_bulk_delete_invalid_json(client):
r = client.post("/api/rag-admin/bulk-delete", content="not json")
assert r.status_code == 400
def test_rag_search_missing_query(client):
r = client.get("/api/rag/search")
assert r.status_code == 400
body = r.json().get("error", "").lower()
assert "q" in body or "query" in body
def test_rag_search_empty_store(client):
r = client.get("/api/rag/search?q=test")
assert r.status_code == 200
data = r.json()
assert "count" in data or "error" in data
def test_rag_search_clamps_k(client):
r = client.get("/api/rag/search?q=test&k=999")
assert r.status_code == 200
def test_rag_stats_public(client):
r = client.get("/api/rag/stats")
assert r.status_code == 200
data = r.json()
if "error" not in data:
assert "count" in data
assert "dim" in data
assert "model" in data
assert "loaded" in data
def test_rag_upload_txt(client):
files = {"file": ("hello.txt", b"Hello world. This is a test document.", "text/plain")}
r = client.post("/api/rag/upload", files=files)
assert r.status_code in (200, 500)
data = r.json()
if r.status_code == 200:
assert data.get("ok") is True
assert data.get("chunks") >= 1
def test_rag_upload_unsupported(client):
files = {"file": ("bad.xyz", b"data", "application/octet-stream")}
r = client.post("/api/rag/upload", files=files)
assert r.status_code == 400
assert "unsupported" in r.json().get("error", "").lower()
def test_rag_upload_no_filename(client):
files = {"file": ("", b"data", "text/plain")}
r = client.post("/api/rag/upload", files=files)
# FastAPI rejects an empty filename at the validation layer (422)
assert r.status_code in (400, 422)
def test_rag_documents_empty(client):
r = client.get("/api/rag/documents")
assert r.status_code == 200
data = r.json()
if "error" not in data:
assert "count" in data
assert "documents" in data
assert isinstance(data["documents"], list)
assert data["count"] == len(data["documents"])
def test_rag_documents_after_upload(client):
files = {"file": ("doc_test.txt", b"Hello world. This is content.", "text/plain")}
up = client.post("/api/rag/upload", files=files)
if up.status_code != 200:
return
r = client.get("/api/rag/documents")
assert r.status_code == 200
data = r.json()
if "error" in data:
return
sources = [d["source"] for d in data["documents"]]
assert any("doc_test.txt" in s for s in sources)
def test_rag_delete_missing_source(client):
r = client.delete("/api/rag/delete")
assert r.status_code == 400
def test_rag_delete_nonexistent(client):
r = client.delete("/api/rag/delete?source=does_not_exist_xyz.txt")
assert r.status_code == 200
data = r.json()
if "error" not in data:
assert data.get("removed") == 0
def test_rag_delete_after_upload(client):
files = {"file": ("del_test.txt", b"Content to be deleted.", "text/plain")}
up = client.post("/api/rag/upload", files=files)
if up.status_code != 200:
return
r = client.delete("/api/rag/delete?source=del_test.txt")
assert r.status_code == 200
data = r.json()
if "error" in data:
return
assert data.get("removed", 0) >= 1
def test_rag_clear_empty(client):
r = client.delete("/api/rag/clear")
assert r.status_code == 200
data = r.json()
if "error" not in data:
assert data.get("removed", 0) >= 0
def test_rag_clear_after_upload(client):
files = {"file": ("clear_test.txt", b"Content to be cleared.", "text/plain")}
up = client.post("/api/rag/upload", files=files)
if up.status_code != 200:
return
r = client.delete("/api/rag/clear")
assert r.status_code == 200
data = r.json()
if "error" in data:
return
assert data.get("removed", 0) >= 1
docs = client.get("/api/rag/documents").json()
if "error" not in docs:
assert docs.get("count", 0) == 0