251211:1314 Frontend: reeactor Admin panel
This commit is contained in:
157
frontend/lib/services/__tests__/correspondence.service.test.ts
Normal file
157
frontend/lib/services/__tests__/correspondence.service.test.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { correspondenceService } from '../correspondence.service';
|
||||
import apiClient from '@/lib/api/client';
|
||||
|
||||
// apiClient is already mocked in vitest.setup.ts
|
||||
|
||||
describe('correspondenceService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
it('should call GET /correspondences with params', async () => {
|
||||
const mockResponse = {
|
||||
data: [{ id: 1, title: 'Test' }],
|
||||
meta: { total: 1 },
|
||||
};
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await correspondenceService.getAll({ projectId: 1 });
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/correspondences', {
|
||||
params: { projectId: 1 },
|
||||
});
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should call GET /correspondences without params', async () => {
|
||||
const mockResponse = { data: [] };
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
await correspondenceService.getAll();
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/correspondences', {
|
||||
params: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getById', () => {
|
||||
it('should call GET /correspondences/:id', async () => {
|
||||
const mockResponse = { id: 1, title: 'Test' };
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await correspondenceService.getById(1);
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/correspondences/1');
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should work with string id', async () => {
|
||||
const mockResponse = { id: 1 };
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
await correspondenceService.getById('123');
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/correspondences/123');
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should call POST /correspondences with data', async () => {
|
||||
const createDto = {
|
||||
title: 'New Correspondence',
|
||||
projectId: 1,
|
||||
correspondenceTypeId: 1,
|
||||
};
|
||||
const mockResponse = { id: 1, ...createDto };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await correspondenceService.create(createDto);
|
||||
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/correspondences', createDto);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should call PUT /correspondences/:id with data', async () => {
|
||||
const updateData = { title: 'Updated Title' };
|
||||
const mockResponse = { id: 1, title: 'Updated Title' };
|
||||
vi.mocked(apiClient.put).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await correspondenceService.update(1, updateData);
|
||||
|
||||
expect(apiClient.put).toHaveBeenCalledWith('/correspondences/1', updateData);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should call DELETE /correspondences/:id', async () => {
|
||||
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
|
||||
|
||||
const result = await correspondenceService.delete(1);
|
||||
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/correspondences/1');
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('submit', () => {
|
||||
it('should call POST /correspondences/:id/submit', async () => {
|
||||
const submitDto = { recipientIds: [2, 3] };
|
||||
const mockResponse = { id: 1, status: 'submitted' };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await correspondenceService.submit(1, submitDto);
|
||||
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/correspondences/1/submit', submitDto);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('processWorkflow', () => {
|
||||
it('should call POST /correspondences/:id/workflow', async () => {
|
||||
const workflowDto = { action: 'approve', comment: 'LGTM' };
|
||||
const mockResponse = { id: 1, status: 'approved' };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await correspondenceService.processWorkflow(1, workflowDto);
|
||||
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/correspondences/1/workflow', workflowDto);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addReference', () => {
|
||||
it('should call POST /correspondences/:id/references', async () => {
|
||||
const referenceDto = { referencedDocumentId: 2, referenceType: 'reply_to' };
|
||||
const mockResponse = { id: 1 };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await correspondenceService.addReference(1, referenceDto);
|
||||
|
||||
expect(apiClient.post).toHaveBeenCalledWith(
|
||||
'/correspondences/1/references',
|
||||
referenceDto
|
||||
);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeReference', () => {
|
||||
it('should call DELETE /correspondences/:id/references with body', async () => {
|
||||
const referenceDto = { referencedDocumentId: 2 };
|
||||
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
|
||||
|
||||
const result = await correspondenceService.removeReference(1, referenceDto);
|
||||
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/correspondences/1/references', {
|
||||
data: referenceDto,
|
||||
});
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
329
frontend/lib/services/__tests__/master-data.service.test.ts
Normal file
329
frontend/lib/services/__tests__/master-data.service.test.ts
Normal file
@@ -0,0 +1,329 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { masterDataService } from '../master-data.service';
|
||||
import apiClient from '@/lib/api/client';
|
||||
|
||||
// apiClient is already mocked in vitest.setup.ts
|
||||
|
||||
describe('masterDataService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
// --- Tags ---
|
||||
describe('Tags', () => {
|
||||
describe('getTags', () => {
|
||||
it('should call GET /tags with params', async () => {
|
||||
const mockTags = [{ id: 1, name: 'Important' }];
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: { data: mockTags } });
|
||||
|
||||
const result = await masterDataService.getTags({ search: 'test' });
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/master/tags', { params: { search: 'test' } });
|
||||
expect(result).toEqual(mockTags);
|
||||
});
|
||||
|
||||
it('should handle unwrapped response', async () => {
|
||||
const mockTags = [{ id: 1, name: 'Urgent' }];
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: mockTags });
|
||||
|
||||
const result = await masterDataService.getTags();
|
||||
|
||||
expect(result).toEqual(mockTags);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTag', () => {
|
||||
it('should call POST /tags', async () => {
|
||||
const createDto = { name: 'New Tag', color: '#ff0000' };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1, ...createDto } });
|
||||
|
||||
const result = await masterDataService.createTag(createDto);
|
||||
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/master/tags', createDto);
|
||||
expect(result).toEqual({ id: 1, ...createDto });
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateTag', () => {
|
||||
it('should call PUT /tags/:id', async () => {
|
||||
const updateDto = { name: 'Updated Tag' };
|
||||
vi.mocked(apiClient.patch).mockResolvedValue({ data: { id: 1, ...updateDto } });
|
||||
|
||||
const result = await masterDataService.updateTag(1, updateDto);
|
||||
|
||||
expect(apiClient.patch).toHaveBeenCalledWith('/master/tags/1', updateDto);
|
||||
expect(result).toEqual({ id: 1, ...updateDto });
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteTag', () => {
|
||||
it('should call DELETE /tags/:id', async () => {
|
||||
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
|
||||
|
||||
const result = await masterDataService.deleteTag(1);
|
||||
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/master/tags/1');
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// --- Organizations ---
|
||||
describe('Organizations', () => {
|
||||
describe('getOrganizations', () => {
|
||||
it('should call GET /organizations and unwrap paginated response', async () => {
|
||||
const mockOrgs = [{ organizationId: 1, name: 'Org A' }];
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: { data: mockOrgs } });
|
||||
|
||||
const result = await masterDataService.getOrganizations();
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/organizations', { params: undefined });
|
||||
expect(result).toEqual(mockOrgs);
|
||||
});
|
||||
|
||||
it('should handle array response', async () => {
|
||||
const mockOrgs = [{ organizationId: 1 }];
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: mockOrgs });
|
||||
|
||||
const result = await masterDataService.getOrganizations();
|
||||
|
||||
expect(result).toEqual(mockOrgs);
|
||||
});
|
||||
|
||||
it('should return empty array as fallback', async () => {
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: {} });
|
||||
|
||||
const result = await masterDataService.getOrganizations();
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createOrganization', () => {
|
||||
it('should call POST /organizations', async () => {
|
||||
const createDto = { name: 'New Org', code: 'ORG-001' };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: { organizationId: 1, ...createDto } });
|
||||
|
||||
const result = await masterDataService.createOrganization(createDto);
|
||||
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/organizations', createDto);
|
||||
expect(result.organizationId).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateOrganization', () => {
|
||||
it('should call PUT /organizations/:id', async () => {
|
||||
const updateDto = { name: 'Updated Org' };
|
||||
vi.mocked(apiClient.put).mockResolvedValue({ data: { organizationId: 1, ...updateDto } });
|
||||
|
||||
const result = await masterDataService.updateOrganization(1, updateDto);
|
||||
|
||||
expect(apiClient.put).toHaveBeenCalledWith('/organizations/1', updateDto);
|
||||
expect(result.name).toBe('Updated Org');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteOrganization', () => {
|
||||
it('should call DELETE /organizations/:id', async () => {
|
||||
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
|
||||
|
||||
await masterDataService.deleteOrganization(1);
|
||||
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/organizations/1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// --- Disciplines ---
|
||||
describe('Disciplines', () => {
|
||||
describe('getDisciplines', () => {
|
||||
it('should call GET /master/disciplines with contractId', async () => {
|
||||
const mockDisciplines = [{ id: 1, name: 'Civil' }];
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: { data: mockDisciplines } });
|
||||
|
||||
const result = await masterDataService.getDisciplines(1);
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/master/disciplines', {
|
||||
params: { contractId: 1 },
|
||||
});
|
||||
expect(result).toEqual(mockDisciplines);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createDiscipline', () => {
|
||||
it('should call POST /master/disciplines', async () => {
|
||||
const createDto = { name: 'Electrical', contractId: 1 };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1, ...createDto } });
|
||||
|
||||
const result = await masterDataService.createDiscipline(createDto);
|
||||
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/master/disciplines', createDto);
|
||||
expect(result.name).toBe('Electrical');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteDiscipline', () => {
|
||||
it('should call DELETE /master/disciplines/:id', async () => {
|
||||
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
|
||||
|
||||
await masterDataService.deleteDiscipline(1);
|
||||
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/master/disciplines/1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// --- SubTypes ---
|
||||
describe('SubTypes', () => {
|
||||
describe('getSubTypes', () => {
|
||||
it('should call GET /master/sub-types with contractId and typeId', async () => {
|
||||
const mockSubTypes = [{ id: 1, name: 'Submittal' }];
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: { data: mockSubTypes } });
|
||||
|
||||
const result = await masterDataService.getSubTypes(1, 2);
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/master/sub-types', {
|
||||
params: { contractId: 1, correspondenceTypeId: 2 },
|
||||
});
|
||||
expect(result).toEqual(mockSubTypes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSubType', () => {
|
||||
it('should call POST /master/sub-types', async () => {
|
||||
const createDto = { name: 'New SubType' };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1, ...createDto } });
|
||||
|
||||
const result = await masterDataService.createSubType(createDto);
|
||||
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/master/sub-types', createDto);
|
||||
expect(result).toEqual({ id: 1, ...createDto });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// --- RFA Types ---
|
||||
describe('RfaTypes', () => {
|
||||
describe('getRfaTypes', () => {
|
||||
it('should call GET /master/rfa-types', async () => {
|
||||
const mockTypes = [{ id: 1, name: 'Material Approval' }];
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: { data: mockTypes } });
|
||||
|
||||
const result = await masterDataService.getRfaTypes(1);
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/master/rfa-types', {
|
||||
params: { contractId: 1 },
|
||||
});
|
||||
expect(result).toEqual(mockTypes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createRfaType', () => {
|
||||
it('should call POST /master/rfa-types', async () => {
|
||||
const data = { name: 'New RFA Type' };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1, ...data } });
|
||||
|
||||
const result = await masterDataService.createRfaType(data);
|
||||
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/master/rfa-types', data);
|
||||
expect(result).toEqual({ id: 1, ...data });
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateRfaType', () => {
|
||||
it('should call PATCH /master/rfa-types/:id', async () => {
|
||||
const data = { name: 'Updated Type' };
|
||||
vi.mocked(apiClient.patch).mockResolvedValue({ data: { id: 1, ...data } });
|
||||
|
||||
await masterDataService.updateRfaType(1, data);
|
||||
|
||||
expect(apiClient.patch).toHaveBeenCalledWith('/master/rfa-types/1', data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteRfaType', () => {
|
||||
it('should call DELETE /master/rfa-types/:id', async () => {
|
||||
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
|
||||
|
||||
await masterDataService.deleteRfaType(1);
|
||||
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/master/rfa-types/1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// --- Correspondence Types ---
|
||||
describe('CorrespondenceTypes', () => {
|
||||
describe('getCorrespondenceTypes', () => {
|
||||
it('should call GET /master/correspondence-types', async () => {
|
||||
const mockTypes = [{ id: 1, name: 'Letter' }];
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: { data: mockTypes } });
|
||||
|
||||
const result = await masterDataService.getCorrespondenceTypes();
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/master/correspondence-types');
|
||||
expect(result).toEqual(mockTypes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createCorrespondenceType', () => {
|
||||
it('should call POST /master/correspondence-types', async () => {
|
||||
const data = { name: 'Memo' };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 2, ...data } });
|
||||
|
||||
await masterDataService.createCorrespondenceType(data);
|
||||
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/master/correspondence-types', data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateCorrespondenceType', () => {
|
||||
it('should call PATCH /master/correspondence-types/:id', async () => {
|
||||
const data = { name: 'Updated Type' };
|
||||
vi.mocked(apiClient.patch).mockResolvedValue({ data: { id: 1, ...data } });
|
||||
|
||||
await masterDataService.updateCorrespondenceType(1, data);
|
||||
|
||||
expect(apiClient.patch).toHaveBeenCalledWith('/master/correspondence-types/1', data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteCorrespondenceType', () => {
|
||||
it('should call DELETE /master/correspondence-types/:id', async () => {
|
||||
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
|
||||
|
||||
await masterDataService.deleteCorrespondenceType(1);
|
||||
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/master/correspondence-types/1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// --- Number Format ---
|
||||
describe('NumberFormat', () => {
|
||||
describe('saveNumberFormat', () => {
|
||||
it('should call POST /document-numbering/formats', async () => {
|
||||
const data = { projectId: 1, correspondenceTypeId: 1, format: '{PREFIX}-{YYYY}-{SEQ}' };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1, ...data } });
|
||||
|
||||
await masterDataService.saveNumberFormat(data);
|
||||
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/document-numbering/formats', data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNumberFormat', () => {
|
||||
it('should call GET /document-numbering/formats with params', async () => {
|
||||
const mockFormat = { id: 1, format: '{PREFIX}-{SEQ}' };
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: mockFormat });
|
||||
|
||||
const result = await masterDataService.getNumberFormat(1, 2);
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/document-numbering/formats', {
|
||||
params: { projectId: 1, correspondenceTypeId: 2 },
|
||||
});
|
||||
expect(result).toEqual(mockFormat);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
95
frontend/lib/services/__tests__/project.service.test.ts
Normal file
95
frontend/lib/services/__tests__/project.service.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { projectService } from '../project.service';
|
||||
import apiClient from '@/lib/api/client';
|
||||
|
||||
// apiClient is already mocked in vitest.setup.ts
|
||||
|
||||
describe('projectService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
it('should call GET /projects with params', async () => {
|
||||
const mockData = [{ id: 1, name: 'Project Alpha' }];
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: mockData });
|
||||
|
||||
const result = await projectService.getAll({ search: 'alpha' });
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/projects', {
|
||||
params: { search: 'alpha' },
|
||||
});
|
||||
expect(result).toEqual(mockData);
|
||||
});
|
||||
|
||||
it('should unwrap paginated response', async () => {
|
||||
const mockData = [{ id: 1, name: 'Test' }];
|
||||
vi.mocked(apiClient.get).mockResolvedValue({
|
||||
data: { data: mockData, meta: { total: 1 } },
|
||||
});
|
||||
|
||||
const result = await projectService.getAll();
|
||||
|
||||
expect(result).toEqual(mockData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getById', () => {
|
||||
it('should call GET /projects/:id', async () => {
|
||||
const mockResponse = { id: 1, name: 'Project Alpha', code: 'P-001' };
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await projectService.getById(1);
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/projects/1');
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should work with string id', async () => {
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: {} });
|
||||
|
||||
await projectService.getById('123');
|
||||
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/projects/123');
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should call POST /projects with data', async () => {
|
||||
const createDto = { projectName: 'New Project', projectCode: 'P-002' };
|
||||
const mockResponse = { id: 2, ...createDto };
|
||||
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await projectService.create(createDto);
|
||||
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/projects', createDto);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should call PUT /projects/:id with data', async () => {
|
||||
const updateData = { projectName: 'Updated Project' };
|
||||
const mockResponse = { id: 1, projectName: 'Updated Project' };
|
||||
vi.mocked(apiClient.put).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await projectService.update(1, updateData);
|
||||
|
||||
expect(apiClient.put).toHaveBeenCalledWith('/projects/1', updateData);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should call DELETE /projects/:id', async () => {
|
||||
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
|
||||
|
||||
const result = await projectService.delete(1);
|
||||
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/projects/1');
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
56
frontend/lib/services/contract.service.ts
Normal file
56
frontend/lib/services/contract.service.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import apiClient from "@/lib/api/client";
|
||||
import {
|
||||
CreateContractDto,
|
||||
UpdateContractDto,
|
||||
SearchContractDto,
|
||||
} from "@/types/dto/contract/contract.dto";
|
||||
|
||||
export const contractService = {
|
||||
/**
|
||||
* Get all contracts (supports filtering by projectId)
|
||||
* GET /contracts?projectId=1
|
||||
*/
|
||||
getAll: async (params?: SearchContractDto) => {
|
||||
const response = await apiClient.get("/contracts", { params });
|
||||
if (response.data && Array.isArray(response.data.data)) {
|
||||
return response.data.data;
|
||||
}
|
||||
return response.data.data || response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get contract by ID
|
||||
* GET /contracts/:id
|
||||
*/
|
||||
getById: async (id: number) => {
|
||||
const response = await apiClient.get(`/contracts/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create new contract
|
||||
* POST /contracts
|
||||
*/
|
||||
create: async (data: CreateContractDto) => {
|
||||
const response = await apiClient.post("/contracts", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update contract
|
||||
* PATCH /contracts/:id
|
||||
*/
|
||||
update: async (id: number, data: UpdateContractDto) => {
|
||||
const response = await apiClient.patch(`/contracts/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete contract
|
||||
* DELETE /contracts/:id
|
||||
*/
|
||||
delete: async (id: number) => {
|
||||
const response = await apiClient.delete(`/contracts/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
@@ -11,33 +11,33 @@ import {
|
||||
CreateOrganizationDto,
|
||||
UpdateOrganizationDto,
|
||||
SearchOrganizationDto,
|
||||
} from "@/types/dto/organization.dto";
|
||||
} from "@/types/dto/organization/organization.dto";
|
||||
|
||||
export const masterDataService = {
|
||||
// --- Tags Management ---
|
||||
|
||||
/** ดึงรายการ Tags ทั้งหมด (Search & Pagination) */
|
||||
getTags: async (params?: SearchTagDto) => {
|
||||
const response = await apiClient.get("/tags", { params });
|
||||
const response = await apiClient.get("/master/tags", { params });
|
||||
// Support both wrapped and unwrapped scenarios
|
||||
return response.data.data || response.data;
|
||||
},
|
||||
|
||||
/** สร้าง Tag ใหม่ */
|
||||
createTag: async (data: CreateTagDto) => {
|
||||
const response = await apiClient.post("/tags", data);
|
||||
const response = await apiClient.post("/master/tags", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** แก้ไข Tag */
|
||||
updateTag: async (id: number | string, data: UpdateTagDto) => {
|
||||
const response = await apiClient.put(`/tags/${id}`, data);
|
||||
const response = await apiClient.patch(`/master/tags/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** ลบ Tag */
|
||||
deleteTag: async (id: number | string) => {
|
||||
const response = await apiClient.delete(`/tags/${id}`);
|
||||
const response = await apiClient.delete(`/master/tags/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
||||
57
frontend/lib/services/organization.service.ts
Normal file
57
frontend/lib/services/organization.service.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import apiClient from "@/lib/api/client";
|
||||
import {
|
||||
CreateOrganizationDto,
|
||||
UpdateOrganizationDto,
|
||||
SearchOrganizationDto,
|
||||
} from "@/types/dto/organization/organization.dto";
|
||||
|
||||
export const organizationService = {
|
||||
/**
|
||||
* Get all organizations (supports filtering by projectId)
|
||||
* GET /organizations?projectId=1
|
||||
*/
|
||||
getAll: async (params?: SearchOrganizationDto) => {
|
||||
const response = await apiClient.get("/organizations", { params });
|
||||
// Normalize response if wrapped in data.data or direct data
|
||||
if (response.data && Array.isArray(response.data.data)) {
|
||||
return response.data.data;
|
||||
}
|
||||
return response.data.data || response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get organization by ID
|
||||
* GET /organizations/:id
|
||||
*/
|
||||
getById: async (id: number) => {
|
||||
const response = await apiClient.get(`/organizations/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create new organization
|
||||
* POST /organizations
|
||||
*/
|
||||
create: async (data: CreateOrganizationDto) => {
|
||||
const response = await apiClient.post("/organizations", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update organization
|
||||
* PATCH /organizations/:id
|
||||
*/
|
||||
update: async (id: number, data: UpdateOrganizationDto) => {
|
||||
const response = await apiClient.patch(`/organizations/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete organization
|
||||
* DELETE /organizations/:id
|
||||
*/
|
||||
delete: async (id: number) => {
|
||||
const response = await apiClient.delete(`/organizations/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
@@ -49,39 +49,8 @@ export const projectService = {
|
||||
|
||||
// --- Related Data / Dropdown Helpers ---
|
||||
|
||||
/** * ดึงรายชื่อองค์กรในโครงการ (สำหรับ Dropdown 'To/From')
|
||||
* GET /projects/:id/organizations
|
||||
*/
|
||||
getOrganizations: async (projectId: string | number) => {
|
||||
const response = await apiClient.get(`/projects/${projectId}/organizations`);
|
||||
// Unwrap the response data if it's wrapped in a 'data' property by the interceptor
|
||||
return response.data.data || response.data;
|
||||
},
|
||||
|
||||
/** * ดึงรายชื่อสัญญาในโครงการ
|
||||
* GET /projects/:id/contracts
|
||||
*/
|
||||
/** * ดึงรายชื่อสัญญาในโครงการ (Legacy/Specific)
|
||||
* GET /projects/:id/contracts
|
||||
*/
|
||||
getContracts: async (projectId: string | number) => {
|
||||
// Note: If backend doesn't have /projects/:id/contracts, use /contracts?projectId=:id
|
||||
const response = await apiClient.get(`/contracts`, { params: { projectId } });
|
||||
// Handle paginated response
|
||||
if (response.data && Array.isArray(response.data.data)) {
|
||||
return response.data.data;
|
||||
}
|
||||
return response.data.data || response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ดึงรายการสัญญาเรื้งหมด (Global Search)
|
||||
*/
|
||||
getAllContracts: async (params?: any) => {
|
||||
const response = await apiClient.get("/contracts", { params });
|
||||
if (response.data && Array.isArray(response.data.data)) {
|
||||
return response.data.data;
|
||||
}
|
||||
return response.data.data || response.data;
|
||||
}
|
||||
// --- Related Data / Dropdown Helpers ---
|
||||
// Organizations and Contracts should now be fetched via their respective services
|
||||
// organizationService.getAll({ projectId })
|
||||
// contractService.getAll({ projectId })
|
||||
};
|
||||
|
||||
35
frontend/lib/test-utils.tsx
Normal file
35
frontend/lib/test-utils.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
/**
|
||||
* Creates a wrapper with QueryClient for testing hooks
|
||||
* @returns Object with wrapper component and queryClient instance
|
||||
*/
|
||||
export function createTestQueryClient() {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
gcTime: 0,
|
||||
staleTime: 0,
|
||||
},
|
||||
mutations: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
|
||||
return { wrapper, queryClient };
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for all pending operations in React Query
|
||||
*/
|
||||
export async function waitForQueryClient(queryClient: QueryClient) {
|
||||
await queryClient.getQueryCache().clear();
|
||||
await queryClient.getMutationCache().clear();
|
||||
}
|
||||
Reference in New Issue
Block a user