opticedge-cloud-utils 1.1.13 → 1.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +2 -2
- package/.eslintrc.js +30 -30
- package/.prettierignore +4 -4
- package/.prettierrc +8 -8
- package/dist/chunk.d.ts +1 -0
- package/dist/chunk.js +37 -0
- package/dist/env.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/jest.config.js +12 -12
- package/package.json +40 -40
- package/src/auth.ts +29 -29
- package/src/chunk.ts +33 -0
- package/src/db/mongo.ts +45 -45
- package/src/db/mongo2.ts +45 -45
- package/src/db/mongo3.ts +63 -63
- package/src/env.ts +10 -8
- package/src/index.ts +14 -13
- package/src/parser.ts +9 -9
- package/src/regex.ts +19 -19
- package/src/retry.ts +155 -155
- package/src/secrets.ts +21 -21
- package/src/task.ts +132 -132
- package/src/tw/utils.ts +18 -18
- package/src/tw/wallet.ts +68 -68
- package/src/validator.ts +4 -4
- package/tests/auth.test.ts +89 -89
- package/tests/chunk.test.ts +48 -0
- package/tests/db/mongo.test.ts +82 -82
- package/tests/db/mongo2.test.ts +96 -96
- package/tests/db/mongo3.test.ts +149 -149
- package/tests/env.test.ts +18 -18
- package/tests/parser.test.ts +26 -26
- package/tests/regex.test.ts +69 -69
- package/tests/retry.test.ts +416 -416
- package/tests/secrets.test.ts +44 -44
- package/tests/task.test.ts +337 -337
- package/tests/tw/utils.test.ts +29 -29
- package/tests/tw/wallet.test.ts +206 -206
- package/tests/validator.ts +37 -37
- package/tsconfig.json +17 -17
package/tests/tw/utils.test.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { isValidWebhookSignature } from '../../src/tw/utils'
|
|
2
|
-
import crypto from 'crypto'
|
|
3
|
-
|
|
4
|
-
describe('isValidWebhookSignature', () => {
|
|
5
|
-
const secret = 'test_secret'
|
|
6
|
-
const body = '{"message":"hello"}'
|
|
7
|
-
|
|
8
|
-
it('returns true for a valid signature', () => {
|
|
9
|
-
const validSignature = crypto.createHmac('sha256', secret).update(body).digest('hex')
|
|
10
|
-
|
|
11
|
-
expect(isValidWebhookSignature(secret, body, validSignature)).toBe(true)
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('returns false for an invalid signature', () => {
|
|
15
|
-
const invalidSignature = 'invalidsignature123'
|
|
16
|
-
|
|
17
|
-
expect(isValidWebhookSignature(secret, body, invalidSignature)).toBe(false)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('returns false if body or secret is tampered', () => {
|
|
21
|
-
const originalSignature = crypto.createHmac('sha256', secret).update(body).digest('hex')
|
|
22
|
-
|
|
23
|
-
// wrong body
|
|
24
|
-
expect(isValidWebhookSignature(secret, '{"message":"tampered"}', originalSignature)).toBe(false)
|
|
25
|
-
|
|
26
|
-
// wrong secret
|
|
27
|
-
expect(isValidWebhookSignature('wrong_secret', body, originalSignature)).toBe(false)
|
|
28
|
-
})
|
|
29
|
-
})
|
|
1
|
+
import { isValidWebhookSignature } from '../../src/tw/utils'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
|
|
4
|
+
describe('isValidWebhookSignature', () => {
|
|
5
|
+
const secret = 'test_secret'
|
|
6
|
+
const body = '{"message":"hello"}'
|
|
7
|
+
|
|
8
|
+
it('returns true for a valid signature', () => {
|
|
9
|
+
const validSignature = crypto.createHmac('sha256', secret).update(body).digest('hex')
|
|
10
|
+
|
|
11
|
+
expect(isValidWebhookSignature(secret, body, validSignature)).toBe(true)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('returns false for an invalid signature', () => {
|
|
15
|
+
const invalidSignature = 'invalidsignature123'
|
|
16
|
+
|
|
17
|
+
expect(isValidWebhookSignature(secret, body, invalidSignature)).toBe(false)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('returns false if body or secret is tampered', () => {
|
|
21
|
+
const originalSignature = crypto.createHmac('sha256', secret).update(body).digest('hex')
|
|
22
|
+
|
|
23
|
+
// wrong body
|
|
24
|
+
expect(isValidWebhookSignature(secret, '{"message":"tampered"}', originalSignature)).toBe(false)
|
|
25
|
+
|
|
26
|
+
// wrong secret
|
|
27
|
+
expect(isValidWebhookSignature('wrong_secret', body, originalSignature)).toBe(false)
|
|
28
|
+
})
|
|
29
|
+
})
|
package/tests/tw/wallet.test.ts
CHANGED
|
@@ -1,206 +1,206 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
// tests/wallet.test.ts (or append to your existing test file)
|
|
3
|
-
import axios from 'axios'
|
|
4
|
-
import { pregenerateInAppWallet } from '../../src/tw/wallet'
|
|
5
|
-
import * as secrets from '../../src/secrets'
|
|
6
|
-
|
|
7
|
-
jest.mock('axios')
|
|
8
|
-
jest.mock('../../src/secrets')
|
|
9
|
-
|
|
10
|
-
const mockedAxios = axios as jest.Mocked<typeof axios>
|
|
11
|
-
const mockedGetSecret = secrets.getSecret as jest.Mock
|
|
12
|
-
|
|
13
|
-
const makeSuccessResponse = (data: any) => ({ data, status: 200 })
|
|
14
|
-
|
|
15
|
-
// small helper to synthesize axios-like errors
|
|
16
|
-
function makeAxiosError(message: string, status?: number, noResponse = false) {
|
|
17
|
-
const err: any = new Error(message)
|
|
18
|
-
err.isAxiosError = true
|
|
19
|
-
if (!noResponse) {
|
|
20
|
-
if (typeof status === 'number') {
|
|
21
|
-
err.response = { status, data: { message: `mocked ${status}` } }
|
|
22
|
-
} else {
|
|
23
|
-
// no numeric status provided but simulate a response
|
|
24
|
-
err.response = { status: 500, data: { message: 'mock' } }
|
|
25
|
-
}
|
|
26
|
-
} else {
|
|
27
|
-
// explicitly simulate network error: no response property
|
|
28
|
-
delete err.response
|
|
29
|
-
}
|
|
30
|
-
return err
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe('pregenerateInAppWallet with retry behavior', () => {
|
|
34
|
-
const mockProjectId = 'test-project'
|
|
35
|
-
const mockClientId = 'test-client-id'
|
|
36
|
-
const mockSecretKeyName = 'test-key'
|
|
37
|
-
const mockEmail = 'user@example.com'
|
|
38
|
-
const mockWalletAddress = '0xabc123'
|
|
39
|
-
const mockSecretValue = 'mock-secret-key'
|
|
40
|
-
|
|
41
|
-
const realMathRandom = Math.random
|
|
42
|
-
const realWarn = console.warn
|
|
43
|
-
const realError = console.error
|
|
44
|
-
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
jest.clearAllMocks()
|
|
47
|
-
// make retry jitter deterministic (so delay becomes 0)
|
|
48
|
-
Math.random = jest.fn(() => 0)
|
|
49
|
-
|
|
50
|
-
jest
|
|
51
|
-
.spyOn(axios, 'isAxiosError')
|
|
52
|
-
.mockImplementation((e: any) => Boolean(e && e.isAxiosError) as any)
|
|
53
|
-
// silence console.warn/error in passing tests (but still allow spying)
|
|
54
|
-
console.warn = jest.fn()
|
|
55
|
-
console.error = jest.fn()
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
afterEach(() => {
|
|
59
|
-
// restore
|
|
60
|
-
Math.random = realMathRandom
|
|
61
|
-
console.warn = realWarn
|
|
62
|
-
console.error = realError
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('returns null and logs if secret is missing', async () => {
|
|
66
|
-
// simulate secret manager returning nothing
|
|
67
|
-
mockedGetSecret.mockResolvedValueOnce(undefined)
|
|
68
|
-
|
|
69
|
-
const result = await pregenerateInAppWallet(
|
|
70
|
-
mockProjectId,
|
|
71
|
-
mockClientId,
|
|
72
|
-
mockSecretKeyName,
|
|
73
|
-
mockEmail
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
// should not call axios at all
|
|
77
|
-
expect(mockedAxios.post).not.toHaveBeenCalled()
|
|
78
|
-
expect(result).toBeNull()
|
|
79
|
-
|
|
80
|
-
// console.error should be called with "Missing TW secret key"
|
|
81
|
-
expect((console.error as jest.Mock).mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
82
|
-
expect((console.error as jest.Mock).mock.calls[0][0]).toEqual('Missing TW secret key')
|
|
83
|
-
// second arg should contain the projectId and secretName
|
|
84
|
-
expect((console.error as jest.Mock).mock.calls[0][1]).toEqual(
|
|
85
|
-
expect.objectContaining({ projectId: mockProjectId, twSecretKeyName: mockSecretKeyName })
|
|
86
|
-
)
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('retries on 500 then succeeds and returns address', async () => {
|
|
90
|
-
mockedGetSecret.mockResolvedValue(mockSecretValue)
|
|
91
|
-
|
|
92
|
-
// first call: server 500 -> rejected
|
|
93
|
-
mockedAxios.post
|
|
94
|
-
.mockRejectedValueOnce(makeAxiosError('server error', 500))
|
|
95
|
-
// second call: success
|
|
96
|
-
.mockResolvedValueOnce({
|
|
97
|
-
data: {
|
|
98
|
-
wallet: { address: mockWalletAddress }
|
|
99
|
-
}
|
|
100
|
-
} as any)
|
|
101
|
-
|
|
102
|
-
const result = await pregenerateInAppWallet(
|
|
103
|
-
mockProjectId,
|
|
104
|
-
mockClientId,
|
|
105
|
-
mockSecretKeyName,
|
|
106
|
-
mockEmail
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
expect(mockedGetSecret).toHaveBeenCalledWith(mockProjectId, mockSecretKeyName)
|
|
110
|
-
expect(mockedAxios.post).toHaveBeenCalledTimes(2)
|
|
111
|
-
expect(result).toBe(mockWalletAddress)
|
|
112
|
-
// onRetry should have been called (we log via console.warn in our version)
|
|
113
|
-
expect((console.warn as jest.Mock).mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('does NOT retry on 400 and returns null immediately', async () => {
|
|
117
|
-
mockedGetSecret.mockResolvedValue(mockSecretValue)
|
|
118
|
-
mockedAxios.post.mockRejectedValue(makeAxiosError('bad request', 400))
|
|
119
|
-
|
|
120
|
-
const result = await pregenerateInAppWallet(
|
|
121
|
-
mockProjectId,
|
|
122
|
-
mockClientId,
|
|
123
|
-
mockSecretKeyName,
|
|
124
|
-
mockEmail
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
expect(mockedAxios.post).toHaveBeenCalledTimes(1)
|
|
128
|
-
expect(result).toBeNull()
|
|
129
|
-
// console.warn should not indicate retry
|
|
130
|
-
expect((console.warn as jest.Mock).mock.calls.length).toBe(0)
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('retries on network/no-response errors and then succeeds', async () => {
|
|
134
|
-
mockedGetSecret.mockResolvedValue(mockSecretValue)
|
|
135
|
-
|
|
136
|
-
// First: network error (no response)
|
|
137
|
-
mockedAxios.post
|
|
138
|
-
.mockRejectedValueOnce(makeAxiosError('network down', undefined, true))
|
|
139
|
-
.mockResolvedValueOnce({
|
|
140
|
-
data: { wallet: { address: mockWalletAddress } }
|
|
141
|
-
} as any)
|
|
142
|
-
|
|
143
|
-
const result = await pregenerateInAppWallet(
|
|
144
|
-
mockProjectId,
|
|
145
|
-
mockClientId,
|
|
146
|
-
mockSecretKeyName,
|
|
147
|
-
mockEmail
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
expect(mockedAxios.post).toHaveBeenCalledTimes(2)
|
|
151
|
-
expect(result).toBe(mockWalletAddress)
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('exhausts retries on repeated 500s and returns null', async () => {
|
|
155
|
-
mockedGetSecret.mockResolvedValue(mockSecretValue)
|
|
156
|
-
|
|
157
|
-
// Always 500 errors
|
|
158
|
-
mockedAxios.post.mockRejectedValue(makeAxiosError('server error', 500))
|
|
159
|
-
|
|
160
|
-
const result = await pregenerateInAppWallet(
|
|
161
|
-
mockProjectId,
|
|
162
|
-
mockClientId,
|
|
163
|
-
mockSecretKeyName,
|
|
164
|
-
mockEmail
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
// retry attempts are implementation-dependent (your retry opts). We assert >1 calls
|
|
168
|
-
expect(mockedAxios.post.mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
169
|
-
// final result should be null when retries exhausted
|
|
170
|
-
expect(result).toBeNull()
|
|
171
|
-
// error should have been logged
|
|
172
|
-
expect((console.error as jest.Mock).mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
it('logs invalid wallet shape and returns null when wallet.address is missing (2xx response)', async () => {
|
|
176
|
-
mockedGetSecret.mockResolvedValueOnce(mockSecretValue)
|
|
177
|
-
|
|
178
|
-
// Simulate a 200 response that has wallet but no address
|
|
179
|
-
mockedAxios.post.mockResolvedValueOnce(makeSuccessResponse({ wallet: {} }))
|
|
180
|
-
|
|
181
|
-
const result = await pregenerateInAppWallet(
|
|
182
|
-
mockProjectId,
|
|
183
|
-
mockClientId,
|
|
184
|
-
mockSecretKeyName,
|
|
185
|
-
mockEmail
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
// axios was called once, but the function returns null (final failure)
|
|
189
|
-
expect(mockedAxios.post).toHaveBeenCalledTimes(1)
|
|
190
|
-
expect(result).toBeNull()
|
|
191
|
-
|
|
192
|
-
// console.error should be called with the shape error message
|
|
193
|
-
expect((console.error as jest.Mock).mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
194
|
-
|
|
195
|
-
// the first arg of the logged call should be the string we log in the implementation
|
|
196
|
-
// (adjust if your message differs)
|
|
197
|
-
const firstArg = (console.error as jest.Mock).mock.calls[0][0]
|
|
198
|
-
expect(firstArg).toEqual(
|
|
199
|
-
'Invalid wallet response shape' /* or 'Invalid wallet response shape' */
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
// the second arg should contain status and data info
|
|
203
|
-
const secondArg = (console.error as jest.Mock).mock.calls[0][1]
|
|
204
|
-
expect(secondArg).toEqual(expect.objectContaining({ status: 200, data: { wallet: {} } }))
|
|
205
|
-
})
|
|
206
|
-
})
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
// tests/wallet.test.ts (or append to your existing test file)
|
|
3
|
+
import axios from 'axios'
|
|
4
|
+
import { pregenerateInAppWallet } from '../../src/tw/wallet'
|
|
5
|
+
import * as secrets from '../../src/secrets'
|
|
6
|
+
|
|
7
|
+
jest.mock('axios')
|
|
8
|
+
jest.mock('../../src/secrets')
|
|
9
|
+
|
|
10
|
+
const mockedAxios = axios as jest.Mocked<typeof axios>
|
|
11
|
+
const mockedGetSecret = secrets.getSecret as jest.Mock
|
|
12
|
+
|
|
13
|
+
const makeSuccessResponse = (data: any) => ({ data, status: 200 })
|
|
14
|
+
|
|
15
|
+
// small helper to synthesize axios-like errors
|
|
16
|
+
function makeAxiosError(message: string, status?: number, noResponse = false) {
|
|
17
|
+
const err: any = new Error(message)
|
|
18
|
+
err.isAxiosError = true
|
|
19
|
+
if (!noResponse) {
|
|
20
|
+
if (typeof status === 'number') {
|
|
21
|
+
err.response = { status, data: { message: `mocked ${status}` } }
|
|
22
|
+
} else {
|
|
23
|
+
// no numeric status provided but simulate a response
|
|
24
|
+
err.response = { status: 500, data: { message: 'mock' } }
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
// explicitly simulate network error: no response property
|
|
28
|
+
delete err.response
|
|
29
|
+
}
|
|
30
|
+
return err
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('pregenerateInAppWallet with retry behavior', () => {
|
|
34
|
+
const mockProjectId = 'test-project'
|
|
35
|
+
const mockClientId = 'test-client-id'
|
|
36
|
+
const mockSecretKeyName = 'test-key'
|
|
37
|
+
const mockEmail = 'user@example.com'
|
|
38
|
+
const mockWalletAddress = '0xabc123'
|
|
39
|
+
const mockSecretValue = 'mock-secret-key'
|
|
40
|
+
|
|
41
|
+
const realMathRandom = Math.random
|
|
42
|
+
const realWarn = console.warn
|
|
43
|
+
const realError = console.error
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
jest.clearAllMocks()
|
|
47
|
+
// make retry jitter deterministic (so delay becomes 0)
|
|
48
|
+
Math.random = jest.fn(() => 0)
|
|
49
|
+
|
|
50
|
+
jest
|
|
51
|
+
.spyOn(axios, 'isAxiosError')
|
|
52
|
+
.mockImplementation((e: any) => Boolean(e && e.isAxiosError) as any)
|
|
53
|
+
// silence console.warn/error in passing tests (but still allow spying)
|
|
54
|
+
console.warn = jest.fn()
|
|
55
|
+
console.error = jest.fn()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
// restore
|
|
60
|
+
Math.random = realMathRandom
|
|
61
|
+
console.warn = realWarn
|
|
62
|
+
console.error = realError
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('returns null and logs if secret is missing', async () => {
|
|
66
|
+
// simulate secret manager returning nothing
|
|
67
|
+
mockedGetSecret.mockResolvedValueOnce(undefined)
|
|
68
|
+
|
|
69
|
+
const result = await pregenerateInAppWallet(
|
|
70
|
+
mockProjectId,
|
|
71
|
+
mockClientId,
|
|
72
|
+
mockSecretKeyName,
|
|
73
|
+
mockEmail
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
// should not call axios at all
|
|
77
|
+
expect(mockedAxios.post).not.toHaveBeenCalled()
|
|
78
|
+
expect(result).toBeNull()
|
|
79
|
+
|
|
80
|
+
// console.error should be called with "Missing TW secret key"
|
|
81
|
+
expect((console.error as jest.Mock).mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
82
|
+
expect((console.error as jest.Mock).mock.calls[0][0]).toEqual('Missing TW secret key')
|
|
83
|
+
// second arg should contain the projectId and secretName
|
|
84
|
+
expect((console.error as jest.Mock).mock.calls[0][1]).toEqual(
|
|
85
|
+
expect.objectContaining({ projectId: mockProjectId, twSecretKeyName: mockSecretKeyName })
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('retries on 500 then succeeds and returns address', async () => {
|
|
90
|
+
mockedGetSecret.mockResolvedValue(mockSecretValue)
|
|
91
|
+
|
|
92
|
+
// first call: server 500 -> rejected
|
|
93
|
+
mockedAxios.post
|
|
94
|
+
.mockRejectedValueOnce(makeAxiosError('server error', 500))
|
|
95
|
+
// second call: success
|
|
96
|
+
.mockResolvedValueOnce({
|
|
97
|
+
data: {
|
|
98
|
+
wallet: { address: mockWalletAddress }
|
|
99
|
+
}
|
|
100
|
+
} as any)
|
|
101
|
+
|
|
102
|
+
const result = await pregenerateInAppWallet(
|
|
103
|
+
mockProjectId,
|
|
104
|
+
mockClientId,
|
|
105
|
+
mockSecretKeyName,
|
|
106
|
+
mockEmail
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
expect(mockedGetSecret).toHaveBeenCalledWith(mockProjectId, mockSecretKeyName)
|
|
110
|
+
expect(mockedAxios.post).toHaveBeenCalledTimes(2)
|
|
111
|
+
expect(result).toBe(mockWalletAddress)
|
|
112
|
+
// onRetry should have been called (we log via console.warn in our version)
|
|
113
|
+
expect((console.warn as jest.Mock).mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('does NOT retry on 400 and returns null immediately', async () => {
|
|
117
|
+
mockedGetSecret.mockResolvedValue(mockSecretValue)
|
|
118
|
+
mockedAxios.post.mockRejectedValue(makeAxiosError('bad request', 400))
|
|
119
|
+
|
|
120
|
+
const result = await pregenerateInAppWallet(
|
|
121
|
+
mockProjectId,
|
|
122
|
+
mockClientId,
|
|
123
|
+
mockSecretKeyName,
|
|
124
|
+
mockEmail
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
expect(mockedAxios.post).toHaveBeenCalledTimes(1)
|
|
128
|
+
expect(result).toBeNull()
|
|
129
|
+
// console.warn should not indicate retry
|
|
130
|
+
expect((console.warn as jest.Mock).mock.calls.length).toBe(0)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('retries on network/no-response errors and then succeeds', async () => {
|
|
134
|
+
mockedGetSecret.mockResolvedValue(mockSecretValue)
|
|
135
|
+
|
|
136
|
+
// First: network error (no response)
|
|
137
|
+
mockedAxios.post
|
|
138
|
+
.mockRejectedValueOnce(makeAxiosError('network down', undefined, true))
|
|
139
|
+
.mockResolvedValueOnce({
|
|
140
|
+
data: { wallet: { address: mockWalletAddress } }
|
|
141
|
+
} as any)
|
|
142
|
+
|
|
143
|
+
const result = await pregenerateInAppWallet(
|
|
144
|
+
mockProjectId,
|
|
145
|
+
mockClientId,
|
|
146
|
+
mockSecretKeyName,
|
|
147
|
+
mockEmail
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
expect(mockedAxios.post).toHaveBeenCalledTimes(2)
|
|
151
|
+
expect(result).toBe(mockWalletAddress)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('exhausts retries on repeated 500s and returns null', async () => {
|
|
155
|
+
mockedGetSecret.mockResolvedValue(mockSecretValue)
|
|
156
|
+
|
|
157
|
+
// Always 500 errors
|
|
158
|
+
mockedAxios.post.mockRejectedValue(makeAxiosError('server error', 500))
|
|
159
|
+
|
|
160
|
+
const result = await pregenerateInAppWallet(
|
|
161
|
+
mockProjectId,
|
|
162
|
+
mockClientId,
|
|
163
|
+
mockSecretKeyName,
|
|
164
|
+
mockEmail
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
// retry attempts are implementation-dependent (your retry opts). We assert >1 calls
|
|
168
|
+
expect(mockedAxios.post.mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
169
|
+
// final result should be null when retries exhausted
|
|
170
|
+
expect(result).toBeNull()
|
|
171
|
+
// error should have been logged
|
|
172
|
+
expect((console.error as jest.Mock).mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('logs invalid wallet shape and returns null when wallet.address is missing (2xx response)', async () => {
|
|
176
|
+
mockedGetSecret.mockResolvedValueOnce(mockSecretValue)
|
|
177
|
+
|
|
178
|
+
// Simulate a 200 response that has wallet but no address
|
|
179
|
+
mockedAxios.post.mockResolvedValueOnce(makeSuccessResponse({ wallet: {} }))
|
|
180
|
+
|
|
181
|
+
const result = await pregenerateInAppWallet(
|
|
182
|
+
mockProjectId,
|
|
183
|
+
mockClientId,
|
|
184
|
+
mockSecretKeyName,
|
|
185
|
+
mockEmail
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
// axios was called once, but the function returns null (final failure)
|
|
189
|
+
expect(mockedAxios.post).toHaveBeenCalledTimes(1)
|
|
190
|
+
expect(result).toBeNull()
|
|
191
|
+
|
|
192
|
+
// console.error should be called with the shape error message
|
|
193
|
+
expect((console.error as jest.Mock).mock.calls.length).toBeGreaterThanOrEqual(1)
|
|
194
|
+
|
|
195
|
+
// the first arg of the logged call should be the string we log in the implementation
|
|
196
|
+
// (adjust if your message differs)
|
|
197
|
+
const firstArg = (console.error as jest.Mock).mock.calls[0][0]
|
|
198
|
+
expect(firstArg).toEqual(
|
|
199
|
+
'Invalid wallet response shape' /* or 'Invalid wallet response shape' */
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
// the second arg should contain status and data info
|
|
203
|
+
const secondArg = (console.error as jest.Mock).mock.calls[0][1]
|
|
204
|
+
expect(secondArg).toEqual(expect.objectContaining({ status: 200, data: { wallet: {} } }))
|
|
205
|
+
})
|
|
206
|
+
})
|
package/tests/validator.ts
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
// isValidEmail.test.ts
|
|
2
|
-
import { describe, it, expect } from '@jest/globals'
|
|
3
|
-
import { isValidEmail } from '../src'
|
|
4
|
-
|
|
5
|
-
describe('isValidEmail', () => {
|
|
6
|
-
it('returns true for simple valid emails', () => {
|
|
7
|
-
expect(isValidEmail('test@example.com')).toBe(true)
|
|
8
|
-
expect(isValidEmail('user.name@domain.co')).toBe(true)
|
|
9
|
-
expect(isValidEmail('user_name+tag@sub.domain.org')).toBe(true)
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('returns false for missing parts', () => {
|
|
13
|
-
expect(isValidEmail('')).toBe(false)
|
|
14
|
-
expect(isValidEmail('plainaddress')).toBe(false)
|
|
15
|
-
expect(isValidEmail('@no-local-part.com')).toBe(false)
|
|
16
|
-
expect(isValidEmail('username@')).toBe(false)
|
|
17
|
-
expect(isValidEmail('user@domain')).toBe(false)
|
|
18
|
-
expect(isValidEmail('username@.com')).toBe(false)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('handles whitespace correctly', () => {
|
|
22
|
-
expect(isValidEmail(' test@example.com ')).toBe(true) // trims input
|
|
23
|
-
expect(isValidEmail('\nuser@domain.com\t')).toBe(true)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('rejects invalid characters or formats', () => {
|
|
27
|
-
expect(isValidEmail('user@@domain.com')).toBe(false)
|
|
28
|
-
expect(isValidEmail('user@domain,com')).toBe(false)
|
|
29
|
-
expect(isValidEmail('user@domain..com')).toBe(false)
|
|
30
|
-
expect(isValidEmail('user@.domain.com')).toBe(false)
|
|
31
|
-
expect(isValidEmail('user@domain com')).toBe(false)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('accepts minimal valid domain structures', () => {
|
|
35
|
-
expect(isValidEmail('x@y.z')).toBe(true) // still valid
|
|
36
|
-
})
|
|
37
|
-
})
|
|
1
|
+
// isValidEmail.test.ts
|
|
2
|
+
import { describe, it, expect } from '@jest/globals'
|
|
3
|
+
import { isValidEmail } from '../src'
|
|
4
|
+
|
|
5
|
+
describe('isValidEmail', () => {
|
|
6
|
+
it('returns true for simple valid emails', () => {
|
|
7
|
+
expect(isValidEmail('test@example.com')).toBe(true)
|
|
8
|
+
expect(isValidEmail('user.name@domain.co')).toBe(true)
|
|
9
|
+
expect(isValidEmail('user_name+tag@sub.domain.org')).toBe(true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('returns false for missing parts', () => {
|
|
13
|
+
expect(isValidEmail('')).toBe(false)
|
|
14
|
+
expect(isValidEmail('plainaddress')).toBe(false)
|
|
15
|
+
expect(isValidEmail('@no-local-part.com')).toBe(false)
|
|
16
|
+
expect(isValidEmail('username@')).toBe(false)
|
|
17
|
+
expect(isValidEmail('user@domain')).toBe(false)
|
|
18
|
+
expect(isValidEmail('username@.com')).toBe(false)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('handles whitespace correctly', () => {
|
|
22
|
+
expect(isValidEmail(' test@example.com ')).toBe(true) // trims input
|
|
23
|
+
expect(isValidEmail('\nuser@domain.com\t')).toBe(true)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('rejects invalid characters or formats', () => {
|
|
27
|
+
expect(isValidEmail('user@@domain.com')).toBe(false)
|
|
28
|
+
expect(isValidEmail('user@domain,com')).toBe(false)
|
|
29
|
+
expect(isValidEmail('user@domain..com')).toBe(false)
|
|
30
|
+
expect(isValidEmail('user@.domain.com')).toBe(false)
|
|
31
|
+
expect(isValidEmail('user@domain com')).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('accepts minimal valid domain structures', () => {
|
|
35
|
+
expect(isValidEmail('x@y.z')).toBe(true) // still valid
|
|
36
|
+
})
|
|
37
|
+
})
|
package/tsconfig.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "CommonJS",
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"strict": true,
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"moduleResolution": "node",
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"resolveJsonModule": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
"types": ["node", "jest"]
|
|
14
|
-
},
|
|
15
|
-
"include": ["src"],
|
|
16
|
-
"exclude": ["node_modules", "dist"]
|
|
17
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"moduleResolution": "node",
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"types": ["node", "jest"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|