diff --git a/README.md b/README.md index 08971e5..1bb51d4 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,13 @@ mailtrap domains send-setup-instructions --id 123 --email "admin@yourdomain.com" mailtrap templates list mailtrap templates create --name "Welcome" --subject "Hello {{name}}" --body-html '

Hi!

' +# Webhooks +mailtrap webhooks list +mailtrap webhooks get --id 1 +mailtrap webhooks create --url "https://example.com/hooks" --type email_sending --sending-stream transactional --event-types delivery,bounce +mailtrap webhooks update --id 1 --active=false --event-types delivery,bounce,unsubscribe +mailtrap webhooks delete --id 1 + # Contacts mailtrap contacts create --email "user@example.com" --first-name "John" mailtrap contact-lists list @@ -141,6 +148,7 @@ mailtrap domains list --output text | **Domains** | `domains list`, `domains get`, `domains create`, `domains delete`, `domains send-setup-instructions` | | **Templates** | `templates list`, `templates get`, `templates create`, `templates update`, `templates delete` | | **Suppressions** | `suppressions list`, `suppressions delete` | +| **Webhooks** | `webhooks list`, `webhooks get`, `webhooks create`, `webhooks update`, `webhooks delete` | | **Stats** | `stats get`, `stats by-domain`, `stats by-category`, `stats by-esp`, `stats by-date` | | **Email Logs** | `email-logs list`, `email-logs get` | | **Contacts** | `contacts get`, `contacts create`, `contacts update`, `contacts delete`, `contacts import`, `contacts export`, `contacts import-status`, `contacts export-status`, `contacts create-event` | diff --git a/cmd/root.go b/cmd/root.go index cd1d13a..3be8dcf 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -28,6 +28,7 @@ import ( "github.com/mailtrap/mailtrap-cli/internal/commands/suppressions" "github.com/mailtrap/mailtrap-cli/internal/commands/templates" "github.com/mailtrap/mailtrap-cli/internal/commands/tokens" + "github.com/mailtrap/mailtrap-cli/internal/commands/webhooks" ) func NewRootCmd(f *cmdutil.Factory) *cobra.Command { @@ -57,6 +58,7 @@ func NewRootCmd(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(stats.NewCmdStats(f)) cmd.AddCommand(templates.NewCmdTemplates(f)) cmd.AddCommand(email_logs.NewCmdEmailLogs(f)) + cmd.AddCommand(webhooks.NewCmdWebhooks(f)) // Sandbox cmd.AddCommand(projects.NewCmdProjects(f)) diff --git a/internal/commands/webhooks/create.go b/internal/commands/webhooks/create.go new file mode 100644 index 0000000..a71f0e5 --- /dev/null +++ b/internal/commands/webhooks/create.go @@ -0,0 +1,98 @@ +package webhooks + +import ( + "context" + + "github.com/mailtrap/mailtrap-cli/internal/client" + "github.com/mailtrap/mailtrap-cli/internal/cmdutil" + "github.com/mailtrap/mailtrap-cli/internal/config" + "github.com/mailtrap/mailtrap-cli/internal/output" + "github.com/spf13/cobra" +) + +type WebhookWithSecret struct { + Webhook + SigningSecret string `json:"signing_secret,omitempty"` +} + +type webhookCreateResponse struct { + Data WebhookWithSecret `json:"data"` +} + +var webhookCreateColumns = append(append([]output.Column{}, webhookColumns...), output.Column{Header: "SIGNING SECRET", Field: "signing_secret"}) + +func NewCmdCreate(f *cmdutil.Factory) *cobra.Command { + var ( + webhookURL string + webhookType string + active bool + payloadFormat string + sendingStream string + eventTypes []string + domainID int + ) + + cmd := &cobra.Command{ + Use: "create", + Short: "Create a webhook", + RunE: func(cmd *cobra.Command, args []string) error { + if err := cmdutil.RequireFlag("url", webhookURL); err != nil { + return err + } + if err := cmdutil.RequireFlag("type", webhookType); err != nil { + return err + } + + c, err := f.NewClient() + if err != nil { + return err + } + + if _, err := config.RequireAccountID(); err != nil { + return err + } + + path := cmdutil.AccountPath("webhooks") + + webhookFields := map[string]interface{}{ + "url": webhookURL, + "webhook_type": webhookType, + } + if cmd.Flags().Changed("active") { + webhookFields["active"] = active + } + if cmd.Flags().Changed("payload-format") { + webhookFields["payload_format"] = payloadFormat + } + if cmd.Flags().Changed("sending-stream") { + webhookFields["sending_stream"] = sendingStream + } + if cmd.Flags().Changed("event-types") { + webhookFields["event_types"] = eventTypes + } + if cmd.Flags().Changed("domain-id") { + webhookFields["domain_id"] = domainID + } + + body := map[string]interface{}{"webhook": webhookFields} + + var resp webhookCreateResponse + if err := c.Post(context.Background(), client.BaseGeneral, path, body, &resp); err != nil { + return err + } + + format := cmdutil.GetOutputFormat() + return output.Print(f.IOStreams.Out, format, resp.Data, webhookCreateColumns) + }, + } + + cmd.Flags().StringVar(&webhookURL, "url", "", "Webhook URL (required)") + cmd.Flags().StringVar(&webhookType, "type", "", "Webhook type: email_sending, audit_log (required)") + cmd.Flags().BoolVar(&active, "active", true, "Whether the webhook is active") + cmd.Flags().StringVar(&payloadFormat, "payload-format", "", "Payload format: json, jsonlines") + cmd.Flags().StringVar(&sendingStream, "sending-stream", "", "Sending stream: transactional, bulk") + cmd.Flags().StringSliceVar(&eventTypes, "event-types", nil, "Event types (comma-separated): delivery, soft_bounce, bounce, suspension, unsubscribe, open, spam_complaint, click, reject") + cmd.Flags().IntVar(&domainID, "domain-id", 0, "Domain ID to scope the webhook to") + + return cmd +} diff --git a/internal/commands/webhooks/delete.go b/internal/commands/webhooks/delete.go new file mode 100644 index 0000000..57889cf --- /dev/null +++ b/internal/commands/webhooks/delete.go @@ -0,0 +1,47 @@ +package webhooks + +import ( + "context" + "fmt" + + "github.com/mailtrap/mailtrap-cli/internal/client" + "github.com/mailtrap/mailtrap-cli/internal/cmdutil" + "github.com/mailtrap/mailtrap-cli/internal/config" + "github.com/spf13/cobra" +) + +func NewCmdDelete(f *cmdutil.Factory) *cobra.Command { + var webhookID string + + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete a webhook", + RunE: func(cmd *cobra.Command, args []string) error { + if err := cmdutil.RequireFlag("id", webhookID); err != nil { + return err + } + + c, err := f.NewClient() + if err != nil { + return err + } + + if _, err := config.RequireAccountID(); err != nil { + return err + } + + path := cmdutil.AccountPath("webhooks", webhookID) + + if err := c.Delete(context.Background(), client.BaseGeneral, path, nil); err != nil { + return err + } + + fmt.Fprintln(f.IOStreams.Out, "Webhook deleted successfully.") + return nil + }, + } + + cmd.Flags().StringVar(&webhookID, "id", "", "Webhook ID") + + return cmd +} diff --git a/internal/commands/webhooks/get.go b/internal/commands/webhooks/get.go new file mode 100644 index 0000000..5d2d96c --- /dev/null +++ b/internal/commands/webhooks/get.go @@ -0,0 +1,48 @@ +package webhooks + +import ( + "context" + + "github.com/mailtrap/mailtrap-cli/internal/client" + "github.com/mailtrap/mailtrap-cli/internal/cmdutil" + "github.com/mailtrap/mailtrap-cli/internal/config" + "github.com/mailtrap/mailtrap-cli/internal/output" + "github.com/spf13/cobra" +) + +func NewCmdGet(f *cmdutil.Factory) *cobra.Command { + var webhookID string + + cmd := &cobra.Command{ + Use: "get", + Short: "Get a webhook", + RunE: func(cmd *cobra.Command, args []string) error { + if err := cmdutil.RequireFlag("id", webhookID); err != nil { + return err + } + + c, err := f.NewClient() + if err != nil { + return err + } + + if _, err := config.RequireAccountID(); err != nil { + return err + } + + path := cmdutil.AccountPath("webhooks", webhookID) + + var resp webhookResponse + if err := c.Get(context.Background(), client.BaseGeneral, path, nil, &resp); err != nil { + return err + } + + format := cmdutil.GetOutputFormat() + return output.Print(f.IOStreams.Out, format, resp.Data, webhookColumns) + }, + } + + cmd.Flags().StringVar(&webhookID, "id", "", "Webhook ID") + + return cmd +} \ No newline at end of file diff --git a/internal/commands/webhooks/list.go b/internal/commands/webhooks/list.go new file mode 100644 index 0000000..45153a8 --- /dev/null +++ b/internal/commands/webhooks/list.go @@ -0,0 +1,70 @@ +package webhooks + +import ( + "context" + + "github.com/mailtrap/mailtrap-cli/internal/client" + "github.com/mailtrap/mailtrap-cli/internal/cmdutil" + "github.com/mailtrap/mailtrap-cli/internal/config" + "github.com/mailtrap/mailtrap-cli/internal/output" + "github.com/spf13/cobra" +) + +type Webhook struct { + ID int `json:"id"` + URL string `json:"url"` + Active bool `json:"active"` + WebhookType string `json:"webhook_type"` + PayloadFormat string `json:"payload_format"` + SendingStream *string `json:"sending_stream,omitempty"` + DomainID *int `json:"domain_id,omitempty"` + EventTypes []string `json:"event_types,omitempty"` +} + +type webhookListResponse struct { + Data []Webhook `json:"data"` +} + +type webhookResponse struct { + Data Webhook `json:"data"` +} + +var webhookColumns = []output.Column{ + {Header: "ID", Field: "id"}, + {Header: "URL", Field: "url"}, + {Header: "TYPE", Field: "webhook_type"}, + {Header: "ACTIVE", Field: "active"}, + {Header: "FORMAT", Field: "payload_format"}, + {Header: "STREAM", Field: "sending_stream"}, + {Header: "DOMAIN ID", Field: "domain_id"}, + {Header: "EVENTS", Field: "event_types"}, +} + +func NewCmdList(f *cmdutil.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "List all webhooks", + RunE: func(cmd *cobra.Command, args []string) error { + c, err := f.NewClient() + if err != nil { + return err + } + + if _, err := config.RequireAccountID(); err != nil { + return err + } + + path := cmdutil.AccountPath("webhooks") + + var resp webhookListResponse + if err := c.Get(context.Background(), client.BaseGeneral, path, nil, &resp); err != nil { + return err + } + + format := cmdutil.GetOutputFormat() + return output.Print(f.IOStreams.Out, format, resp.Data, webhookColumns) + }, + } + + return cmd +} diff --git a/internal/commands/webhooks/update.go b/internal/commands/webhooks/update.go new file mode 100644 index 0000000..4ba0e4b --- /dev/null +++ b/internal/commands/webhooks/update.go @@ -0,0 +1,74 @@ +package webhooks + +import ( + "context" + + "github.com/mailtrap/mailtrap-cli/internal/client" + "github.com/mailtrap/mailtrap-cli/internal/cmdutil" + "github.com/mailtrap/mailtrap-cli/internal/config" + "github.com/mailtrap/mailtrap-cli/internal/output" + "github.com/spf13/cobra" +) + +func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command { + var ( + webhookID string + webhookURL string + active bool + payloadFormat string + eventTypes []string + ) + + cmd := &cobra.Command{ + Use: "update", + Short: "Update a webhook", + RunE: func(cmd *cobra.Command, args []string) error { + if err := cmdutil.RequireFlag("id", webhookID); err != nil { + return err + } + + c, err := f.NewClient() + if err != nil { + return err + } + + if _, err := config.RequireAccountID(); err != nil { + return err + } + + path := cmdutil.AccountPath("webhooks", webhookID) + + webhookFields := map[string]interface{}{} + if cmd.Flags().Changed("url") { + webhookFields["url"] = webhookURL + } + if cmd.Flags().Changed("active") { + webhookFields["active"] = active + } + if cmd.Flags().Changed("payload-format") { + webhookFields["payload_format"] = payloadFormat + } + if cmd.Flags().Changed("event-types") { + webhookFields["event_types"] = eventTypes + } + + body := map[string]interface{}{"webhook": webhookFields} + + var resp webhookResponse + if err := c.Patch(context.Background(), client.BaseGeneral, path, body, &resp); err != nil { + return err + } + + format := cmdutil.GetOutputFormat() + return output.Print(f.IOStreams.Out, format, resp.Data, webhookColumns) + }, + } + + cmd.Flags().StringVar(&webhookID, "id", "", "Webhook ID (required)") + cmd.Flags().StringVar(&webhookURL, "url", "", "Webhook URL") + cmd.Flags().BoolVar(&active, "active", true, "Whether the webhook is active") + cmd.Flags().StringVar(&payloadFormat, "payload-format", "", "Payload format: json, jsonlines") + cmd.Flags().StringSliceVar(&eventTypes, "event-types", nil, "Event types (comma-separated): delivery, soft_bounce, bounce, suspension, unsubscribe, open, spam_complaint, click, reject") + + return cmd +} \ No newline at end of file diff --git a/internal/commands/webhooks/webhooks.go b/internal/commands/webhooks/webhooks.go new file mode 100644 index 0000000..6a874cf --- /dev/null +++ b/internal/commands/webhooks/webhooks.go @@ -0,0 +1,21 @@ +package webhooks + +import ( + "github.com/mailtrap/mailtrap-cli/internal/cmdutil" + "github.com/spf13/cobra" +) + +func NewCmdWebhooks(f *cmdutil.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "webhooks", + Short: "Manage webhooks", + } + + cmd.AddCommand(NewCmdList(f)) + cmd.AddCommand(NewCmdGet(f)) + cmd.AddCommand(NewCmdCreate(f)) + cmd.AddCommand(NewCmdUpdate(f)) + cmd.AddCommand(NewCmdDelete(f)) + + return cmd +} diff --git a/internal/commands/webhooks/webhooks_test.go b/internal/commands/webhooks/webhooks_test.go new file mode 100644 index 0000000..ef1daa4 --- /dev/null +++ b/internal/commands/webhooks/webhooks_test.go @@ -0,0 +1,366 @@ +package webhooks_test + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/mailtrap/mailtrap-cli/internal/client" + "github.com/mailtrap/mailtrap-cli/internal/cmdutil" + "github.com/mailtrap/mailtrap-cli/internal/commands/webhooks" + "github.com/mailtrap/mailtrap-cli/internal/config" + "github.com/spf13/viper" +) + +func setupTest(handler http.HandlerFunc) (*cmdutil.Factory, *bytes.Buffer, func()) { + server := httptest.NewServer(handler) + + c := client.New("test-token") + c.SetBaseURL(client.BaseGeneral, server.URL) + + buf := &bytes.Buffer{} + f := &cmdutil.Factory{ + Config: func() *config.Config { + return &config.Config{APIToken: "test-token", AccountID: "123"} + }, + IOStreams: &cmdutil.IOStreams{ + Out: buf, + ErrOut: &bytes.Buffer{}, + }, + ClientOverride: c, + } + + viper.Set("api-token", "test-token") + viper.Set("account-id", "123") + viper.Set("output", "table") + + return f, buf, func() { + server.Close() + viper.Reset() + } +} + +func TestWebhooksList(t *testing.T) { + f, buf, cleanup := setupTest(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("expected GET, got %s", r.Method) + } + if !strings.HasSuffix(r.URL.Path, "/api/accounts/123/webhooks") { + t.Errorf("unexpected path: %s", r.URL.Path) + } + if r.Header.Get("Api-Token") != "test-token" { + t.Errorf("expected Api-Token header 'test-token', got %q", r.Header.Get("Api-Token")) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "data": []map[string]interface{}{ + { + "id": 1, + "url": "https://example.com/mailtrap/webhooks", + "active": true, + "webhook_type": "email_sending", + "payload_format": "json", + "sending_stream": "transactional", + "domain_id": 435, + "event_types": []string{"delivery", "bounce"}, + }, + { + "id": 2, + "url": "https://example.com/audit", + "active": true, + "webhook_type": "audit_log", + "payload_format": "json", + }, + }, + }) + }) + defer cleanup() + + cmd := webhooks.NewCmdWebhooks(f) + cmd.SetArgs([]string{"list"}) + cmd.SetOut(buf) + + if err := cmd.Execute(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + output := buf.String() + if !strings.Contains(output, "email_sending") { + t.Errorf("expected output to contain 'email_sending', got:\n%s", output) + } + if !strings.Contains(output, "audit_log") { + t.Errorf("expected output to contain 'audit_log', got:\n%s", output) + } + if !strings.Contains(output, "transactional") { + t.Errorf("expected output to contain 'transactional', got:\n%s", output) + } +} + +func TestWebhooksListJSON(t *testing.T) { + f, buf, cleanup := setupTest(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "data": []map[string]interface{}{ + { + "id": 1, + "url": "https://example.com/mailtrap/webhooks", + "active": true, + "webhook_type": "email_sending", + "payload_format": "json", + }, + }, + }) + }) + defer cleanup() + + viper.Set("output", "json") + + cmd := webhooks.NewCmdWebhooks(f) + cmd.SetArgs([]string{"list"}) + cmd.SetOut(buf) + + if err := cmd.Execute(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var result []map[string]interface{} + if err := json.Unmarshal(buf.Bytes(), &result); err != nil { + t.Fatalf("output is not valid JSON: %v\noutput:\n%s", err, buf.String()) + } + + if len(result) != 1 { + t.Fatalf("expected 1 webhook, got %d", len(result)) + } + if result[0]["webhook_type"] != "email_sending" { + t.Errorf("expected webhook_type 'email_sending', got %v", result[0]["webhook_type"]) + } +} + +func TestWebhooksGet(t *testing.T) { + f, buf, cleanup := setupTest(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("expected GET, got %s", r.Method) + } + if !strings.HasSuffix(r.URL.Path, "/api/accounts/123/webhooks/1") { + t.Errorf("unexpected path: %s", r.URL.Path) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "data": map[string]interface{}{ + "id": 1, + "url": "https://example.com/mailtrap/webhooks", + "active": true, + "webhook_type": "email_sending", + "payload_format": "json", + "sending_stream": "transactional", + "domain_id": 435, + "event_types": []string{"delivery", "bounce"}, + }, + }) + }) + defer cleanup() + + cmd := webhooks.NewCmdWebhooks(f) + cmd.SetArgs([]string{"get", "--id", "1"}) + cmd.SetOut(buf) + + if err := cmd.Execute(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + output := buf.String() + if !strings.Contains(output, "email_sending") { + t.Errorf("expected output to contain 'email_sending', got:\n%s", output) + } + if !strings.Contains(output, "https://example.com/mailtrap/webhooks") { + t.Errorf("expected output to contain webhook URL, got:\n%s", output) + } +} + +func TestWebhooksGetMissingID(t *testing.T) { + f, _, cleanup := setupTest(func(w http.ResponseWriter, r *http.Request) {}) + defer cleanup() + + buf := &bytes.Buffer{} + f.IOStreams.Out = buf + + cmd := webhooks.NewCmdWebhooks(f) + cmd.SetArgs([]string{"get"}) + cmd.SetOut(buf) + + err := cmd.Execute() + if err == nil { + t.Fatal("expected error when --id is missing") + } + if !strings.Contains(err.Error(), "--id is required") { + t.Errorf("expected '--id is required' error, got: %v", err) + } +} + +func TestWebhooksCreate(t *testing.T) { + f, buf, cleanup := setupTest(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Errorf("expected POST, got %s", r.Method) + } + if !strings.HasSuffix(r.URL.Path, "/api/accounts/123/webhooks") { + t.Errorf("unexpected path: %s", r.URL.Path) + } + + body, _ := io.ReadAll(r.Body) + var reqBody map[string]map[string]interface{} + json.Unmarshal(body, &reqBody) + + webhook := reqBody["webhook"] + if webhook["url"] != "https://example.com/hooks" { + t.Errorf("unexpected url: %v", webhook["url"]) + } + if webhook["webhook_type"] != "email_sending" { + t.Errorf("unexpected webhook_type: %v", webhook["webhook_type"]) + } + if webhook["sending_stream"] != "transactional" { + t.Errorf("unexpected sending_stream: %v", webhook["sending_stream"]) + } + if events, ok := webhook["event_types"].([]interface{}); !ok || len(events) != 2 { + t.Errorf("expected 2 event_types, got %v", webhook["event_types"]) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "data": map[string]interface{}{ + "id": 10, + "url": "https://example.com/hooks", + "active": true, + "webhook_type": "email_sending", + "payload_format": "json", + "sending_stream": "transactional", + "event_types": []string{"delivery", "bounce"}, + "signing_secret": "abc123secret", + }, + }) + }) + defer cleanup() + + cmd := webhooks.NewCmdWebhooks(f) + cmd.SetArgs([]string{ + "create", + "--url", "https://example.com/hooks", + "--type", "email_sending", + "--sending-stream", "transactional", + "--event-types", "delivery,bounce", + }) + cmd.SetOut(buf) + + if err := cmd.Execute(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + output := buf.String() + if !strings.Contains(output, "abc123secret") { + t.Errorf("expected output to contain signing secret, got:\n%s", output) + } + if !strings.Contains(output, "email_sending") { + t.Errorf("expected output to contain 'email_sending', got:\n%s", output) + } +} + +func TestWebhooksCreateMissingURL(t *testing.T) { + f, _, cleanup := setupTest(func(w http.ResponseWriter, r *http.Request) {}) + defer cleanup() + + buf := &bytes.Buffer{} + f.IOStreams.Out = buf + + cmd := webhooks.NewCmdWebhooks(f) + cmd.SetArgs([]string{"create", "--type", "email_sending"}) + cmd.SetOut(buf) + + err := cmd.Execute() + if err == nil { + t.Fatal("expected error when --url is missing") + } + if !strings.Contains(err.Error(), "--url is required") { + t.Errorf("expected '--url is required' error, got: %v", err) + } +} + +func TestWebhooksUpdate(t *testing.T) { + f, buf, cleanup := setupTest(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPatch { + t.Errorf("expected PATCH, got %s", r.Method) + } + if !strings.HasSuffix(r.URL.Path, "/api/accounts/123/webhooks/1") { + t.Errorf("unexpected path: %s", r.URL.Path) + } + + body, _ := io.ReadAll(r.Body) + var reqBody map[string]map[string]interface{} + json.Unmarshal(body, &reqBody) + + webhook := reqBody["webhook"] + if webhook["active"] != false { + t.Errorf("expected active=false, got %v", webhook["active"]) + } + if _, ok := webhook["url"]; ok { + t.Errorf("did not expect url to be set, got %v", webhook["url"]) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "data": map[string]interface{}{ + "id": 1, + "url": "https://example.com/mailtrap/webhooks", + "active": false, + "webhook_type": "email_sending", + "payload_format": "json", + }, + }) + }) + defer cleanup() + + cmd := webhooks.NewCmdWebhooks(f) + cmd.SetArgs([]string{"update", "--id", "1", "--active=false"}) + cmd.SetOut(buf) + + if err := cmd.Execute(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + output := buf.String() + if !strings.Contains(output, "email_sending") { + t.Errorf("expected output to contain 'email_sending', got:\n%s", output) + } + if !strings.Contains(output, "false") { + t.Errorf("expected output to contain 'false' for active, got:\n%s", output) + } +} + +func TestWebhooksDelete(t *testing.T) { + f, buf, cleanup := setupTest(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodDelete { + t.Errorf("expected DELETE, got %s", r.Method) + } + if !strings.HasSuffix(r.URL.Path, "/api/accounts/123/webhooks/1") { + t.Errorf("unexpected path: %s", r.URL.Path) + } + w.WriteHeader(http.StatusOK) + }) + defer cleanup() + + cmd := webhooks.NewCmdWebhooks(f) + cmd.SetArgs([]string{"delete", "--id", "1"}) + cmd.SetOut(buf) + + if err := cmd.Execute(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !strings.Contains(buf.String(), "Webhook deleted successfully") { + t.Errorf("expected success message, got:\n%s", buf.String()) + } +}