Files
wehub-resource-sync 2cab53bc94
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers CLI - Convert documentation to AI skills dockerfile:Dockerfile name:skill-seekers]) (push) Waiting to run
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers MCP Server - 25 tools for AI assistants dockerfile:Dockerfile.mcp name:skill-seekers-mcp]) (push) Waiting to run
Docker Publish / Test Docker Images (push) Blocked by required conditions
Test Vector Database Adaptors / Test chroma Adaptor (push) Waiting to run
Test Vector Database Adaptors / Test faiss Adaptor (push) Waiting to run
Test Vector Database Adaptors / Test qdrant Adaptor (push) Waiting to run
Test Vector Database Adaptors / Test weaviate Adaptor (push) Waiting to run
Test Vector Database Adaptors / Test MCP Vector DB Tools (push) Waiting to run
Tests / Code Quality (Ruff & Mypy) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (macos-latest, 3.11) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (macos-latest, 3.12) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.10) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.11) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.12) (push) Waiting to run
Tests / Tests (push) Blocked by required conditions
Tests / Serial / Integration / E2E Tests (push) Blocked by required conditions
Tests / MCP Server Tests (push) Blocked by required conditions
chore: import upstream snapshot with attribution
2026-07-13 12:46:28 +08:00

198 lines
6.5 KiB
Python

"""Tests for the FastAPI embedding server (embedding/server.py).
Uses starlette TestClient for in-process HTTP testing.
"""
import pytest
from unittest.mock import patch
try:
from starlette.testclient import TestClient
from skill_seekers.embedding.server import app as _embedding_app
STARLETTE_AVAILABLE = True
except (ImportError, SystemExit):
STARLETTE_AVAILABLE = False
pytestmark = pytest.mark.skipif(not STARLETTE_AVAILABLE, reason="Starlette not installed")
@pytest.fixture
def mock_generator():
with patch("skill_seekers.embedding.server.generator") as mock_gen:
mock_gen.list_models.return_value = [
{
"name": "text-embedding-3-small",
"provider": "openai",
"dimensions": 1536,
"max_tokens": 8191,
},
{
"name": "text-embedding-3-large",
"provider": "openai",
"dimensions": 3072,
"max_tokens": 8191,
},
]
mock_gen.generate.return_value = [0.1, 0.2, 0.3]
mock_gen.generate_batch.return_value = ([[0.1, 0.2], [0.3, 0.4]], 2)
mock_gen.compute_hash.return_value = "mock_hash_abc123"
yield mock_gen
@pytest.fixture
def mock_cache():
with patch("skill_seekers.embedding.server.cache") as mock_cache:
mock_cache.has.return_value = False
mock_cache.get.return_value = None
mock_cache.size.return_value = 42
mock_cache.stats.return_value = {
"total": 42,
"by_model": {"text-embedding-3-small": 42},
"top_accessed": [],
"expired": 0,
"ttl_days": 30,
}
mock_cache.clear.return_value = 5
mock_cache.clear_expired.return_value = 3
yield mock_cache
@pytest.fixture
def client(mock_generator, mock_cache): # noqa: ARG001
with TestClient(_embedding_app) as c:
yield c
class TestRoot:
def test_root_endpoint(self, client):
response = client.get("/")
assert response.status_code == 200
data = response.json()
assert data["service"] == "Skill Seekers Embedding API"
assert data["version"] == "1.0.0"
assert "/docs" in data["docs"]
assert "/health" in data["health"]
class TestHealth:
def test_health_endpoint(self, client):
response = client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "ok"
assert data["version"] == "1.0.0"
assert "models" in data
assert data["cache_enabled"] is True
assert data["cache_size"] == 42
class TestModels:
def test_list_models(self, client):
response = client.get("/models")
assert response.status_code == 200
data = response.json()
assert data["count"] == 2
assert len(data["models"]) == 2
assert data["models"][0]["name"] == "text-embedding-3-small"
assert data["models"][0]["provider"] == "openai"
assert data["models"][0]["dimensions"] == 1536
class TestEmbedText:
def test_embed_single_text(self, client):
response = client.post(
"/embed", json={"text": "Hello world", "model": "text-embedding-3-small"}
)
assert response.status_code == 200
data = response.json()
assert data["model"] == "text-embedding-3-small"
assert len(data["embedding"]) == 3
assert data["cached"] is False
def test_embed_cached(self, client, mock_cache, mock_generator):
mock_cache.has.return_value = True
mock_cache.get.return_value = [0.5, 0.6, 0.7]
response = client.post(
"/embed", json={"text": "cached text", "model": "text-embedding-3-small"}
)
assert response.status_code == 200
data = response.json()
assert data["cached"] is True
assert data["embedding"] == [0.5, 0.6, 0.7]
def test_embed_with_normalize(self, client):
response = client.post("/embed", json={"text": "test", "normalize": False})
assert response.status_code == 200
class TestEmbedBatch:
def test_embed_batch(self, client):
response = client.post("/embed/batch", json={"texts": ["text1", "text2"]})
assert response.status_code == 200
data = response.json()
assert data["count"] == 2
assert data["dimensions"] == 2
assert len(data["embeddings"]) == 2
def test_embed_batch_empty(self, client, mock_generator):
mock_generator.generate_batch.return_value = ([[0.1]], 1)
response = client.post("/embed/batch", json={"texts": ["one"]})
assert response.status_code == 200
class TestEmbedSkill:
def test_embed_skill(self, client, tmp_path):
skill_dir = tmp_path / "test-skill"
skill_dir.mkdir()
(skill_dir / "SKILL.md").write_text(
"# Test Skill\n\nThis is a test skill with enough content\n" * 5
)
response = client.post("/embed/skill", json={"skill_path": str(skill_dir)})
assert response.status_code == 200
data = response.json()
assert data["skill_name"] == "test-skill"
assert data["model"] == "text-embedding-3-small"
def test_embed_skill_not_found(self, client):
response = client.post("/embed/skill", json={"skill_path": "/nonexistent/path"})
assert response.status_code == 404
def test_embed_skill_no_skill_md(self, client, tmp_path):
skill_dir = tmp_path / "empty-skill"
skill_dir.mkdir()
response = client.post("/embed/skill", json={"skill_path": str(skill_dir)})
assert response.status_code == 404
class TestCacheEndpoints:
def test_cache_stats(self, client):
response = client.get("/cache/stats")
assert response.status_code == 200
data = response.json()
assert data["total"] == 42
def test_clear_cache_all(self, client):
response = client.post("/cache/clear")
assert response.status_code == 200
data = response.json()
assert data["status"] == "ok"
assert data["deleted"] == 5
def test_clear_cache_by_model(self, client):
response = client.post("/cache/clear?model=text-embedding-3-small")
assert response.status_code == 200
data = response.json()
assert data["model"] == "text-embedding-3-small"
def test_clear_expired(self, client):
response = client.post("/cache/clear-expired")
assert response.status_code == 200
data = response.json()
assert data["status"] == "ok"
assert data["deleted"] == 3