import { Test, TestingModule } from '@nestjs/testing'; import { BackupShareController } from '../../../src/api/controllers/backup-share.controller'; import { BackupShareApplicationService } from '../../../src/application/services/backup-share-application.service'; import { ServiceAuthGuard } from '../../../src/shared/guards/service-auth.guard'; import { ConfigService } from '@nestjs/config'; import { createStoreSharePayload, createRetrieveSharePayload, createRevokeSharePayload, generatePublicKey, } from '../../utils/test-utils'; describe('BackupShareController', () => { let controller: BackupShareController; let mockApplicationService: jest.Mocked; const mockRequest = { sourceService: 'identity-service', sourceIp: '127.0.0.1', }; beforeEach(async () => { mockApplicationService = { storeBackupShare: jest.fn(), getBackupShare: jest.fn(), revokeShare: jest.fn(), } as any; const module: TestingModule = await Test.createTestingModule({ controllers: [BackupShareController], providers: [ { provide: BackupShareApplicationService, useValue: mockApplicationService }, { provide: ConfigService, useValue: { get: jest.fn().mockReturnValue('test-secret'), }, }, ], }) .overrideGuard(ServiceAuthGuard) .useValue({ canActivate: () => true }) .compile(); controller = module.get(BackupShareController); }); describe('storeShare', () => { it('should store a backup share successfully', async () => { const dto = createStoreSharePayload(); mockApplicationService.storeBackupShare.mockResolvedValue({ shareId: '1' }); const result = await controller.storeShare(dto, mockRequest); expect(result.success).toBe(true); expect(result.shareId).toBe('1'); expect(result.message).toBe('Backup share stored successfully'); expect(mockApplicationService.storeBackupShare).toHaveBeenCalledWith( expect.objectContaining({ userId: dto.userId, accountSequence: dto.accountSequence, publicKey: dto.publicKey, encryptedShareData: dto.encryptedShareData, sourceService: 'identity-service', sourceIp: '127.0.0.1', }), ); }); it('should pass optional threshold and totalParties', async () => { const dto = createStoreSharePayload({ threshold: 3, totalParties: 5, }); mockApplicationService.storeBackupShare.mockResolvedValue({ shareId: '2' }); await controller.storeShare(dto, mockRequest); expect(mockApplicationService.storeBackupShare).toHaveBeenCalledWith( expect.objectContaining({ threshold: 3, totalParties: 5, }), ); }); it('should handle different user IDs', async () => { const dto = createStoreSharePayload({ userId: '999888777' }); mockApplicationService.storeBackupShare.mockResolvedValue({ shareId: '3' }); await controller.storeShare(dto, mockRequest); expect(mockApplicationService.storeBackupShare).toHaveBeenCalledWith( expect.objectContaining({ userId: '999888777', }), ); }); }); describe('retrieveShare', () => { it('should retrieve a backup share successfully', async () => { const dto = createRetrieveSharePayload(); mockApplicationService.getBackupShare.mockResolvedValue({ encryptedShareData: 'decrypted-data', partyIndex: 2, publicKey: dto.publicKey, }); const result = await controller.retrieveShare(dto, mockRequest); expect(result.success).toBe(true); expect(result.encryptedShareData).toBe('decrypted-data'); expect(result.partyIndex).toBe(2); expect(result.publicKey).toBe(dto.publicKey); }); it('should pass deviceId when provided', async () => { const dto = createRetrieveSharePayload({ deviceId: 'device-123' }); mockApplicationService.getBackupShare.mockResolvedValue({ encryptedShareData: 'data', partyIndex: 2, publicKey: dto.publicKey, }); await controller.retrieveShare(dto, mockRequest); expect(mockApplicationService.getBackupShare).toHaveBeenCalledWith( expect.objectContaining({ deviceId: 'device-123', }), ); }); it('should include recovery token in query', async () => { const dto = createRetrieveSharePayload({ recoveryToken: 'special-token-123' }); mockApplicationService.getBackupShare.mockResolvedValue({ encryptedShareData: 'data', partyIndex: 2, publicKey: dto.publicKey, }); await controller.retrieveShare(dto, mockRequest); expect(mockApplicationService.getBackupShare).toHaveBeenCalledWith( expect.objectContaining({ recoveryToken: 'special-token-123', }), ); }); }); describe('revokeShare', () => { it('should revoke a backup share successfully', async () => { const dto = createRevokeSharePayload(); mockApplicationService.revokeShare.mockResolvedValue(undefined); const result = await controller.revokeShare(dto, mockRequest); expect(result.success).toBe(true); expect(result.message).toBe('Backup share revoked successfully'); }); it('should pass correct reason to service', async () => { const dto = createRevokeSharePayload({ reason: 'SECURITY_BREACH' }); mockApplicationService.revokeShare.mockResolvedValue(undefined); await controller.revokeShare(dto, mockRequest); expect(mockApplicationService.revokeShare).toHaveBeenCalledWith( expect.objectContaining({ reason: 'SECURITY_BREACH', }), ); }); it('should handle different revoke reasons', async () => { const reasons = ['ROTATION', 'ACCOUNT_CLOSED', 'SECURITY_BREACH', 'USER_REQUEST']; for (const reason of reasons) { const dto = createRevokeSharePayload({ reason, publicKey: generatePublicKey(reason[0].toLowerCase()), }); mockApplicationService.revokeShare.mockResolvedValue(undefined); await controller.revokeShare(dto, mockRequest); expect(mockApplicationService.revokeShare).toHaveBeenCalledWith( expect.objectContaining({ reason }), ); } }); }); describe('request context', () => { it('should extract source service from request', async () => { const dto = createStoreSharePayload(); const customRequest = { sourceService: 'recovery-service', sourceIp: '10.0.0.1' }; mockApplicationService.storeBackupShare.mockResolvedValue({ shareId: '1' }); await controller.storeShare(dto, customRequest); expect(mockApplicationService.storeBackupShare).toHaveBeenCalledWith( expect.objectContaining({ sourceService: 'recovery-service', sourceIp: '10.0.0.1', }), ); }); }); });