Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

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