-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(sdk tests): add a few sdk tests
- Loading branch information
1 parent
dda9ebe
commit b9ec43e
Showing
6 changed files
with
471 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
228 changes: 228 additions & 0 deletions
228
packages/sdk/src/client/passkey/actions/account.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
import { describe, expect, test, vi } from 'vitest' | ||
import { type Address, type Hash, type TransactionReceipt } from 'viem' | ||
import { writeContract, waitForTransactionReceipt } from 'viem/actions' | ||
import { deployAccount } from './account.js' | ||
|
||
// Mock the passkey utils | ||
vi.mock('../../../utils/passkey.js', () => ({ | ||
getPublicKeyBytesFromPasskeySignature: vi.fn().mockReturnValue([ | ||
Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex'), | ||
Buffer.from('0000000000000000000000000000000000000000000000000000000000000002', 'hex') | ||
]) | ||
})) | ||
|
||
// Mock viem actions | ||
vi.mock('viem/actions', () => ({ | ||
writeContract: vi.fn(), | ||
waitForTransactionReceipt: vi.fn(), | ||
})) | ||
|
||
// Add FactoryAbi mock at the top with other mocks | ||
vi.mock('../../../abi/Factory.js', () => ({ | ||
FactoryAbi: [ | ||
{ | ||
inputs: [ | ||
{ type: 'bytes32', name: '_salt' }, | ||
{ type: 'string', name: '_uniqueAccountId' }, | ||
{ type: 'bytes[]', name: '_initialValidators' }, | ||
{ type: 'address[]', name: '_initialK1Owners' }, | ||
], | ||
name: 'deployProxySsoAccount', | ||
outputs: [{ type: 'address', name: 'accountAddress' }], | ||
stateMutability: 'nonpayable', | ||
type: 'function', | ||
}, | ||
], | ||
})) | ||
|
||
describe('deployAccount', () => { | ||
|
||
// Setup common test data | ||
const mockSalt = new Uint8Array([ | ||
213, 36, 52, 69, 251, 82, 199, 45, 113, 6, 20, 213, 78, 47, 165, | ||
164, 106, 221, 105, 67, 247, 47, 200, 167, 137, 64, 151, 12, 179, | ||
74, 90, 23 | ||
]) | ||
|
||
// CBOR-encoded COSE key with known x,y coordinates | ||
const mockCredentialPublicKey = new Uint8Array([ | ||
0xa5, // map of 5 pairs | ||
0x01, // key 1 (kty) | ||
0x02, // value 2 (EC2) | ||
0x03, // key 3 (alg) | ||
0x26, // value -7 (ES256) | ||
0x20, // key -1 (crv) | ||
0x01, // value 1 (P-256) | ||
0x21, // key -2 (x coordinate) | ||
0x58, 0x20, // bytes(32) | ||
...new Uint8Array(32).fill(0x01), // x coordinate filled with 0x01 | ||
0x22, // key -3 (y coordinate) | ||
0x58, 0x20, // bytes(32) | ||
...new Uint8Array(32).fill(0x02), // y coordinate filled with 0x02 | ||
]) | ||
|
||
const mockClient = { | ||
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', | ||
chain: { id: 1 }, | ||
} as any | ||
const mockContracts = { | ||
accountFactory: '0x1234567890123456789012345678901234567890' as Address, | ||
passkey: '0x2234567890123456789012345678901234567890' as Address, | ||
session: '0x3234567890123456789012345678901234567890' as Address, | ||
} | ||
|
||
const mockTransactionHash = '0xhash' as Hash | ||
const mockTransactionReceipt: TransactionReceipt = { | ||
status: 'success', | ||
contractAddress: '0x4234567890123456789012345678901234567890', | ||
blockNumber: 1n, | ||
blockHash: '0xblockhash' as Hash, | ||
transactionHash: mockTransactionHash, | ||
logs: [], | ||
logsBloom: '0x', | ||
cumulativeGasUsed: 0n, | ||
effectiveGasPrice: 0n, | ||
gasUsed: 0n, | ||
type: 'eip1559', | ||
from: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', | ||
to: '0x1234567890123456789012345678901234567890', | ||
transactionIndex: 0, | ||
} | ||
|
||
test('deploys account successfully', async () => { | ||
// Setup mocks | ||
vi.mocked(writeContract).mockResolvedValue(mockTransactionHash) | ||
vi.mocked(waitForTransactionReceipt).mockResolvedValue(mockTransactionReceipt) | ||
|
||
const result = await deployAccount(mockClient, { | ||
credentialPublicKey: mockCredentialPublicKey, | ||
contracts: mockContracts, | ||
expectedOrigin: 'https://example.com', | ||
salt: mockSalt, | ||
}) | ||
|
||
// Verify the result | ||
expect(result).toEqual({ | ||
address: '0x4234567890123456789012345678901234567890', | ||
transactionReceipt: mockTransactionReceipt, | ||
}) | ||
|
||
// Verify writeContract was called with correct parameters | ||
expect(writeContract).toHaveBeenCalledWith( | ||
mockClient, | ||
expect.objectContaining({ | ||
address: mockContracts.accountFactory, | ||
functionName: 'deployProxySsoAccount', | ||
}) | ||
) | ||
}) | ||
|
||
test('handles transaction failure', async () => { | ||
// Setup mock for failed transaction | ||
vi.mocked(writeContract).mockResolvedValue(mockTransactionHash) | ||
vi.mocked(waitForTransactionReceipt).mockResolvedValue({ | ||
...mockTransactionReceipt, | ||
status: 'reverted', | ||
}) | ||
|
||
await expect( | ||
deployAccount(mockClient, { | ||
credentialPublicKey: mockCredentialPublicKey, | ||
contracts: mockContracts, | ||
expectedOrigin: 'https://example.com', | ||
salt: mockSalt, | ||
}) | ||
).rejects.toThrow('Account deployment transaction reverted') | ||
}) | ||
|
||
test('handles missing contract address in receipt', async () => { | ||
// Setup mock for missing contract address | ||
vi.mocked(writeContract).mockResolvedValue(mockTransactionHash) | ||
vi.mocked(waitForTransactionReceipt).mockResolvedValue({ | ||
...mockTransactionReceipt, | ||
contractAddress: null, | ||
}) | ||
|
||
await expect( | ||
deployAccount(mockClient, { | ||
credentialPublicKey: mockCredentialPublicKey, | ||
contracts: mockContracts, | ||
expectedOrigin: 'https://example.com', | ||
salt: mockSalt, | ||
}) | ||
).rejects.toThrow('No contract address in transaction receipt') | ||
}) | ||
|
||
test('calls onTransactionSent callback when provided', async () => { | ||
const onTransactionSent = vi.fn() | ||
vi.mocked(writeContract).mockResolvedValue(mockTransactionHash) | ||
vi.mocked(waitForTransactionReceipt).mockResolvedValue(mockTransactionReceipt) | ||
|
||
await deployAccount(mockClient, { | ||
credentialPublicKey: mockCredentialPublicKey, | ||
contracts: mockContracts, | ||
expectedOrigin: 'https://example.com', | ||
salt: mockSalt, | ||
onTransactionSent, | ||
}) | ||
|
||
expect(onTransactionSent).toHaveBeenCalledWith(mockTransactionHash) | ||
}) | ||
|
||
test('uses window.location.origin when expectedOrigin is not provided', async () => { | ||
// Mock window.location | ||
const originalWindow = global.window | ||
global.window = { | ||
...originalWindow, | ||
location: { | ||
...originalWindow?.location, | ||
origin: 'https://example.com', | ||
}, | ||
} as any | ||
|
||
vi.mocked(writeContract).mockResolvedValue(mockTransactionHash) | ||
vi.mocked(waitForTransactionReceipt).mockResolvedValue(mockTransactionReceipt) | ||
|
||
const writeContractSpy = vi.mocked(writeContract) | ||
await deployAccount(mockClient, { | ||
credentialPublicKey: mockCredentialPublicKey, | ||
contracts: mockContracts, | ||
salt: mockSalt, | ||
}) | ||
|
||
// Simpler assertion that just checks the key parts | ||
const lastCall = writeContractSpy.mock.lastCall | ||
expect(lastCall?.[0]).toBe(mockClient) | ||
expect(lastCall?.[1]).toMatchObject({ | ||
address: mockContracts.accountFactory, | ||
functionName: 'deployProxySsoAccount', | ||
}) | ||
|
||
// Restore window | ||
global.window = originalWindow | ||
}) | ||
|
||
test('handles paymaster configuration', async () => { | ||
vi.mocked(writeContract).mockResolvedValue(mockTransactionHash) | ||
vi.mocked(waitForTransactionReceipt).mockResolvedValue(mockTransactionReceipt) | ||
|
||
const paymasterAddress = '0x5234567890123456789012345678901234567890' as Address | ||
const paymasterInput = '0x1234' as const | ||
|
||
await deployAccount(mockClient, { | ||
credentialPublicKey: mockCredentialPublicKey, | ||
contracts: mockContracts, | ||
expectedOrigin: 'https://example.com', | ||
paymasterAddress, | ||
paymasterInput, | ||
}) | ||
|
||
expect(writeContract).toHaveBeenCalledWith( | ||
mockClient, | ||
expect.objectContaining({ | ||
paymaster: paymasterAddress, | ||
paymasterInput, | ||
}) | ||
) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { describe, expect, test } from 'vitest'; | ||
import { encodePasskeyModuleParameters, encodeModuleData, encodeSession, encodeSessionTx } from './encoding'; | ||
|
||
describe('encoding utils', () => { | ||
describe('encodePasskeyModuleParameters', () => { | ||
test('correctly encodes passkey parameters', () => { | ||
const passkey = { | ||
passkeyPublicKey: [ | ||
Buffer.from('1234567890123456789012345678901234567890123456789012345678901234', 'hex'), | ||
Buffer.from('abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd', 'hex') | ||
], | ||
expectedOrigin: 'https://example.com' | ||
}; | ||
|
||
const encoded = encodePasskeyModuleParameters(passkey); | ||
|
||
console.log("XDBG - encoding.test.ts - encoded: ", encoded); | ||
|
||
// The encoding should be a hex string | ||
expect(encoded).toMatch(/^0x[0-9a-f]+$/i); | ||
|
||
// Should contain both public key components and the origin | ||
expect(encoded).toContain('1234567890123456789012345678901234567890123456789012345678901234'); | ||
expect(encoded).toContain('abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd'); | ||
expect(encoded).toContain(Buffer.from('https://example.com').toString('hex')); | ||
}); | ||
}); | ||
|
||
describe('encodeModuleData', () => { | ||
test('correctly encodes module data', () => { | ||
const moduleData = { | ||
address: '0x1234567890123456789012345678901234567890' as const, | ||
parameters: '0xabcdef' as const | ||
}; | ||
|
||
const encoded = encodeModuleData(moduleData); | ||
|
||
// The encoding should be a hex string | ||
expect(encoded).toMatch(/^0x[0-9a-f]+$/i); | ||
|
||
// Should contain both the address and parameters | ||
expect(encoded.toLowerCase()).toContain(moduleData.address.slice(2).toLowerCase()); | ||
expect(encoded.toLowerCase()).toContain(moduleData.parameters.slice(2).toLowerCase()); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { describe, expect, test } from "vitest"; | ||
import { | ||
getPublicKeyBytesFromPasskeySignature, | ||
getPasskeySignatureFromPublicKeyBytes, | ||
} from "./passkey"; | ||
|
||
describe("passkey utils", () => { | ||
describe("getPublicKeyBytesFromPasskeySignature", () => { | ||
test("correctly decodes CBOR-encoded COSE key", () => { | ||
// This is a sample CBOR-encoded COSE key with known x,y coordinates | ||
// Format: map with 5 entries: | ||
// 1: 2 (kty: EC2) | ||
// 3: -7 (alg: ES256) | ||
// -1: 1 (crv: P-256) | ||
// -2: x coordinate (32 bytes) | ||
// -3: y coordinate (32 bytes) | ||
const samplePublicKey = new Uint8Array([ | ||
0xa5, // map of 5 pairs | ||
0x01, // key 1 (kty) | ||
0x02, // value 2 (EC2) | ||
0x03, // key 3 (alg) | ||
0x26, // value -7 (ES256) | ||
0x20, // key -1 (crv) | ||
0x01, // value 1 (P-256) | ||
0x21, // key -2 (x coordinate) | ||
0x58, | ||
0x20, // bytes(32) | ||
...new Uint8Array(32).fill(0x01), // x coordinate filled with 0x01 | ||
0x22, // key -3 (y coordinate) | ||
0x58, | ||
0x20, // bytes(32) | ||
...new Uint8Array(32).fill(0x02), // y coordinate filled with 0x02 | ||
]); | ||
|
||
const [x, y] = getPublicKeyBytesFromPasskeySignature(samplePublicKey); | ||
|
||
// Check that x coordinate is all 0x01 | ||
expect(Buffer.from(x).every((byte) => byte === 0x01)).toBe(true); | ||
// Check that y coordinate is all 0x02 | ||
expect(Buffer.from(y).every((byte) => byte === 0x02)).toBe(true); | ||
// Check lengths | ||
expect(x.length).toBe(32); | ||
expect(y.length).toBe(32); | ||
}); | ||
|
||
test("roundtrip conversion works", () => { | ||
// Create sample x,y coordinates as hex strings | ||
const xHex = "0x" + "01".repeat(32); | ||
const yHex = "0x" + "02".repeat(32); | ||
|
||
// Convert to COSE format | ||
const coseKey = getPasskeySignatureFromPublicKeyBytes([xHex, yHex]); | ||
|
||
// Convert back to coordinates | ||
const [x, y] = getPublicKeyBytesFromPasskeySignature(coseKey); | ||
|
||
// Check that we got back our original values | ||
expect(Buffer.from(x).toString("hex")).toBe(xHex.slice(2)); | ||
expect(Buffer.from(y).toString("hex")).toBe(yHex.slice(2)); | ||
}); | ||
|
||
test("throws on invalid CBOR data", () => { | ||
const invalidCBOR = new Uint8Array([0xff, 0xff, 0xff]); // Invalid CBOR bytes | ||
|
||
expect(() => { | ||
getPublicKeyBytesFromPasskeySignature(invalidCBOR); | ||
}).toThrow(); | ||
}); | ||
|
||
test("throws if x or y coordinates are missing", () => { | ||
// CBOR map with only kty, alg, and crv (missing x,y) | ||
const incompleteCOSE = new Uint8Array([ | ||
0xa3, // map of 3 pairs | ||
0x01, // key 1 (kty) | ||
0x02, // value 2 (EC2) | ||
0x03, // key 3 (alg) | ||
0x26, // value -7 (ES256) | ||
0x20, // key -1 (crv) | ||
0x01, // value 1 (P-256) | ||
]); | ||
|
||
expect(() => { | ||
getPublicKeyBytesFromPasskeySignature(incompleteCOSE); | ||
}).toThrow(); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.