diff --git a/tests/test_wavey_gist.py b/tests/test_wavey_gist.py index 5285738..15e1f13 100644 --- a/tests/test_wavey_gist.py +++ b/tests/test_wavey_gist.py @@ -5,7 +5,7 @@ import requests -from utils.wavey_gist import upload_to_gist +from utils.wavey_gist import DEFAULT_GIST_TITLE, PRIMARY_FILE_NAME, upload_to_gist def _mock_response(*, post_json: dict | None = None) -> MagicMock: @@ -32,7 +32,11 @@ def test_successful_upload(self, mock_post: MagicMock) -> None: payload = mock_post.call_args[1]["json"] self.assertEqual(payload["title"], "Test") - self.assertEqual(payload["markdown"], "# Test\n\nSome **markdown**") + # Wavey Gist now expects a `files` snapshot — the legacy `markdown` field + # is rejected with HTTP 400. README.md is the preferred primary filename + # (https://gist.wavey.info/llms.txt). + self.assertNotIn("markdown", payload) + self.assertEqual(payload["files"][PRIMARY_FILE_NAME]["content"], "# Test\n\nSome **markdown**") self.assertEqual(mock_post.call_args[1]["headers"]["Authorization"], "Bearer test-key") @patch.dict("utils.wavey_gist.os.environ", {"WAVEY_GIST_API_KEY": "test-key"}) @@ -42,8 +46,8 @@ def test_no_title_sends_raw_content(self, mock_post: MagicMock) -> None: upload_to_gist("Content only") payload = mock_post.call_args[1]["json"] - self.assertEqual(payload["title"], "Monitoring Details") - self.assertEqual(payload["markdown"], "Content only") + self.assertEqual(payload["title"], DEFAULT_GIST_TITLE) + self.assertEqual(payload["files"][PRIMARY_FILE_NAME]["content"], "Content only") @patch.dict("utils.wavey_gist.os.environ", {}, clear=True) @patch("utils.wavey_gist.requests.post") @@ -57,6 +61,17 @@ def test_missing_url_returns_empty(self, mock_post: MagicMock) -> None: mock_post.return_value = _mock_response(post_json={"id": "abc123"}) self.assertEqual(upload_to_gist("x"), "") + @patch.dict("utils.wavey_gist.os.environ", {"WAVEY_GIST_API_KEY": "test-key"}) + @patch("utils.wavey_gist.requests.post") + def test_http_error_returns_empty(self, mock_post: MagicMock) -> None: + # E.g. the legacy `markdown` field — the API now rejects unknown fields + # with HTTP 400. The function must keep alerting and return "" so the + # caller can fall back to the in-Telegram summary. + mock_response = MagicMock() + mock_response.raise_for_status.side_effect = requests.HTTPError("400 Client Error") + mock_post.return_value = mock_response + self.assertEqual(upload_to_gist("x"), "") + @patch.dict("utils.wavey_gist.os.environ", {"WAVEY_GIST_API_KEY": "test-key"}) @patch("utils.wavey_gist.requests.post") def test_request_failure_returns_empty(self, mock_post: MagicMock) -> None: diff --git a/utils/wavey_gist.py b/utils/wavey_gist.py index ae8b9b4..250d507 100644 --- a/utils/wavey_gist.py +++ b/utils/wavey_gist.py @@ -10,6 +10,9 @@ WAVEY_GIST_API_URL = "https://api.wavey.info/api/v1/gists" DEFAULT_GIST_TITLE = "Monitoring Details" +# Wavey Gist picks `README.md` as the primary file when present, so the rendered +# page opens with our content. See https://gist.wavey.info/llms.txt. +PRIMARY_FILE_NAME = "README.md" def upload_to_gist(content: str, title: str = "") -> str: @@ -17,7 +20,8 @@ def upload_to_gist(content: str, title: str = "") -> str: Args: content: The markdown text to upload. - title: Optional title, prepended as a top-level markdown heading. + title: Optional title, prepended as a top-level markdown heading and used + as the gist's display title. Returns: The URL of the created gist, or an empty string on failure. @@ -31,23 +35,30 @@ def upload_to_gist(content: str, title: str = "") -> str: return "" markdown = f"# {title}\n\n{content}" if title else content + # Wavey Gist's create endpoint now expects a `files` snapshot — the legacy + # `title` + `markdown` fields are rejected with HTTP 400. See + # https://gist.wavey.info/llms.txt. + payload: dict = { + "title": title or DEFAULT_GIST_TITLE, + "files": {PRIMARY_FILE_NAME: {"content": markdown}}, + } try: response = requests.post( WAVEY_GIST_API_URL, - json={"title": title or DEFAULT_GIST_TITLE, "markdown": markdown}, + json=payload, headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, timeout=10, ) response.raise_for_status() - payload = response.json() + body = response.json() except (requests.RequestException, ValueError) as e: logger.warning("Failed to upload to Wavey Gist: %s", e) return "" - url = payload.get("url", "") + url = body.get("url", "") if not url: - logger.warning("Wavey Gist response did not include a URL: %s", payload) + logger.warning("Wavey Gist response did not include a URL: %s", body) return "" logger.info("Uploaded gist to %s", url)