rwadurian/backend/services/backup-service/test/unit/api/backup-share.controller.spe...

213 lines
7.1 KiB
TypeScript

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<BackupShareApplicationService>;
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>(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',
}),
);
});
});
});