Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Linter
permissions:
pull-requests: read
checks: write

on:
push:
branches:
- "master"
- "renovate/**"
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# python-lint
- name: Install the latest version of ruff
uses: astral-sh/ruff-action@278981a28ce3188b1e39527901f38254bf3aac89 # v4.1.0
with:
version: "latest"
args: "format --check --diff"
- name: Run Ruff linter
run: |
# check the formatted codes
ruff check --select I --fix
ruff format
# run the linters
ruff check .
# Dockerfile lint
- name: hadolint
uses: reviewdog/action-hadolint@1b2cfa6ba72072ad35158d7ff3aa49bbdc03506d # v1.51.0
with:
hadolint_ignore: DL3007 SC2114
reporter: github-check

# misspell
- name: misspell
uses: reviewdog/action-misspell@d6429416b12b09b4e2768307d53bef58d172e962 # v1.27.0
with:
locale: "US"
reporter: github-check
74 changes: 74 additions & 0 deletions .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Unittest
permissions: {}

on:
push:
branches:
- "master"
- "renovate/**"
paths:
- '.github/workflows/unittest.yml'
- 'weather_briefing/**'
- 'tests/**'
- 'uv.lock'
- 'pyproject.toml'
pull_request:
paths:
- '.github/workflows/unittest.yml'
- 'weather_briefing/**'
- 'tests/**'
- 'uv.lock'
- 'pyproject.toml'

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14"]
os: [ubuntu-latest, macOS-latest]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: ${{ matrix.python-version }}
- name: Install the latest version of uv
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
enable-cache: true
version: '0.11.28'
- name: Test with python ${{ matrix.python-version }}
run: |
uv sync --frozen
uv run pytest

coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: '3.14'
- name: Install the latest version of uv and set the python version
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
enable-cache: true
version: '0.11.28'
- name: Generate Coverage Reports
run: |
uv sync --frozen
uv run pytest --cov --cov-branch --cov-report=xml
- name: Upload results to Codecov
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies = [
dev = [
"anyio>=4.11,<5",
"pytest>=9,<10",
"pytest-cov>=7,<8",
"ruff>=0.5,<1",
]

Expand Down
30 changes: 10 additions & 20 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ def test_weather_provider_order_can_be_configured(monkeypatch) -> None:

settings = Settings.from_env()

assert weather_providers_for(
_resolved_location(mainland=True), settings.weather_providers
) == ("open-meteo", "qweather")
assert weather_providers_for(_resolved_location(mainland=True), settings.weather_providers) == (
"open-meteo",
"qweather",
)


def test_non_mainland_weather_providers_default_to_open_meteo_only(monkeypatch) -> None:
Expand All @@ -65,14 +66,10 @@ def test_non_mainland_weather_providers_default_to_open_meteo_only(monkeypatch)

settings = Settings.from_env()

assert weather_providers_for(
_resolved_location(mainland=False), settings.weather_providers
) == ("open-meteo",)
assert weather_providers_for(_resolved_location(mainland=False), settings.weather_providers) == ("open-meteo",)


def test_optional_rss_sources_are_loaded_from_named_file(
monkeypatch, tmp_path: Path
) -> None:
def test_optional_rss_sources_are_loaded_from_named_file(monkeypatch, tmp_path: Path) -> None:
_required_environment(monkeypatch)
source_file = tmp_path / "rss-sources.json"
source_file.write_text(
Expand All @@ -87,9 +84,7 @@ def test_optional_rss_sources_are_loaded_from_named_file(
assert [feed.id for feed in settings.feeds] == ["test"]


def test_location_file_supports_multiple_places_and_optional_coordinates(
monkeypatch, tmp_path: Path
) -> None:
def test_location_file_supports_multiple_places_and_optional_coordinates(monkeypatch, tmp_path: Path) -> None:
_required_environment(monkeypatch)
location_file = tmp_path / "locations.json"
location_file.write_text(
Expand All @@ -108,18 +103,13 @@ def test_location_file_supports_multiple_places_and_optional_coordinates(
assert settings.locations[1].longitude == 116.380556


def test_rss_source_location_ids_must_reference_configured_locations(
monkeypatch, tmp_path: Path
) -> None:
def test_rss_source_location_ids_must_reference_configured_locations(monkeypatch, tmp_path: Path) -> None:
_required_environment(monkeypatch)
location_file = tmp_path / "locations.json"
location_file.write_text(
'[{"id":"beijing","name":"Beijing"}]', encoding="utf-8"
)
location_file.write_text('[{"id":"beijing","name":"Beijing"}]', encoding="utf-8")
source_file = tmp_path / "rss-sources.json"
source_file.write_text(
'[{"id":"feed","name":"Feed","url":"https://example.invalid/feed",'
'"location_ids":["shanghai"]}]',
'[{"id":"feed","name":"Feed","url":"https://example.invalid/feed","location_ids":["shanghai"]}]',
encoding="utf-8",
)
monkeypatch.setenv("BRIEFING_LOCATIONS_FILE", str(location_file))
Expand Down
4 changes: 1 addition & 3 deletions tests/test_content_cleaners.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ def test_html_cleaner_removes_page_chrome_without_rewriting_article_text() -> No


def test_html_cleaner_decodes_entities_to_visible_text() -> None:
cleaned = HTMLContentCleaner().clean(
"<p>风力 7&amp;ndash;8 级</p>", ContentCleaningRules()
)
cleaned = HTMLContentCleaner().clean("<p>风力 7&amp;ndash;8 级</p>", ContentCleaningRules())

assert cleaned == "风力 7&ndash;8 级"
20 changes: 8 additions & 12 deletions tests/test_geocoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ def handler(request: httpx.Request) -> httpx.Response:
)

async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
result = await OpenMeteoGeocodingProvider(client).geocode(
LocationSpec("beijing", "北京市西城区中南海")
)
result = await OpenMeteoGeocodingProvider(client).geocode(LocationSpec("beijing", "北京市西城区中南海"))

assert result.latitude == 39.911389
assert result.country_code == "CN"
Expand Down Expand Up @@ -76,9 +74,7 @@ def handler(request: httpx.Request) -> httpx.Response:
)

async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
result = await OpenMeteoGeocodingProvider(client).geocode(
LocationSpec("example", "中国北京市西城区中南海")
)
result = await OpenMeteoGeocodingProvider(client).geocode(LocationSpec("example", "中国北京市西城区中南海"))

assert result.latitude == 39.911389
assert result.longitude == 116.380556
Expand Down Expand Up @@ -135,9 +131,9 @@ def handler(request: httpx.Request) -> httpx.Response:
)

async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
result = await NominatimGeocodingProvider(
client, user_agent="weather-briefing-test/1"
).geocode(LocationSpec("beijing", "北京市西城区中南海地区"))
result = await NominatimGeocodingProvider(client, user_agent="weather-briefing-test/1").geocode(
LocationSpec("beijing", "北京市西城区中南海地区")
)

assert result.latitude == 39.911389
assert result.longitude == 116.380556
Expand All @@ -164,9 +160,9 @@ async def geocode(self, location: LocationSpec) -> ResolvedLocation:
)

original_name = "中国北京市西城区中南海1号"
result = await PrecisionReducingGeocodingProvider(
RoadLevelGeocoder()
).geocode(LocationSpec("example", original_name))
result = await PrecisionReducingGeocodingProvider(RoadLevelGeocoder()).geocode(
LocationSpec("example", original_name)
)

assert queries == [original_name, "中国北京市西城区中南海"]
assert result.name == original_name
Expand Down
4 changes: 1 addition & 3 deletions tests/test_publishers.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,4 @@ async def test_telegram_rejects_oversized_single_message_before_delivery() -> No
async with httpx.AsyncClient(transport=httpx.MockTransport(lambda _: httpx.Response(200))) as client:
publisher = TelegramPublisher(client, "runtime-token", "runtime-chat")
with pytest.raises(DeliveryError, match="exceeds"):
await publisher.publish(
RenderedMessage("<b>short markup</b>", 4097), single_message=True
)
await publisher.publish(RenderedMessage("<b>short markup</b>", 4097), single_message=True)
6 changes: 1 addition & 5 deletions tests/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,7 @@ async def summarize(self, system_prompt: str, payload: dict[str, object]) -> dic
"conclusions": [conclusion],
"active_warnings": [],
"resolved_warning_ids": [],
"advice": (
[conclusion]
if payload["mode"] == "daily" or self._include_hourly_advice
else []
),
"advice": ([conclusion] if payload["mode"] == "daily" or self._include_hourly_advice else []),
"disaster_tracking": [],
"should_publish": self._should_publish,
}
Expand Down
10 changes: 3 additions & 7 deletions tests/test_weather_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,14 @@ def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(
200,
json={
"metadata": {
"attributions": ["https://developer.qweather.com/attribution.html"]
},
"metadata": {"attributions": ["https://developer.qweather.com/attribution.html"]},
"indexes": [
{
"code": "cn-mee",
"name": "中国环境空气质量指数",
"aqi": 68,
"category": "良",
"health": {
"advice": {"generalPopulation": "可以正常进行户外活动。"}
},
"health": {"advice": {"generalPopulation": "可以正常进行户外活动。"}},
}
],
"pollutants": [
Expand Down Expand Up @@ -201,7 +197,7 @@ def handler(request: httpx.Request) -> httpx.Response:
"us_aqi": 42,
"us_aqi_pm2_5": 35,
"pm2_5": 9.5,
}
},
},
)

Expand Down
Loading
Loading