From 479509c6b1212c029e027455056202d86ae3c56a Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Thu, 15 Oct 2026 00:46:35 +0200 Subject: [PATCH 1/4] feat(insurance): read complete plan line-item pages --- pkg/insurance/client.go | 13 +- pkg/insurance/line_items_client.go | 143 ++++++++++++++++++ pkg/insurance/line_items_client_test.go | 193 ++++++++++++++++++++++++ 3 files changed, 345 insertions(+), 4 deletions(-) create mode 100644 pkg/insurance/line_items_client.go create mode 100644 pkg/insurance/line_items_client_test.go diff --git a/pkg/insurance/client.go b/pkg/insurance/client.go index beb73d23..82cf7615 100644 --- a/pkg/insurance/client.go +++ b/pkg/insurance/client.go @@ -201,27 +201,32 @@ func (b *boundedReader) Read(p []byte) (int, error) { // get performs one GET and returns the body of a 2xx response. Any other // status, including a 3xx that was not followed, becomes an *HTTPError. func (c *Client) get(ctx context.Context, path string, q url.Values) (io.ReadCloser, time.Time, error) { + body, _, fetchedAt, err := c.getWithHeaders(ctx, path, q) + return body, fetchedAt, err +} + +func (c *Client) getWithHeaders(ctx context.Context, path string, q url.Values) (io.ReadCloser, http.Header, time.Time, error) { u := c.baseURL + path if len(q) > 0 { u += "?" + q.Encode() } req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, http.NoBody) if err != nil { - return nil, time.Time{}, fmt.Errorf("building archera request: %w", err) + return nil, nil, time.Time{}, fmt.Errorf("building archera request: %w", err) } c.setAuth(req) req.Header.Set("Accept", "application/json") // baseURL is the fixed BaseURL constant; only in-package tests override it. resp, err := c.hc.Do(req) //nolint:gosec // G704: see comment above if err != nil { - return nil, time.Time{}, fmt.Errorf("archera request failed: %w", sanitizeTransportError(err)) + return nil, nil, time.Time{}, fmt.Errorf("archera request failed: %w", sanitizeTransportError(err)) } fetchedAt := time.Now().UTC() if resp.StatusCode < 200 || resp.StatusCode > 299 { defer func() { _ = resp.Body.Close() }() - return nil, fetchedAt, newHTTPError(resp, c.redact) + return nil, nil, fetchedAt, newHTTPError(resp, c.redact) } - return resp.Body, fetchedAt, nil + return resp.Body, resp.Header, fetchedAt, nil } // sanitizeTransportError drops the *url.Error wrapper so the request URL (org diff --git a/pkg/insurance/line_items_client.go b/pkg/insurance/line_items_client.go new file mode 100644 index 00000000..6bb824b8 --- /dev/null +++ b/pkg/insurance/line_items_client.go @@ -0,0 +1,143 @@ +package insurance + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" + "strconv" + "strings" +) + +const lineItemsPageSize int64 = 100 + +type lineItemsPagination struct { + Total, TotalPages, Page, FirstPage, LastPage int64 + NextPage, PreviousPage *int64 +} + +func decodePagination(header string, requested int64) (lineItemsPagination, error) { + var p lineItemsPagination + var m map[string]json.RawMessage + if err := decodeOne(strings.NewReader(header), &m); err != nil { + return p, errors.New("X-Pagination: invalid JSON metadata") + } + err := readFields(m, jsonField{"total", &p.Total, false}, jsonField{"total_pages", &p.TotalPages, false}, + jsonField{"page", &p.Page, false}, jsonField{"first_page", &p.FirstPage, false}, jsonField{"last_page", &p.LastPage, false}) + if err != nil { + return p, fmt.Errorf("X-Pagination: %w", err) + } + for _, f := range []jsonField{{"next_page", &p.NextPage, false}, {"previous_page", &p.PreviousPage, false}} { + if _, ok := m[f.name]; ok { + if err = readFields(m, f); err != nil { + return p, fmt.Errorf("X-Pagination: %w", err) + } + } + } + if err = p.validate(requested); err != nil { + return p, fmt.Errorf("X-Pagination: %w", err) + } + return p, nil +} + +func (p lineItemsPagination) validate(requested int64) error { + if p.Total < 0 || p.TotalPages < 0 || p.Page != requested || p.FirstPage != 1 { + return errors.New("invalid total or page counters") + } + if p.Total == 0 { + return p.validateEmpty() + } + pages := p.Total / lineItemsPageSize + if p.Total%lineItemsPageSize != 0 { + pages++ + } + if p.TotalPages != pages || p.LastPage != pages || p.Page > pages { + return errors.New("inconsistent page totals") + } + return p.validateProgress() +} + +func (p lineItemsPagination) validateEmpty() error { + if p.Page != 1 || p.TotalPages > 1 || p.LastPage != p.TotalPages { + return errors.New("invalid empty pagination") + } + return nil +} + +func (p lineItemsPagination) validateProgress() error { + if p.Page < p.TotalPages && p.NextPage != nil && *p.NextPage != p.Page+1 { + return errors.New("invalid next_page") + } + if p.Page > 1 && p.PreviousPage != nil && *p.PreviousPage != p.Page-1 { + return errors.New("invalid previous_page") + } + return nil +} + +func (p lineItemsPagination) rowCount() int64 { + if p.Total == 0 { + return 0 + } + if p.Page == p.TotalPages && p.Total%lineItemsPageSize != 0 { + return p.Total % lineItemsPageSize + } + return lineItemsPageSize +} + +// LineItems returns a complete snapshot or an error; partial pages never escape. +func (c *Client) LineItems(ctx context.Context, planID string) ([]LineItem, error) { + if !isUUID(planID) { + return nil, errors.New("archera plan ID must be a UUID") + } + rows := make([]LineItem, 0) + seen := make(map[string]bool) + remaining := int64(maxResponseBytes) + var first lineItemsPagination + for page := int64(1); ; page++ { + items, metadata, err := c.lineItemsPage(ctx, planID, page, &remaining) + if err != nil { + return nil, err + } + if page == 1 { + first = metadata + } + if first.Total != metadata.Total || first.TotalPages != metadata.TotalPages { + return nil, errors.New("X-Pagination: totals changed between pages") + } + for i := range items { + id := strings.ToLower(items[i].ID) + if seen[id] { + return nil, errors.New("duplicate line item id across pages") + } + seen[id] = true + rows = append(rows, items[i]) + } + if page >= first.TotalPages { + return rows, nil + } + } +} + +func (c *Client) lineItemsPage(ctx context.Context, planID string, page int64, remaining *int64) ([]LineItem, lineItemsPagination, error) { + q := url.Values{"page": {strconv.FormatInt(page, 10)}, "page_size": {"100"}, "order_by": {"id"}} + body, headers, _, err := c.getWithHeaders(ctx, c.planPath(planID)+"/line-items", q) + if err != nil { + return nil, lineItemsPagination{}, err + } + defer func() { _ = body.Close() }() + metadata, err := decodePagination(headers.Get("X-Pagination"), page) + if err != nil { + return nil, metadata, err + } + r := &boundedReader{r: body, n: *remaining} + items, err := DecodeLineItems(r, planID) + *remaining = r.n + if err != nil { + return nil, metadata, sanitizeDecodeError(err, c.redact) + } + if int64(len(items)) != metadata.rowCount() { + return nil, metadata, errors.New("X-Pagination: line item count disagrees with metadata") + } + return items, metadata, nil +} diff --git a/pkg/insurance/line_items_client_test.go b/pkg/insurance/line_items_client_test.go new file mode 100644 index 00000000..59db2e37 --- /dev/null +++ b/pkg/insurance/line_items_client_test.go @@ -0,0 +1,193 @@ +package insurance + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func lineItemsPage(start, count int) string { + rows := make([]string, count) + for i := range rows { + rows[i] = strings.Replace(lineItemJSON(), "22222222-2222-4222-8222-222222222222", fmt.Sprintf("%08d-2222-4222-8222-222222222222", start+i), 1) + } + return "[" + strings.Join(rows, ",") + "]" +} + +type lineItemsTransport func(*http.Request) (*http.Response, error) + +func (f lineItemsTransport) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } + +type lineItemsBody struct { + io.Reader + closed bool +} + +func (b *lineItemsBody) Close() error { b.closed = true; return nil } + +func TestClientLineItemsClosesBodies(t *testing.T) { + t.Parallel() + for _, header := range []string{paginationJSON(1, 1, 1), "", paginationJSON(2, 1, 1)} { + body := &lineItemsBody{Reader: strings.NewReader(lineItemsPage(1, 1))} + c, err := newClient(BaseURL, Config{APIKey: NewSecret(testKey), OrgID: testOrg}, &http.Client{Transport: lineItemsTransport(func(*http.Request) (*http.Response, error) { + return &http.Response{StatusCode: 200, Header: http.Header{"X-Pagination": []string{header}}, Body: body}, nil + })}) + require.NoError(t, err) + _, _ = c.LineItems(context.Background(), testPlan) + assert.True(t, body.closed) + } +} + +func TestClientLineItemsSecondPageTransportAndCancellation(t *testing.T) { + t.Parallel() + for _, failure := range []error{errors.New("connection reset"), context.Canceled} { + requests := 0 + body := &lineItemsBody{Reader: strings.NewReader(lineItemsPage(1, 100))} + c, err := newClient(BaseURL, Config{APIKey: NewSecret(testKey), OrgID: testOrg}, &http.Client{Transport: lineItemsTransport(func(*http.Request) (*http.Response, error) { + requests++ + if requests == 2 { + return nil, failure + } + return &http.Response{StatusCode: 200, Header: http.Header{"X-Pagination": []string{paginationJSON(101, 2, 1)}}, Body: body}, nil + })}) + require.NoError(t, err) + rows, err := c.LineItems(context.Background(), testPlan) + require.ErrorIs(t, err, failure) + assert.Nil(t, rows) + assert.True(t, body.closed) + assert.Equal(t, 2, requests) + assert.NotContains(t, err.Error(), testPlan) + } +} + +func TestClientLineItemsRejectsCaseEquivalentDuplicatesAcrossPages(t *testing.T) { + t.Parallel() + const id = "abcdef03-2345-4678-89ab-cdef01234567" + c := testClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + page, _ := strconv.Atoi(r.URL.Query().Get("page")) + w.Header().Set("X-Pagination", paginationJSON(101, 2, page)) + if page == 1 { + _, _ = w.Write([]byte(strings.Replace(lineItemsPage(1, 100), "00000001-2222-4222-8222-222222222222", id, 1))) + return + } + _, _ = w.Write([]byte(strings.Replace(lineItemsPage(101, 1), "00000101-2222-4222-8222-222222222222", strings.ToUpper(id), 1))) + })) + rows, err := c.LineItems(context.Background(), testPlan) + require.ErrorContains(t, err, "duplicate") + assert.Nil(t, rows) +} + +func paginationJSON(total, pages, page int) string { + return fmt.Sprintf(`{"total":%d,"total_pages":%d,"page":%d,"first_page":1,"last_page":%d,"next_page":%d,"previous_page":%d}`, total, pages, page, pages, page+1, page-1) +} + +func TestClientLineItemsPagination(t *testing.T) { + t.Parallel() + requests := 0 + c := testClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requests++ + assert.Equal(t, "/v1/org/"+testOrg+"/commitment-plans/"+testPlan+"/line-items", r.URL.Path) + assert.Equal(t, testKey, r.Header.Get("x-api-key")) + assert.Equal(t, "100", r.URL.Query().Get("page_size")) + assert.Equal(t, "id", r.URL.Query().Get("order_by")) + page, err := strconv.Atoi(r.URL.Query().Get("page")) + require.NoError(t, err) + w.Header().Set("X-Pagination", paginationJSON(101, 2, page)) + if page == 1 { + _, _ = w.Write([]byte(lineItemsPage(1, 100))) + return + } + _, _ = w.Write([]byte(lineItemsPage(101, 1))) + })) + rows, err := c.LineItems(context.Background(), testPlan) + require.NoError(t, err) + assert.Len(t, rows, 101) + assert.Equal(t, 2, requests) +} + +func TestClientLineItemsEmpty(t *testing.T) { + t.Parallel() + for _, pages := range []int{0, 1} { + c := testClient(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("X-Pagination", paginationJSON(0, pages, 1)) + _, _ = w.Write([]byte(`[]`)) + })) + rows, err := c.LineItems(context.Background(), testPlan) + require.NoError(t, err) + assert.NotNil(t, rows) + assert.Empty(t, rows) + } +} + +func TestClientLineItemsRejectsIncompletePages(t *testing.T) { + t.Parallel() + cases := map[string]struct{ header, body string }{ + "missing header": {"", "[]"}, + "null header": {"null", "[]"}, + "missing total": {`{"total_pages":0,"page":1,"first_page":1,"last_page":0}`, "[]"}, + "null total": {`{"total":null,"total_pages":0,"page":1,"first_page":1,"last_page":0}`, "[]"}, + "wrong page": {paginationJSON(1, 1, 2), lineItemsPage(1, 1)}, + "bad pages": {paginationJSON(101, 1, 1), lineItemsPage(1, 100)}, + "short page": {paginationJSON(101, 2, 1), lineItemsPage(1, 99)}, + "empty middle": {paginationJSON(201, 3, 1), "[]"}, + "overflow": {`{"total":9223372036854775808,"total_pages":1,"page":1,"first_page":1,"last_page":1}`, "[]"}, + "max int": {`{"total":9223372036854775807,"total_pages":1,"page":1,"first_page":1,"last_page":1}`, "[]"}, + "bad next": {strings.Replace(paginationJSON(101, 2, 1), `"next_page":2`, `"next_page":3`, 1), lineItemsPage(1, 100)}, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + c := testClient(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("X-Pagination", tc.header) + _, _ = w.Write([]byte(tc.body)) + })) + rows, err := c.LineItems(context.Background(), testPlan) + require.Error(t, err) + assert.Nil(t, rows) + }) + } +} + +func TestClientLineItemsSecondPageFailure(t *testing.T) { + t.Parallel() + for _, failure := range []string{"HTTP", "total", "duplicate", "size", "previous", "decode"} { + t.Run(failure, func(t *testing.T) { + c := testClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + page, _ := strconv.Atoi(r.URL.Query().Get("page")) + w.Header().Set("X-Pagination", paginationJSON(101, 2, page)) + if page == 1 { + _, _ = w.Write([]byte(lineItemsPage(1, 100))) + return + } + switch failure { + case "HTTP": + w.WriteHeader(500) + _, _ = w.Write([]byte(`{"message":"` + testKey + `"}`)) + case "total": + w.Header().Set("X-Pagination", paginationJSON(102, 2, 2)) + _, _ = w.Write([]byte(lineItemsPage(101, 2))) + case "duplicate": + _, _ = w.Write([]byte(lineItemsPage(1, 1))) + case "size": + _, _ = w.Write([]byte(lineItemsPage(101, 1) + strings.Repeat(" ", maxResponseBytes-100))) + case "previous": + w.Header().Set("X-Pagination", strings.Replace(paginationJSON(101, 2, 2), `"previous_page":1`, `"previous_page":0`, 1)) + _, _ = w.Write([]byte(lineItemsPage(101, 1))) + case "decode": + _, _ = w.Write([]byte(strings.Replace(lineItemsPage(101, 1), `"provider":"aws"`, `"provider":"`+testKey+`"`, 1))) + } + })) + rows, err := c.LineItems(context.Background(), testPlan) + require.Error(t, err) + assert.Nil(t, rows) + assert.NotContains(t, err.Error(), testKey) + }) + } +} From b9050be6b409541b418d536c5256f213f0744ff9 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Thu, 15 Oct 2026 00:51:30 +0200 Subject: [PATCH 2/4] test(insurance): isolate pagination guard failures --- pkg/insurance/line_items_client.go | 2 +- pkg/insurance/line_items_client_test.go | 36 ++++++++++++++++--------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/pkg/insurance/line_items_client.go b/pkg/insurance/line_items_client.go index 6bb824b8..4925dfbf 100644 --- a/pkg/insurance/line_items_client.go +++ b/pkg/insurance/line_items_client.go @@ -85,7 +85,7 @@ func (p lineItemsPagination) rowCount() int64 { return lineItemsPageSize } -// LineItems returns a complete snapshot or an error; partial pages never escape. +// LineItems returns all paginated rows or an error; partial pages never escape. func (c *Client) LineItems(ctx context.Context, planID string) ([]LineItem, error) { if !isUUID(planID) { return nil, errors.New("archera plan ID must be a UUID") diff --git a/pkg/insurance/line_items_client_test.go b/pkg/insurance/line_items_client_test.go index 59db2e37..19ae8307 100644 --- a/pkg/insurance/line_items_client_test.go +++ b/pkg/insurance/line_items_client_test.go @@ -130,27 +130,33 @@ func TestClientLineItemsEmpty(t *testing.T) { func TestClientLineItemsRejectsIncompletePages(t *testing.T) { t.Parallel() cases := map[string]struct{ header, body string }{ - "missing header": {"", "[]"}, - "null header": {"null", "[]"}, - "missing total": {`{"total_pages":0,"page":1,"first_page":1,"last_page":0}`, "[]"}, - "null total": {`{"total":null,"total_pages":0,"page":1,"first_page":1,"last_page":0}`, "[]"}, - "wrong page": {paginationJSON(1, 1, 2), lineItemsPage(1, 1)}, - "bad pages": {paginationJSON(101, 1, 1), lineItemsPage(1, 100)}, - "short page": {paginationJSON(101, 2, 1), lineItemsPage(1, 99)}, - "empty middle": {paginationJSON(201, 3, 1), "[]"}, - "overflow": {`{"total":9223372036854775808,"total_pages":1,"page":1,"first_page":1,"last_page":1}`, "[]"}, - "max int": {`{"total":9223372036854775807,"total_pages":1,"page":1,"first_page":1,"last_page":1}`, "[]"}, - "bad next": {strings.Replace(paginationJSON(101, 2, 1), `"next_page":2`, `"next_page":3`, 1), lineItemsPage(1, 100)}, + "missing header": {"", "[]"}, + "null header": {"null", "[]"}, + "missing total": {`{"total_pages":0,"page":1,"first_page":1,"last_page":0}`, "[]"}, + "null total": {`{"total":null,"total_pages":0,"page":1,"first_page":1,"last_page":0}`, "[]"}, + "wrong page": {paginationJSON(1, 1, 2), lineItemsPage(1, 1)}, + "bad pages": {paginationJSON(101, 1, 1), lineItemsPage(1, 100)}, + "short page": {paginationJSON(101, 2, 1), lineItemsPage(1, 99)}, + "empty middle": {paginationJSON(201, 3, 1), "[]"}, + "terminal missing row": {paginationJSON(1, 1, 1), "[]"}, + "terminal unexpected row": {paginationJSON(0, 0, 1), lineItemsPage(1, 1)}, + "terminal excess row": {paginationJSON(1, 1, 1), lineItemsPage(1, 2)}, + "overflow": {`{"total":9223372036854775808,"total_pages":1,"page":1,"first_page":1,"last_page":1}`, "[]"}, + "max int": {`{"total":9223372036854775807,"total_pages":1,"page":1,"first_page":1,"last_page":1}`, "[]"}, + "bad next": {strings.Replace(paginationJSON(101, 2, 1), `"next_page":2`, `"next_page":3`, 1), lineItemsPage(1, 100)}, } for name, tc := range cases { t.Run(name, func(t *testing.T) { + requests := 0 c := testClient(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + requests++ w.Header().Set("X-Pagination", tc.header) _, _ = w.Write([]byte(tc.body)) })) rows, err := c.LineItems(context.Background(), testPlan) require.Error(t, err) assert.Nil(t, rows) + assert.Equal(t, 1, requests) }) } } @@ -176,7 +182,13 @@ func TestClientLineItemsSecondPageFailure(t *testing.T) { case "duplicate": _, _ = w.Write([]byte(lineItemsPage(1, 1))) case "size": - _, _ = w.Write([]byte(lineItemsPage(101, 1) + strings.Repeat(" ", maxResponseBytes-100))) + firstSize := len(lineItemsPage(1, 100)) + second := lineItemsPage(101, 1) + second += strings.Repeat(" ", maxResponseBytes-firstSize/2-len(second)) + assert.Less(t, firstSize, maxResponseBytes) + assert.Less(t, len(second), maxResponseBytes) + assert.Greater(t, firstSize+len(second), maxResponseBytes) + _, _ = w.Write([]byte(second)) case "previous": w.Header().Set("X-Pagination", strings.Replace(paginationJSON(101, 2, 2), `"previous_page":1`, `"previous_page":0`, 1)) _, _ = w.Write([]byte(lineItemsPage(101, 1))) From b1be8413849cfd1736fe5b9cbdedf408af658e2a Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Fri, 16 Oct 2026 11:48:14 +0200 Subject: [PATCH 3/4] test(insurance): cover pagination guard mutants Cover empty-result and page-total consistency guards, requested-page, first_page, negative total, last_page, null next_page and non-UUID plan ID. Co-Authored-By: Claude Opus 5.5 --- pkg/insurance/line_items_client_test.go | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/pkg/insurance/line_items_client_test.go b/pkg/insurance/line_items_client_test.go index 19ae8307..253b1adf 100644 --- a/pkg/insurance/line_items_client_test.go +++ b/pkg/insurance/line_items_client_test.go @@ -203,3 +203,47 @@ func TestClientLineItemsSecondPageFailure(t *testing.T) { }) } } + +func TestClientLineItemsPaginationGuards(t *testing.T) { + t.Parallel() + hdr := func(total, totalPages, page, first, last int, next string) string { + extra := "" + if next != "" { + extra = `,"next_page":` + next + } + return fmt.Sprintf(`{"total":%d,"total_pages":%d,"page":%d,"first_page":%d,"last_page":%d%s}`, total, totalPages, page, first, last, extra) + } + cases := map[string]struct{ header, body, want string }{ + "empty with huge total_pages": {hdr(0, 1000000, 1, 1, 1000000, ""), "[]", "invalid empty pagination"}, + "total pages disagree": {hdr(250, 2, 1, 1, 2, "2"), lineItemsPage(1, 100), "inconsistent page totals"}, + "last page disagrees": {hdr(101, 2, 1, 1, 3, "2"), lineItemsPage(1, 100), "inconsistent page totals"}, + "response page not requested": {hdr(201, 3, 2, 1, 3, "3"), lineItemsPage(1, 100), "invalid total or page counters"}, + "first page not one": {hdr(1, 1, 1, 0, 1, ""), lineItemsPage(1, 1), "invalid total or page counters"}, + "negative total": {hdr(-1, 1, 1, 1, 1, ""), "[]", "invalid total or page counters"}, + "null next page on first page": {hdr(101, 2, 1, 1, 2, "null"), lineItemsPage(1, 100), "next_page"}, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + requests := 0 + c := testClient(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + requests++ + w.Header().Set("X-Pagination", tc.header) + _, _ = w.Write([]byte(tc.body)) + })) + rows, err := c.LineItems(context.Background(), testPlan) + require.ErrorContains(t, err, tc.want) + assert.Nil(t, rows) + assert.Equal(t, 1, requests) + }) + } +} + +func TestClientLineItemsRejectsNonUUIDPlanBeforeRequest(t *testing.T) { + t.Parallel() + requests := 0 + c := testClient(t, http.HandlerFunc(func(http.ResponseWriter, *http.Request) { requests++ })) + rows, err := c.LineItems(context.Background(), "../other/plan") + require.ErrorContains(t, err, "UUID") + assert.Nil(t, rows) + assert.Zero(t, requests) +} From bb81244582d9277f3e64b10e2371441f0067baf4 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Fri, 16 Oct 2026 12:03:49 +0200 Subject: [PATCH 4/4] test(insurance): isolate total_pages consistency guard Co-Authored-By: Claude Opus 5.5 --- pkg/insurance/line_items_client_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/insurance/line_items_client_test.go b/pkg/insurance/line_items_client_test.go index 253b1adf..6f92a6f2 100644 --- a/pkg/insurance/line_items_client_test.go +++ b/pkg/insurance/line_items_client_test.go @@ -216,6 +216,7 @@ func TestClientLineItemsPaginationGuards(t *testing.T) { cases := map[string]struct{ header, body, want string }{ "empty with huge total_pages": {hdr(0, 1000000, 1, 1, 1000000, ""), "[]", "invalid empty pagination"}, "total pages disagree": {hdr(250, 2, 1, 1, 2, "2"), lineItemsPage(1, 100), "inconsistent page totals"}, + "total pages alone disagrees": {hdr(250, 2, 1, 1, 3, "2"), lineItemsPage(1, 100), "inconsistent page totals"}, "last page disagrees": {hdr(101, 2, 1, 1, 3, "2"), lineItemsPage(1, 100), "inconsistent page totals"}, "response page not requested": {hdr(201, 3, 2, 1, 3, "3"), lineItemsPage(1, 100), "invalid total or page counters"}, "first page not one": {hdr(1, 1, 1, 0, 1, ""), lineItemsPage(1, 1), "invalid total or page counters"},