From 2ad117dcc05c4a8dd6b1ccd705a2355bdd7de8b4 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 07:09:12 +0000 Subject: [PATCH] feat: add org webhook CRUD Co-Authored-By: Virgil --- webhooks.go | 36 +++++++++++++ webhooks_test.go | 128 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/webhooks.go b/webhooks.go index 1cc8894..f057229 100644 --- a/webhooks.go +++ b/webhooks.go @@ -43,3 +43,39 @@ func (s *WebhookService) IterOrgHooks(ctx context.Context, org string) iter.Seq2 path := ResolvePath("/api/v1/orgs/{org}/hooks", pathParams("org", org)) return ListIter[types.Hook](ctx, s.client, path, nil) } + +// GetOrgHook returns a single webhook for an organisation. +func (s *WebhookService) GetOrgHook(ctx context.Context, org string, id int64) (*types.Hook, error) { + path := ResolvePath("/api/v1/orgs/{org}/hooks/{id}", pathParams("org", org, "id", int64String(id))) + var out types.Hook + if err := s.client.Get(ctx, path, &out); err != nil { + return nil, err + } + return &out, nil +} + +// CreateOrgHook creates a webhook for an organisation. +func (s *WebhookService) CreateOrgHook(ctx context.Context, org string, opts *types.CreateHookOption) (*types.Hook, error) { + path := ResolvePath("/api/v1/orgs/{org}/hooks", pathParams("org", org)) + var out types.Hook + if err := s.client.Post(ctx, path, opts, &out); err != nil { + return nil, err + } + return &out, nil +} + +// EditOrgHook updates an existing organisation webhook. +func (s *WebhookService) EditOrgHook(ctx context.Context, org string, id int64, opts *types.EditHookOption) (*types.Hook, error) { + path := ResolvePath("/api/v1/orgs/{org}/hooks/{id}", pathParams("org", org, "id", int64String(id))) + var out types.Hook + if err := s.client.Patch(ctx, path, opts, &out); err != nil { + return nil, err + } + return &out, nil +} + +// DeleteOrgHook deletes an organisation webhook. +func (s *WebhookService) DeleteOrgHook(ctx context.Context, org string, id int64) error { + path := ResolvePath("/api/v1/orgs/{org}/hooks/{id}", pathParams("org", org, "id", int64String(id))) + return s.client.Delete(ctx, path) +} diff --git a/webhooks_test.go b/webhooks_test.go index b6a973e..4e0ce05 100644 --- a/webhooks_test.go +++ b/webhooks_test.go @@ -155,6 +155,134 @@ func TestWebhookService_ListOrgHooks_Good(t *testing.T) { } } +func TestWebhookService_GetOrgHook_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("expected GET, got %s", r.Method) + } + if r.URL.Path != "/api/v1/orgs/myorg/hooks/10" { + t.Errorf("wrong path: %s", r.URL.Path) + } + json.NewEncoder(w).Encode(types.Hook{ + ID: 10, + Type: "forgejo", + Active: true, + URL: "https://example.com/org-hook", + }) + })) + defer srv.Close() + + f := NewForge(srv.URL, "tok") + hook, err := f.Webhooks.GetOrgHook(context.Background(), "myorg", 10) + if err != nil { + t.Fatal(err) + } + if hook.ID != 10 { + t.Errorf("got id=%d, want 10", hook.ID) + } + if hook.URL != "https://example.com/org-hook" { + t.Errorf("got url=%q, want %q", hook.URL, "https://example.com/org-hook") + } +} + +func TestWebhookService_CreateOrgHook_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Errorf("expected POST, got %s", r.Method) + } + if r.URL.Path != "/api/v1/orgs/myorg/hooks" { + t.Errorf("wrong path: %s", r.URL.Path) + } + var opts types.CreateHookOption + if err := json.NewDecoder(r.Body).Decode(&opts); err != nil { + t.Fatal(err) + } + if opts.Type != "forgejo" { + t.Errorf("got type=%q, want %q", opts.Type, "forgejo") + } + json.NewEncoder(w).Encode(types.Hook{ + ID: 11, + Type: opts.Type, + Active: opts.Active, + Events: opts.Events, + }) + })) + defer srv.Close() + + f := NewForge(srv.URL, "tok") + hook, err := f.Webhooks.CreateOrgHook(context.Background(), "myorg", &types.CreateHookOption{ + Type: "forgejo", + Active: true, + Events: []string{"push"}, + }) + if err != nil { + t.Fatal(err) + } + if hook.ID != 11 { + t.Errorf("got id=%d, want 11", hook.ID) + } + if hook.Type != "forgejo" { + t.Errorf("got type=%q, want %q", hook.Type, "forgejo") + } +} + +func TestWebhookService_EditOrgHook_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPatch { + t.Errorf("expected PATCH, got %s", r.Method) + } + if r.URL.Path != "/api/v1/orgs/myorg/hooks/10" { + t.Errorf("wrong path: %s", r.URL.Path) + } + var opts types.EditHookOption + if err := json.NewDecoder(r.Body).Decode(&opts); err != nil { + t.Fatal(err) + } + if opts.Active != false { + t.Fatalf("unexpected edit payload: %+v", opts) + } + active := false + json.NewEncoder(w).Encode(types.Hook{ + ID: 10, + Type: "forgejo", + Active: active, + }) + })) + defer srv.Close() + + f := NewForge(srv.URL, "tok") + hook, err := f.Webhooks.EditOrgHook(context.Background(), "myorg", 10, &types.EditHookOption{ + Active: false, + }) + if err != nil { + t.Fatal(err) + } + if hook.ID != 10 { + t.Errorf("got id=%d, want 10", hook.ID) + } + if hook.Active { + t.Error("expected active=false") + } +} + +func TestWebhookService_DeleteOrgHook_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodDelete { + t.Errorf("expected DELETE, got %s", r.Method) + } + if r.URL.Path != "/api/v1/orgs/myorg/hooks/10" { + t.Errorf("wrong path: %s", r.URL.Path) + } + w.WriteHeader(http.StatusNoContent) + })) + defer srv.Close() + + f := NewForge(srv.URL, "tok") + if err := f.Webhooks.DeleteOrgHook(context.Background(), "myorg", 10); err != nil { + t.Fatal(err) + } +} + func TestWebhookService_NotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound)