Files
bridge/pkg/threshold/src/client.test.ts
T
zeekay e661decabb chain routes: /v1/bc -> /v1/chain
luxd serves chain routes under /v1/chain, and the node, cli and sdk releases
already ship it. The deployed gateways still answer the old prefix, so remote
reads break until they are redeployed; the estate moves together rather than
sitting half converted.

Lines that record an observation - a probe, a status code, a past release -
keep the prefix they were written against, as do URLs on hosts that are not
ours. Avalanche endpoints move to /ext/bc, which is the path that host serves.
2026-09-01 08:20:22 -07:00

186 lines
5.0 KiB
TypeScript

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { ThresholdClient } from './client'
import type { KeygenResponse, SignatureResponse } from './types'
describe('ThresholdClient', () => {
let client: ThresholdClient
beforeEach(() => {
client = new ThresholdClient({
endpoint: 'http://localhost:9650/v1/chain/T',
chainId: 'test-chain',
})
})
describe('constructor', () => {
it('should create client with options', () => {
expect(client).toBeInstanceOf(ThresholdClient)
})
})
describe('keygen', () => {
it('should call keygen with chain', async () => {
const mockResponse: KeygenResponse = {
sessionId: 'sess-123',
keyId: 'key-1',
protocol: 'cggmp21',
status: 'pending',
threshold: 2,
totalParties: 3,
startedAt: Date.now(),
}
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ jsonrpc: '2.0', id: 1, result: mockResponse }),
})
const result = await client.keygen({ keyId: 'key-1', chain: 'ethereum' })
expect(result.keyId).toBe('key-1')
expect(result.protocol).toBe('cggmp21')
})
it('should call keygen with protocol', async () => {
const mockResponse: KeygenResponse = {
sessionId: 'sess-456',
keyId: 'frost-key',
protocol: 'frost',
status: 'pending',
threshold: 2,
totalParties: 3,
startedAt: Date.now(),
}
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ jsonrpc: '2.0', id: 1, result: mockResponse }),
})
const result = await client.keygen({ keyId: 'frost-key', protocol: 'frost' })
expect(result.protocol).toBe('frost')
})
})
describe('keygenForChain', () => {
it('should be a convenience wrapper', async () => {
const mockResponse: KeygenResponse = {
sessionId: 'sess-789',
keyId: 'sol-key',
protocol: 'frost',
status: 'pending',
threshold: 2,
totalParties: 3,
startedAt: Date.now(),
}
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ jsonrpc: '2.0', id: 1, result: mockResponse }),
})
const result = await client.keygenForChain('sol-key', 'solana')
expect(result.keyId).toBe('sol-key')
expect(result.protocol).toBe('frost')
})
})
describe('sign', () => {
it('should request signature', async () => {
const mockResponse = {
sessionId: 'sign-123',
keyId: 'key-1',
status: 'pending',
createdAt: Date.now(),
expiresAt: Date.now() + 60000,
}
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ jsonrpc: '2.0', id: 1, result: mockResponse }),
})
const result = await client.sign({
keyId: 'key-1',
messageHash: '0x1234567890abcdef',
})
expect(result.sessionId).toBe('sign-123')
expect(result.status).toBe('pending')
})
})
describe('signAndWait', () => {
it('should sign and poll for completion', async () => {
const signResponse = {
sessionId: 'sign-456',
keyId: 'key-1',
status: 'pending',
createdAt: Date.now(),
expiresAt: Date.now() + 60000,
}
const sigResponse: SignatureResponse = {
sessionId: 'sign-456',
status: 'completed',
signature: '0xabcdef',
r: '0x123',
s: '0x456',
v: 27,
completedAt: Date.now(),
}
let callCount = 0
global.fetch = vi.fn().mockImplementation(() => {
callCount++
if (callCount === 1) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ jsonrpc: '2.0', id: 1, result: signResponse }),
})
}
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ jsonrpc: '2.0', id: 2, result: sigResponse }),
})
})
const result = await client.signAndWait({
keyId: 'key-1',
messageHash: '0xabcdef',
})
expect(result.status).toBe('completed')
expect(result.signature).toBe('0xabcdef')
})
})
describe('error handling', () => {
it('should throw on RPC error', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () =>
Promise.resolve({
jsonrpc: '2.0',
id: 1,
error: { code: -32600, message: 'Invalid request' },
}),
})
await expect(client.keygen({ keyId: 'key-1' })).rejects.toThrow('RPC error')
})
it('should throw on HTTP error', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 500,
statusText: 'Internal Server Error',
})
await expect(client.keygen({ keyId: 'key-1' })).rejects.toThrow('HTTP 500')
})
})
})