Skip to content

refactor(utils): extract shared HTTP-post helper for delivery modules#952

Open
mayankbharati-ops wants to merge 5 commits intoTracer-Cloud:mainfrom
mayankbharati-ops:refactor/864-shared-delivery-transport-helper
Open

refactor(utils): extract shared HTTP-post helper for delivery modules#952
mayankbharati-ops wants to merge 5 commits intoTracer-Cloud:mainfrom
mayankbharati-ops:refactor/864-shared-delivery-transport-helper

Conversation

@mayankbharati-ops
Copy link
Copy Markdown
Contributor

Closes #864.

Summary

`app/utils/slack_delivery.py`, `app/utils/discord_delivery.py`, and `app/utils/telegram_delivery.py` each reimplemented the same "POST JSON + parse response + return `(ok, error, id)` style result" pattern. The transport pieces (HTTP POST, timeout, exception suppression, JSON decode) were identical; only the success criteria, auth scheme, and error-extraction differed.

This PR adds `app/utils/delivery_transport.py` and migrates all three modules onto it, per the issue's acceptance criteria.

Design

```python
@DataClass(frozen=True)
class DeliveryResponse:
ok: bool # request did not raise
status_code: int = 0 # HTTP status (0 if request raised)
data: dict = {} # parsed JSON object body, or {} on non-JSON / non-dict / decode error
text: str = "" # raw response body
error: str = "" # exception message when ok=False

def post_json(url, payload, *, headers=None, timeout=15.0, follow_redirects=False) -> DeliveryResponse:
...
```

The helper deliberately does not decide provider-level success. `ok=True` means the HTTP request completed without raising; callers inspect `status_code` and `data` per their own provider semantics:

Provider Success check
Slack chat.postMessage / reactions `data["ok"]`
Slack incoming webhook / NextJS proxy `200 ≤ status_code < 300`
Discord channels API `status_code in (200, 201)`
Telegram bot API `status_code == 200`

Refactored call sites

  • Slack — all 4 code paths (`_call_reactions_api`, `_post_direct`, `_post_via_webapp`, `_post_via_incoming_webhook`) now go through `post_json`. `send_slack_report` orchestration is unchanged. Bearer-header construction factored into `_slack_bearer_headers`.
  • Discord — `post_discord_message` and `create_discord_thread` go through `post_json`. `Bot ` header construction factored into `_discord_auth_headers`. Error extraction factored into `_discord_error_from_data`.
  • Telegram — `post_telegram_message` goes through `post_json`. The bot-token redaction in error strings is preserved (the shared transport never sees the bot token in the URL after the request raises, but `_redact_token` still runs over `response.error` defensively).

Public function signatures unchanged. Provider-specific payload building stays in the calling modules per the issue's scope.

Test coverage

File Tests Status
`tests/utils/test_delivery_transport.py` 15 (new) happy path, 5 transport-failure exception shapes, JSON decode fallbacks (non-JSON / JSON array / empty body), header/timeout/follow_redirects pass-through, DeliveryResponse frozen-dataclass invariants
`tests/utils/test_slack_delivery.py` 24 (new) reactions API (success / known-idempotent failure / unexpected error / transport exception / payload assertions), `_post_direct` (success, slack_error, exception, payload), incoming webhook (success / 5 status codes / transport exception / blocks+extra merge), `_post_via_webapp` (skipped without TRACER_API_URL / success / 5xx), `send_slack_report` orchestration (no thread_ts / webhook fallback / direct path / direct→webapp fallback chain)
`tests/utils/test_discord_delivery.py` 32 existing + 3 new pre-existing tests updated to patch new transport boundary; new `TestDelegatesToSharedTransport` class pins the contract: module no longer imports `httpx`, both Discord helpers go through `post_json`
`tests/utils/test_telegram_delivery.py` 26 existing + 3 new same delegation pins, plus a regression test that bot-token is redacted in error strings even when the failure originates in the shared transport

Verification

  • `pytest tests/` (excluding e2e/deployment/chaos/synthetic): 3191 pass, 2 skipped, 1 xfailed, 0 failures.
  • `pytest tests/utils/`: 105 pass, 1 skipped.
  • `ruff check app/ tests/` and `ruff format --check`: clean.
  • `mypy app/utils/`: clean on the new and refactored modules. (The 5 errors mypy reports are pre-existing missing-stub warnings for `pymysql`, `datasets`, and `huggingface_hub` in `app/integrations/` — unrelated to this PR.)

