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
1 change: 1 addition & 0 deletions backend/tests/unit/external/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Unit tests for external and third-party library behavior that Infrahub depends on."""
30 changes: 30 additions & 0 deletions backend/tests/unit/external/test_httpx_serialization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import json

import httpx


def test_httpx_json_serialization_matches_signature_format() -> None:
"""Guard against httpx changing its JSON serialization.

The webhook signature is computed over compact JSON (no spaces).
If httpx ever changes its serializer this test will break before.
"""
payload = {
"data": {"id": "abc123", "kind": "BuiltinTag", "display_label": "my tag"},
"event_type": "infrahub.node.created",
"branch": "main",
}

# What our signature code signs (compact JSON, deterministic key order)
signed_body = json.dumps(payload, separators=(",", ":")).encode()

# What httpx will actually put on the wire
request = httpx.Request("POST", "http://test.com", json=payload)
httpx_body = request.content

assert httpx_body == signed_body, (
f"httpx JSON serialization diverged from compact json.dumps.\n"
f" signed : {signed_body!r}\n"
f" httpx : {httpx_body!r}\n"
"Webhook receivers will reject signatures if these differ."
)
30 changes: 30 additions & 0 deletions backend/tests/unit/webhook/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,33 @@ def test_standard_webhook_header() -> None:
"webhook-signature": "v1,BQN9AgPA4evuGDChi9VKxNKRwediIXsAQz8hVHMfKNg=",
"webhook-timestamp": "1740656629",
}


def test_webhook_signature_with_payload() -> None:
"""Signature is computed on compact JSON of the payload, not str(dict) or spaced JSON.

Hardcoded expected value catches regressions if the serialization format changes.
"""
webhook = StandardWebhook(
name="test",
url="http://test.com",
event_type="test",
validate_certificates=True,
shared_key="my-webhook-secret",
)
webhook._payload = {
"data": {"id": "abc123", "kind": "BuiltinTag", "display_label": "my tag"},
"event_type": "infrahub.node.created",
"branch": "main",
}
test_id = UUID("217b4ebc-b84f-4736-b1ee-222182aed371")
time1 = Timestamp("2025-02-27T11:43:49.064807Z")
webhook._assign_headers(uuid=test_id, at=time1)

assert webhook._headers == {
"Accept": "application/json",
"Content-Type": "application/json",
"webhook-id": "msg_217b4ebcb84f4736b1ee222182aed371",
"webhook-timestamp": "1740656629",
"webhook-signature": "v1,5JQxZW3lMNdaSnofcSV0Y3krxQ7aZI7EyThUqHVDGc4=",
}
Loading