/** * PartyShareMapper Unit Tests */ import { PartyShareMapper, PartySharePersistence } from '../../../src/infrastructure/persistence/mappers/party-share.mapper'; import { PartyShare } from '../../../src/domain/entities/party-share.entity'; import { ShareId, PartyId, SessionId, Threshold, ShareData, PublicKey, } from '../../../src/domain/value-objects'; import { PartyShareType, PartyShareStatus } from '../../../src/domain/enums'; describe('PartyShareMapper', () => { let mapper: PartyShareMapper; beforeEach(() => { mapper = new PartyShareMapper(); }); const createTestShareData = () => ShareData.create( Buffer.from('encrypted-share-data-here-for-testing'), Buffer.from('123456789012'), // 12 bytes IV Buffer.from('1234567890123456'), // 16 bytes authTag ); const createTestPublicKey = () => PublicKey.fromHex( '03' + '0'.repeat(64), // Compressed public key format (33 bytes) ); const createDomainEntity = (): PartyShare => { return PartyShare.create({ partyId: PartyId.create('user123-server'), sessionId: SessionId.generate(), shareType: PartyShareType.WALLET, shareData: createTestShareData(), publicKey: createTestPublicKey(), threshold: Threshold.create(3, 2), }); }; const createPersistenceRecord = (): PartySharePersistence => ({ id: 'share_1699887766123_abc123xyz', partyId: 'user123-server', sessionId: '550e8400-e29b-41d4-a716-446655440000', shareType: 'wallet', shareData: JSON.stringify({ data: Buffer.from('encrypted-share-data-here-for-testing').toString('base64'), iv: Buffer.from('123456789012').toString('base64'), authTag: Buffer.from('1234567890123456').toString('base64'), }), publicKey: '03' + '0'.repeat(64), thresholdN: 3, thresholdT: 2, status: 'active', createdAt: new Date('2024-01-01T00:00:00Z'), updatedAt: new Date('2024-01-01T00:00:00Z'), lastUsedAt: null, }); describe('toPersistence', () => { it('should map domain entity to persistence model', () => { const entity = createDomainEntity(); const result = mapper.toPersistence(entity); expect(result).toBeDefined(); expect(result.id).toBe(entity.id.value); expect(result.partyId).toBe(entity.partyId.value); expect(result.sessionId).toBe(entity.sessionId.value); expect(result.shareType).toBe(entity.shareType); expect(result.thresholdT).toBe(entity.threshold.t); expect(result.thresholdN).toBe(entity.threshold.n); expect(result.status).toBe(entity.status); }); it('should convert public key to hex', () => { const entity = createDomainEntity(); const result = mapper.toPersistence(entity); expect(result.publicKey).toBeDefined(); expect(typeof result.publicKey).toBe('string'); expect(result.publicKey).toMatch(/^[0-9a-f]+$/i); }); it('should serialize share data as JSON string', () => { const entity = createDomainEntity(); const result = mapper.toPersistence(entity); expect(typeof result.shareData).toBe('string'); const parsed = JSON.parse(result.shareData); expect(parsed).toHaveProperty('data'); expect(parsed).toHaveProperty('iv'); expect(parsed).toHaveProperty('authTag'); }); it('should handle revoked status', () => { const entity = createDomainEntity(); entity.revoke('Test revocation reason'); const result = mapper.toPersistence(entity); expect(result.status).toBe(PartyShareStatus.REVOKED); }); }); describe('toDomain', () => { it('should map persistence model to domain entity', () => { const record = createPersistenceRecord(); const result = mapper.toDomain(record); expect(result).toBeInstanceOf(PartyShare); expect(result.id.value).toBe(record.id); expect(result.partyId.value).toBe(record.partyId); expect(result.sessionId.value).toBe(record.sessionId); expect(result.threshold.t).toBe(record.thresholdT); expect(result.threshold.n).toBe(record.thresholdN); }); it('should reconstruct value objects', () => { const record = createPersistenceRecord(); const result = mapper.toDomain(record); expect(result.id).toBeDefined(); expect(result.partyId).toBeDefined(); expect(result.threshold).toBeDefined(); expect(result.publicKey).toBeDefined(); expect(result.shareData).toBeDefined(); }); it('should preserve timestamps', () => { const record = createPersistenceRecord(); const result = mapper.toDomain(record); expect(result.createdAt).toEqual(record.createdAt); expect(result.updatedAt).toEqual(record.updatedAt); }); it('should handle null lastUsedAt', () => { const record = createPersistenceRecord(); record.lastUsedAt = null; const result = mapper.toDomain(record); expect(result.lastUsedAt).toBeUndefined(); }); }); describe('bidirectional mapping', () => { it('should preserve data through round-trip conversion', () => { const original = createDomainEntity(); const persistence = mapper.toPersistence(original); const restored = mapper.toDomain(persistence); expect(restored.id.value).toBe(original.id.value); expect(restored.partyId.value).toBe(original.partyId.value); expect(restored.sessionId.value).toBe(original.sessionId.value); expect(restored.threshold.t).toBe(original.threshold.t); expect(restored.threshold.n).toBe(original.threshold.n); expect(restored.publicKey.toHex()).toBe(original.publicKey.toHex()); }); }); describe('toDomainList', () => { it('should map array of persistence records to domain entities', () => { const records = [ createPersistenceRecord(), { ...createPersistenceRecord(), id: 'share_1699887766124_def456uvw', sessionId: '660e8400-e29b-41d4-a716-446655440001', }, ]; const results = mapper.toDomainList(records); expect(results).toHaveLength(2); results.forEach((result) => { expect(result).toBeInstanceOf(PartyShare); }); }); it('should return empty array for empty input', () => { const results = mapper.toDomainList([]); expect(results).toHaveLength(0); }); }); });