7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
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
172 lines
6.2 KiB
Python
172 lines
6.2 KiB
Python
"""Tests for metrics query_utils module."""
|
|
|
|
from sqlalchemy import Column, DateTime, String
|
|
from sqlalchemy.orm import declarative_base
|
|
|
|
from datetime import datetime, timedelta, UTC
|
|
|
|
from local_deep_research.metrics.query_utils import (
|
|
get_period_cutoff,
|
|
get_period_days,
|
|
get_research_mode_condition,
|
|
get_time_filter_condition,
|
|
)
|
|
|
|
# Create a model for SQLAlchemy column testing
|
|
Base = declarative_base()
|
|
|
|
|
|
class QueryTestModel(Base):
|
|
"""Model for SQLAlchemy column comparisons."""
|
|
|
|
__tablename__ = "query_test_table"
|
|
id = Column(String, primary_key=True)
|
|
timestamp = Column(DateTime)
|
|
mode = Column(String)
|
|
|
|
|
|
class TestGetPeriodDays:
|
|
"""Tests for get_period_days — single period vocabulary (both UIs)."""
|
|
|
|
def test_all_returns_none(self):
|
|
assert get_period_days("all") is None
|
|
|
|
def test_dashboard_vocabulary(self):
|
|
# Main dashboard / context-overflow buttons send these.
|
|
assert get_period_days("7d") == 7
|
|
assert get_period_days("30d") == 30
|
|
assert get_period_days("3m") == 90
|
|
assert get_period_days("1y") == 365
|
|
|
|
def test_link_analytics_vocabulary(self):
|
|
# The standalone link-analytics page sends these aliases; they must
|
|
# resolve to the same day counts as 3m/1y.
|
|
assert get_period_days("90d") == 90
|
|
assert get_period_days("365d") == 365
|
|
|
|
def test_unknown_uses_default(self):
|
|
assert get_period_days("nonsense") == 30
|
|
assert get_period_days("nonsense", default=7) == 7
|
|
|
|
|
|
class TestGetPeriodCutoff:
|
|
"""Tests for get_period_cutoff function."""
|
|
|
|
def test_returns_none_for_all(self):
|
|
assert get_period_cutoff("all") is None
|
|
|
|
def test_known_periods_map_to_days(self):
|
|
now = datetime.now(UTC)
|
|
for period, days in [
|
|
("7d", 7),
|
|
("30d", 30),
|
|
("3m", 90),
|
|
("90d", 90),
|
|
("1y", 365),
|
|
("365d", 365),
|
|
]:
|
|
cutoff = get_period_cutoff(period)
|
|
expected = now - timedelta(days=days)
|
|
assert abs((cutoff - expected).total_seconds()) < 5, period
|
|
|
|
def test_unknown_period_defaults_to_30_days(self):
|
|
cutoff = get_period_cutoff("month") # old vocabulary, no longer valid
|
|
expected = datetime.now(UTC) - timedelta(days=30)
|
|
assert abs((cutoff - expected).total_seconds()) < 5
|
|
|
|
|
|
class TestGetTimeFilterCondition:
|
|
"""Tests for get_time_filter_condition function."""
|
|
|
|
def test_returns_none_for_all_period(self):
|
|
"""Should return None when period is 'all'."""
|
|
result = get_time_filter_condition("all", QueryTestModel.timestamp)
|
|
assert result is None
|
|
|
|
def test_returns_condition_for_7_days(self):
|
|
"""Should return a BinaryExpression for 7 days period."""
|
|
result = get_time_filter_condition("7d", QueryTestModel.timestamp)
|
|
assert result is not None
|
|
# Should be a comparison expression
|
|
assert hasattr(result, "left") or hasattr(result, "compare")
|
|
|
|
def test_returns_condition_for_30_days(self):
|
|
"""Should return a condition for 30 days period."""
|
|
result = get_time_filter_condition("30d", QueryTestModel.timestamp)
|
|
assert result is not None
|
|
|
|
def test_returns_condition_for_3_months(self):
|
|
"""Should return a condition for 90 days (3 months)."""
|
|
result = get_time_filter_condition("3m", QueryTestModel.timestamp)
|
|
assert result is not None
|
|
|
|
def test_returns_condition_for_1_year(self):
|
|
"""Should return a condition for 365 days (1 year)."""
|
|
result = get_time_filter_condition("1y", QueryTestModel.timestamp)
|
|
assert result is not None
|
|
|
|
def test_defaults_to_30_days_for_unknown_period(self):
|
|
"""Should default to 30 days for unknown period strings."""
|
|
result = get_time_filter_condition("unknown", QueryTestModel.timestamp)
|
|
# Should return condition (defaults to 30d)
|
|
assert result is not None
|
|
|
|
def test_defaults_to_30_days_for_empty_period(self):
|
|
"""Should default to 30 days for empty period string."""
|
|
result = get_time_filter_condition("", QueryTestModel.timestamp)
|
|
assert result is not None
|
|
|
|
def test_all_valid_periods(self):
|
|
"""All standard periods should return conditions."""
|
|
periods = ["7d", "30d", "3m", "1y"]
|
|
for period in periods:
|
|
result = get_time_filter_condition(period, QueryTestModel.timestamp)
|
|
assert result is not None, (
|
|
f"Period {period} should return a condition"
|
|
)
|
|
|
|
|
|
class TestGetResearchModeCondition:
|
|
"""Tests for get_research_mode_condition function."""
|
|
|
|
def test_returns_none_for_all_mode(self):
|
|
"""Should return None when research_mode is 'all'."""
|
|
result = get_research_mode_condition("all", QueryTestModel.mode)
|
|
assert result is None
|
|
|
|
def test_returns_condition_for_quick(self):
|
|
"""Should return condition for 'quick' mode."""
|
|
result = get_research_mode_condition("quick", QueryTestModel.mode)
|
|
assert result is not None
|
|
|
|
def test_returns_condition_for_detailed(self):
|
|
"""Should return condition for 'detailed' mode."""
|
|
result = get_research_mode_condition("detailed", QueryTestModel.mode)
|
|
assert result is not None
|
|
|
|
def test_returns_none_for_unknown_mode(self):
|
|
"""Should return None for unknown mode strings."""
|
|
result = get_research_mode_condition("unknown", QueryTestModel.mode)
|
|
assert result is None
|
|
|
|
def test_returns_none_for_empty_mode(self):
|
|
"""Should return None for empty mode string."""
|
|
result = get_research_mode_condition("", QueryTestModel.mode)
|
|
assert result is None
|
|
|
|
def test_quick_and_detailed_are_valid_modes(self):
|
|
"""Only 'quick' and 'detailed' should return conditions."""
|
|
# Valid modes
|
|
assert (
|
|
get_research_mode_condition("quick", QueryTestModel.mode)
|
|
is not None
|
|
)
|
|
assert (
|
|
get_research_mode_condition("detailed", QueryTestModel.mode)
|
|
is not None
|
|
)
|
|
|
|
# Invalid modes
|
|
assert get_research_mode_condition("fast", QueryTestModel.mode) is None
|
|
assert get_research_mode_condition("slow", QueryTestModel.mode) is None
|