feat: 今天环境条新增下一个倒数日、浙江油价、上证指数、降雨/AQI
- 后端新增三源:中石化浙江油价(3步会话)、新浪上证行情、Open-Meteo 空气质量(国标AQI换算),按源独立超时与状态 - 天气行改为 降雨概率% · AQI 等级(无雨无AQI时保留观测时间),stale 补缓存标记 - 前端 strip 六格两行(桌面)/四行(移动),倒数日取本地缓存最近一条,超12字截断 - 修复两处 AppSheet 动画竞态存量 e2e 失败(modal contract footer 时序、scrollable 重开残留 dialog)
This commit is contained in:
@@ -125,7 +125,11 @@ async def test_fetch_weather_uses_fixed_haishu_location_and_current_conditions()
|
||||
"temperature_2m": 28.4,
|
||||
"apparent_temperature": 30.1,
|
||||
"weather_code": 2,
|
||||
}
|
||||
},
|
||||
"hourly": {
|
||||
"time": ["2026-09-16T14:00", "2026-09-16T15:00", "2026-09-16T16:00"],
|
||||
"precipitation_probability": [40, 75, None],
|
||||
},
|
||||
}
|
||||
|
||||
result = await environment.fetch_weather(request)
|
||||
@@ -136,6 +140,7 @@ async def test_fetch_weather_uses_fixed_haishu_location_and_current_conditions()
|
||||
"latitude": 29.88,
|
||||
"longitude": 121.55,
|
||||
"current": "temperature_2m,apparent_temperature,weather_code",
|
||||
"hourly": "precipitation_probability",
|
||||
"timezone": "Asia/Shanghai",
|
||||
},
|
||||
}
|
||||
@@ -143,6 +148,7 @@ async def test_fetch_weather_uses_fixed_haishu_location_and_current_conditions()
|
||||
"temperature_c": 28.4,
|
||||
"apparent_temperature_c": 30.1,
|
||||
"weather_code": 2,
|
||||
"precipitation_probability": 75,
|
||||
"observed_at": "2026-09-16T14:15:00+08:00",
|
||||
"source": "Open-Meteo",
|
||||
}
|
||||
@@ -171,6 +177,102 @@ async def test_fetch_weather_rejects_invalid_contract(current):
|
||||
await environment.fetch_weather(request)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"hourly",
|
||||
[
|
||||
None,
|
||||
[],
|
||||
"now",
|
||||
{"time": ["2026-09-16T14:00"]},
|
||||
{"time": [], "precipitation_probability": []},
|
||||
{"time": ["2026-09-16T14:00"], "precipitation_probability": [10, 20]},
|
||||
{"time": [123], "precipitation_probability": [10]},
|
||||
{"time": ["2026-09-16T14:00"], "precipitation_probability": ["40"]},
|
||||
{"time": ["2026-09-16T14:00"], "precipitation_probability": [True]},
|
||||
{"time": ["2026-09-16T14:00"], "precipitation_probability": [120]},
|
||||
{"time": ["2026-09-16T14:00"], "precipitation_probability": [-1]},
|
||||
],
|
||||
)
|
||||
async def test_fetch_weather_rejects_invalid_precipitation_hourly(hourly):
|
||||
payload = {
|
||||
"timezone": "Asia/Shanghai",
|
||||
"current": {
|
||||
"time": "2026-09-16T14:15",
|
||||
"temperature_2m": 28.4,
|
||||
"apparent_temperature": 30.1,
|
||||
"weather_code": 2,
|
||||
},
|
||||
}
|
||||
if hourly is not None:
|
||||
payload["hourly"] = hourly
|
||||
|
||||
async def request(*_args, **_kwargs):
|
||||
return payload
|
||||
|
||||
with pytest.raises((KeyError, TypeError, ValueError)):
|
||||
await environment.fetch_weather(request)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("probabilities", "expected"),
|
||||
[
|
||||
([40, 75, None], 75),
|
||||
([None, None, None], None),
|
||||
([0], 0),
|
||||
([None, 60], 60),
|
||||
],
|
||||
)
|
||||
async def test_fetch_weather_precipitation_probability_window(probabilities, expected):
|
||||
payload = {
|
||||
"timezone": "Asia/Shanghai",
|
||||
"current": {
|
||||
"time": "2026-09-16T14:15",
|
||||
"temperature_2m": 28.4,
|
||||
"apparent_temperature": 30.1,
|
||||
"weather_code": 2,
|
||||
},
|
||||
"hourly": {
|
||||
"time": [
|
||||
"2026-09-16T13:00",
|
||||
"2026-09-16T14:00",
|
||||
"2026-09-16T15:00",
|
||||
"2026-09-16T16:00",
|
||||
][: len(probabilities) + 1],
|
||||
"precipitation_probability": [99, *probabilities],
|
||||
},
|
||||
}
|
||||
|
||||
async def request(*_args, **_kwargs):
|
||||
return payload
|
||||
|
||||
result = await environment.fetch_weather(request)
|
||||
|
||||
assert result["precipitation_probability"] == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_weather_precipitation_probability_none_when_hour_slot_missing():
|
||||
payload = {
|
||||
"timezone": "Asia/Shanghai",
|
||||
"current": {
|
||||
"time": "2026-09-16T14:15",
|
||||
"temperature_2m": 28.4,
|
||||
"apparent_temperature": 30.1,
|
||||
"weather_code": 2,
|
||||
},
|
||||
"hourly": {"time": ["2026-09-20T14:00"], "precipitation_probability": [40]},
|
||||
}
|
||||
|
||||
async def request(*_args, **_kwargs):
|
||||
return payload
|
||||
|
||||
result = await environment.fetch_weather(request)
|
||||
|
||||
assert result["precipitation_probability"] is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_weather_rejects_wrong_response_timezone():
|
||||
async def request(*_args, **_kwargs):
|
||||
@@ -613,6 +715,10 @@ async def test_creator_cancellation_does_not_close_shared_default_fetch_client(m
|
||||
"apparent_temperature": 26,
|
||||
"weather_code": 1,
|
||||
},
|
||||
"hourly": {
|
||||
"time": ["2026-09-16T14:00"],
|
||||
"precipitation_probability": [10],
|
||||
},
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -774,7 +880,7 @@ def test_today_environment_endpoint_returns_aggregated_payload(client, monkeypat
|
||||
"/api/v1/setup/initialize",
|
||||
json={"username": "owner", "password": "correct horse battery staple"},
|
||||
)
|
||||
payload = {
|
||||
environment_payload = {
|
||||
"date": {
|
||||
"solar_date": "2026-09-16",
|
||||
"weekday": "星期三",
|
||||
@@ -785,12 +891,36 @@ def test_today_environment_endpoint_returns_aggregated_payload(client, monkeypat
|
||||
"gold": None,
|
||||
"errors": {"weather": "unavailable", "gold": "unavailable"},
|
||||
}
|
||||
extras_payload = {
|
||||
"air": None,
|
||||
"oil": None,
|
||||
"ashare": None,
|
||||
"errors": {"air": "unavailable", "oil": "unavailable", "ashare": "unavailable"},
|
||||
}
|
||||
|
||||
async def aggregate():
|
||||
return payload
|
||||
return environment_payload
|
||||
|
||||
async def aggregate_extras():
|
||||
return extras_payload
|
||||
|
||||
monkeypatch.setattr("backend.main.get_today_environment", aggregate)
|
||||
monkeypatch.setattr("backend.main.get_today_environment_extras", aggregate_extras)
|
||||
response = client.get("/api/v1/today/environment")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == payload
|
||||
assert response.json() == {
|
||||
"date": environment_payload["date"],
|
||||
"weather": None,
|
||||
"gold": None,
|
||||
"air": None,
|
||||
"oil": None,
|
||||
"ashare": None,
|
||||
"errors": {
|
||||
"weather": "unavailable",
|
||||
"gold": "unavailable",
|
||||
"air": "unavailable",
|
||||
"oil": "unavailable",
|
||||
"ashare": "unavailable",
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user