Acceptance criteria audit

  • Slack, Discord, and Telegram helpers all use the shared transport helper
  • Provider-specific response shapes stay unchanged
  • Existing public helper names stay unchanged (`post_discord_message`, `create_discord_thread`, `post_telegram_message`, `send_slack_report`, etc.)
  • `tests/utils/test_slack_delivery.py` added
  • `tests/utils/test_discord_delivery.py` extended
  • `tests/utils/test_telegram_delivery.py` extended
  • Focused test module for the new shared helper added (`tests/utils/test_delivery_transport.py`)

Scope

Files Change
`app/utils/delivery_transport.py` new (97 lines)
`app/utils/slack_delivery.py` refactored — all 4 `httpx.post` call sites replaced
`app/utils/discord_delivery.py` refactored — both call sites replaced
`app/utils/telegram_delivery.py` refactored — single call site replaced
`tests/utils/test_delivery_transport.py` new
`tests/utils/test_slack_delivery.py` new
`tests/utils/test_discord_delivery.py` 3 new tests + patch-path updates on pre-existing tests
`tests/utils/test_telegram_delivery.py` 3 new tests + patch-path updates on pre-existing tests

Closes Tracer-Cloud#864.

``app/utils/slack_delivery.py``, ``app/utils/discord_delivery.py``, and
``app/utils/telegram_delivery.py`` each issued ``httpx.post`` directly,
applied a per-provider timeout, parsed the response body, and converted
exceptions to ``(False, error, ...)`` tuples. The transport pieces were
identical; only the success criteria, auth scheme, and error-message
extraction differed per provider.

Add ``app/utils/delivery_transport.py`` with ``post_json`` plus a
``DeliveryResponse`` dataclass that captures the shared transport
behavior: HTTP POST, timeout, optional ``follow_redirects``, exception
suppression, and JSON decoding with graceful fallback. The helper
deliberately does not decide provider-level success — callers inspect
``status_code`` / ``data`` per their own semantics.

Refactored callers:

- Slack: ``_call_reactions_api``, ``_post_direct``, ``_post_via_webapp``,
  ``_post_via_incoming_webhook`` — all four code paths now go through
  ``post_json``. ``send_slack_report`` orchestration unchanged.
- Discord: ``post_discord_message``, ``create_discord_thread`` — same
  Bearer/Bot header pattern factored into ``_discord_auth_headers``.
- Telegram: ``post_telegram_message`` — bot-token redaction in error
  messages preserved by re-running ``_redact_token`` over
  ``response.error``.

Provider-specific payload building, success criteria
(``data["ok"]`` for Slack, status codes for Discord/Telegram), and
error extraction all stay in the calling modules. Public function
signatures are unchanged.

Tests:

- ``tests/utils/test_delivery_transport.py`` (new) — 15 tests covering
  happy path, every transport-failure shape (httpx.ConnectError,
  ReadTimeout, RequestError, OSError, RuntimeError), JSON-decode
  fallbacks (non-JSON body, JSON array, empty body), header / timeout /
  follow_redirects pass-through, and DeliveryResponse frozen-dataclass
  invariants.
- ``tests/utils/test_slack_delivery.py`` (new) — 24 tests covering all
  four Slack code paths, ``send_slack_report`` orchestration, and
  fallback chain ``direct → webapp``.
- ``tests/utils/test_discord_delivery.py`` (extended) — 3 new tests
  pinning the helper-delegation contract: module no longer imports
  ``httpx``, ``post_discord_message`` and ``create_discord_thread``
  both go through ``post_json``.
- ``tests/utils/test_telegram_delivery.py`` (extended) — 3 new tests
  pinning the same contract plus a regression test that the bot-token
  is redacted out of error strings even when the failure originates
  from the shared transport.
- All pre-existing tests in ``test_discord_delivery.py`` and
  ``test_telegram_delivery.py`` updated to patch
  ``app.utils.delivery_transport.httpx.post`` (the new transport
  boundary) instead of the per-module ``httpx.post`` import that no
  longer exists.

Verification:

