mirror of
https://github.com/hanzo-fi/go-libs.git
synced 2026-10-30 20:53:44 +00:00
810 lines
21 KiB
Go
810 lines
21 KiB
Go
package api_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
logging "github.com/hanzo-fi/go-libs/v5/pkg/observe/log"
|
|
bunpaginate "github.com/hanzo-fi/go-libs/v5/pkg/storage/bun/paginate"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/transport/api"
|
|
)
|
|
|
|
func TestWriteErrorResponse(t *testing.T) {
|
|
t.Parallel()
|
|
testCases := []struct {
|
|
name string
|
|
statusCode int
|
|
errorCode string
|
|
err error
|
|
expected api.ErrorResponse
|
|
}{
|
|
{
|
|
name: "basic error",
|
|
statusCode: http.StatusBadRequest,
|
|
errorCode: "INVALID_REQUEST",
|
|
err: errors.New("invalid request"),
|
|
expected: api.ErrorResponse{
|
|
ErrorCode: "INVALID_REQUEST",
|
|
ErrorMessage: "invalid request",
|
|
},
|
|
},
|
|
{
|
|
name: "not found error",
|
|
statusCode: http.StatusNotFound,
|
|
errorCode: api.ErrorCodeNotFound,
|
|
err: errors.New("resource not found"),
|
|
expected: api.ErrorResponse{
|
|
ErrorCode: api.ErrorCodeNotFound,
|
|
ErrorMessage: "resource not found",
|
|
},
|
|
},
|
|
{
|
|
name: "internal error",
|
|
statusCode: http.StatusInternalServerError,
|
|
errorCode: api.ErrorInternal,
|
|
err: errors.New("internal server error"),
|
|
expected: api.ErrorResponse{
|
|
ErrorCode: api.ErrorInternal,
|
|
ErrorMessage: "internal server error",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
|
|
// Call the function
|
|
api.WriteErrorResponse(rr, tc.statusCode, tc.errorCode, tc.err)
|
|
|
|
// Check the status code
|
|
require.Equal(t, tc.statusCode, rr.Code)
|
|
|
|
// Check the content type
|
|
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
// Check the response body
|
|
var response api.ErrorResponse
|
|
err := json.NewDecoder(rr.Body).Decode(&response)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expected, response)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNotFound(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
err := errors.New("resource not found")
|
|
|
|
// Call the function
|
|
api.NotFound(rr, err)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusNotFound, rr.Code)
|
|
|
|
// Check the content type
|
|
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
// Check the response body
|
|
var response api.ErrorResponse
|
|
decodeErr := json.NewDecoder(rr.Body).Decode(&response)
|
|
require.NoError(t, decodeErr)
|
|
require.Equal(t, api.ErrorCodeNotFound, response.ErrorCode)
|
|
require.Equal(t, err.Error(), response.ErrorMessage)
|
|
}
|
|
|
|
func TestNoContent(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
|
|
// Call the function
|
|
api.NoContent(rr)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusNoContent, rr.Code)
|
|
|
|
// Check that the body is empty
|
|
require.Empty(t, rr.Body.String())
|
|
}
|
|
|
|
func TestForbidden(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
err := errors.New("access denied")
|
|
code := "ACCESS_DENIED"
|
|
|
|
// Call the function
|
|
api.Forbidden(rr, code, err)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusForbidden, rr.Code)
|
|
|
|
// Check the content type
|
|
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
// Check the response body
|
|
var response api.ErrorResponse
|
|
decodeErr := json.NewDecoder(rr.Body).Decode(&response)
|
|
require.NoError(t, decodeErr)
|
|
require.Equal(t, code, response.ErrorCode)
|
|
require.Equal(t, err.Error(), response.ErrorMessage)
|
|
}
|
|
|
|
func TestBadRequest(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
err := errors.New("invalid request")
|
|
code := "INVALID_REQUEST"
|
|
|
|
// Call the function
|
|
api.BadRequest(rr, code, err)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusBadRequest, rr.Code)
|
|
|
|
// Check the content type
|
|
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
// Check the response body
|
|
var response api.ErrorResponse
|
|
decodeErr := json.NewDecoder(rr.Body).Decode(&response)
|
|
require.NoError(t, decodeErr)
|
|
require.Equal(t, code, response.ErrorCode)
|
|
require.Equal(t, err.Error(), response.ErrorMessage)
|
|
}
|
|
|
|
func TestBadRequestWithDetails(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
err := errors.New("invalid request")
|
|
code := "INVALID_REQUEST"
|
|
details := "Additional error details"
|
|
|
|
// Call the function
|
|
api.BadRequestWithDetails(rr, code, err, details)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusBadRequest, rr.Code)
|
|
|
|
// Check the content type
|
|
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
// Check the response body
|
|
var response api.ErrorResponse
|
|
decodeErr := json.NewDecoder(rr.Body).Decode(&response)
|
|
require.NoError(t, decodeErr)
|
|
require.Equal(t, code, response.ErrorCode)
|
|
require.Equal(t, err.Error(), response.ErrorMessage)
|
|
require.Equal(t, details, response.Details)
|
|
}
|
|
|
|
func TestInternalServerError(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a buffer to capture log output
|
|
var buf bytes.Buffer
|
|
logger := logging.NewDefaultLogger(&buf, true, false, false)
|
|
|
|
// Create a request with the logger in context
|
|
req, err := http.NewRequest("GET", "/test", nil)
|
|
require.NoError(t, err)
|
|
ctx := logging.ContextWithLogger(context.Background(), logger)
|
|
req = req.WithContext(ctx)
|
|
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
testErr := errors.New(`pq: duplicate key value violates unique constraint "accounts_pkey" at /srv/app/internal/storage/accounts.go:42`)
|
|
|
|
// Call the function
|
|
api.InternalServerError(rr, req, testErr)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusInternalServerError, rr.Code)
|
|
|
|
// Check the content type
|
|
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
// Check the response body
|
|
rawBody := rr.Body.String()
|
|
var response api.ErrorResponse
|
|
decodeErr := json.NewDecoder(bytes.NewBufferString(rawBody)).Decode(&response)
|
|
require.NoError(t, decodeErr)
|
|
require.Equal(t, api.ErrorInternal, response.ErrorCode)
|
|
require.Equal(t, "internal server error", response.ErrorMessage)
|
|
require.NotContains(t, rawBody, "accounts_pkey")
|
|
require.NotContains(t, rawBody, "/srv/app/internal/storage/accounts.go")
|
|
|
|
// Verify that the error was logged
|
|
logOutput := buf.String()
|
|
require.Contains(t, logOutput, "accounts_pkey")
|
|
require.Contains(t, logOutput, "/srv/app/internal/storage/accounts.go")
|
|
}
|
|
|
|
func TestAccepted(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
data := map[string]string{"message": "accepted"}
|
|
|
|
// Call the function
|
|
api.Accepted(rr, data)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusAccepted, rr.Code)
|
|
|
|
// Check the content type
|
|
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
// Check the response body
|
|
var response api.BaseResponse[any]
|
|
decodeErr := json.NewDecoder(rr.Body).Decode(&response)
|
|
require.NoError(t, decodeErr)
|
|
require.NotNil(t, response.Data)
|
|
|
|
// Convert the data to a map
|
|
dataMap, ok := (*response.Data).(map[string]interface{})
|
|
require.True(t, ok)
|
|
require.Equal(t, "accepted", dataMap["message"])
|
|
}
|
|
|
|
func TestCreated(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
data := map[string]string{"id": "123", "name": "test"}
|
|
|
|
// Call the function
|
|
api.Created(rr, data)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusCreated, rr.Code)
|
|
|
|
// Check the content type
|
|
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
// Check the response body
|
|
var response api.BaseResponse[any]
|
|
decodeErr := json.NewDecoder(rr.Body).Decode(&response)
|
|
require.NoError(t, decodeErr)
|
|
require.NotNil(t, response.Data)
|
|
|
|
// Convert the data to a map
|
|
dataMap, ok := (*response.Data).(map[string]interface{})
|
|
require.True(t, ok)
|
|
require.Equal(t, "123", dataMap["id"])
|
|
require.Equal(t, "test", dataMap["name"])
|
|
}
|
|
|
|
func TestRawOk(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
data := map[string]string{"message": "success"}
|
|
|
|
// Call the function
|
|
api.RawOk(rr, data)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
// Check the content type
|
|
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
// Check the response body
|
|
var response map[string]string
|
|
decodeErr := json.NewDecoder(rr.Body).Decode(&response)
|
|
require.NoError(t, decodeErr)
|
|
require.Equal(t, "success", response["message"])
|
|
}
|
|
|
|
func TestOk(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
data := map[string]string{"message": "success"}
|
|
|
|
// Call the function
|
|
api.Ok(rr, data)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
// Check the content type
|
|
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
// Check the response body
|
|
var response api.BaseResponse[any]
|
|
decodeErr := json.NewDecoder(rr.Body).Decode(&response)
|
|
require.NoError(t, decodeErr)
|
|
require.NotNil(t, response.Data)
|
|
|
|
// Convert the data to a map
|
|
dataMap, ok := (*response.Data).(map[string]interface{})
|
|
require.True(t, ok)
|
|
require.Equal(t, "success", dataMap["message"])
|
|
}
|
|
|
|
func TestRenderCursor(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
cursor := bunpaginate.Cursor[string]{
|
|
PageSize: 10,
|
|
HasMore: true,
|
|
Next: "next-token",
|
|
Previous: "prev-token",
|
|
Data: []string{"item1", "item2", "item3"},
|
|
}
|
|
|
|
// Call the function
|
|
api.RenderCursor(rr, cursor)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
// Check the content type
|
|
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
// Check the response body
|
|
var response api.BaseResponse[string]
|
|
decodeErr := json.NewDecoder(rr.Body).Decode(&response)
|
|
require.NoError(t, decodeErr)
|
|
require.Nil(t, response.Data)
|
|
require.NotNil(t, response.Cursor)
|
|
require.Equal(t, cursor.PageSize, response.Cursor.PageSize)
|
|
require.Equal(t, cursor.HasMore, response.Cursor.HasMore)
|
|
require.Equal(t, cursor.Next, response.Cursor.Next)
|
|
require.Equal(t, cursor.Previous, response.Cursor.Previous)
|
|
require.Equal(t, cursor.Data, response.Cursor.Data)
|
|
}
|
|
|
|
func TestWriteResponse(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
body := []byte(`{"message":"test"}`)
|
|
|
|
// Call the function
|
|
api.WriteResponse(rr, http.StatusOK, body)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
// Check the response body
|
|
require.Equal(t, string(body), rr.Body.String())
|
|
}
|
|
|
|
func TestCursorFromListResponse(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a response recorder
|
|
rr := httptest.NewRecorder()
|
|
|
|
// Create a list query
|
|
query := api.ListQuery[string]{
|
|
Pagination: api.Pagination{
|
|
Limit: 10,
|
|
PaginationToken: "token",
|
|
},
|
|
Payload: "test",
|
|
}
|
|
|
|
// Create a list response
|
|
response := &api.ListResponse[string]{
|
|
Data: []string{"item1", "item2", "item3"},
|
|
Next: "next-token",
|
|
Previous: "prev-token",
|
|
HasMore: true,
|
|
}
|
|
|
|
// Call the function
|
|
api.CursorFromListResponse(rr, query, response)
|
|
|
|
// Check the status code
|
|
require.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
// Check the content type
|
|
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
|
|
|
// Check the response body
|
|
var apiResponse api.BaseResponse[string]
|
|
decodeErr := json.NewDecoder(rr.Body).Decode(&apiResponse)
|
|
require.NoError(t, decodeErr)
|
|
require.Nil(t, apiResponse.Data)
|
|
require.NotNil(t, apiResponse.Cursor)
|
|
require.Equal(t, query.Limit, apiResponse.Cursor.PageSize)
|
|
require.Equal(t, response.HasMore, apiResponse.Cursor.HasMore)
|
|
require.Equal(t, response.Next, apiResponse.Cursor.Next)
|
|
require.Equal(t, response.Previous, apiResponse.Cursor.Previous)
|
|
require.Equal(t, response.Data, apiResponse.Cursor.Data)
|
|
}
|
|
|
|
func TestParsePaginationToken(t *testing.T) {
|
|
t.Parallel()
|
|
testCases := []struct {
|
|
name string
|
|
queryParams map[string]string
|
|
expectedResult string
|
|
}{
|
|
{
|
|
name: "with pagination token",
|
|
queryParams: map[string]string{
|
|
"paginationToken": "test-token",
|
|
},
|
|
expectedResult: "test-token",
|
|
},
|
|
{
|
|
name: "without pagination token",
|
|
queryParams: map[string]string{},
|
|
expectedResult: "",
|
|
},
|
|
{
|
|
name: "with empty pagination token",
|
|
queryParams: map[string]string{
|
|
"paginationToken": "",
|
|
},
|
|
expectedResult: "",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a URL with query parameters
|
|
u, _ := url.Parse("http://example.com")
|
|
q := u.Query()
|
|
for k, v := range tc.queryParams {
|
|
q.Set(k, v)
|
|
}
|
|
u.RawQuery = q.Encode()
|
|
|
|
// Create a request with the URL
|
|
req, _ := http.NewRequest("GET", u.String(), nil)
|
|
|
|
// Call the function
|
|
result := api.ParsePaginationToken(req)
|
|
|
|
// Check the result
|
|
require.Equal(t, tc.expectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParsePageSize(t *testing.T) {
|
|
t.Parallel()
|
|
testCases := []struct {
|
|
name string
|
|
queryParams map[string]string
|
|
expectedResult int
|
|
}{
|
|
{
|
|
name: "with page size",
|
|
queryParams: map[string]string{
|
|
"pageSize": "20",
|
|
},
|
|
expectedResult: 20,
|
|
},
|
|
{
|
|
name: "without page size",
|
|
queryParams: map[string]string{},
|
|
expectedResult: 15, // default limit
|
|
},
|
|
{
|
|
name: "with empty page size",
|
|
queryParams: map[string]string{
|
|
"pageSize": "",
|
|
},
|
|
expectedResult: 15, // default limit
|
|
},
|
|
{
|
|
name: "with max page size",
|
|
queryParams: map[string]string{
|
|
"pageSize": "100",
|
|
},
|
|
expectedResult: api.MaxPageSize,
|
|
},
|
|
{
|
|
name: "with non-numeric page size",
|
|
queryParams: map[string]string{
|
|
"pageSize": "invalid",
|
|
},
|
|
expectedResult: 15, // falls back to default limit
|
|
},
|
|
{
|
|
name: "with negative page size",
|
|
queryParams: map[string]string{
|
|
"pageSize": "-1",
|
|
},
|
|
expectedResult: 15, // falls back to default limit
|
|
},
|
|
{
|
|
name: "with zero page size",
|
|
queryParams: map[string]string{
|
|
"pageSize": "0",
|
|
},
|
|
expectedResult: 15, // falls back to default limit
|
|
},
|
|
{
|
|
name: "with page size above max",
|
|
queryParams: map[string]string{
|
|
"pageSize": "101",
|
|
},
|
|
expectedResult: api.MaxPageSize, // clamped
|
|
},
|
|
{
|
|
name: "with huge page size overflowing int32",
|
|
queryParams: map[string]string{
|
|
"pageSize": "99999999999999999999",
|
|
},
|
|
expectedResult: 15, // falls back to default limit
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a URL with query parameters
|
|
u, _ := url.Parse("http://example.com")
|
|
q := u.Query()
|
|
for k, v := range tc.queryParams {
|
|
q.Set(k, v)
|
|
}
|
|
u.RawQuery = q.Encode()
|
|
|
|
// Create a request with the URL
|
|
req, _ := http.NewRequest("GET", u.String(), nil)
|
|
|
|
// Call the function, it must never panic
|
|
var result int
|
|
require.NotPanics(t, func() {
|
|
result = api.ParsePageSize(req)
|
|
})
|
|
|
|
// Check the result
|
|
require.Equal(t, tc.expectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReadPaginatedRequest(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a URL with query parameters
|
|
u, _ := url.Parse("http://example.com")
|
|
q := u.Query()
|
|
q.Set("pageSize", "20")
|
|
q.Set("paginationToken", "test-token")
|
|
u.RawQuery = q.Encode()
|
|
|
|
// Create a request with the URL
|
|
req, _ := http.NewRequest("GET", u.String(), nil)
|
|
|
|
// Define a payload extractor function
|
|
type TestPayload struct {
|
|
Filter string
|
|
}
|
|
payloadExtractor := func(r *http.Request) TestPayload {
|
|
return TestPayload{
|
|
Filter: r.URL.Query().Get("filter"),
|
|
}
|
|
}
|
|
|
|
// Call the function
|
|
result := api.ReadPaginatedRequest(req, payloadExtractor)
|
|
|
|
// Check the result
|
|
require.Equal(t, 20, result.Limit)
|
|
require.Equal(t, "test-token", result.PaginationToken)
|
|
require.Equal(t, TestPayload{Filter: ""}, result.Payload)
|
|
|
|
// Test with nil payload extractor
|
|
result = api.ReadPaginatedRequest[TestPayload](req, nil)
|
|
require.Equal(t, 20, result.Limit)
|
|
require.Equal(t, "test-token", result.PaginationToken)
|
|
require.Equal(t, TestPayload{}, result.Payload)
|
|
|
|
// Invalid page size must fall back to the default limit, not panic
|
|
u, _ = url.Parse("http://example.com")
|
|
q = u.Query()
|
|
q.Set("pageSize", "invalid")
|
|
u.RawQuery = q.Encode()
|
|
req, _ = http.NewRequest("GET", u.String(), nil)
|
|
|
|
require.NotPanics(t, func() {
|
|
result = api.ReadPaginatedRequest(req, payloadExtractor)
|
|
})
|
|
require.Equal(t, 15, result.Limit) // default limit
|
|
}
|
|
|
|
func TestGetQueryMap(t *testing.T) {
|
|
t.Parallel()
|
|
testCases := []struct {
|
|
name string
|
|
queryParams map[string][]string
|
|
key string
|
|
expectedResult map[string]string
|
|
}{
|
|
{
|
|
name: "with query map",
|
|
queryParams: map[string][]string{
|
|
"filter[name]": {"john"},
|
|
"filter[age]": {"30"},
|
|
"filter[email]": {"[email protected]"},
|
|
"sort": {"name"},
|
|
},
|
|
key: "filter",
|
|
expectedResult: map[string]string{
|
|
"name": "john",
|
|
"age": "30",
|
|
"email": "[email protected]",
|
|
},
|
|
},
|
|
{
|
|
name: "without query map",
|
|
queryParams: map[string][]string{
|
|
"sort": {"name"},
|
|
},
|
|
key: "filter",
|
|
expectedResult: map[string]string{},
|
|
},
|
|
{
|
|
name: "with empty query map",
|
|
queryParams: map[string][]string{
|
|
"filter[]": {"empty"},
|
|
},
|
|
key: "filter",
|
|
expectedResult: map[string]string{},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
// Call the function
|
|
result := api.GetQueryMap(tc.queryParams, tc.key)
|
|
|
|
// Check the result
|
|
require.Equal(t, tc.expectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFetchAllPaginated(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a test server
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
cursor := r.URL.Query().Get("cursor")
|
|
|
|
// First request
|
|
if cursor == "" {
|
|
response := api.BaseResponse[string]{
|
|
Cursor: &bunpaginate.Cursor[string]{
|
|
HasMore: true,
|
|
Next: "next-token",
|
|
Data: []string{"item1", "item2"},
|
|
},
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
_ = json.NewEncoder(w).Encode(response)
|
|
return
|
|
}
|
|
|
|
// Second request
|
|
if cursor == "next-token" {
|
|
response := api.BaseResponse[string]{
|
|
Cursor: &bunpaginate.Cursor[string]{
|
|
HasMore: false,
|
|
Data: []string{"item3", "item4"},
|
|
},
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
_ = json.NewEncoder(w).Encode(response)
|
|
return
|
|
}
|
|
|
|
// Unexpected cursor
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Create a client
|
|
client := server.Client()
|
|
|
|
// Call the function
|
|
ctx := context.Background()
|
|
queryParams := url.Values{
|
|
"param1": []string{"value1"},
|
|
}
|
|
|
|
result, err := api.FetchAllPaginated[string](ctx, client, server.URL, queryParams)
|
|
|
|
// Check the result
|
|
require.NoError(t, err)
|
|
require.Equal(t, []string{"item1", "item2", "item3", "item4"}, result)
|
|
|
|
// Test with error response
|
|
errorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer errorServer.Close()
|
|
|
|
_, err = api.FetchAllPaginated[string](ctx, errorServer.Client(), errorServer.URL, queryParams)
|
|
require.Error(t, err)
|
|
|
|
// Test with invalid JSON response
|
|
invalidServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte("invalid json"))
|
|
}))
|
|
defer invalidServer.Close()
|
|
|
|
_, err = api.FetchAllPaginated[string](ctx, invalidServer.Client(), invalidServer.URL, queryParams)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestErrorResponse(t *testing.T) {
|
|
t.Parallel()
|
|
// Test Error method
|
|
errorResponse := api.ErrorResponse{
|
|
ErrorCode: "TEST_ERROR",
|
|
ErrorMessage: "test error message",
|
|
}
|
|
|
|
require.Equal(t, "[TEST_ERROR] test error message", errorResponse.Error())
|
|
}
|
|
|
|
func TestResponseUtils(t *testing.T) {
|
|
t.Run("Encode", func(t *testing.T) {
|
|
t.Parallel()
|
|
data := map[string]string{"key": "value"}
|
|
encoded := api.Encode(t, data)
|
|
require.Equal(t, `{"key":"value"}`, string(encoded))
|
|
})
|
|
|
|
t.Run("Buffer", func(t *testing.T) {
|
|
t.Parallel()
|
|
data := map[string]string{"key": "value"}
|
|
buffer := api.Buffer(t, data)
|
|
require.Equal(t, `{"key":"value"}`, buffer.String())
|
|
})
|
|
|
|
t.Run("Decode", func(t *testing.T) {
|
|
t.Parallel()
|
|
data := map[string]string{"key": "value"}
|
|
buffer := bytes.NewBufferString(`{"key":"value"}`)
|
|
var result map[string]string
|
|
api.Decode(t, buffer, &result)
|
|
require.Equal(t, data, result)
|
|
})
|
|
|
|
t.Run("DecodeSingleResponse", func(t *testing.T) {
|
|
t.Parallel()
|
|
buffer := bytes.NewBufferString(`{"data":{"key":"value"}}`)
|
|
result, ok := api.DecodeSingleResponse[map[string]string](t, buffer)
|
|
require.True(t, ok)
|
|
require.Equal(t, map[string]string{"key": "value"}, result)
|
|
})
|
|
|
|
t.Run("DecodeCursorResponse", func(t *testing.T) {
|
|
t.Parallel()
|
|
buffer := bytes.NewBufferString(`{"cursor":{"hasMore":true,"data":["item1","item2"]}}`)
|
|
cursor := api.DecodeCursorResponse[string](t, buffer)
|
|
require.NotNil(t, cursor)
|
|
require.True(t, cursor.HasMore)
|
|
require.Equal(t, []string{"item1", "item2"}, cursor.Data)
|
|
})
|
|
}
|