"""Tests feature RAG : authoring corpus (rag_upsert/dump/delete) + retrieval (mockés).""" import base64 import json as _json import nacl.pwhash from kazeia_central.core.adb import Adb from kazeia_central.core.store import Store from kazeia_central.features.medical.rag import service as rsvc _OPS = nacl.pwhash.argon2id.OPSLIMIT_MIN _MEM = nacl.pwhash.argon2id.MEMLIMIT_MIN NOW = 1_700_000_000_000 def _store(tmp_path): s = Store(tmp_path / "c.db", kdf_ops=_OPS, kdf_mem=_MEM) s.unlock("pw") return s class _RagAdb(Adb): def __init__(self): super().__init__() self._docs = {} self.upserts = [] self.deleted = [] def content_query(self, path, *, serial=None, where=None, expect_rows=False, _retry=True): if path == "rag_status": return [{"enabled": 1, "ready": 1, "model": "e5-small", "dim": 384, "doc_count": len(self._docs), "chunk_count": len(self._docs), "pending": 0}] if path == "rag": return [{"source": s, "char_count": len(t), "chunk_count": 1, "updated_at": 1} for s, t in self._docs.items()] if path == "rag_query": return [{"source": "doc1", "score": 0.91, "text": "chunk pertinent pour: " + (where or "")}] return [] def content_call(self, method, *, arg_json=None, extras=None, serial=None): if method == "rag_upsert_json": self._docs[arg_json["source"]] = arg_json["text"] self.upserts.append(arg_json) return {"ok": "true"} if method == "rag_dump_json": src = arg_json["source"] if src not in self._docs: return {"ok": "false", "error": "not_found"} payload = {"source": src, "text": self._docs[src]} return {"ok": "true", "result_b64": base64.b64encode(_json.dumps(payload).encode()).decode()} return {"ok": "true"} def shell(self, command, *, serial=None, timeout=None): if "content delete" in command and "/rag/" in command: src = command.split("/rag/")[1].strip() self.deleted.append(src) self._docs.pop(src, None) return "" def test_rag_upsert_get_richtext(tmp_path): s = _store(tmp_path) adb = _RagAdb() rsvc.upsert_doc(adb, s, "D1", "anxiete", "Anxiété : signes.\nGestion : respiration lente.", now=NOW) assert adb.upserts[0]["source"] == "anxiete" and ":" in adb.upserts[0]["text"] d = rsvc.get_doc(adb, "D1", "anxiete") # rag_dump → texte riche intact assert d["text"].startswith("Anxiété") and ":" in d["text"] and "\n" in d["text"] assert "anxiete" in {x["source"] for x in rsvc.list_docs(adb, "D1")} def test_rag_delete(tmp_path): s = _store(tmp_path) adb = _RagAdb() rsvc.upsert_doc(adb, s, "D1", "temp", "x", now=NOW) rsvc.delete_doc(adb, s, "D1", "temp", now=NOW) assert adb.deleted == ["temp"] and "temp" not in adb._docs def test_rag_query_and_status(): adb = _RagAdb() hits = rsvc.query(adb, "D1", "je me sens anxieux") assert hits and hits[0]["source"] == "doc1" and hits[0]["score"] > 0.5 st = rsvc.status(adb, "D1") assert st["ready"] is True and st["model"] == "e5-small"