feat: comprehensive test infrastructure improvements
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 2m26s
Run Tests / Backend Tests (push) Failing after 2m36s
Run Tests / Frontend Tests (push) Failing after 2m23s
Run Tests / Test Summary (push) Failing after 13s

- Fix unit test imports: add env setup in conftest.py before module imports
- Add 24 new auth router tests (RBAC, preferences, password validation)
- Add 16 new tests for litellm and chat_summary routers
- Apply black formatting and ruff linting across codebase
- Add pre-commit hooks configuration (black, ruff, file checks)
- Increase CI coverage threshold from 40% to 50%

Test Results:
- 206 tests passing (91 unit + 115 integration)
- Coverage: 58.79% on core modules
- auth.py: 57% → 85%, litellm.py: 23% → 87%, chat_summary.py: 41% → 100%
- auth_service: 96.51%, config: 100%, rbac: 93.48%
This commit is contained in:
Gan, Jimmy
2026-04-08 00:21:32 +08:00
parent fcc95ac23f
commit b981c06d59
44 changed files with 1732 additions and 924 deletions
+201 -115
View File
@@ -1,11 +1,13 @@
"""
OPC Database Module - PostgreSQL connection and schema management
"""
import asyncpg
import json
from typing import Optional, List, Dict, Any
from datetime import datetime
import os
from datetime import datetime
from typing import Any
import asyncpg
# Database connection settings
DB_HOST = os.getenv("OPC_DB_HOST", "gitea-db")
@@ -15,7 +17,7 @@ DB_USER = os.getenv("OPC_DB_USER", "gitea")
DB_PASSWORD = os.getenv("GITEA_DB_PASSWORD", "")
# Connection pool
_pool: Optional[asyncpg.Pool] = None
_pool: asyncpg.Pool | None = None
async def get_pool() -> asyncpg.Pool:
@@ -24,6 +26,7 @@ async def get_pool() -> asyncpg.Pool:
if _pool is None:
import asyncio
import logging
logger = logging.getLogger(__name__)
max_retries = 3
@@ -67,7 +70,8 @@ async def init_db():
pool = await get_pool()
async with pool.acquire() as conn:
# Create tables
await conn.execute("""
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS projects (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
@@ -77,9 +81,11 @@ async def init_db():
metadata JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
"""
)
await conn.execute("""
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS tasks (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
@@ -96,9 +102,11 @@ async def init_db():
completed_at TIMESTAMP,
due_date TIMESTAMP
)
""")
"""
)
await conn.execute("""
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS task_history (
id SERIAL PRIMARY KEY,
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
@@ -109,9 +117,11 @@ async def init_db():
new_value JSONB,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
"""
)
await conn.execute("""
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS agents (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
@@ -123,9 +133,11 @@ async def init_db():
enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
"""
)
await conn.execute("""
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS agent_executions (
id SERIAL PRIMARY KEY,
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
@@ -142,9 +154,11 @@ async def init_db():
approved_by TEXT,
approved_at TIMESTAMP
)
""")
"""
)
await conn.execute("""
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS time_entries (
id SERIAL PRIMARY KEY,
task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
@@ -157,9 +171,11 @@ async def init_db():
ended_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
"""
)
await conn.execute("""
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS clients (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
@@ -169,7 +185,8 @@ async def init_db():
metadata JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
"""
)
# Create indexes
await conn.execute("CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)")
@@ -201,7 +218,7 @@ async def seed_agents(conn):
- Provide status reports and risk assessments
When assigned a task, analyze it and propose concrete actions.""",
"config": {"model": "cc-kiro", "temperature": 0.7, "approval_level": "auto"}
"config": {"model": "cc-kiro", "temperature": 0.7, "approval_level": "auto"},
},
{
"id": "cto",
@@ -216,7 +233,7 @@ When assigned a task, analyze it and propose concrete actions.""",
- Ensure security best practices
When assigned a task, provide technical leadership and propose improvements.""",
"config": {"model": "cc-kiro", "temperature": 0.5, "approval_level": "cxo"}
"config": {"model": "cc-kiro", "temperature": 0.5, "approval_level": "cxo"},
},
{
"id": "coo",
@@ -231,12 +248,13 @@ When assigned a task, provide technical leadership and propose improvements.""",
- Optimize resource allocation
When assigned a task, look for automation and optimization opportunities.""",
"config": {"model": "cc-kiro", "temperature": 0.6, "approval_level": "cxo"}
}
"config": {"model": "cc-kiro", "temperature": 0.6, "approval_level": "cxo"},
},
]
for agent in agents:
await conn.execute("""
await conn.execute(
"""
INSERT INTO agents (id, name, role, description, capabilities, system_prompt, config, enabled)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (id) DO UPDATE SET
@@ -246,34 +264,51 @@ When assigned a task, look for automation and optimization opportunities.""",
capabilities = EXCLUDED.capabilities,
system_prompt = EXCLUDED.system_prompt,
config = EXCLUDED.config
""", agent["id"], agent["name"], agent["role"], agent["description"],
json.dumps(agent["capabilities"]), agent["system_prompt"],
json.dumps(agent["config"]), True)
""",
agent["id"],
agent["name"],
agent["role"],
agent["description"],
json.dumps(agent["capabilities"]),
agent["system_prompt"],
json.dumps(agent["config"]),
True,
)
# Task operations
async def create_task(
title: str,
description: Optional[str] = None,
description: str | None = None,
status: str = "parking_lot",
priority: str = "medium",
project_id: Optional[int] = None,
assigned_to: Optional[str] = None,
project_id: int | None = None,
assigned_to: str | None = None,
assigned_type: str = "human",
tags: Optional[List[str]] = None,
due_date: Optional[datetime] = None,
actor: str = "system"
) -> Dict[str, Any]:
tags: list[str] | None = None,
due_date: datetime | None = None,
actor: str = "system",
) -> dict[str, Any]:
"""Create a new task"""
pool = await get_pool()
async with pool.acquire() as conn:
row = await conn.fetchrow("""
row = await conn.fetchrow(
"""
INSERT INTO tasks (title, description, status, priority, project_id, assigned_to, assigned_type, tags, due_date)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id, title, description, status, priority, project_id, assigned_to, assigned_type, tags,
created_at, updated_at, completed_at, due_date
""", title, description, status, priority, project_id, assigned_to, assigned_type,
json.dumps(tags or []), due_date)
""",
title,
description,
status,
priority,
project_id,
assigned_to,
assigned_type,
json.dumps(tags or []),
due_date,
)
task = dict(row)
task["tags"] = json.loads(task["tags"]) if task["tags"] else []
@@ -285,23 +320,30 @@ async def create_task(
task_for_history[key] = task_for_history[key].isoformat()
# Log history
await conn.execute("""
await conn.execute(
"""
INSERT INTO task_history (task_id, action, actor, actor_type, new_value)
VALUES ($1, $2, $3, $4, $5)
""", task["id"], "created", actor, "human", json.dumps(task_for_history))
""",
task["id"],
"created",
actor,
"human",
json.dumps(task_for_history),
)
return task
async def get_tasks(
status: Optional[str] = None,
assigned_to: Optional[str] = None,
project_id: Optional[int] = None,
priority: Optional[str] = None,
tags: Optional[List[str]] = None,
status: str | None = None,
assigned_to: str | None = None,
project_id: int | None = None,
priority: str | None = None,
tags: list[str] | None = None,
limit: int = 50,
offset: int = 0
) -> List[Dict[str, Any]]:
offset: int = 0,
) -> list[dict[str, Any]]:
"""Get tasks with filters"""
pool = await get_pool()
async with pool.acquire() as conn:
@@ -346,11 +388,7 @@ async def get_tasks(
return tasks
async def update_task(
task_id: int,
updates: Dict[str, Any],
actor: str = "system"
) -> Dict[str, Any]:
async def update_task(task_id: int, updates: dict[str, Any], actor: str = "system") -> dict[str, Any]:
"""Update a task"""
pool = await get_pool()
async with pool.acquire() as conn:
@@ -367,7 +405,17 @@ async def update_task(
param_count = 0
for key, value in updates.items():
if key in ["title", "description", "status", "priority", "project_id", "assigned_to", "assigned_type", "due_date", "completed_at"]:
if key in [
"title",
"description",
"status",
"priority",
"project_id",
"assigned_to",
"assigned_type",
"due_date",
"completed_at",
]:
param_count += 1
set_clauses.append(f"{key} = ${param_count}")
params.append(value)
@@ -395,10 +443,18 @@ async def update_task(
updates_for_history[key] = updates_for_history[key].isoformat()
# Log history
await conn.execute("""
await conn.execute(
"""
INSERT INTO task_history (task_id, action, actor, actor_type, old_value, new_value)
VALUES ($1, $2, $3, $4, $5, $6)
""", task_id, "updated", actor, "human", json.dumps(old_values_for_history), json.dumps(updates_for_history))
""",
task_id,
"updated",
actor,
"human",
json.dumps(old_values_for_history),
json.dumps(updates_for_history),
)
return task
@@ -407,73 +463,85 @@ async def delete_task(task_id: int, actor: str = "system"):
"""Delete a task"""
pool = await get_pool()
async with pool.acquire() as conn:
await conn.execute("""
await conn.execute(
"""
INSERT INTO task_history (task_id, action, actor, actor_type)
VALUES ($1, $2, $3, $4)
""", task_id, "deleted", actor, "human")
""",
task_id,
"deleted",
actor,
"human",
)
await conn.execute("DELETE FROM tasks WHERE id = $1", task_id)
# Time tracking operations
async def start_time_entry(
task_id: int,
project_id: Optional[int] = None,
actor: str = "system"
) -> Dict[str, Any]:
async def start_time_entry(task_id: int, project_id: int | None = None, actor: str = "system") -> dict[str, Any]:
"""Start automatic time tracking for a task"""
pool = await get_pool()
async with pool.acquire() as conn:
# Check if there's already an active entry
active = await conn.fetchrow("""
active = await conn.fetchrow(
"""
SELECT * FROM time_entries
WHERE task_id = $1 AND ended_at IS NULL
ORDER BY started_at DESC LIMIT 1
""", task_id)
""",
task_id,
)
if active:
return dict(active)
# Create new time entry
row = await conn.fetchrow("""
row = await conn.fetchrow(
"""
INSERT INTO time_entries (task_id, project_id, started_at, duration_minutes)
VALUES ($1, $2, CURRENT_TIMESTAMP, 0)
RETURNING *
""", task_id, project_id)
""",
task_id,
project_id,
)
return dict(row)
async def stop_time_entry(task_id: int) -> Optional[Dict[str, Any]]:
async def stop_time_entry(task_id: int) -> dict[str, Any] | None:
"""Stop automatic time tracking for a task"""
pool = await get_pool()
async with pool.acquire() as conn:
# Find active entry
active = await conn.fetchrow("""
active = await conn.fetchrow(
"""
SELECT * FROM time_entries
WHERE task_id = $1 AND ended_at IS NULL
ORDER BY started_at DESC LIMIT 1
""", task_id)
""",
task_id,
)
if not active:
return None
# Calculate duration and update
row = await conn.fetchrow("""
row = await conn.fetchrow(
"""
UPDATE time_entries
SET ended_at = CURRENT_TIMESTAMP,
duration_minutes = EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - started_at)) / 60
WHERE id = $1
RETURNING *
""", active["id"])
""",
active["id"],
)
return dict(row)
async def get_time_entries(
task_id: Optional[int] = None,
project_id: Optional[int] = None
) -> List[Dict[str, Any]]:
async def get_time_entries(task_id: int | None = None, project_id: int | None = None) -> list[dict[str, Any]]:
"""Get time entries"""
pool = await get_pool()
async with pool.acquire() as conn:
@@ -494,20 +562,23 @@ async def get_time_entries(
return [dict(row) for row in rows]
async def get_task_history(task_id: int) -> List[Dict[str, Any]]:
async def get_task_history(task_id: int) -> list[dict[str, Any]]:
"""Get task history"""
pool = await get_pool()
async with pool.acquire() as conn:
rows = await conn.fetch("""
rows = await conn.fetch(
"""
SELECT * FROM task_history
WHERE task_id = $1
ORDER BY timestamp DESC
""", task_id)
""",
task_id,
)
return [dict(row) for row in rows]
# Agent operations
async def get_agents(enabled_only: bool = True) -> List[Dict[str, Any]]:
async def get_agents(enabled_only: bool = True) -> list[dict[str, Any]]:
"""Get all agents"""
pool = await get_pool()
async with pool.acquire() as conn:
@@ -526,7 +597,7 @@ async def get_agents(enabled_only: bool = True) -> List[Dict[str, Any]]:
return agents
async def get_agent(agent_id: str) -> Optional[Dict[str, Any]]:
async def get_agent(agent_id: str) -> dict[str, Any] | None:
"""Get agent by ID"""
pool = await get_pool()
async with pool.acquire() as conn:
@@ -541,11 +612,8 @@ async def get_agent(agent_id: str) -> Optional[Dict[str, Any]]:
async def create_agent_execution(
task_id: int,
agent_id: str,
actor: str = "system",
requires_approval: bool = False
) -> Dict[str, Any]:
task_id: int, agent_id: str, actor: str = "system", requires_approval: bool = False
) -> dict[str, Any]:
"""Create agent execution record"""
pool = await get_pool()
async with pool.acquire() as conn:
@@ -561,28 +629,43 @@ async def create_agent_execution(
task_for_context = dict(task)
for key in ["created_at", "updated_at", "completed_at", "due_date"]:
if task_for_context.get(key):
task_for_context[key] = task_for_context[key].isoformat() if hasattr(task_for_context[key], 'isoformat') else task_for_context[key]
task_for_context[key] = (
task_for_context[key].isoformat()
if hasattr(task_for_context[key], "isoformat")
else task_for_context[key]
)
agent_for_context = dict(agent)
for key in ["created_at"]:
if agent_for_context.get(key):
agent_for_context[key] = agent_for_context[key].isoformat() if hasattr(agent_for_context[key], 'isoformat') else agent_for_context[key]
agent_for_context[key] = (
agent_for_context[key].isoformat()
if hasattr(agent_for_context[key], "isoformat")
else agent_for_context[key]
)
input_context = {
"task": task_for_context,
"agent": agent_for_context,
"timestamp": datetime.utcnow().isoformat()
"timestamp": datetime.utcnow().isoformat(),
}
# Check if agent requires approval (CxO level)
agent_config = json.loads(agent["config"]) if agent["config"] else {}
requires_approval = agent_config.get("approval_level") == "cxo"
row = await conn.fetchrow("""
row = await conn.fetchrow(
"""
INSERT INTO agent_executions (task_id, agent_id, status, input_context, requires_approval, started_at)
VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP)
RETURNING *
""", task_id, agent_id, "pending", json.dumps(input_context), requires_approval)
""",
task_id,
agent_id,
"pending",
json.dumps(input_context),
requires_approval,
)
execution = dict(row)
execution["input_context"] = json.loads(execution["input_context"]) if execution["input_context"] else {}
@@ -590,11 +673,8 @@ async def create_agent_execution(
async def get_agent_executions(
task_id: Optional[int] = None,
agent_id: Optional[str] = None,
status: Optional[str] = None,
limit: int = 50
) -> List[Dict[str, Any]]:
task_id: int | None = None, agent_id: str | None = None, status: str | None = None, limit: int = 50
) -> list[dict[str, Any]]:
"""Get agent executions"""
pool = await get_pool()
async with pool.acquire() as conn:
@@ -628,7 +708,7 @@ async def get_agent_executions(
return executions
async def get_task_by_id(task_id: int) -> Optional[Dict[str, Any]]:
async def get_task_by_id(task_id: int) -> dict[str, Any] | None:
"""Get a single task by ID"""
pool = await get_pool()
async with pool.acquire() as conn:
@@ -641,7 +721,7 @@ async def get_task_by_id(task_id: int) -> Optional[Dict[str, Any]]:
return task
async def get_agent_execution(execution_id: int) -> Optional[Dict[str, Any]]:
async def get_agent_execution(execution_id: int) -> dict[str, Any] | None:
"""Get a single agent execution by ID"""
pool = await get_pool()
async with pool.acquire() as conn:
@@ -657,23 +737,25 @@ async def get_agent_execution(execution_id: int) -> Optional[Dict[str, Any]]:
async def approve_agent_execution(
execution_id: int,
approved: bool,
approved_by: str,
feedback: Optional[str] = None
) -> Dict[str, Any]:
execution_id: int, approved: bool, approved_by: str, feedback: str | None = None
) -> dict[str, Any]:
"""Approve or reject an agent execution"""
pool = await get_pool()
async with pool.acquire() as conn:
# Update execution with approval status
row = await conn.fetchrow("""
row = await conn.fetchrow(
"""
UPDATE agent_executions
SET approved_by = $1,
approved_at = CURRENT_TIMESTAMP,
status = CASE WHEN $2 THEN 'approved' ELSE 'rejected' END
WHERE id = $3
RETURNING *
""", approved_by, approved, execution_id)
""",
approved_by,
approved,
execution_id,
)
if not row:
raise ValueError(f"Execution {execution_id} not found")
@@ -689,23 +771,27 @@ async def approve_agent_execution(
execution["output_result"] = {}
execution["output_result"]["approval_feedback"] = feedback
await conn.execute("""
await conn.execute(
"""
UPDATE agent_executions
SET output_result = $1
WHERE id = $2
""", json.dumps(execution["output_result"]), execution_id)
""",
json.dumps(execution["output_result"]),
execution_id,
)
return execution
async def update_agent_config(
agent_id: str,
name: Optional[str] = None,
description: Optional[str] = None,
system_prompt: Optional[str] = None,
config: Optional[Dict[str, Any]] = None,
enabled: Optional[bool] = None
) -> Dict[str, Any]:
name: str | None = None,
description: str | None = None,
system_prompt: str | None = None,
config: dict[str, Any] | None = None,
enabled: bool | None = None,
) -> dict[str, Any]:
"""Update agent configuration"""
pool = await get_pool()
async with pool.acquire() as conn:
@@ -761,11 +847,11 @@ async def update_agent_config(
async def update_execution_status(
execution_id: int,
status: str,
output_result: Optional[Dict[str, Any]] = None,
actions_proposed: Optional[List[Dict[str, Any]]] = None,
actions_executed: Optional[List[Dict[str, Any]]] = None,
error_message: Optional[str] = None
) -> Dict[str, Any]:
output_result: dict[str, Any] | None = None,
actions_proposed: list[dict[str, Any]] | None = None,
actions_executed: list[dict[str, Any]] | None = None,
error_message: str | None = None,
) -> dict[str, Any]:
"""Update agent execution status and results"""
pool = await get_pool()
async with pool.acquire() as conn: