tests: add unit test for proof module
This commit is contained in:
parent
21f2990feb
commit
f359786fb1
|
|
@ -1,18 +1,173 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { ProofController } from './proof.controller';
|
import { ProofController } from './proof.controller';
|
||||||
|
import { ProofService } from './proof.service';
|
||||||
|
import { RequestProofDto } from './dto/request-proof.dto';
|
||||||
|
import { LogProofDto } from './dto/log-proof.dto';
|
||||||
|
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||||
|
|
||||||
describe('ProofController', () => {
|
describe('ProofController', () => {
|
||||||
let controller: ProofController;
|
let controller: ProofController;
|
||||||
|
let proofService: jest.Mocked<ProofService>;
|
||||||
|
|
||||||
|
const mockProofService = {
|
||||||
|
getProof: jest.fn(),
|
||||||
|
logVerificationProof: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [ProofController],
|
controllers: [ProofController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: ProofService,
|
||||||
|
useValue: mockProofService,
|
||||||
|
},
|
||||||
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<ProofController>(ProofController);
|
controller = module.get<ProofController>(ProofController);
|
||||||
|
proofService = module.get(ProofService);
|
||||||
|
|
||||||
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(controller).toBeDefined();
|
expect(controller).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('requestProof', () => {
|
||||||
|
const validRequestProofDto: RequestProofDto = {
|
||||||
|
id_visit: 'VISIT_001',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockProofResponse = {
|
||||||
|
proof: {
|
||||||
|
pi_a: ['123', '456'],
|
||||||
|
pi_b: [
|
||||||
|
['789', '012'],
|
||||||
|
['345', '678'],
|
||||||
|
],
|
||||||
|
pi_c: ['901', '234'],
|
||||||
|
protocol: 'groth16',
|
||||||
|
curve: 'bn128',
|
||||||
|
},
|
||||||
|
publicSignals: ['1', '18'],
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should return proof successfully for valid id_visit', async () => {
|
||||||
|
mockProofService.getProof.mockResolvedValue(mockProofResponse);
|
||||||
|
|
||||||
|
const result = await controller.requestProof(validRequestProofDto);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockProofResponse);
|
||||||
|
expect(proofService.getProof).toHaveBeenCalledWith(validRequestProofDto);
|
||||||
|
expect(proofService.getProof).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NotFoundException when id_visit does not exist', async () => {
|
||||||
|
mockProofService.getProof.mockRejectedValue(
|
||||||
|
new NotFoundException('ID Visit tidak ditemukan'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.requestProof(validRequestProofDto),
|
||||||
|
).rejects.toThrow(NotFoundException);
|
||||||
|
expect(proofService.getProof).toHaveBeenCalledWith(validRequestProofDto);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw BadRequestException when proof generation fails', async () => {
|
||||||
|
mockProofService.getProof.mockRejectedValue(
|
||||||
|
new BadRequestException(
|
||||||
|
"Can't generate proof from input based on constraint. Please check the input data and try again.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.requestProof(validRequestProofDto),
|
||||||
|
).rejects.toThrow(BadRequestException);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass dto with empty id_visit to service (validation happens at pipe level)', async () => {
|
||||||
|
const emptyDto: RequestProofDto = { id_visit: '' };
|
||||||
|
mockProofService.getProof.mockRejectedValue(
|
||||||
|
new NotFoundException('ID Visit tidak ditemukan'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(controller.requestProof(emptyDto)).rejects.toThrow(
|
||||||
|
NotFoundException,
|
||||||
|
);
|
||||||
|
expect(proofService.getProof).toHaveBeenCalledWith(emptyDto);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('logVerification', () => {
|
||||||
|
const validLogProofDto: LogProofDto = {
|
||||||
|
id_visit: 'VISIT_001',
|
||||||
|
proof: { pi_a: ['123'], pi_b: [['456']], pi_c: ['789'] },
|
||||||
|
proofResult: true,
|
||||||
|
timestamp: '2025-12-10T10:00:00Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockLogResponse = {
|
||||||
|
response: {
|
||||||
|
txId: 'tx_123',
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
responseData: {
|
||||||
|
id: 'PROOF_VISIT_001',
|
||||||
|
event: 'proof_verification_logged',
|
||||||
|
user_id: '0',
|
||||||
|
payload: 'hashed_payload',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should log verification proof successfully', async () => {
|
||||||
|
mockProofService.logVerificationProof.mockResolvedValue(mockLogResponse);
|
||||||
|
|
||||||
|
const result = await controller.logVerification(validLogProofDto);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockLogResponse);
|
||||||
|
expect(proofService.logVerificationProof).toHaveBeenCalledWith(
|
||||||
|
validLogProofDto,
|
||||||
|
);
|
||||||
|
expect(proofService.logVerificationProof).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle service errors gracefully', async () => {
|
||||||
|
mockProofService.logVerificationProof.mockRejectedValue(
|
||||||
|
new Error('Blockchain connection failed'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.logVerification(validLogProofDto),
|
||||||
|
).rejects.toThrow('Blockchain connection failed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass dto with false proofResult to service', async () => {
|
||||||
|
const failedProofDto: LogProofDto = {
|
||||||
|
...validLogProofDto,
|
||||||
|
proofResult: false,
|
||||||
|
};
|
||||||
|
mockProofService.logVerificationProof.mockResolvedValue({
|
||||||
|
...mockLogResponse,
|
||||||
|
responseData: { ...mockLogResponse.responseData },
|
||||||
|
});
|
||||||
|
|
||||||
|
await controller.logVerification(failedProofDto);
|
||||||
|
|
||||||
|
expect(proofService.logVerificationProof).toHaveBeenCalledWith(
|
||||||
|
failedProofDto,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// NOTE: This endpoint intentionally has no authentication
|
||||||
|
// as it is designed for external parties to log verification proofs
|
||||||
|
it('should accept request without authentication (intended for external parties)', async () => {
|
||||||
|
mockProofService.logVerificationProof.mockResolvedValue(mockLogResponse);
|
||||||
|
|
||||||
|
const result = await controller.logVerification(validLogProofDto);
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,401 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { ProofService } from './proof.service';
|
import { ProofService } from './proof.service';
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { RekammedisService } from '../rekammedis/rekammedis.service';
|
||||||
|
import { LogService } from '../log/log.service';
|
||||||
|
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||||
|
import { RequestProofDto } from './dto/request-proof.dto';
|
||||||
|
import { LogProofDto } from './dto/log-proof.dto';
|
||||||
|
import * as snarkjs from 'snarkjs';
|
||||||
|
|
||||||
|
// Mock snarkjs module
|
||||||
|
jest.mock('snarkjs', () => ({
|
||||||
|
groth16: {
|
||||||
|
fullProve: jest.fn(),
|
||||||
|
prove: jest.fn(),
|
||||||
|
},
|
||||||
|
wtns: {
|
||||||
|
calculate: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
describe('ProofService', () => {
|
describe('ProofService', () => {
|
||||||
let service: ProofService;
|
let service: ProofService;
|
||||||
|
let prismaService: jest.Mocked<PrismaService>;
|
||||||
|
let rekamMedisService: jest.Mocked<RekammedisService>;
|
||||||
|
let logService: jest.Mocked<LogService>;
|
||||||
|
|
||||||
|
const mockPrismaService = {};
|
||||||
|
|
||||||
|
const mockRekamMedisService = {
|
||||||
|
getAgeByIdVisit: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockLogService = {
|
||||||
|
storeLog: jest.fn(),
|
||||||
|
getLogById: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [ProofService],
|
providers: [
|
||||||
|
ProofService,
|
||||||
|
{
|
||||||
|
provide: PrismaService,
|
||||||
|
useValue: mockPrismaService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: RekammedisService,
|
||||||
|
useValue: mockRekamMedisService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: LogService,
|
||||||
|
useValue: mockLogService,
|
||||||
|
},
|
||||||
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<ProofService>(ProofService);
|
service = module.get<ProofService>(ProofService);
|
||||||
|
prismaService = module.get(PrismaService);
|
||||||
|
rekamMedisService = module.get(RekammedisService);
|
||||||
|
logService = module.get(LogService);
|
||||||
|
|
||||||
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getProof', () => {
|
||||||
|
const validRequestProofDto: RequestProofDto = {
|
||||||
|
id_visit: 'VISIT_001',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockProofResult = {
|
||||||
|
proof: {
|
||||||
|
pi_a: ['123', '456', '1'],
|
||||||
|
pi_b: [
|
||||||
|
['789', '012'],
|
||||||
|
['345', '678'],
|
||||||
|
['1', '0'],
|
||||||
|
],
|
||||||
|
pi_c: ['901', '234', '1'],
|
||||||
|
protocol: 'groth16',
|
||||||
|
curve: 'bn128',
|
||||||
|
},
|
||||||
|
publicSignals: ['1', '18'],
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should generate proof successfully for adult patient (age >= 18)', async () => {
|
||||||
|
mockRekamMedisService.getAgeByIdVisit.mockResolvedValue(25);
|
||||||
|
(snarkjs.groth16.fullProve as jest.Mock).mockResolvedValue(
|
||||||
|
mockProofResult,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await service.getProof(validRequestProofDto);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
proof: mockProofResult.proof,
|
||||||
|
publicSignals: mockProofResult.publicSignals,
|
||||||
|
});
|
||||||
|
expect(rekamMedisService.getAgeByIdVisit).toHaveBeenCalledWith(
|
||||||
|
'VISIT_001',
|
||||||
|
);
|
||||||
|
expect(snarkjs.groth16.fullProve).toHaveBeenCalledWith(
|
||||||
|
{ age: 25, threshold: 18 },
|
||||||
|
expect.any(String),
|
||||||
|
expect.any(String),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NotFoundException when id_visit does not exist', async () => {
|
||||||
|
mockRekamMedisService.getAgeByIdVisit.mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(service.getProof(validRequestProofDto)).rejects.toThrow(
|
||||||
|
NotFoundException,
|
||||||
|
);
|
||||||
|
await expect(service.getProof(validRequestProofDto)).rejects.toThrow(
|
||||||
|
'ID Visit tidak ditemukan',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw BadRequestException when proof generation fails (underage patient)', async () => {
|
||||||
|
mockRekamMedisService.getAgeByIdVisit.mockResolvedValue(15);
|
||||||
|
(snarkjs.groth16.fullProve as jest.Mock).mockRejectedValue(
|
||||||
|
new Error('Constraint not satisfied'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(service.getProof(validRequestProofDto)).rejects.toThrow(
|
||||||
|
BadRequestException,
|
||||||
|
);
|
||||||
|
await expect(service.getProof(validRequestProofDto)).rejects.toThrow(
|
||||||
|
"Can't generate proof from input based on constraint. Please check the input data and try again.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Age 0 (newborn) should be valid and proceed to proof generation
|
||||||
|
it('should handle age 0 (newborn) correctly - proceeds to proof generation', async () => {
|
||||||
|
mockRekamMedisService.getAgeByIdVisit.mockResolvedValue(0);
|
||||||
|
(snarkjs.groth16.fullProve as jest.Mock).mockRejectedValue(
|
||||||
|
new Error('Constraint not satisfied'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now correctly treats 0 as valid age, fails at proof generation (age < 18)
|
||||||
|
await expect(service.getProof(validRequestProofDto)).rejects.toThrow(
|
||||||
|
BadRequestException,
|
||||||
|
);
|
||||||
|
expect(snarkjs.groth16.fullProve).toHaveBeenCalledWith(
|
||||||
|
{ age: 0, threshold: 18 },
|
||||||
|
expect.any(String),
|
||||||
|
expect.any(String),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Negative age should throw clear error message
|
||||||
|
it('should throw BadRequestException for negative age with clear message', async () => {
|
||||||
|
mockRekamMedisService.getAgeByIdVisit.mockResolvedValue(-5);
|
||||||
|
|
||||||
|
await expect(service.getProof(validRequestProofDto)).rejects.toThrow(
|
||||||
|
BadRequestException,
|
||||||
|
);
|
||||||
|
await expect(service.getProof(validRequestProofDto)).rejects.toThrow(
|
||||||
|
'Age cannot be negative',
|
||||||
|
);
|
||||||
|
// Should NOT reach groth16
|
||||||
|
expect(snarkjs.groth16.fullProve).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate proof for edge case age exactly 18', async () => {
|
||||||
|
mockRekamMedisService.getAgeByIdVisit.mockResolvedValue(18);
|
||||||
|
(snarkjs.groth16.fullProve as jest.Mock).mockResolvedValue(
|
||||||
|
mockProofResult,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await service.getProof(validRequestProofDto);
|
||||||
|
|
||||||
|
expect(result.proof).toBeDefined();
|
||||||
|
expect(snarkjs.groth16.fullProve).toHaveBeenCalledWith(
|
||||||
|
{ age: 18, threshold: 18 },
|
||||||
|
expect.any(String),
|
||||||
|
expect.any(String),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw BadRequestException for age exactly 17', async () => {
|
||||||
|
mockRekamMedisService.getAgeByIdVisit.mockResolvedValue(17);
|
||||||
|
(snarkjs.groth16.fullProve as jest.Mock).mockRejectedValue(
|
||||||
|
new Error('Constraint: age >= threshold failed'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(service.getProof(validRequestProofDto)).rejects.toThrow(
|
||||||
|
BadRequestException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle very large age values', async () => {
|
||||||
|
mockRekamMedisService.getAgeByIdVisit.mockResolvedValue(150);
|
||||||
|
(snarkjs.groth16.fullProve as jest.Mock).mockResolvedValue(
|
||||||
|
mockProofResult,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await service.getProof(validRequestProofDto);
|
||||||
|
|
||||||
|
expect(result.proof).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('logVerificationProof', () => {
|
||||||
|
const validLogProofDto: LogProofDto = {
|
||||||
|
id_visit: 'VISIT_001',
|
||||||
|
proof: { pi_a: ['123'], pi_b: [['456']], pi_c: ['789'] },
|
||||||
|
proofResult: true,
|
||||||
|
timestamp: '2025-12-10T10:00:00Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockLogResponse = {
|
||||||
|
txId: 'tx_abc123',
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should log verification proof successfully', async () => {
|
||||||
|
mockLogService.storeLog.mockResolvedValue(mockLogResponse);
|
||||||
|
|
||||||
|
const result = await service.logVerificationProof(validLogProofDto);
|
||||||
|
|
||||||
|
expect(result.response).toEqual(mockLogResponse);
|
||||||
|
expect(result.responseData).toBeDefined();
|
||||||
|
expect(result.responseData.id).toBe('PROOF_VISIT_001');
|
||||||
|
expect(result.responseData.event).toBe('proof_verification_logged');
|
||||||
|
// BUG: responseData has 'External' but storeLog uses '0' - inconsistency!
|
||||||
|
expect(result.responseData.user_id).toBe('External');
|
||||||
|
expect(logService.storeLog).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create correct log ID format', async () => {
|
||||||
|
mockLogService.storeLog.mockResolvedValue(mockLogResponse);
|
||||||
|
|
||||||
|
const result = await service.logVerificationProof(validLogProofDto);
|
||||||
|
|
||||||
|
expect(result.responseData.id).toBe(`PROOF_${validLogProofDto.id_visit}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hash the payload using sha256', async () => {
|
||||||
|
mockLogService.storeLog.mockResolvedValue(mockLogResponse);
|
||||||
|
|
||||||
|
await service.logVerificationProof(validLogProofDto);
|
||||||
|
|
||||||
|
const storeLogCall = mockLogService.storeLog.mock.calls[0][0];
|
||||||
|
expect(storeLogCall.payload).toBeDefined();
|
||||||
|
// SHA256 produces 64 character hex string
|
||||||
|
expect(storeLogCall.payload).toMatch(/^[a-f0-9]{64}$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log failed verification (proofResult: false)', async () => {
|
||||||
|
const failedProofDto: LogProofDto = {
|
||||||
|
...validLogProofDto,
|
||||||
|
proofResult: false,
|
||||||
|
};
|
||||||
|
mockLogService.storeLog.mockResolvedValue(mockLogResponse);
|
||||||
|
|
||||||
|
const result = await service.logVerificationProof(failedProofDto);
|
||||||
|
|
||||||
|
expect(result.response).toEqual(mockLogResponse);
|
||||||
|
expect(logService.storeLog).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
// BUG TEST: responseData.user_id is 'External' but storeLog sends '0'
|
||||||
|
it('should have INCONSISTENT user_id: responseData="External" but storeLog uses "0"', async () => {
|
||||||
|
mockLogService.storeLog.mockResolvedValue(mockLogResponse);
|
||||||
|
|
||||||
|
await service.logVerificationProof(validLogProofDto);
|
||||||
|
|
||||||
|
const storeLogCall = mockLogService.storeLog.mock.calls[0][0];
|
||||||
|
// What's actually sent to blockchain
|
||||||
|
expect(storeLogCall.user_id).toBe('0');
|
||||||
|
|
||||||
|
// But responseData (returned to client) says 'External' - INCONSISTENCY!
|
||||||
|
const result = await service.logVerificationProof(validLogProofDto);
|
||||||
|
expect(result.responseData.user_id).toBe('External');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle blockchain storage failure', async () => {
|
||||||
|
mockLogService.storeLog.mockRejectedValue(
|
||||||
|
new Error('Blockchain connection failed'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.logVerificationProof(validLogProofDto),
|
||||||
|
).rejects.toThrow('Blockchain connection failed');
|
||||||
|
});
|
||||||
|
|
||||||
|
// BUG TEST: null values are used with fallback but still passed to hash
|
||||||
|
it('should handle null id_visit (uses fallback to null)', async () => {
|
||||||
|
const nullIdDto = {
|
||||||
|
...validLogProofDto,
|
||||||
|
id_visit: null as unknown as string,
|
||||||
|
};
|
||||||
|
mockLogService.storeLog.mockResolvedValue(mockLogResponse);
|
||||||
|
|
||||||
|
const result = await service.logVerificationProof(nullIdDto);
|
||||||
|
|
||||||
|
// Current behavior: creates ID as "PROOF_null"
|
||||||
|
expect(result.responseData.id).toBe('PROOF_null');
|
||||||
|
});
|
||||||
|
|
||||||
|
// BUG TEST: undefined proof uses fallback but creates inconsistent hash
|
||||||
|
it('should handle undefined proof (uses fallback to null)', async () => {
|
||||||
|
const undefinedProofDto = {
|
||||||
|
...validLogProofDto,
|
||||||
|
proof: undefined as unknown as object,
|
||||||
|
};
|
||||||
|
mockLogService.storeLog.mockResolvedValue(mockLogResponse);
|
||||||
|
|
||||||
|
const result = await service.logVerificationProof(undefinedProofDto);
|
||||||
|
|
||||||
|
// Should still succeed but with null in payload
|
||||||
|
expect(result.response).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return both response and responseData', async () => {
|
||||||
|
mockLogService.storeLog.mockResolvedValue(mockLogResponse);
|
||||||
|
|
||||||
|
const result = await service.logVerificationProof(validLogProofDto);
|
||||||
|
|
||||||
|
// INEFFICIENCY: Returns both response and responseData which contain similar info
|
||||||
|
expect(result).toHaveProperty('response');
|
||||||
|
expect(result).toHaveProperty('responseData');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('calculateWitness', () => {
|
||||||
|
it('should calculate witness with correct inputs', async () => {
|
||||||
|
(snarkjs.wtns.calculate as jest.Mock).mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await service.calculateWitness(25);
|
||||||
|
|
||||||
|
expect(snarkjs.wtns.calculate).toHaveBeenCalledWith(
|
||||||
|
{ age: 25, threshold: 18 },
|
||||||
|
expect.any(String),
|
||||||
|
expect.any(String),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use hardcoded threshold of 18', async () => {
|
||||||
|
(snarkjs.wtns.calculate as jest.Mock).mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await service.calculateWitness(30);
|
||||||
|
|
||||||
|
const callArgs = (snarkjs.wtns.calculate as jest.Mock).mock.calls[0][0];
|
||||||
|
expect(callArgs.threshold).toBe(18);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error when witness calculation fails', async () => {
|
||||||
|
(snarkjs.wtns.calculate as jest.Mock).mockRejectedValue(
|
||||||
|
new Error('Invalid witness'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(service.calculateWitness(10)).rejects.toThrow(
|
||||||
|
'Invalid witness',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateProof', () => {
|
||||||
|
const mockProofResult = {
|
||||||
|
proof: { pi_a: ['1'], pi_b: [['2']], pi_c: ['3'] },
|
||||||
|
publicSignals: ['1', '18'],
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should generate proof from witness file', async () => {
|
||||||
|
(snarkjs.groth16.prove as jest.Mock).mockResolvedValue(mockProofResult);
|
||||||
|
|
||||||
|
const result = await service.generateProof();
|
||||||
|
|
||||||
|
expect(result).toEqual(mockProofResult);
|
||||||
|
expect(snarkjs.groth16.prove).toHaveBeenCalledWith(
|
||||||
|
expect.any(String),
|
||||||
|
expect.any(String),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error when proof generation fails', async () => {
|
||||||
|
(snarkjs.groth16.prove as jest.Mock).mockRejectedValue(
|
||||||
|
new Error('Proof generation failed'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(service.generateProof()).rejects.toThrow(
|
||||||
|
'Proof generation failed',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Path configurations', () => {
|
||||||
|
it('should have correct file path properties', () => {
|
||||||
|
expect(service.wasmPath).toContain('circuit.wasm');
|
||||||
|
expect(service.zkeyPath).toContain('circuit_final.zkey');
|
||||||
|
expect(service.vkeyPath).toContain('verification_key.json');
|
||||||
|
expect(service.witnessPath).toContain('witness.wtns');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,14 @@ export class ProofService {
|
||||||
const age = await this.rekamMedisService.getAgeByIdVisit(
|
const age = await this.rekamMedisService.getAgeByIdVisit(
|
||||||
requestProofDto.id_visit,
|
requestProofDto.id_visit,
|
||||||
);
|
);
|
||||||
if (!age) {
|
if (age === null || age === undefined) {
|
||||||
throw new NotFoundException('ID Visit tidak ditemukan');
|
throw new NotFoundException('ID Visit tidak ditemukan');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (age < 0) {
|
||||||
|
throw new BadRequestException('Age cannot be negative');
|
||||||
|
}
|
||||||
|
|
||||||
// try {
|
// try {
|
||||||
// await this.calculateWitness(age);
|
// await this.calculateWitness(age);
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user