Skip to content

Commit 62264a4

Browse files
author
AKRAM@il.ibm.com
committed
Add tests for external wallet cleanup in CollectEndorsementsView
Tests verify that Done() is called on external wallet signers even when errors occur during the signature collection process. This ensures proper resource cleanup in all scenarios: - Success case: Done() called once per external wallet - Error in issue signatures: Done() still called - Error in transfer signatures: Done() still called - Multiple external wallets: Done() called on all wallets even if one fails The tests use mock external wallet signers to verify the cleanup behavior introduced by the defer-based fix.
1 parent f6f8f6b commit 62264a4

File tree

1 file changed

+291
-0
lines changed

1 file changed

+291
-0
lines changed
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package ttx_test
8+
9+
import (
10+
"testing"
11+
12+
"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"
13+
"github.com/hyperledger-labs/fabric-smart-client/platform/view/services/endpoint"
14+
"github.com/hyperledger-labs/fabric-smart-client/platform/view/view"
15+
"github.com/hyperledger-labs/fabric-token-sdk/token"
16+
"github.com/hyperledger-labs/fabric-token-sdk/token/driver"
17+
"github.com/hyperledger-labs/fabric-token-sdk/token/driver/mock"
18+
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx"
19+
mock2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx/dep/mock"
20+
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx/dep/tokenapi"
21+
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
// mockExternalWalletSigner is a mock implementation of ExternalWalletSigner
26+
type mockExternalWalletSigner struct {
27+
signCalled int
28+
doneCalled int
29+
signError error
30+
doneError error
31+
}
32+
33+
func (m *mockExternalWalletSigner) Sign(party view.Identity, message []byte) ([]byte, error) {
34+
m.signCalled++
35+
if m.signError != nil {
36+
return nil, m.signError
37+
}
38+
return []byte("external_signature"), nil
39+
}
40+
41+
func (m *mockExternalWalletSigner) Done() error {
42+
m.doneCalled++
43+
return m.doneError
44+
}
45+
46+
func TestCollectEndorsementsView_ExternalWalletCleanup(t *testing.T) {
47+
testCases := []struct {
48+
name string
49+
setupError error
50+
expectDoneCalled bool
51+
expectDoneCallCount int
52+
errorInSignature bool
53+
errorInTransferSignature bool
54+
}{
55+
{
56+
name: "success - Done called once",
57+
setupError: nil,
58+
expectDoneCalled: true,
59+
expectDoneCallCount: 1,
60+
},
61+
{
62+
name: "error in issue signatures - Done still called",
63+
setupError: nil,
64+
expectDoneCalled: true,
65+
expectDoneCallCount: 1,
66+
errorInSignature: true,
67+
},
68+
{
69+
name: "error in transfer signatures - Done still called",
70+
setupError: nil,
71+
expectDoneCalled: true,
72+
expectDoneCallCount: 1,
73+
errorInTransferSignature: true,
74+
},
75+
}
76+
77+
for _, tc := range testCases {
78+
t.Run(tc.name, func(t *testing.T) {
79+
// Setup mock context
80+
ctx := &mock2.Context{}
81+
ctx.ContextReturns(t.Context())
82+
83+
// Setup token management service
84+
tms := &mock2.TokenManagementServiceWithExtensions{}
85+
tms.NetworkReturns("test_network")
86+
tms.ChannelReturns("test_channel")
87+
tmsID := token.TMSID{
88+
Network: "test_network",
89+
Channel: "test_channel",
90+
Namespace: "test_namespace",
91+
}
92+
tms.IDReturns(tmsID)
93+
94+
// Setup token deserializer and identity provider
95+
tokenDes := &mock.Deserializer{}
96+
tokenIP := &mock.IdentityProvider{}
97+
tokenIP.IsMeReturns(false) // Not me, so it will try external wallet
98+
tms.SigServiceReturns(token.NewSignatureService(tokenDes, tokenIP))
99+
100+
// Setup wallet manager with external wallet
101+
walletManager := &mock2.WalletManager{}
102+
wallet := &mock2.Wallet{}
103+
wallet.IDReturns("external_wallet_1")
104+
walletManager.OwnerWalletReturns(wallet)
105+
tms.WalletManagerReturns(walletManager)
106+
107+
tokenAPITMS := tokenapi.NewMockedManagementService(t, tmsID)
108+
tms.SetTokenManagementServiceStub = func(arg1 *token.Request) error {
109+
arg1.SetTokenService(tokenAPITMS)
110+
return nil
111+
}
112+
113+
tmsp := &mock2.TokenManagementServiceProvider{}
114+
tmsp.TokenManagementServiceReturns(tms, nil)
115+
116+
network := &mock2.Network{}
117+
network.ComputeTxIDReturns("test_anchor")
118+
np := &mock2.NetworkProvider{}
119+
np.GetNetworkReturns(network, nil)
120+
121+
// Create request with issue metadata
122+
req := token.NewRequest(nil, "test_anchor")
123+
req.Metadata.Issues = []*driver.IssueMetadata{
124+
{
125+
Issuer: driver.AuditableIdentity{
126+
Identity: []byte("external_issuer"),
127+
},
128+
},
129+
}
130+
tms.NewRequestReturns(req, nil)
131+
132+
// Setup context service calls
133+
ctx.GetServiceReturnsOnCall(0, tmsp, nil)
134+
ctx.GetServiceReturnsOnCall(1, np, nil)
135+
ctx.GetServiceReturnsOnCall(2, &endpoint.Service{}, nil)
136+
ctx.GetServiceReturnsOnCall(3, np, nil)
137+
ctx.GetServiceReturnsOnCall(4, tmsp, nil)
138+
139+
// Create transaction
140+
tx, err := ttx.NewTransaction(ctx, []byte("test_signer"))
141+
require.NoError(t, err)
142+
143+
// Create mock external wallet signer
144+
mockEWS := &mockExternalWalletSigner{}
145+
if tc.errorInSignature {
146+
mockEWS.signError = errors.New("signature error")
147+
}
148+
149+
// Create CollectEndorsementsView with external wallet signer option
150+
opts := []ttx.EndorsementsOpt{
151+
ttx.WithExternalWalletSignerProvider(func(walletID string) ttx.ExternalWalletSigner {
152+
return mockEWS
153+
}),
154+
ttx.WithSkipApproval(),
155+
ttx.WithSkipAuditing(),
156+
}
157+
158+
view := ttx.NewCollectEndorsementsView(tx, opts...)
159+
160+
// Call the view
161+
_, err = view.Call(ctx)
162+
163+
// Verify Done was called regardless of error
164+
if tc.expectDoneCalled {
165+
assert.Equal(t, tc.expectDoneCallCount, mockEWS.doneCalled,
166+
"Done() should be called %d time(s)", tc.expectDoneCallCount)
167+
} else {
168+
assert.Equal(t, 0, mockEWS.doneCalled, "Done() should not be called")
169+
}
170+
171+
// Verify error behavior
172+
if tc.errorInSignature || tc.errorInTransferSignature {
173+
require.Error(t, err, "Expected error when signature fails")
174+
}
175+
})
176+
}
177+
}
178+
179+
func TestCollectEndorsementsView_MultipleExternalWallets(t *testing.T) {
180+
// Setup mock context
181+
ctx := &mock2.Context{}
182+
ctx.ContextReturns(t.Context())
183+
184+
// Setup token management service
185+
tms := &mock2.TokenManagementServiceWithExtensions{}
186+
tms.NetworkReturns("test_network")
187+
tms.ChannelReturns("test_channel")
188+
tmsID := token.TMSID{
189+
Network: "test_network",
190+
Channel: "test_channel",
191+
Namespace: "test_namespace",
192+
}
193+
tms.IDReturns(tmsID)
194+
195+
// Setup token deserializer and identity provider
196+
tokenDes := &mock.Deserializer{}
197+
tokenIP := &mock.IdentityProvider{}
198+
tokenIP.IsMeReturns(false)
199+
tms.SigServiceReturns(token.NewSignatureService(tokenDes, tokenIP))
200+
201+
// Setup wallet manager with multiple external wallets
202+
walletManager := &mock2.WalletManager{}
203+
wallet1 := &mock2.Wallet{}
204+
wallet1.IDReturns("external_wallet_1")
205+
wallet2 := &mock2.Wallet{}
206+
wallet2.IDReturns("external_wallet_2")
207+
208+
callCount := 0
209+
walletManager.OwnerWalletStub = func(ctx2 interface{}, identity view.Identity) interface{} {
210+
callCount++
211+
if callCount%2 == 1 {
212+
return wallet1
213+
}
214+
return wallet2
215+
}
216+
tms.WalletManagerReturns(walletManager)
217+
218+
tokenAPITMS := tokenapi.NewMockedManagementService(t, tmsID)
219+
tms.SetTokenManagementServiceStub = func(arg1 *token.Request) error {
220+
arg1.SetTokenService(tokenAPITMS)
221+
return nil
222+
}
223+
224+
tmsp := &mock2.TokenManagementServiceProvider{}
225+
tmsp.TokenManagementServiceReturns(tms, nil)
226+
227+
network := &mock2.Network{}
228+
network.ComputeTxIDReturns("test_anchor")
229+
np := &mock2.NetworkProvider{}
230+
np.GetNetworkReturns(network, nil)
231+
232+
// Create request with multiple issues
233+
req := token.NewRequest(nil, "test_anchor")
234+
req.Metadata.Issues = []*driver.IssueMetadata{
235+
{
236+
Issuer: driver.AuditableIdentity{
237+
Identity: []byte("external_issuer_1"),
238+
},
239+
},
240+
{
241+
Issuer: driver.AuditableIdentity{
242+
Identity: []byte("external_issuer_2"),
243+
},
244+
},
245+
}
246+
tms.NewRequestReturns(req, nil)
247+
248+
// Setup context service calls
249+
ctx.GetServiceReturnsOnCall(0, tmsp, nil)
250+
ctx.GetServiceReturnsOnCall(1, np, nil)
251+
ctx.GetServiceReturnsOnCall(2, &endpoint.Service{}, nil)
252+
ctx.GetServiceReturnsOnCall(3, np, nil)
253+
ctx.GetServiceReturnsOnCall(4, tmsp, nil)
254+
255+
// Create transaction
256+
tx, err := ttx.NewTransaction(ctx, []byte("test_signer"))
257+
require.NoError(t, err)
258+
259+
// Create mock external wallet signers
260+
mockEWS1 := &mockExternalWalletSigner{}
261+
mockEWS2 := &mockExternalWalletSigner{}
262+
263+
// Simulate error in second wallet's signature
264+
mockEWS2.signError = errors.New("wallet 2 signature error")
265+
266+
walletSigners := map[string]*mockExternalWalletSigner{
267+
"external_wallet_1": mockEWS1,
268+
"external_wallet_2": mockEWS2,
269+
}
270+
271+
// Create CollectEndorsementsView with external wallet signer option
272+
opts := []ttx.EndorsementsOpt{
273+
ttx.WithExternalWalletSignerProvider(func(walletID string) ttx.ExternalWalletSigner {
274+
return walletSigners[walletID]
275+
}),
276+
ttx.WithSkipApproval(),
277+
ttx.WithSkipAuditing(),
278+
}
279+
280+
view := ttx.NewCollectEndorsementsView(tx, opts...)
281+
282+
// Call the view - should fail due to wallet 2 error
283+
_, err = view.Call(ctx)
284+
require.Error(t, err, "Expected error from wallet 2")
285+
286+
// Verify Done was called on both wallets despite the error
287+
assert.Equal(t, 1, mockEWS1.doneCalled, "Done() should be called on wallet 1")
288+
assert.Equal(t, 1, mockEWS2.doneCalled, "Done() should be called on wallet 2 even after error")
289+
}
290+
291+
// Made with Bob

0 commit comments

Comments
 (0)