Skip to content

Commit 5b35b64

Browse files
authored
fix: pass empty Locations and PrivateLocations to backend [sc-23129] (#125)
* fix: remove omitempty from Locations and PrivateLocations When omitempty, the property is not sent to the server. Our PUT endpoints however act like PATCH would, meaning that partial updates are supported. When a property is not set, it is kept intact, even though the intention most likely was to remove it. * fix: always send empty [] for locations (public/private) even when not set If the backend does not receive a value, or receives a null value, it will not update the database. This is an issue for many other values too, but as an API level fix will be needed, for now this patch only addresses the fields we've received reports about. To make the patch a little easier, Create now calls CreateCheck, Update UpdateCheck and Delete DeleteCheck. The latter two were already equivalent. Create and CreateCheck had some differences (mainly that that former would send the request to /v1/checks instead of /v1/checks/<type>), but given how CreateCheck covers all check types, there should be no difference. * chore: update tests, these need a complete overhaul though * fix: keep using /v1/checks for Create after all as it behaves differently
1 parent dac9fad commit 5b35b64

File tree

3 files changed

+75
-90
lines changed

3 files changed

+75
-90
lines changed

checkly.go

Lines changed: 48 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -66,27 +66,9 @@ func (c *client) Create(
6666
ctx context.Context,
6767
check Check,
6868
) (*Check, error) {
69-
data, err := json.Marshal(check)
70-
if err != nil {
71-
return nil, err
72-
}
73-
status, res, err := c.apiCall(
74-
ctx,
75-
http.MethodPost,
76-
withAutoAssignAlertsFlag("checks"),
77-
data,
78-
)
79-
if err != nil {
80-
return nil, err
81-
}
82-
if status != http.StatusCreated {
83-
return nil, fmt.Errorf("unexpected response status %d: %q", status, res)
84-
}
85-
var result Check
86-
if err = json.NewDecoder(strings.NewReader(res)).Decode(&result); err != nil {
87-
return nil, fmt.Errorf("decoding error for data %s: %v", res, err)
88-
}
89-
return &result, nil
69+
// There are differences between /v1/checks and /v1/checks/<type>. Keep
70+
// using /v1/checks here for backwards compatibility reasons.
71+
return c.createCheck(ctx, check, "checks")
9072
}
9173

9274
// Update updates an existing check with the specified details. It returns the
@@ -98,28 +80,7 @@ func (c *client) Update(
9880
ctx context.Context,
9981
ID string, check Check,
10082
) (*Check, error) {
101-
data, err := json.Marshal(check)
102-
if err != nil {
103-
return nil, err
104-
}
105-
status, res, err := c.apiCall(
106-
ctx,
107-
http.MethodPut,
108-
withAutoAssignAlertsFlag(fmt.Sprintf("checks/%s", ID)),
109-
data,
110-
)
111-
if err != nil {
112-
return nil, err
113-
}
114-
if status != http.StatusOK {
115-
return nil, fmt.Errorf("unexpected response status %d: %q", status, res)
116-
}
117-
var result Check
118-
err = json.NewDecoder(strings.NewReader(res)).Decode(&result)
119-
if err != nil {
120-
return nil, fmt.Errorf("decoding error for data %s: %v", res, err)
121-
}
122-
return &result, nil
83+
return c.UpdateCheck(ctx, ID, check)
12384
}
12485

12586
// Delete deletes the check with the specified ID.
@@ -130,19 +91,7 @@ func (c *client) Delete(
13091
ctx context.Context,
13192
ID string,
13293
) error {
133-
status, res, err := c.apiCall(
134-
ctx,
135-
http.MethodDelete,
136-
fmt.Sprintf("checks/%s", ID),
137-
nil,
138-
)
139-
if err != nil {
140-
return err
141-
}
142-
if status != http.StatusNoContent {
143-
return fmt.Errorf("unexpected response status %d: %q", status, res)
144-
}
145-
return nil
94+
return c.DeleteCheck(ctx, ID)
14695
}
14796

14897
// Get takes the ID of an existing check, and returns the check parameters, or
@@ -180,29 +129,37 @@ func (c *client) CreateCheck(
180129
ctx context.Context,
181130
check Check,
182131
) (*Check, error) {
183-
data, err := json.Marshal(check)
184-
if err != nil {
185-
return nil, err
186-
}
187-
var checkType string
132+
var endpoint string
188133
switch check.Type {
189134
case "BROWSER":
190-
checkType = "checks/browser"
135+
endpoint = "checks/browser"
191136
case "API":
192-
checkType = "checks/api"
137+
endpoint = "checks/api"
193138
case "HEARTBEAT":
194-
checkType = "checks/heartbeat"
139+
endpoint = "checks/heartbeat"
195140
case "MULTI_STEP":
196-
checkType = "checks/multistep"
141+
endpoint = "checks/multistep"
197142
case "TCP":
198143
return nil, fmt.Errorf("user error: use CreateTCPCheck to create TCP checks")
199144
default:
200-
return nil, fmt.Errorf("unknown check type: %s", checkType)
145+
return nil, fmt.Errorf("unknown check type: %s", endpoint)
146+
}
147+
return c.createCheck(ctx, check, endpoint)
148+
}
149+
150+
func (c *client) createCheck(
151+
ctx context.Context,
152+
check Check,
153+
endpoint string,
154+
) (*Check, error) {
155+
data, err := json.Marshal(check)
156+
if err != nil {
157+
return nil, err
201158
}
202159
status, res, err := c.apiCall(
203160
ctx,
204161
http.MethodPost,
205-
withAutoAssignAlertsFlag(checkType),
162+
withAutoAssignAlertsFlag(endpoint),
206163
data,
207164
)
208165
if err != nil {
@@ -278,6 +235,14 @@ func (c *client) UpdateCheck(
278235
ctx context.Context,
279236
ID string, check Check,
280237
) (*Check, error) {
238+
// A nil value for a list will cause the backend to not update the value.
239+
// We must send empty lists instead.
240+
if check.Locations == nil {
241+
check.Locations = []string{}
242+
}
243+
if check.PrivateLocations == nil {
244+
check.PrivateLocations = &[]string{}
245+
}
281246
data, err := json.Marshal(check)
282247
if err != nil {
283248
return nil, err
@@ -337,6 +302,14 @@ func (c *client) UpdateTCPCheck(
337302
ID string,
338303
check TCPCheck,
339304
) (*TCPCheck, error) {
305+
// A nil value for a list will cause the backend to not update the value.
306+
// We must send empty lists instead.
307+
if check.Locations == nil {
308+
check.Locations = []string{}
309+
}
310+
if check.PrivateLocations == nil {
311+
check.PrivateLocations = &[]string{}
312+
}
340313
// Unfortunately `checkType` is required for this endpoint, so sneak it in
341314
// using an anonymous struct.
342315
payload := struct {
@@ -532,6 +505,14 @@ func (c *client) UpdateGroup(
532505
ID int64,
533506
group Group,
534507
) (*Group, error) {
508+
// A nil value for a list will cause the backend to not update the value.
509+
// We must send empty lists instead.
510+
if group.Locations == nil {
511+
group.Locations = []string{}
512+
}
513+
if group.PrivateLocations == nil {
514+
group.PrivateLocations = &[]string{}
515+
}
535516
data, err := json.Marshal(group)
536517
if err != nil {
537518
return nil, err

checkly_test.go

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@ import (
2727
var wantCheckID = "73d29e72-6540-4bb5-967e-e07fa2c9465e"
2828

2929
var wantCheck = checkly.Check{
30-
Name: "test",
31-
Type: checkly.TypeAPI,
32-
Frequency: 10,
33-
Activated: true,
34-
Muted: false,
35-
DoubleCheck: true,
36-
ShouldFail: false,
37-
Locations: []string{"eu-west-1"},
30+
Name: "test",
31+
Type: checkly.TypeAPI,
32+
Frequency: 10,
33+
Activated: true,
34+
Muted: false,
35+
DoubleCheck: true,
36+
ShouldFail: false,
37+
Locations: []string{"eu-west-1"},
38+
PrivateLocations: &[]string{},
3839
Request: checkly.Request{
3940
Method: http.MethodGet,
4041
URL: "https://example.com",
@@ -161,14 +162,16 @@ func TestAPIError(t *testing.T) {
161162
t.Parallel()
162163
ts := cannedResponseServer(t,
163164
http.MethodPost,
164-
"/v1/checks?autoAssignAlerts=false",
165+
"/v1/checks/api?autoAssignAlerts=false",
165166
validateAnything,
166167
http.StatusBadRequest,
167168
"BadRequest.json",
168169
)
169170
defer ts.Close()
170171
client := checkly.NewClient(ts.URL, "dummy-key", ts.Client(), nil)
171-
_, err := client.Create(context.Background(), checkly.Check{})
172+
_, err := client.CreateCheck(context.Background(), checkly.Check{
173+
Type: checkly.TypeAPI,
174+
})
172175
if err == nil {
173176
t.Error("want error when API returns 'bad request' status, got nil")
174177
}
@@ -334,12 +337,13 @@ func TestDeleteCheck(t *testing.T) {
334337
var wantGroupID int64 = 135
335338

336339
var wantGroup = checkly.Group{
337-
Name: "test",
338-
Activated: true,
339-
Muted: false,
340-
Tags: []string{"auto"},
341-
Locations: []string{"eu-west-1"},
342-
Concurrency: 3,
340+
Name: "test",
341+
Activated: true,
342+
Muted: false,
343+
Tags: []string{"auto"},
344+
Locations: []string{"eu-west-1"},
345+
PrivateLocations: &[]string{},
346+
Concurrency: 3,
343347
APICheckDefaults: checkly.APICheckDefaults{
344348
BaseURL: "example.com/api/test",
345349
Headers: []checkly.KeyValue{
@@ -429,7 +433,7 @@ func TestCreateGroup(t *testing.T) {
429433
if err != nil {
430434
t.Error(err)
431435
}
432-
ignored := cmpopts.IgnoreFields(checkly.Group{}, "ID", "AlertChannelSubscriptions")
436+
ignored := cmpopts.IgnoreFields(checkly.Group{}, "ID", "AlertChannelSubscriptions", "PrivateLocations")
433437
if !cmp.Equal(wantGroup, *gotGroup, ignored) {
434438
t.Error(cmp.Diff(wantGroup, *gotGroup, ignoreGroupFields))
435439
}
@@ -450,7 +454,7 @@ func TestGetGroup(t *testing.T) {
450454
if err != nil {
451455
t.Error(err)
452456
}
453-
ignored := cmpopts.IgnoreFields(checkly.Group{}, "ID", "AlertChannelSubscriptions")
457+
ignored := cmpopts.IgnoreFields(checkly.Group{}, "ID", "AlertChannelSubscriptions", "PrivateLocations")
454458
if !cmp.Equal(wantGroup, *gotGroup, ignored) {
455459
t.Error(cmp.Diff(wantGroup, *gotGroup, ignored))
456460
}
@@ -471,7 +475,7 @@ func TestUpdateGroup(t *testing.T) {
471475
if err != nil {
472476
t.Error(err)
473477
}
474-
ignored := cmpopts.IgnoreFields(checkly.Group{}, "ID", "AlertChannelSubscriptions")
478+
ignored := cmpopts.IgnoreFields(checkly.Group{}, "ID", "AlertChannelSubscriptions", "PrivateLocations")
475479
if !cmp.Equal(wantGroup, *gotGroup, ignored) {
476480
t.Error(cmp.Diff(wantGroup, *gotGroup, ignoreGroupFields))
477481
}

types.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ type Check struct {
474474
Muted bool `json:"muted"`
475475
ShouldFail bool `json:"shouldFail"`
476476
RunParallel bool `json:"runParallel"`
477-
Locations []string `json:"locations,omitempty"`
477+
Locations []string `json:"locations"`
478478
DegradedResponseTime int `json:"degradedResponseTime"`
479479
MaxResponseTime int `json:"maxResponseTime"`
480480
Script string `json:"script,omitempty"`
@@ -517,7 +517,7 @@ type MultiStepCheck struct {
517517
Muted bool `json:"muted"`
518518
ShouldFail bool `json:"shouldFail"`
519519
RunParallel bool `json:"runParallel"`
520-
Locations []string `json:"locations,omitempty"`
520+
Locations []string `json:"locations"`
521521
Script string `json:"script,omitempty"`
522522
EnvironmentVariables []EnvironmentVariable `json:"environmentVariables"`
523523
Tags []string `json:"tags,omitempty"`
@@ -559,7 +559,7 @@ type TCPCheck struct {
559559
Muted bool `json:"muted"`
560560
ShouldFail bool `json:"shouldFail"`
561561
RunParallel bool `json:"runParallel"`
562-
Locations []string `json:"locations,omitempty"`
562+
Locations []string `json:"locations"`
563563
DegradedResponseTime int `json:"degradedResponseTime,omitempty"`
564564
MaxResponseTime int `json:"maxResponseTime,omitempty"`
565565
Tags []string `json:"tags,omitempty"`
@@ -569,7 +569,7 @@ type TCPCheck struct {
569569
GroupID int64 `json:"groupId,omitempty"`
570570
GroupOrder int `json:"groupOrder,omitempty"`
571571
AlertChannelSubscriptions []AlertChannelSubscription `json:"alertChannelSubscriptions,omitempty"`
572-
PrivateLocations *[]string `json:"privateLocations,omitempty"`
572+
PrivateLocations *[]string `json:"privateLocations"`
573573
RuntimeID *string `json:"runtimeId"`
574574
RetryStrategy *RetryStrategy `json:"retryStrategy,omitempty"`
575575
CreatedAt time.Time `json:"created_at,omitempty"`

0 commit comments

Comments
 (0)