Skip to content

Commit 26eb6c4

Browse files
feat: improve monitor info output with grouped region display (#15)
fix #14
1 parent 70ea00c commit 26eb6c4

7 files changed

Lines changed: 295 additions & 3 deletions

File tree

internal/cmd/app.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func NewApp() *cli.Command {
1313
Suggest: true,
1414
Usage: "This is OpenStatus Command Line Interface, the OpenStatus.dev CLI",
1515
Description: "OpenStatus is a command line interface for managing your monitors and triggering your synthetics tests. \n\nPlease report any issues at https://github.com/openstatusHQ/cli/issues/new",
16-
Version: "v1.0.0",
16+
Version: "v1.0.1",
1717
Commands: []*cli.Command{
1818
monitors.MonitorsCmd(),
1919
run.RunCmd(),

internal/cmd/app_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ func Test_NewApp(t *testing.T) {
2020
t.Errorf("Expected app name 'openstatus', got %s", app.Name)
2121
}
2222

23-
if app.Version != "v1.0.0" {
24-
t.Errorf("Expected version 'v1.0.0', got %s", app.Version)
23+
if app.Version != "v1.0.1" {
24+
t.Errorf("Expected version 'v1.0.1', got %s", app.Version)
2525
}
2626

2727
if !app.Suggest {

internal/config/monitor.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ type Request struct {
4848
Method Method `json:"method,omitempty" ,yaml:"method,omitempty"`
4949
// URL to request
5050
URL string `json:"url,omitempty" ,yaml:"url,omitempty"`
51+
// Whether to follow HTTP redirects (defaults to true when not specified)
52+
FollowRedirects *bool `json:"followRedirects,omitempty" ,yaml:"followRedirects,omitempty"`
5153
// Host to connect to
5254
Host string `json:"host,omitempty" ,yaml:"host,omitempty"`
5355
// Port to connect to

internal/config/openstatus_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,94 @@ func Test_ReadOpenStatus(t *testing.T) {
9797
})
9898
}
9999

100+
func Test_ReadOpenStatus_FollowRedirects(t *testing.T) {
101+
t.Run("followRedirects false is parsed correctly", func(t *testing.T) {
102+
yaml := `
103+
"redirect-monitor":
104+
active: true
105+
frequency: 10m
106+
kind: http
107+
name: Redirect Monitor
108+
regions:
109+
- iad
110+
request:
111+
method: GET
112+
url: https://example.com
113+
followRedirects: false
114+
`
115+
f, err := os.CreateTemp(".", "openstatus*.yaml")
116+
if err != nil {
117+
t.Fatal(err)
118+
}
119+
defer os.Remove(f.Name())
120+
121+
if _, err := f.Write([]byte(yaml)); err != nil {
122+
t.Fatal(err)
123+
}
124+
if err := f.Close(); err != nil {
125+
t.Fatal(err)
126+
}
127+
128+
out, err := config.ReadOpenStatus(f.Name())
129+
if err != nil {
130+
t.Fatal(err)
131+
}
132+
133+
monitor, exists := out["redirect-monitor"]
134+
if !exists {
135+
t.Fatal("Expected 'redirect-monitor' to exist in output")
136+
}
137+
138+
if monitor.Request.FollowRedirects == nil {
139+
t.Fatal("Expected FollowRedirects to be non-nil")
140+
}
141+
if *monitor.Request.FollowRedirects != false {
142+
t.Errorf("Expected FollowRedirects to be false, got %v", *monitor.Request.FollowRedirects)
143+
}
144+
})
145+
146+
t.Run("followRedirects omitted is nil", func(t *testing.T) {
147+
yaml := `
148+
"no-redirect-field":
149+
active: true
150+
frequency: 10m
151+
kind: http
152+
name: No Redirect Field
153+
regions:
154+
- iad
155+
request:
156+
method: GET
157+
url: https://example.com
158+
`
159+
f, err := os.CreateTemp(".", "openstatus*.yaml")
160+
if err != nil {
161+
t.Fatal(err)
162+
}
163+
defer os.Remove(f.Name())
164+
165+
if _, err := f.Write([]byte(yaml)); err != nil {
166+
t.Fatal(err)
167+
}
168+
if err := f.Close(); err != nil {
169+
t.Fatal(err)
170+
}
171+
172+
out, err := config.ReadOpenStatus(f.Name())
173+
if err != nil {
174+
t.Fatal(err)
175+
}
176+
177+
monitor, exists := out["no-redirect-field"]
178+
if !exists {
179+
t.Fatal("Expected 'no-redirect-field' to exist in output")
180+
}
181+
182+
if monitor.Request.FollowRedirects != nil {
183+
t.Errorf("Expected FollowRedirects to be nil, got %v", *monitor.Request.FollowRedirects)
184+
}
185+
})
186+
}
187+
100188
func Test_ParseConfigMonitorsToMonitor(t *testing.T) {
101189
t.Run("Parse monitors map to slice", func(t *testing.T) {
102190
monitors := config.Monitors{

internal/monitors/monitor_create_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package monitors_test
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"io"
67
"net/http"
78
"testing"
@@ -209,3 +210,103 @@ func Test_CreateMonitor(t *testing.T) {
209210
}
210211
})
211212
}
213+
214+
func boolPtr(b bool) *bool {
215+
return &b
216+
}
217+
218+
func Test_CreateMonitor_FollowRedirects(t *testing.T) {
219+
t.Parallel()
220+
221+
t.Run("followRedirects false is sent in request body", func(t *testing.T) {
222+
var capturedBody map[string]json.RawMessage
223+
224+
interceptor := &interceptorHTTPClient{
225+
f: func(req *http.Request) (*http.Response, error) {
226+
bodyBytes, _ := io.ReadAll(req.Body)
227+
json.Unmarshal(bodyBytes, &capturedBody)
228+
229+
respBody := `{"monitor":{"id":"100","name":"Redirect Monitor","url":"https://example.com","periodicity":"PERIODICITY_10M","method":"HTTP_METHOD_GET","regions":["REGION_FLY_IAD"],"active":true,"followRedirects":false}}`
230+
return &http.Response{
231+
StatusCode: http.StatusOK,
232+
Body: io.NopCloser(bytes.NewReader([]byte(respBody))),
233+
Header: http.Header{"Content-Type": []string{"application/json"}},
234+
}, nil
235+
},
236+
}
237+
238+
monitor := config.Monitor{
239+
Name: "Redirect Monitor",
240+
Active: true,
241+
Frequency: config.The10M,
242+
Kind: config.HTTP,
243+
Regions: []config.Region{config.Iad},
244+
Request: config.Request{
245+
URL: "https://example.com",
246+
Method: config.Get,
247+
FollowRedirects: boolPtr(false),
248+
},
249+
}
250+
251+
_, err := monitors.CreateMonitor(interceptor.GetHTTPClient(), "test-api-key", monitor)
252+
if err != nil {
253+
t.Fatalf("Expected no error, got %v", err)
254+
}
255+
256+
var monitorPayload map[string]json.RawMessage
257+
json.Unmarshal(capturedBody["monitor"], &monitorPayload)
258+
259+
followRedirectsRaw, exists := monitorPayload["followRedirects"]
260+
if !exists {
261+
t.Fatal("Expected followRedirects to be present in request body")
262+
}
263+
264+
var followRedirects bool
265+
json.Unmarshal(followRedirectsRaw, &followRedirects)
266+
if followRedirects != false {
267+
t.Errorf("Expected followRedirects to be false, got %v", followRedirects)
268+
}
269+
})
270+
271+
t.Run("followRedirects nil is omitted from request body", func(t *testing.T) {
272+
var capturedBody map[string]json.RawMessage
273+
274+
interceptor := &interceptorHTTPClient{
275+
f: func(req *http.Request) (*http.Response, error) {
276+
bodyBytes, _ := io.ReadAll(req.Body)
277+
json.Unmarshal(bodyBytes, &capturedBody)
278+
279+
respBody := `{"monitor":{"id":"101","name":"Default Monitor","url":"https://example.com","periodicity":"PERIODICITY_10M","method":"HTTP_METHOD_GET","regions":["REGION_FLY_IAD"],"active":true}}`
280+
return &http.Response{
281+
StatusCode: http.StatusOK,
282+
Body: io.NopCloser(bytes.NewReader([]byte(respBody))),
283+
Header: http.Header{"Content-Type": []string{"application/json"}},
284+
}, nil
285+
},
286+
}
287+
288+
monitor := config.Monitor{
289+
Name: "Default Monitor",
290+
Active: true,
291+
Frequency: config.The10M,
292+
Kind: config.HTTP,
293+
Regions: []config.Region{config.Iad},
294+
Request: config.Request{
295+
URL: "https://example.com",
296+
Method: config.Get,
297+
},
298+
}
299+
300+
_, err := monitors.CreateMonitor(interceptor.GetHTTPClient(), "test-api-key", monitor)
301+
if err != nil {
302+
t.Fatalf("Expected no error, got %v", err)
303+
}
304+
305+
var monitorPayload map[string]json.RawMessage
306+
json.Unmarshal(capturedBody["monitor"], &monitorPayload)
307+
308+
if _, exists := monitorPayload["followRedirects"]; exists {
309+
t.Error("Expected followRedirects to be absent from request body when nil")
310+
}
311+
})
312+
}

internal/monitors/monitor_update_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package monitors_test
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"io"
67
"net/http"
78
"testing"
@@ -200,3 +201,99 @@ func Test_UpdateMonitor(t *testing.T) {
200201
}
201202
})
202203
}
204+
205+
func Test_UpdateMonitor_FollowRedirects(t *testing.T) {
206+
t.Parallel()
207+
208+
t.Run("followRedirects false is sent in update request body", func(t *testing.T) {
209+
var capturedBody map[string]json.RawMessage
210+
211+
interceptor := &interceptorHTTPClient{
212+
f: func(req *http.Request) (*http.Response, error) {
213+
bodyBytes, _ := io.ReadAll(req.Body)
214+
json.Unmarshal(bodyBytes, &capturedBody)
215+
216+
respBody := `{"monitor":{"id":"123","name":"Redirect Monitor","url":"https://example.com","periodicity":"PERIODICITY_10M","method":"HTTP_METHOD_GET","regions":["REGION_FLY_IAD"],"active":true,"followRedirects":false}}`
217+
return &http.Response{
218+
StatusCode: http.StatusOK,
219+
Body: io.NopCloser(bytes.NewReader([]byte(respBody))),
220+
Header: http.Header{"Content-Type": []string{"application/json"}},
221+
}, nil
222+
},
223+
}
224+
225+
monitor := config.Monitor{
226+
Name: "Redirect Monitor",
227+
Active: true,
228+
Frequency: config.The10M,
229+
Kind: config.HTTP,
230+
Regions: []config.Region{config.Iad},
231+
Request: config.Request{
232+
URL: "https://example.com",
233+
Method: config.Get,
234+
FollowRedirects: boolPtr(false),
235+
},
236+
}
237+
238+
_, err := monitors.UpdateMonitor(interceptor.GetHTTPClient(), "test-api-key", 123, monitor)
239+
if err != nil {
240+
t.Fatalf("Expected no error, got %v", err)
241+
}
242+
243+
var monitorPayload map[string]json.RawMessage
244+
json.Unmarshal(capturedBody["monitor"], &monitorPayload)
245+
246+
followRedirectsRaw, exists := monitorPayload["followRedirects"]
247+
if !exists {
248+
t.Fatal("Expected followRedirects to be present in update request body")
249+
}
250+
251+
var followRedirects bool
252+
json.Unmarshal(followRedirectsRaw, &followRedirects)
253+
if followRedirects != false {
254+
t.Errorf("Expected followRedirects to be false, got %v", followRedirects)
255+
}
256+
})
257+
258+
t.Run("followRedirects nil is omitted from update request body", func(t *testing.T) {
259+
var capturedBody map[string]json.RawMessage
260+
261+
interceptor := &interceptorHTTPClient{
262+
f: func(req *http.Request) (*http.Response, error) {
263+
bodyBytes, _ := io.ReadAll(req.Body)
264+
json.Unmarshal(bodyBytes, &capturedBody)
265+
266+
respBody := `{"monitor":{"id":"124","name":"Default Monitor","url":"https://example.com","periodicity":"PERIODICITY_10M","method":"HTTP_METHOD_GET","regions":["REGION_FLY_IAD"],"active":true}}`
267+
return &http.Response{
268+
StatusCode: http.StatusOK,
269+
Body: io.NopCloser(bytes.NewReader([]byte(respBody))),
270+
Header: http.Header{"Content-Type": []string{"application/json"}},
271+
}, nil
272+
},
273+
}
274+
275+
monitor := config.Monitor{
276+
Name: "Default Monitor",
277+
Active: true,
278+
Frequency: config.The10M,
279+
Kind: config.HTTP,
280+
Regions: []config.Region{config.Iad},
281+
Request: config.Request{
282+
URL: "https://example.com",
283+
Method: config.Get,
284+
},
285+
}
286+
287+
_, err := monitors.UpdateMonitor(interceptor.GetHTTPClient(), "test-api-key", 124, monitor)
288+
if err != nil {
289+
t.Fatalf("Expected no error, got %v", err)
290+
}
291+
292+
var monitorPayload map[string]json.RawMessage
293+
json.Unmarshal(capturedBody["monitor"], &monitorPayload)
294+
295+
if _, exists := monitorPayload["followRedirects"]; exists {
296+
t.Error("Expected followRedirects to be absent from update request body when nil")
297+
}
298+
})
299+
}

internal/monitors/monitors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,10 @@ func configToHTTPMonitor(m config.Monitor) *monitorv1.HTTPMonitor {
403403
monitor.DegradedAt = &m.DegradedAfter
404404
}
405405

406+
if m.Request.FollowRedirects != nil {
407+
monitor.FollowRedirects = m.Request.FollowRedirects
408+
}
409+
406410
return monitor
407411
}
408412

0 commit comments

Comments
 (0)