- ``pytest tests/``: 3191 pass, 2 skipped, 1 xfailed, 0 failures.
- ``pytest tests/utils/``: 105 pass, 1 skipped (was 32 + 26 = 58 across
  delivery modules; now 105 with helper + Slack additions).
- ``ruff check app/ tests/`` and ``ruff format --check``: clean.
- ``mypy app/utils/``: clean on touched modules; the 5 reported errors
  are pre-existing missing-stub warnings in
  ``app/integrations/{mariadb,mysql,hf_remote}`` unrelated to this PR.
Comment thread tests/utils/test_discord_delivery.py Fixed
Comment thread tests/utils/test_telegram_delivery.py Fixed
The previous CI run hit a known transient ImportError in
anyio.lowlevel/mcp when opensre integrations list ran on a
pytest-xdist worker. Reproduces zero times locally on origin/main and
on this branch; pushing an empty commit to re-run with a fresh
interpreter pool.
@mayankbharati-ops
Copy link
Copy Markdown
Contributor Author

Heads up: the test (ubuntu-latest) failure on the previous run is a transient anyio.lowlevel circular-import flake triggered by mcp during opensre integrations list on a pytest-xdist worker. Reproduces 0/3 times locally on origin/main and on this branch — same Python 3.13 env. PR #780 (which I had open earlier on the same env) also passed CI cleanly. Pushed an empty commit to re-trigger; expect 5/5 green this time.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 25, 2026

Greptile Summary

This PR extracts a shared post_json / DeliveryResponse transport layer (app/utils/delivery_transport.py) and migrates all four Slack, both Discord, and the single Telegram httpx.post call sites onto it, eliminating the duplicated try/except + JSON-decode pattern across three modules. Public function signatures are preserved, provider-specific success criteria stay in the callers, and 15 new transport-focused tests plus 27 new/updated delivery tests were added with all 3,191 existing tests still passing.

Confidence Score: 5/5

Safe to merge — all remaining findings are P2 style/coverage suggestions with no correctness impact

The refactor is mechanically correct: every provider's success semantics, error strings, and public signatures are preserved. The three P2 comments (mutable dict in frozen dataclass, dropped exception-type log field, missing Slack delegation regression test) are quality notes that do not affect runtime behaviour or data correctness.

tests/utils/test_slack_delivery.py is the only file worth revisiting to add the TestDelegatesToSharedTransport class that the Discord and Telegram test files already have.

Important Files Changed

Filename Overview
app/utils/delivery_transport.py New shared HTTP-POST transport; clean design, well-typed, never re-raises — minor: data dict is mutable despite frozen=True dataclass
app/utils/discord_delivery.py Both call sites migrated to post_json; auth and error helpers factored out; return signatures unchanged
app/utils/slack_delivery.py All four httpx.post call sites replaced; _slack_bearer_headers factored out; minor: exception type removed from _post_direct log
app/utils/telegram_delivery.py Single call site migrated; bot-token redaction preserved; isinstance(result, dict) guard added — a net improvement over the original
tests/utils/test_delivery_transport.py 15 new tests covering happy path, 5 exception shapes, JSON decode fallbacks, kwarg pass-through, and frozen-dataclass invariants
tests/utils/test_slack_delivery.py 24 new tests; good coverage of all 4 code paths but missing TestDelegatesToSharedTransport regression class present in Discord/Telegram test files
tests/utils/test_discord_delivery.py Patch paths updated to delivery_transport.httpx.post; 3 new delegation tests including module-no-httpx assertion
tests/utils/test_telegram_delivery.py Patch paths updated; 3 new delegation tests including token-redaction regression and module-no-httpx assertion

Sequence Diagram

sequenceDiagram
    participant Caller
    participant Provider as "slack/discord/telegram delivery"
    participant Transport as "delivery_transport.post_json"
    participant httpx

    Caller->>Provider: public helper call
    Provider->>Transport: post_json(url, payload, headers, timeout)
    Transport->>httpx: httpx.post(url, json, headers, timeout, follow_redirects)
    alt network success
        httpx-->>Transport: Response object
        Transport-->>Provider: DeliveryResponse ok=True status_code data text
        Note over Provider: Apply provider-level check on status_code or data
    else network error
        httpx-->>Transport: Exception raised
        Transport-->>Provider: DeliveryResponse ok=False error=str(exc)
    end
    Provider-->>Caller: result tuple
Loading

Comments Outside Diff (1)

  1. app/utils/slack_delivery.py, line 344-354 (link)

    P2 Exception type dropped from _post_direct log message

    The original log included type(exc).__name__ to help distinguish TimeoutError from ConnectionError etc. at a glance. The refactored message omits it, making the log slightly harder to triage under load when multiple exception shapes are possible.

Reviews (1): Last reviewed commit: "ci: retrigger after anyio/mcp circular-i..." | Re-trigger Greptile

Comment thread app/utils/delivery_transport.py
Comment thread tests/utils/test_slack_delivery.py
@muddlebee
Copy link
Copy Markdown
Collaborator

@mayankbharati-ops thank you for the PR. could you pls fix all the review points above?

@mayankbharati-ops
Copy link
Copy Markdown
Contributor Author

yeah sure @muddlebee , I'll update it and let you know

- Wrap `DeliveryResponse.data` in `MappingProxyType` so the frozen
  dataclass is fully immutable end-to-end. Mutating the parsed body
  now raises `TypeError` instead of silently succeeding, and caller-
  passed dicts can no longer leak mutations into the response.
- Add `error_type` field on `DeliveryResponse` populated with
  `type(exc).__name__` on transport failures. `slack_delivery._post_direct`
  threads it back into the exception log so on-call can distinguish
  `TimeoutError` from `ConnectionError` at a glance — restores the
  pre-refactor log shape that Tracer-Cloud#864 had dropped.
- Add `TestDelegatesToSharedTransport` to `tests/utils/test_slack_delivery.py`,
  mirroring the regression class on the discord/telegram test files.
  Pins that `slack_delivery` does not import `httpx` and that all four
  code paths (`_call_reactions_api`, `_post_direct`, `_post_via_webapp`,
  `_post_via_incoming_webhook`) route through `delivery_transport.post_json`.
- Add focused tests for the immutable-data, error_type, and slack
  exc_type-log behaviours (+17 tests; 3208 pass / 2 skipped / 1 xfail).

Tighten `_discord_error_from_data` signature to `Mapping[str, Any]` so
mypy stays clean against the new read-only `data` type.
@mayankbharati-ops
Copy link
Copy Markdown
Contributor Author

@muddlebee — pushed 3f30698 addressing all three Greptile P2 findings:

1. DeliveryResponse.data mutable despite frozen=True
Wrapped data in MappingProxyType inside __post_init__, with the type annotation tightened to collections.abc.Mapping[str, Any]. Mutating now raises TypeError, and caller-supplied dicts can no longer leak mutations into the response (the proxy wraps a defensive copy). Tightened _discord_error_from_data signature to Mapping[str, Any] so mypy stays clean against the new read-only type.

2. Exception type dropped from _post_direct log
Added an error_type: str field on DeliveryResponse, populated with type(exc).__name__ on the transport's exception path. slack_delivery._post_direct threads it back into the log line as exc_type=<ClassName> — distinguishes TimeoutError vs ConnectionError at a glance without parsing the error string. Public return shape unchanged.

3. Missing TestDelegatesToSharedTransport in test_slack_delivery.py
Added the regression class to mirror Discord/Telegram. Pins that slack_delivery does not import httpx directly and that all four code paths (_call_reactions_api, _post_direct, _post_via_webapp, _post_via_incoming_webhook) route through delivery_transport.post_json.

Test coverage: +17 new tests covering the read-only-data invariant, caller-dict isolation, default-mapping independence, dict-equality preservation, error_type per-exception-class population, the new exc_type= log line, and per-code-path delegation. Full suite: 3208 pass / 2 skipped / 1 xfailed (was 3191). Lint, format-check, and mypy app/utils/ all clean. CI green.

Copy link
Copy Markdown
Collaborator

@muddlebee muddlebee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nits only..

