Skip to content
This repository was archived by the owner on Aug 4, 2025. It is now read-only.

Commit 4498baf

Browse files
authored
feat: add pagination functionality (#68)
1 parent d0e4230 commit 4498baf

File tree

4 files changed

+360
-30
lines changed

4 files changed

+360
-30
lines changed

cmd/tx-sidecar/autoproperty.go

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -323,13 +323,48 @@ func (s *Server) RunAutoProperty(developerUsers, investorUsers []string) {
323323

324324
ctx := context.Background()
325325

326-
// Initial setup: create two off plan properties
327-
for i := 0; i < 2; i++ {
326+
// Initial setup: create ten off plan properties for more test data
327+
for i := 0; i < 10; i++ {
328328
if err := s.createOffPlanProperty(developerUsers); err != nil {
329329
zlog.Error().Err(err).Msg("autoproperty create off plan")
330330
}
331331
}
332332

333+
// Initial batch creation: create 20 properties quickly for testing pagination
334+
zlog.Info().Msg("AutoProperty: Creating initial batch of properties for testing...")
335+
for i := 0; i < 20; i++ {
336+
zlog.Info().Msgf("AutoProperty: Creating initial property %d/20...", i+1)
337+
338+
p, err := s.registerProperty(ctx, developerUsers)
339+
if err != nil {
340+
zlog.Error().Err(err).Msgf("autoproperty batch register property %d", i+1)
341+
continue
342+
}
343+
344+
// Edit metadata for the property
345+
if err := s.autoEditPropertyMetadata(ctx, &p); err != nil {
346+
zlog.Error().Err(err).Msgf("autoproperty batch edit metadata %d", i+1)
347+
}
348+
349+
// Add some variety by randomly listing for sale or transferring shares
350+
if rand.Intn(3) == 0 { // 33% chance to list for sale
351+
if err := s.listPropertyForSale(&p); err != nil {
352+
zlog.Error().Err(err).Msgf("autoproperty batch list for sale %d", i+1)
353+
}
354+
}
355+
356+
if rand.Intn(2) == 0 { // 50% chance to transfer shares
357+
if err := s.transferShares(ctx, &p, investorUsers); err != nil {
358+
zlog.Error().Err(err).Msgf("autoproperty batch transfer %d", i+1)
359+
}
360+
}
361+
362+
// Short delay to avoid overwhelming the system
363+
time.Sleep(1 * time.Second)
364+
}
365+
366+
zlog.Info().Msg("AutoProperty: Initial batch creation complete. Starting continuous mode...")
367+
333368
// Continuous property creation loop
334369
for {
335370
zlog.Info().Msg("AutoProperty: Adding a new property...")
@@ -359,7 +394,7 @@ func (s *Server) RunAutoProperty(developerUsers, investorUsers []string) {
359394
}
360395
}
361396

362-
zlog.Info().Msg("AutoProperty: Done with this property. Waiting 10 seconds before next one...")
363-
time.Sleep(10 * time.Second)
397+
zlog.Info().Msg("AutoProperty: Done with this property. Waiting 3 seconds before next one...")
398+
time.Sleep(3 * time.Second)
364399
}
365400
}

cmd/tx-sidecar/transaction.go

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,74 @@ func (s *Server) saveTransactionsToFile() {
163163
}
164164
}
165165

