honcho/tests/routes/test_peers.py

625 lines
20 KiB
Python

import pytest
from fastapi.testclient import TestClient
from nanoid import generate as generate_nanoid
from src.models import Peer, Workspace
def test_get_or_create_peer(client: TestClient, sample_data: tuple[Workspace, Peer]):
test_workspace, _ = sample_data
name = str(generate_nanoid())
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers",
json={"name": name, "metadata": {"peer_key": "peer_value"}},
)
assert response.status_code == 200
data = response.json()
assert data["id"] == name
assert data["metadata"] == {"peer_key": "peer_value"}
assert "id" in data
def test_get_or_create_peer_with_configuration(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test peer creation with configuration parameter"""
test_workspace, _ = sample_data
name = str(generate_nanoid())
configuration = {"experimental": True, "beta": False}
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers",
json={"name": name, "configuration": configuration},
)
assert response.status_code == 200
data = response.json()
assert data["id"] == name
assert data["configuration"] == configuration
def test_get_or_create_peer_with_all_optional_params(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test peer creation with all optional parameters"""
test_workspace, _ = sample_data
name = str(generate_nanoid())
metadata = {"key": "value", "number": 42}
configuration = {"feature1": True, "feature2": False}
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers",
json={"name": name, "metadata": metadata, "configuration": configuration},
)
assert response.status_code == 200
data = response.json()
assert data["id"] == name
assert data["metadata"] == metadata
assert data["configuration"] == configuration
def test_get_or_create_existing_peer(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
test_workspace, _ = sample_data
name = str(generate_nanoid())
# Create the peer
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers",
json={"name": name, "metadata": {"peer_key": "peer_value"}},
)
assert response.status_code == 200
peer1 = response.json()
# Try to create the same peer again - should return existing peer
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers",
json={"name": name, "metadata": {"peer_key": "peer_value"}},
)
assert response.status_code == 200
peer2 = response.json()
# Both should be the same peer
assert peer1["id"] == peer2["id"]
assert peer1["metadata"] == peer2["metadata"]
def test_get_peers(client: TestClient, sample_data: tuple[Workspace, Peer]):
test_workspace, _ = sample_data
# Create a few peers with metadata
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers",
json={"name": str(generate_nanoid()), "metadata": {"peer_key": "peer_value"}},
)
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers",
json={"name": str(generate_nanoid()), "metadata": {"peer_key": "peer_value"}},
)
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers",
json={"name": str(generate_nanoid()), "metadata": {"peer_key": "peer_value2"}},
)
# Get all peers
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/list",
json={},
)
assert response.status_code == 200
data = response.json()
assert "items" in data
assert len(data["items"]) > 0
# Get peers with simple filter (backward compatibility)
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/list",
json={"filter": {"metadata": {"peer_key": "peer_value"}}},
)
assert response.status_code == 200
data = response.json()
assert "items" in data
assert len(data["items"]) == 2
assert data["items"][0]["metadata"]["peer_key"] == "peer_value"
# Test new filter with NOT operator
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/list",
json={"filter": {"NOT": [{"metadata": {"peer_key": "peer_value2"}}]}},
)
assert response.status_code == 200
data = response.json()
assert "items" in data
# Should find peers that don't have peer_key = "peer_value2"
# This includes the 2 peers with "peer_value" + the sample peer with empty metadata
assert len(data["items"]) == 3
def test_get_peers_with_empty_filter(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test peer listing with empty filter object"""
test_workspace, _ = sample_data
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/list", json={"filter": {}}
)
assert response.status_code == 200
data = response.json()
assert "items" in data
assert isinstance(data["items"], list)
def test_get_peers_with_null_filter(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test peer listing with null filter"""
test_workspace, _ = sample_data
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/list", json={"filter": None}
)
assert response.status_code == 200
data = response.json()
assert "items" in data
assert isinstance(data["items"], list)
def test_update_peer(client: TestClient, sample_data: tuple[Workspace, Peer]):
test_workspace, test_peer = sample_data
response = client.put(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}",
json={"metadata": {"new_key": "new_value"}},
)
assert response.status_code == 200
data = response.json()
assert data["metadata"] == {"new_key": "new_value"}
def test_update_peer_with_configuration(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test peer update with configuration parameter"""
test_workspace, test_peer = sample_data
configuration = {"new_feature": True, "legacy_feature": False}
response = client.put(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}",
json={"configuration": configuration},
)
assert response.status_code == 200
data = response.json()
assert data["configuration"] == configuration
def test_update_peer_with_all_optional_params(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test peer update with both metadata and configuration"""
test_workspace, test_peer = sample_data
metadata = {"updated_key": "updated_value", "count": 100}
configuration = {"experimental": True, "beta": True}
response = client.put(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}",
json={"metadata": metadata, "configuration": configuration},
)
assert response.status_code == 200
data = response.json()
assert data["metadata"] == metadata
assert data["configuration"] == configuration
def test_update_peer_with_null_metadata(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test peer update with null metadata"""
test_workspace, test_peer = sample_data
# First set some metadata
client.put(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}",
json={"metadata": {"temp": "value"}},
)
# Then clear it with null
response = client.put(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}",
json={"metadata": None},
)
assert response.status_code == 200
data = response.json()
assert "metadata" in data
def test_update_peer_with_null_configuration(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test peer update with null configuration"""
test_workspace, test_peer = sample_data
response = client.put(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}",
json={"configuration": None},
)
assert response.status_code == 200
data = response.json()
assert "configuration" in data
def test_get_sessions_for_peer_no_sessions(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
test_workspace, test_peer = sample_data
# Get sessions for the peer
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/sessions",
json={},
)
assert response.status_code == 200
data = response.json()
assert "items" in data
def test_get_sessions_for_peer(client: TestClient, sample_data: tuple[Workspace, Peer]):
test_workspace, test_peer = sample_data
# Create session for the peer
session_name = str(generate_nanoid())
create_response = client.post(
f"/v2/workspaces/{test_workspace.name}/sessions",
json={"id": session_name, "peer_names": {test_peer.name: {}}},
)
assert create_response.status_code == 200
created_session = create_response.json()
assert created_session["id"] == session_name
# Now get sessions for the peer and validate the session is returned
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/sessions",
)
assert response.status_code == 200
data = response.json()
assert "items" in data
# Check that the created session is in the returned items
session_ids = [item["id"] for item in data["items"]]
assert session_name in session_ids
assert len(data["items"]) == 1
def test_get_sessions_for_peer_with_empty_filter(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test getting sessions for peer with empty filter object"""
test_workspace, test_peer = sample_data
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/sessions",
json={"filter": {}},
)
assert response.status_code == 200
data = response.json()
assert "items" in data
assert isinstance(data["items"], list)
def test_create_and_get_messages_for_peer(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
test_workspace, test_peer = sample_data
# Create messages for the peer
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages",
json={
"messages": [
{
"content": "Hello world",
"peer_id": test_peer.name,
"metadata": {"message_key": "message_value"},
},
{
"content": "Second message",
"peer_id": test_peer.name,
"metadata": {"message_key": "message_value2"},
},
]
},
)
assert response.status_code == 200
data = response.json()
assert len(data) == 2
assert data[0]["content"] == "Hello world"
assert data[1]["content"] == "Second message"
assert data[0]["metadata"] == {"message_key": "message_value"}
# Get messages for the peer
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages/list",
json={},
)
assert response.status_code == 200
data = response.json()
assert "items" in data
assert len(data["items"]) == 2
assert data["items"][0]["content"] == "Hello world"
assert data["items"][1]["content"] == "Second message"
assert data["items"][0]["metadata"] == {"message_key": "message_value"}
assert data["items"][1]["metadata"] == {"message_key": "message_value2"}
def test_get_messages_for_peer_with_reverse(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test getting messages for peer with reverse parameter"""
test_workspace, test_peer = sample_data
# Create messages
client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages",
json={
"messages": [
{"content": "First message", "peer_id": test_peer.name},
{"content": "Second message", "peer_id": test_peer.name},
]
},
)
# Test normal order
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages/list",
json={},
)
assert response.status_code == 200
normal_data = response.json()
# Test reversed order
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages/list?reverse=true",
json={},
)
assert response.status_code == 200
reversed_data = response.json()
# Both should have items
assert len(normal_data["items"]) > 0
assert len(reversed_data["items"]) > 0
def test_get_messages_for_peer_with_empty_filter(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test getting messages for peer with empty filter object"""
test_workspace, test_peer = sample_data
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages/list",
json={"filter": {}},
)
assert response.status_code == 200
data = response.json()
assert "items" in data
assert isinstance(data["items"], list)
def test_get_messages_for_peer_with_null_filter(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test getting messages for peer with null filter"""
test_workspace, test_peer = sample_data
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages/list",
json={"filter": None},
)
assert response.status_code == 200
data = response.json()
assert "items" in data
assert isinstance(data["items"], list)
def test_chat(client: TestClient, sample_data: tuple[Workspace, Peer]):
test_workspace, test_peer = sample_data
target_peer = str(generate_nanoid())
# Test chat endpoint
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/chat",
json={
"queries": "Hello, how are you?",
"stream": False,
"target": target_peer,
},
)
assert response.status_code == 200
data = response.json()
assert "content" in data
def test_chat_with_optional_params(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test chat endpoint with optional parameters"""
test_workspace, test_peer = sample_data
session_id = str(generate_nanoid())
# Create a session first
client.post(
f"/v2/workspaces/{test_workspace.name}/sessions",
json={"id": session_id, "peer_names": {test_peer.name: {}}},
)
# Test chat without optional parameters
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/chat",
json={
"queries": "Hello, how are you?",
"stream": False,
"session_id": session_id,
},
)
assert response.status_code == 200
data = response.json()
assert "content" in data
def test_get_peer_representation_with_session(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test peer representation with session_id parameter"""
test_workspace, test_peer = sample_data
session_id = str(generate_nanoid())
# Create a session first
client.post(
f"/v2/workspaces/{test_workspace.name}/sessions",
json={"id": session_id, "peer_names": {test_peer.name: {}}},
)
# Test representation scoped to session
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/representation",
json={
"session_id": session_id,
"queries": "Hello, how are you?",
},
)
assert response.status_code == 200
def test_search_peer(client: TestClient, sample_data: tuple[Workspace, Peer]):
"""Test the peer search functionality"""
test_workspace, test_peer = sample_data
# Add some messages to search through
client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages",
json={
"messages": [
{"content": "Search this content", "peer_id": test_peer.name},
{"content": "Another searchable message", "peer_id": test_peer.name},
]
},
)
# Search with a query
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/search",
json={"query": "search query"},
)
assert response.status_code == 200
data = response.json()
# Response should have pagination structure
assert "items" in data
assert "total" in data
assert "page" in data
assert "size" in data
assert isinstance(data["items"], list)
def test_search_peer_empty_query(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test peer search with empty query"""
test_workspace, test_peer = sample_data
# Search with empty query
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/search",
json={"query": ""},
)
assert response.status_code == 200
data = response.json()
# Response should still have proper pagination structure
assert "items" in data
assert isinstance(data["items"], list)
def test_search_peer_nonexistent(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test searching a peer that doesn't exist"""
test_workspace, _ = sample_data
nonexistent_peer_id = str(generate_nanoid())
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{nonexistent_peer_id}/search",
json={"query": "test query"},
)
# This should probably return 404 or handle gracefully
# The exact behavior depends on the crud.search implementation
assert response.status_code in [200, 404, 422]
def test_search_peer_with_semantic_search_false(
client: TestClient, sample_data: tuple[Workspace, Peer]
):
"""Test peer search with semantic=false"""
test_workspace, test_peer = sample_data
# Add some messages to search through
client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages",
json={
"messages": [
{"content": "Search this content", "peer_id": test_peer.name},
{"content": "Another searchable message", "peer_id": test_peer.name},
]
},
)
# Search with semantic=false
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/search",
json={"query": "search", "semantic": False},
)
assert response.status_code == 200
data = response.json()
# Response should have pagination structure
assert "items" in data
assert "total" in data
assert "page" in data
assert "size" in data
assert isinstance(data["items"], list)
def test_search_peer_with_semantic_search_true_disabled(
client: TestClient,
sample_data: tuple[Workspace, Peer],
monkeypatch: pytest.MonkeyPatch,
):
"""Test peer search with semantic=true when EMBED_MESSAGES is disabled"""
# Override the EMBED_MESSAGES setting to False for this test
monkeypatch.setattr("src.config.settings.LLM.EMBED_MESSAGES", False)
test_workspace, test_peer = sample_data
# Add some messages to search through
client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages",
json={
"messages": [
{"content": "Search this content", "peer_id": test_peer.name},
{"content": "Another searchable message", "peer_id": test_peer.name},
]
},
)
# Search with semantic=true (should fail if EMBED_MESSAGES is disabled)
response = client.post(
f"/v2/workspaces/{test_workspace.name}/peers/{test_peer.name}/search",
json={"query": "search", "semantic": True},
)
assert response.status_code == 405
data = response.json()
assert "Semantic search requires EMBED_MESSAGES flag to be enabled" in data.get(
"detail", ""
)