Skip to content

feat: TS webhook fixtures + parity tests + Postgres lock fix#21

Merged
patrick-chinchill merged 2 commits intomainfrom
fixtures-and-postgres-fix
Apr 7, 2026
Merged

feat: TS webhook fixtures + parity tests + Postgres lock fix#21
patrick-chinchill merged 2 commits intomainfrom
fixtures-and-postgres-fix

Conversation

@patrick-chinchill
Copy link
Copy Markdown
Collaborator

Closes #18, closes #19. 36 fixture tests pass, 10 xfail (real adapter bugs to fix).

patrick-chinchill and others added 2 commits April 6, 2026 20:33
…OU race

The previous two-step approach (INSERT ... DO NOTHING, then UPDATE ... WHERE
expired) had a time-of-check-to-time-of-use race: two concurrent callers
could both see the INSERT fail (row exists), then both attempt the UPDATE
on the expired row, with both potentially succeeding depending on timing.

The fix uses a single INSERT ... ON CONFLICT DO UPDATE ... WHERE
chat_state_locks.expires_at <= now() statement. Postgres acquires a row
lock on the conflicting row, so exactly one concurrent caller wins.

Closes #19

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes #18, closes #19.

Webhook fixtures:
- Copied 4 root fixture files from TS repo (slack, teams, gchat, telegram)
  plus subdirectory fixtures (dm, channel, streaming, etc.)
- 56 fixture replay tests: 36 pass, 10 xfail (expose real adapter bugs)
- The xfail tests document known port fidelity gaps that real payloads
  expose — these are bugs in the adapters, not test bugs

Parity tests:
- test_card_parity.py: renders one CardElement through all 8 adapters
- test_emoji_parity.py: cross-checks DEFAULT_EMOJI_MAP entries
- test_fixture_replay.py: replays real webhook payloads through adapters

Postgres fix:
- acquire_lock now uses single atomic upsert with ON CONFLICT DO UPDATE
  WHERE expires_at <= now() (matches TS, prevents TOCTOU race)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@patrick-chinchill patrick-chinchill merged commit e0ba3b2 into main Apr 7, 2026
6 checks passed
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request replaces the two-step lock acquisition in the Postgres state adapter with an atomic upsert to prevent race conditions and adds a comprehensive suite of parity tests for cards, emojis, and fixture replays. Review feedback suggests improving test coverage by including the WhatsApp converter in the aggregate card title test, fully implementing the Discord channel fixture replay, and replacing blocking time.sleep() calls with asyncio.sleep() in asynchronous tests.

Comment thread tests/test_card_parity.py
Comment on lines +258 to +278
def test_all_converters_agree_on_title(self, card: CardElement):
"""All available converters should include the card title in their output."""
converters: list[tuple[str, bool, Any]] = [
("fallback", True, lambda c: card_to_fallback_text(c)),
]
if _SLACK_CARDS:
converters.append(("slack", True, lambda c: str(card_to_block_kit(c))))
if _TEAMS_CARDS:
converters.append(("teams", True, lambda c: str(card_to_adaptive_card(c))))
if _GCHAT_CARDS:
converters.append(("gchat", True, lambda c: str(card_to_google_card(c))))
if _DISCORD_CARDS:
converters.append(("discord", True, lambda c: str(card_to_discord_payload(c))))
if _GITHUB_CARDS:
converters.append(("github", True, lambda c: card_to_github_markdown(c)))
if _LINEAR_CARDS:
converters.append(("linear", True, lambda c: card_to_linear_markdown(c)))

