import { Test, TestingModule } from '@nestjs/testing'; import { ConfigService } from '@nestjs/config'; import { GetBackupShareHandler } from '../../../src/application/queries/get-backup-share/get-backup-share.handler'; import { GetBackupShareQuery } from '../../../src/application/queries/get-backup-share/get-backup-share.query'; import { BACKUP_SHARE_REPOSITORY, BackupShare, BackupShareStatus } from '../../../src/domain'; import { AesEncryptionService } from '../../../src/infrastructure/crypto/aes-encryption.service'; import { AuditLogRepository } from '../../../src/infrastructure/persistence/repositories/audit-log.repository'; import { ApplicationError } from '../../../src/application/errors/application.error'; describe('GetBackupShareHandler', () => { let handler: GetBackupShareHandler; let mockRepository: any; let mockEncryptionService: any; let mockAuditLogRepository: any; const createMockShare = (status: BackupShareStatus = BackupShareStatus.ACTIVE) => { const share = BackupShare.create({ userId: BigInt(12345), accountSequence: BigInt(1001), publicKey: '02' + 'a'.repeat(64), encryptedShareData: 'double-encrypted-data', encryptionKeyId: 'key-v1', }); share.setShareId(BigInt(1)); if (status === BackupShareStatus.REVOKED) { share.revoke('TEST'); } return share; }; beforeEach(async () => { mockRepository = { findByUserIdAndPublicKey: jest.fn(), save: jest.fn().mockImplementation((share) => Promise.resolve(share)), }; mockEncryptionService = { decrypt: jest.fn().mockResolvedValue('original-encrypted-data'), }; mockAuditLogRepository = { log: jest.fn().mockResolvedValue(undefined), countRetrievesByUserToday: jest.fn().mockResolvedValue(0), }; const module: TestingModule = await Test.createTestingModule({ providers: [ GetBackupShareHandler, { provide: BACKUP_SHARE_REPOSITORY, useValue: mockRepository }, { provide: AesEncryptionService, useValue: mockEncryptionService }, { provide: AuditLogRepository, useValue: mockAuditLogRepository }, { provide: ConfigService, useValue: { get: (key: string) => { if (key === 'MAX_RETRIEVE_PER_DAY') return 3; return undefined; }, }, }, ], }).compile(); handler = module.get(GetBackupShareHandler); }); it('should be defined', () => { expect(handler).toBeDefined(); }); describe('execute', () => { const validQuery = new GetBackupShareQuery( '12345', '02' + 'a'.repeat(64), 'valid-recovery-token', 'identity-service', '127.0.0.1', ); it('should retrieve backup share successfully', async () => { const mockShare = createMockShare(); mockRepository.findByUserIdAndPublicKey.mockResolvedValue(mockShare); const result = await handler.execute(validQuery); expect(result.encryptedShareData).toBe('original-encrypted-data'); expect(result.partyIndex).toBe(2); expect(result.publicKey).toBe(validQuery.publicKey); expect(mockEncryptionService.decrypt).toHaveBeenCalledWith( 'double-encrypted-data', 'key-v1', ); expect(mockAuditLogRepository.log).toHaveBeenCalledWith( expect.objectContaining({ action: 'RETRIEVE', success: true, }), ); }); it('should throw error if share not found', async () => { mockRepository.findByUserIdAndPublicKey.mockResolvedValue(null); await expect(handler.execute(validQuery)).rejects.toThrow(ApplicationError); await expect(handler.execute(validQuery)).rejects.toThrow('Backup share not found'); }); it('should throw error if share is not active', async () => { const revokedShare = createMockShare(BackupShareStatus.REVOKED); mockRepository.findByUserIdAndPublicKey.mockResolvedValue(revokedShare); await expect(handler.execute(validQuery)).rejects.toThrow(ApplicationError); await expect(handler.execute(validQuery)).rejects.toThrow('Backup share is not active'); }); it('should throw error if rate limit exceeded', async () => { mockAuditLogRepository.countRetrievesByUserToday.mockResolvedValue(3); await expect(handler.execute(validQuery)).rejects.toThrow(ApplicationError); await expect(handler.execute(validQuery)).rejects.toThrow('Rate limit exceeded'); }); it('should increment access count', async () => { const mockShare = createMockShare(); const initialAccessCount = mockShare.accessCount; mockRepository.findByUserIdAndPublicKey.mockResolvedValue(mockShare); await handler.execute(validQuery); expect(mockShare.accessCount).toBe(initialAccessCount + 1); expect(mockRepository.save).toHaveBeenCalled(); }); }); });