Repository: nukosuke/go-zendesk
Pull request #249 (2023-01-11 00:38:55)

Add 'list app installations' API
GET /api/v2/apps/installations

https://developer.zendesk.com/api-reference/ticketing/apps/apps/


Patch:
diff --git a/fixture/GET/apps.json b/fixture/GET/apps.json
new file mode 100644
index 0000000..055193c
--- /dev/null
+++ b/fixture/GET/apps.json
@@ -0,0 +1,46 @@
+{
+  "installations": [
+    {
+      "id": 42,
+      "app_id": 913,
+      "product": "support",
+      "settings": {
+        "name": "Mystery App",
+        "title": "Mystery App"
+      },
+      "settings_objects": [
+        {
+          "name": "setting-one",
+          "value": "value-one"
+        },
+        {
+          "name": "setting-two",
+          "value": "value-two"
+        }
+      ],
+      "enabled": true,
+      "created_at": "2023-01-01T01:01:01Z",
+      "updated_at": "2023-01-01T01:01:01Z",
+      "paid": false
+    },
+    {
+      "id": 42,
+      "app_id": 917,
+      "product": "support",
+      "settings": {
+        "name": "Mystery App 2",
+        "title": "Mystery App 2"
+      },
+      "settings_objects": [
+        {
+          "name": "foo",
+          "value": "bar"
+        }
+      ],
+      "enabled": true,
+      "created_at": "2023-02-02T02:02:02Z",
+      "updated_at": "2023-02-02T02:02:02Z",
+      "paid": false
+    }
+  ]
+}
diff --git a/go.mod b/go.mod
index f275667..2bfa393 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/nukosuke/go-zendesk
 
