1
1
import { InvalidParamsError , SnapError } from '@metamask/snaps-sdk' ;
2
- import { object } from 'superstruct' ;
3
- import type { Struct } from 'superstruct' ;
2
+ import { constants } from 'starknet' ;
3
+ import { object , string } from 'superstruct' ;
4
+ import type { Struct , Infer } from 'superstruct' ;
4
5
5
- import { validateRequest , validateResponse } from './rpc' ;
6
+ import type { StarknetAccount } from '../../test/utils' ;
7
+ import { generateAccounts } from '../../test/utils' ;
8
+ import type { SnapState } from '../types/snapState' ;
9
+ import { STARKNET_SEPOLIA_TESTNET_NETWORK } from './constants' ;
10
+ import {
11
+ AccountRpcController ,
12
+ RpcController ,
13
+ validateRequest ,
14
+ validateResponse ,
15
+ } from './rpc' ;
16
+ import * as snapHelper from './snap' ;
17
+ import * as snapUtils from './snapUtils' ;
18
+ import * as starknetUtils from './starknetUtils' ;
6
19
import { AddressStruct } from './superstruct' ;
7
20
8
- const struct = object ( {
21
+ jest . mock ( './snap' ) ;
22
+ jest . mock ( './logger' ) ;
23
+
24
+ const validateStruct = object ( {
9
25
signerAddress : AddressStruct ,
10
26
} ) ;
11
27
12
- const params = {
28
+ const validateParam = {
13
29
signerAddress :
14
30
'0x04882a372da3dfe1c53170ad75893832469bf87b62b13e84662565c4a88f25cd' ,
15
31
} ;
16
32
17
33
describe ( 'validateRequest' , ( ) => {
18
34
it ( 'does not throw error if the request is valid' , ( ) => {
19
35
expect ( ( ) =>
20
- validateRequest ( params , struct as unknown as Struct ) ,
36
+ validateRequest ( validateParam , validateStruct as unknown as Struct ) ,
21
37
) . not . toThrow ( ) ;
22
38
} ) ;
23
39
@@ -27,15 +43,15 @@ describe('validateRequest', () => {
27
43
} ;
28
44
29
45
expect ( ( ) =>
30
- validateRequest ( requestParams , struct as unknown as Struct ) ,
46
+ validateRequest ( requestParams , validateStruct as unknown as Struct ) ,
31
47
) . toThrow ( InvalidParamsError ) ;
32
48
} ) ;
33
49
} ) ;
34
50
35
51
describe ( 'validateResponse' , ( ) => {
36
52
it ( 'does not throw error if the response is valid' , ( ) => {
37
53
expect ( ( ) =>
38
- validateResponse ( params , struct as unknown as Struct ) ,
54
+ validateResponse ( validateParam , validateStruct as unknown as Struct ) ,
39
55
) . not . toThrow ( ) ;
40
56
} ) ;
41
57
@@ -45,7 +61,159 @@ describe('validateResponse', () => {
45
61
} ;
46
62
47
63
expect ( ( ) =>
48
- validateResponse ( response , struct as unknown as Struct ) ,
64
+ validateResponse ( response , validateStruct as unknown as Struct ) ,
49
65
) . toThrow ( new SnapError ( 'Invalid Response' ) ) ;
50
66
} ) ;
51
67
} ) ;
68
+
69
+ describe ( 'RpcController' , ( ) => {
70
+ class MockRpc extends RpcController < string , string > {
71
+ protected requestStruct = string ( ) ;
72
+
73
+ protected responseStruct = string ( ) ;
74
+
75
+ // Set it to public to be able to spy on it
76
+ async handleRequest ( params : string ) {
77
+ return `done ${ params } ` ;
78
+ }
79
+ }
80
+
81
+ it ( 'executes request' , async ( ) => {
82
+ const rpc = new MockRpc ( ) ;
83
+
84
+ const result = await rpc . execute ( 'test' ) ;
85
+
86
+ expect ( result ) . toBe ( 'done test' ) ;
87
+ } ) ;
88
+
89
+ it ( 'throws `Failed to execute the rpc method` if an error was thrown' , async ( ) => {
90
+ const rpc = new MockRpc ( ) ;
91
+
92
+ jest
93
+ . spyOn ( MockRpc . prototype , 'handleRequest' )
94
+ . mockRejectedValue ( new Error ( 'error' ) ) ;
95
+
96
+ await expect ( rpc . execute ( 'test' ) ) . rejects . toThrow (
97
+ 'Failed to execute the rpc method' ,
98
+ ) ;
99
+ } ) ;
100
+
101
+ it ( 'throws the actual error if an snap error was thrown' , async ( ) => {
102
+ const rpc = new MockRpc ( ) ;
103
+
104
+ await expect ( rpc . execute ( 1 as unknown as string ) ) . rejects . toThrow (
105
+ 'Expected a string, but received: 1' ,
106
+ ) ;
107
+ } ) ;
108
+ } ) ;
109
+
110
+ describe ( 'AccountRpcController' , ( ) => {
111
+ const state : SnapState = {
112
+ accContracts : [ ] ,
113
+ erc20Tokens : [ ] ,
114
+ networks : [ STARKNET_SEPOLIA_TESTNET_NETWORK ] ,
115
+ transactions : [ ] ,
116
+ } ;
117
+
118
+ const RequestStruct = object ( {
119
+ address : string ( ) ,
120
+ chainId : string ( ) ,
121
+ } ) ;
122
+
123
+ type Request = Infer < typeof RequestStruct > ;
124
+
125
+ class MockAccountRpc extends AccountRpcController < Request , string > {
126
+ protected requestStruct = RequestStruct ;
127
+
128
+ protected responseStruct = string ( ) ;
129
+
130
+ // Set it to public to be able to spy on it
131
+ async handleRequest ( param : Request ) {
132
+ return `done ${ param . address } and ${ param . chainId } ` ;
133
+ }
134
+ }
135
+
136
+ const mockAccount = async ( network : constants . StarknetChainId ) => {
137
+ const accounts = await generateAccounts ( network , 1 ) ;
138
+ return accounts [ 0 ] ;
139
+ } ;
140
+
141
+ const prepareExecute = async ( account : StarknetAccount ) => {
142
+ const verifyIfAccountNeedUpgradeOrDeploySpy = jest . spyOn (
143
+ snapUtils ,
144
+ 'verifyIfAccountNeedUpgradeOrDeploy' ,
145
+ ) ;
146
+
147
+ const getKeysFromAddressSpy = jest . spyOn (
148
+ starknetUtils ,
149
+ 'getKeysFromAddress' ,
150
+ ) ;
151
+
152
+ const getStateDataSpy = jest . spyOn ( snapHelper , 'getStateData' ) ;
153
+
154
+ getStateDataSpy . mockResolvedValue ( state ) ;
155
+
156
+ getKeysFromAddressSpy . mockResolvedValue ( {
157
+ privateKey : account . privateKey ,
158
+ publicKey : account . publicKey ,
159
+ addressIndex : account . addressIndex ,
160
+ derivationPath : account . derivationPath as unknown as any ,
161
+ } ) ;
162
+
163
+ verifyIfAccountNeedUpgradeOrDeploySpy . mockReturnThis ( ) ;
164
+
165
+ return {
166
+ getKeysFromAddressSpy,
167
+ getStateDataSpy,
168
+ verifyIfAccountNeedUpgradeOrDeploySpy,
169
+ } ;
170
+ } ;
171
+
172
+ it ( 'executes request' , async ( ) => {
173
+ const chainId = constants . StarknetChainId . SN_SEPOLIA ;
174
+ const account = await mockAccount ( chainId ) ;
175
+ await prepareExecute ( account ) ;
176
+ const rpc = new MockAccountRpc ( ) ;
177
+
178
+ const result = await rpc . execute ( {
179
+ address : account . address ,
180
+ chainId,
181
+ } ) ;
182
+
183
+ expect ( result ) . toBe ( `done ${ account . address } and ${ chainId } ` ) ;
184
+ } ) ;
185
+
186
+ it ( 'fetchs account before execute' , async ( ) => {
187
+ const chainId = constants . StarknetChainId . SN_SEPOLIA ;
188
+ const account = await mockAccount ( chainId ) ;
189
+ const { getKeysFromAddressSpy } = await prepareExecute ( account ) ;
190
+ const rpc = new MockAccountRpc ( ) ;
191
+
192
+ await rpc . execute ( { address : account . address , chainId } ) ;
193
+
194
+ expect ( getKeysFromAddressSpy ) . toHaveBeenCalled ( ) ;
195
+ } ) ;
196
+
197
+ it . each ( [ true , false ] ) (
198
+ `assign verifyIfAccountNeedUpgradeOrDeploy's argument "showAlert" to %s if the constructor option 'showInvalidAccountAlert' is set to %s` ,
199
+ async ( showInvalidAccountAlert : boolean ) => {
200
+ const chainId = constants . StarknetChainId . SN_SEPOLIA ;
201
+ const account = await mockAccount ( chainId ) ;
202
+ const { verifyIfAccountNeedUpgradeOrDeploySpy } = await prepareExecute (
203
+ account ,
204
+ ) ;
205
+ const rpc = new MockAccountRpc ( {
206
+ showInvalidAccountAlert,
207
+ } ) ;
208
+
209
+ await rpc . execute ( { address : account . address , chainId } ) ;
210
+
211
+ expect ( verifyIfAccountNeedUpgradeOrDeploySpy ) . toHaveBeenCalledWith (
212
+ expect . any ( Object ) ,
213
+ account . address ,
214
+ account . publicKey ,
215
+ showInvalidAccountAlert ,
216
+ ) ;
217
+ } ,
218
+ ) ;
219
+ } ) ;
0 commit comments