/** * PartyShare Entity Unit Tests */ import { PartyShare } from '../../../src/domain/entities/party-share.entity'; import { ShareId, PartyId, SessionId, ShareData, PublicKey, Threshold, } from '../../../src/domain/value-objects'; import { PartyShareType, PartyShareStatus } from '../../../src/domain/enums'; import { ShareCreatedEvent, ShareRotatedEvent, ShareRevokedEvent } from '../../../src/domain/events'; describe('PartyShare Entity', () => { // Test fixtures const createTestShareData = () => ShareData.create( Buffer.from('encrypted-share-data-here'), Buffer.from('123456789012'), // 12 bytes IV Buffer.from('1234567890123456'), // 16 bytes authTag ); const createTestPublicKey = () => PublicKey.fromHex( '03' + '0'.repeat(64), // Compressed public key format ); const createTestThreshold = () => Threshold.create(3, 2); const createTestPartyShare = () => PartyShare.create({ partyId: PartyId.create('user123-server'), sessionId: SessionId.create('550e8400-e29b-41d4-a716-446655440000'), shareType: PartyShareType.WALLET, shareData: createTestShareData(), publicKey: createTestPublicKey(), threshold: createTestThreshold(), }); describe('create', () => { it('should create a valid party share', () => { const share = createTestPartyShare(); expect(share.id).toBeDefined(); expect(share.id.value).toMatch(/^share_\d+_[a-z0-9]+$/); expect(share.partyId.value).toBe('user123-server'); expect(share.sessionId.value).toBe('550e8400-e29b-41d4-a716-446655440000'); expect(share.shareType).toBe(PartyShareType.WALLET); expect(share.status).toBe(PartyShareStatus.ACTIVE); expect(share.threshold.toString()).toBe('2-of-3'); }); it('should emit ShareCreatedEvent', () => { const share = createTestPartyShare(); const events = share.domainEvents; expect(events).toHaveLength(1); expect(events[0]).toBeInstanceOf(ShareCreatedEvent); const event = events[0] as ShareCreatedEvent; expect(event.shareId).toBe(share.id.value); expect(event.partyId).toBe('user123-server'); expect(event.shareType).toBe(PartyShareType.WALLET); }); }); describe('reconstruct', () => { it('should reconstruct share without emitting events', () => { const share = PartyShare.reconstruct({ id: ShareId.create('share_1699887766123_abc123xyz'), partyId: PartyId.create('user123-server'), sessionId: SessionId.create('550e8400-e29b-41d4-a716-446655440000'), shareType: PartyShareType.WALLET, shareData: createTestShareData(), publicKey: createTestPublicKey(), threshold: createTestThreshold(), status: PartyShareStatus.ACTIVE, createdAt: new Date('2024-01-01'), updatedAt: new Date('2024-01-01'), }); expect(share.id.value).toBe('share_1699887766123_abc123xyz'); expect(share.domainEvents).toHaveLength(0); }); }); describe('markAsUsed', () => { it('should update lastUsedAt timestamp', () => { const share = createTestPartyShare(); share.clearDomainEvents(); expect(share.lastUsedAt).toBeUndefined(); share.markAsUsed(); expect(share.lastUsedAt).toBeDefined(); expect(share.lastUsedAt).toBeInstanceOf(Date); }); it('should throw error when share is not active', () => { const share = createTestPartyShare(); share.clearDomainEvents(); share.revoke('test reason'); expect(() => share.markAsUsed()).toThrow(); }); }); describe('rotate', () => { it('should create new share with rotated status for old share', () => { const share = createTestPartyShare(); share.clearDomainEvents(); const newShareData = ShareData.create( Buffer.from('new-encrypted-share-data'), Buffer.from('123456789012'), Buffer.from('1234567890123456'), ); const newSessionId = SessionId.create('660e8400-e29b-41d4-a716-446655440001'); const newShare = share.rotate(newShareData, newSessionId); // Old share should be marked as rotated expect(share.status).toBe(PartyShareStatus.ROTATED); // New share should be active expect(newShare.status).toBe(PartyShareStatus.ACTIVE); expect(newShare.id.value).not.toBe(share.id.value); expect(newShare.publicKey.equals(share.publicKey)).toBe(true); // Event should be emitted on new share const events = newShare.domainEvents; expect(events.some(e => e instanceof ShareRotatedEvent)).toBe(true); }); it('should throw error when share is not active', () => { const share = createTestPartyShare(); share.revoke('test reason'); const newShareData = createTestShareData(); const newSessionId = SessionId.create('660e8400-e29b-41d4-a716-446655440001'); expect(() => share.rotate(newShareData, newSessionId)).toThrow(); }); }); describe('revoke', () => { it('should mark share as revoked', () => { const share = createTestPartyShare(); share.clearDomainEvents(); share.revoke('Security concern'); expect(share.status).toBe(PartyShareStatus.REVOKED); const events = share.domainEvents; expect(events).toHaveLength(1); expect(events[0]).toBeInstanceOf(ShareRevokedEvent); }); it('should throw error when share is already revoked', () => { const share = createTestPartyShare(); share.revoke('First revocation'); expect(() => share.revoke('Second revocation')).toThrow('Share is already revoked'); }); }); describe('validateThreshold', () => { it('should validate threshold correctly', () => { const share = createTestPartyShare(); // 2-of-3 threshold expect(share.validateThreshold(2)).toBe(true); expect(share.validateThreshold(3)).toBe(true); expect(share.validateThreshold(1)).toBe(false); // Less than t expect(share.validateThreshold(4)).toBe(false); // More than n }); }); describe('isActive', () => { it('should return true for active share', () => { const share = createTestPartyShare(); expect(share.isActive()).toBe(true); }); it('should return false for revoked share', () => { const share = createTestPartyShare(); share.revoke('test'); expect(share.isActive()).toBe(false); }); }); describe('belongsToParty', () => { it('should return true for matching party', () => { const share = createTestPartyShare(); expect(share.belongsToParty(PartyId.create('user123-server'))).toBe(true); }); it('should return false for non-matching party', () => { const share = createTestPartyShare(); expect(share.belongsToParty(PartyId.create('other-server'))).toBe(false); }); }); describe('clearDomainEvents', () => { it('should clear all domain events', () => { const share = createTestPartyShare(); expect(share.domainEvents.length).toBeGreaterThan(0); share.clearDomainEvents(); expect(share.domainEvents).toHaveLength(0); }); }); });