From d76761dad39127e0509711cc803b8f03ef6f5126 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:01:08 -0500 Subject: [PATCH] feat: implement report endpoints --- README.md | 76 ++++++ client.go | 3 + reports.go | 190 +++++++++++++++ reports_test.go | 614 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 883 insertions(+) create mode 100644 reports.go create mode 100644 reports_test.go diff --git a/README.md b/README.md index 2d3ddbb..d971d9b 100644 --- a/README.md +++ b/README.md @@ -304,4 +304,80 @@ if err != nil { } else { fmt.Println("List item deleted successfully!") } +``` + +### Reports + +#### Get Report by Id + +```go +import ( + "context" + "fmt" + "github.com/StevanFreeborn/onspring-api-sdk-go/onspring" +) + +client := onspring.NewClient("your-api-key") + +report, err := client.Reports.Get(context.TODO(), 1) + +if err != nil { + fmt.Printf("Error: %v\n", err) +} else { + fmt.Printf("Retrieved Report Columns: %v\n", report.Columns) + fmt.Printf("Retrieved Report Rows: %d\n", len(report.Rows)) +} +``` + +You can also specify the data format and data type for the report: + +```go +report, err := client.Reports.Get( + context.TODO(), + 1, + onspring.WithDataFormat("Formatted"), + onspring.WithDataType("ChartData"), +) +``` + +#### Get Reports by App + +##### Retrieve a single page + +```go +import ( + "context" + "fmt" + "github.com/StevanFreeborn/onspring-api-sdk-go/onspring" +) + +client := onspring.NewClient("your-api-key") + +page, err := client.Reports.List(context.TODO(), 1) + +if err != nil { + fmt.Printf("Error: %v\n", err) +} else { + fmt.Printf("Retrieved Page %d of Reports: %+v\n", page.PageNumber, page.Items) +} +``` + +##### Retrieve all pages + +```go +import ( + "context" + "fmt" + "github.com/StevanFreeborn/onspring-api-sdk-go/onspring" +) + +client := onspring.NewClient("your-api-key") + +for report, err := range client.Reports.ListAll(context.TODO(), 1) { + if err != nil { + fmt.Printf("Error during iteration: %v\n", err) + break + } + fmt.Printf("Retrieved Report: %+v\n", report) +} diff --git a/client.go b/client.go index e463aa1..c1051c0 100644 --- a/client.go +++ b/client.go @@ -49,6 +49,8 @@ type Client struct { Fields *FieldsEndpoint // Lists provides access to the lists within an Onspring instance. Lists *ListsEndpoint + // Reports provides access to the reports within an Onspring instance. + Reports *ReportsEndpoint } // NewClient creates a new Onspring API client with the provided API key. @@ -85,6 +87,7 @@ func NewClient(apiKey string, opts ...ClientOption) *Client { c.Apps = &AppsEndpoint{client: c} c.Fields = &FieldsEndpoint{client: c} c.Lists = &ListsEndpoint{client: c} + c.Reports = &ReportsEndpoint{client: c} return c } diff --git a/reports.go b/reports.go new file mode 100644 index 0000000..e6a8b0c --- /dev/null +++ b/reports.go @@ -0,0 +1,190 @@ +package onspring + +import ( + "context" + "fmt" + "iter" + "net/http" +) + +const ( + reportsPath = "/reports" +) + +// ReportsEndpoint provides access to reports in an Onspring instance. +type ReportsEndpoint struct { + client *Client +} + +// ReportData represents the data returned from an Onspring report. +type ReportData struct { + Columns []string `json:"columns"` + Rows []ReportRow `json:"rows"` +} + +// ReportRow represents a row in a report. +type ReportRow struct { + RecordId *int `json:"recordId"` + Cells []any `json:"cells"` +} + +// Report represents a report associated to an app. +type Report struct { + AppId int `json:"appId"` + Id int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` +} + +// ReportOption is a functional option for configuring report requests. +type ReportOption func(*reportRequest) + +type reportRequest struct { + DataFormat string + DataType string +} + +func (r *reportRequest) ToParams() map[string]string { + params := map[string]string{} + + if r.DataFormat != "" { + params["apiDataFormat"] = r.DataFormat + } + + if r.DataType != "" { + params["dataType"] = r.DataType + } + + return params +} + +func createReportRequest(opts []ReportOption) *reportRequest { + r := &reportRequest{} + + for _, opt := range opts { + opt(r) + } + + return r +} + +// WithDataFormat sets the data format for the report request. +// Valid values are "Raw" and "Formatted". +func WithDataFormat(format string) ReportOption { + return func(r *reportRequest) { + r.DataFormat = format + } +} + +// WithDataType sets the data type for the report request. +// Valid values are "ReportData" and "ChartData". +func WithDataType(dataType string) ReportOption { + return func(r *reportRequest) { + r.DataType = dataType + } +} + +// Get retrieves a report from the Onspring API. +// +// Parameters: +// - ctx: The context for the request +// - reportId: The id of the report to retrieve +// - opts: Optional report configuration functions (e.g., WithDataFormat, WithDataType) +// +// Returns: +// - ReportData: The data for a report +// - error: An error if the request fails +func (rp *ReportsEndpoint) Get(ctx context.Context, reportId int, opts ...ReportOption) (ReportData, error) { + reportReq := createReportRequest(opts) + path := fmt.Sprintf("%s/id/%d", reportsPath, reportId) + req, requestCreationErr := rp.client.newRequest(ctx, http.MethodGet, path, reportReq.ToParams(), nil) + + var reportData ReportData + + if requestCreationErr != nil { + return reportData, requestCreationErr + } + + responseErr := rp.client.doWithJsonResponse(req, &reportData) + + if responseErr != nil { + return reportData, responseErr + } + + return reportData, nil +} + +// List retrieves a paginated list of reports for an app from the Onspring API. +// +// Parameters: +// - ctx: The context for the request +// - appId: The id of the app to retrieve reports for +// - pagingOpts: Optional paging configuration functions (e.g., ForPageNumber, WithPageSize) +// +// Returns: +// - Page[Report]: A page of reports with pagination metadata +// - error: An error if the request fails +func (rp *ReportsEndpoint) List(ctx context.Context, appId int, pagingOpts ...PagingOption) (Page[Report], error) { + pagingRequest := createPagingRequest(pagingOpts) + path := fmt.Sprintf("%s/appId/%d", reportsPath, appId) + + req, requestCreationErr := rp.client.newRequest(ctx, http.MethodGet, path, pagingRequest.ToParams(), nil) + + var page Page[Report] + + if requestCreationErr != nil { + return page, requestCreationErr + } + + responseErr := rp.client.doWithJsonResponse(req, &page) + + if responseErr != nil { + return page, responseErr + } + + return page, nil +} + +// ListAll returns an iterator that yields all Reports for an app across all pages. +// It automatically handles pagination by making sequential calls to List +// until all items have been retrieved or the caller stops the iteration. +// +// The iterator yields each Report and any error encountered during fetching. +// If an error occurs during a page request, the error is yielded and +// iteration terminates. +// +// Parameters: +// - ctx: The context for the request +// - appId: The id of the app to retrieve reports for +// - pagingOpts: Optional paging configuration functions (e.g., ForPageNumber, WithPageSize) +// +// Returns: +// - iter.Seq2[Report, error]: An iterator yielding: +// - Report: The individual report record. +// - error: An error if a specific page request fails during iteration. +func (rp *ReportsEndpoint) ListAll(ctx context.Context, appId int, pagingOpts ...PagingOption) iter.Seq2[Report, error] { + return func(yield func(Report, error) bool) { + pagingRequest := createPagingRequest(pagingOpts) + + for { + page, err := rp.List(ctx, appId, ForPageNumber(pagingRequest.PageNumber), WithPageSize(pagingRequest.PageSize)) + + if err != nil { + yield(Report{}, err) + return + } + + for _, item := range page.Items { + if !yield(item, nil) { + return + } + } + + if page.PageNumber >= page.TotalPages { + break + } + + pagingRequest.PageNumber++ + } + } +} diff --git a/reports_test.go b/reports_test.go new file mode 100644 index 0000000..bb3734e --- /dev/null +++ b/reports_test.go @@ -0,0 +1,614 @@ +package onspring_test + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "slices" + "strconv" + "testing" + + "github.com/StevanFreeborn/onspring-api-sdk-go" +) + +func TestReports(t *testing.T) { + t.Run("Get", func(t *testing.T) { + t.Run("it should return an error if context is nil", func(t *testing.T) { + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + var nilContext context.Context = nil + + _, err := client.Reports.Get(nilContext, 1) + + if err == nil { + t.Errorf("Expected error for nil context, got nil") + } + }) + + t.Run("it should return an error if context is canceled", func(t *testing.T) { + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + ctx, cancel := context.WithCancel(t.Context()) + + cancel() + + _, err := client.Reports.Get(ctx, 1) + + if err == nil { + t.Errorf("Expected error for canceled context, got nil") + } + }) + + t.Run("it should return an error if encounters a network error", func(t *testing.T) { + client := onspring.NewClient( + "test-api-key", + onspring.WithBaseURL("http://invalid-url"), + onspring.WithHTTPClient(&http.Client{Transport: &ErrorTransport{}}), + ) + + _, err := client.Reports.Get(t.Context(), 1) + + if err == nil { + t.Errorf("Expected network error, got nil") + } + }) + + t.Run("it should return an error if create a request fails", func(t *testing.T) { + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + invalidClient := onspring.NewClient( + "test-api-key", + onspring.WithBaseURL("http://[::1]:namedport"), + onspring.WithHTTPClient(client.HTTPClient()), + ) + + _, err := invalidClient.Reports.Get(t.Context(), 1) + + if err == nil { + t.Errorf("Expected request creation error, got nil") + } + }) + + t.Run("it should return an error if the endpoint returns a non-200 status code", func(t *testing.T) { + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + }) + + _, err := client.Reports.Get(t.Context(), 1) + + if err == nil { + t.Errorf("Expected error, got nil") + } + }) + + t.Run("it should perform a GET request to the /reports/id/:reportId endpoint and return a report", func(t *testing.T) { + reportId := 1 + recordId := 100 + + expectedReport := onspring.ReportData{ + Columns: []string{"Column1", "Column2"}, + Rows: []onspring.ReportRow{ + { + RecordId: &recordId, + Cells: []any{"value1", "value2"}, + }, + }, + } + + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("Expected GET method, got %s", r.Method) + } + + expectedPath := fmt.Sprintf("/reports/id/%d", reportId) + + if r.URL.Path != expectedPath { + t.Errorf("Expected %s endpoint, got %s", expectedPath, r.URL.Path) + } + + jsonData, _ := json.Marshal(expectedReport) + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(jsonData) + }) + + report, err := client.Reports.Get(t.Context(), reportId) + + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + if !reflect.DeepEqual(expectedReport.Columns, report.Columns) { + t.Errorf("Expected columns %v but got %v", expectedReport.Columns, report.Columns) + } + + if len(report.Rows) != len(expectedReport.Rows) { + t.Errorf("Expected %d rows but got %d", len(expectedReport.Rows), len(report.Rows)) + } + }) + + t.Run("it should include apiDataFormat query param when provided", func(t *testing.T) { + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("Expected GET method, got %s", r.Method) + } + + expectedPath := fmt.Sprintf("/reports/id/%d", 1) + + if r.URL.Path != expectedPath { + t.Errorf("Expected %s endpoint, got %s", expectedPath, r.URL.Path) + } + + apiDataFormat := r.URL.Query().Get("apiDataFormat") + + if apiDataFormat != "Formatted" { + t.Errorf("Expected query param apiDataFormat to be Formatted but got %s", apiDataFormat) + } + + expectedReport := onspring.ReportData{ + Columns: []string{"Column1"}, + Rows: []onspring.ReportRow{}, + } + + jsonData, _ := json.Marshal(expectedReport) + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(jsonData) + }) + + _, _ = client.Reports.Get(t.Context(), 1, onspring.WithDataFormat("Formatted")) + }) + + t.Run("it should include dataType query param when provided", func(t *testing.T) { + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("Expected GET method, got %s", r.Method) + } + + expectedPath := fmt.Sprintf("/reports/id/%d", 1) + + if r.URL.Path != expectedPath { + t.Errorf("Expected %s endpoint, got %s", expectedPath, r.URL.Path) + } + + dataType := r.URL.Query().Get("dataType") + + if dataType != "ChartData" { + t.Errorf("Expected query param dataType to be ChartData but got %s", dataType) + } + + expectedReport := onspring.ReportData{ + Columns: []string{"Column1"}, + Rows: []onspring.ReportRow{}, + } + + jsonData, _ := json.Marshal(expectedReport) + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(jsonData) + }) + + _, _ = client.Reports.Get(t.Context(), 1, onspring.WithDataType("ChartData")) + }) + }) + + t.Run("List", func(t *testing.T) { + t.Run("it should return an error if context is nil", func(t *testing.T) { + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + var nilContext context.Context = nil + + _, err := client.Reports.List(nilContext, 1) + + if err == nil { + t.Errorf("Expected error for nil context, got nil") + } + }) + + t.Run("it should return an error if context is canceled", func(t *testing.T) { + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + ctx, cancel := context.WithCancel(t.Context()) + + cancel() + + _, err := client.Reports.List(ctx, 1) + + if err == nil { + t.Errorf("Expected error for canceled context, got nil") + } + }) + + t.Run("it should return an error if encounters a network error", func(t *testing.T) { + client := onspring.NewClient( + "test-api-key", + onspring.WithBaseURL("http://invalid-url"), + onspring.WithHTTPClient(&http.Client{Transport: &ErrorTransport{}}), + ) + + _, err := client.Reports.List(t.Context(), 1) + + if err == nil { + t.Errorf("Expected network error, got nil") + } + }) + + t.Run("it should return an error if create a request fails", func(t *testing.T) { + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + invalidClient := onspring.NewClient( + "test-api-key", + onspring.WithBaseURL("http://[::1]:namedport"), + onspring.WithHTTPClient(client.HTTPClient()), + ) + + _, err := invalidClient.Reports.List(t.Context(), 1) + + if err == nil { + t.Errorf("Expected request creation error, got nil") + } + }) + + t.Run("it should return an error if the endpoint returns a non-200 status code", func(t *testing.T) { + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + }) + + _, err := client.Reports.List(t.Context(), 1) + + if err == nil { + t.Errorf("Expected error, got nil") + } + }) + + t.Run("it should perform a GET request to the /reports/appId/:appId endpoint and return page of reports", func(t *testing.T) { + appId := 1 + expectedPageNumber := 1 + expectedPageSize := 50 + + expectedPage := onspring.Page[onspring.Report]{ + TotalPages: 1, + TotalRecords: 1, + PageNumber: 1, + PageSize: 1, + Items: []onspring.Report{ + { + AppId: 1, + Id: 1, + Name: "Report", + Description: "A test report", + }, + }, + } + + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("Expected GET method, got %s", r.Method) + } + + expectedPath := fmt.Sprintf("/reports/appId/%d", appId) + + if r.URL.Path != expectedPath { + t.Errorf("Expected %s endpoint, got %s", expectedPath, r.URL.Path) + } + + pageNumber := r.URL.Query().Get("pageNumber") + pageSize := r.URL.Query().Get("pageSize") + + if pageNumber != strconv.Itoa(expectedPageNumber) { + t.Errorf("Expected query param pageNumber to be %d but got %s", expectedPageNumber, pageNumber) + } + + if pageSize != strconv.Itoa(expectedPageSize) { + t.Errorf("Expected query param pageSize to be %d but got %s", expectedPageSize, pageSize) + } + + jsonData, _ := json.Marshal(expectedPage) + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(jsonData) + }) + + page, err := client.Reports.List(t.Context(), appId) + + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + if !reflect.DeepEqual(expectedPage, page) { + t.Errorf("Expected %v but got %v", expectedPage, page) + } + }) + + t.Run("it should perform a GET request to the /reports/appId/:appId endpoint with non-default paging information when provided", func(t *testing.T) { + appId := 1 + expectedPageNumber := 2 + expectedPageSize := 1 + + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("Expected GET method, got %s", r.Method) + } + + expectedPath := fmt.Sprintf("/reports/appId/%d", appId) + + if r.URL.Path != expectedPath { + t.Errorf("Expected %s endpoint, got %s", expectedPath, r.URL.Path) + } + + pageNumber := r.URL.Query().Get("pageNumber") + pageSize := r.URL.Query().Get("pageSize") + + if pageNumber != strconv.Itoa(expectedPageNumber) { + t.Errorf("Expected query param pageNumber to be %d but got %s", expectedPageNumber, pageNumber) + } + + if pageSize != strconv.Itoa(expectedPageSize) { + t.Errorf("Expected query param pageSize to be %d but got %s", expectedPageSize, pageSize) + } + + w.WriteHeader(http.StatusOK) + }) + + _, _ = client.Reports.List( + t.Context(), + appId, + onspring.ForPageNumber(expectedPageNumber), + onspring.WithPageSize(expectedPageSize), + ) + }) + }) + + t.Run("ListAll", func(t *testing.T) { + t.Run("it should return an error if fails to retrieve any pages of reports", func(t *testing.T) { + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + }) + + for _, err := range client.Reports.ListAll(t.Context(), 1) { + if err == nil { + t.Errorf("Expected error, got nil") + } + } + }) + + t.Run("it should return all the reports from multiple pages", func(t *testing.T) { + expectedReports := []onspring.Report{ + { + AppId: 1, + Id: 1, + Name: "Report 1", + Description: "First report", + }, + { + AppId: 1, + Id: 2, + Name: "Report 2", + Description: "Second report", + }, + } + + pageOne := onspring.Page[onspring.Report]{ + TotalPages: 2, + TotalRecords: 2, + PageNumber: 1, + PageSize: 1, + Items: []onspring.Report{expectedReports[0]}, + } + + pageTwo := onspring.Page[onspring.Report]{ + TotalPages: 2, + TotalRecords: 2, + PageNumber: 2, + PageSize: 1, + Items: []onspring.Report{expectedReports[1]}, + } + + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("Expected GET method, got %s", r.Method) + } + + pageNumber := r.URL.Query().Get("pageNumber") + + if pageNumber == "1" { + jsonData, _ := json.Marshal(pageOne) + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(jsonData) + } + + if pageNumber == "2" { + jsonData, _ := json.Marshal(pageTwo) + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(jsonData) + } + }) + + retrievedReports := []onspring.Report{} + + for report, _ := range client.Reports.ListAll(t.Context(), 1) { + retrievedReports = append(retrievedReports, report) + } + + if !slices.Equal(expectedReports, retrievedReports) { + t.Errorf("Expected %v but got %v", expectedReports, retrievedReports) + } + }) + + t.Run("it should return reports and errors if some pages fail and some succeed", func(t *testing.T) { + expectedReports := []onspring.Report{ + { + AppId: 1, + Id: 1, + Name: "Report 1", + Description: "First report", + }, + } + + pageOne := onspring.Page[onspring.Report]{ + TotalPages: 2, + TotalRecords: 2, + PageNumber: 1, + PageSize: 1, + Items: []onspring.Report{expectedReports[0]}, + } + + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("Expected GET method, got %s", r.Method) + } + + pageNumber := r.URL.Query().Get("pageNumber") + + if pageNumber == "1" { + jsonData, _ := json.Marshal(pageOne) + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(jsonData) + } + + if pageNumber == "2" { + w.WriteHeader(http.StatusInternalServerError) + } + }) + + retrievedReports := []onspring.Report{} + encounteredErrors := []error{} + + for report, err := range client.Reports.ListAll(t.Context(), 1) { + if err != nil { + encounteredErrors = append(encounteredErrors, err) + } else { + retrievedReports = append(retrievedReports, report) + } + } + + if !slices.Equal(expectedReports, retrievedReports) { + t.Errorf("Expected %v but got %v", expectedReports, retrievedReports) + } + + if len(encounteredErrors) != 1 { + t.Errorf("Expected to receive one error, but received %d", len(encounteredErrors)) + } + }) + + t.Run("it should start paging from specified page number when given", func(t *testing.T) { + expectedReports := []onspring.Report{ + { + AppId: 1, + Id: 2, + Name: "Report 2", + Description: "Second report", + }, + } + + pageTwo := onspring.Page[onspring.Report]{ + TotalPages: 2, + TotalRecords: 2, + PageNumber: 2, + PageSize: 1, + Items: []onspring.Report{expectedReports[0]}, + } + + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("Expected GET method, got %s", r.Method) + } + + pageNumber := r.URL.Query().Get("pageNumber") + + if pageNumber == "2" { + jsonData, _ := json.Marshal(pageTwo) + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(jsonData) + } + }) + + retrievedReports := []onspring.Report{} + + for report, _ := range client.Reports.ListAll(t.Context(), 1, onspring.ForPageNumber(2)) { + retrievedReports = append(retrievedReports, report) + } + + if !slices.Equal(expectedReports, retrievedReports) { + t.Errorf("Expected %v but got %v", expectedReports, retrievedReports) + } + }) + + t.Run("it should retrieve pages using specified page size when given", func(t *testing.T) { + expectedReports := []onspring.Report{ + { + AppId: 1, + Id: 1, + Name: "Report 1", + Description: "First report", + }, + { + AppId: 1, + Id: 2, + Name: "Report 2", + Description: "Second report", + }, + } + + page := onspring.Page[onspring.Report]{ + TotalPages: 1, + TotalRecords: 2, + PageNumber: 1, + PageSize: 2, + Items: expectedReports, + } + + _, client := setupMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("Expected GET method, got %s", r.Method) + } + + pageSize := r.URL.Query().Get("pageSize") + + if pageSize == "2" { + jsonData, _ := json.Marshal(page) + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(jsonData) + } + }) + + retrievedReports := []onspring.Report{} + + for report, _ := range client.Reports.ListAll(t.Context(), 1, onspring.WithPageSize(2)) { + retrievedReports = append(retrievedReports, report) + } + + if !slices.Equal(expectedReports, retrievedReports) { + t.Errorf("Expected %v but got %v", expectedReports, retrievedReports) + } + }) + }) +}