package webhook_test import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "superwork-tui/internal/webhook" ) func post(t *testing.T, srv *webhook.Server, path, eventHeader, body string) *httptest.ResponseRecorder { t.Helper() req := httptest.NewRequest(http.MethodPost, path, bytes.NewBufferString(body)) req.Header.Set("X-Gitea-Event", eventHeader) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() srv.ServeHTTP(w, req) return w } func TestServer_PREvent(t *testing.T) { var got webhook.Event srv := webhook.NewServer(func(e webhook.Event) { got = e }) payload := `{"action":"opened","pull_request":{"number":7,"html_url":"http://x/pr/7","head":{"ref":"feat/my-branch"},"title":"My PR","body":"Closes #3"}}` w := post(t, srv, "/webhook", "pull_request", payload) if w.Code != 200 { t.Fatalf("want 200, got %d", w.Code) } pr, ok := got.(webhook.PrEvent) if !ok { t.Fatalf("want PrEvent, got %T", got) } if pr.Action != "opened" { t.Errorf("action: want opened, got %q", pr.Action) } if pr.PR != "7" { t.Errorf("pr: want 7, got %q", pr.PR) } if pr.Branch != "feat/my-branch" { t.Errorf("branch: %q", pr.Branch) } if pr.HTMLUrl != "http://x/pr/7" { t.Errorf("htmlUrl: %q", pr.HTMLUrl) } if pr.Title != "My PR" { t.Errorf("title: %q", pr.Title) } if pr.Body != "Closes #3" { t.Errorf("body: %q", pr.Body) } if pr.IssueNumber != 0 { t.Errorf("issueNumber should be 0 on canonical route, got %d", pr.IssueNumber) } } func TestServer_PREventLegacyRoute(t *testing.T) { var got webhook.Event srv := webhook.NewServer(func(e webhook.Event) { got = e }) payload := `{"action":"opened","pull_request":{"number":7,"html_url":"http://x/pr/7","head":{"ref":"feat/x"},"title":"T","body":""}}` w := post(t, srv, "/webhook/42", "pull_request", payload) if w.Code != 200 { t.Fatalf("want 200, got %d", w.Code) } pr := got.(webhook.PrEvent) if pr.IssueNumber != 42 { t.Errorf("issueNumber: want 42, got %d", pr.IssueNumber) } } func TestServer_IssueEvent(t *testing.T) { var got webhook.Event srv := webhook.NewServer(func(e webhook.Event) { got = e }) payload := `{"action":"opened","issue":{"number":5,"html_url":"http://x/issues/5","title":"Issue title","body":"some body"}}` w := post(t, srv, "/webhook", "issues", payload) if w.Code != 200 { t.Fatalf("want 200, got %d", w.Code) } ev, ok := got.(webhook.IssueEvent) if !ok { t.Fatalf("want IssueEvent, got %T", got) } if ev.Action != "opened" { t.Errorf("action: %q", ev.Action) } if ev.IssueNumber != 5 { t.Errorf("issueNumber: %d", ev.IssueNumber) } if ev.Title != "Issue title" { t.Errorf("title: %q", ev.Title) } if ev.Body != "some body" { t.Errorf("body: %q", ev.Body) } if ev.HTMLUrl != "http://x/issues/5" { t.Errorf("htmlUrl: %q", ev.HTMLUrl) } } func TestServer_IssueCommentEvent_OnPR(t *testing.T) { var got webhook.Event srv := webhook.NewServer(func(e webhook.Event) { got = e }) // issue.pull_request non-null → prNumber = issue.number payload := `{"action":"created","issue":{"number":9,"pull_request":{"merged_at":null}},"comment":{"body":"","html_url":"http://x/c/1"}}` w := post(t, srv, "/webhook", "issue_comment", payload) if w.Code != 200 { t.Fatalf("want 200, got %d", w.Code) } ev, ok := got.(webhook.IssueCommentEvent) if !ok { t.Fatalf("want IssueCommentEvent, got %T", got) } if ev.IssueNumber != 9 { t.Errorf("issueNumber: %d", ev.IssueNumber) } if ev.PRNumber != 9 { t.Errorf("prNumber: want 9 (PR comment), got %d", ev.PRNumber) } if ev.CommentBody != "" { t.Errorf("commentBody: %q", ev.CommentBody) } } func TestServer_IssueCommentEvent_NotOnPR(t *testing.T) { var got webhook.Event srv := webhook.NewServer(func(e webhook.Event) { got = e }) payload := `{"action":"created","issue":{"number":9},"comment":{"body":"hello","html_url":"http://x/c/2"}}` w := post(t, srv, "/webhook", "issue_comment", payload) if w.Code != 200 { t.Fatalf("want 200, got %d", w.Code) } ev := got.(webhook.IssueCommentEvent) if ev.PRNumber != 0 { t.Errorf("prNumber: want 0 (not a PR), got %d", ev.PRNumber) } } func TestServer_PushEvent(t *testing.T) { var got webhook.Event srv := webhook.NewServer(func(e webhook.Event) { got = e }) payload := `{"ref":"refs/heads/main"}` w := post(t, srv, "/webhook", "push", payload) if w.Code != 200 { t.Fatalf("want 200, got %d", w.Code) } ev, ok := got.(webhook.PushEvent) if !ok { t.Fatalf("want PushEvent, got %T", got) } if ev.Branch != "main" { t.Errorf("branch: want main, got %q", ev.Branch) } } func TestServer_MalformedJSON_Returns400(t *testing.T) { called := false srv := webhook.NewServer(func(e webhook.Event) { called = true }) w := post(t, srv, "/webhook", "pull_request", "{bad json") if w.Code != 400 { t.Fatalf("want 400, got %d", w.Code) } if called { t.Error("OnEvent should not be called for malformed JSON") } } func TestServer_WrongMethod_Returns405(t *testing.T) { srv := webhook.NewServer(func(e webhook.Event) {}) req := httptest.NewRequest(http.MethodGet, "/webhook", nil) w := httptest.NewRecorder() srv.ServeHTTP(w, req) if w.Code != 405 { t.Fatalf("want 405, got %d", w.Code) } } func TestServer_UnknownPath_Returns404(t *testing.T) { srv := webhook.NewServer(func(e webhook.Event) {}) req := httptest.NewRequest(http.MethodPost, "/unknown", nil) w := httptest.NewRecorder() srv.ServeHTTP(w, req) if w.Code != 404 { t.Fatalf("want 404, got %d", w.Code) } } func TestServer_NonMatchingEventHeader_Returns200_NoEvent(t *testing.T) { called := false srv := webhook.NewServer(func(e webhook.Event) { called = true }) // push event but missing ref → null parse → no event emitted, still 200 w := post(t, srv, "/webhook", "push", `{"action":"opened"}`) if w.Code != 200 { t.Fatalf("want 200, got %d", w.Code) } if called { t.Error("OnEvent should not be called when event doesn't parse") } } func TestServer_ResponseBodyIsJSON(t *testing.T) { srv := webhook.NewServer(func(e webhook.Event) {}) payload := `{"action":"opened","pull_request":{"number":1,"html_url":"http://x","head":{"ref":"x"},"title":"","body":""}}` w := post(t, srv, "/webhook", "pull_request", payload) var resp map[string]any if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("response not JSON: %v", err) } if resp["ok"] != true { t.Errorf("want ok:true, got %v", resp["ok"]) } }