package webhook import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "net/http" "net/http/httptest" "strings" "testing" "git.folja.dev/awayatan/yaju-is-always-watching-you/internal/forgejo" ) type fakeDispatcher struct { comments int pushes int } func (f *fakeDispatcher) HandleIssueComment(forgejo.IssueCommentPayload) error { f.comments++ return nil } func (f *fakeDispatcher) HandlePush(forgejo.PushPayload) error { f.pushes++ return nil } func sign(secret, body string) string { mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(body)) return hex.EncodeToString(mac.Sum(nil)) } func TestServeHTTP_RejectsBadSignature(t *testing.T) { fd := &fakeDispatcher{} h := NewHandler("secret", fd) body := `{}` req := httptest.NewRequest(http.MethodPost, "/webhook", strings.NewReader(body)) req.Header.Set("X-Forgejo-Event", "push") req.Header.Set("X-Forgejo-Signature", "deadbeef") rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("status = %d, want %d", rec.Code, http.StatusUnauthorized) } if fd.pushes != 0 { t.Fatalf("dispatcher should not run on bad signature, pushes = %d", fd.pushes) } } func TestServeHTTP_DispatchesPush(t *testing.T) { fd := &fakeDispatcher{} h := NewHandler("secret", fd) body := `{"ref":"refs/heads/main"}` req := httptest.NewRequest(http.MethodPost, "/webhook", strings.NewReader(body)) req.Header.Set("X-Forgejo-Event", "push") req.Header.Set("X-Forgejo-Signature", sign("secret", body)) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusNoContent { t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusNoContent, rec.Body.String()) } if fd.pushes != 1 { t.Fatalf("pushes = %d, want 1", fd.pushes) } } func TestServeHTTP_DispatchesIssueComment(t *testing.T) { fd := &fakeDispatcher{} h := NewHandler("secret", fd) body := `{"action":"created"}` req := httptest.NewRequest(http.MethodPost, "/webhook", strings.NewReader(body)) req.Header.Set("X-Forgejo-Event", "issue_comment") req.Header.Set("X-Forgejo-Signature", sign("secret", body)) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusNoContent { t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusNoContent, rec.Body.String()) } if fd.comments != 1 { t.Fatalf("comments = %d, want 1", fd.comments) } } func TestServeHTTP_IgnoresUnhandledEvent(t *testing.T) { fd := &fakeDispatcher{} h := NewHandler("secret", fd) body := `{}` req := httptest.NewRequest(http.MethodPost, "/webhook", strings.NewReader(body)) req.Header.Set("X-Forgejo-Event", "fork") req.Header.Set("X-Forgejo-Signature", sign("secret", body)) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusNoContent { t.Fatalf("status = %d, want %d", rec.Code, http.StatusNoContent) } if fd.comments != 0 || fd.pushes != 0 { t.Fatalf("dispatcher should not be called for unhandled events") } }