diff --git a/controller/controller_test.go b/controller/controller_test.go index cb920c52..57ebdcc2 100644 --- a/controller/controller_test.go +++ b/controller/controller_test.go @@ -161,6 +161,11 @@ func TestCreateRouter(t *testing.T) { ExpectedCode: http.StatusOK, Gzip: true, }, + { + Name: "service-statuses-pagination", + Path: "/api/v1/statuses?page=1&pageSize=20", + ExpectedCode: http.StatusOK, + }, { Name: "service-status", Path: "/api/v1/statuses/core_frontend", @@ -285,3 +290,59 @@ func TestServiceStatusesHandler(t *testing.T) { t.Errorf("expected:\n %s\n\ngot:\n %s", expectedOutput, output) } } + +func TestServiceStatusesHandlerWithPagination(t *testing.T) { + defer storage.Get().Clear() + defer cache.Clear() + staticFolder = "../web/static" + firstResult := &testSuccessfulResult + secondResult := &testUnsuccessfulResult + storage.Get().Insert(&testService, firstResult) + storage.Get().Insert(&testService, secondResult) + // Can't be bothered dealing with timezone issues on the worker that runs the automated tests + firstResult.Timestamp = time.Time{} + secondResult.Timestamp = time.Time{} + router := CreateRouter(&config.Config{}) + + request, _ := http.NewRequest("GET", "/api/v1/statuses?page=2&pageSize=1", nil) + responseRecorder := httptest.NewRecorder() + router.ServeHTTP(responseRecorder, request) + if responseRecorder.Code != http.StatusOK { + t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, http.StatusOK, responseRecorder.Code) + } + + output := responseRecorder.Body.String() + expectedOutput := `{"group_name":{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"errors":null,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"}]}}` + if output != expectedOutput { + t.Errorf("expected:\n %s\n\ngot:\n %s", expectedOutput, output) + } +} + +// TestServiceStatusesHandlerWithBadPagination checks that the behavior when bad pagination parameters are passed +// is to use the default pagination parameters +func TestServiceStatusesHandlerWithBadPagination(t *testing.T) { + defer storage.Get().Clear() + defer cache.Clear() + staticFolder = "../web/static" + firstResult := &testSuccessfulResult + secondResult := &testUnsuccessfulResult + storage.Get().Insert(&testService, firstResult) + storage.Get().Insert(&testService, secondResult) + // Can't be bothered dealing with timezone issues on the worker that runs the automated tests + firstResult.Timestamp = time.Time{} + secondResult.Timestamp = time.Time{} + router := CreateRouter(&config.Config{}) + + request, _ := http.NewRequest("GET", "/api/v1/statuses?page=INVALID&pageSize=INVALID", nil) + responseRecorder := httptest.NewRecorder() + router.ServeHTTP(responseRecorder, request) + if responseRecorder.Code != http.StatusOK { + t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, http.StatusOK, responseRecorder.Code) + } + + output := responseRecorder.Body.String() + expectedOutput := `{"group_name":{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"errors":null,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"},{"status":200,"hostname":"example.org","duration":750000000,"errors":["error-1","error-2"],"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":false},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":false}],"success":false,"timestamp":"0001-01-01T00:00:00Z"}]}}` + if output != expectedOutput { + t.Errorf("expected:\n %s\n\ngot:\n %s", expectedOutput, output) + } +} diff --git a/controller/util.go b/controller/util.go index ab1669fc..74a50250 100644 --- a/controller/util.go +++ b/controller/util.go @@ -5,25 +5,41 @@ import ( "strconv" ) +const ( + // DefaultPage is the default page to use if none is specified or an invalid value is provided + DefaultPage = 1 + + // DefaultPageSize is the default page siZE to use if none is specified or an invalid value is provided + DefaultPageSize = 20 + + // MaximumPageSize is the maximum page size allowed + MaximumPageSize = 100 +) + func extractPageAndPageSizeFromRequest(r *http.Request) (page int, pageSize int) { var err error if pageParameter := r.URL.Query().Get("page"); len(pageParameter) == 0 { - page = 1 + page = DefaultPage } else { page, err = strconv.Atoi(pageParameter) if err != nil { - page = 1 + page = DefaultPage + } + if page < 1 { + page = DefaultPage } } if pageSizeParameter := r.URL.Query().Get("pageSize"); len(pageSizeParameter) == 0 { - pageSize = 20 + pageSize = DefaultPageSize } else { pageSize, err = strconv.Atoi(pageSizeParameter) if err != nil { - pageSize = 20 + pageSize = DefaultPageSize } - if pageSize > 100 { - pageSize = 100 + if pageSize > MaximumPageSize { + pageSize = MaximumPageSize + } else if pageSize < 1 { + pageSize = DefaultPageSize } } return diff --git a/controller/util_test.go b/controller/util_test.go new file mode 100644 index 00000000..948c1c3c --- /dev/null +++ b/controller/util_test.go @@ -0,0 +1,67 @@ +package controller + +import ( + "fmt" + "net/http" + "testing" +) + +func TestExtractPageAndPageSizeFromRequest(t *testing.T) { + type Scenario struct { + Name string + Page string + PageSize string + ExpectedPage int + ExpectedPageSize int + } + scenarios := []Scenario{ + { + Page: "1", + PageSize: "20", + ExpectedPage: 1, + ExpectedPageSize: 20, + }, + { + Page: "2", + PageSize: "10", + ExpectedPage: 2, + ExpectedPageSize: 10, + }, + { + Page: "2", + PageSize: "10", + ExpectedPage: 2, + ExpectedPageSize: 10, + }, + { + Page: "1", + PageSize: "999999", + ExpectedPage: 1, + ExpectedPageSize: MaximumPageSize, + }, + { + Page: "-1", + PageSize: "-1", + ExpectedPage: DefaultPage, + ExpectedPageSize: DefaultPageSize, + }, + { + Page: "invalid", + PageSize: "invalid", + ExpectedPage: DefaultPage, + ExpectedPageSize: DefaultPageSize, + }, + } + for _, scenario := range scenarios { + t.Run("page-"+scenario.Page+"-pageSize-"+scenario.PageSize, func(t *testing.T) { + request, _ := http.NewRequest("GET", fmt.Sprintf("/api/v1/statuses?page=%s&pageSize=%s", scenario.Page, scenario.PageSize), nil) + actualPage, actualPageSize := extractPageAndPageSizeFromRequest(request) + if actualPage != scenario.ExpectedPage { + t.Errorf("expected %d, got %d", scenario.ExpectedPage, actualPage) + } + if actualPageSize != scenario.ExpectedPageSize { + t.Errorf("expected %d, got %d", scenario.ExpectedPageSize, actualPageSize) + } + }) + } +}