Skip to content

Commit 58d8271

Browse files
committed
tapfreighter: add comprehensive 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 729f7c6 commit 58d8271

File tree

1 file changed

+297
-0
lines changed

1 file changed

+297
-0
lines changed

tapfreighter/supply_commit_test.go

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
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 test setup.
70+
func setupChainPorterTest(t *testing.T,
71+
ctx context.Context, setup chainPorterTestSetup,
72+
) (*ChainPorter, *MockBurnSupplyCommitter, *MockDelegationKeyChecker) {
73+
74+
mockDelegationChecker := &MockDelegationKeyChecker{}
75+
76+
// Only set up delegation key expectations if we have a manager.
77+
if !setup.expectNoManager {
78+
for assetID, result := range setup.delegationKeyResponses {
79+
mockDelegationChecker.On("HasDelegationKey", ctx, assetID).
80+
Return(result.hasKey, result.err)
81+
}
82+
}
83+
84+
var (
85+
mockManager *MockBurnSupplyCommitter
86+
manager BurnSupplyCommitter
87+
)
88+
89+
// If we expect a manager, set it up with the expected burn calls.
90+
if !setup.expectNoManager {
91+
mockManager = &MockBurnSupplyCommitter{}
92+
93+
if setup.expectedBurnCalls > 0 {
94+
call := mockManager.On("SendBurnEvent",
95+
ctx,
96+
mock.AnythingOfType("asset.Specifier"),
97+
mock.AnythingOfType("universe.BurnLeaf"))
98+
99+
if setup.managerError != nil {
100+
call.Return(setup.managerError)
101+
} else {
102+
call.Return(nil)
103+
}
104+
call.Times(setup.expectedBurnCalls)
105+
}
106+
manager = mockManager
107+
}
108+
109+
porter := &ChainPorter{
110+
cfg: &ChainPorterConfig{
111+
BurnCommitter: manager,
112+
DelegationKeyChecker: mockDelegationChecker,
113+
},
114+
}
115+
116+
return porter, mockManager, mockDelegationChecker
117+
}
118+
119+
// TestChainPorterSupplyCommitEvents tests the comprehensive functionality of
120+
// supply commit burn event processing including delegation key filtering, error
121+
// handling, and various burn scenarios.
122+
func TestChainPorterSupplyCommitEvents(t *testing.T) {
123+
t.Parallel()
124+
125+
ctx := context.Background()
126+
127+
// We'll make some test data, including asset IDs, anchor transaction
128+
// ID, and a series of burns.
129+
assetID1 := asset.RandID(t)
130+
assetID2 := asset.RandID(t)
131+
assetID3 := asset.RandID(t)
132+
anchorTxid := chainhash.Hash{1, 2, 3, 4}
133+
groupKey := test.RandPubKey(t)
134+
135+
// Create various burn scenarios
136+
burnWithoutGroup := &AssetBurn{
137+
AssetID: assetID1[:],
138+
Amount: 1000,
139+
AnchorTxid: anchorTxid,
140+
GroupKey: nil,
141+
Note: "burn without group",
142+
}
143+
144+
burnWithGroup := &AssetBurn{
145+
AssetID: assetID2[:],
146+
Amount: 500,
147+
AnchorTxid: anchorTxid,
148+
GroupKey: groupKey.SerializeCompressed(),
149+
Note: "burn with group",
150+
}
151+
152+
burnForFiltering := &AssetBurn{
153+
AssetID: assetID3[:],
154+
Amount: 250,
155+
AnchorTxid: anchorTxid,
156+
GroupKey: nil,
157+
Note: "burn for filtering test",
158+
}
159+
160+
burnWithInvalidGroup := &AssetBurn{
161+
AssetID: assetID1[:],
162+
Amount: 100,
163+
AnchorTxid: anchorTxid,
164+
GroupKey: []byte{0xFF, 0xFF},
165+
Note: "burn with invalid group",
166+
}
167+
168+
tests := []struct {
169+
name string
170+
setup chainPorterTestSetup
171+
}{
172+
{
173+
name: "successful burn events with mixed group keys",
174+
setup: chainPorterTestSetup{
175+
burns: []*AssetBurn{
176+
burnWithoutGroup, burnWithGroup,
177+
},
178+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
179+
assetID1: {hasKey: true, err: nil},
180+
assetID2: {hasKey: true, err: nil},
181+
},
182+
expectedBurnCalls: 2,
183+
expectError: false,
184+
},
185+
},
186+
{
187+
name: "delegation key filtering - only some have " +
188+
"delegation keys",
189+
setup: chainPorterTestSetup{
190+
burns: []*AssetBurn{
191+
burnWithoutGroup, burnWithGroup, burnForFiltering,
192+
},
193+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
194+
assetID1: {hasKey: true, err: nil},
195+
assetID2: {hasKey: false, err: nil},
196+
assetID3: {hasKey: true, err: nil},
197+
},
198+
expectedBurnCalls: 2,
199+
expectError: false,
200+
},
201+
},
202+
{
203+
name: "delegation key filtering - no assets have " +
204+
"delegation keys",
205+
setup: chainPorterTestSetup{
206+
burns: []*AssetBurn{
207+
burnWithoutGroup, burnWithGroup,
208+
},
209+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
210+
assetID1: {hasKey: false, err: nil},
211+
assetID2: {hasKey: false, err: nil},
212+
},
213+
expectedBurnCalls: 0,
214+
expectError: false,
215+
},
216+
},
217+
{
218+
name: "delegation key checker error - filtered out " +
219+
"gracefully",
220+
setup: chainPorterTestSetup{
221+
burns: []*AssetBurn{
222+
burnWithoutGroup, burnWithGroup,
223+
},
224+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
225+
assetID1: {hasKey: false, err: assert.AnError},
226+
assetID2: {hasKey: true, err: nil},
227+
},
228+
expectedBurnCalls: 1,
229+
expectError: false,
230+
},
231+
},
232+
{
233+
name: "burn committer error",
234+
setup: chainPorterTestSetup{
235+
burns: []*AssetBurn{burnWithoutGroup},
236+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
237+
assetID1: {hasKey: true, err: nil},
238+
},
239+
expectedBurnCalls: 1,
240+
managerError: assert.AnError,
241+
expectError: true,
242+
},
243+
},
244+
{
245+
name: "no burn committer configured",
246+
setup: chainPorterTestSetup{
247+
burns: []*AssetBurn{burnWithoutGroup, burnWithGroup},
248+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
249+
assetID1: {hasKey: true, err: nil},
250+
assetID2: {hasKey: true, err: nil},
251+
},
252+
expectNoManager: true,
253+
expectError: false,
254+
},
255+
},
256+
{
257+
name: "invalid group key bytes",
258+
setup: chainPorterTestSetup{
259+
burns: []*AssetBurn{burnWithInvalidGroup},
260+
delegationKeyResponses: map[asset.ID]delegationKeyResult{
261+
assetID1: {hasKey: true, err: nil},
262+
},
263+
expectedBurnCalls: 0,
264+
expectError: true,
265+
},
266+
},
267+
}
268+
269+
for _, tc := range tests {
270+
tc := tc
271+
t.Run(tc.name, func(t *testing.T) {
272+
t.Parallel()
273+
274+
// We'll set up the test, call the method with the
275+
// specified args, then assert the results.
276+
porter, mockManager, mockDelegationChecker := setupChainPorterTest(
277+
t, ctx, tc.setup,
278+
)
279+
280+
// Execute the test
281+
err := porter.sendBurnSupplyCommitEvents(ctx, tc.setup.burns)
282+
283+
if tc.setup.expectError {
284+
require.Error(t, err)
285+
} else {
286+
require.NoError(t, err)
287+
}
288+
289+
if mockManager != nil {
290+
mockManager.AssertExpectations(t)
291+
}
292+
if !tc.setup.expectNoManager {
293+
mockDelegationChecker.AssertExpectations(t)
294+
}
295+
})
296+
}
297+
}

0 commit comments

Comments
 (0)