-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwebhook_test.go
More file actions
115 lines (101 loc) · 3.92 KB
/
Copy pathwebhook_test.go
File metadata and controls
115 lines (101 loc) · 3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package seatlayer
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"strings"
"testing"
)
const testSecret = "whsec_test"
func sign(payload, secret string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
return "sha256=" + hex.EncodeToString(mac.Sum(nil))
}
func TestVerifyWebhookAcceptsSignedDelivery(t *testing.T) {
payload := `{"type":"booking.created","occurrenceId":"occ_1"}`
event, err := VerifyWebhook([]byte(payload), sign(payload, testSecret), testSecret)
if err != nil {
t.Fatalf("VerifyWebhook: %v", err)
}
if event["type"] != "booking.created" {
t.Fatalf("event = %v", event)
}
}
func TestVerifyWebhookRejectsReserialisedBody(t *testing.T) {
// The classic integration bug: decoding the body and re-encoding it changes
// the bytes, so the signature no longer matches.
//
// The mechanism is Go-specific. encoding/json marshals a map with its keys
// SORTED, while a real delivery arrives in the order we serialised it —
// deliveryId, occurrenceId, event, at. Round-tripping therefore reorders it.
// (An alphabetical payload would survive the round trip by coincidence, which
// is exactly why this test uses a realistic delivery shape instead.)
original := `{"deliveryId":"d_1","occurrenceId":"occ_1","event":"booking.created","at":1754006400000}`
var decoded map[string]any
if err := json.Unmarshal([]byte(original), &decoded); err != nil {
t.Fatalf("setup: %v", err)
}
reserialised, _ := json.Marshal(decoded)
if string(reserialised) == original {
t.Fatal("setup: re-serialising did not change the bytes, so this test proves nothing")
}
_, err := VerifyWebhook(reserialised, sign(original, testSecret), testSecret)
if !errors.Is(err, ErrWebhookVerification) {
t.Fatalf("want ErrWebhookVerification, got %v", err)
}
}
func TestVerifyWebhookRejectsWrongSecret(t *testing.T) {
payload := `{"ok":true}`
_, err := VerifyWebhook([]byte(payload), sign(payload, "whsec_other"), testSecret)
if !errors.Is(err, ErrWebhookVerification) || !strings.Contains(err.Error(), "did not match") {
t.Fatalf("want a signature mismatch, got %v", err)
}
}
func TestVerifyWebhookRejectsMissingHeader(t *testing.T) {
_, err := VerifyWebhook([]byte(`{}`), "", testSecret)
if !errors.Is(err, ErrWebhookVerification) {
t.Fatalf("want ErrWebhookVerification, got %v", err)
}
}
func TestVerifyWebhookRejectsUnknownScheme(t *testing.T) {
_, err := VerifyWebhook([]byte(`{}`), "md5=abc", testSecret)
if !strings.Contains(err.Error(), "unsupported signature format") {
t.Fatalf("want an unsupported-format error, got %v", err)
}
}
func TestVerifyWebhookRejectsTruncatedSignature(t *testing.T) {
payload := `{"ok":true}`
truncated := sign(payload, testSecret)[:20]
if _, err := VerifyWebhook([]byte(payload), truncated, testSecret); !errors.Is(err, ErrWebhookVerification) {
t.Fatalf("want ErrWebhookVerification, got %v", err)
}
}
func TestVerifyWebhookRequiresSecret(t *testing.T) {
payload := `{}`
_, err := VerifyWebhook([]byte(payload), sign(payload, testSecret), "")
if !strings.Contains(err.Error(), "signing secret is required") {
t.Fatalf("want a missing-secret error, got %v", err)
}
}
func TestVerifyWebhookReportsUnparseableBodyDistinctly(t *testing.T) {
payload := `not json`
_, err := VerifyWebhook([]byte(payload), sign(payload, testSecret), testSecret)
if !strings.Contains(err.Error(), "not valid JSON") {
t.Fatalf("want a JSON error distinct from a signature failure, got %v", err)
}
}
func TestVerifyWebhookHandlesNonASCII(t *testing.T) {
// A venue named "Théâtre" must verify — this is where a string/byte mismatch
// in the HMAC would show up.
payload := `{"venue":"Théâtre du Châtelet — 日本"}`
event, err := VerifyWebhook([]byte(payload), sign(payload, testSecret), testSecret)
if err != nil {
t.Fatalf("VerifyWebhook: %v", err)
}
if event["venue"] != "Théâtre du Châtelet — 日本" {
t.Fatalf("venue = %v", event["venue"])
}
}