"""Tests for us2kr.deliver.blogger (no network: the httpx client is a stub).""" from __future__ import annotations import json from datetime import UTC, date, datetime from typing import Any import httpx import pytest from us2kr.config import Settings from us2kr.deliver import build_deliverers from us2kr.deliver.base import DeliveryError from us2kr.deliver.blogger import BloggerDeliverer, briefing_html, post_title from us2kr.models import BriefingResult, MarketPayload, Report TOKEN_OK = {"access_token": "at-1", "expires_in": 3600} POST_OK = {"kind": "blogger#post", "id": "42", "url": "https://nybrief.blogspot.com/2026/09/post.html"} def make_result(headline: str | None = "9월 첫 거래일 하락") -> BriefingResult: payload = MarketPayload( session_date=date(2026, 8, 31), us_close_time=datetime(2026, 8, 31, 20, 0, tzinfo=UTC), generated_at=datetime(2026, 9, 1, 6, 15, tzinfo=UTC), krx_date=date(2026, 9, 1), krx_open_today=True, ) report = None if headline is not None: report = Report( headline=headline, market_overview="지수가 내렸습니다.", key_drivers=["금리 상승"], rates_fx_commodities="금리 상승.", korea_implications=["반도체 부담"], watchlist_today=["환율"], risk_flags=["변동성"], ) markdown = ( "# 🇺🇸→🇰🇷 미국 마감 브리핑 — 2026-09-01\n" "미국 마감: 2026-09-01 05:00 KST\n\n" "## 총평\n**하락 마감**\n\n" "| 종목 | 종가 |\n|---|---|\n| S&P 500 | 7,686.42 |\n" ) return BriefingResult(payload=payload, report=report, markdown=markdown, degraded=report is None) class _Resp: def __init__(self, status: int, body: dict[str, Any]): self.status_code = status self._body = body self.text = json.dumps(body) def json(self) -> dict[str, Any]: return self._body class FakeHttp: """Minimal stand-in for httpx.Client: scripted responses, recorded calls.""" def __init__(self, responses: list[Any]): self._responses = list(responses) self.calls: list[dict[str, Any]] = [] def request(self, method: str, url: str, **kwargs: Any) -> Any: self.calls.append({"method": method, "url": url, **kwargs}) if not self._responses: raise AssertionError("unexpected extra HTTP call") item = self._responses.pop(0) if isinstance(item, Exception): raise item return item def post(self, url: str, **kwargs: Any) -> Any: return self.request("POST", url, **kwargs) def make_deliverer(responses: list[Any]) -> tuple[BloggerDeliverer, FakeHttp]: client = FakeHttp(responses) deliverer = BloggerDeliverer( "cid", "csecret", "rtoken", "12345", labels=["미국증시"], client=client # type: ignore[arg-type] ) return deliverer, client # ------------------------------------------------------------------ html & title def test_briefing_html_drops_h1_and_renders_tables(): html = briefing_html(make_result().markdown) assert "미국 마감 브리핑 — 2026-09-01" not in html # H1 is the post title, not body assert "
| " not in html # every cell got the inline style
def test_post_title_with_and_without_headline():
assert post_title(make_result()) == "미국 마감 브리핑 2026-09-01 — 9월 첫 거래일 하락"
assert post_title(make_result(headline=None)) == "미국 마감 브리핑 2026-09-01 (미 2026-08-31 세션)"
# ------------------------------------------------------------------ delivery
EMPTY_LIST = {"kind": "blogger#postList"} # no items key on an empty blog
def test_deliver_refreshes_token_lists_then_inserts():
deliverer, client = make_deliverer(
[_Resp(200, TOKEN_OK), _Resp(200, EMPTY_LIST), _Resp(200, POST_OK)]
)
receipt = deliverer.deliver(make_result())
assert receipt == POST_OK["url"]
token_call, list_call, insert_call = client.calls
assert token_call["data"]["grant_type"] == "refresh_token"
assert list_call["method"] == "GET" and list_call["params"]["fetchBodies"] == "false"
assert insert_call["method"] == "POST"
assert "refresh_token" not in insert_call["url"] # secrets never in URLs
assert insert_call["url"].endswith("/blogs/12345/posts/")
assert insert_call["headers"]["Authorization"] == "Bearer at-1"
body = insert_call["json"]
assert body["title"].startswith("미국 마감 브리핑 2026-09-01")
assert body["labels"] == ["미국증시"] and " |