"""Unit tests for chunk / embed / search. Run: python3 -m unittest discover . The embedding endpoint is a stub HTTP server on loopback (same process, test only) speaking both wire formats with a deterministic bag-of-words vector, so search ranking is meaningful without a model. """ import hashlib import json import os import re import shutil import sqlite3 import tempfile import threading import unittest from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from . import embed as em from .extract import extract from .manifest import validate_document from .sanitize import sanitize from .walk import walk def manifest(root, model="stub-model", dim=64, size=8, overlap=2): return validate_document({ "freeagent": 1, "id": "t", "owner": {"host": "h"}, "scope": {"roots": [root]}, "mask": ["read", "search", "index"], "stages": ["walk", "extract", "sanitize", "chunk", "embed"], "sweep": "manual", "sanitize": {"mode": "flag"}, "embed": {"model": model, "dim": dim, "chunk": {"size_tokens": size, "overlap_tokens": overlap}}, "sink": {"catalog": "local", "vectors": "local", "plaintext": "never"}, }) def bow_vector(text, dim): v = [0.0] * dim for w in re.findall(r"\w+", text.lower()): h = int(hashlib.md5(w.encode()).hexdigest(), 16) v[h % dim] += 1.0 return v class _Stub(BaseHTTPRequestHandler): dim = 64 serve = ("ollama", "openai") calls = [] def do_POST(self): n = int(self.headers.get("Content-Length", "0")) req = json.loads(self.rfile.read(n)) _Stub.calls.append((self.path, req["model"], len(req["input"]))) inputs = req["input"] if isinstance(req["input"], list) else [req["input"]] if self.path == "/api/embed" and "ollama" in self.serve: body = {"embeddings": [bow_vector(t, self.dim) for t in inputs]} elif self.path == "/v1/embeddings" and "openai" in self.serve: body = {"data": [{"index": i, "embedding": bow_vector(t, self.dim)} for i, t in enumerate(inputs)]} else: self.send_response(404) self.send_header("Content-Length", "0") self.end_headers() return data = json.dumps(body).encode() self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data) def log_message(self, *a): pass class ChunkUnitTests(unittest.TestCase): def test_windows_cover_text_and_overlap(self): text = " ".join("w%d" % i for i in range(20)) wins = list(em.chunk_text(text, 8, 2)) self.assertEqual(wins[0][0], 0) self.assertEqual(wins[-1][1], len(text)) # 20 tokens, step 6: windows at 0, 6, 12 — the third reaches the end self.assertEqual([em.count_tokens(w[2]) for w in wins], [8, 8, 8]) # step = 6: window 2 starts at token 6 = "w6" self.assertTrue(wins[1][2].startswith("w6 ")) # every char is inside at least one window covered = set() for s, e, _ in wins: covered.update(range(s, e)) self.assertEqual(len(covered), len(text)) def test_short_and_empty(self): self.assertEqual(list(em.chunk_text("", 8, 2)), []) self.assertEqual(list(em.chunk_text(" \n ", 8, 2)), []) wins = list(em.chunk_text("just three tokens", 8, 2)) self.assertEqual(len(wins), 1) self.assertEqual(wins[0][2], "just three tokens") def test_punctuation_is_a_token_and_ids_are_content_addressed(self): self.assertEqual(em.count_tokens("Hi, there!"), 4) self.assertEqual(em.chunk_id("d", "x"), em.chunk_id("d", "x")) self.assertNotEqual(em.chunk_id("d", "x"), em.chunk_id("e", "x")) def test_pack_roundtrip(self): v = [0.5, -1.25, 3.0] self.assertEqual(list(em.unpack(em.pack(v))), v) self.assertAlmostEqual(em.cosine([1, 0], [1, 0]), 1.0) self.assertAlmostEqual(em.cosine([1, 0], [0, 1]), 0.0) self.assertEqual(em.cosine([0, 0], [1, 1]), 0.0) class Case(unittest.TestCase): @classmethod def setUpClass(cls): cls.srv = ThreadingHTTPServer(("127.0.0.1", 0), _Stub) threading.Thread(target=cls.srv.serve_forever, daemon=True).start() cls.url = "http://127.0.0.1:%d" % cls.srv.server_address[1] @classmethod def tearDownClass(cls): cls.srv.shutdown() cls.srv.server_close() def setUp(self): self.root = tempfile.mkdtemp(prefix="fa-em-") self.out = tempfile.mkdtemp(prefix="fa-emout-") _Stub.dim, _Stub.serve, _Stub.calls = 64, ("ollama", "openai"), [] self.put("apples.txt", "apples pears plums. " * 12 + "orchard harvest.") self.put("routers.txt", "router switch firewall vlan. " * 12 + "network segment.") self.put("twin.txt", "apples pears plums. " * 12 + "orchard harvest.") self.env = {"EMBED_URL": self.url, "EMBED_BATCH": "4"} def tearDown(self): shutil.rmtree(self.root, ignore_errors=True) shutil.rmtree(self.out, ignore_errors=True) def put(self, name, text): with open(os.path.join(self.root, name), "w", encoding="utf-8") as fh: fh.write(text) def prep(self, m): walk(m, self.out) extract(m, self.out, use_external=False) sanitize(m, self.out, env={}) def chunks(self): with open(os.path.join(self.out, "chunks.jsonl"), encoding="utf-8") as fh: return [json.loads(l) for l in fh] def db(self, sql, *params): conn = sqlite3.connect(em.vectors_path(self.out)) try: return conn.execute(sql, params).fetchall() finally: conn.close() class StageTests(Case): def test_chunk_stage(self): m = manifest(self.root) self.prep(m) s = em.chunk(m, self.out) rows = self.chunks() self.assertEqual(s["counts"]["docs"], 2) # twin shares content with apples self.assertEqual(s["counts"]["chunks"], len(rows)) self.assertGreater(len(rows), 4) docs = {r["doc"] for r in rows} self.assertEqual(len(docs), 2) self.assertEqual(rows, sorted(rows, key=lambda r: (r["doc"], r["idx"]))) # deterministic em.chunk(m, self.out) self.assertEqual(rows, self.chunks()) def test_chunk_requires_sanitize(self): with self.assertRaises(FileNotFoundError): em.chunk(manifest(self.root), self.out) def test_embed_probe_refuses_wrong_dim(self): m = manifest(self.root, dim=32) # stub serves 64 self.prep(m) em.chunk(m, self.out) with self.assertRaises(em.EmbedError) as cm: em.embed(m, self.out, env=self.env) self.assertIn("serves 64-dim", str(cm.exception)) self.assertFalse(os.path.exists(em.vectors_path(self.out))) # refused before opening the store def test_embed_unreachable_fails_closed(self): m = manifest(self.root) self.prep(m) em.chunk(m, self.out) with self.assertRaises(em.EmbedError): em.embed(m, self.out, env={"EMBED_URL": "http://127.0.0.1:9"}) def test_embed_writes_reuses_and_keeps_old_models(self): m = manifest(self.root) self.prep(m) em.chunk(m, self.out) s1 = em.embed(m, self.out, env=self.env) n = s1["counts"]["unique"] # repeated paragraphs share a chunk id self.assertLess(n, s1["counts"]["chunks"]) self.assertEqual(s1["counts"]["embedded"], n) self.assertEqual(s1["endpoint"], "stub-model@%s(ollama)" % self.url) # batches of 4 + one probe call embed_calls = [c for c in _Stub.calls if c[0] == "/api/embed"] self.assertEqual(sum(c[2] for c in embed_calls), n + 1) self.assertTrue(all(c[2] <= 4 for c in embed_calls)) # second run: nothing re-embedded s2 = em.embed(m, self.out, env=self.env) self.assertEqual((s2["counts"]["embedded"], s2["counts"]["reused"]), (0, n)) # a second model ADDS rows; the first model's rows survive m2 = manifest(self.root, model="other-model") s3 = em.embed(m2, self.out, env=self.env) self.assertEqual(s3["counts"]["embedded"], n) self.assertEqual(sorted(x["model"] for x in s3["models_in_store"]), ["other-model", "stub-model"]) self.assertEqual(self.db("SELECT COUNT(*) FROM vectors WHERE model='stub-model'")[0][0], n) def test_stale_vectors_are_kept_and_counted(self): m = manifest(self.root) self.prep(m) em.chunk(m, self.out) em.embed(m, self.out, env=self.env) before = self.db("SELECT COUNT(*) FROM vectors")[0][0] self.put("routers.txt", "completely different content now.") self.prep(m) em.chunk(m, self.out) s = em.embed(m, self.out, env=self.env) self.assertGreater(s["stale_vectors"], 0) self.assertGreaterEqual(self.db("SELECT COUNT(*) FROM vectors")[0][0], before) # unchanged doc's chunks were reused, not re-embedded self.assertGreater(s["counts"]["reused"], 0) def test_openai_autodetect_and_forced_api(self): _Stub.serve = ("openai",) m = manifest(self.root) self.prep(m) em.chunk(m, self.out) s = em.embed(m, self.out, env=self.env) self.assertTrue(s["endpoint"].endswith("(openai)")) with self.assertRaises(em.EmbedError): em.embed(m, self.out, env=dict(self.env, EMBED_API="ollama")) with self.assertRaises(em.EmbedError): em.build_endpoint(m, dict(self.env, EMBED_API="magic")) def test_search_ranks_the_right_document(self): m = manifest(self.root) self.prep(m) em.chunk(m, self.out) em.embed(m, self.out, env=self.env) hits = em.search(m, self.out, "firewall vlan router", k=3, env=self.env) self.assertEqual(len(hits), 3) self.assertIn("router", hits[0]["text"]) self.assertNotIn("apples", hits[0]["text"]) hits = em.search(m, self.out, "pears and plums", k=1, env=self.env) self.assertIn("apples", hits[0]["text"]) self.assertTrue(0 < hits[0]["score"] <= 1.0) # a model with no vectors yields nothing, not an error self.assertEqual(em.search(manifest(self.root, model="nope"), self.out, "x", env=self.env), []) if __name__ == "__main__": unittest.main()