Skip to content

Commit 4140486

Browse files
committed
fix(ledger): avoid stale credit transaction pagination
1 parent a99b484 commit 4140486

2 files changed

Lines changed: 137 additions & 0 deletions

File tree

openmeter/ledger/customerbalance/ledger_loader.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func (l *ledgerCreditTransactionLoader) Load(ctx context.Context, input creditTr
5353
}
5454

5555
if result.NextCursor == nil {
56+
hasMore = false
5657
break
5758
}
5859

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package customerbalance
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
"time"
8+
9+
"github.com/alpacahq/alpacadecimal"
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/openmeterio/openmeter/openmeter/customer"
13+
"github.com/openmeterio/openmeter/openmeter/ledger"
14+
"github.com/openmeterio/openmeter/openmeter/ledger/creditvoid"
15+
ledgerhistorical "github.com/openmeterio/openmeter/openmeter/ledger/historical"
16+
"github.com/openmeterio/openmeter/pkg/currencyx"
17+
"github.com/openmeterio/openmeter/pkg/models"
18+
)
19+
20+
func TestLedgerCreditTransactionLoaderDoesNotRetainHasMoreAfterHiddenFinalPage(t *testing.T) {
21+
now := time.Date(2026, 7, 9, 12, 0, 0, 0, time.UTC)
22+
visible := ledgerLoaderTestTransaction(t, "tx-visible", now.Add(-time.Minute), nil, ledger.AccountTypeCustomerAccrued)
23+
hidden := ledgerLoaderTestTransaction(t, "tx-hidden", now.Add(-2*time.Minute), models.Annotations{
24+
creditvoid.AnnotationCreditVoidRecordID: "void-record-1",
25+
}, ledger.AccountTypeCustomerReceivable)
26+
27+
next := visible.Cursor()
28+
fakeLedger := &ledgerLoaderFakeLedger{
29+
pages: []ledger.ListTransactionsResult{
30+
{
31+
Items: []ledger.Transaction{visible},
32+
NextCursor: &next,
33+
},
34+
{
35+
Items: []ledger.Transaction{hidden},
36+
},
37+
},
38+
}
39+
40+
loader := newLedgerCreditTransactionLoader(
41+
&service{Ledger: fakeLedger},
42+
ledger.ListTransactionsCreditMovementNegative,
43+
)
44+
45+
currency := currencyx.Code("USD")
46+
got, err := loader.Load(t.Context(), creditTransactionLoaderInput{
47+
Limit: 1,
48+
CustomerID: customer.CustomerID{Namespace: "ns", ID: "customer-id"},
49+
AccountID: "fbo-account",
50+
Currency: &currency,
51+
AsOf: now,
52+
})
53+
require.NoError(t, err)
54+
require.False(t, got.HasMore)
55+
require.Len(t, got.Items, 1)
56+
require.Equal(t, "tx-visible", got.Items[0].ID.ID)
57+
require.Len(t, fakeLedger.inputs, 2)
58+
require.NotNil(t, fakeLedger.inputs[1].Cursor)
59+
}
60+
61+
type ledgerLoaderFakeLedger struct {
62+
pages []ledger.ListTransactionsResult
63+
inputs []ledger.ListTransactionsInput
64+
}
65+
66+
func (l *ledgerLoaderFakeLedger) CommitGroup(context.Context, ledger.TransactionGroupInput) (ledger.TransactionGroup, error) {
67+
return nil, errors.New("unexpected CommitGroup call")
68+
}
69+
70+
func (l *ledgerLoaderFakeLedger) GetTransactionGroup(context.Context, models.NamespacedID) (ledger.TransactionGroup, error) {
71+
return nil, errors.New("unexpected GetTransactionGroup call")
72+
}
73+
74+
func (l *ledgerLoaderFakeLedger) ListTransactions(_ context.Context, input ledger.ListTransactionsInput) (ledger.ListTransactionsResult, error) {
75+
l.inputs = append(l.inputs, input)
76+
if len(l.pages) == 0 {
77+
return ledger.ListTransactionsResult{}, errors.New("unexpected ListTransactions call")
78+
}
79+
80+
page := l.pages[0]
81+
l.pages = l.pages[1:]
82+
83+
return page, nil
84+
}
85+
86+
func ledgerLoaderTestTransaction(
87+
t *testing.T,
88+
id string,
89+
bookedAt time.Time,
90+
annotations models.Annotations,
91+
offsetAccountType ledger.AccountType,
92+
) ledger.Transaction {
93+
t.Helper()
94+
95+
route := ledger.Route{Currency: currencyx.Code("USD")}
96+
key, err := ledger.BuildRoutingKey(route)
97+
require.NoError(t, err)
98+
99+
tx, err := ledgerhistorical.NewTransactionFromData(ledgerhistorical.TransactionData{
100+
ID: id,
101+
Namespace: "ns",
102+
Annotations: annotations,
103+
CreatedAt: bookedAt,
104+
BookedAt: bookedAt,
105+
}, []ledgerhistorical.EntryData{
106+
{
107+
ID: id + "-fbo",
108+
Namespace: "ns",
109+
CreatedAt: bookedAt,
110+
SubAccountID: "fbo-subaccount",
111+
AccountType: ledger.AccountTypeCustomerFBO,
112+
Route: route,
113+
RouteID: "fbo-route",
114+
RouteKey: key.Value(),
115+
RouteKeyVer: key.Version(),
116+
Amount: alpacadecimal.NewFromInt(-10),
117+
TransactionID: id,
118+
},
119+
{
120+
ID: id + "-offset",
121+
Namespace: "ns",
122+
CreatedAt: bookedAt,
123+
SubAccountID: "offset-subaccount",
124+
AccountType: offsetAccountType,
125+
Route: route,
126+
RouteID: "offset-route",
127+
RouteKey: key.Value(),
128+
RouteKeyVer: key.Version(),
129+
Amount: alpacadecimal.NewFromInt(10),
130+
TransactionID: id,
131+
},
132+
})
133+
require.NoError(t, err)
134+
135+
return tx
136+
}

0 commit comments

Comments
 (0)