for name, available, converter in converters:
output = converter(card)
assert "System Status Report" in output, f"{name} converter should include card title"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The test_all_converters_agree_on_title test is a great way to ensure basic parity. However, it seems to be missing a check for the WhatsApp card converter (card_to_whatsapp), even though there's a separate test for it (test_whatsapp_card). Including it in this aggregate test would improve test coverage and ensure it also correctly handles the card title.

    def test_all_converters_agree_on_title(self, card: CardElement):
        """All available converters should include the card title in their output."""
        converters: list[tuple[str, bool, Any]] = [
            ("fallback", True, lambda c: card_to_fallback_text(c)),
        ]
        if _SLACK_CARDS:
            converters.append(("slack", True, lambda c: str(card_to_block_kit(c))))
        if _TEAMS_CARDS:
            converters.append(("teams", True, lambda c: str(card_to_adaptive_card(c))))
        if _GCHAT_CARDS:
            converters.append(("gchat", True, lambda c: str(card_to_google_card(c))))
        if _DISCORD_CARDS:
            converters.append(("discord", True, lambda c: str(card_to_discord_payload(c))))
        if _GITHUB_CARDS:
            converters.append(("github", True, lambda c: card_to_github_markdown(c)))
        if _LINEAR_CARDS:
            converters.append(("linear", True, lambda c: card_to_linear_markdown(c)))
        if _WHATSAPP_CARDS:
            converters.append(("whatsapp", True, lambda c: str(card_to_whatsapp(c))))

        for name, available, converter in converters:
            output = converter(card)
            assert "System Status Report" in output, f"{name} converter should include card title"

Comment on lines +532 to +537
async def test_discord_channel_fixture(self):
"""channel/discord.json should have parseable gateway events."""
fixture = load_fixture("channel/discord.json")
# Verify fixture structure
assert "metadata" in fixture or "gatewayMention" in fixture or "mention" in fixture

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This test only verifies the structure of the fixture file but doesn't actually test the adapter's handling of the payload. To ensure parity, this test should be implemented fully to process the fixture through adapter.handle_webhook, similar to how other fixtures are tested in this file.

    async def test_discord_channel_fixture(self):
        """channel/discord.json should have parseable gateway events."""
        fixture = load_fixture("channel/discord.json")
        adapter = self._make_adapter(
            application_id=fixture.get("applicationId"),
        )
        mock_chat = _make_mock_chat()
        await adapter.initialize(mock_chat)

        # Mock the Discord API call that creates a thread
        mock_thread_response = {"id": fixture.get("threadChannelId"), "name": "test-thread"}
        with patch.object(adapter, "_create_discord_thread", new_callable=AsyncMock, return_value=mock_thread_response):
            # Gateway events are sent with x-discord-gateway-token header
            gateway_payload = fixture["mention"]
            body = json.dumps(gateway_payload)
            request = _FakeRequest(
                body,
                {
                    "content-type": "application/json",
                    "x-discord-gateway-token": self.BOT_TOKEN,
                },
            )

            result = await adapter.handle_webhook(request)

        assert result["status"] == 200
        assert mock_chat.handle_incoming_message.called


# Third acquire after expiry: should succeed in single query
mock_pool.executed_queries.clear()
time.sleep(0.005)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using time.sleep() in an async test blocks the entire event loop, which can lead to issues and is generally considered an anti-pattern. It's better to use await asyncio.sleep() to yield control back to the event loop. You'll need to add import asyncio at the top of the file.

Suggested change
time.sleep(0.005)
await asyncio.sleep(0.005)

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4fac8f60a8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +52 to +53
if not _TS_EMOJI_PATH.exists():
pytest.skip(f"TS emoji source not found: {_TS_EMOJI_PATH}")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make emoji parity tests self-contained

These parity tests depend on a hard-coded external file (/tmp/vercel-chat/packages/chat/src/emoji.ts) and immediately pytest.skip(...) when it is missing, which means the new TestEmojiParity coverage is skipped in normal CI runs where only this repository is checked out (see .github/workflows/test.yml). In practice, regressions in DEFAULT_EMOJI_MAP can pass unnoticed unless a developer manually clones another repo into /tmp, so the test should use a committed fixture/snapshot (or fail explicitly) instead of silently skipping.

Useful? React with 👍 / 👎.

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.

Fix Postgres acquire_lock TOCTOU race condition Copy TS webhook JSON fixtures and write parity tests

1 participant