Skip to content

Commit a898a12

Browse files
tmdeveloper007utksh1
authored andcommitted
test: add unit tests for _parse_discovered_at in executor
1 parent 5267f90 commit a898a12

1 file changed

Lines changed: 130 additions & 0 deletions

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"""
2+
Unit tests for _parse_discovered_at in backend/secuscan/executor.py.
3+
4+
The executor module has heavy imports (FastAPI, database, cache, etc.) that are
5+
not available in this sandbox. All backend.secuscan dependencies are mocked so
6+
that the real _parse_discovered_at function can be imported and tested.
7+
"""
8+
9+
import sys
10+
from unittest.mock import MagicMock
11+
from datetime import datetime, timezone
12+
13+
import pytest
14+
15+
# Mock all backend.secuscan modules that executor.py imports at module level.
16+
# Without these, executor.py cannot be imported.
17+
_MOCKED_BACKEND_MODULES = {
18+
"backend.secuscan.auth",
19+
"backend.secuscan.cache",
20+
"backend.secuscan.config",
21+
"backend.secuscan.database",
22+
"backend.secuscan.plugins",
23+
"backend.secuscan.models",
24+
"backend.secuscan.ratelimit",
25+
"backend.secuscan.risk_scoring",
26+
"backend.secuscan.capabilities",
27+
"backend.secuscan.parser_sandbox",
28+
"backend.secuscan.network_policy",
29+
"backend.secuscan.notification_service",
30+
"backend.secuscan.execution_context",
31+
"backend.secuscan.finding_intelligence",
32+
"backend.secuscan.platform_resources",
33+
"backend.secuscan.logging_utils",
34+
"backend.secuscan.request_context",
35+
"backend.secuscan.routes",
36+
"backend.secuscan.saved_views",
37+
"backend.secuscan.workflows",
38+
"backend.secuscan.workflows_scheduler",
39+
"backend.secuscan.ratelimit",
40+
}
41+
42+
for mod in _MOCKED_BACKEND_MODULES:
43+
sys.modules[mod] = MagicMock()
44+
45+
sys.modules["redis"] = MagicMock()
46+
sys.modules["redis.asyncio"] = MagicMock()
47+
sys.modules["fastapi"] = MagicMock()
48+
sys.modules["pydantic"] = MagicMock()
49+
sys.modules["aiosqlite"] = MagicMock()
50+
sys.modules["httpx"] = MagicMock()
51+
52+
from backend.secuscan.executor import _parse_discovered_at
53+
54+
55+
class TestParseDiscoveredAt:
56+
"""Tests for _parse_discovered_at: extracts discovered_at from a finding dict."""
57+
58+
def test_returns_utc_when_discovered_at_is_valid_iso(self):
59+
"""A valid ISO datetime string is returned as timezone-aware UTC."""
60+
finding = {"discovered_at": "2026-07-15T12:30:00Z"}
61+
result = _parse_discovered_at(finding)
62+
assert result is not None
63+
assert result.tzinfo == timezone.utc
64+
assert result.hour == 12
65+
66+
def test_returns_utc_when_discovered_at_has_timezone_offset(self):
67+
"""A datetime with an explicit offset is converted to UTC."""
68+
finding = {"discovered_at": "2026-07-15T08:30:00-04:00"}
69+
result = _parse_discovered_at(finding)
70+
assert result is not None
71+
assert result.tzinfo == timezone.utc
72+
assert result.hour == 12 # 8:30 AM EDT = 12:30 PM UTC
73+
74+
def test_returns_utc_when_discovered_at_has_space_separator(self):
75+
"""A datetime with a space (SQLite format) is parsed and returned as UTC."""
76+
finding = {"discovered_at": "2026-07-15 12:30:00"}
77+
result = _parse_discovered_at(finding)
78+
assert result is not None
79+
assert result.tzinfo == timezone.utc
80+
assert result.hour == 12
81+
82+
def test_returns_none_when_discovered_at_is_missing(self):
83+
"""When discovered_at is absent from the dict, None is passed to parse_to_utc."""
84+
finding = {}
85+
result = _parse_discovered_at(finding)
86+
assert result is not None
87+
assert isinstance(result, datetime)
88+
# Should fall back to utc_now (current time)
89+
assert result.tzinfo == timezone.utc
90+
91+
def test_returns_none_when_discovered_at_is_none(self):
92+
"""When discovered_at is explicitly None, the function falls back to utc_now."""
93+
finding = {"discovered_at": None}
94+
result = _parse_discovered_at(finding)
95+
assert result is not None
96+
assert isinstance(result, datetime)
97+
assert result.tzinfo == timezone.utc
98+
99+
def test_returns_none_when_discovered_at_is_invalid(self):
100+
"""An unparseable discovered_at string falls back to utc_now."""
101+
finding = {"discovered_at": "not-a-valid-date"}
102+
result = _parse_discovered_at(finding)
103+
assert result is not None
104+
assert isinstance(result, datetime)
105+
assert result.tzinfo == timezone.utc
106+
107+
def test_returns_none_when_discovered_at_is_empty_string(self):
108+
"""An empty discovered_at string falls back to utc_now."""
109+
finding = {"discovered_at": ""}
110+
result = _parse_discovered_at(finding)
111+
assert result is not None
112+
assert isinstance(result, datetime)
113+
assert result.tzinfo == timezone.utc
114+
115+
def test_returns_datetime_object_when_discovered_at_is_datetime(self):
116+
"""When discovered_at is already a datetime object, it is returned as UTC."""
117+
aware_dt = datetime(2026, 7, 15, 14, 30, 0, tzinfo=timezone.utc)
118+
finding = {"discovered_at": aware_dt}
119+
result = _parse_discovered_at(finding)
120+
assert result is not None
121+
assert result.tzinfo == timezone.utc
122+
assert result.hour == 14
123+
124+
def test_returns_none_when_discovered_at_is_unix_timestamp(self):
125+
"""A Unix timestamp (int) in discovered_at falls back to utc_now."""
126+
finding = {"discovered_at": 1751232000}
127+
result = _parse_discovered_at(finding)
128+
assert result is not None
129+
assert isinstance(result, datetime)
130+
assert result.tzinfo == timezone.utc

0 commit comments

Comments
 (0)