Comment thread app/utils/slack_delivery.py Outdated
if not response.ok:
logger.error(
"[slack] Direct post exception channel=%s thread_ts=%s "
"exc_type=%s detail=%s (caller may attempt fallback)",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the log key changed from type=%s to exc_type=%s. If any log parser keys off type=, it might miss this. Consider keeping type=%s unless you intentionally want to rename it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any particular reason behind this @mayankbharati-ops ? exc_type

Comment thread app/utils/discord_delivery.py Outdated
def _discord_auth_headers(bot_token: str) -> dict[str, str]:
return {
"Authorization": f"Bot {bot_token}",
"Content-Type": "application/json; charset=utf-8",
Copy link
Copy Markdown
Collaborator

@muddlebee muddlebee Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: httpx.post(..., json=payload) already injects Content-Type: application/json, so this header is redundant.

Comment thread app/utils/delivery_transport.py Outdated
data: Mapping[str, Any] = field(default_factory=dict)
text: str = ""
error: str = ""
error_type: str = ""
Copy link
Copy Markdown
Collaborator

@muddlebee muddlebee Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: consider renaming error_type to exc_type so it matches the Slack log field name.

@muddlebee muddlebee self-requested a review April 27, 2026 15:19
Copy link
Copy Markdown
Collaborator

@muddlebee muddlebee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls address the above

… drop redundant header, fix CodeQL

- slack_delivery `_post_direct` exception log key reverted to ``type=%s``
  (matching the exact pre-refactor format ``type=%s channel=%s
  thread_ts=%s detail=%s``) so existing log parsers that key off
  ``type=`` keep working. Per @muddlebee's nit on PR Tracer-Cloud#952.
- DeliveryResponse field renamed ``error_type`` → ``exc_type``. Pythonic
  abbreviation for "exception type"; can't be ``type`` because that
  shadows the builtin. Field/log-key are intentionally distinct: the
  field is the Python attribute, the log key matches the legacy format.
- Drop ``Content-Type: application/json; charset=utf-8`` from
  ``_slack_bearer_headers`` and ``_discord_auth_headers``. ``httpx.post``
  already sets ``Content-Type: application/json`` automatically when the
  request uses the ``json=`` kwarg, so the explicit header was
  redundant. No behavioural change for Slack/Discord (UTF-8 is httpx's
  default encoding and neither provider parses the charset suffix).
- Fix CodeQL py/import-and-import-from alerts on tests Tracer-Cloud#503/Tracer-Cloud#504. The
  ``test_module_does_not_import_httpx`` regression test in each
  delivery test file was importing the module under test via both
  ``import X as mod`` and ``from X import Y`` styles. Switched to a
  single ``from app.utils import <module>`` style so the same module
  is no longer dual-imported.

Test count unchanged (3208 pass / 2 skipped / 1 xfail). Lint, format,
and ``mypy app/utils/`` clean.
@mayankbharati-ops
Copy link
Copy Markdown
Contributor Author

@muddlebee — pushed 059958a addressing all four nits + the two CodeQL alerts. Replying inline since they all interact:

1. slack_delivery.py:221 — log key type= vs exc_type=
Good catch on the log-parser concern. Reverted to the exact pre-refactor format: "...type=%s channel=%s thread_ts=%s detail=%s...". No diff vs main for that log line now, so any parser keying off type= keeps working.

2. delivery_transport.py:62 — rename error_typeexc_type
Done. DeliveryResponse.exc_type (Pythonic short form for "exception type"). Couldn't literally name the field type because it shadows the builtin, so the field name is exc_type while the slack log key is type= — the two are intentionally different: the field is the Python attribute, the log key matches the legacy format. Added a docstring note explaining the distinction.

3. discord_delivery.py:17 — redundant Content-Type header
Right, httpx.post(json=payload) already sends Content-Type: application/json and serializes UTF-8. Dropped from both _discord_auth_headers and _slack_bearer_headers — both helpers now return just the auth header. No behavioural change (verified via the per-callsite tests + full delivery suite).

4. slack_delivery.py:221 (follow-up) — "any particular reason behind exc_type?"
Pure naming choice when adding the new DeliveryResponse field. No log-format motivation. With the rename in (2) and the log key reverted in (1), there's no longer an exc_type token in any log message — the slack log emits type=… as before.

5. CodeQL #503 / #504import and import from on the same module
The new test_module_does_not_import_httpx regression tests on the discord/telegram (and the new slack) test files were doing import app.utils.X as mod while the file already had from app.utils.X import (…) at the top. Switched to a single from import style: each test file now does from app.utils import <module> once, and the regression test reuses that reference (assert not hasattr(<module>, "httpx")). Both CodeQL alerts now resolve in this run.

Verification: 3208 pass / 2 skipped / 1 xfailed (unchanged). ruff check, ruff format --check, mypy app/utils/ all clean. CI: quality, typecheck, test, Analyze (python), CodeQL — all SUCCESS.

…ng without it

Live e2e probe against real ``slack.com/api/chat.postMessage`` revealed
that dropping the explicit ``Content-Type: application/json; charset=utf-8``
header (per the previous nit-fix commit) caused Slack to add a
``missing_charset`` entry to ``response_metadata.warnings`` on every
request. httpx alone sets only the bare ``application/json`` for ``json=``
kwargs, but Slack's docs explicitly recommend the charset suffix
(https://api.slack.com/web#posting_json) and emit the warning otherwise.

Restored the ``charset=utf-8`` header on ``_slack_bearer_headers`` with
an inline comment explaining the reason. ``_discord_auth_headers`` stays
auth-only (Discord does not emit this warning, and its docs do not
require the charset suffix).

Pinned via direct header assertions on the two existing slack tests that
already captured headers (``test_sends_correct_url_and_headers`` for the
reactions API, ``test_sends_thread_reply_payload`` for chat.postMessage),
so a future regression that re-drops the charset will fail loudly.

Verified live:
- direct ``chat.postMessage`` probe now returns ``warnings=[]`` (was
  ``['missing_charset']`` before this commit).
- 38/38 live checks against real Slack / Discord / Telegram pass.
- 3208 unit tests pass; lint / format / mypy clean.
@mayankbharati-ops
Copy link
Copy Markdown
Contributor Author

@muddlebee — ran a deep live end-to-end probe against the real Slack / Discord / Telegram APIs (no mocks, real HTTPS, real provider error shapes), and the live test caught a real regression introduced by my previous nit-fix commit. Pushed 9f08657 to fix it.

What the live probe found

# Probe Result
1 Real success: httpbin.org/post → ok=True, status 200, parsed JSON, MappingProxyType data immutable on the wire object
2 Real DNS failure: *.invalidok=False, status_code=0, exc_type="ConnectError"
3 Real Slack chat.postMessage w/ bogus token → slack_error=invalid_auth parsed correctly. But: response_metadata.warnings=['missing_charset']
4 Real Slack reactions.add w/ bogus token → returns False, no crash
5 Real Slack incoming webhook (bad URL) → returns False on real 4xx
6 Real Discord /channels/.../messages w/ bogus token → real 401 body {"message": "401: Unauthorized"} parsed via _discord_error_from_data
7 Real Discord create-thread w/ bogus token → real 401 surfaced cleanly
8 Real Telegram sendMessage w/ bogus token → 401 parsed, bot token redacted from error string
9 httpx logger filter scrubs bot<token>/sendMessagebot<redacted>/sendMessage on real call
10 All three modules not hasattr(mod, "httpx") at runtime
11 On-the-wire Content-Type: application/json confirmed via httpbin echo

The regression and the fix (9f08657)

The previous commit dropped Content-Type: application/json; charset=utf-8 from _slack_bearer_headers because httpx auto-sets Content-Type: application/json for json= kwargs. But httpx omits the charset=utf-8 suffix, and Slack's API (docs) explicitly recommends it — without it, response_metadata.warnings carries ['missing_charset'] on every Slack POST. The request still succeeds at the auth layer (the invalid_auth came back fine), so it didn't show up in any unit test, but it's a behavioural regression vs main.

Fix: restored the explicit Content-Type: application/json; charset=utf-8 on _slack_bearer_headers only (Slack-specific). _discord_auth_headers stays auth-only — Discord does not emit this warning and its docs do not require the charset suffix. Inline comments on both helpers explain the asymmetry.

Re-verified live: direct probe to chat.postMessage now returns warnings=[] on the live API. 38/38 real-network checks pass.

Pinned via tests: the existing test_sends_correct_url_and_headers (reactions) and test_sends_thread_reply_payload (chat.postMessage) tests now assert on the captured Content-Type: application/json; charset=utf-8 header, so a future regression that drops the charset will fail loudly in CI.

Final verification

  • 3208 pass / 2 skipped / 1 xfailed
  • ruff check, ruff format --check, mypy app/utils/ clean
  • 38/38 live checks against real Slack / Discord / Telegram APIs
  • CI: quality / typecheck / test / Analyze (python) / CodeQL — all SUCCESS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extract one shared HTTP-post helper for Slack, Discord, and Telegram delivery

3 participants