Skip to content

Commit ae1bab2

Browse files
committed
chore: added tests for ethereum.ts
1 parent 7835df5 commit ae1bab2

File tree

1 file changed

+314
-0
lines changed

1 file changed

+314
-0
lines changed

test/ethereum.test.ts

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
import {
2+
getAddress,
3+
fromHex,
4+
toHex,
5+
createSiweMessage,
6+
type SiweMessage,
7+
type Hex,
8+
} from '../src/lib/web3/ethereum'
9+
10+
describe('ethereum', () => {
11+
describe('getAddress', () => {
12+
test('should return lowercase address for valid Ethereum address', () => {
13+
const validAddresses = [
14+
'0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
15+
'0x742d35cc6634c0532925a3b8d4c9db96c4b4d8b6',
16+
'0x1234567890123456789012345678901234567890',
17+
'0xABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCD',
18+
]
19+
20+
validAddresses.forEach((address) => {
21+
const result = getAddress(address)
22+
expect(result).toBe(address.toLowerCase())
23+
expect(result).toMatch(/^0x[a-f0-9]{40}$/)
24+
})
25+
})
26+
27+
test('should throw error for invalid address format', () => {
28+
const invalidAddresses = [
29+
'0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b', // too short
30+
'0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b67', // too long
31+
'742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', // missing 0x
32+
'0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8bG', // invalid character
33+
'0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b!', // invalid character
34+
'', // empty string
35+
'not-an-address', // random string
36+
]
37+
38+
invalidAddresses.forEach((address) => {
39+
expect(() => getAddress(address)).toThrow(
40+
`@supabase/auth-js: Address "${address}" is invalid.`
41+
)
42+
})
43+
})
44+
45+
test('should handle edge cases', () => {
46+
// Valid address with all zeros
47+
expect(getAddress('0x0000000000000000000000000000000000000000')).toBe(
48+
'0x0000000000000000000000000000000000000000'
49+
)
50+
51+
// Valid address with all f's
52+
expect(getAddress('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')).toBe(
53+
'0xffffffffffffffffffffffffffffffffffffffff'
54+
)
55+
})
56+
})
57+
58+
describe('fromHex', () => {
59+
test('should convert hex to number', () => {
60+
const testCases: Array<{ hex: Hex; expected: number }> = [
61+
{ hex: '0x0', expected: 0 },
62+
{ hex: '0x1', expected: 1 },
63+
{ hex: '0xff', expected: 255 },
64+
{ hex: '0x100', expected: 256 },
65+
{ hex: '0xffff', expected: 65535 },
66+
{ hex: '0x123456', expected: 1193046 },
67+
{ hex: '0x7fffffff', expected: 2147483647 },
68+
]
69+
70+
testCases.forEach(({ hex, expected }) => {
71+
expect(fromHex(hex)).toBe(expected)
72+
})
73+
})
74+
75+
test('should handle uppercase and lowercase hex', () => {
76+
expect(fromHex('0xFF')).toBe(255)
77+
expect(fromHex('0xff')).toBe(255)
78+
expect(fromHex('0xFf')).toBe(255)
79+
})
80+
})
81+
82+
describe('toHex', () => {
83+
test('should convert string to hex', () => {
84+
const testCases: Array<{ input: string; expected: Hex }> = [
85+
{ input: '', expected: '0x' },
86+
{ input: 'a', expected: '0x61' },
87+
{ input: 'hello', expected: '0x68656c6c6f' },
88+
{ input: 'Hello World!', expected: '0x48656c6c6f20576f726c6421' },
89+
{ input: '123', expected: '0x313233' },
90+
{ input: 'привет', expected: '0xd0bfd180d0b8d0b2d0b5d182' },
91+
]
92+
93+
testCases.forEach(({ input, expected }) => {
94+
expect(toHex(input)).toBe(expected)
95+
})
96+
})
97+
98+
test('should handle special characters', () => {
99+
expect(toHex('\n')).toBe('0x0a')
100+
expect(toHex('\t')).toBe('0x09')
101+
expect(toHex('\r')).toBe('0x0d')
102+
expect(toHex(' ')).toBe('0x20')
103+
expect(toHex('!@#$%^&*()')).toBe('0x21402324255e262a2829')
104+
})
105+
})
106+
107+
describe('createSiweMessage', () => {
108+
const baseMessage: SiweMessage = {
109+
address: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
110+
chainId: 1,
111+
domain: 'example.com',
112+
uri: 'https://example.com',
113+
version: '1',
114+
}
115+
116+
test('should create basic SIWE message', () => {
117+
const message = createSiweMessage(baseMessage)
118+
119+
expect(message).toContain('example.com wants you to sign in with your Ethereum account:')
120+
expect(message).toContain('0x742d35cc6634c0532925a3b8d4c9db96c4b4d8b6')
121+
expect(message).toContain('URI: https://example.com')
122+
expect(message).toContain('Version: 1')
123+
expect(message).toContain('Chain ID: 1')
124+
expect(message).toContain('Issued At:')
125+
})
126+
127+
test('should include optional fields when provided', () => {
128+
const messageWithOptions: SiweMessage = {
129+
...baseMessage,
130+
statement: 'Please sign this message to authenticate',
131+
nonce: '1234567890',
132+
expirationTime: new Date('2024-12-31T23:59:59Z'),
133+
notBefore: new Date('2024-01-01T00:00:00Z'),
134+
requestId: 'req-123',
135+
resources: ['https://example.com/resource1', 'https://example.com/resource2'],
136+
scheme: 'https',
137+
}
138+
139+
const message = createSiweMessage(messageWithOptions)
140+
141+
expect(message).toContain('Please sign this message to authenticate')
142+
expect(message).toContain('Nonce: 1234567890')
143+
expect(message).toContain('Expiration Time: 2024-12-31T23:59:59.000Z')
144+
expect(message).toContain('Not Before: 2024-01-01T00:00:00.000Z')
145+
expect(message).toContain('Request ID: req-123')
146+
expect(message).toContain('Resources:')
147+
expect(message).toContain('- https://example.com/resource1')
148+
expect(message).toContain('- https://example.com/resource2')
149+
expect(message).toContain('https://example.com wants you to sign in')
150+
})
151+
152+
test('should handle scheme correctly', () => {
153+
const messageWithScheme: SiweMessage = {
154+
...baseMessage,
155+
scheme: 'https',
156+
}
157+
158+
const message = createSiweMessage(messageWithScheme)
159+
expect(message).toContain('https://example.com wants you to sign in')
160+
})
161+
162+
test('should validate chainId', () => {
163+
const invalidChainId: SiweMessage = {
164+
...baseMessage,
165+
chainId: 1.5, // non-integer
166+
}
167+
168+
expect(() => createSiweMessage(invalidChainId)).toThrow(
169+
'@supabase/auth-js: Invalid SIWE message field "chainId". Chain ID must be a EIP-155 chain ID. Provided value: 1.5'
170+
)
171+
})
172+
173+
test('should validate domain', () => {
174+
const invalidDomain: SiweMessage = {
175+
...baseMessage,
176+
domain: '', // empty domain
177+
}
178+
179+
expect(() => createSiweMessage(invalidDomain)).toThrow(
180+
'@supabase/auth-js: Invalid SIWE message field "domain". Domain must be provided.'
181+
)
182+
})
183+
184+
test('should validate nonce length', () => {
185+
const shortNonce: SiweMessage = {
186+
...baseMessage,
187+
nonce: '123', // too short
188+
}
189+
190+
expect(() => createSiweMessage(shortNonce)).toThrow(
191+
'@supabase/auth-js: Invalid SIWE message field "nonce". Nonce must be at least 8 characters. Provided value: 123'
192+
)
193+
})
194+
195+
test('should validate uri', () => {
196+
const invalidUri: SiweMessage = {
197+
...baseMessage,
198+
uri: '', // empty uri
199+
}
200+
201+
expect(() => createSiweMessage(invalidUri)).toThrow(
202+
'@supabase/auth-js: Invalid SIWE message field "uri". URI must be provided.'
203+
)
204+
})
205+
206+
test('should validate version', () => {
207+
const invalidVersion: SiweMessage = {
208+
...baseMessage,
209+
version: '2' as any, // invalid version
210+
}
211+
212+
expect(() => createSiweMessage(invalidVersion)).toThrow(
213+
'@supabase/auth-js: Invalid SIWE message field "version". Version must be \'1\'. Provided value: 2'
214+
)
215+
})
216+
217+
test('should validate statement does not contain newlines', () => {
218+
const invalidStatement: SiweMessage = {
219+
...baseMessage,
220+
statement: 'Line 1\nLine 2', // contains newline
221+
}
222+
223+
expect(() => createSiweMessage(invalidStatement)).toThrow(
224+
'@supabase/auth-js: Invalid SIWE message field "statement". Statement must not include \'\\n\'. Provided value: Line 1\nLine 2'
225+
)
226+
})
227+
228+
test('should validate resources array', () => {
229+
const invalidResources: SiweMessage = {
230+
...baseMessage,
231+
resources: ['valid-resource', '', 'another-valid'], // contains empty string
232+
}
233+
234+
expect(() => createSiweMessage(invalidResources)).toThrow(
235+
'@supabase/auth-js: Invalid SIWE message field "resources". Every resource must be a valid string. Provided value: '
236+
)
237+
})
238+
239+
test('should validate resources are strings', () => {
240+
const invalidResources: SiweMessage = {
241+
...baseMessage,
242+
resources: ['valid-resource', null as any, 'another-valid'], // contains null
243+
}
244+
245+
expect(() => createSiweMessage(invalidResources)).toThrow(
246+
'@supabase/auth-js: Invalid SIWE message field "resources". Every resource must be a valid string. Provided value: null'
247+
)
248+
})
249+
250+
test('should handle empty resources array', () => {
251+
const messageWithEmptyResources: SiweMessage = {
252+
...baseMessage,
253+
resources: [],
254+
}
255+
256+
const message = createSiweMessage(messageWithEmptyResources)
257+
expect(message).toContain('Resources:')
258+
})
259+
260+
test('should format message correctly with all optional fields', () => {
261+
const fullMessage: SiweMessage = {
262+
address: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
263+
chainId: 137,
264+
domain: 'polygon.example.com',
265+
uri: 'https://polygon.example.com/auth',
266+
version: '1',
267+
statement: 'Sign in to access your account',
268+
nonce: 'abcdef1234567890',
269+
expirationTime: new Date('2024-12-31T23:59:59Z'),
270+
notBefore: new Date('2024-01-01T00:00:00Z'),
271+
requestId: 'auth-request-12345',
272+
resources: [
273+
'https://polygon.example.com/api',
274+
'https://polygon.example.com/dashboard',
275+
],
276+
scheme: 'https',
277+
}
278+
279+
const message = createSiweMessage(fullMessage)
280+
281+
// Check the structure
282+
const lines = message.split('\n')
283+
expect(lines[0]).toBe('https://polygon.example.com wants you to sign in with your Ethereum account:')
284+
expect(lines[1]).toBe('0x742d35cc6634c0532925a3b8d4c9db96c4b4d8b6')
285+
expect(lines[2]).toBe('')
286+
expect(lines[3]).toBe('Sign in to access your account')
287+
expect(lines[4]).toBe('')
288+
expect(lines[5]).toBe('URI: https://polygon.example.com/auth')
289+
expect(lines[6]).toBe('Version: 1')
290+
expect(lines[7]).toBe('Chain ID: 137')
291+
expect(lines[8]).toBe('Nonce: abcdef1234567890')
292+
expect(lines[9]).toMatch(/^Issued At: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/)
293+
expect(lines[10]).toBe('Expiration Time: 2024-12-31T23:59:59.000Z')
294+
expect(lines[11]).toBe('Not Before: 2024-01-01T00:00:00.000Z')
295+
expect(lines[12]).toBe('Request ID: auth-request-12345')
296+
expect(lines[13]).toBe('Resources:')
297+
expect(lines[14]).toBe('- https://polygon.example.com/api')
298+
expect(lines[15]).toBe('- https://polygon.example.com/dashboard')
299+
})
300+
301+
test('should handle issuedAt default value', () => {
302+
const beforeTest = new Date()
303+
const message = createSiweMessage(baseMessage)
304+
const afterTest = new Date()
305+
306+
const issuedAtMatch = message.match(/Issued At: (.+)/)
307+
expect(issuedAtMatch).toBeTruthy()
308+
309+
const issuedAt = new Date(issuedAtMatch![1])
310+
expect(issuedAt.getTime()).toBeGreaterThanOrEqual(beforeTest.getTime())
311+
expect(issuedAt.getTime()).toBeLessThanOrEqual(afterTest.getTime())
312+
})
313+
})
314+
})

0 commit comments

Comments
 (0)