Skip to content

Commit 885aa2a

Browse files
committed
Update the client to the latest API spec
Also improve robustness of the current code, as well as fixing up typos and comments, and improving testability.
1 parent 0a4d905 commit 885aa2a

24 files changed

+1610
-324
lines changed

app.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ type App struct {
5858
ApnsPrivateKey string `json:"apnsPrivateKey"`
5959
// Use the Apple Push Notification service sandbox endpoint.
6060
ApnsUseSandboxEndpoint bool `json:"apnsUseSandboxEndpoint"`
61+
// Unix timestamp representing the date and time of creation.
62+
Created int `json:"created"`
63+
// Unix timestamp representing the date and time of last modification.
64+
Modified int `json:"modified"`
65+
// Links related to this resource.
66+
Links map[string]interface{} `json:"_links,omitempty"`
6167
}
6268

6369
// Apps fetches a list of all your Ably apps.

app_test.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,57 @@
11
package control
22

33
import (
4-
"github.com/stretchr/testify/assert"
4+
"encoding/json"
55
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
69
)
710

811
func TestApp(t *testing.T) {
912
client, _ := newTestClient(t)
1013
app := newTestApp(t, &client)
1114

1215
apps, err := client.Apps()
13-
assert.NoError(t, err)
14-
15-
assert.NotEqual(t, len(apps), 0)
16+
require.NoError(t, err)
17+
assert.NotEmpty(t, apps)
1618

1719
a, err := client.UpdateApp(app.ID, &NewApp{TLSOnly: false})
18-
assert.NoError(t, err)
20+
require.NoError(t, err)
1921
assert.False(t, a.TLSOnly)
22+
2023
newApp := NewApp{
2124
Status: "disabled",
2225
TLSOnly: true,
2326
ApnsUseSandboxEndpoint: true,
2427
}
2528
a, err = client.UpdateApp(app.ID, &newApp)
26-
assert.NoError(t, err)
27-
29+
require.NoError(t, err)
2830
assert.Equal(t, newApp.Status, a.Status)
2931
assert.Equal(t, newApp.TLSOnly, a.TLSOnly)
3032
assert.Equal(t, newApp.ApnsUseSandboxEndpoint, a.ApnsUseSandboxEndpoint)
33+
}
34+
35+
func TestAppUnmarshalIncludesNewFields(t *testing.T) {
36+
data := `{
37+
"id": "app123",
38+
"accountId": "acc456",
39+
"name": "Test App",
40+
"status": "enabled",
41+
"tlsOnly": true,
42+
"created": 1700000000,
43+
"modified": 1700001000,
44+
"_links": {
45+
"self": "https://control.ably.net/v1/apps/app123"
46+
}
47+
}`
3148

32-
err = client.DeleteApp(app.ID)
49+
var app App
50+
err := json.Unmarshal([]byte(data), &app)
3351
assert.NoError(t, err)
52+
assert.Equal(t, "app123", app.ID)
53+
assert.Equal(t, 1700000000, app.Created)
54+
assert.Equal(t, 1700001000, app.Modified)
55+
assert.NotNil(t, app.Links)
56+
assert.Equal(t, "https://control.ably.net/v1/apps/app123", app.Links["self"])
3457
}

client.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// - Integration rules
1616
//
1717
// Control API is currently in Preview.
18+
// Deprecated: Use github.com/org/terraform-provider-ably/client instead.
1819
package control
1920

