Skip to content

Commit aa6418f

Browse files
authored
Add support for webhook API (#228)
* Add WebhookAPI and impl * Add WebhookAPI to API list and generate mock * Add fixtures for webhook * Webhook update API returns No Content * Add test for webhook.go
1 parent b64dba3 commit aa6418f

File tree

7 files changed

+310
-9
lines changed

7 files changed

+310
-9
lines changed

fixture/GET/webhook.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"webhook": {
3+
"authentication": {
4+
"add_position": "header",
5+
"type": "basic_auth"
6+
},
7+
"created_at": "2020-10-20T08:16:28Z",
8+
"created_by": "1234567",
9+
"endpoint": "https://example.com/status/200",
10+
"http_method": "POST",
11+
"id": "01EJFTSCC78X5V07NPY2MHR00M",
12+
"name": "Example Webhook",
13+
"request_format": "json",
14+
"status": "active",
15+
"subscriptions": [
16+
"conditional_ticket_events"
17+
],
18+
"updated_at": "2020-10-20T08:16:28Z",
19+
"updated_by": "1234567"
20+
}
21+
}

fixture/POST/webhooks.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"webhook": {
3+
"authentication": {
4+
"add_position": "header",
5+
"type": "basic_auth"
6+
},
7+
"created_at": "2020-10-20T08:16:28Z",
8+
"created_by": "1234567",
9+
"endpoint": "https://example.com/status/200",
10+
"http_method": "POST",
11+
"id": "01EJFTSCC78X5V07NPY2MHR00M",
12+
"name": "Example Webhook",
13+
"request_format": "json",
14+
"status": "active",
15+
"subscriptions": [
16+
"conditional_ticket_events"
17+
],
18+
"updated_at": "2020-10-20T08:16:28Z",
19+
"updated_by": "1234567"
20+
}
21+
}

zendesk/api.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,22 @@ type API interface {
1515
GroupMembershipAPI
1616
LocaleAPI
1717
MacroAPI
18-
TicketAPI
19-
TicketFieldAPI
20-
TicketFormAPI
21-
TriggerAPI
22-
TargetAPI
23-
UserAPI
24-
UserFieldAPI
2518
OrganizationAPI
19+
OrganizationMembershipAPI
2620
SearchAPI
2721
SLAPolicyAPI
22+
TargetAPI
2823
TagAPI
2924
TicketAuditAPI
25+
TicketAPI
3026
TicketCommentAPI
27+
TicketFieldAPI
28+
TicketFormAPI
29+
TriggerAPI
30+
UserAPI
31+
UserFieldAPI
3132
ViewAPI
32-
OrganizationMembershipAPI
33+
WebhookAPI
3334
}
3435

3536
var _ API = (*Client)(nil)

zendesk/mock/client.go

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

zendesk/webhook.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package zendesk
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"time"
8+
)
9+
10+
// Webhook is struct for webhook payload.
11+
// https://developer.zendesk.com/api-reference/event-connectors/webhooks/webhooks/
12+
type Webhook struct {
13+
Authentication *WebhookAuthentication `json:"authentication,omitempty"`
14+
CreatedAt time.Time `json:"created_at,omitempty"`
15+
CreatedBy string `json:"created_by,omitempty"`
16+
Description string `json:"description,omitempty"`
17+
Endpoint string `json:"endpoint"`
18+
ExternalSource interface{} `json:"external_source,omitempty"`
19+
HTTPMethod string `json:"http_method"`
20+
ID string `json:"id,omitempty"`
21+
Name string `json:"name"`
22+
RequestFormat string `json:"request_format"`
23+
SigningSecret *WebhookSigningSecret `json:"signing_secret,omitempty"`
24+
Status string `json:"status"`
25+
Subscriptions []string `json:"subscriptions,omitempty"`
26+
UpdatedAt time.Time `json:"updated_at,omitempty"`
27+
UpdatedBy string `json:"updated_by,omitempty"`
28+
}
29+
30+
type WebhookAuthentication struct {
31+
Type string `json:"type"`
32+
Data interface{} `json:"data"`
33+
AddPosition string `json:"add_position"`
34+
}
35+
36+
type WebhookSigningSecret struct {
37+
Algorithm string `json:"algorithm"`
38+
Secret string `json:"secret"`
39+
}
40+
41+
type WebhookAPI interface {
42+
CreateWebhook(ctx context.Context, hook *Webhook) (*Webhook, error)
43+
GetWebhook(ctx context.Context, webhookID string) (*Webhook, error)
44+
UpdateWebhook(ctx context.Context, webhookID string, hook *Webhook) error
45+
DeleteWebhook(ctx context.Context, webhookID string) error
46+
}
47+
48+
// CreateWebhook creates new webhook.
49+
//
50+
// https://developer.zendesk.com/api-reference/event-connectors/webhooks/webhooks/#create-or-clone-webhook
51+
func (z *Client) CreateWebhook(ctx context.Context, hook *Webhook) (*Webhook, error) {
52+
var data, result struct {
53+
Webhook *Webhook `json:"webhook"`
54+
}
55+
data.Webhook = hook
56+
57+
body, err := z.post(ctx, "/webhooks", data)
58+
if err != nil {
59+
return nil, err
60+
}
61+
62+
err = json.Unmarshal(body, &result)
63+
if err != nil {
64+
return nil, err
65+
}
66+
return result.Webhook, nil
67+
}
68+
69+
// GetWebhook gets a specified webhook.
70+
//
71+
// https://developer.zendesk.com/api-reference/event-connectors/webhooks/webhooks/#show-webhook
72+
func (z *Client) GetWebhook(ctx context.Context, webhookID string) (*Webhook, error) {
73+
var result struct {
74+
Webhook *Webhook `json:"webhook"`
75+
}
76+
77+
body, err := z.get(ctx, fmt.Sprintf("/webhooks/%s", webhookID))
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
err = json.Unmarshal(body, &result)
83+
if err != nil {
84+
return nil, err
85+
}
86+
87+
return result.Webhook, nil
88+
}
89+
90+
// UpdateWebhook updates a webhook with the specified webhook.
91+
//
92+
// https://developer.zendesk.com/api-reference/event-connectors/webhooks/webhooks/#update-webhook
93+
func (z *Client) UpdateWebhook(ctx context.Context, webhookID string, hook *Webhook) error {
94+
var data struct {
95+
Webhook *Webhook `json:"webhook"`
96+
}
97+
data.Webhook = hook
98+
99+
_, err := z.put(ctx, fmt.Sprintf("/webhooks/%s", webhookID), data)
100+
if err != nil {
101+
return err
102+
}
103+
104+
return nil
105+
}
106+
107+
// DeleteWebhook deletes the specified webhook.
108+
//
109+
// https://developer.zendesk.com/api-reference/event-connectors/webhooks/webhooks/#delete-webhook
110+
func (z *Client) DeleteWebhook(ctx context.Context, webhookID string) error {
111+
err := z.delete(ctx, fmt.Sprintf("/webhooks/%s", webhookID))
112+
if err != nil {
113+
return err
114+
}
115+
116+
return nil
117+
}

