package mockalertreceiver import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "io" "log/slog" "net/http" "net/http/httptest" "strconv" "testing" "time" ) func TestReceiverVerifiesAndRecordsSignedDelivery(t *testing.T) { t.Parallel() secret := "test-alert-receiver-secret-at-least-32-bytes" now := time.Unix(1_800_000_000, 0).UTC() server, err := New(Options{Secret: secret, ClockSkew: 5 * time.Minute}, slog.New(slog.NewTextHandler(io.Discard, nil))) if err != nil { t.Fatal(err) } server.now = func() time.Time { return now } httpServer := httptest.NewServer(server.Handler()) defer httpServer.Close() body, _ := json.Marshal(envelope{EventID: "10000000-0000-0000-0000-000000000001", EventType: "alert.webhook_test_requested", Data: json.RawMessage(`{"test_ref":"10000000-0000-0000-0000-000000000099"}`)}) timestamp := strconv.FormatInt(now.Unix(), 10) request, _ := http.NewRequest(http.MethodPost, httpServer.URL+"/webhooks/alerts", bytes.NewReader(body)) request.Header.Set("X-Voice-Event-ID", "10000000-0000-0000-0000-000000000001") request.Header.Set("X-Voice-Timestamp", timestamp) request.Header.Set("X-Voice-Signature", testSignature([]byte(secret), timestamp, body)) response, err := http.DefaultClient.Do(request) if err != nil { t.Fatal(err) } response.Body.Close() if response.StatusCode != http.StatusNoContent { t.Fatalf("delivery status=%d", response.StatusCode) } response, err = http.Get(httpServer.URL + "/events?test_ref=10000000-0000-0000-0000-000000000099") if err != nil { t.Fatal(err) } defer response.Body.Close() var result struct { Items []Event `json:"items"` } if err = json.NewDecoder(response.Body).Decode(&result); err != nil || len(result.Items) != 1 || result.Items[0].EventType != "alert.webhook_test_requested" { t.Fatalf("events=%+v err=%v", result.Items, err) } invalid, _ := http.NewRequest(http.MethodPost, httpServer.URL+"/webhooks/alerts", bytes.NewReader(body)) invalid.Header.Set("X-Voice-Event-ID", "10000000-0000-0000-0000-000000000001") invalid.Header.Set("X-Voice-Timestamp", timestamp) invalid.Header.Set("X-Voice-Signature", "v1=00") response, err = http.DefaultClient.Do(invalid) if err != nil { t.Fatal(err) } response.Body.Close() if response.StatusCode != http.StatusUnauthorized { t.Fatalf("invalid signature status=%d", response.StatusCode) } } func testSignature(secret []byte, timestamp string, body []byte) string { mac := hmac.New(sha256.New, secret) _, _ = mac.Write([]byte(timestamp + ".")) _, _ = mac.Write(body) return "v1=" + hex.EncodeToString(mac.Sum(nil)) }