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
144 lines
5.7 KiB
Python
144 lines
5.7 KiB
Python
"""
|
|
Edge case tests for calculate_duration() in web/models/database.py.
|
|
|
|
These tests target the dateutil.parser fallback paths and timezone
|
|
handling that are NOT covered by the existing test_database.py.
|
|
"""
|
|
|
|
from unittest.mock import patch
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
|
|
class TestDateutilFallbackPaths:
|
|
"""Tests for the dateutil.parser.parse fallback code paths."""
|
|
|
|
def test_completed_at_dateutil_fallback(self):
|
|
"""Weird completed_at format that fails fromisoformat but dateutil can parse."""
|
|
from local_deep_research.web.models.database import calculate_duration
|
|
|
|
created_at = "2025-01-01T10:00:00+00:00"
|
|
# Month name format — fromisoformat fails, dateutil parses it
|
|
completed_at = "Jan 1, 2025 10:05:00 UTC"
|
|
|
|
result = calculate_duration(created_at, completed_at)
|
|
assert result is not None
|
|
assert result == 300 # 5 minutes
|
|
|
|
def test_created_at_dateutil_fallback(self):
|
|
"""Weird created_at format that fails standard parsing but dateutil can handle."""
|
|
from local_deep_research.web.models.database import calculate_duration
|
|
|
|
created_at = "Jan 1, 2025 10:00:00 UTC"
|
|
completed_at = "2025-01-01T10:02:00+00:00"
|
|
|
|
result = calculate_duration(created_at, completed_at)
|
|
assert result is not None
|
|
assert result == 120 # 2 minutes
|
|
|
|
def test_created_at_completely_unparseable_returns_none(self):
|
|
"""Completely unparseable created_at → returns None."""
|
|
from local_deep_research.web.models.database import calculate_duration
|
|
|
|
result = calculate_duration(
|
|
"not-a-date-at-all", "2025-01-01T10:00:00+00:00"
|
|
)
|
|
assert result is None
|
|
|
|
def test_completed_at_unparseable_falls_back_to_now(self):
|
|
"""Completely unparseable completed_at → falls back to current time."""
|
|
from local_deep_research.web.models.database import calculate_duration
|
|
|
|
now = datetime.now(timezone.utc)
|
|
created_at = (now - timedelta(seconds=60)).isoformat()
|
|
|
|
# Patch dateutil.parser.parse to fail when called for the completed_at
|
|
with patch(
|
|
"dateutil.parser.parse", side_effect=Exception("Cannot parse")
|
|
):
|
|
result = calculate_duration(created_at, "completely-invalid-!!!")
|
|
# Falls back to datetime.now(UTC), so duration ≈ 60s
|
|
assert result is not None
|
|
assert 55 <= result <= 70
|
|
|
|
|
|
class TestTimezoneHandling:
|
|
"""Tests for timezone-aware vs timezone-naive mixing."""
|
|
|
|
def test_timezone_offset_format(self):
|
|
"""Timestamps with +HH:MM offset are handled."""
|
|
from local_deep_research.web.models.database import calculate_duration
|
|
|
|
created_at = "2025-01-01T10:00:00+05:30"
|
|
completed_at = "2025-01-01T10:30:00+05:30"
|
|
|
|
result = calculate_duration(created_at, completed_at)
|
|
assert result == 1800 # 30 minutes
|
|
|
|
def test_different_timezone_offsets(self):
|
|
"""Timestamps with different timezone offsets are normalized."""
|
|
from local_deep_research.web.models.database import calculate_duration
|
|
|
|
# Same absolute moment, different offsets
|
|
created_at = "2025-01-01T10:00:00+00:00"
|
|
completed_at = "2025-01-01T12:00:00+02:00" # Same as 10:00 UTC
|
|
|
|
result = calculate_duration(created_at, completed_at)
|
|
assert result == 0 # Same instant
|
|
|
|
def test_negative_timezone_offset(self):
|
|
"""Negative timezone offset works correctly."""
|
|
from local_deep_research.web.models.database import calculate_duration
|
|
|
|
created_at = "2025-01-01T10:00:00-05:00" # UTC 15:00
|
|
completed_at = "2025-01-01T16:00:00+00:00" # UTC 16:00
|
|
|
|
result = calculate_duration(created_at, completed_at)
|
|
assert result == 3600 # 1 hour difference
|
|
|
|
|
|
class TestSpaceSeparatedFormats:
|
|
"""Tests for space-separated timestamp formats."""
|
|
|
|
def test_space_separated_with_microseconds_and_tz(self):
|
|
"""Space-separated format with microseconds and timezone."""
|
|
from local_deep_research.web.models.database import calculate_duration
|
|
|
|
# Include timezone to avoid naive datetime issues with astimezone
|
|
created_at = "2025-01-01 10:00:00.123456+00:00"
|
|
completed_at = "2025-01-01 10:01:00.654321+00:00"
|
|
|
|
result = calculate_duration(created_at, completed_at)
|
|
assert result is not None
|
|
assert result == 60 or result == 61
|
|
|
|
def test_space_separated_without_microseconds_and_tz(self):
|
|
"""Space-separated format without microseconds, with timezone."""
|
|
from local_deep_research.web.models.database import calculate_duration
|
|
|
|
created_at = "2025-01-01 10:00:00+00:00"
|
|
completed_at = "2025-01-01 10:05:00+00:00"
|
|
|
|
result = calculate_duration(created_at, completed_at)
|
|
assert result == 300 # 5 minutes
|
|
|
|
def test_space_to_t_replacement_fallback(self):
|
|
"""Space-separated with timezone falls back to replace(' ', 'T')."""
|
|
from local_deep_research.web.models.database import calculate_duration
|
|
|
|
# This has timezone info but no T separator — triggers the replace fallback
|
|
created_at = "2025-01-01 10:00:00+00:00"
|
|
completed_at = "2025-01-01 10:03:00+00:00"
|
|
|
|
result = calculate_duration(created_at, completed_at)
|
|
assert result == 180 # 3 minutes
|
|
|
|
def test_iso_with_explicit_utc_offset(self):
|
|
"""ISO format with explicit UTC offset."""
|
|
from local_deep_research.web.models.database import calculate_duration
|
|
|
|
created_at = "2025-01-01T10:00:00+00:00"
|
|
completed_at = "2025-01-01T10:10:00+00:00"
|
|
|
|
result = calculate_duration(created_at, completed_at)
|
|
assert result == 600 # 10 minutes
|