@@ -5,6 +5,10 @@ package services
55
66import (
77 "context"
8+ "crypto/hmac"
9+ "crypto/sha256"
10+ "encoding/hex"
11+ "io"
812 "net/http"
913 "net/http/httptest"
1014 "strings"
@@ -21,6 +25,89 @@ func newTestWebhookService(url string) *webhookClient {
2125 return webhookClient
2226}
2327
28+ func TestWebhookClient_CreateRequest_HMACSignature (t * testing.T ) {
29+ const secret = "test-webhook-secret"
30+ body := `{"event":"dependencyVulnerabilities","severity":"high"}`
31+
32+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
33+ receivedBody , err := io .ReadAll (r .Body )
34+ require .NoError (t , err )
35+
36+ mac := hmac .New (sha256 .New , []byte (secret ))
37+ _ , _ = mac .Write (receivedBody )
38+ expectedSignature := "sha256=" + hex .EncodeToString (mac .Sum (nil ))
39+
40+ assert .Equal (t , body , string (receivedBody ))
41+ assert .Equal (t , expectedSignature , r .Header .Get ("X-Hub-Signature-256" ))
42+ assert .Equal (t , secret , r .Header .Get ("X-Webhook-Secret" ))
43+ w .WriteHeader (http .StatusOK )
44+ }))
45+ defer server .Close ()
46+
47+ secretValue := secret
48+ client := NewWebhookService (server .URL , & secretValue )
49+ client .retryDelays = []time.Duration {0 , 0 , 0 }
50+
51+ resp , err := client .CreateRequest (context .Background (), http .MethodPost , server .URL , strings .NewReader (body ))
52+ require .NoError (t , err )
53+ require .NotNil (t , resp )
54+ defer resp .Body .Close ()
55+ assert .Equal (t , http .StatusOK , resp .StatusCode )
56+ }
57+
58+ func TestWebhookClient_CreateRequest_DoesNotSignWithoutSecret (t * testing.T ) {
59+ emptySecret := ""
60+ cases := []struct {
61+ name string
62+ secret * string
63+ }{
64+ {name : "nil secret" , secret : nil },
65+ {name : "empty secret" , secret : & emptySecret },
66+ }
67+
68+ for _ , tc := range cases {
69+ t .Run (tc .name , func (t * testing.T ) {
70+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
71+ assert .Empty (t , r .Header .Get ("X-Hub-Signature-256" ))
72+ assert .Empty (t , r .Header .Get ("X-Webhook-Secret" ))
73+ w .WriteHeader (http .StatusOK )
74+ }))
75+ defer server .Close ()
76+
77+ client := NewWebhookService (server .URL , tc .secret )
78+ client .retryDelays = []time.Duration {0 , 0 , 0 }
79+ resp , err := client .CreateRequest (context .Background (), http .MethodPost , server .URL , strings .NewReader (`{"test":"data"}` ))
80+ require .NoError (t , err )
81+ require .NotNil (t , resp )
82+ defer resp .Body .Close ()
83+ })
84+ }
85+ }
86+
87+ func TestWebhookClient_CreateRequest_ReusesSignatureAcrossRetries (t * testing.T ) {
88+ const secret = "test-webhook-secret"
89+ body := `{"event":"test"}`
90+ var signatures []string
91+
92+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
93+ signatures = append (signatures , r .Header .Get ("X-Hub-Signature-256" ))
94+ w .WriteHeader (http .StatusInternalServerError )
95+ }))
96+ defer server .Close ()
97+
98+ secretValue := secret
99+ client := NewWebhookService (server .URL , & secretValue )
100+ client .retryDelays = []time.Duration {0 , 0 , 0 }
101+ resp , err := client .CreateRequest (context .Background (), http .MethodPost , server .URL , strings .NewReader (body ))
102+ require .NoError (t , err )
103+ require .NotNil (t , resp )
104+ defer resp .Body .Close ()
105+
106+ require .Len (t , signatures , 3 )
107+ assert .Equal (t , signatures [0 ], signatures [1 ])
108+ assert .Equal (t , signatures [1 ], signatures [2 ])
109+ }
110+
24111func TestWebhookClient_CreateRequest_RetryLogic (t * testing.T ) {
25112 t .Run ("should succeed on first attempt when request is successful" , func (t * testing.T ) {
26113 attemptCount := 0
0 commit comments