tests: Add unit test for obat module. fix: fix multinode implementation on docker-compose-swarm.yaml
This commit is contained in:
parent
e6fcb80d88
commit
21f2990feb
|
|
@ -1,18 +1,295 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ObatController } from './obat.controller';
|
||||
import { ObatService } from './obat.service';
|
||||
import { AuthGuard } from '../auth/guard/auth.guard';
|
||||
import { UpdateObatDto } from './dto/update-obat-dto';
|
||||
import { CreateObatDto } from './dto/create-obat-dto';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import type { ActiveUserPayload } from '../auth/decorator/current-user.decorator';
|
||||
|
||||
describe('ObatController', () => {
|
||||
let controller: ObatController;
|
||||
let obatService: jest.Mocked<ObatService>;
|
||||
|
||||
const mockUser: ActiveUserPayload = {
|
||||
sub: 1,
|
||||
username: 'testuser',
|
||||
role: 'admin' as any,
|
||||
csrf: 'test-csrf-token',
|
||||
};
|
||||
|
||||
const mockObat = {
|
||||
id: 1,
|
||||
id_visit: 'VISIT001',
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
deleted_status: null,
|
||||
};
|
||||
|
||||
const mockObatService = {
|
||||
getAllObat: jest.fn(),
|
||||
getObatById: jest.fn(),
|
||||
createObat: jest.fn(),
|
||||
updateObat: jest.fn(),
|
||||
getLogObatById: jest.fn(),
|
||||
deleteObat: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [ObatController],
|
||||
}).compile();
|
||||
providers: [
|
||||
{
|
||||
provide: ObatService,
|
||||
useValue: mockObatService,
|
||||
},
|
||||
],
|
||||
})
|
||||
.overrideGuard(AuthGuard)
|
||||
.useValue({ canActivate: () => true })
|
||||
.compile();
|
||||
|
||||
controller = module.get<ObatController>(ObatController);
|
||||
obatService = module.get(ObatService);
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
|
||||
describe('getAllObat', () => {
|
||||
it('should return all obat with pagination', async () => {
|
||||
const expectedResult = {
|
||||
0: mockObat,
|
||||
totalCount: 1,
|
||||
};
|
||||
mockObatService.getAllObat.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await controller.getAllObat(
|
||||
10,
|
||||
0,
|
||||
1,
|
||||
'id',
|
||||
'Paracetamol',
|
||||
'asc',
|
||||
);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(obatService.getAllObat).toHaveBeenCalledWith({
|
||||
take: 10,
|
||||
skip: 0,
|
||||
page: 1,
|
||||
orderBy: { id: 'asc' },
|
||||
obat: 'Paracetamol',
|
||||
order: 'asc',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle undefined orderBy parameter', async () => {
|
||||
const expectedResult = { 0: mockObat, totalCount: 1 };
|
||||
mockObatService.getAllObat.mockResolvedValue(expectedResult);
|
||||
|
||||
await controller.getAllObat(
|
||||
10,
|
||||
0,
|
||||
1,
|
||||
undefined as unknown as string,
|
||||
undefined as unknown as string,
|
||||
undefined as unknown as 'asc' | 'desc',
|
||||
);
|
||||
|
||||
expect(obatService.getAllObat).toHaveBeenCalledWith({
|
||||
take: 10,
|
||||
skip: 0,
|
||||
page: 1,
|
||||
orderBy: undefined,
|
||||
obat: undefined,
|
||||
order: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass order parameter when orderBy is provided', async () => {
|
||||
mockObatService.getAllObat.mockResolvedValue({ totalCount: 0 });
|
||||
|
||||
await controller.getAllObat(
|
||||
10,
|
||||
0,
|
||||
1,
|
||||
'obat',
|
||||
undefined as unknown as string,
|
||||
'desc',
|
||||
);
|
||||
|
||||
expect(obatService.getAllObat).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
orderBy: { obat: 'desc' },
|
||||
order: 'desc',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getObatById', () => {
|
||||
it('should return obat by id', async () => {
|
||||
mockObatService.getObatById.mockResolvedValue(mockObat);
|
||||
|
||||
const result = await controller.getObatById(1);
|
||||
|
||||
expect(result).toEqual(mockObat);
|
||||
expect(obatService.getObatById).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should return null when obat not found', async () => {
|
||||
mockObatService.getObatById.mockResolvedValue(null);
|
||||
|
||||
const result = await controller.getObatById(999);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createObat', () => {
|
||||
it('should create obat successfully', async () => {
|
||||
const createDto: CreateObatDto = {
|
||||
id_visit: 'VISIT001',
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
const expectedResult = { id: 1, ...createDto, status: 'PENDING' };
|
||||
mockObatService.createObat.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await controller.createObat(createDto, mockUser);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(obatService.createObat).toHaveBeenCalledWith(createDto, mockUser);
|
||||
});
|
||||
|
||||
it('should throw BadRequestException when visit ID not found', async () => {
|
||||
const createDto: CreateObatDto = {
|
||||
id_visit: 'INVALID',
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
mockObatService.createObat.mockRejectedValue(
|
||||
new BadRequestException('Visit ID INVALID not found'),
|
||||
);
|
||||
|
||||
await expect(controller.createObat(createDto, mockUser)).rejects.toThrow(
|
||||
BadRequestException,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateObatById', () => {
|
||||
it('should update obat successfully', async () => {
|
||||
const updateDto: UpdateObatDto = {
|
||||
obat: 'Ibuprofen',
|
||||
jumlah_obat: 20,
|
||||
aturan_pakai: '2x1',
|
||||
};
|
||||
const expectedResult = { id: 1, status: 'PENDING' };
|
||||
mockObatService.updateObat.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await controller.updateObatById(1, updateDto, mockUser);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(obatService.updateObat).toHaveBeenCalledWith(
|
||||
1,
|
||||
updateDto,
|
||||
mockUser,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw BadRequestException when obat not found', async () => {
|
||||
const updateDto: UpdateObatDto = {
|
||||
obat: 'Ibuprofen',
|
||||
jumlah_obat: 20,
|
||||
aturan_pakai: '2x1',
|
||||
};
|
||||
mockObatService.updateObat.mockRejectedValue(
|
||||
new BadRequestException('Medicine with ID 999 not found'),
|
||||
);
|
||||
|
||||
await expect(
|
||||
controller.updateObatById(999, updateDto, mockUser),
|
||||
).rejects.toThrow(BadRequestException);
|
||||
});
|
||||
|
||||
it('should throw BadRequestException when no changes detected', async () => {
|
||||
const updateDto: UpdateObatDto = {
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
mockObatService.updateObat.mockRejectedValue(
|
||||
new BadRequestException('No changes in medicine data detected'),
|
||||
);
|
||||
|
||||
await expect(
|
||||
controller.updateObatById(1, updateDto, mockUser),
|
||||
).rejects.toThrow('No changes in medicine data detected');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getObatLogs', () => {
|
||||
it('should return obat logs', async () => {
|
||||
const expectedLogs = {
|
||||
logs: [
|
||||
{
|
||||
id: 'OBAT_1',
|
||||
event: 'obat_created',
|
||||
status: 'ORIGINAL',
|
||||
},
|
||||
],
|
||||
isTampered: false,
|
||||
currentDataHash: 'abc123',
|
||||
};
|
||||
mockObatService.getLogObatById.mockResolvedValue(expectedLogs);
|
||||
|
||||
const result = await controller.getObatLogs('1');
|
||||
|
||||
expect(result).toEqual(expectedLogs);
|
||||
expect(obatService.getLogObatById).toHaveBeenCalledWith('1');
|
||||
});
|
||||
|
||||
it('should handle tampered data detection', async () => {
|
||||
const expectedLogs = {
|
||||
logs: [],
|
||||
isTampered: true,
|
||||
currentDataHash: 'abc123',
|
||||
};
|
||||
mockObatService.getLogObatById.mockResolvedValue(expectedLogs);
|
||||
|
||||
const result = await controller.getObatLogs('1');
|
||||
|
||||
expect(result.isTampered).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteObatById', () => {
|
||||
it('should delete obat successfully', async () => {
|
||||
const expectedResult = { id: 1, status: 'PENDING', action: 'DELETE' };
|
||||
mockObatService.deleteObat.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await controller.deleteObatById(1, mockUser);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(obatService.deleteObat).toHaveBeenCalledWith(1, mockUser);
|
||||
});
|
||||
|
||||
it('should throw BadRequestException when obat not found', async () => {
|
||||
mockObatService.deleteObat.mockRejectedValue(
|
||||
new BadRequestException('Obat with id 999 not found'),
|
||||
);
|
||||
|
||||
await expect(controller.deleteObatById(999, mockUser)).rejects.toThrow(
|
||||
BadRequestException,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,14 +7,6 @@ import { CreateObatDto } from './dto/create-obat-dto';
|
|||
import { UpdateObatDto } from './dto/update-obat-dto';
|
||||
import { ObatService } from './obat.service';
|
||||
|
||||
type PrismaDelegate<T> = {
|
||||
findMany: jest.Mock;
|
||||
findUnique: jest.Mock;
|
||||
count: jest.Mock;
|
||||
create: jest.Mock;
|
||||
update: jest.Mock;
|
||||
};
|
||||
|
||||
const createPrismaMock = () => ({
|
||||
pemberian_obat: {
|
||||
findMany: jest.fn(),
|
||||
|
|
@ -22,10 +14,14 @@ const createPrismaMock = () => ({
|
|||
count: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
} as PrismaDelegate<any>,
|
||||
},
|
||||
rekam_medis: {
|
||||
findUnique: jest.fn(),
|
||||
},
|
||||
validation_queue: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
$transaction: jest.fn(),
|
||||
});
|
||||
|
||||
const createLogServiceMock = () => ({
|
||||
|
|
@ -60,161 +56,234 @@ describe('ObatService', () => {
|
|||
service = module.get<ObatService>(ObatService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('createHashingPayload', () => {
|
||||
it('should create consistent hash for same data', () => {
|
||||
const data = {
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
const hash1 = service.createHashingPayload(data);
|
||||
const hash2 = service.createHashingPayload(data);
|
||||
|
||||
expect(hash1).toBe(hash2);
|
||||
expect(typeof hash1).toBe('string');
|
||||
expect(hash1.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should create different hash for different data', () => {
|
||||
const data1 = {
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
const data2 = { obat: 'Ibuprofen', jumlah_obat: 10, aturan_pakai: '3x1' };
|
||||
|
||||
expect(service.createHashingPayload(data1)).not.toBe(
|
||||
service.createHashingPayload(data2),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('determineStatus', () => {
|
||||
it('should return ORIGINAL for last item with obat_created event', () => {
|
||||
const rawLog = {
|
||||
value: {
|
||||
event: 'obat_created',
|
||||
payload: 'hash123',
|
||||
timestamp: '2024-01-01',
|
||||
user_id: 1,
|
||||
},
|
||||
txId: 'tx123',
|
||||
};
|
||||
|
||||
const result = service.determineStatus(rawLog, 0, 1);
|
||||
|
||||
expect(result.status).toBe('ORIGINAL');
|
||||
expect(result.txId).toBe('tx123');
|
||||
});
|
||||
|
||||
it('should return UPDATED for non-last items', () => {
|
||||
const rawLog = {
|
||||
value: {
|
||||
event: 'obat_updated',
|
||||
payload: 'hash123',
|
||||
timestamp: '2024-01-01',
|
||||
user_id: 1,
|
||||
},
|
||||
txId: 'tx123',
|
||||
};
|
||||
|
||||
const result = service.determineStatus(rawLog, 0, 2);
|
||||
|
||||
expect(result.status).toBe('UPDATED');
|
||||
});
|
||||
|
||||
it('should return UPDATED for last item with non-created event', () => {
|
||||
const rawLog = {
|
||||
value: {
|
||||
event: 'obat_updated',
|
||||
payload: 'hash123',
|
||||
timestamp: '2024-01-01',
|
||||
user_id: 1,
|
||||
},
|
||||
txId: 'tx123',
|
||||
};
|
||||
|
||||
const result = service.determineStatus(rawLog, 0, 1);
|
||||
|
||||
expect(result.status).toBe('UPDATED');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllObat', () => {
|
||||
it('returns paginated data and total count', async () => {
|
||||
prisma.pemberian_obat.findMany.mockResolvedValueOnce([
|
||||
{ id: 1, obat: 'Paracetamol' },
|
||||
]);
|
||||
prisma.pemberian_obat.count.mockResolvedValueOnce(10);
|
||||
const mockObatList = [
|
||||
{ id: 1, obat: 'Paracetamol', deleted_status: null },
|
||||
{ id: 2, obat: 'Ibuprofen', deleted_status: null },
|
||||
];
|
||||
|
||||
it('should return paginated data with total count', async () => {
|
||||
prisma.pemberian_obat.findMany.mockResolvedValue(mockObatList);
|
||||
prisma.pemberian_obat.count.mockResolvedValue(10);
|
||||
|
||||
const result = await service.getAllObat({
|
||||
take: 10,
|
||||
page: 1,
|
||||
orderBy: { id: 'asc' },
|
||||
order: 'asc',
|
||||
obat: 'Para',
|
||||
});
|
||||
|
||||
expect(prisma.pemberian_obat.findMany).toHaveBeenCalledWith({
|
||||
skip: 0,
|
||||
take: 10,
|
||||
where: {
|
||||
obat: { contains: 'Para' },
|
||||
obat: undefined,
|
||||
OR: [
|
||||
{ deleted_status: null },
|
||||
{ deleted_status: 'DELETE_VALIDATION' },
|
||||
{ deleted_status: { not: 'DELETED' } },
|
||||
],
|
||||
},
|
||||
orderBy: { id: 'asc' },
|
||||
});
|
||||
expect(prisma.pemberian_obat.count).toHaveBeenCalledWith({
|
||||
where: {
|
||||
obat: { contains: 'Para' },
|
||||
},
|
||||
});
|
||||
expect(result).toEqual({
|
||||
0: { id: 1, obat: 'Paracetamol' },
|
||||
totalCount: 10,
|
||||
});
|
||||
expect(result.totalCount).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createObat', () => {
|
||||
const payload: CreateObatDto = {
|
||||
id_visit: 'VISIT-1',
|
||||
obat: 'Amoxicillin',
|
||||
jumlah_obat: 2,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
it('should filter by obat name', async () => {
|
||||
prisma.pemberian_obat.findMany.mockResolvedValue([mockObatList[0]]);
|
||||
prisma.pemberian_obat.count.mockResolvedValue(1);
|
||||
|
||||
it('throws when visit not found', async () => {
|
||||
prisma.rekam_medis.findUnique.mockResolvedValueOnce(null);
|
||||
await service.getAllObat({ obat: 'Para' });
|
||||
|
||||
await expect(service.createObat(payload, mockUser)).rejects.toThrow(
|
||||
BadRequestException,
|
||||
expect(prisma.pemberian_obat.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: expect.objectContaining({
|
||||
obat: { contains: 'Para' },
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(prisma.pemberian_obat.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates obat and stores log', async () => {
|
||||
prisma.rekam_medis.findUnique.mockResolvedValueOnce({
|
||||
id_visit: 'VISIT-1',
|
||||
});
|
||||
prisma.pemberian_obat.create.mockResolvedValueOnce({
|
||||
id: 42,
|
||||
...payload,
|
||||
});
|
||||
logService.storeLog.mockResolvedValueOnce({ txId: 'abc' });
|
||||
it('should handle skip parameter', async () => {
|
||||
prisma.pemberian_obat.findMany.mockResolvedValue([]);
|
||||
prisma.pemberian_obat.count.mockResolvedValue(0);
|
||||
|
||||
const result = await service.createObat(payload, mockUser);
|
||||
await service.getAllObat({ skip: 5 });
|
||||
|
||||
expect(prisma.pemberian_obat.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
id_visit: 'VISIT-1',
|
||||
obat: 'Amoxicillin',
|
||||
jumlah_obat: 2,
|
||||
aturan_pakai: '3x1',
|
||||
},
|
||||
});
|
||||
expect(prisma.pemberian_obat.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
skip: 5,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(logService.storeLog).toHaveBeenCalledWith({
|
||||
id: 'OBAT_42',
|
||||
event: 'obat_created',
|
||||
user_id: mockUser.sub,
|
||||
payload: expect.any(String),
|
||||
});
|
||||
it('should calculate skip from page when skip not provided', async () => {
|
||||
prisma.pemberian_obat.findMany.mockResolvedValue([]);
|
||||
prisma.pemberian_obat.count.mockResolvedValue(0);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 42,
|
||||
id_visit: 'VISIT-1',
|
||||
obat: 'Amoxicillin',
|
||||
jumlah_obat: 2,
|
||||
aturan_pakai: '3x1',
|
||||
txId: 'abc',
|
||||
});
|
||||
await service.getAllObat({ take: 10, page: 3 });
|
||||
|
||||
expect(prisma.pemberian_obat.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
skip: 20,
|
||||
take: 10,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default take of 10 when not provided', async () => {
|
||||
prisma.pemberian_obat.findMany.mockResolvedValue([]);
|
||||
prisma.pemberian_obat.count.mockResolvedValue(0);
|
||||
|
||||
await service.getAllObat({});
|
||||
|
||||
expect(prisma.pemberian_obat.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
take: 10,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle custom orderBy', async () => {
|
||||
prisma.pemberian_obat.findMany.mockResolvedValue([]);
|
||||
prisma.pemberian_obat.count.mockResolvedValue(0);
|
||||
|
||||
await service.getAllObat({ orderBy: { obat: 'desc' }, order: 'desc' });
|
||||
|
||||
expect(prisma.pemberian_obat.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
orderBy: { obat: 'desc' },
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateObatById', () => {
|
||||
const updatePayload: UpdateObatDto = {
|
||||
id_visit: 'VISIT-1',
|
||||
obat: 'Ibuprofen',
|
||||
jumlah_obat: 1,
|
||||
aturan_pakai: '2x1',
|
||||
};
|
||||
describe('getObatById', () => {
|
||||
it('should return obat by id', async () => {
|
||||
const mockObat = { id: 1, obat: 'Paracetamol' };
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(mockObat);
|
||||
|
||||
it('updates obat and stores log', async () => {
|
||||
prisma.pemberian_obat.update.mockResolvedValueOnce({
|
||||
id: 99,
|
||||
...updatePayload,
|
||||
id_visit: 'VISIT-1',
|
||||
const result = await service.getObatById(1);
|
||||
|
||||
expect(result).toEqual(mockObat);
|
||||
expect(prisma.pemberian_obat.findUnique).toHaveBeenCalledWith({
|
||||
where: { id: 1 },
|
||||
});
|
||||
logService.storeLog.mockResolvedValueOnce({ txId: 'updated' });
|
||||
});
|
||||
|
||||
const result = await service.updateObat(99, updatePayload, mockUser);
|
||||
it('should return null when obat not found', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(null);
|
||||
|
||||
expect(prisma.pemberian_obat.update).toHaveBeenCalledWith({
|
||||
where: { id: 99 },
|
||||
data: {
|
||||
obat: 'Ibuprofen',
|
||||
jumlah_obat: 1,
|
||||
aturan_pakai: '2x1',
|
||||
},
|
||||
});
|
||||
const result = await service.getObatById(999);
|
||||
|
||||
expect(logService.storeLog).toHaveBeenCalledWith({
|
||||
id: 'OBAT_99',
|
||||
event: 'obat_updated',
|
||||
user_id: mockUser.sub,
|
||||
payload: expect.any(String),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 99,
|
||||
id_visit: 'VISIT-1',
|
||||
obat: 'Ibuprofen',
|
||||
jumlah_obat: 1,
|
||||
aturan_pakai: '2x1',
|
||||
txId: 'updated',
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLogObatById', () => {
|
||||
it('returns processed logs and tamper status', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValueOnce({
|
||||
id: 5,
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 1,
|
||||
aturan_pakai: '3x1',
|
||||
});
|
||||
const mockObat = {
|
||||
id: 5,
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 1,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
|
||||
it('should return logs with tamper status false when hashes match', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(mockObat);
|
||||
const expectedHash = service.createHashingPayload({
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 1,
|
||||
aturan_pakai: '3x1',
|
||||
obat: mockObat.obat,
|
||||
jumlah_obat: mockObat.jumlah_obat,
|
||||
aturan_pakai: mockObat.aturan_pakai,
|
||||
});
|
||||
|
||||
logService.getLogById.mockResolvedValueOnce([
|
||||
logService.getLogById.mockResolvedValue([
|
||||
{
|
||||
value: {
|
||||
event: 'obat_created',
|
||||
|
|
@ -229,19 +298,435 @@ describe('ObatService', () => {
|
|||
const result = await service.getLogObatById('5');
|
||||
|
||||
expect(logService.getLogById).toHaveBeenCalledWith('OBAT_5');
|
||||
expect(result).toEqual({
|
||||
logs: [
|
||||
{
|
||||
expect(result.isTampered).toBe(false);
|
||||
expect(result.currentDataHash).toBe(expectedHash);
|
||||
});
|
||||
|
||||
it('should detect tampered data when hashes do not match', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(mockObat);
|
||||
|
||||
logService.getLogById.mockResolvedValue([
|
||||
{
|
||||
value: {
|
||||
event: 'obat_created',
|
||||
payload: expectedHash,
|
||||
payload: 'different_hash',
|
||||
timestamp: '2024-01-01T00:00:00Z',
|
||||
user_id: 1,
|
||||
txId: 'abc',
|
||||
status: 'ORIGINAL',
|
||||
},
|
||||
],
|
||||
isTampered: false,
|
||||
currentDataHash: expectedHash,
|
||||
txId: 'abc',
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await service.getLogObatById('5');
|
||||
|
||||
expect(result.isTampered).toBe(true);
|
||||
});
|
||||
|
||||
it('should throw error when obat not found', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(null);
|
||||
logService.getLogById.mockResolvedValue([{ value: { payload: 'hash' } }]);
|
||||
|
||||
await expect(service.getLogObatById('999')).rejects.toThrow(
|
||||
'Obat with id 999 not found',
|
||||
);
|
||||
});
|
||||
|
||||
// BUG TEST: This test exposes the bug where empty logs array causes crash
|
||||
it('should handle empty logs array gracefully', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(mockObat);
|
||||
logService.getLogById.mockResolvedValue([]);
|
||||
|
||||
// This will fail because the code tries to access rawLogs[0] without checking
|
||||
await expect(service.getLogObatById('5')).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isIdVisitExists', () => {
|
||||
it('should return true when visit exists', async () => {
|
||||
prisma.rekam_medis.findUnique.mockResolvedValue({ id_visit: 'VISIT001' });
|
||||
|
||||
const result = await service.isIdVisitExists('VISIT001');
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when visit does not exist', async () => {
|
||||
prisma.rekam_medis.findUnique.mockResolvedValue(null);
|
||||
|
||||
const result = await service.isIdVisitExists('INVALID');
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createObat', () => {
|
||||
const createDto: CreateObatDto = {
|
||||
id_visit: 'VISIT001',
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
|
||||
it('should create validation queue entry for new obat', async () => {
|
||||
prisma.rekam_medis.findUnique.mockResolvedValue({ id_visit: 'VISIT001' });
|
||||
prisma.validation_queue.create.mockResolvedValue({
|
||||
id: 1,
|
||||
table_name: 'pemberian_obat',
|
||||
action: 'CREATE',
|
||||
status: 'PENDING',
|
||||
});
|
||||
|
||||
const result = await service.createObat(createDto, mockUser);
|
||||
|
||||
expect(prisma.validation_queue.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
table_name: 'pemberian_obat',
|
||||
action: 'CREATE',
|
||||
dataPayload: createDto,
|
||||
status: 'PENDING',
|
||||
user_id_request: mockUser.sub,
|
||||
},
|
||||
});
|
||||
expect(result.status).toBe('PENDING');
|
||||
});
|
||||
|
||||
it('should throw BadRequestException when visit ID not found', async () => {
|
||||
prisma.rekam_medis.findUnique.mockResolvedValue(null);
|
||||
|
||||
await expect(service.createObat(createDto, mockUser)).rejects.toThrow(
|
||||
BadRequestException,
|
||||
);
|
||||
await expect(service.createObat(createDto, mockUser)).rejects.toThrow(
|
||||
'Visit ID VISIT001 not found',
|
||||
);
|
||||
});
|
||||
|
||||
it('should propagate database errors', async () => {
|
||||
prisma.rekam_medis.findUnique.mockResolvedValue({ id_visit: 'VISIT001' });
|
||||
prisma.validation_queue.create.mockRejectedValue(
|
||||
new Error('Database error'),
|
||||
);
|
||||
|
||||
await expect(service.createObat(createDto, mockUser)).rejects.toThrow(
|
||||
'Database error',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createObatToDBAndBlockchain', () => {
|
||||
const createDto: CreateObatDto = {
|
||||
id_visit: 'VISIT001',
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
|
||||
it('should create obat and store log in transaction', async () => {
|
||||
prisma.rekam_medis.findUnique.mockResolvedValue({ id_visit: 'VISIT001' });
|
||||
|
||||
const mockTx = {
|
||||
pemberian_obat: {
|
||||
create: jest.fn().mockResolvedValue({ id: 1, ...createDto }),
|
||||
},
|
||||
};
|
||||
prisma.$transaction.mockImplementation(async (callback) =>
|
||||
callback(mockTx),
|
||||
);
|
||||
logService.storeLog.mockResolvedValue({ txId: 'blockchain_tx_123' });
|
||||
|
||||
const result = await service.createObatToDBAndBlockchain(createDto, 1);
|
||||
|
||||
expect(mockTx.pemberian_obat.create).toHaveBeenCalledWith({
|
||||
data: createDto,
|
||||
});
|
||||
expect(logService.storeLog).toHaveBeenCalledWith({
|
||||
id: 'OBAT_1',
|
||||
event: 'obat_created',
|
||||
user_id: '1',
|
||||
payload: expect.any(String),
|
||||
});
|
||||
expect(result.txId).toBe('blockchain_tx_123');
|
||||
});
|
||||
|
||||
it('should throw when visit ID not found', async () => {
|
||||
prisma.rekam_medis.findUnique.mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
service.createObatToDBAndBlockchain(createDto, 1),
|
||||
).rejects.toThrow(BadRequestException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateObat', () => {
|
||||
const existingObat = {
|
||||
id: 1,
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
|
||||
const updateDto: UpdateObatDto = {
|
||||
obat: 'Ibuprofen',
|
||||
jumlah_obat: 20,
|
||||
aturan_pakai: '2x1',
|
||||
};
|
||||
|
||||
it('should create validation queue entry for update', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(existingObat);
|
||||
prisma.validation_queue.create.mockResolvedValue({
|
||||
id: 1,
|
||||
action: 'UPDATE',
|
||||
status: 'PENDING',
|
||||
});
|
||||
|
||||
const result = await service.updateObat(1, updateDto, mockUser);
|
||||
|
||||
expect(prisma.validation_queue.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
table_name: 'pemberian_obat',
|
||||
action: 'UPDATE',
|
||||
dataPayload: updateDto,
|
||||
record_id: '1',
|
||||
user_id_request: mockUser.sub,
|
||||
status: 'PENDING',
|
||||
},
|
||||
});
|
||||
expect(result.status).toBe('PENDING');
|
||||
});
|
||||
|
||||
it('should throw when ID is invalid (NaN)', async () => {
|
||||
await expect(
|
||||
service.updateObat(NaN, updateDto, mockUser),
|
||||
).rejects.toThrow('Medicine ID not valid');
|
||||
});
|
||||
|
||||
it('should throw when obat not found', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
service.updateObat(999, updateDto, mockUser),
|
||||
).rejects.toThrow('Medicine with ID 999 not found');
|
||||
});
|
||||
|
||||
it('should throw when no changes detected', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(existingObat);
|
||||
|
||||
const noChangeDto: UpdateObatDto = {
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
|
||||
await expect(
|
||||
service.updateObat(1, noChangeDto, mockUser),
|
||||
).rejects.toThrow('No changes in medicine data detected');
|
||||
});
|
||||
|
||||
it('should detect change in obat field only', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(existingObat);
|
||||
prisma.validation_queue.create.mockResolvedValue({ id: 1 });
|
||||
|
||||
const partialChangeDto: UpdateObatDto = {
|
||||
obat: 'Different',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
|
||||
await service.updateObat(1, partialChangeDto, mockUser);
|
||||
|
||||
expect(prisma.validation_queue.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should detect change in jumlah_obat field only', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(existingObat);
|
||||
prisma.validation_queue.create.mockResolvedValue({ id: 1 });
|
||||
|
||||
const partialChangeDto: UpdateObatDto = {
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 99,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
|
||||
await service.updateObat(1, partialChangeDto, mockUser);
|
||||
|
||||
expect(prisma.validation_queue.create).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateObatToDBAndBlockchain', () => {
|
||||
const updateDto: UpdateObatDto = {
|
||||
obat: 'Ibuprofen',
|
||||
jumlah_obat: 20,
|
||||
aturan_pakai: '2x1',
|
||||
};
|
||||
|
||||
it('should update obat and store log in transaction', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue({ id: 1 });
|
||||
|
||||
const mockTx = {
|
||||
pemberian_obat: {
|
||||
update: jest.fn().mockResolvedValue({ id: 1, ...updateDto }),
|
||||
},
|
||||
};
|
||||
prisma.$transaction.mockImplementation(async (callback) =>
|
||||
callback(mockTx),
|
||||
);
|
||||
logService.storeLog.mockResolvedValue({ txId: 'blockchain_tx_456' });
|
||||
|
||||
const result = await service.updateObatToDBAndBlockchain(1, updateDto, 1);
|
||||
|
||||
expect(mockTx.pemberian_obat.update).toHaveBeenCalledWith({
|
||||
where: { id: 1 },
|
||||
data: updateDto,
|
||||
});
|
||||
expect(logService.storeLog).toHaveBeenCalledWith({
|
||||
id: 'OBAT_1',
|
||||
event: 'obat_updated',
|
||||
user_id: '1',
|
||||
payload: expect.any(String),
|
||||
});
|
||||
expect(result.txId).toBe('blockchain_tx_456');
|
||||
});
|
||||
|
||||
it('should throw when ID is invalid', async () => {
|
||||
await expect(
|
||||
service.updateObatToDBAndBlockchain(NaN, updateDto, 1),
|
||||
).rejects.toThrow('ID medicine not valid');
|
||||
});
|
||||
|
||||
it('should throw when obat not found', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
service.updateObatToDBAndBlockchain(999, updateDto, 1),
|
||||
).rejects.toThrow('Medicine with id 999 not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteObat', () => {
|
||||
const existingObat = {
|
||||
id: 1,
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
|
||||
it('should create validation queue and mark as DELETE_VALIDATION', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(existingObat);
|
||||
|
||||
const mockTx = {
|
||||
pemberian_obat: {
|
||||
update: jest.fn().mockResolvedValue({
|
||||
...existingObat,
|
||||
deleted_status: 'DELETE_VALIDATION',
|
||||
}),
|
||||
},
|
||||
};
|
||||
prisma.$transaction.mockImplementation(async (callback) =>
|
||||
callback(mockTx),
|
||||
);
|
||||
prisma.validation_queue.create.mockResolvedValue({
|
||||
id: 1,
|
||||
action: 'DELETE',
|
||||
status: 'PENDING',
|
||||
});
|
||||
|
||||
const result = await service.deleteObat(1, mockUser);
|
||||
|
||||
expect(prisma.validation_queue.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
table_name: 'pemberian_obat',
|
||||
action: 'DELETE',
|
||||
dataPayload: existingObat,
|
||||
record_id: '1',
|
||||
user_id_request: mockUser.sub,
|
||||
status: 'PENDING',
|
||||
},
|
||||
});
|
||||
expect(mockTx.pemberian_obat.update).toHaveBeenCalledWith({
|
||||
where: { id: 1 },
|
||||
data: { deleted_status: 'DELETE_VALIDATION' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when obat not found', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(null);
|
||||
|
||||
await expect(service.deleteObat(999, mockUser)).rejects.toThrow(
|
||||
'Obat with id 999 not found',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteObatFromDBAndBlockchain', () => {
|
||||
const existingObat = {
|
||||
id: 1,
|
||||
obat: 'Paracetamol',
|
||||
jumlah_obat: 10,
|
||||
aturan_pakai: '3x1',
|
||||
};
|
||||
|
||||
it('should mark as deleted and store log', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(existingObat);
|
||||
|
||||
const mockTx = {
|
||||
pemberian_obat: {
|
||||
update: jest.fn().mockResolvedValue({
|
||||
...existingObat,
|
||||
deleted_status: 'DELETED',
|
||||
}),
|
||||
},
|
||||
};
|
||||
prisma.$transaction.mockImplementation(async (callback) =>
|
||||
callback(mockTx),
|
||||
);
|
||||
logService.storeLog.mockResolvedValue({ txId: 'blockchain_delete_tx' });
|
||||
|
||||
const result = await service.deleteObatFromDBAndBlockchain(1, 1);
|
||||
|
||||
expect(mockTx.pemberian_obat.update).toHaveBeenCalledWith({
|
||||
where: { id: 1 },
|
||||
data: { deleted_status: 'DELETED' },
|
||||
});
|
||||
expect(logService.storeLog).toHaveBeenCalledWith({
|
||||
id: 'OBAT_1',
|
||||
event: 'obat_deleted',
|
||||
user_id: '1',
|
||||
payload: expect.any(String),
|
||||
});
|
||||
expect(result.txId).toBe('blockchain_delete_tx');
|
||||
});
|
||||
|
||||
it('should throw when ID is invalid', async () => {
|
||||
await expect(
|
||||
service.deleteObatFromDBAndBlockchain(NaN, 1),
|
||||
).rejects.toThrow('Medicine ID not valid');
|
||||
});
|
||||
|
||||
it('should throw when obat not found', async () => {
|
||||
prisma.pemberian_obat.findUnique.mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
service.deleteObatFromDBAndBlockchain(999, 1),
|
||||
).rejects.toThrow('Medicine with ID 999 not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('countObat', () => {
|
||||
it('should return count excluding deleted records', async () => {
|
||||
prisma.pemberian_obat.count.mockResolvedValue(42);
|
||||
|
||||
const result = await service.countObat();
|
||||
|
||||
expect(result).toBe(42);
|
||||
expect(prisma.pemberian_obat.count).toHaveBeenCalledWith({
|
||||
where: {
|
||||
OR: [
|
||||
{ deleted_status: null },
|
||||
{ deleted_status: 'DELETE_VALIDATION' },
|
||||
{ deleted_status: { not: 'DELETED' } },
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -102,6 +102,20 @@ export class ObatService {
|
|||
throw new Error(`Obat with id ${id} not found`);
|
||||
}
|
||||
|
||||
if (!rawLogs || rawLogs.length === 0) {
|
||||
const currentDataHash = this.createHashingPayload({
|
||||
obat: currentData.obat,
|
||||
jumlah_obat: currentData.jumlah_obat,
|
||||
aturan_pakai: currentData.aturan_pakai,
|
||||
});
|
||||
|
||||
return {
|
||||
logs: [],
|
||||
isTampered: true,
|
||||
currentDataHash: currentDataHash,
|
||||
};
|
||||
}
|
||||
|
||||
const currentDataHash = this.createHashingPayload({
|
||||
obat: currentData.obat,
|
||||
jumlah_obat: currentData.jumlah_obat,
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ services:
|
|||
- /home/labai1/josafat/hospital-log/backend/blockchain/chaincode:/opt/gopath/src/github.com/hyperledger/fabric/peer/chaincode
|
||||
- /home/labai1/josafat/hospital-log/backend/blockchain/network/organizations:/opt/gopath/src/github.com/hyperledger/fabric/peer/organizations
|
||||
- /home/labai1/josafat/hospital-log/backend/blockchain/network/channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts
|
||||
extra_hosts:
|
||||
- "peer1.hospital.com:192.168.11.94"
|
||||
- "peer2.hospital.com:192.168.11.63"
|
||||
depends_on:
|
||||
- orderer
|
||||
- peer0
|
||||
|
|
@ -128,8 +131,14 @@ services:
|
|||
- /var/run/docker.sock:/host/var/run/docker.sock
|
||||
- /home/labai2/josafat/hospital-log/backend/blockchain/network/organizations/peerOrganizations/hospital.com/peers/peer1.hospital.com/msp:/etc/hyperledger/fabric/msp
|
||||
- /home/labai2/josafat/hospital-log/backend/blockchain/network/organizations/peerOrganizations/hospital.com/peers/peer1.hospital.com/tls:/etc/hyperledger/fabric/tls
|
||||
extra_hosts:
|
||||
- "peer0.hospital.com:192.168.11.211"
|
||||
- "peer2.hospital.com:192.168.11.63"
|
||||
ports:
|
||||
- "8051:8051"
|
||||
- target: 8051
|
||||
published: 8051
|
||||
protocol: tcp
|
||||
mode: host
|
||||
networks:
|
||||
- hospital_net
|
||||
deploy:
|
||||
|
|
@ -152,14 +161,22 @@ services:
|
|||
- CORE_PEER_CHAINCODEADDRESS=peer2.hospital.com:7052
|
||||
- CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:7052
|
||||
- CORE_PEER_GOSSIP_BOOTSTRAP=peer0.hospital.com:7051
|
||||
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer2.hospital.com:9051
|
||||
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=192.168.11.63:9051
|
||||
- CORE_PEER_LOCALMSPID=HospitalMSP
|
||||
volumes:
|
||||
- /var/run/docker.sock:/host/var/run/docker.sock
|
||||
- /run/desktop/mnt/host/c/fabric-data/network/organizations/peerOrganizations/hospital.com/peers/peer2.hospital.com/msp:/etc/hyperledger/fabric/msp
|
||||
- /run/desktop/mnt/host/c/fabric-data/network/organizations/peerOrganizations/hospital.com/peers/peer2.hospital.com/tls:/etc/hyperledger/fabric/tls
|
||||
- /home/my_device/josafat/hospital-log/backend/blockchain/network/organizations/peerOrganizations/hospital.com/peers/peer2.hospital.com/msp:/etc/hyperledger/fabric/msp
|
||||
- /home/my_device/josafat/hospital-log/backend/blockchain/network/organizations/peerOrganizations/hospital.com/peers/peer2.hospital.com/tls:/etc/hyperledger/fabric/tls
|
||||
- /home/my_device/josafat/hospital-log/backend/blockchain/data:/var/hyperledger/production
|
||||
extra_hosts:
|
||||
- "peer0.hospital.com:192.168.11.211"
|
||||
- "orderer.hospital.com:192.168.11.211"
|
||||
- "peer1.hospital.com:192.168.11.94"
|
||||
ports:
|
||||
- "9051:9051"
|
||||
- target: 9051
|
||||
published: 9051
|
||||
protocol: tcp
|
||||
mode: host
|
||||
networks:
|
||||
- hospital_net
|
||||
deploy:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user