package forgejo import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/hex" "net/http" "net/http/httptest" "testing" ) func computeHMAC(body []byte, secret string) string { mac := hmac.New(sha256.New, []byte(secret)) mac.Write(body) return hex.EncodeToString(mac.Sum(nil)) } func TestVerifyWebhookSignature_Valid(t *testing.T) { secret := "test-webhook-secret" payload := []byte(`{"action":"opened","issue":{"number":1,"title":"Test","state":"open"}}`) signature := computeHMAC(payload, secret) req := httptest.NewRequest(http.MethodPost, "/webhook", bytes.NewReader(payload)) req.Header.Set("X-Forgejo-Signature", signature) body, err := VerifyWebhookSignature(req, secret) if err != nil { t.Fatalf("expected no error, got: %v", err) } if !bytes.Equal(body, payload) { t.Errorf("expected body %q, got %q", payload, body) } } func TestVerifyWebhookSignature_InvalidSignature(t *testing.T) { secret := "test-webhook-secret" payload := []byte(`{"action":"opened"}`) req := httptest.NewRequest(http.MethodPost, "/webhook", bytes.NewReader(payload)) req.Header.Set("X-Forgejo-Signature", "deadbeef00000000000000000000000000000000000000000000000000000000") _, err := VerifyWebhookSignature(req, secret) if err == nil { t.Fatal("expected error for invalid signature, got nil") } } func TestVerifyWebhookSignature_MissingHeader(t *testing.T) { payload := []byte(`{"action":"opened"}`) req := httptest.NewRequest(http.MethodPost, "/webhook", bytes.NewReader(payload)) // No X-Forgejo-Signature header set _, err := VerifyWebhookSignature(req, "some-secret") if err == nil { t.Fatal("expected error for missing signature header, got nil") } expected := "missing X-Forgejo-Signature header" if err.Error() != expected { t.Errorf("expected error message %q, got %q", expected, err.Error()) } } func TestVerifyWebhookSignature_WrongSecret(t *testing.T) { payload := []byte(`{"action":"opened"}`) signature := computeHMAC(payload, "correct-secret") req := httptest.NewRequest(http.MethodPost, "/webhook", bytes.NewReader(payload)) req.Header.Set("X-Forgejo-Signature", signature) _, err := VerifyWebhookSignature(req, "wrong-secret") if err == nil { t.Fatal("expected error for wrong secret, got nil") } } func TestParseWebhookPayload_Valid(t *testing.T) { data := []byte(`{"action":"opened","issue":{"number":42,"title":"Bug report","state":"open"}}`) payload, err := ParseWebhookPayload(data) if err != nil { t.Fatalf("expected no error, got: %v", err) } if payload.Action != "opened" { t.Errorf("expected action %q, got %q", "opened", payload.Action) } if payload.Issue.Number != 42 { t.Errorf("expected issue number 42, got %d", payload.Issue.Number) } if payload.Issue.Title != "Bug report" { t.Errorf("expected issue title %q, got %q", "Bug report", payload.Issue.Title) } if payload.Issue.State != "open" { t.Errorf("expected issue state %q, got %q", "open", payload.Issue.State) } } func TestParseWebhookPayload_InvalidJSON(t *testing.T) { data := []byte(`{not valid json}`) _, err := ParseWebhookPayload(data) if err == nil { t.Fatal("expected error for invalid JSON, got nil") } } func TestParseWebhookPayload_EmptyObject(t *testing.T) { data := []byte(`{}`) payload, err := ParseWebhookPayload(data) if err != nil { t.Fatalf("expected no error for empty object, got: %v", err) } if payload.Action != "" { t.Errorf("expected empty action, got %q", payload.Action) } if payload.Issue.Number != 0 { t.Errorf("expected issue number 0, got %d", payload.Issue.Number) } }