Files
zeekay 898d818183 fix(hanzo-memory): fix tests and clean up deprecated code
- Make MemoryResponse.memory field optional (used by legacy /v1/remember endpoint)
- Fix datetime.utcnow() deprecation warning (use datetime.now(timezone.utc))
- Make LanceDB import optional in backend_registry.py
- Remove duplicate imports in db/__init__.py
- Add singleton get_sqlite_client() function
- Fix import path (embeddings vs embedding) in mcp/server.py
- Move module-level MCPMemoryServer instantiation to main()
- Add 7 missing abstract method implementations to InfinityClient
- Fix test patching target (get_db_client not get_client)
- Add proper HTTPException re-raise in exception handlers
- Add reset_db_client() and reset_memory_service() to test fixtures
- Add list/DataFrame type handling in search_memories()
- Delete stale build directory and lancedb_client.py
- Fix nested workspace issue (remove hanzo-agent from uv.workspace)
- Add test dependencies (polars, httpx, fastapi, fastembed)
- Update CI workflow to install test dependencies

All 84 hanzo-memory tests pass, 3156 core SDK tests pass.
2026-02-02 22:14:46 -08:00

229 lines
7.2 KiB
Python

"""Tests for Knowledge Base API endpoints."""
import polars as pl
import pytest
from fastapi.testclient import TestClient
from hanzo_memory.server import app
class TestKnowledgeAPI:
"""Test Knowledge Base API endpoints."""
@pytest.fixture(autouse=True)
def setup(self, mock_auth, mock_db_client, mock_services):
"""Set up test client and mocks."""
self.client = TestClient(app)
self.headers = {"Authorization": "Bearer test-token"}
self.mock_db = mock_db_client
self.mock_embedding_service = mock_services["embedding"]
def test_create_knowledge_base(self):
"""Test creating a knowledge base."""
self.mock_db.create_knowledge_base.return_value = {
"kb_id": "kb123",
"name": "Test KB",
}
response = self.client.post(
"/v1/kb/create",
json={
"userid": "user123",
"name": "Test KB",
"kb_id": "kb123",
},
headers=self.headers,
)
assert response.status_code == 200
data = response.json()
assert data["kb_id"] == "kb123"
assert "Test KB" in data["message"]
# Verify DB calls
self.mock_db.create_knowledge_base.assert_called_once()
call_args = self.mock_db.create_knowledge_base.call_args[1]
assert call_args["kb_id"] == "kb123"
assert call_args["user_id"] == "user123"
assert call_args["name"] == "Test KB"
def test_create_knowledge_base_default_project(self):
"""Test creating KB with default project."""
# Mock project creation to raise (already exists)
self.mock_db.create_project.side_effect = Exception("Already exists")
self.mock_db.create_knowledge_base.return_value = {"kb_id": "kb123"}
response = self.client.post(
"/v1/kb/create",
json={
"userid": "user123",
"name": "Test KB",
},
headers=self.headers,
)
assert response.status_code == 200
# Verify default project was attempted
self.mock_db.create_project.assert_called_once()
project_args = self.mock_db.create_project.call_args[1]
assert project_args["project_id"] == "default-user123"
assert project_args["user_id"] == "user123"
def test_list_knowledge_bases(self):
"""Test listing knowledge bases."""
response = self.client.get(
"/v1/kb/list",
params={"userid": "user123"},
headers=self.headers,
)
assert response.status_code == 200
data = response.json()
assert data["userid"] == "user123"
assert "knowledge_bases" in data
assert data["total"] == 0
def test_add_facts(self):
"""Test adding facts to a knowledge base."""
self.mock_embedding_service.embed_text.return_value = [[0.1] * 384]
self.mock_db.add_fact.return_value = {"fact_id": "fact123"}
response = self.client.post(
"/v1/kb/facts/add",
json={
"userid": "user123",
"kb_id": "kb123",
"facts": [
{
"content": "Test fact 1",
"metadata": {"type": "test"},
},
{
"content": "Test fact 2",
"parent_id": "fact123",
},
],
},
headers=self.headers,
)
assert response.status_code == 200
data = response.json()
assert data["kb_id"] == "kb123"
assert data["facts_added"] == 2
assert len(data["facts"]) == 2
# Verify embeddings were generated
assert self.mock_embedding_service.embed_text.call_count == 2
# Verify facts were added
assert self.mock_db.add_fact.call_count == 2
def test_get_facts_with_query(self):
"""Test searching facts with a query."""
# Mock search results
mock_df = pl.DataFrame(
{
"fact_id": ["fact1", "fact2"],
"content": ["Fact 1", "Fact 2"],
"parent_id": ["", "parent1"],
"metadata": ['{"type": "test"}', '{"type": "example"}'],
"_similarity": [0.95, 0.85],
}
)
self.mock_db.search_facts.return_value = mock_df
self.mock_embedding_service.embed_text.return_value = [[0.2] * 384]
response = self.client.post(
"/v1/kb/facts/get",
json={
"userid": "user123",
"kb_id": "kb123",
"query": "test query",
"limit": 10,
},
headers=self.headers,
)
assert response.status_code == 200
data = response.json()
assert data["kb_id"] == "kb123"
assert len(data["facts"]) == 2
assert data["facts"][0]["fact_id"] == "fact1"
assert data["facts"][0]["similarity_score"] == 0.95
# Verify search was called
self.mock_db.search_facts.assert_called_once()
search_args = self.mock_db.search_facts.call_args[1]
assert search_args["kb_id"] == "kb123"
assert search_args["limit"] == 10
def test_get_facts_without_query(self):
"""Test getting facts without a search query."""
# API requires query parameter
response = self.client.post(
"/v1/kb/facts/get",
json={
"userid": "user123",
"kb_id": "kb123",
},
headers=self.headers,
)
assert response.status_code == 400
# With query, should return empty results
response = self.client.post(
"/v1/kb/facts/get",
json={
"userid": "user123",
"kb_id": "kb123",
"query": "test",
},
headers=self.headers,
)
assert response.status_code == 200
data = response.json()
assert data["kb_id"] == "kb123"
assert data["facts"] == []
assert data["total"] == 0
def test_delete_fact(self):
"""Test deleting a fact."""
response = self.client.post(
"/v1/kb/facts/delete",
json={
"userid": "user123",
"kb_id": "kb123",
"fact_id": "fact123",
"cascade": True,
},
headers=self.headers,
)
assert response.status_code == 200
data = response.json()
assert data["kb_id"] == "kb123"
assert data["fact_id"] == "fact123"
assert data["deleted"] is True
assert data["cascade"] is True
def test_knowledge_base_error_handling(self):
"""Test error handling in knowledge base operations."""
self.mock_db.create_knowledge_base.side_effect = Exception("DB Error")
response = self.client.post(
"/v1/kb/create",
json={
"userid": "user123",
"name": "Test KB",
},
headers=self.headers,
)
assert response.status_code == 500
data = response.json()
assert "detail" in data
assert "DB Error" in data["detail"]