fix(CORE-001D3B): reject malformed alert history entries
This commit is contained in:
@@ -67,7 +67,18 @@ class AlertHistoryRepository:
|
|||||||
raise AlertHistoryLoadError(self.history_path, f"Alert history could not be read: {exc}") from exc
|
raise AlertHistoryLoadError(self.history_path, f"Alert history could not be read: {exc}") from exc
|
||||||
if not isinstance(data, list):
|
if not isinstance(data, list):
|
||||||
raise AlertHistoryLoadError(self.history_path, "Alert history payload must be a list")
|
raise AlertHistoryLoadError(self.history_path, "Alert history payload must be a list")
|
||||||
return [AlertEvent.from_dict(item) for item in data if isinstance(item, dict)]
|
events: list[AlertEvent] = []
|
||||||
|
for index, item in enumerate(data):
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
raise AlertHistoryLoadError(self.history_path, f"Alert history entry {index} must be an object")
|
||||||
|
try:
|
||||||
|
events.append(AlertEvent.from_dict(item))
|
||||||
|
except (TypeError, ValueError) as exc:
|
||||||
|
raise AlertHistoryLoadError(
|
||||||
|
self.history_path,
|
||||||
|
f"Alert history entry {index} is invalid: {exc}",
|
||||||
|
) from exc
|
||||||
|
return events
|
||||||
|
|
||||||
def save(self, events: list[AlertEvent]) -> None:
|
def save(self, events: list[AlertEvent]) -> None:
|
||||||
with self.history_path.open("w") as f:
|
with self.history_path.open("w") as f:
|
||||||
|
|||||||
@@ -179,6 +179,24 @@ def test_alert_history_repository_raises_on_corrupt_history_file(tmp_path: Path)
|
|||||||
repository.load()
|
repository.load()
|
||||||
|
|
||||||
|
|
||||||
|
def test_alert_history_repository_raises_on_non_list_payload(tmp_path: Path) -> None:
|
||||||
|
history_path = tmp_path / "alert_history.json"
|
||||||
|
history_path.write_text('{"severity": "warning"}', encoding="utf-8")
|
||||||
|
repository = AlertHistoryRepository(history_path=history_path)
|
||||||
|
|
||||||
|
with pytest.raises(AlertHistoryLoadError, match="Alert history payload must be a list"):
|
||||||
|
repository.load()
|
||||||
|
|
||||||
|
|
||||||
|
def test_alert_history_repository_raises_on_invalid_list_entry(tmp_path: Path) -> None:
|
||||||
|
history_path = tmp_path / "alert_history.json"
|
||||||
|
history_path.write_text('[{"severity": "warning"}]', encoding="utf-8")
|
||||||
|
repository = AlertHistoryRepository(history_path=history_path)
|
||||||
|
|
||||||
|
with pytest.raises(AlertHistoryLoadError, match="Alert history entry 0 is invalid"):
|
||||||
|
repository.load()
|
||||||
|
|
||||||
|
|
||||||
def test_alert_service_marks_history_unavailable_on_corrupt_storage(alert_service: AlertService) -> None:
|
def test_alert_service_marks_history_unavailable_on_corrupt_storage(alert_service: AlertService) -> None:
|
||||||
alert_service.repository.history_path.write_text("{not valid json", encoding="utf-8")
|
alert_service.repository.history_path.write_text("{not valid json", encoding="utf-8")
|
||||||
config = PortfolioConfig(
|
config = PortfolioConfig(
|
||||||
@@ -208,7 +226,7 @@ def test_alert_service_marks_history_unavailable_on_corrupt_storage(alert_servic
|
|||||||
|
|
||||||
|
|
||||||
def test_alert_service_preview_marks_history_unavailable_on_corrupt_storage(alert_service: AlertService) -> None:
|
def test_alert_service_preview_marks_history_unavailable_on_corrupt_storage(alert_service: AlertService) -> None:
|
||||||
alert_service.repository.history_path.write_text("{not valid json", encoding="utf-8")
|
alert_service.repository.history_path.write_text('[{"severity": "warning"}]', encoding="utf-8")
|
||||||
config = PortfolioConfig(
|
config = PortfolioConfig(
|
||||||
gold_value=215_000.0,
|
gold_value=215_000.0,
|
||||||
entry_price=215.0,
|
entry_price=215.0,
|
||||||
|
|||||||
Reference in New Issue
Block a user