166-
// listTransactionsHandler returns the list of tracked transactions
166+
// PaginatedTransactionsResponse represents the paginated response for transactions
167+
type PaginatedTransactionsResponse struct {
168+
Transactions []TrackedTx `json:"transactions"`
169+
Total int `json:"total"`
170+
Page int `json:"page"`
171+
PageSize int `json:"page_size"`
172+
HasNext bool `json:"has_next"`
173+
HasPrev bool `json:"has_prev"`
174+
}
175+
176+
// listTransactionsHandler returns the list of tracked transactions with pagination
167177
// @Summary List transactions
168-
// @Description Lists all transaction hashes that have been successfully processed and stored by the sidecar.
178+
// @Description Lists all transaction hashes that have been successfully processed and stored by the sidecar with pagination support.
169179
// @Produce json
170-
// @Success 200 {array} TrackedTx
180+
// @Param page query int false "Page number (default: 1)"
181+
// @Param page_size query int false "Number of transactions per page (default: 50, max: 1000)"
182+
// @Success 200 {object} PaginatedTransactionsResponse
171183
// @Router /tx/list [get]
172184
func (s *Server) listTransactionsHandler(w http.ResponseWriter, r *http.Request) {
173185
zlog.Info().Str("handler", "listTransactionsHandler").Msg("received request")
186+
187+
// Parse pagination parameters
188+
page := 1
189+
pageSize := 50
190+
191+
if pageStr := r.URL.Query().Get("page"); pageStr != "" {
192+
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
193+
page = p
194+
}
195+
}
196+
197+
if pageSizeStr := r.URL.Query().Get("page_size"); pageSizeStr != "" {
198+
if ps, err := strconv.Atoi(pageSizeStr); err == nil && ps > 0 {
199+
pageSize = ps
200+
if pageSize > 1000 {
201+
pageSize = 1000 // Cap at 1000
202+
}
203+
}
204+
}
205+
206+
total := len(s.transactions)
207+
totalPages := (total + pageSize - 1) / pageSize
208+
209+
// Calculate start and end indices
210+
start := (page - 1) * pageSize
211+
end := start + pageSize
212+
213+
var paginatedTransactions []TrackedTx
214+
if start < total {
215+
if end > total {
216+
end = total
217+
}
218+
paginatedTransactions = s.transactions[start:end]
219+
} else {
220+
paginatedTransactions = []TrackedTx{}
221+
}
222+
223+
response := PaginatedTransactionsResponse{
224+
Transactions: paginatedTransactions,
225+
Total: total,
226+
Page: page,
227+
PageSize: pageSize,
228+
HasNext: page < totalPages,
229+
HasPrev: page > 1,
230+
}
231+
174232
w.Header().Set("Content-Type", "application/json")
175-
json.NewEncoder(w).Encode(s.transactions)
233+
json.NewEncoder(w).Encode(response)
176234
}
177235

178236
// getTransactionHandler returns a specific transaction by its hash

