feat: content provenance and complete frame management workflows
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
from unittest.mock import Mock
|
||||
|
||||
from pigway_frame_flow.models import WeatherConfig
|
||||
from pigway_frame_flow.providers.weather import ConfigurableWeatherProvider
|
||||
|
||||
|
||||
def transport():
|
||||
response = Mock()
|
||||
response.json.return_value = {
|
||||
"current": {
|
||||
"temperature_2m": 24,
|
||||
"weather_code": 3,
|
||||
"time": "2026-10-02T10:00",
|
||||
},
|
||||
"daily": {
|
||||
"time": ["2026-10-02"],
|
||||
"weather_code": [61],
|
||||
"temperature_2m_min": [20],
|
||||
"temperature_2m_max": [26],
|
||||
"precipitation_probability_max": [80],
|
||||
},
|
||||
}
|
||||
return Mock(return_value=response)
|
||||
|
||||
|
||||
def test_current_forecast_and_cache(service):
|
||||
get = transport()
|
||||
provider = ConfigurableWeatherProvider(service.db, get)
|
||||
provider.configure(WeatherConfig(provider="open-meteo"))
|
||||
assert provider.get_current()["temperature"] == "24°C"
|
||||
assert provider.get_forecast()[0]["rain_probability"] == 80
|
||||
assert get.call_count == 1
|
||||
assert get.call_args.kwargs["timeout"] == 8
|
||||
assert not get.call_args.kwargs["allow_redirects"]
|
||||
|
||||
|
||||
def test_weather_stale_and_error_backoff(service):
|
||||
get = transport()
|
||||
provider = ConfigurableWeatherProvider(service.db, get)
|
||||
provider.configure(WeatherConfig(provider="open-meteo"))
|
||||
provider.get_current()
|
||||
provider.next_fetch = 0
|
||||
get.side_effect = TimeoutError("private transport detail")
|
||||
assert provider.get_current()["stale"]
|
||||
assert provider.get_current()["temperature"] == "24°C"
|
||||
assert get.call_count == 2
|
||||
assert "private" not in str(provider.get_current())
|
||||
|
||||
|
||||
def test_unavailable_never_masquerades_as_mock(service):
|
||||
get = Mock(side_effect=TimeoutError("secret"))
|
||||
provider = ConfigurableWeatherProvider(service.db, get)
|
||||
provider.configure(WeatherConfig(provider="open-meteo"))
|
||||
current = provider.get_current()
|
||||
assert not current["mock"] and not current["available"] and current["stale"]
|
||||
assert "secret" not in str(current)
|
||||
|
||||
|
||||
def test_provider_settings_persist_and_validate(client, service):
|
||||
assert client.get("/api/v1/providers/weather").json()["current"]["mock"]
|
||||
assert (
|
||||
client.put("/api/v1/settings/weather", json={"latitude": 100}).status_code
|
||||
== 422
|
||||
)
|
||||
assert (
|
||||
client.put(
|
||||
"/api/v1/settings/weather",
|
||||
json={
|
||||
"provider": "mock",
|
||||
"location": "Test town",
|
||||
"latitude": 30,
|
||||
"longitude": 121,
|
||||
},
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
assert ConfigurableWeatherProvider(service.db).config().location == "Test town"
|
||||
|
||||
|
||||
def test_manual_job_run_and_preview_are_distinct(client, service):
|
||||
job = client.post(
|
||||
"/api/v1/jobs",
|
||||
json={
|
||||
"name": "Preview",
|
||||
"enabled": False,
|
||||
"schedule": {"type": "daily", "time": "07:00"},
|
||||
"source": {"type": "template", "template": "quote", "data": {}},
|
||||
},
|
||||
).json()
|
||||
assert client.get("/api/v1/jobs/" + job["id"]).status_code == 200
|
||||
preview = client.post("/api/v1/jobs/" + job["id"] + "/preview").json()
|
||||
service.process_one()
|
||||
assert service.operation(preview["operation_id"])["status"] == "COMPLETED"
|
||||
assert service.registry.get("living-room").client.uploads == 0
|
||||
service.settings.mock = False
|
||||
assert client.post("/api/v1/jobs/" + job["id"] + "/run").status_code == 403
|
||||
service.settings.mock = True
|
||||
first = client.post(
|
||||
"/api/v1/jobs/" + job["id"] + "/run", headers={"Idempotency-Key": "manual-test"}
|
||||
)
|
||||
second = client.post(
|
||||
"/api/v1/jobs/" + job["id"] + "/run", headers={"Idempotency-Key": "manual-test"}
|
||||
)
|
||||
assert first.status_code == 202 and first.json() == second.json()
|
||||
|
||||
|
||||
def test_image_url_allowlist_is_persistent_and_restrictive(client, service):
|
||||
assert client.get("/api/v1/settings/image-url").json()["allowed_hosts"] == []
|
||||
assert (
|
||||
client.put(
|
||||
"/api/v1/settings/image-url",
|
||||
json={"allowed_hosts": ["https://example.com"]},
|
||||
).status_code
|
||||
== 422
|
||||
)
|
||||
assert (
|
||||
client.put(
|
||||
"/api/v1/settings/image-url", json={"allowed_hosts": ["*.example.com"]}
|
||||
).status_code
|
||||
== 422
|
||||
)
|
||||
assert client.put(
|
||||
"/api/v1/settings/image-url",
|
||||
json={"allowed_hosts": ["EXAMPLE.com", "example.com"]},
|
||||
).json()["allowed_hosts"] == ["example.com"]
|
||||
assert service.settings.image_url_hosts == ["example.com"]
|
||||
assert service.db.one("SELECT payload FROM app_config WHERE key='image_url_hosts'")
|
||||
Reference in New Issue
Block a user