213 lines
6.9 KiB
TypeScript
213 lines
6.9 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',
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
});
|