cmd/tx-sidecar/transaction_test.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
"time"
9+
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestListTransactionsPagination(t *testing.T) {
14+
// Create a server with some test transactions
15+
server := &Server{
16+
transactions: []TrackedTx{
17+
{Timestamp: time.Now(), Type: "register_property", TxHash: "hash1"},
18+
{Timestamp: time.Now(), Type: "transfer_shares", TxHash: "hash2"},
19+
{Timestamp: time.Now(), Type: "edit_property_metadata", TxHash: "hash3"},
20+
{Timestamp: time.Now(), Type: "register_property", TxHash: "hash4"},
21+
{Timestamp: time.Now(), Type: "transfer_shares", TxHash: "hash5"},
22+
{Timestamp: time.Now(), Type: "register_property", TxHash: "hash6"},
23+
{Timestamp: time.Now(), Type: "transfer_shares", TxHash: "hash7"},
24+
},
25+
}
26+
27+
tests := []struct {
28+
name string
29+
queryParams string
30+
expectedCount int
31+
expectedPage int
32+
expectedTotal int
33+
expectedHasNext bool
34+
expectedHasPrev bool
35+
}{
36+
{
37+
name: "Default pagination",
38+
queryParams: "",
39+
expectedCount: 7, // All transactions since we have less than default 50
40+
expectedPage: 1,
41+
expectedTotal: 7,
42+
expectedHasNext: false,
43+
expectedHasPrev: false,
44+
},
45+
{
46+
name: "First page with limit 3",
47+
queryParams: "?page=1&page_size=3",
48+
expectedCount: 3,
49+
expectedPage: 1,
50+
expectedTotal: 7,
51+
expectedHasNext: true,
52+
expectedHasPrev: false,
53+
},
54+
{
55+
name: "Second page with limit 3",
56+
queryParams: "?page=2&page_size=3",
57+
expectedCount: 3,
58+
expectedPage: 2,
59+
expectedTotal: 7,
60+
expectedHasNext: true,
61+
expectedHasPrev: true,
62+
},
63+
{
64+
name: "Third page with limit 3",
65+
queryParams: "?page=3&page_size=3",
66+
expectedCount: 1, // Last page with remaining transaction
67+
expectedPage: 3,
68+
expectedTotal: 7,
69+
expectedHasNext: false,
70+
expectedHasPrev: true,
71+
},
72+
{
73+
name: "Page beyond available data",
74+
queryParams: "?page=10&page_size=3",
75+
expectedCount: 0,
76+
expectedPage: 10,
77+
expectedTotal: 7,
78+
expectedHasNext: false,
79+
expectedHasPrev: true,
80+
},
81+
{
82+
name: "Large page size",
83+
queryParams: "?page=1&page_size=100",
84+
expectedCount: 7,
85+
expectedPage: 1,
86+
expectedTotal: 7,
87+
expectedHasNext: false,
88+
expectedHasPrev: false,
89+
},
90+
}
91+
92+
for _, tt := range tests {
93+
t.Run(tt.name, func(t *testing.T) {
94+
req, err := http.NewRequest("GET", "/tx/list"+tt.queryParams, nil)
95+
require.NoError(t, err)
96+
97+
rr := httptest.NewRecorder()
98+
server.listTransactionsHandler(rr, req)
99+
100+
require.Equal(t, http.StatusOK, rr.Code)
101+
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
102+
103+
var response PaginatedTransactionsResponse
104+
err = json.Unmarshal(rr.Body.Bytes(), &response)
105+
require.NoError(t, err)
106+
107+
require.Equal(t, tt.expectedCount, len(response.Transactions))
108+
require.Equal(t, tt.expectedPage, response.Page)
109+
require.Equal(t, tt.expectedTotal, response.Total)
110+
require.Equal(t, tt.expectedHasNext, response.HasNext)
111+
require.Equal(t, tt.expectedHasPrev, response.HasPrev)
112+
})
113+
}
114+
}
115+
116+
func TestListTransactionsPaginationEdgeCases(t *testing.T) {
117+
// Create a server with no transactions
118+
server := &Server{
119+
transactions: []TrackedTx{},
120+
}
121+
122+
req, err := http.NewRequest("GET", "/tx/list", nil)
123+
require.NoError(t, err)
124+
125+
rr := httptest.NewRecorder()
126+
server.listTransactionsHandler(rr, req)
127+
128+
require.Equal(t, http.StatusOK, rr.Code)
129+
130+
var response PaginatedTransactionsResponse
131+
err = json.Unmarshal(rr.Body.Bytes(), &response)
132+
require.NoError(t, err)
133+
134+
require.Equal(t, 0, len(response.Transactions))
135+
require.Equal(t, 1, response.Page)
136+
require.Equal(t, 0, response.Total)
137+
require.Equal(t, false, response.HasNext)
138+
require.Equal(t, false, response.HasPrev)
139+
}
140+
141+
func TestListTransactionsPaginationInvalidParams(t *testing.T) {
142+
server := &Server{
143+
transactions: []TrackedTx{
144+
{Timestamp: time.Now(), Type: "register_property", TxHash: "hash1"},
145+
{Timestamp: time.Now(), Type: "transfer_shares", TxHash: "hash2"},
146+
},
147+
}
148+
149+
tests := []struct {
150+
name string
151+
queryParams string
152+
expectedPage int
153+
expectedPageSize int
154+
}{
155+
{
156+
name: "Invalid page number",
157+
queryParams: "?page=invalid&page_size=10",
158+
expectedPage: 1, // Should default to 1
159+
expectedPageSize: 10,
160+
},
161+
{
162+
name: "Invalid page size",
163+
queryParams: "?page=1&page_size=invalid",
164+
expectedPage: 1,
165+
expectedPageSize: 50, // Should default to 50
166+
},
167+
{
168+
name: "Negative page number",
169+
queryParams: "?page=-1&page_size=10",
170+
expectedPage: 1, // Should default to 1
171+
expectedPageSize: 10,
172+
},
173+
{
174+
name: "Zero page size",
175+
queryParams: "?page=1&page_size=0",
176+
expectedPage: 1,
177+
expectedPageSize: 50, // Should default to 50
178+
},
179+
{
180+
name: "Excessive page size",
181+
queryParams: "?page=1&page_size=2000",
182+
expectedPage: 1,
183+
expectedPageSize: 1000, // Should cap at 1000
184+
},
185+
}
186+
187+
for _, tt := range tests {
188+
t.Run(tt.name, func(t *testing.T) {
189+
req, err := http.NewRequest("GET", "/tx/list"+tt.queryParams, nil)
190+
require.NoError(t, err)
191+
192+
rr := httptest.NewRecorder()
193+
server.listTransactionsHandler(rr, req)
194+
195+
require.Equal(t, http.StatusOK, rr.Code)
196+
197+
var response PaginatedTransactionsResponse
198+
err = json.Unmarshal(rr.Body.Bytes(), &response)
199+
require.NoError(t, err)
200+
201+
require.Equal(t, tt.expectedPage, response.Page)
202+
require.Equal(t, tt.expectedPageSize, response.PageSize)
203+
})
204+
}
205+
}

0 commit comments

Comments
 (0)