-go 1.15
+go 1.19
 
 require (
 	github.com/golang/mock v1.6.0
diff --git a/go.sum b/go.sum
index f7c771d..05ed0f5 100644
--- a/go.sum
+++ b/go.sum
@@ -27,5 +27,4 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/zendesk/api.go b/zendesk/api.go
index 286aa77..5c1b30b 100644
--- a/zendesk/api.go
+++ b/zendesk/api.go
@@ -5,8 +5,9 @@ package zendesk
 
 // API an interface containing all of the zendesk client methods
 type API interface {
-	AutomationAPI
+	AppAPI
 	AttachmentAPI
+	AutomationAPI
 	BaseAPI
 	BrandAPI
 	CustomRoleAPI
@@ -19,8 +20,8 @@ type API interface {
 	OrganizationMembershipAPI
 	SearchAPI
 	SLAPolicyAPI
-	TargetAPI
 	TagAPI
+	TargetAPI
 	TicketAuditAPI
 	TicketAPI
 	TicketCommentAPI
diff --git a/zendesk/app.go b/zendesk/app.go
new file mode 100644
index 0000000..2b0e3b5
--- /dev/null
+++ b/zendesk/app.go
@@ -0,0 +1,53 @@
+package zendesk
+
+import (
+	"context"
+	"encoding/json"
+	"time"
+)
+
+// AppInstallation is a struct representing an app that has been installed from the Zendesk Marketplace.
+// https://developer.zendesk.com/api-reference/ticketing/apps/apps/#example-responses-11
+type AppInstallation struct {
+	ID       int64  `json:"id"`
+	AppID    int64  `json:"app_id"`
+	Product  string `json:"product"`
+	Settings struct {
+		Name  string `json:"name"`
+		Title string `json:"title"`
+	} `json:"settings"`
+	SettingsObjects []struct {
+		Name  string `json:"name"`
+		Value string `json:"value"`
+	} `json:"settings_objects"`
+	Enabled                   bool      `json:"enabled"`
+	Updated                   string    `json:"updated"`
+	UpdatedAt                 time.Time `json:"updated_at"`
+	CreatedAt                 time.Time `json:"created_at"`
+	RecurringPayment          bool      `json:"recurring_payment"`
+	Collapsible               bool      `json:"collapsible"`
+	Paid                      bool      `json:"paid"`
+	HasUnpaidSubscription     bool      `json:"has_unpaid_subscription"`
+	HasIncompleteSubscription bool      `json:"has_incomplete_subscription"`
+}
+
+// AppAPI is an interface containing all methods associated with zendesk apps
+type AppAPI interface {
+	ListInstallations(ctx context.Context) ([]AppInstallation, error)
+}
+
+// ListInstallations shows all apps installed in the current account.
+// ref: https://developer.zendesk.com/api-reference/ticketing/apps/apps/#list-app-installations
+func (z *Client) ListInstallations(ctx context.Context) ([]AppInstallation, error) {
+	var out struct {
+		Installations []AppInstallation `json:"installations"`
+	}
+
+	body, err := z.get(ctx, "/apps/installations")
+	if err != nil {
+		return nil, err
+	}
+
+	err = json.Unmarshal(body, &out)
+	return out.Installations, err
+}
diff --git a/zendesk/mock/client.go b/zendesk/mock/client.go
index f6f4473..4918cf9 100644
--- a/zendesk/mock/client.go
+++ b/zendesk/mock/client.go
@@ -1239,6 +1239,21 @@ func (mr *ClientMockRecorder) GetWebhook(arg0, arg1 interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWebhook", reflect.TypeOf((*Client)(nil).GetWebhook), arg0, arg1)
 }
 
+// ListInstallations mocks base method.
+func (m *Client) ListInstallations(arg0 context.Context) ([]zendesk.AppInstallation, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ListInstallations", arg0)
+	ret0, _ := ret[0].([]zendesk.AppInstallation)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ListInstallations indicates an expected call of ListInstallations.
+func (mr *ClientMockRecorder) ListInstallations(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListInstallations", reflect.TypeOf((*Client)(nil).ListInstallations), arg0)
+}
+
 // ListTicketComments mocks base method.
 func (m *Client) ListTicketComments(arg0 context.Context, arg1 int64) ([]zendesk.TicketComment, error) {
 	m.ctrl.T.Helper()


Test patch:
diff --git a/zendesk/app_test.go b/zendesk/app_test.go
new file mode 100644
index 0000000..a923d3c
--- /dev/null
+++ b/zendesk/app_test.go
@@ -0,0 +1,98 @@
+package zendesk
+
+import (
+	"context"
+	"net/http"
+	"reflect"
+	"testing"
+	"time"
+)
+
+func TestListAppInstallations(t *testing.T) {
+	mockAPI := newMockAPIWithStatus(http.MethodGet, "apps.json", http.StatusOK)
+	client := newTestClient(mockAPI)
+	defer mockAPI.Close()
+
+	actual, err := client.ListInstallations(ctx)
+	if err != nil {
+		t.Fatalf("Failed to send request to list app installations: %s", err)
+	}
+
+	expected := []AppInstallation{
+		{
+			ID:      42,
+			AppID:   913,
+			Product: "support",
+			Settings: struct {
+				Name  string `json:"name"`
+				Title string `json:"title"`
+			}{
+				Name:  "Mystery App",
+				Title: "Mystery App",
+			},
+			SettingsObjects: []struct {
+				Name  string `json:"name"`
+				Value string `json:"value"`
+			}{
+				{
+					Name:  "setting-one",
+					Value: "value-one",
+				},
+				{
+					Name:  "setting-two",
+					Value: "value-two",
+				},
+			},
+			Enabled:   true,
+			Paid:      false,
+			UpdatedAt: time.Date(2023, 1, 1, 1, 1, 1, 0, time.UTC),
+			CreatedAt: time.Date(2023, 1, 1, 1, 1, 1, 0, time.UTC),
+		},
+		{
+			ID:      42,
+			AppID:   917,
+			Product: "support",
+			Settings: struct {
+				Name  string `json:"name"`
+				Title string `json:"title"`
+			}{
+				Name:  "Mystery App 2",
+				Title: "Mystery App 2",
+			},
+			SettingsObjects: []struct {
+				Name  string `json:"name"`
+				Value string `json:"value"`
+			}{
+				{
+					Name:  "foo",
+					Value: "bar",
+				},
+			},
+			Enabled:   true,
+			Paid:      false,
+			UpdatedAt: time.Date(2023, 2, 2, 2, 2, 2, 0, time.UTC),
+			CreatedAt: time.Date(2023, 2, 2, 2, 2, 2, 0, time.UTC),
+		},
+	}
+
+	if len(actual) != 2 {
+		t.Fatalf("expected 2 apps, got %d", len(actual))
+	}
+	if !reflect.DeepEqual(expected, actual) {
+		t.Fatalf("apps not equal")
+	}
+}
+
+func TestListAppInstallationsCanceledContext(t *testing.T) {
+	mockAPI := newMockAPIWithStatus(http.MethodPost, "apps.json", http.StatusOK)
+	client := newTestClient(mockAPI)
+	defer mockAPI.Close()
+
+	canceled, cancel := context.WithCancel(ctx)
+	cancel()
+
+	_, err := client.ListInstallations(canceled)
+	if err == nil {
+		t.Fatalf("did not get expected error")
+	}
+}