2021
import (
@@ -170,34 +171,46 @@ func (c *Client) request(method, path string, in, out interface{}) error {
170171
if err != nil {
171172
return err
172173
}
173-
req.Header.Set("Authorization", "Bearer "+c.token)
174-
req.Header.Set("Ably-Agent", c.ablyAgent)
175174
if in != nil {
176175
req.Header.Set("Content-Type", "application/json")
177176
}
178177

178+
return c.requestRaw(req, path, out)
179+
}
180+
181+
func (c *Client) requestRaw(req *retryablehttp.Request, path string, out interface{}) error {
182+
req.Header.Set("Authorization", "Bearer "+c.token)
183+
req.Header.Set("Ably-Agent", c.ablyAgent)
184+
179185
res, err := c.httpClient.Do(req)
180186
if err != nil {
181187
return err
182188
}
183189
defer res.Body.Close()
184190

185191
if res.StatusCode < 200 || res.StatusCode >= 300 {
186-
respBody, _ := io.ReadAll(res.Body)
187-
var errorInfo ErrorInfo
188-
err = json.Unmarshal(respBody, &errorInfo)
189-
if err == nil {
190-
errorInfo.APIPath = path
191-
return errorInfo
192-
} else {
192+
respBody, readErr := io.ReadAll(res.Body)
193+
if readErr != nil {
193194
return ErrorInfo{
194-
Message: string(respBody),
195+
Message: fmt.Sprintf("failed to read error response body: %v", readErr),
195196
Code: 0,
196197
StatusCode: res.StatusCode,
197-
HRef: "",
198198
APIPath: path,
199199
}
200200
}
201+
var errorInfo ErrorInfo
202+
err = json.Unmarshal(respBody, &errorInfo)
203+
if err == nil {
204+
errorInfo.APIPath = path
205+
return errorInfo
206+
}
207+
return ErrorInfo{
208+
Message: string(respBody),
209+
Code: 0,
210+
StatusCode: res.StatusCode,
211+
HRef: "",
212+
APIPath: path,
213+
}
201214
}
202215
if out != nil {
203216
return json.NewDecoder(res.Body).Decode(out)

client_test.go

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,68 +12,52 @@ import (
1212
"time"
1313

1414
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
1516
)
1617

1718
var token string
1819
var url string = API_URL
19-
var apps []string
2020

2121
func TestMain(m *testing.M) {
2222
token = os.Getenv("ABLY_ACCOUNT_TOKEN")
23-
rand.Seed(time.Now().UnixNano())
2423

2524
if os.Getenv("ABLY_CONTROL_URL") != "" {
2625
url = os.Getenv("ABLY_CONTROL_URL")
2726
}
2827

29-
// Attempt to clean up apps if anything went wrong (only if not skipping integration tests)
30-
if os.Getenv("SKIP_INTEGRATION") == "" && token != "" {
31-
client, _, err := NewClientWithURL(token, url)
32-
if err == nil {
33-
for _, v := range apps {
34-
_ = client.DeleteApp(v)
35-
}
36-
}
37-
}
38-
39-
code := m.Run()
40-
os.Exit(code)
28+
os.Exit(m.Run())
4129
}
4230

43-
// skipIntegrationTest skips tests that interact with the API if SKIP_INTEGRATION is set.
4431
func skipIntegrationTest(t *testing.T) {
32+
t.Helper()
4533
if os.Getenv("SKIP_INTEGRATION") != "" {
4634
t.Skip("SKIP_INTEGRATION is set, skipping integration test")
4735
}
4836
}
4937

5038
func newTestApp(t *testing.T, client *Client) App {
39+
t.Helper()
5140
n := rand.Uint64()
5241
name := "test-" + fmt.Sprint(n)
5342
t.Logf("creating app with name: %s", name)
54-
app := NewApp{
43+
app, err := client.CreateApp(&NewApp{
5544
Name: name,
5645
Status: "enabled",
57-
//TLSOnly: false,
58-
FcmKey: "",
59-
FcmServiceAccount: "",
60-
FcmProjectId: "",
61-
ApnsCertificate: "",
62-
ApnsPrivateKey: "",
63-
ApnsUseSandboxEndpoint: false,
64-
}
65-
app_ret, err := client.CreateApp(&app)
46+
})
47+
require.NoError(t, err)
6648

67-
assert.NoError(t, err)
68-
apps = append(apps, app.ID)
49+
t.Cleanup(func() {
50+
_ = client.DeleteApp(app.ID)
51+
})
6952

70-
return app_ret
53+
return app
7154
}
7255

7356
func newTestClient(t *testing.T) (Client, Me) {
57+
t.Helper()
7458
skipIntegrationTest(t)
7559
client, me, err := NewClientWithURL(token, url)
76-
assert.NoError(t, err)
60+
require.NoError(t, err)
7761
return client, me
7862
}
7963

error.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ type ErrorInfo struct {
1515
// The URL to documentation about the error code.
1616
HRef string `json:"href"`
1717
// Any additional details about the error message.
18-
Details map[string][]string `json:"details"`
18+
Details map[string]interface{} `json:"details"`
1919
// The API path that resulted in this error.
20-
APIPath string
20+
APIPath string `json:"-"`
2121
}
2222

2323
// ErrorInfo implements the Error interface.
@@ -34,8 +34,13 @@ func (e ErrorInfo) Error() string {
3434
if len(e.Details) != 0 {
3535
for k, v := range e.Details {
3636
err += fmt.Sprintf("\n %s:", k)
37-
for _, str := range v {
38-
err += fmt.Sprintf("\n %s", str)
37+
switch val := v.(type) {
38+
case []interface{}:
39+
for _, item := range val {
40+
err += fmt.Sprintf("\n %v", item)
41+
}
42+
default:
43+
err += fmt.Sprintf("\n %v", val)
3944
}
4045
}
4146
}

error_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package control
22

33
import (
4+
"encoding/json"
45
"testing"
56

67
"github.com/stretchr/testify/assert"
@@ -20,3 +21,49 @@ func TestError(t *testing.T) {
2021

2122
assert.Equal(t, expected, errorInfo)
2223
}
24+
25+
func TestErrorInfoWithInterfaceDetails(t *testing.T) {
26+
e := ErrorInfo{
27+
Message: "Validation failed",
28+
Code: 40000,
29+
StatusCode: 400,
30+
Details: map[string]interface{}{
31+
"fields": []interface{}{"name is required", "status is invalid"},
32+
"count": 42.0,
33+
},
34+
APIPath: "/apps",
35+
}
36+
37+
errStr := e.Error()
38+
assert.Contains(t, errStr, "Validation failed")
39+
assert.Contains(t, errStr, "name is required")
40+
assert.Contains(t, errStr, "status is invalid")
41+
assert.Contains(t, errStr, "42")
42+
}
43+
44+
func TestErrorInfoUnmarshalDetails(t *testing.T) {
45+
data := `{
46+
"message": "Bad request",
47+
"code": 40000,
48+
"statusCode": 400,
49+
"href": "",
50+
"details": {
51+
"errors": ["field1 invalid", "field2 missing"],
52+
"severity": "high"
53+
}
54+
}`
55+
56+
var e ErrorInfo
57+
err := json.Unmarshal([]byte(data), &e)
58+
assert.NoError(t, err)
59+
assert.NotNil(t, e.Details)
60+
61+
errors, ok := e.Details["errors"].([]interface{})
62+
assert.True(t, ok)
63+
assert.Len(t, errors, 2)
64+
assert.Equal(t, "field1 invalid", errors[0])
65+
66+
severity, ok := e.Details["severity"].(string)
67+
assert.True(t, ok)
68+
assert.Equal(t, "high", severity)
69+
}

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
module github.com/ably/ably-control-go
22

3-
go 1.24
3+
go 1.24.0
44

55
toolchain go1.24.1
66

77
require (
88
github.com/hashicorp/go-retryablehttp v0.7.8
99
github.com/stretchr/testify v1.7.1
10+
software.sslmate.com/src/go-pkcs12 v0.7.0
1011
)
1112

1213
require (
1314
github.com/davecgh/go-spew v1.1.0 // indirect
1415
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
1516
github.com/pmezard/go-difflib v1.0.0 // indirect
17+
golang.org/x/crypto v0.48.0 // indirect
1618
gopkg.in/yaml.v3 v3.0.1 // indirect
1719
)

go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
1717
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
1818
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
1919
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
20-
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
21-
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
20+
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
21+
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
22+
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
23+
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
2224
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
2325
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
2426
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
2527
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
2628
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
29+
software.sslmate.com/src/go-pkcs12 v0.7.0 h1:Db8W44cB54TWD7stUFFSWxdfpdn6fZVcDl0w3R4RVM0=
30+
software.sslmate.com/src/go-pkcs12 v0.7.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=

0 commit comments

Comments
 (0)