Files
planet/backend/tests/test_enum_contracts.py
linkong 899e3bce43
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
release / images (push) Has been cancelled
ci / delivery (push) Has been cancelled
release: bump version to 0.71.0
2026-06-11 16:47:24 +08:00

75 lines
2.8 KiB
Python

"""Compatibility contracts for stable backend protocol enums."""
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from types import SimpleNamespace
from app.core.enums import (
BreakingLevel,
BreakingScope,
JobStatus,
NewsImportanceLevel,
NewsSourceType,
PlaygroundMessageKind,
PlaygroundMessageStatus,
UserRole,
parse_enum,
)
from app.services.earth_news_classification import (
BREAKING_LEVEL_RANK,
BREAKING_TTL,
breaking_sort_rank,
importance_level,
)
def test_protocol_enum_values_remain_api_compatible() -> None:
assert [item.value for item in NewsImportanceLevel] == ["low", "medium", "high", "critical"]
assert [item.value for item in BreakingLevel] == ["none", "watch", "breaking", "critical"]
assert [item.value for item in BreakingScope] == ["regional", "global"]
assert [item.value for item in NewsSourceType] == ["rss", "atom", "aggregated", "reference", "manual"]
assert [item.value for item in UserRole] == ["viewer", "admin", "super_admin"]
assert JobStatus.RUNNING.value == "running"
assert PlaygroundMessageKind.THINKING.value == "thinking"
assert PlaygroundMessageStatus.ERROR.value == "error"
assert PlaygroundMessageStatus.STOPPED.value == "stopped"
def test_parse_enum_accepts_legacy_strings_and_safely_falls_back(caplog) -> None:
assert parse_enum(JobStatus, "RUNNING", JobStatus.FAILED) is JobStatus.RUNNING
assert parse_enum(JobStatus, None, JobStatus.QUEUED) is JobStatus.QUEUED
assert parse_enum(JobStatus, "legacy-unknown", JobStatus.FAILED) is JobStatus.FAILED
assert "legacy-unknown" in caplog.text
def test_importance_level_boundaries() -> None:
expected = {
34: NewsImportanceLevel.LOW,
35: NewsImportanceLevel.MEDIUM,
59: NewsImportanceLevel.MEDIUM,
60: NewsImportanceLevel.HIGH,
79: NewsImportanceLevel.HIGH,
80: NewsImportanceLevel.CRITICAL,
}
assert {score: importance_level(score) for score in expected} == expected
def test_breaking_rank_and_ttl_contracts() -> None:
assert BREAKING_LEVEL_RANK[BreakingLevel.CRITICAL] > BREAKING_LEVEL_RANK[BreakingLevel.BREAKING]
assert BREAKING_TTL[BreakingLevel.WATCH] == timedelta(hours=6)
assert BREAKING_TTL[BreakingLevel.BREAKING] == timedelta(hours=12)
assert BREAKING_TTL[BreakingLevel.CRITICAL] == timedelta(hours=24)
now = datetime.now(UTC)
active = SimpleNamespace(
breaking_level=BreakingLevel.BREAKING.value,
breaking_expires_at=now + timedelta(minutes=1),
)
expired = SimpleNamespace(
breaking_level=BreakingLevel.CRITICAL.value,
breaking_expires_at=now - timedelta(minutes=1),
)
assert breaking_sort_rank(active) == BREAKING_LEVEL_RANK[BreakingLevel.BREAKING]
assert breaking_sort_rank(expired) == 0