Initial commit: VoIdeaAI - voice-first AI idea assistant

This commit is contained in:
2026-05-13 12:51:42 +03:00
commit 688d043dad
421 changed files with 47915 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
"""Root conftest for VoIdea tests."""
import os
import sys
from pathlib import Path
# Ensure app is importable
sys.path.insert(0, str(Path(__file__).parent.parent))
# Set test environment
os.environ.setdefault("PROJECT_ENV", "test")
os.environ.setdefault("PROJECT_VERSION", "1.0.0")
os.environ.setdefault("JWT_SECRET_KEY", "test-secret-key-not-for-production")
+1
View File
@@ -0,0 +1 @@
"""Integration tests for VoIdeaAI."""
+80
View File
@@ -0,0 +1,80 @@
"""Integration tests for auth API endpoints.
Uses FastAPI TestClient to test the full request-response cycle.
Requires a running database or test DB configured via DATABASE_URL.
"""
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
class TestAuthIntegration:
def test_health(self):
resp = client.get("/health")
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "healthy"
def test_register_validation(self):
resp = client.post(
"/api/v1/auth/register",
json={"email": "bad", "password": "12", "display_name": ""},
)
assert resp.status_code == 422
def test_login_invalid_credentials(self):
resp = client.post(
"/api/v1/auth/login",
json={"email": "nonexistent@test.com", "password": "wrongpass"},
)
assert resp.status_code == 401
assert resp.json()["detail"] == "Invalid credentials"
def test_register_and_login_flow(self):
import uuid
suffix = uuid.uuid4().hex[:8]
email = f"test{suffix}@example.com"
reg_resp = client.post(
"/api/v1/auth/register",
json={
"email": email,
"password": "StrongPass1",
"display_name": "Test User",
},
)
assert reg_resp.status_code == 201
tokens = reg_resp.json()
assert "access_token" in tokens
assert "refresh_token" in tokens
login_resp = client.post(
"/api/v1/auth/login",
json={"email": email, "password": "StrongPass1"},
)
assert login_resp.status_code == 200
assert "access_token" in login_resp.json()
def test_refresh_token(self):
import uuid
suffix = uuid.uuid4().hex[:8]
email = f"refresh{suffix}@example.com"
reg = client.post(
"/api/v1/auth/register",
json={"email": email, "password": "Pass1234", "display_name": "T"},
)
refresh_token = reg.json()["refresh_token"]
refresh_resp = client.post(
"/api/v1/auth/refresh",
json={"refresh_token": refresh_token},
)
assert refresh_resp.status_code == 200
new_tokens = refresh_resp.json()
assert new_tokens["refresh_token"] != refresh_token
+105
View File
@@ -0,0 +1,105 @@
"""Integration tests for voice API endpoints.
Uses FastAPI TestClient. Registers a test user and tests voice chat flow.
"""
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def _register_user() -> tuple[str, str]:
import uuid
suffix = uuid.uuid4().hex[:8]
email = f"voice{suffix}@example.com"
resp = client.post(
"/api/v1/auth/register",
json={"email": email, "password": "TestPass1", "display_name": "VoiceTester"},
)
assert resp.status_code == 201
data = resp.json()
return data["access_token"], email
class TestVoiceIntegration:
def test_list_role_agents(self):
resp = client.get("/api/v1/voice/agents")
assert resp.status_code == 200
agents = resp.json()
assert len(agents) >= 13
names = [a["name"] for a in agents]
assert "Бизнес-аналитик" in names
assert "Дирижёр" not in names
def test_chat_requires_auth(self):
resp = client.post(
"/api/v1/voice/chat",
json={"text": "hello"},
)
assert resp.status_code in (401, 403)
def test_chat_with_auth(self):
token, _ = _register_user()
resp = client.post(
"/api/v1/voice/chat",
json={"text": "Придумай идею для стартапа"},
headers={"Authorization": f"Bearer {token}"},
)
assert resp.status_code == 200
data = resp.json()
assert "response" in data
assert "agent_name" in data
assert "session_id" in data
assert data["session_id"] != ""
def test_chat_with_session(self):
token, _ = _register_user()
chat1 = client.post(
"/api/v1/voice/chat",
json={"text": "Экология"},
headers={"Authorization": f"Bearer {token}"},
)
session_id = chat1.json()["session_id"]
chat2 = client.post(
"/api/v1/voice/chat",
json={"text": "А какие риски?", "session_id": session_id},
headers={"Authorization": f"Bearer {token}"},
)
assert chat2.status_code == 200
assert chat2.json()["session_id"] == session_id
def test_sessions_list(self):
token, _ = _register_user()
client.post(
"/api/v1/voice/chat",
json={"text": "Тестовая идея"},
headers={"Authorization": f"Bearer {token}"},
)
resp = client.get(
"/api/v1/voice/sessions",
headers={"Authorization": f"Bearer {token}"},
)
assert resp.status_code == 200
sessions = resp.json()
assert len(sessions) >= 1
def test_rate_interaction(self):
token, _ = _register_user()
chat = client.post(
"/api/v1/voice/chat",
json={"text": "Идея для оценки"},
headers={"Authorization": f"Bearer {token}"},
)
interaction_id = chat.json().get("interaction_id")
if not interaction_id:
return
resp = client.post(
"/api/v1/voice/rate",
json={"interaction_id": interaction_id, "rating": 5},
headers={"Authorization": f"Bearer {token}"},
)
assert resp.status_code == 200
View File
+13
View File
@@ -0,0 +1,13 @@
"""Smoke tests for VoIdea health endpoints."""
def test_health_endpoint_exists():
"""Health endpoint route exists and returns expected structure.
This test verifies the route config without hitting a real server.
"""
from app.main import app
routes = [route.path for route in app.routes]
assert "/health" in routes
assert "/api/v1/health" in routes
+41
View File
@@ -0,0 +1,41 @@
# Agent Tests - VoIdea
## Overview
Unit tests for system agents.
## Structure
```
tests/unit/agents/
├── test_base.py # Base agent tests
├── test_doc_agent.py
├── test_backlog_agent.py
├── test_spec_agent.py
├── test_audit_agent.py
├── test_observer_agent.py
├── test_evolution_agent.py
└── test_registry.py
```
## Running Tests
```bash
# Run all agent tests
pytest tests/unit/agents/ -v
# Run specific agent tests
pytest tests/unit/agents/test_doc_agent.py -v
# With coverage
pytest tests/unit/agents/ --cov=app.agents --cov-report=html
```
## Notes
- Tests use mocks where database is required
- Integration tests require actual database connection
---
*Maintained by DocAgent*
+78
View File
@@ -0,0 +1,78 @@
"""Tests for AuditAgent."""
import pytest
from app.agents.audit_agent import AuditAgent
@pytest.fixture
def audit_agent():
return AuditAgent()
@pytest.mark.asyncio
async def test_audit_agent_initialization(audit_agent):
"""Test AuditAgent initializes correctly."""
assert audit_agent.name == "audit_agent"
assert audit_agent.version == "1.0.0"
@pytest.mark.asyncio
async def test_audit_agent_health_check(audit_agent):
"""Test AuditAgent health check."""
result = await audit_agent.health_check()
assert isinstance(result, bool)
@pytest.mark.asyncio
async def test_audit_agent_run(audit_agent):
"""Test AuditAgent run."""
context = {"action": "quick", "paths": ["app"]}
result = await audit_agent.run(context)
assert hasattr(result, "success")
assert hasattr(result, "message")
@pytest.mark.asyncio
async def test_audit_agent_quick_audit(audit_agent):
"""Test AuditAgent quick audit."""
result = await audit_agent._run_quick_audit(["app"])
assert hasattr(result, "success")
assert hasattr(result, "data")
@pytest.mark.asyncio
async def test_audit_agent_docs_check(audit_agent):
"""Test AuditAgent docs check."""
result = await audit_agent._check_docs()
assert hasattr(result, "success")
assert "missing_docs" in result.data
@pytest.mark.asyncio
async def test_audit_agent_status_transitions(audit_agent):
"""Test AuditAgent status transitions."""
assert audit_agent.status.value == "idle"
await audit_agent.set_running("audit")
assert audit_agent.status.value == "running"
await audit_agent.set_idle()
assert audit_agent.status.value == "idle"
@pytest.mark.asyncio
async def test_audit_agent_metrics(audit_agent):
"""Test AuditAgent metrics."""
metrics = await audit_agent.get_metrics()
assert "agent_id" in metrics
assert "status" in metrics
@pytest.mark.asyncio
async def test_audit_agent_full_audit(audit_agent):
"""Test AuditAgent full audit."""
context = {"action": "full", "paths": ["app"]}
result = await audit_agent.run(context)
assert hasattr(result, "success")
assert hasattr(result, "data")
+73
View File
@@ -0,0 +1,73 @@
"""Tests for BacklogAgent."""
import pytest
from uuid import uuid4
from app.agents.backlog_agent import BacklogAgent
from app.agents.base import AgentResult
@pytest.fixture
def backlog_agent():
return BacklogAgent(session=None)
@pytest.mark.asyncio
async def test_backlog_agent_initialization(backlog_agent):
"""Test BacklogAgent initializes correctly."""
assert backlog_agent.name == "backlog_agent"
assert backlog_agent.version == "1.0.0"
@pytest.mark.asyncio
async def test_backlog_agent_health_check(backlog_agent):
"""Test BacklogAgent health check."""
result = await backlog_agent.health_check()
assert result is True
@pytest.mark.asyncio
async def test_backlog_agent_run_without_session(backlog_agent):
"""Test BacklogAgent run without database session."""
result = await backlog_agent.run()
assert result.success is False
assert "session" in result.message.lower()
@pytest.mark.asyncio
async def test_backlog_agent_suggest_items(backlog_agent):
"""Test BacklogAgent suggestions."""
context = {"action": "suggest"}
result = await backlog_agent.run(context)
assert result.success
assert "suggestions" in result.data
@pytest.mark.asyncio
async def test_backlog_agent_list_items(backlog_agent):
"""Test BacklogAgent list items without session."""
context = {"action": "list"}
result = await backlog_agent.run(context)
assert result.success is False
@pytest.mark.asyncio
async def test_backlog_agent_status(backlog_agent):
"""Test BacklogAgent status."""
await backlog_agent.set_running("test")
assert backlog_agent.status.value == "running"
await backlog_agent.set_idle()
assert backlog_agent.status.value == "idle"
@pytest.mark.asyncio
async def test_backlog_agent_create_without_session(backlog_agent):
"""Test BacklogAgent create without session."""
context = {
"action": "create",
"title": "Test Task",
"item_type": "task",
}
result = await backlog_agent.run(context)
assert result.success is False
+130
View File
@@ -0,0 +1,130 @@
"""Tests for BaseAgent versioning methods."""
import hashlib
from pathlib import Path
import pytest
from app.agents.base import BaseAgent
class SimpleTestAgent(BaseAgent):
"""Minimal agent for testing base methods."""
name = "test_agent"
version = "1.0.0"
description = "Test agent"
async def run(self, context=None):
return await super().run(context) # pragma: no cover
async def health_check(self):
return True
@pytest.fixture
def agent():
return SimpleTestAgent()
@pytest.fixture
def cleanup_changelog():
yield
path = Path("CHANGELOG") / "agents" / "test_agent.md"
if path.exists():
path.unlink()
@pytest.mark.asyncio
async def test_agent_initialization(agent):
"""Test agent initializes with default version."""
assert agent.name == "test_agent"
assert agent.version == "1.0.0"
def test_compute_checksum(agent):
"""Test compute_checksum returns valid SHA256."""
checksum = agent.compute_checksum()
assert len(checksum) == 64
assert all(c in "0123456789abcdef" for c in checksum)
def test_compute_checksum_is_deterministic(agent):
"""Test checksum is deterministic for same file."""
assert agent.compute_checksum() == agent.compute_checksum()
def test_bump_version_patch(agent):
"""Test bump_version patch increments patch."""
new_version = agent.bump_version("patch")
assert new_version == "1.0.1"
assert agent.version == "1.0.1"
def test_bump_version_minor(agent):
"""Test bump_version minor increments minor, resets patch."""
agent.version = "1.0.5"
new_version = agent.bump_version("minor")
assert new_version == "1.1.0"
assert agent.version == "1.1.0"
def test_bump_version_major(agent):
"""Test bump_version major increments major, resets minor and patch."""
agent.version = "1.2.3"
new_version = agent.bump_version("major")
assert new_version == "2.0.0"
assert agent.version == "2.0.0"
def test_bump_version_default_is_patch(agent):
"""Test bump_version defaults to patch."""
agent.version = "2.0.0"
new_version = agent.bump_version()
assert new_version == "2.0.1"
@pytest.mark.asyncio
async def test_write_changelog_entry_creates_file(agent, cleanup_changelog):
"""Test _write_changelog_entry creates a new changelog file."""
agent._write_changelog_entry("1.0.0", ["Initial version"])
path = Path("CHANGELOG") / "agents" / "test_agent.md"
assert path.exists()
content = path.read_text(encoding="utf-8")
assert "# test_agent Changelog" in content
assert "## 1.0.0" in content
assert "- Initial version" in content
@pytest.mark.asyncio
async def test_write_changelog_entry_appends(agent, cleanup_changelog):
"""Test _write_changelog_entry appends to existing file."""
agent._write_changelog_entry("1.0.0", ["Initial version"])
agent._write_changelog_entry("1.0.1", ["Bugfix"])
path = Path("CHANGELOG") / "agents" / "test_agent.md"
content = path.read_text(encoding="utf-8")
assert "## 1.0.0" in content
assert "## 1.0.1" in content
assert "- Bugfix" in content
@pytest.mark.asyncio
async def test_check_version_no_changelog(agent, cleanup_changelog):
"""Test _check_version creates initial changelog on first run."""
result = await agent._check_version(["Initial version"])
assert result is not None
path = Path("CHANGELOG") / "agents" / "test_agent.md"
assert path.exists()
@pytest.mark.asyncio
async def test_check_version_unchanged(agent, cleanup_changelog):
"""Test _check_version returns None when nothing changed."""
agent._write_changelog_entry("1.0.0", ["Initial version"])
result = await agent._check_version()
assert result is None
def test_changelog_dir_default(agent):
"""Test changelog_dir attribute."""
assert str(agent.changelog_dir) == "CHANGELOG\\agents"
+87
View File
@@ -0,0 +1,87 @@
"""Tests for DocAgent."""
import pytest
from pathlib import Path
from app.agents.doc_agent import DocAgent
@pytest.fixture
def doc_agent():
return DocAgent()
@pytest.mark.asyncio
async def test_doc_agent_initialization(doc_agent):
"""Test DocAgent initializes correctly."""
assert doc_agent.name == "doc_agent"
assert doc_agent.version == "1.0.0"
assert doc_agent.description
@pytest.mark.asyncio
async def test_doc_agent_health_check(doc_agent):
"""Test DocAgent health check."""
result = await doc_agent.health_check()
assert isinstance(result, bool)
@pytest.mark.asyncio
async def test_doc_agent_run_without_context(doc_agent):
"""Test DocAgent run without context."""
result = await doc_agent.run()
assert hasattr(result, "success")
assert hasattr(result, "message")
@pytest.mark.asyncio
async def test_doc_agent_run_with_context(doc_agent):
"""Test DocAgent run with context."""
context = {"action": "update_all"}
result = await doc_agent.run(context)
assert hasattr(result, "success")
assert hasattr(result, "message")
@pytest.mark.asyncio
async def test_doc_agent_status_transitions(doc_agent):
"""Test DocAgent status transitions."""
assert doc_agent.status.value == "idle"
await doc_agent.set_running("test_task")
assert doc_agent.status.value == "running"
await doc_agent.set_idle()
assert doc_agent.status.value == "idle"
await doc_agent.set_error("test error")
assert doc_agent.status.value == "error"
await doc_agent.set_idle()
assert doc_agent.status.value == "idle"
@pytest.mark.asyncio
async def test_doc_agent_metrics(doc_agent):
"""Test DocAgent metrics retrieval."""
metrics = await doc_agent.get_metrics()
assert "agent_id" in metrics
assert "status" in metrics
assert metrics["agent_id"] == "doc_agent"
@pytest.mark.asyncio
async def test_get_file_purpose(doc_agent):
"""Test file purpose detection."""
assert "entry point" in doc_agent._get_file_purpose("main.py").lower()
assert "configuration" in doc_agent._get_file_purpose("config.py").lower()
assert "data models" in doc_agent._get_file_purpose("models.py").lower()
assert "Module file" in doc_agent._get_file_purpose("unknown.py")
@pytest.mark.asyncio
async def test_doc_agent_invalid_action(doc_agent):
"""Test DocAgent with invalid action."""
context = {"action": "invalid_action"}
result = await doc_agent.run(context)
assert hasattr(result, "success")
+180
View File
@@ -0,0 +1,180 @@
"""Tests for EvolutionAgent."""
import pytest
from app.agents.evolution_agent import ALL_AGENTS, EvolutionAgent
@pytest.fixture
def evolution_agent():
return EvolutionAgent()
@pytest.mark.asyncio
async def test_evolution_agent_initialization(evolution_agent):
"""Test EvolutionAgent initializes correctly."""
assert evolution_agent.name == "evolution_agent"
assert evolution_agent.version == "1.0.0"
@pytest.mark.asyncio
async def test_evolution_agent_health_check(evolution_agent):
"""Test EvolutionAgent health check."""
result = await evolution_agent.health_check()
assert isinstance(result, bool)
@pytest.mark.asyncio
async def test_evolution_agent_get_status(evolution_agent):
"""Test EvolutionAgent get status."""
result = await evolution_agent.run()
assert result.success
assert "total_agents" in result.data
@pytest.mark.asyncio
async def test_evolution_agent_analyze(evolution_agent):
"""Test EvolutionAgent analyze."""
context = {"action": "analyze"}
result = await evolution_agent.run(context)
assert result.success
assert "analyses" in result.data
@pytest.mark.asyncio
async def test_evolution_agent_suggest(evolution_agent):
"""Test EvolutionAgent suggest improvements."""
context = {"action": "suggest"}
result = await evolution_agent.run(context)
assert result.success
assert "suggestions" in result.data
@pytest.mark.asyncio
async def test_evolution_agent_evolve_without_id(evolution_agent):
"""Test EvolutionAgent evolve without agent_id."""
context = {"action": "evolve"}
result = await evolution_agent.run(context)
assert result.success is False
@pytest.mark.asyncio
async def test_evolution_agent_evolve(evolution_agent):
"""Test EvolutionAgent evolve with capabilities bump."""
context = {
"action": "evolve",
"agent_id": "doc_agent",
"capabilities": ["new_capability"],
}
result = await evolution_agent.run(context)
assert result.success
assert result.data.get("agent_id") == "doc_agent"
assert "new_version" in result.data
@pytest.mark.asyncio
async def test_evolution_agent_version_check(evolution_agent):
"""Test EvolutionAgent version_check action."""
context = {"action": "version_check"}
result = await evolution_agent.run(context)
assert result.success
assert "versions" in result.data
@pytest.mark.asyncio
async def test_evolution_agent_version_bump(evolution_agent):
"""Test EvolutionAgent version_bump action."""
context = {
"action": "version_bump",
"agent_id": "doc_agent",
"version_type": "minor",
"entries": ["Added: new capability"],
}
result = await evolution_agent.run(context)
assert result.success
assert result.data.get("agent_id") == "doc_agent"
assert result.data.get("new_version") == "1.1.0"
@pytest.mark.asyncio
async def test_evolution_agent_version_bump_invalid_type(evolution_agent):
"""Test EvolutionAgent version_bump with invalid type."""
context = {
"action": "version_bump",
"agent_id": "doc_agent",
"version_type": "patch",
"entries": [],
}
result = await evolution_agent.run(context)
assert result.success is False
@pytest.mark.asyncio
async def test_evolution_agent_version_bump_major(evolution_agent):
"""Test EvolutionAgent version_bump major."""
context = {
"action": "version_bump",
"agent_id": "doc_agent",
"version_type": "major",
"entries": ["Breaking: changed API"],
}
result = await evolution_agent.run(context)
assert result.success
assert result.data.get("new_version") == "2.0.0"
@pytest.mark.asyncio
async def test_evolution_agent_version_check_specific(evolution_agent):
"""Test EvolutionAgent version_check for specific agent."""
context = {
"action": "version_check",
"agent_id": "doc_agent",
}
result = await evolution_agent.run(context)
assert result.success
assert "doc_agent" in result.data["versions"]
@pytest.mark.asyncio
async def test_all_agents_list_completeness(evolution_agent):
"""Test ALL_AGENTS contains all 11 agents."""
assert len(ALL_AGENTS) == 11
agent_ids = {a["id"] for a in ALL_AGENTS}
expected = {
"doc_agent", "backlog_agent", "spec_agent", "audit_agent",
"observer_agent", "security_agent", "qa_tester_agent",
"fix_agent", "ui_test_agent", "rollout_agent", "evolution_agent",
}
assert agent_ids == expected
@pytest.mark.asyncio
async def test_evolution_agent_status_transitions(evolution_agent):
"""Test EvolutionAgent status transitions."""
assert evolution_agent.status.value == "idle"
await evolution_agent.set_running("evolution")
assert evolution_agent.status.value == "running"
await evolution_agent.set_idle()
assert evolution_agent.status.value == "idle"
@pytest.mark.asyncio
async def test_evolution_agent_metrics(evolution_agent):
"""Test EvolutionAgent metrics."""
metrics = await evolution_agent.get_metrics()
assert "agent_id" in metrics
assert "status" in metrics
assert metrics["capabilities_tracked"] == 11
@pytest.mark.asyncio
async def test_get_agent_capabilities(evolution_agent):
"""Test capability retrieval."""
caps = evolution_agent._get_agent_capabilities("doc_agent")
assert len(caps) > 0
assert "documentation" in caps
caps = evolution_agent._get_agent_capabilities("unknown")
assert len(caps) == 0
+68
View File
@@ -0,0 +1,68 @@
"""Tests for FixAgent."""
import pytest
from app.agents.fix_agent import FixAgent
@pytest.fixture
def fix_agent():
return FixAgent()
@pytest.mark.asyncio
async def test_fix_agent_initialization(fix_agent):
"""Test FixAgent initializes correctly."""
assert fix_agent.name == "fix_agent"
assert fix_agent.version == "1.0.0"
@pytest.mark.asyncio
async def test_fix_agent_health_check(fix_agent):
"""Test FixAgent health check."""
result = await fix_agent.health_check()
assert isinstance(result, bool)
@pytest.mark.asyncio
async def test_fix_agent_run(fix_agent):
"""Test FixAgent run."""
result = await fix_agent.run({"action": "suggest"})
assert hasattr(result, "success")
@pytest.mark.asyncio
async def test_fix_agent_analyze(fix_agent):
"""Test FixAgent analyze."""
context = {
"bug_data": {
"error_message": "AttributeError: 'NoneType' object has no attribute 'id'",
}
}
result = await fix_agent._analyze_bug(context)
assert result.success
assert "analysis" in result.data
@pytest.mark.asyncio
async def test_fix_agent_identify_error_type(fix_agent):
"""Test FixAgent error type identification."""
assert fix_agent._identify_error_type("AttributeError: 'NoneType'") == "AttributeError"
assert fix_agent._identify_error_type("ValueError: invalid value") == "ValueError"
assert fix_agent._identify_error_type("KeyError: 'id'") == "KeyError"
@pytest.mark.asyncio
async def test_fix_agent_suggest_fixes(fix_agent):
"""Test FixAgent suggest fixes."""
result = await fix_agent._suggest_fixes({})
assert result.success
assert "suggestions" in result.data
@pytest.mark.asyncio
async def test_fix_agent_severity_assessment(fix_agent):
"""Test FixAgent severity assessment."""
assert fix_agent._assess_severity("security vulnerability") == "critical"
assert fix_agent._assess_severity("crash on startup") == "high"
assert fix_agent._assess_severity("minor bug") == "medium"
+71
View File
@@ -0,0 +1,71 @@
"""Tests for ObserverAgent."""
import pytest
from app.agents.observer_agent import ObserverAgent
@pytest.fixture
def observer_agent():
return ObserverAgent(session=None)
@pytest.mark.asyncio
async def test_observer_agent_initialization(observer_agent):
"""Test ObserverAgent initializes correctly."""
assert observer_agent.name == "observer_agent"
assert observer_agent.version == "1.0.0"
@pytest.mark.asyncio
async def test_observer_agent_health_check(observer_agent):
"""Test ObserverAgent health check."""
result = await observer_agent.health_check()
assert result is True
@pytest.mark.asyncio
async def test_observer_agent_run_without_session(observer_agent):
"""Test ObserverAgent run without session."""
result = await observer_agent.run()
assert result.success is False
@pytest.mark.asyncio
async def test_observer_agent_collect_disabled(observer_agent):
"""Test ObserverAgent collect when disabled."""
context = {
"action": "collect",
"metric_name": "test_metric",
"metric_value": 1.0,
}
result = await observer_agent.run(context)
assert result.success
assert result.data.get("skipped") or result.data.get("enabled") is False
@pytest.mark.asyncio
async def test_observer_agent_status(observer_agent):
"""Test ObserverAgent status."""
await observer_agent.set_running("observation")
assert observer_agent.status.value == "running"
await observer_agent.set_idle()
assert observer_agent.status.value == "idle"
@pytest.mark.asyncio
async def test_observer_agent_metrics(observer_agent):
"""Test ObserverAgent metrics."""
metrics = await observer_agent.get_metrics()
assert "agent_id" in metrics
assert "enabled" in metrics
assert "status" in metrics
@pytest.mark.asyncio
async def test_observer_agent_report_without_session(observer_agent):
"""Test ObserverAgent report without session."""
context = {"action": "report", "period": "daily"}
result = await observer_agent.run(context)
assert result.success is False
+64
View File
@@ -0,0 +1,64 @@
"""Tests for QATesterAgent."""
import pytest
from app.agents.qa_tester_agent import QATesterAgent
@pytest.fixture
def qa_tester_agent():
return QATesterAgent(session=None)
@pytest.mark.asyncio
async def test_qa_tester_initialization(qa_tester_agent):
"""Test QATesterAgent initializes correctly."""
assert qa_tester_agent.name == "qa_tester_agent"
assert qa_tester_agent.version == "1.0.0"
assert qa_tester_agent.max_temp_users == 10
@pytest.mark.asyncio
async def test_qa_tester_health_check(qa_tester_agent):
"""Test QATesterAgent health check."""
result = await qa_tester_agent.health_check()
assert result is True
@pytest.mark.asyncio
async def test_qa_tester_run_without_session(qa_tester_agent):
"""Test QATesterAgent run without session."""
result = await qa_tester_agent.run()
assert hasattr(result, "success")
@pytest.mark.asyncio
async def test_qa_tester_create_users_simulation(qa_tester_agent):
"""Test QATesterAgent create users simulation."""
result = await qa_tester_agent._create_temp_users(3)
assert result.success
assert result.data.get("simulated") is True
@pytest.mark.asyncio
async def test_qa_tester_status(qa_tester_agent):
"""Test QATesterAgent status."""
result = await qa_tester_agent._get_status()
assert result.success
assert "temp_users_count" in result.data
@pytest.mark.asyncio
async def test_qa_tester_run_tests(qa_tester_agent):
"""Test QATesterAgent run tests."""
result = await qa_tester_agent._run_tests()
assert result.success
assert "total" in result.data
assert "passed" in result.data
@pytest.mark.asyncio
async def test_qa_tester_cleanup_simulation(qa_tester_agent):
"""Test QATesterAgent cleanup simulation."""
result = await qa_tester_agent._cleanup()
assert result.success
+81
View File
@@ -0,0 +1,81 @@
"""Tests for AgentRegistry."""
import pytest
from app.agents.registry import AgentRegistry, get_agent, get_all_agents
@pytest.fixture
def registry():
return AgentRegistry()
def test_registry_initialization(registry):
"""Test registry initializes with all agents."""
agents = registry.list_agents()
assert len(agents) >= 5
agent_names = [a["name"] for a in agents]
assert "doc_agent" in agent_names
assert "backlog_agent" in agent_names
assert "spec_agent" in agent_names
assert "audit_agent" in agent_names
assert "observer_agent" in agent_names
assert "evolution_agent" in agent_names
def test_get_agent(registry):
"""Test getting specific agent."""
agent = registry.get("doc_agent")
assert agent is not None
assert agent.name == "doc_agent"
def test_get_nonexistent_agent(registry):
"""Test getting non-existent agent."""
agent = registry.get("nonexistent_agent")
assert agent is None
def test_list_agents(registry):
"""Test listing all agents."""
agents = registry.list_agents()
assert isinstance(agents, list)
assert all("name" in a for a in agents)
assert all("status" in a for a in agents)
def test_get_all_agents_function():
"""Test get_all_agents function."""
agents = get_all_agents()
assert isinstance(agents, list)
assert len(agents) > 0
def test_get_agent_function():
"""Test get_agent function."""
agent = get_agent("spec_agent")
assert agent is not None
assert agent.name == "spec_agent"
@pytest.mark.asyncio
async def test_run_nonexistent_agent(registry):
"""Test running non-existent agent."""
result = await registry.run_agent("nonexistent")
assert result.success is False
@pytest.mark.asyncio
async def test_health_check_all(registry):
"""Test health check for all agents."""
results = await registry.health_check_all()
assert isinstance(results, dict)
assert all(isinstance(v, bool) for v in results.values())
@pytest.mark.asyncio
async def test_get_metrics_all(registry):
"""Test getting metrics for all agents."""
metrics = registry.get_metrics_all()
assert isinstance(metrics, dict)
assert all("status" in m for m in metrics.values())
+78
View File
@@ -0,0 +1,78 @@
"""Tests for RolloutAgent."""
import pytest
from app.agents.rollout_agent import RolloutAgent
@pytest.fixture
def rollout_agent():
return RolloutAgent()
@pytest.mark.asyncio
async def test_rollout_agent_initialization(rollout_agent):
"""Test RolloutAgent initializes correctly."""
assert rollout_agent.name == "rollout_agent"
assert rollout_agent.version == "1.0.0"
assert rollout_agent._current_stage == 0
@pytest.mark.asyncio
async def test_rollout_agent_health_check(rollout_agent):
"""Test RolloutAgent health check."""
result = await rollout_agent.health_check()
assert result is True
@pytest.mark.asyncio
async def test_rollout_agent_get_status(rollout_agent):
"""Test RolloutAgent get status."""
result = await rollout_agent._get_status()
assert result.success
assert "current_stage" in result.data
assert result.data["stage_name"] == "development"
@pytest.mark.asyncio
async def test_rollout_agent_promote(rollout_agent):
"""Test RolloutAgent promote."""
result = await rollout_agent._promote_to_next_stage()
assert result.success
assert result.data["stage_name"] == "3_users"
@pytest.mark.asyncio
async def test_rollout_agent_rollback(rollout_agent):
"""Test RolloutAgent rollback."""
await rollout_agent._promote_to_next_stage()
result = await rollout_agent._rollback(0)
assert result.success
assert result.data.get("rollback_history")
@pytest.mark.asyncio
async def test_rollout_agent_pause_resume(rollout_agent):
"""Test RolloutAgent pause and resume."""
pause_result = await rollout_agent._pause_rollout()
assert pause_result.success
resume_result = await rollout_agent._resume_rollout()
assert resume_result.success
@pytest.mark.asyncio
async def test_rollout_agent_health_check_metrics(rollout_agent):
"""Test RolloutAgent health check."""
result = await rollout_agent._check_stage_health()
assert hasattr(result, "success")
assert "metrics" in result.data
assert "issues" in result.data
@pytest.mark.asyncio
async def test_rollout_agent_final_stage_cannot_promote(rollout_agent):
"""Test RolloutAgent cannot promote past final stage."""
rollout_agent._current_stage = 5
result = await rollout_agent._promote_to_next_stage()
assert result.success is False
+64
View File
@@ -0,0 +1,64 @@
"""Tests for SecurityAgent."""
import pytest
from app.agents.security_agent import SecurityAgent
@pytest.fixture
def security_agent():
return SecurityAgent()
@pytest.mark.asyncio
async def test_security_agent_initialization(security_agent):
"""Test SecurityAgent initializes correctly."""
assert security_agent.name == "security_agent"
assert security_agent.version == "1.0.0"
@pytest.mark.asyncio
async def test_security_agent_health_check(security_agent):
"""Test SecurityAgent health check."""
result = await security_agent.health_check()
assert isinstance(result, bool)
@pytest.mark.asyncio
async def test_security_agent_run(security_agent):
"""Test SecurityAgent run."""
context = {"action": "scan", "paths": ["app"]}
result = await security_agent.run(context)
assert hasattr(result, "success")
assert hasattr(result, "message")
@pytest.mark.asyncio
async def test_security_agent_full_check(security_agent):
"""Test SecurityAgent full security check."""
result = await security_agent.run({"action": "full", "paths": ["app"]})
assert hasattr(result, "success")
assert hasattr(result, "data")
@pytest.mark.asyncio
async def test_security_agent_dependencies(security_agent):
"""Test SecurityAgent dependency check."""
result = await security_agent._check_dependencies()
assert hasattr(result, "success")
@pytest.mark.asyncio
async def test_security_agent_compliance(security_agent):
"""Test SecurityAgent 152-FZ compliance check."""
result = await security_agent._check_152_fz_compliance()
assert hasattr(result, "success")
assert "warnings" in result.data
@pytest.mark.asyncio
async def test_security_agent_metrics(security_agent):
"""Test SecurityAgent metrics."""
metrics = await security_agent.get_metrics()
assert "agent_id" in metrics
assert "status" in metrics
+76
View File
@@ -0,0 +1,76 @@
"""Tests for SpecAgent."""
import pytest
from pathlib import Path
from app.agents.spec_agent import SpecAgent
@pytest.fixture
def spec_agent():
return SpecAgent()
@pytest.mark.asyncio
async def test_spec_agent_initialization(spec_agent):
"""Test SpecAgent initializes correctly."""
assert spec_agent.name == "spec_agent"
assert spec_agent.version == "1.0.0"
@pytest.mark.asyncio
async def test_spec_agent_health_check(spec_agent):
"""Test SpecAgent health check."""
result = await spec_agent.health_check()
assert isinstance(result, bool)
@pytest.mark.asyncio
async def test_spec_agent_check_version(spec_agent):
"""Test SpecAgent version check."""
result = await spec_agent.run({"action": "check"})
assert result.success
assert "current_version" in result.data
@pytest.mark.asyncio
async def test_spec_agent_bump_version(spec_agent):
"""Test SpecAgent version bump."""
context = {
"action": "version_bump",
"version_type": "patch",
}
result = await spec_agent.run(context)
assert result.success
assert "new_version" in result.data
@pytest.mark.asyncio
async def test_spec_agent_parse_commit_type(spec_agent):
"""Test commit type parsing."""
assert spec_agent._parse_commit_type("feat: add feature") == "feat"
assert spec_agent._parse_commit_type("fix: bug fix") == "fix"
assert spec_agent._parse_commit_type("docs: update docs") == "docs"
@pytest.mark.asyncio
async def test_spec_agent_parse_commit_message(spec_agent):
"""Test commit message parsing."""
assert "add feature" in spec_agent._parse_commit_message("feat: add feature")
assert "bug fix" in spec_agent._parse_commit_message("fix: bug fix")
@pytest.mark.asyncio
async def test_spec_agent_metrics(spec_agent):
"""Test SpecAgent metrics."""
metrics = await spec_agent.get_metrics()
assert "agent_id" in metrics
assert "version" in metrics
assert "status" in metrics
@pytest.mark.asyncio
async def test_spec_agent_unknown_action(spec_agent):
"""Test SpecAgent with unknown action."""
result = await spec_agent.run({"action": "unknown"})
assert result.success
+62
View File
@@ -0,0 +1,62 @@
"""Tests for UITestAgent."""
import pytest
from app.agents.ui_test_agent import UITestAgent
@pytest.fixture
def ui_test_agent():
return UITestAgent()
@pytest.mark.asyncio
async def test_ui_test_agent_initialization(ui_test_agent):
"""Test UITestAgent initializes correctly."""
assert ui_test_agent.name == "ui_test_agent"
assert ui_test_agent.version == "1.0.0"
@pytest.mark.asyncio
async def test_ui_test_agent_health_check(ui_test_agent):
"""Test UITestAgent health check."""
result = await ui_test_agent.health_check()
assert isinstance(result, bool)
@pytest.mark.asyncio
async def test_ui_test_agent_run(ui_test_agent):
"""Test UITestAgent run."""
result = await ui_test_agent.run({"action": "layout"})
assert hasattr(result, "success")
@pytest.mark.asyncio
async def test_ui_test_agent_visual_regression(ui_test_agent):
"""Test UITestAgent visual regression."""
result = await ui_test_agent._visual_regression_test("desktop")
assert hasattr(result, "success")
assert "passed" in result.data
@pytest.mark.asyncio
async def test_ui_test_agent_accessibility(ui_test_agent):
"""Test UITestAgent accessibility check."""
result = await ui_test_agent._accessibility_check()
assert hasattr(result, "success")
assert "issues" in result.data
@pytest.mark.asyncio
async def test_ui_test_agent_layout(ui_test_agent):
"""Test UITestAgent layout validation."""
result = await ui_test_agent._layout_validation()
assert hasattr(result, "success")
@pytest.mark.asyncio
async def test_ui_test_agent_responsive(ui_test_agent):
"""Test UITestAgent responsive test."""
result = await ui_test_agent._responsive_test()
assert hasattr(result, "success")
assert "viewports" in result.data
View File
+53
View File
@@ -0,0 +1,53 @@
"""Tests for API route registration and root endpoints."""
from app.main import app
def test_root_endpoint():
"""Test root endpoint returns correct info."""
routes = {r.path for r in app.routes if hasattr(r, "methods")}
assert "/" in routes
assert "/health" in routes
assert "/api/v1/health" in routes
def test_all_api_routes_registered():
"""Test all expected API v1 routes are registered."""
routes = {r.path for r in app.routes if hasattr(r, "methods")}
expected = {
"/api/v1/auth/register",
"/api/v1/auth/login",
"/api/v1/auth/refresh",
"/api/v1/auth/oauth/{provider}",
"/api/v1/auth/oauth/{provider}/callback",
"/api/v1/users/me",
"/api/v1/ideas/",
"/api/v1/ideas/{idea_id}",
"/api/v1/ideas/{idea_id}/analyze",
"/api/v1/agents/",
"/api/v1/agents/{agent_name}",
"/api/v1/agents/{agent_name}/run",
"/api/v1/sync/pull",
"/api/v1/sync/push",
"/api/v1/admin/users",
"/api/v1/admin/users/{user_id}/role",
"/api/v1/admin/health",
"/api/v1/admin/logs",
}
for path in expected:
assert path in routes, f"Missing route: {path}"
def test_openapi_schema():
"""Test OpenAPI schema is generated."""
schema = app.openapi()
assert "VoIdea" in schema["info"]["title"]
assert "paths" in schema
assert len(schema["paths"]) >= 20
def test_app_title():
"""Test app has correct title."""
assert "VoIdea" in app.title
+78
View File
@@ -0,0 +1,78 @@
"""Tests for API schema validation."""
import pytest
from pydantic import ValidationError
from app.schemas.auth import LoginRequest, RegisterRequest
from app.schemas.idea import IdeaCreate, IdeaUpdate
from app.schemas.user import UserCreate
class TestAuthSchemas:
def test_register_valid(self):
data = RegisterRequest(
email="test@example.com",
password="password123",
display_name="Test User",
)
assert data.email == "test@example.com"
def test_register_short_password(self):
with pytest.raises(ValidationError):
RegisterRequest(
email="test@example.com",
password="123",
display_name="Test",
)
def test_register_invalid_email(self):
with pytest.raises(ValidationError):
RegisterRequest(
email="not-an-email",
password="password123",
display_name="Test",
)
def test_login_valid(self):
data = LoginRequest(email="test@example.com", password="pass")
assert data.email == "test@example.com"
class TestIdeaSchemas:
def test_idea_create_valid(self):
data = IdeaCreate(title="My Idea", content="Some content")
assert data.title == "My Idea"
assert data.is_public is False
def test_idea_create_with_tags(self):
data = IdeaCreate(
title="My Idea",
content="Content",
tags=["tech", "AI"],
is_public=True,
)
assert data.tags == ["tech", "AI"]
assert data.is_public is True
def test_idea_create_empty_title(self):
with pytest.raises(ValidationError):
IdeaCreate(title="", content="Content")
def test_idea_update_partial(self):
data = IdeaUpdate(title="New Title")
assert data.title == "New Title"
assert data.content is None
def test_idea_update_empty(self):
data = IdeaUpdate()
assert data.title is None
class TestUserSchemas:
def test_user_create_valid(self):
data = UserCreate(
email="test@example.com",
password="password123",
display_name="Test",
)
assert data.display_name == "Test"