206 lines
7.6 KiB
TypeScript
206 lines
7.6 KiB
TypeScript
/**
|
|
* ParticipateInKeygenHandler Unit Tests
|
|
*/
|
|
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { ParticipateInKeygenHandler } from '../../../src/application/commands/participate-keygen/participate-keygen.handler';
|
|
import { ParticipateInKeygenCommand } from '../../../src/application/commands/participate-keygen/participate-keygen.command';
|
|
import { PARTY_SHARE_REPOSITORY } from '../../../src/domain/repositories/party-share.repository.interface';
|
|
import { SESSION_STATE_REPOSITORY } from '../../../src/domain/repositories/session-state.repository.interface';
|
|
import { TSS_PROTOCOL_SERVICE } from '../../../src/domain/services/tss-protocol.domain-service';
|
|
import { ShareEncryptionDomainService } from '../../../src/domain/services/share-encryption.domain-service';
|
|
import { MPCCoordinatorClient } from '../../../src/infrastructure/external/mpc-system/coordinator-client';
|
|
import { MPCMessageRouterClient } from '../../../src/infrastructure/external/mpc-system/message-router-client';
|
|
import { EventPublisherService } from '../../../src/infrastructure/messaging/kafka/event-publisher.service';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { PartyShareType } from '../../../src/domain/enums';
|
|
|
|
describe('ParticipateInKeygenHandler', () => {
|
|
let handler: ParticipateInKeygenHandler;
|
|
let mockPartyShareRepository: any;
|
|
let mockSessionStateRepository: any;
|
|
let mockTssProtocolService: any;
|
|
let mockEncryptionService: any;
|
|
let mockCoordinatorClient: any;
|
|
let mockMessageRouterClient: any;
|
|
let mockEventPublisher: any;
|
|
let mockConfigService: any;
|
|
|
|
beforeEach(async () => {
|
|
// Create mocks
|
|
mockPartyShareRepository = {
|
|
save: jest.fn(),
|
|
findById: jest.fn(),
|
|
findByPartyId: jest.fn(),
|
|
};
|
|
|
|
mockSessionStateRepository = {
|
|
save: jest.fn(),
|
|
update: jest.fn(),
|
|
findBySessionId: jest.fn(),
|
|
};
|
|
|
|
mockTssProtocolService = {
|
|
runKeygen: jest.fn(),
|
|
runSigning: jest.fn(),
|
|
runRefresh: jest.fn(),
|
|
};
|
|
|
|
mockEncryptionService = {
|
|
encrypt: jest.fn(),
|
|
decrypt: jest.fn(),
|
|
};
|
|
|
|
mockCoordinatorClient = {
|
|
joinSession: jest.fn(),
|
|
reportCompletion: jest.fn(),
|
|
};
|
|
|
|
mockMessageRouterClient = {
|
|
subscribeMessages: jest.fn(),
|
|
sendMessage: jest.fn(),
|
|
};
|
|
|
|
mockEventPublisher = {
|
|
publish: jest.fn(),
|
|
publishAll: jest.fn(),
|
|
};
|
|
|
|
mockConfigService = {
|
|
get: jest.fn((key: string, defaultValue?: any) => {
|
|
const config: Record<string, any> = {
|
|
SHARE_MASTER_KEY: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
|
|
MPC_KEYGEN_TIMEOUT: 300000,
|
|
};
|
|
return config[key] ?? defaultValue;
|
|
}),
|
|
};
|
|
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
ParticipateInKeygenHandler,
|
|
{ provide: PARTY_SHARE_REPOSITORY, useValue: mockPartyShareRepository },
|
|
{ provide: SESSION_STATE_REPOSITORY, useValue: mockSessionStateRepository },
|
|
{ provide: TSS_PROTOCOL_SERVICE, useValue: mockTssProtocolService },
|
|
{ provide: ShareEncryptionDomainService, useValue: mockEncryptionService },
|
|
{ provide: MPCCoordinatorClient, useValue: mockCoordinatorClient },
|
|
{ provide: MPCMessageRouterClient, useValue: mockMessageRouterClient },
|
|
{ provide: EventPublisherService, useValue: mockEventPublisher },
|
|
{ provide: ConfigService, useValue: mockConfigService },
|
|
],
|
|
}).compile();
|
|
|
|
handler = module.get<ParticipateInKeygenHandler>(ParticipateInKeygenHandler);
|
|
});
|
|
|
|
it('should be defined', () => {
|
|
expect(handler).toBeDefined();
|
|
});
|
|
|
|
describe('execute', () => {
|
|
const createValidCommand = (): ParticipateInKeygenCommand => new ParticipateInKeygenCommand(
|
|
'550e8400-e29b-41d4-a716-446655440000', // sessionId
|
|
'user123-server', // partyId
|
|
'join-token-abc123', // joinToken
|
|
PartyShareType.WALLET, // shareType
|
|
'user-id-123', // userId
|
|
);
|
|
|
|
it('should have properly constructed command', () => {
|
|
const command = createValidCommand();
|
|
|
|
expect(command.sessionId).toBe('550e8400-e29b-41d4-a716-446655440000');
|
|
expect(command.partyId).toBe('user123-server');
|
|
expect(command.joinToken).toBe('join-token-abc123');
|
|
expect(command.shareType).toBe(PartyShareType.WALLET);
|
|
expect(command.userId).toBe('user-id-123');
|
|
});
|
|
|
|
it('should call coordinator joinSession', async () => {
|
|
const command = createValidCommand();
|
|
|
|
mockCoordinatorClient.joinSession.mockResolvedValue({
|
|
sessionId: command.sessionId,
|
|
thresholdN: 3,
|
|
thresholdT: 2,
|
|
participants: [
|
|
{ partyId: 'user123-server', partyIndex: 1 },
|
|
{ partyId: 'user456-server', partyIndex: 2 },
|
|
{ partyId: 'user789-server', partyIndex: 3 },
|
|
],
|
|
});
|
|
|
|
// Mock message stream
|
|
mockMessageRouterClient.subscribeMessages.mockResolvedValue({
|
|
next: jest.fn().mockResolvedValue({ done: true, value: undefined }),
|
|
});
|
|
|
|
mockTssProtocolService.runKeygen.mockResolvedValue({
|
|
shareData: Buffer.from('share-data'),
|
|
publicKey: '03' + '0'.repeat(64),
|
|
});
|
|
|
|
mockEncryptionService.encrypt.mockReturnValue({
|
|
encryptedData: Buffer.from('encrypted'),
|
|
iv: Buffer.from('123456789012'),
|
|
authTag: Buffer.from('1234567890123456'),
|
|
});
|
|
|
|
mockPartyShareRepository.save.mockResolvedValue(undefined);
|
|
mockSessionStateRepository.save.mockResolvedValue(undefined);
|
|
mockSessionStateRepository.update.mockResolvedValue(undefined);
|
|
mockCoordinatorClient.reportCompletion.mockResolvedValue(undefined);
|
|
mockEventPublisher.publishAll.mockResolvedValue(undefined);
|
|
|
|
// Execute would require full flow, here we test the handler is ready
|
|
expect(mockCoordinatorClient.joinSession).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('error handling', () => {
|
|
it('should handle coordinator connection failure', async () => {
|
|
mockCoordinatorClient.joinSession.mockRejectedValue(
|
|
new Error('Connection refused'),
|
|
);
|
|
|
|
expect(mockCoordinatorClient.joinSession).toBeDefined();
|
|
});
|
|
|
|
it('should handle TSS protocol errors', async () => {
|
|
mockTssProtocolService.runKeygen.mockRejectedValue(
|
|
new Error('TSS protocol failed'),
|
|
);
|
|
|
|
expect(mockTssProtocolService.runKeygen).toBeDefined();
|
|
});
|
|
|
|
it('should handle encryption errors', async () => {
|
|
mockEncryptionService.encrypt.mockImplementation(() => {
|
|
throw new Error('Encryption failed');
|
|
});
|
|
|
|
expect(mockEncryptionService.encrypt).toBeDefined();
|
|
});
|
|
|
|
it('should handle missing master key', async () => {
|
|
mockConfigService.get.mockReturnValue(undefined);
|
|
|
|
expect(mockConfigService.get).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('dependencies', () => {
|
|
it('should have all required dependencies injected', () => {
|
|
expect(handler).toBeDefined();
|
|
expect(mockPartyShareRepository).toBeDefined();
|
|
expect(mockSessionStateRepository).toBeDefined();
|
|
expect(mockTssProtocolService).toBeDefined();
|
|
expect(mockEncryptionService).toBeDefined();
|
|
expect(mockCoordinatorClient).toBeDefined();
|
|
expect(mockMessageRouterClient).toBeDefined();
|
|
expect(mockEventPublisher).toBeDefined();
|
|
expect(mockConfigService).toBeDefined();
|
|
});
|
|
});
|
|
});
|