feat(webhooks): add git hook helpers
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Successful in 51s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 22:27:49 +00:00
parent 8ecc090a0f
commit 79bb4277b3
3 changed files with 146 additions and 0 deletions

View file

@ -217,6 +217,10 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
| method | UserService.Unstar | `func (s *UserService) Unstar(ctx context.Context, owner, repo string) error` | Unstar unstars a repository as the authenticated user. | No direct tests. |
| method | WebhookService.IterOrgHooks | `func (s *WebhookService) IterOrgHooks(ctx context.Context, org string) iter.Seq2[types.Hook, error]` | IterOrgHooks returns an iterator over all webhooks for an organisation. | No direct tests. |
| method | WebhookService.ListOrgHooks | `func (s *WebhookService) ListOrgHooks(ctx context.Context, org string) ([]types.Hook, error)` | ListOrgHooks returns all webhooks for an organisation. | `TestWebhookService_Good_ListOrgHooks` |
| method | WebhookService.ListGitHooks | `func (s *WebhookService) ListGitHooks(ctx context.Context, owner, repo string) ([]types.GitHook, error)` | ListGitHooks returns all Git hooks for a repository. | `TestWebhookService_Good_ListGitHooks` |
| method | WebhookService.GetGitHook | `func (s *WebhookService) GetGitHook(ctx context.Context, owner, repo, id string) (*types.GitHook, error)` | GetGitHook returns a single Git hook for a repository. | `TestWebhookService_Good_GetGitHook` |
| method | WebhookService.EditGitHook | `func (s *WebhookService) EditGitHook(ctx context.Context, owner, repo, id string, opts *types.EditGitHookOption) (*types.GitHook, error)` | EditGitHook updates an existing Git hook in a repository. | `TestWebhookService_Good_EditGitHook` |
| method | WebhookService.DeleteGitHook | `func (s *WebhookService) DeleteGitHook(ctx context.Context, owner, repo, id string) error` | DeleteGitHook deletes a Git hook from a repository. | `TestWebhookService_Good_DeleteGitHook` |
| method | WebhookService.IterUserHooks | `func (s *WebhookService) IterUserHooks(ctx context.Context) iter.Seq2[types.Hook, error]` | IterUserHooks returns an iterator over all webhooks for the authenticated user. | No direct tests. |
| method | WebhookService.ListUserHooks | `func (s *WebhookService) ListUserHooks(ctx context.Context) ([]types.Hook, error)` | ListUserHooks returns all webhooks for the authenticated user. | `TestWebhookService_Good_ListUserHooks` |
| method | WebhookService.GetUserHook | `func (s *WebhookService) GetUserHook(ctx context.Context, id int64) (*types.Hook, error)` | GetUserHook returns a single webhook for the authenticated user. | `TestWebhookService_Good_GetUserHook` |

View file

@ -32,6 +32,38 @@ func (s *WebhookService) TestHook(ctx context.Context, owner, repo string, id in
return s.client.Post(ctx, path, nil, nil)
}
// ListGitHooks returns all Git hooks for a repository.
func (s *WebhookService) ListGitHooks(ctx context.Context, owner, repo string) ([]types.GitHook, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/hooks/git", pathParams("owner", owner, "repo", repo))
return ListAll[types.GitHook](ctx, s.client, path, nil)
}
// GetGitHook returns a single Git hook for a repository.
func (s *WebhookService) GetGitHook(ctx context.Context, owner, repo, id string) (*types.GitHook, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/hooks/git/{id}", pathParams("owner", owner, "repo", repo, "id", id))
var out types.GitHook
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
}
return &out, nil
}
// EditGitHook updates an existing Git hook in a repository.
func (s *WebhookService) EditGitHook(ctx context.Context, owner, repo, id string, opts *types.EditGitHookOption) (*types.GitHook, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/hooks/git/{id}", pathParams("owner", owner, "repo", repo, "id", id))
var out types.GitHook
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
return nil, err
}
return &out, nil
}
// DeleteGitHook deletes a Git hook from a repository.
func (s *WebhookService) DeleteGitHook(ctx context.Context, owner, repo, id string) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/hooks/git/{id}", pathParams("owner", owner, "repo", repo, "id", id))
return s.client.Delete(ctx, path)
}
// ListUserHooks returns all webhooks for the authenticated user.
func (s *WebhookService) ListUserHooks(ctx context.Context) ([]types.Hook, error) {
return ListAll[types.Hook](ctx, s.client, "/api/v1/user/hooks", nil)

View file

@ -127,6 +127,116 @@ func TestWebhookService_TestHook_Good(t *testing.T) {
}
}
func TestWebhookService_ListGitHooks_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/repos/core/go-forge/hooks/git" {
t.Errorf("wrong path: %s", r.URL.Path)
}
w.Header().Set("X-Total-Count", "1")
json.NewEncoder(w).Encode([]types.GitHook{
{Name: "pre-receive", Content: "#!/bin/sh\nexit 0", IsActive: true},
})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
hooks, err := f.Webhooks.ListGitHooks(context.Background(), "core", "go-forge")
if err != nil {
t.Fatal(err)
}
if len(hooks) != 1 {
t.Fatalf("got %d hooks, want 1", len(hooks))
}
if hooks[0].Name != "pre-receive" {
t.Errorf("got name=%q, want %q", hooks[0].Name, "pre-receive")
}
}
func TestWebhookService_GetGitHook_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/repos/core/go-forge/hooks/git/pre-receive" {
t.Errorf("wrong path: %s", r.URL.Path)
}
json.NewEncoder(w).Encode(types.GitHook{
Name: "pre-receive",
Content: "#!/bin/sh\nexit 0",
IsActive: true,
})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
hook, err := f.Webhooks.GetGitHook(context.Background(), "core", "go-forge", "pre-receive")
if err != nil {
t.Fatal(err)
}
if hook.Name != "pre-receive" {
t.Errorf("got name=%q, want %q", hook.Name, "pre-receive")
}
if !hook.IsActive {
t.Error("expected is_active=true")
}
}
func TestWebhookService_EditGitHook_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/repos/core/go-forge/hooks/git/pre-receive" {
t.Errorf("wrong path: %s", r.URL.Path)
}
var opts types.EditGitHookOption
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
t.Fatal(err)
}
if opts.Content != "#!/bin/sh\nexit 0" {
t.Fatalf("unexpected edit payload: %+v", opts)
}
json.NewEncoder(w).Encode(types.GitHook{
Name: "pre-receive",
Content: opts.Content,
IsActive: true,
})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
hook, err := f.Webhooks.EditGitHook(context.Background(), "core", "go-forge", "pre-receive", &types.EditGitHookOption{
Content: "#!/bin/sh\nexit 0",
})
if err != nil {
t.Fatal(err)
}
if hook.Content != "#!/bin/sh\nexit 0" {
t.Errorf("got content=%q, want %q", hook.Content, "#!/bin/sh\nexit 0")
}
}
func TestWebhookService_DeleteGitHook_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/repos/core/go-forge/hooks/git/pre-receive" {
t.Errorf("wrong path: %s", r.URL.Path)
}
w.WriteHeader(http.StatusNoContent)
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
if err := f.Webhooks.DeleteGitHook(context.Background(), "core", "go-forge", "pre-receive"); err != nil {
t.Fatal(err)
}
}
func TestWebhookService_ListUserHooks_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {