Skip to content

Commit b1b0ad7

Browse files
committed
tapfreighter: add tests for burn supply commit filtering
This commit adds a unified test suite for burn supply commitment event processing in the ChainPorter. The tests verify delegation key filtering, error handling, and various burn scenarios through a table-driven approach with a helper function for cleaner test setup. The test coverage includes successful burn processing with mixed group keys, delegation key filtering scenarios, error handling for both the delegation checker and burn committer, handling of missing dependencies, and validation of invalid group key bytes. The setupChainPorterTest helper function eliminates repetitive mock configuration, making the tests more maintainable and focused on the scenarios being tested.
1 parent 71ef2af commit b1b0ad7

File tree

1 file changed

+302
-0
lines changed

1 file changed

+302
-0
lines changed

tapfreighter/supply_commit_test.go

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
package tapfreighter
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/btcsuite/btcd/chaincfg/chainhash"
8+
"github.com/lightninglabs/taproot-assets/asset"
9+
"github.com/lightninglabs/taproot-assets/internal/test"
10+
"github.com/lightninglabs/taproot-assets/universe"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/mock"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
// MockBurnSupplyCommitter is a mock implementation of the BurnSupplyCommitter
17+
// interface for testing.
18+
type MockBurnSupplyCommitter struct {
19+
mock.Mock
20+
}
21+
22+
// MockDelegationKeyChecker is a mock implementation of the DelegationKeyChecker
23+
// interface for testing.
24+
type MockDelegationKeyChecker struct {
25+
mock.Mock
26+
}
27+
28+
// HasDelegationKey implements the DelegationKeyChecker interface.
29+
func (m *MockDelegationKeyChecker) HasDelegationKey(ctx context.Context,
30+
assetID asset.ID) (bool, error) {
31+
32+
args := m.Called(ctx, assetID)
33+
return args.Bool(0), args.Error(1)
34+
}
35+
36+
// SendEvent implements the BurnSupplyCommitter interface.
37+
func (m *MockBurnSupplyCommitter) SendEvent(ctx context.Context,
38+
assetSpec asset.Specifier, event interface{}) error {
39+
40+
args := m.Called(ctx, assetSpec, event)
41+
return args.Error(0)
42+
}
43+
44+
// SendBurnEvent implements the BurnSupplyCommitter interface.
45+
func (m *MockBurnSupplyCommitter) SendBurnEvent(ctx context.Context,
46+
assetSpec asset.Specifier, burnLeaf universe.BurnLeaf) error {
47+
48+
args := m.Called(ctx, assetSpec, burnLeaf)
49+
return args.Error(0)
50+
}
51+
52+
// delegationKeyResult represents the result of a delegation key check.
53+
type delegationKeyResult struct {
54+
hasKey bool
55+
err error
56+
}
57+
58+
// chainPorterTestSetup holds the configuration for a chain porter test.
59+
type chainPorterTestSetup struct {
60+
burns []*AssetBurn
61+
delegationKeyResponses map[asset.ID]delegationKeyResult
62+
expectedBurnCalls int
63+
expectError bool
64+
expectNoManager bool
65+
managerError error
66+
invalidGroupKey bool
67+
}
68+
69+
// setupChainPorterTest creates a configured ChainPorter with mocks based on the
70+
// test setup.
71+
func setupChainPorterTest(t *testing.T,
72+
ctx context.Context, setup chainPorterTestSetup,
73+
) (*ChainPorter, *MockBurnSupplyCommitter, *MockDelegationKeyChecker) {
74+
75+
mockDelegationChecker := &MockDelegationKeyChecker{}
76+
77+
// Only set up delegation key expectations if we have a manager.
78+
if !setup.expectNoManager {
79+
for assetID, result := range setup.delegationKeyResponses {
80+
mockDelegationChecker.On(
81+
"HasDelegationKey", ctx, assetID,
82+
).Return(
83+
result.hasKey, result.err,
84+
)
85+
}
86+
}
87+
88+
var (
89+
mockManager *MockBurnSupplyCommitter
90+
manager BurnSupplyCommitter
91+
)
92+
93+
// If we expect a manager, set it up with the expected burn calls.
94+
if !setup.expectNoManager {
95+
mockManager = &MockBurnSupplyCommitter{}
96+
97+
if setup.expectedBurnCalls > 0 {
98+
call := mockManager.On("SendBurnEvent",
99+
ctx,
100+
mock.AnythingOfType("asset.Specifier"),
101+
mock.AnythingOfType("universe.BurnLeaf"))
102+
103+
if setup.managerError != nil {
104+
call.Return(setup.managerError)
105+
} else {
106+
call.Return(nil)
107+
}
108+
call.Times(setup.expectedBurnCalls)
109+
}
110+
111+
manager = mockManager
112+
}
113+
114+
porter := &ChainPorter{
115+
cfg: &ChainPorterConfig{
116+
BurnCommitter: manager,
117+
DelegationKeyChecker: mockDelegationChecker,
118+
},
119+
}
120+
121+
return porter, mockManager, mockDelegationChecker
122+
}
123+
124+
// TestChainPorterSupplyCommitEvents tests the comprehensive functionality of
125+
// supply commit burn event processing including delegation key filtering, error
126+
// handling, and various burn scenarios.
127+
func TestChainPorterSupplyCommitEvents(t *testing.T) {
128+
t.Parallel()
129+
130+
ctx := context.Background()
131+
132+
// We'll make some test data, including asset IDs, anchor transaction
133+
// ID, and a series of burns.
134+
assetID1 := asset.RandID(t)
135+
assetID2 := asset.RandID(t)
136+
assetID3 := asset.RandID(t)
137+
anchorTxid := chainhash.Hash{1, 2, 3, 4}
138+
groupKey := test.RandPubKey(t)
139+
140+
// Create various burn scenarios
141+
burnWithoutGroup := &AssetBurn{
142+
AssetID: assetID1[:],
143+
Amount: 1000,
144+
AnchorTxid: anchorTxid,
145+
GroupKey: nil,
146+
Note: "burn without group",
147+
}
148+
149+
burnWithGroup := &AssetBurn{
150+
AssetID: assetID2[:],
151+
Amount: 500,
152+
AnchorTxid: anchorTxid,
153+
GroupKey: groupKey.SerializeCompressed(),
154+
Note: "burn with group",
155+
}
156+
157+
burnForFiltering := &AssetBurn{
158+
AssetID: assetID3[:],
159+
Amount: 250,
160+
AnchorTxid: anchorTxid,
161+
GroupKey: nil,
162+
Note: "burn for filtering test",
163+
}
164+
165+
burnWithInvalidGroup := &AssetBurn{
166+
AssetID: assetID1[:],
167+
Amount: 100,
168+
AnchorTxid: anchorTxid,
169+
GroupKey: []byte{0xFF, 0xFF},
170+
Note: "burn with invalid group",
171+
}
172+
173+
tests := []struct {
174+
name string
175+
setup chainPorterTestSetup
176+
}{ //nolint:lll
177+
{
178+
name: "successful burn events with mixed group keys",
179+
setup: chainPorterTestSetup{
180+
burns: []*AssetBurn{
181+
burnWithoutGroup, burnWithGroup,
182+
},
183+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
184+
assetID1: {hasKey: true, err: nil},
185+
assetID2: {hasKey: true, err: nil},
186+
},
187+
expectedBurnCalls: 2,
188+
expectError: false,
189+
},
190+
},
191+
{
192+
name: "delegation key filtering - only some have " +
193+
"delegation keys",
194+
setup: chainPorterTestSetup{
195+
burns: []*AssetBurn{
196+
burnWithoutGroup, burnWithGroup, burnForFiltering,
197+
},
198+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
199+
assetID1: {hasKey: true, err: nil},
200+
assetID2: {hasKey: false, err: nil},
201+
assetID3: {hasKey: true, err: nil},
202+
},
203+
expectedBurnCalls: 2,
204+
expectError: false,
205+
},
206+
},
207+
{
208+
name: "delegation key filtering - no assets have " +
209+
"delegation keys",
210+
setup: chainPorterTestSetup{
211+
burns: []*AssetBurn{
212+
burnWithoutGroup, burnWithGroup,
213+
},
214+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
215+
assetID1: {hasKey: false, err: nil},
216+
assetID2: {hasKey: false, err: nil},
217+
},
218+
expectedBurnCalls: 0,
219+
expectError: false,
220+
},
221+
},
222+
{
223+
name: "delegation key checker error - filtered out " +
224+
"gracefully",
225+
setup: chainPorterTestSetup{
226+
burns: []*AssetBurn{
227+
burnWithoutGroup, burnWithGroup,
228+
},
229+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
230+
assetID1: {hasKey: false, err: assert.AnError},
231+
assetID2: {hasKey: true, err: nil},
232+
},
233+
expectedBurnCalls: 1,
234+
expectError: false,
235+
},
236+
},
237+
{
238+
name: "burn committer error",
239+
setup: chainPorterTestSetup{
240+
burns: []*AssetBurn{burnWithoutGroup},
241+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
242+
assetID1: {hasKey: true, err: nil},
243+
},
244+
expectedBurnCalls: 1,
245+
managerError: assert.AnError,
246+
expectError: true,
247+
},
248+
},
249+
{
250+
name: "no burn committer configured",
251+
setup: chainPorterTestSetup{
252+
burns: []*AssetBurn{burnWithoutGroup, burnWithGroup},
253+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
254+
assetID1: {hasKey: true, err: nil},
255+
assetID2: {hasKey: true, err: nil},
256+
},
257+
expectNoManager: true,
258+
expectError: false,
259+
},
260+
},
261+
{
262+
name: "invalid group key bytes",
263+
setup: chainPorterTestSetup{
264+
burns: []*AssetBurn{burnWithInvalidGroup},
265+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
266+
assetID1: {hasKey: true, err: nil},
267+
},
268+
expectedBurnCalls: 0,
269+
expectError: true,
270+
},
271+
},
272+
}
273+
274+
for _, tc := range tests {
275+
tc := tc
276+
t.Run(tc.name, func(t *testing.T) {
277+
t.Parallel()
278+
279+
// We'll set up the test, call the method with the
280+
// specified args, then assert the results.
281+
//nolint:lll
282+
porter, mockManager, mockDelegationChecker := setupChainPorterTest(
283+
t, ctx, tc.setup,
284+
)
285+
286+
err := porter.sendBurnSupplyCommitEvents(ctx, tc.setup.burns)
287+
288+
if tc.setup.expectError {
289+
require.Error(t, err)
290+
} else {
291+
require.NoError(t, err)
292+
}
293+
294+
if mockManager != nil {
295+
mockManager.AssertExpectations(t)
296+
}
297+
if !tc.setup.expectNoManager {
298+
mockDelegationChecker.AssertExpectations(t)
299+
}
300+
})
301+
}
302+
}

0 commit comments

Comments
 (0)