zendesk/webhook_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package zendesk
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
)
9+
10+
func TestCreateWebhook(t *testing.T) {
11+
mockAPI := newMockAPI(http.MethodPost, "webhooks.json")
12+
client := newTestClient(mockAPI)
13+
defer mockAPI.Close()
14+
15+
hook, err := client.CreateWebhook(context.Background(), &Webhook{
16+
Authentication: &WebhookAuthentication{
17+
AddPosition: "header",
18+
Data: map[string]string{
19+
"password": "hello_123",
20+
"username": "john_smith",
21+
},
22+
Type: "basic_auth",
23+
},
24+
Endpoint: "https://example.com/status/200",
25+
HTTPMethod: http.MethodGet,
26+
Name: "Example Webhook",
27+
RequestFormat: "json",
28+
Status: "active",
29+
Subscriptions: []string{"conditional_ticket_events"},
30+
})
31+
if err != nil {
32+
t.Fatalf("Failed to create webhook: %v", err)
33+
}
34+
35+
if len(hook.Subscriptions) != 1 || hook.Authentication.AddPosition != "header" {
36+
t.Fatalf("Invalid response of webhook: %v", hook)
37+
}
38+
}
39+
40+
func TestGetWebhook(t *testing.T) {
41+
mockAPI := newMockAPI(http.MethodGet, "webhook.json")
42+
client := newTestClient(mockAPI)
43+
defer mockAPI.Close()
44+
45+
hook, err := client.GetWebhook(ctx, "01EJFTSCC78X5V07NPY2MHR00M")
46+
if err != nil {
47+
t.Fatalf("Failed to get webhook: %s", err)
48+
}
49+
50+
expectedID := "01EJFTSCC78X5V07NPY2MHR00M"
51+
if hook.ID != expectedID {
52+
t.Fatalf("Returned webhook does not have the expected ID %s. Webhook ID is %s", expectedID, hook.ID)
53+
}
54+
}
55+
56+
func TestUpdateWebhook(t *testing.T) {
57+
mockAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
58+
w.WriteHeader(http.StatusNoContent)
59+
w.Write(nil)
60+
}))
61+
client := newTestClient(mockAPI)
62+
defer mockAPI.Close()
63+
64+
err := client.UpdateWebhook(ctx, "01EJFTSCC78X5V07NPY2MHR00M", &Webhook{})
65+
if err != nil {
66+
t.Fatalf("Failed to send request to create webhook: %s", err)
67+
}
68+
}
69+
70+
func TestDeleteWebhook(t *testing.T) {
71+
mockAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
72+
w.WriteHeader(http.StatusNoContent)
73+
w.Write(nil)
74+
}))
75+
client := newTestClient(mockAPI)
76+
defer mockAPI.Close()
77+
78+
err := client.DeleteWebhook(ctx, "01EJFTSCC78X5V07NPY2MHR00M")
79+
if err != nil {
80+
t.Fatalf("Failed to delete webhook: %s", err)
81+
}
82+
}

zendesk/zendesk.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ func (z *Client) put(ctx context.Context, path string, data interface{}) ([]byte
180180
return nil, err
181181
}
182182

183-
if resp.StatusCode != http.StatusOK {
183+
// NOTE: some webhook mutation APIs return status No Content.
184+
if !(resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNoContent) {
184185
return nil, Error{
185186
body: body,
186187
resp: resp,

0 commit comments

Comments
 (0)