Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

317 lines
10 KiB
Python

"""Tests for metrics tracking database models."""
from datetime import datetime, timedelta, timezone
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from local_deep_research.database.models import (
Base,
ModelUsage,
ResearchRating,
SearchCall,
TokenUsage,
)
class TestMetricsModels:
"""Test suite for metrics tracking models."""
@pytest.fixture
def engine(self):
"""Create an in-memory SQLite database for testing."""
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
yield engine
engine.dispose()
@pytest.fixture
def session(self, engine):
"""Create a database session for testing."""
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.close()
def test_token_usage_tracking(self, session):
"""Test TokenUsage model for tracking LLM token consumption."""
usage = TokenUsage(
research_id="research-123",
model_provider="openai",
model_name="gpt-4",
prompt_tokens=500,
completion_tokens=150,
total_tokens=650,
prompt_cost=0.015,
completion_cost=0.0045,
total_cost=0.0195,
timestamp=datetime.now(timezone.utc),
operation_type="synthesis",
operation_details={
"temperature": 0.7,
"purpose": "synthesis",
"request_id": "req_abc123",
},
)
session.add(usage)
session.commit()
# Verify the usage record
saved = session.query(TokenUsage).first()
assert saved is not None
assert saved.model_provider == "openai"
assert saved.model_name == "gpt-4"
assert saved.total_tokens == 650
assert saved.total_cost == 0.0195
assert saved.operation_type == "synthesis"
assert saved.operation_details["purpose"] == "synthesis"
def test_model_usage_aggregation(self, session):
"""Test ModelUsage for aggregating model usage statistics."""
model_usage = ModelUsage(
model_provider="anthropic",
model_name="claude-3-opus",
total_calls=5,
total_tokens=1450,
total_cost=0.10,
avg_response_time_ms=250.5,
error_count=0,
success_rate=100.0,
first_used_at=datetime.now(timezone.utc),
last_used_at=datetime.now(timezone.utc),
)
session.add(model_usage)
session.commit()
# Verify aggregated stats
saved = session.query(ModelUsage).first()
assert saved is not None
assert saved.model_provider == "anthropic"
assert saved.model_name == "claude-3-opus"
assert saved.total_calls == 5
assert saved.total_tokens == 1450
assert saved.total_cost == 0.10
assert saved.success_rate == 100.0
def test_research_rating(self, session):
"""Test ResearchRating model for user feedback."""
rating = ResearchRating(
research_id="research-456",
rating=4,
accuracy=5,
completeness=4,
relevance=5,
readability=3,
feedback="Great research results, but the summary could be clearer.",
created_at=datetime.now(timezone.utc),
)
session.add(rating)
session.commit()
# Verify rating
saved = session.query(ResearchRating).first()
assert saved is not None
assert saved.rating == 4
assert saved.accuracy == 5
assert saved.relevance == 5
assert "summary could be clearer" in saved.feedback
def test_search_call_tracking(self, session):
"""Test SearchCall model for tracking search engine calls."""
search = SearchCall(
research_id="research-789",
search_engine="google",
query="quantum computing applications",
num_results_requested=10,
num_results_returned=10,
response_time_ms=150.5,
success=1,
error_message=None,
rate_limited=0,
timestamp=datetime.now(timezone.utc),
)
session.add(search)
session.commit()
# Verify search call
saved = session.query(SearchCall).first()
assert saved is not None
assert saved.search_engine == "google"
assert saved.query == "quantum computing applications"
assert saved.success == 1
assert saved.response_time_ms == 150.5
def test_metrics_relationships(self, session):
"""Test relationships between metrics models."""
research_id = "research-shared-123"
# Create related metrics for the same research
token_usage = TokenUsage(
research_id=research_id,
model_provider="openai",
model_name="gpt-4",
prompt_tokens=100,
completion_tokens=50,
total_tokens=150,
total_cost=0.0045,
)
search_call = SearchCall(
research_id=research_id,
search_engine="bing",
query="test query",
num_results_returned=5,
)
rating = ResearchRating(
research_id=research_id, rating=5, feedback="Excellent"
)
session.add_all([token_usage, search_call, rating])
session.commit()
# Query by research_id
tokens = (
session.query(TokenUsage).filter_by(research_id=research_id).all()
)
searches = (
session.query(SearchCall).filter_by(research_id=research_id).all()
)
ratings = (
session.query(ResearchRating)
.filter_by(research_id=research_id)
.all()
)
assert len(tokens) == 1
assert len(searches) == 1
assert len(ratings) == 1
def test_cost_tracking(self, session):
"""Test cost tracking across different models."""
# Add multiple token usage records
for i in range(3):
usage = TokenUsage(
research_id=f"research-cost-{i}",
model_provider="openai",
model_name="gpt-4",
prompt_tokens=1000,
completion_tokens=500,
total_tokens=1500,
prompt_cost=0.03,
completion_cost=0.015,
total_cost=0.045,
)
session.add(usage)
session.commit()
# Calculate total costs
from sqlalchemy import func
total_cost = session.query(func.sum(TokenUsage.total_cost)).scalar()
assert total_cost == 0.135 # 3 * 0.045
def test_search_engine_performance(self, session):
"""Test tracking search engine performance metrics."""
engines = ["google", "bing", "duckduckgo"]
for engine in engines:
for i in range(5):
search = SearchCall(
research_id=f"research-perf-{engine}-{i}",
search_engine=engine,
query=f"test query {i}",
num_results_requested=10,
num_results_returned=10 if i != 2 else 0, # One failure
response_time_ms=100 + i * 50,
success=1 if i != 2 else 0,
error_message=None if i != 2 else "Network error",
)
session.add(search)
session.commit()
# Analyze performance by engine
from sqlalchemy import func
engine_stats = (
session.query(
SearchCall.search_engine,
func.count(SearchCall.id).label("total_calls"),
func.avg(SearchCall.response_time_ms).label(
"avg_response_time"
),
func.sum(SearchCall.success).label("successful_calls"),
)
.group_by(SearchCall.search_engine)
.all()
)
assert len(engine_stats) == 3
for stat in engine_stats:
assert stat.total_calls == 5
assert stat.successful_calls == 4 # 4 out of 5 successful
def test_rating_aggregation(self, session):
"""Test aggregating user ratings."""
# Create multiple ratings
for i in range(10):
rating = ResearchRating(
research_id=f"research-rate-{i}",
rating=3 + (i % 3), # Ratings: 3, 4, 5, 3, 4, 5...
accuracy=4 if i % 2 == 0 else 5,
completeness=3 + (i % 2),
relevance=5,
readability=4,
)
session.add(rating)
session.commit()
# Calculate average ratings
from sqlalchemy import func
avg_rating = session.query(func.avg(ResearchRating.rating)).scalar()
avg_accuracy = session.query(func.avg(ResearchRating.accuracy)).scalar()
assert avg_rating > 3.5
assert avg_accuracy > 4.0
def test_time_based_metrics(self, session):
"""Test querying metrics by time ranges."""
now = datetime.now(timezone.utc)
# Create token usage over different time periods
for days_ago in range(7):
for i in range(3):
usage = TokenUsage(
research_id=f"research-time-{days_ago}-{i}",
model_provider="anthropic",
model_name="claude-3",
prompt_tokens=100,
completion_tokens=50,
total_tokens=150,
total_cost=0.005,
timestamp=now - timedelta(days=days_ago),
)
session.add(usage)
session.commit()
# Query last 3 days
three_days_ago = now - timedelta(days=3)
recent_usage = (
session.query(TokenUsage)
.filter(TokenUsage.timestamp >= three_days_ago)
.count()
)
# Should have 3 days * 3 records per day = 9 records
assert recent_usage == 12 # days 0, 1, 2, 3 = 4 days * 3 records