rwadurian/backend/services/mpc-service/tests/unit/domain/value-objects.spec.ts

245 lines
8.0 KiB
TypeScript

/**
* Value Objects Unit Tests
*/
import {
SessionId,
PartyId,
ShareId,
Threshold,
ShareData,
PublicKey,
Signature,
MessageHash,
} from '../../../src/domain/value-objects';
describe('Value Objects', () => {
describe('SessionId', () => {
it('should create valid SessionId', () => {
const sessionId = SessionId.create('550e8400-e29b-41d4-a716-446655440000');
expect(sessionId.value).toBe('550e8400-e29b-41d4-a716-446655440000');
});
it('should generate unique SessionId', () => {
const sessionId1 = SessionId.generate();
const sessionId2 = SessionId.generate();
expect(sessionId1.value).not.toBe(sessionId2.value);
});
it('should throw error for invalid format', () => {
expect(() => SessionId.create('invalid')).toThrow();
expect(() => SessionId.create('')).toThrow();
});
it('should compare equality correctly', () => {
const sessionId1 = SessionId.create('550e8400-e29b-41d4-a716-446655440000');
const sessionId2 = SessionId.create('550e8400-e29b-41d4-a716-446655440000');
const sessionId3 = SessionId.create('660e8400-e29b-41d4-a716-446655440001');
expect(sessionId1.equals(sessionId2)).toBe(true);
expect(sessionId1.equals(sessionId3)).toBe(false);
});
});
describe('PartyId', () => {
it('should create valid PartyId', () => {
const partyId = PartyId.create('user123-server');
expect(partyId.value).toBe('user123-server');
});
it('should create from components', () => {
const partyId = PartyId.fromComponents('user123', 'server');
expect(partyId.value).toBe('user123-server');
});
it('should extract identifier and type', () => {
const partyId = PartyId.create('user123-server');
expect(partyId.getIdentifier()).toBe('user123');
expect(partyId.getType()).toBe('server');
});
it('should throw error for invalid format', () => {
expect(() => PartyId.create('')).toThrow();
expect(() => PartyId.create('nohyphen')).toThrow();
});
});
describe('ShareId', () => {
it('should create valid ShareId', () => {
const shareId = ShareId.create('share_1699887766123_abc123xyz');
expect(shareId.value).toBe('share_1699887766123_abc123xyz');
});
it('should generate unique ShareId', () => {
const shareId1 = ShareId.generate();
const shareId2 = ShareId.generate();
expect(shareId1.value).not.toBe(shareId2.value);
expect(shareId1.value).toMatch(/^share_\d+_[a-z0-9]+$/);
});
it('should throw error for invalid format', () => {
expect(() => ShareId.create('invalid')).toThrow();
});
});
describe('Threshold', () => {
it('should create valid threshold', () => {
const threshold = Threshold.create(3, 2);
expect(threshold.n).toBe(3);
expect(threshold.t).toBe(2);
expect(threshold.toString()).toBe('2-of-3');
});
it('should provide common configurations', () => {
const twoOfThree = Threshold.twoOfThree();
expect(twoOfThree.toString()).toBe('2-of-3');
const threeOfFive = Threshold.threeOfFive();
expect(threeOfFive.toString()).toBe('3-of-5');
});
it('should validate participants', () => {
const threshold = Threshold.create(3, 2);
expect(threshold.validateParticipants(2)).toBe(true);
expect(threshold.validateParticipants(3)).toBe(true);
expect(threshold.validateParticipants(1)).toBe(false);
expect(threshold.validateParticipants(4)).toBe(false);
});
it('should throw error for invalid values', () => {
expect(() => Threshold.create(0, 0)).toThrow();
expect(() => Threshold.create(2, 3)).toThrow(); // t > n
expect(() => Threshold.create(3, 1)).toThrow(); // t < 2
});
});
describe('ShareData', () => {
it('should create valid ShareData', () => {
const shareData = ShareData.create(
Buffer.from('encrypted-data'),
Buffer.from('123456789012'), // 12 bytes
Buffer.from('1234567890123456'), // 16 bytes
);
expect(shareData.encryptedData).toEqual(Buffer.from('encrypted-data'));
});
it('should serialize to JSON and back', () => {
const original = ShareData.create(
Buffer.from('encrypted-data'),
Buffer.from('123456789012'),
Buffer.from('1234567890123456'),
);
const json = original.toJSON();
const restored = ShareData.fromJSON(json);
expect(restored.encryptedData).toEqual(original.encryptedData);
expect(restored.iv).toEqual(original.iv);
expect(restored.authTag).toEqual(original.authTag);
});
it('should throw error for invalid IV length', () => {
expect(() => ShareData.create(
Buffer.from('data'),
Buffer.from('short'), // Less than 12 bytes
Buffer.from('1234567890123456'),
)).toThrow();
});
it('should throw error for invalid authTag length', () => {
expect(() => ShareData.create(
Buffer.from('data'),
Buffer.from('123456789012'),
Buffer.from('short'), // Less than 16 bytes
)).toThrow();
});
});
describe('PublicKey', () => {
it('should create from hex', () => {
const hex = '03' + '0'.repeat(64);
const publicKey = PublicKey.fromHex(hex);
expect(publicKey.toHex()).toBe(hex);
expect(publicKey.isCompressed()).toBe(true);
});
it('should create from base64', () => {
const buffer = Buffer.alloc(33);
buffer[0] = 0x03;
const publicKey = PublicKey.fromBase64(buffer.toString('base64'));
expect(publicKey.bytes).toHaveLength(33);
});
it('should detect compressed vs uncompressed', () => {
const compressed = PublicKey.create(Buffer.alloc(33, 0x03));
const uncompressed = PublicKey.create(Buffer.alloc(65, 0x04));
expect(compressed.isCompressed()).toBe(true);
expect(uncompressed.isCompressed()).toBe(false);
});
it('should throw error for invalid length', () => {
expect(() => PublicKey.create(Buffer.alloc(32))).toThrow();
expect(() => PublicKey.create(Buffer.alloc(64))).toThrow();
});
});
describe('Signature', () => {
it('should create from components', () => {
const r = Buffer.alloc(32, 0x01);
const s = Buffer.alloc(32, 0x02);
const signature = Signature.create(r, s, 0);
expect(signature.r).toEqual(r);
expect(signature.s).toEqual(s);
expect(signature.v).toBe(0);
});
it('should parse from hex', () => {
const hex = '0'.repeat(128); // 64 bytes = r + s
const signature = Signature.fromHex(hex);
expect(signature.r).toHaveLength(32);
expect(signature.s).toHaveLength(32);
});
it('should convert to hex', () => {
const r = Buffer.alloc(32, 0x01);
const s = Buffer.alloc(32, 0x02);
const signature = Signature.create(r, s);
const hex = signature.toHex();
expect(hex).toHaveLength(128);
});
it('should throw error for invalid component length', () => {
expect(() => Signature.create(Buffer.alloc(31), Buffer.alloc(32))).toThrow();
expect(() => Signature.create(Buffer.alloc(32), Buffer.alloc(31))).toThrow();
});
});
describe('MessageHash', () => {
it('should create from hex', () => {
const hex = '0x' + '0'.repeat(64);
const hash = MessageHash.fromHex(hex);
expect(hash.bytes).toHaveLength(32);
});
it('should create from hex without prefix', () => {
const hex = '0'.repeat(64);
const hash = MessageHash.fromHex(hex);
expect(hash.bytes).toHaveLength(32);
});
it('should convert to hex with prefix', () => {
const hex = '0'.repeat(64);
const hash = MessageHash.fromHex(hex);
expect(hash.toHex()).toBe('0x' + hex);
});
it('should throw error for invalid length', () => {
expect(() => MessageHash.fromHex('0x1234')).toThrow();
});
});
});