test(frontend): raise overall statement coverage to 30.42% for Phase 1 MVP
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
// File: frontend/lib/services/__tests__/circulation.service.test.ts
|
||||
// Change Log:
|
||||
// - 2026-06-13: Initial creation - unit tests for circulationService
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import apiClient from '@/lib/api/client';
|
||||
import { circulationService } from '../circulation.service';
|
||||
|
||||
// Mock apiClient
|
||||
vi.mock('@/lib/api/client', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
patch: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('circulationService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
it('ควรดึงรายการ Circulation ทั้งหมดพร้อม params', async () => {
|
||||
const mockResponse = { data: [{ publicId: 'uuid-1', subject: 'Circulation A' }] };
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const searchParams = { search: 'Circ' };
|
||||
const result = await circulationService.getAll(searchParams);
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/circulations', { params: searchParams });
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getByUuid', () => {
|
||||
it('ควรดึงรายละเอียด Circulation ตาม uuid', async () => {
|
||||
const mockResponse = { data: { publicId: 'uuid-1', subject: 'Circulation A' } };
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const result = await circulationService.getByUuid('uuid-1');
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/circulations/uuid-1');
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('ควรสร้าง Circulation ใหม่', async () => {
|
||||
const mockResponse = { data: { publicId: 'uuid-1', subject: 'Circulation A' } };
|
||||
vi.mocked(apiClient.post).mockResolvedValue(mockResponse);
|
||||
const createDto = {
|
||||
correspondenceId: 'uuid-corr',
|
||||
recipientUserIds: ['uuid-user'],
|
||||
};
|
||||
const result = await circulationService.create(createDto as any);
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/circulations', createDto);
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateRouting', () => {
|
||||
it('ควรปรับปรุงข้อมูลการ Routing', async () => {
|
||||
const mockResponse = { data: { success: true } };
|
||||
vi.mocked(apiClient.patch).mockResolvedValue(mockResponse);
|
||||
const routingDto = { action: 'ACKNOWLEDGE', comments: 'Seen.' };
|
||||
const result = await circulationService.updateRouting('uuid-1', routingDto as any);
|
||||
expect(apiClient.patch).toHaveBeenCalledWith('/circulations/uuid-1/routing', routingDto);
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getByCorrespondenceUuid', () => {
|
||||
it('ควรดึงรายการ Circulation โดยอิงตาม correspondence uuid', async () => {
|
||||
const mockResponse = { data: [{ publicId: 'uuid-1', subject: 'Circulation A' }] };
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const result = await circulationService.getByCorrespondenceUuid('uuid-corr-1');
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/circulations', {
|
||||
params: { correspondencePublicId: 'uuid-corr-1', limit: 50 },
|
||||
});
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('ควรลบ Circulation', async () => {
|
||||
const mockResponse = { data: { success: true } };
|
||||
vi.mocked(apiClient.delete).mockResolvedValue(mockResponse);
|
||||
const result = await circulationService.delete('uuid-1');
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/circulations/uuid-1');
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,148 @@
|
||||
// File: frontend/lib/services/__tests__/dashboard.service.test.ts
|
||||
// Change Log:
|
||||
// - 2026-06-13: Initial creation - unit tests for dashboardService
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import apiClient from '@/lib/api/client';
|
||||
import { dashboardService } from '../dashboard.service';
|
||||
|
||||
// Mock apiClient
|
||||
vi.mock('@/lib/api/client', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('dashboardService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getStats', () => {
|
||||
it('ควรดึงข้อมูลสถิติของแดชบอร์ดสำเร็จ', async () => {
|
||||
const mockResponse = { data: { totalDocuments: 100, pendingApprovals: 5 } };
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const result = await dashboardService.getStats('uuid-proj-1');
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/dashboard/stats', { params: { projectId: 'uuid-proj-1' } });
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
|
||||
it('ควรดึงข้อมูลสถิติโดยไม่ต้องส่ง projectId', async () => {
|
||||
const mockResponse = { data: { totalDocuments: 200 } };
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const result = await dashboardService.getStats();
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/dashboard/stats', { params: undefined });
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRecentActivity', () => {
|
||||
it('ควรดึงประวัติการเคลื่อนไหวล่าสุดและจัดรูปแบบให้ถูกต้อง', async () => {
|
||||
const mockResponse = {
|
||||
data: [
|
||||
{
|
||||
id: 'act-1',
|
||||
action: 'CREATE',
|
||||
entityType: 'RFA',
|
||||
entityId: 'uuid-rfa',
|
||||
details: { description: 'สร้างเอกสาร RFA ใหม่' },
|
||||
createdAt: '2026-01-01T00:00:00Z',
|
||||
user: { firstName: 'สมชาย', lastName: 'รักดี', username: 'somchai' },
|
||||
},
|
||||
{
|
||||
id: 'act-2',
|
||||
action: 'UPDATE',
|
||||
entityType: 'Transmittal',
|
||||
entityId: 'uuid-trans',
|
||||
createdAt: '2026-01-01T00:00:00Z',
|
||||
user: { username: 'testuser' },
|
||||
},
|
||||
{
|
||||
id: 'act-3',
|
||||
action: 'DELETE',
|
||||
createdAt: '2026-01-01T00:00:00Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const result = await dashboardService.getRecentActivity('uuid-proj-1');
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result[0].user.name).toBe('สมชาย รักดี');
|
||||
expect(result[0].user.initials).toBe('สร');
|
||||
expect(result[0].description).toBe('สร้างเอกสาร RFA ใหม่');
|
||||
expect(result[0].targetUrl).toBe('/rfas/uuid-rfa');
|
||||
expect(result[1].user.name).toBe('testuser');
|
||||
expect(result[1].user.initials).toBe('T');
|
||||
expect(result[1].description).toBe('UPDATE Transmittal uuid-trans');
|
||||
expect(result[1].targetUrl).toBe('/transmittals/uuid-trans');
|
||||
expect(result[2].user.name).toBe('System');
|
||||
expect(result[2].user.initials).toBe('S');
|
||||
expect(result[2].targetUrl).toBe('/correspondences/');
|
||||
});
|
||||
|
||||
it('ควรคืนค่าเป็นอาเรย์ว่างเมื่อเกิดข้อผิดพลาดจาก API', async () => {
|
||||
vi.mocked(apiClient.get).mockRejectedValue(new Error('API Failure'));
|
||||
const result = await dashboardService.getRecentActivity();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('ควรคืนค่าเป็นอาเรย์ว่างเมื่อข้อมูลไม่ใช้รูปแบบอาเรย์', async () => {
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: { message: 'Not an array' } });
|
||||
const result = await dashboardService.getRecentActivity();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPendingTasks', () => {
|
||||
it('ควรดึงข้อมูลงานที่ค้างและคำนวณจำนวนวันล่วงเลยกับความสำคัญ', async () => {
|
||||
const now = new Date();
|
||||
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||
const fourDaysAgo = new Date(now.getTime() - 4 * 24 * 60 * 60 * 1000);
|
||||
const mockResponse = {
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
instanceId: 'inst-1',
|
||||
workflowCode: 'WF-RFA',
|
||||
currentState: 'REVIEWING',
|
||||
entityType: 'RFA',
|
||||
entityId: 'uuid-rfa-1',
|
||||
documentNumber: 'RFA-001',
|
||||
subject: 'งานด่วนพิเศษ',
|
||||
assignedAt: oneDayAgo.toISOString(),
|
||||
},
|
||||
{
|
||||
instanceId: 'inst-2',
|
||||
workflowCode: 'WF-TR',
|
||||
currentState: 'APPROVED',
|
||||
entityType: 'Transmittal',
|
||||
entityId: 'uuid-tr-1',
|
||||
documentNumber: 'TR-001',
|
||||
subject: '',
|
||||
assignedAt: fourDaysAgo.toISOString(),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const result = await dashboardService.getPendingTasks('uuid-proj-1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].publicId).toBe('inst-1');
|
||||
expect(result[0].title).toBe('งานด่วนพิเศษ');
|
||||
expect(result[0].daysOverdue).toBe(1);
|
||||
expect(result[0].priority).toBe('MEDIUM');
|
||||
expect(result[0].url).toBe('/rfas/uuid-rfa-1');
|
||||
expect(result[1].publicId).toBe('inst-2');
|
||||
expect(result[1].title).toBe('TR-001');
|
||||
expect(result[1].daysOverdue).toBe(4);
|
||||
expect(result[1].priority).toBe('HIGH');
|
||||
expect(result[1].url).toBe('/transmittals/uuid-tr-1');
|
||||
});
|
||||
|
||||
it('ควรคืนค่าเป็นอาเรย์ว่างเมื่อเกิดข้อผิดพลาดจาก API', async () => {
|
||||
vi.mocked(apiClient.get).mockRejectedValue(new Error('API Failure'));
|
||||
const result = await dashboardService.getPendingTasks();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
// File: frontend/lib/services/__tests__/document-numbering.service.test.ts
|
||||
// Change Log:
|
||||
// - 2026-06-13: Refactor to use static imports instead of require, fixing ESM module resolution errors
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { documentNumberingService } from '../document-numbering.service';
|
||||
import apiClient from '@/lib/api/client';
|
||||
|
||||
// Mock apiClient
|
||||
vi.mock('@/lib/api/client', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('documentNumberingService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Admin Dashboard Metrics', () => {
|
||||
it('should get metrics', async () => {
|
||||
const mockMetrics = {
|
||||
totalNumbers: 100,
|
||||
activeReservations: 5,
|
||||
recentActivity: [],
|
||||
};
|
||||
(apiClient.get as any).mockResolvedValue({ data: mockMetrics });
|
||||
const result = await documentNumberingService.getMetrics();
|
||||
expect(result).toEqual(mockMetrics);
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/admin/document-numbering/metrics');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Admin Tools', () => {
|
||||
it('should perform manual override', async () => {
|
||||
const mockDto = {
|
||||
documentNumber: 'DOC-001',
|
||||
newSequence: 100,
|
||||
reason: 'Manual override',
|
||||
};
|
||||
(apiClient.post as any).mockResolvedValue({ data: { success: true } });
|
||||
await documentNumberingService.manualOverride(mockDto);
|
||||
expect(apiClient.post).toHaveBeenCalledWith(
|
||||
'/admin/document-numbering/manual-override',
|
||||
mockDto
|
||||
);
|
||||
});
|
||||
|
||||
it('should void and replace number', async () => {
|
||||
const mockDto = {
|
||||
documentNumber: 'DOC-001',
|
||||
reason: 'Void',
|
||||
replace: true,
|
||||
};
|
||||
const mockResponse = { documentNumber: 'DOC-002' };
|
||||
(apiClient.post as any).mockResolvedValue({ data: mockResponse });
|
||||
const result = await documentNumberingService.voidAndReplace(mockDto);
|
||||
expect(result).toEqual(mockResponse);
|
||||
expect(apiClient.post).toHaveBeenCalledWith(
|
||||
'/admin/document-numbering/void-and-replace',
|
||||
mockDto
|
||||
);
|
||||
});
|
||||
|
||||
it('should cancel number', async () => {
|
||||
const mockDto = {
|
||||
documentNumber: 'DOC-001',
|
||||
reason: 'Cancel',
|
||||
projectId: 1,
|
||||
};
|
||||
(apiClient.post as any).mockResolvedValue({ data: { success: true } });
|
||||
await documentNumberingService.cancelNumber(mockDto);
|
||||
expect(apiClient.post).toHaveBeenCalledWith(
|
||||
'/admin/document-numbering/cancel',
|
||||
mockDto
|
||||
);
|
||||
});
|
||||
|
||||
it('should bulk import with FormData', async () => {
|
||||
const mockFormData = new FormData();
|
||||
mockFormData.append('file', new Blob(['test']), 'test.csv');
|
||||
const mockResponse = { imported: 10, errors: [] };
|
||||
(apiClient.post as any).mockResolvedValue({ data: mockResponse });
|
||||
const result = await documentNumberingService.bulkImport(mockFormData);
|
||||
expect(result).toEqual(mockResponse);
|
||||
expect(apiClient.post).toHaveBeenCalledWith(
|
||||
'/admin/document-numbering/bulk-import',
|
||||
mockFormData,
|
||||
{ headers: { 'Content-Type': 'multipart/form-data' } }
|
||||
);
|
||||
});
|
||||
|
||||
it('should bulk import with array data', async () => {
|
||||
const mockData = [
|
||||
{ documentNumber: 'DOC-001', projectId: 1, sequenceNumber: 1 },
|
||||
{ documentNumber: 'DOC-002', projectId: 1, sequenceNumber: 2 },
|
||||
];
|
||||
const mockResponse = { imported: 2, errors: [] };
|
||||
(apiClient.post as any).mockResolvedValue({ data: mockResponse });
|
||||
const result = await documentNumberingService.bulkImport(mockData);
|
||||
expect(result).toEqual(mockResponse);
|
||||
expect(apiClient.post).toHaveBeenCalledWith(
|
||||
'/admin/document-numbering/bulk-import',
|
||||
mockData,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Audit Logs', () => {
|
||||
it('should get audit logs (currently returns empty)', async () => {
|
||||
const result = await documentNumberingService.getAuditLogs();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,105 @@
|
||||
// File: frontend/lib/services/__tests__/rfa.service.test.ts
|
||||
// Change Log:
|
||||
// - 2026-06-13: Initial creation - unit tests for rfaService
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import apiClient from '@/lib/api/client';
|
||||
import { rfaService } from '../rfa.service';
|
||||
|
||||
// Mock apiClient
|
||||
vi.mock('@/lib/api/client', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
put: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('rfaService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
it('ควรดึงรายการ RFA ทั้งหมดพร้อม params', async () => {
|
||||
const mockResponse = { data: [{ publicId: 'uuid-1', subject: 'Test RFA' }] };
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const searchParams = { search: 'Test' };
|
||||
const result = await rfaService.getAll(searchParams);
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/rfas', { params: searchParams });
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getByUuid', () => {
|
||||
it('ควรดึงรายละเอียด RFA ตาม uuid', async () => {
|
||||
const mockResponse = { data: { publicId: 'uuid-1', subject: 'Test RFA' } };
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const result = await rfaService.getByUuid('uuid-1');
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/rfas/uuid-1');
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('ควรสร้าง RFA ใหม่', async () => {
|
||||
const mockResponse = { data: { publicId: 'uuid-1', subject: 'Test RFA' } };
|
||||
vi.mocked(apiClient.post).mockResolvedValue(mockResponse);
|
||||
const createDto = {
|
||||
projectId: 'uuid-proj',
|
||||
contractId: 'uuid-cont',
|
||||
disciplineId: 'uuid-disp',
|
||||
rfaTypeId: 'uuid-type',
|
||||
subject: 'Test RFA',
|
||||
toOrganizationId: 'uuid-org',
|
||||
};
|
||||
const result = await rfaService.create(createDto as any);
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/rfas', createDto);
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('submit', () => {
|
||||
it('ควรส่ง RFA เข้า workflow', async () => {
|
||||
const mockResponse = { data: { publicId: 'uuid-1', status: 'SUBMITTED' } };
|
||||
vi.mocked(apiClient.post).mockResolvedValue(mockResponse);
|
||||
const submitDto = { templateId: 1, reviewTeamPublicId: 'uuid-team' };
|
||||
const result = await rfaService.submit('uuid-1', submitDto);
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/rfas/uuid-1/submit', submitDto);
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('ควรแก้ไขข้อมูล RFA', async () => {
|
||||
const mockResponse = { data: { publicId: 'uuid-1', subject: 'Updated RFA' } };
|
||||
vi.mocked(apiClient.put).mockResolvedValue(mockResponse);
|
||||
const updateDto = { subject: 'Updated RFA' };
|
||||
const result = await rfaService.update('uuid-1', updateDto);
|
||||
expect(apiClient.put).toHaveBeenCalledWith('/rfas/uuid-1', updateDto);
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('processWorkflow', () => {
|
||||
it('ควรดำเนินการขั้นตอนอนุมัติ (Workflow Action)', async () => {
|
||||
const mockResponse = { data: { status: 'APPROVED' } };
|
||||
vi.mocked(apiClient.post).mockResolvedValue(mockResponse);
|
||||
const actionDto = { action: 'APPROVE', comments: 'Approved!' };
|
||||
const result = await rfaService.processWorkflow('uuid-1', actionDto as any);
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/rfas/uuid-1/action', actionDto);
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('ควรลบ RFA (Soft Delete)', async () => {
|
||||
const mockResponse = { data: { success: true } };
|
||||
vi.mocked(apiClient.delete).mockResolvedValue(mockResponse);
|
||||
const result = await rfaService.delete('uuid-1');
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/rfas/uuid-1');
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,154 @@
|
||||
// File: frontend/lib/services/__tests__/session.service.test.ts
|
||||
// Change Log:
|
||||
// - 2026-06-13: Refactor to use static imports instead of require, fixing ESM module resolution errors
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { sessionService, extractArrayData, transformSession } from '../session.service';
|
||||
import apiClient from '@/lib/api/client';
|
||||
|
||||
// Mock apiClient
|
||||
vi.mock('@/lib/api/client', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('sessionService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getActiveSessions', () => {
|
||||
it('should get active sessions from array response', async () => {
|
||||
const mockSessions = [
|
||||
{
|
||||
id: 1,
|
||||
userId: 1,
|
||||
user: { username: 'testuser', firstName: 'Test', lastName: 'User' },
|
||||
deviceName: 'Chrome',
|
||||
ipAddress: '192.168.1.1',
|
||||
lastActive: '2026-01-01T00:00:00Z',
|
||||
isCurrent: true,
|
||||
},
|
||||
];
|
||||
(apiClient.get as any).mockResolvedValue({ data: mockSessions });
|
||||
const result = await sessionService.getActiveSessions();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].id).toBe(1);
|
||||
expect(result[0].user.username).toBe('testuser');
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/auth/sessions');
|
||||
});
|
||||
|
||||
it('should get active sessions from nested data response', async () => {
|
||||
const mockSessions = [
|
||||
{
|
||||
id: 2,
|
||||
userId: 2,
|
||||
user: { username: 'testuser2', firstName: 'Test2', lastName: 'User2' },
|
||||
deviceName: 'Firefox',
|
||||
ipAddress: '192.168.1.2',
|
||||
lastActive: '2026-01-02T00:00:00Z',
|
||||
isCurrent: false,
|
||||
},
|
||||
];
|
||||
(apiClient.get as any).mockResolvedValue({ data: { data: mockSessions } });
|
||||
const result = await sessionService.getActiveSessions();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].id).toBe(2);
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/auth/sessions');
|
||||
});
|
||||
|
||||
it('should handle string id in session and convert to number', async () => {
|
||||
const mockSessions = [
|
||||
{
|
||||
id: '3',
|
||||
userId: 3,
|
||||
user: { username: 'testuser3', firstName: 'Test3', lastName: 'User3' },
|
||||
deviceName: 'Safari',
|
||||
ipAddress: '192.168.1.3',
|
||||
lastActive: '2026-01-03T00:00:00Z',
|
||||
isCurrent: false,
|
||||
},
|
||||
];
|
||||
(apiClient.get as any).mockResolvedValue({ data: { data: { data: mockSessions } } });
|
||||
const result = await sessionService.getActiveSessions();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].id).toBe(3);
|
||||
expect(typeof result[0].id).toBe('number');
|
||||
});
|
||||
|
||||
it('should return empty array for non-array response', async () => {
|
||||
(apiClient.get as any).mockResolvedValue({ data: 'not an array' });
|
||||
const result = await sessionService.getActiveSessions();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array for null response', async () => {
|
||||
(apiClient.get as any).mockResolvedValue({ data: null });
|
||||
const result = await sessionService.getActiveSessions();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('revokeSession', () => {
|
||||
it('should revoke session by id', async () => {
|
||||
const mockResponse = { success: true };
|
||||
(apiClient.delete as any).mockResolvedValue({ data: mockResponse });
|
||||
const result = await sessionService.revokeSession(1);
|
||||
expect(result).toEqual(mockResponse);
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/auth/sessions/1');
|
||||
});
|
||||
|
||||
it('should revoke session with numeric id', async () => {
|
||||
const mockResponse = { success: true };
|
||||
(apiClient.delete as any).mockResolvedValue({ data: mockResponse });
|
||||
await sessionService.revokeSession(123);
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/auth/sessions/123');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Helper Functions', () => {
|
||||
it('should extract array data from nested structure', () => {
|
||||
const data = { data: { data: [1, 2, 3] } };
|
||||
const result = extractArrayData(data);
|
||||
expect(result).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('should return empty array for non-array data', () => {
|
||||
const data = { data: 'not an array' };
|
||||
const result = extractArrayData(data);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should transform session with number id', () => {
|
||||
const session = {
|
||||
id: 1,
|
||||
userId: 1,
|
||||
user: { username: 'test', firstName: 'Test', lastName: 'User' },
|
||||
deviceName: 'Chrome',
|
||||
ipAddress: '192.168.1.1',
|
||||
lastActive: '2026-01-01T00:00:00Z',
|
||||
isCurrent: true,
|
||||
};
|
||||
const result = transformSession(session);
|
||||
expect(result.id).toBe(1);
|
||||
expect(typeof result.id).toBe('number');
|
||||
});
|
||||
|
||||
it('should transform session with string id to number', () => {
|
||||
const session = {
|
||||
id: '1',
|
||||
userId: 1,
|
||||
user: { username: 'test', firstName: 'Test', lastName: 'User' },
|
||||
deviceName: 'Chrome',
|
||||
ipAddress: '192.168.1.1',
|
||||
lastActive: '2026-01-01T00:00:00Z',
|
||||
isCurrent: true,
|
||||
};
|
||||
const result = transformSession(session);
|
||||
expect(result.id).toBe(1);
|
||||
expect(typeof result.id).toBe('number');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
// File: frontend/lib/services/__tests__/transmittal.service.test.ts
|
||||
// Change Log:
|
||||
// - 2026-06-13: Initial creation - unit tests for transmittalService
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import apiClient from '@/lib/api/client';
|
||||
import { transmittalService } from '../transmittal.service';
|
||||
|
||||
// Mock apiClient
|
||||
vi.mock('@/lib/api/client', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
put: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('transmittalService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
it('ควรดึงรายการ Transmittal ทั้งหมดพร้อม params', async () => {
|
||||
const mockResponse = { data: [{ publicId: 'uuid-1', transmittalNumber: 'TR-001' }] };
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const searchParams = { search: 'TR' };
|
||||
const result = await transmittalService.getAll(searchParams);
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/transmittals', { params: searchParams });
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getByUuid', () => {
|
||||
it('ควรดึงรายละเอียด Transmittal ตาม uuid', async () => {
|
||||
const mockResponse = { data: { publicId: 'uuid-1', transmittalNumber: 'TR-001' } };
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const result = await transmittalService.getByUuid('uuid-1');
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/transmittals/uuid-1');
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('ควรสร้าง Transmittal ใหม่', async () => {
|
||||
const mockResponse = { data: { publicId: 'uuid-1', transmittalNumber: 'TR-001' } };
|
||||
vi.mocked(apiClient.post).mockResolvedValue(mockResponse);
|
||||
const createDto = {
|
||||
projectId: 'uuid-proj',
|
||||
subject: 'Test Transmittal',
|
||||
};
|
||||
const result = await transmittalService.create(createDto as any);
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/transmittals', createDto);
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('ควรแก้ไขข้อมูล Transmittal', async () => {
|
||||
const mockResponse = { data: { publicId: 'uuid-1', subject: 'Updated Transmittal' } };
|
||||
vi.mocked(apiClient.put).mockResolvedValue(mockResponse);
|
||||
const updateDto = { subject: 'Updated Transmittal' };
|
||||
const result = await transmittalService.update('uuid-1', updateDto);
|
||||
expect(apiClient.put).toHaveBeenCalledWith('/transmittals/uuid-1', updateDto);
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('submit', () => {
|
||||
it('ควรส่ง Transmittal เข้า workflow และคืนค่าผลลัพธ์', async () => {
|
||||
const mockResponse = { data: { data: { instanceId: 'inst-1', currentState: 'SUBMITTED' } } };
|
||||
vi.mocked(apiClient.post).mockResolvedValue(mockResponse);
|
||||
const result = await transmittalService.submit('uuid-1');
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/transmittals/uuid-1/submit');
|
||||
expect(result).toEqual(mockResponse.data.data);
|
||||
});
|
||||
|
||||
it('ควรส่ง Transmittal เข้า workflow และจัดการ fallback เมื่อไม่มี data property ใน response', async () => {
|
||||
const mockResponse = { data: { instanceId: 'inst-2', currentState: 'APPROVED' } };
|
||||
vi.mocked(apiClient.post).mockResolvedValue(mockResponse);
|
||||
const result = await transmittalService.submit('uuid-2');
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('ควรลบ Transmittal (Soft Delete)', async () => {
|
||||
const mockResponse = { data: { success: true } };
|
||||
vi.mocked(apiClient.delete).mockResolvedValue(mockResponse);
|
||||
const result = await transmittalService.delete('uuid-1');
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/transmittals/uuid-1');
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,139 @@
|
||||
// File: frontend/lib/services/__tests__/user.service.test.ts
|
||||
// Change Log:
|
||||
// - 2026-06-13: Initial creation - unit tests for userService
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import apiClient from '@/lib/api/client';
|
||||
import { userService } from '../user.service';
|
||||
|
||||
// Mock apiClient
|
||||
vi.mock('@/lib/api/client', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
put: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('userService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
it('ควรดึงข้อมูลผู้ใช้งานทั้งหมดและแปลงข้อมูล (transformUser)', async () => {
|
||||
const mockResponse = {
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
user_id: 123,
|
||||
publicId: 'uuid-user-1',
|
||||
username: 'test1',
|
||||
assignments: [{ role: { roleName: 'Admin' } }],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const result = await userService.getAll({ search: 'test1' });
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/users', { params: { search: 'test1' } });
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual({
|
||||
user_id: 123,
|
||||
userId: 123,
|
||||
publicId: 'uuid-user-1',
|
||||
username: 'test1',
|
||||
assignments: [{ role: { roleName: 'Admin' } }],
|
||||
roles: [{ roleName: 'Admin' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('ควรคืนค่าเป็นอาเรย์ว่างเมื่อไม่พบข้อมูล', async () => {
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ data: null });
|
||||
const result = await userService.getAll();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRoles', () => {
|
||||
it('ควรดึงข้อมูลบทบาทผู้ใช้สำเร็จ', async () => {
|
||||
const mockResponse = { data: [{ roleName: 'Admin' }, { roleName: 'User' }] };
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const result = await userService.getRoles();
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/users/roles');
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getByUuid', () => {
|
||||
it('ควรดึงรายละเอียดผู้ใช้ตาม uuid และทำการ transform', async () => {
|
||||
const mockResponse = {
|
||||
data: {
|
||||
userId: 456,
|
||||
publicId: 'uuid-user-2',
|
||||
username: 'test2',
|
||||
},
|
||||
};
|
||||
vi.mocked(apiClient.get).mockResolvedValue(mockResponse);
|
||||
const result = await userService.getByUuid('uuid-user-2');
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/users/uuid-user-2');
|
||||
expect(result).toEqual({
|
||||
userId: 456,
|
||||
publicId: 'uuid-user-2',
|
||||
username: 'test2',
|
||||
roles: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('ควรสร้างผู้ใช้งานใหม่สำเร็จ', async () => {
|
||||
const mockResponse = {
|
||||
data: {
|
||||
publicId: 'uuid-new',
|
||||
username: 'newuser',
|
||||
},
|
||||
};
|
||||
vi.mocked(apiClient.post).mockResolvedValue(mockResponse);
|
||||
const createDto = { username: 'newuser', email: 'new@example.com' };
|
||||
const result = await userService.create(createDto as any);
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/users', createDto);
|
||||
expect(result).toEqual({
|
||||
publicId: 'uuid-new',
|
||||
username: 'newuser',
|
||||
roles: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('ควรแก้ไขข้อมูลผู้ใช้งานสำเร็จ', async () => {
|
||||
const mockResponse = {
|
||||
data: {
|
||||
publicId: 'uuid-existing',
|
||||
username: 'updateduser',
|
||||
},
|
||||
};
|
||||
vi.mocked(apiClient.put).mockResolvedValue(mockResponse);
|
||||
const updateDto = { username: 'updateduser' };
|
||||
const result = await userService.update('uuid-existing', updateDto);
|
||||
expect(apiClient.put).toHaveBeenCalledWith('/users/uuid-existing', updateDto);
|
||||
expect(result).toEqual({
|
||||
publicId: 'uuid-existing',
|
||||
username: 'updateduser',
|
||||
roles: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('ควรลบผู้ใช้งานสำเร็จ', async () => {
|
||||
const mockResponse = { data: { success: true } };
|
||||
vi.mocked(apiClient.delete).mockResolvedValue(mockResponse);
|
||||
const result = await userService.delete('uuid-existing');
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/users/uuid-existing');
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,277 @@
|
||||
// File: frontend/lib/services/__tests__/workflow-engine.service.test.ts
|
||||
// Change Log:
|
||||
// - 2026-06-13: Refactor to use static imports instead of require, fixing ESM module resolution errors
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
workflowEngineService,
|
||||
normalizeWorkflowType,
|
||||
extractDslDefinition,
|
||||
extractArrayData,
|
||||
extractNestedData,
|
||||
mapWorkflow,
|
||||
} from '../workflow-engine.service';
|
||||
import apiClient from '@/lib/api/client';
|
||||
|
||||
// Mock apiClient
|
||||
vi.mock('@/lib/api/client', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
patch: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('workflowEngineService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Engine Execution', () => {
|
||||
it('should get available actions', async () => {
|
||||
const mockActions = ['APPROVE', 'REJECT'];
|
||||
(apiClient.post as any).mockResolvedValue({ data: { data: mockActions } });
|
||||
const result = await workflowEngineService.getAvailableActions({
|
||||
entityType: 'RFA',
|
||||
entityId: '019505a1-7c3e-7000-8000-abc123def456',
|
||||
});
|
||||
expect(result).toEqual(mockActions);
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/workflow-engine/available-actions', {
|
||||
entityType: 'RFA',
|
||||
entityId: '019505a1-7c3e-7000-8000-abc123def456',
|
||||
});
|
||||
});
|
||||
|
||||
it('should evaluate workflow transition', async () => {
|
||||
const mockEvaluation = { nextState: 'APPROVED', events: [] };
|
||||
(apiClient.post as any).mockResolvedValue({ data: { data: mockEvaluation } });
|
||||
const result = await workflowEngineService.evaluate({
|
||||
entityType: 'RFA',
|
||||
entityId: '019505a1-7c3e-7000-8000-abc123def456',
|
||||
action: 'APPROVE',
|
||||
});
|
||||
expect(result).toEqual(mockEvaluation);
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/workflow-engine/evaluate', {
|
||||
entityType: 'RFA',
|
||||
entityId: '019505a1-7c3e-7000-8000-abc123def456',
|
||||
action: 'APPROVE',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Definition Management', () => {
|
||||
it('should get all workflow definitions', async () => {
|
||||
const mockWorkflows = [
|
||||
{
|
||||
id: 1,
|
||||
workflow_code: 'RFA_FLOW_V1',
|
||||
description: 'RFA Workflow',
|
||||
version: 1,
|
||||
is_active: true,
|
||||
dsl: { workflowName: 'RFA Flow' },
|
||||
compiled: { states: { DFT: {}, FAP: {} } },
|
||||
updated_at: '2026-01-01T00:00:00Z',
|
||||
},
|
||||
];
|
||||
(apiClient.get as any).mockResolvedValue({ data: mockWorkflows });
|
||||
const result = await workflowEngineService.getDefinitions();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].workflowName).toBe('RFA Flow');
|
||||
expect(result[0].workflowType).toBe('RFA');
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/workflow-engine/definitions');
|
||||
});
|
||||
|
||||
it('should get workflow definition by id', async () => {
|
||||
const mockWorkflow = {
|
||||
id: 1,
|
||||
workflow_code: 'RFA_FLOW_V1',
|
||||
description: 'RFA Workflow',
|
||||
version: 1,
|
||||
is_active: true,
|
||||
dsl: { workflowName: 'RFA Flow' },
|
||||
compiled: { states: { DFT: {}, FAP: {} } },
|
||||
updated_at: '2026-01-01T00:00:00Z',
|
||||
};
|
||||
(apiClient.get as any).mockResolvedValue({ data: { data: mockWorkflow } });
|
||||
const result = await workflowEngineService.getDefinitionById(1);
|
||||
expect(result.workflowName).toBe('RFA Flow');
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/workflow-engine/definitions/1');
|
||||
});
|
||||
|
||||
it('should create workflow definition', async () => {
|
||||
const mockCreated = { id: 1, workflow_code: 'NEW_FLOW' };
|
||||
(apiClient.post as any).mockResolvedValue({ data: { data: mockCreated } });
|
||||
const result = await workflowEngineService.createDefinition({
|
||||
workflowCode: 'NEW_FLOW',
|
||||
dslDefinition: '{}',
|
||||
});
|
||||
expect(result).toEqual(mockCreated);
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/workflow-engine/definitions', {
|
||||
workflowCode: 'NEW_FLOW',
|
||||
dslDefinition: '{}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should update workflow definition', async () => {
|
||||
const mockUpdated = { id: 1, workflow_code: 'UPDATED_FLOW' };
|
||||
(apiClient.patch as any).mockResolvedValue({ data: { data: mockUpdated } });
|
||||
const result = await workflowEngineService.updateDefinition(1, {
|
||||
dslDefinition: '{}',
|
||||
});
|
||||
expect(result).toEqual(mockUpdated);
|
||||
expect(apiClient.patch).toHaveBeenCalledWith('/workflow-engine/definitions/1', {
|
||||
dslDefinition: '{}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should validate DSL', async () => {
|
||||
const mockValidation = { valid: true };
|
||||
(apiClient.post as any).mockResolvedValue({ data: { data: mockValidation } });
|
||||
const result = await workflowEngineService.validateDsl({ workflowName: 'Test' });
|
||||
expect(result).toEqual(mockValidation);
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/workflow-engine/definitions/validate', {
|
||||
dsl: { workflowName: 'Test' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should return validation errors for invalid DSL', async () => {
|
||||
const mockValidation = {
|
||||
valid: false,
|
||||
errors: [{ path: 'states', message: 'Invalid state' }],
|
||||
};
|
||||
(apiClient.post as any).mockResolvedValue({ data: { data: mockValidation } });
|
||||
const result = await workflowEngineService.validateDsl({});
|
||||
expect(result.valid).toBe(false);
|
||||
if (!result.valid) {
|
||||
expect(result.errors).toHaveLength(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should delete workflow definition', async () => {
|
||||
const mockDeleted = { id: 1 };
|
||||
(apiClient.delete as any).mockResolvedValue({ data: { data: mockDeleted } });
|
||||
const result = await workflowEngineService.deleteDefinition(1);
|
||||
expect(result).toEqual(mockDeleted);
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/workflow-engine/definitions/1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Workflow Transition and History', () => {
|
||||
it('should transition workflow instance', async () => {
|
||||
const mockTransition = { instanceId: 'uuid-1', state: 'APPROVED' };
|
||||
(apiClient.post as any).mockResolvedValue({ data: { data: mockTransition } });
|
||||
const result = await workflowEngineService.transition(
|
||||
'019505a1-7c3e-7000-8000-abc123def456',
|
||||
{ action: 'APPROVE', comments: 'Approved' },
|
||||
'idempotency-key-123'
|
||||
);
|
||||
expect(result).toEqual(mockTransition);
|
||||
expect(apiClient.post).toHaveBeenCalledWith(
|
||||
'/workflow-engine/instances/019505a1-7c3e-7000-8000-abc123def456/transition',
|
||||
{ action: 'APPROVE', comments: 'Approved' },
|
||||
{ headers: { 'Idempotency-Key': 'idempotency-key-123' } }
|
||||
);
|
||||
});
|
||||
|
||||
it('should get workflow history', async () => {
|
||||
const mockHistory = [
|
||||
{
|
||||
id: 1,
|
||||
fromState: 'DFT',
|
||||
toState: 'FAP',
|
||||
action: 'SUBMIT',
|
||||
actorId: 'user-uuid',
|
||||
timestamp: '2026-01-01T00:00:00Z',
|
||||
},
|
||||
];
|
||||
(apiClient.get as any).mockResolvedValue({ data: { data: mockHistory } });
|
||||
const result = await workflowEngineService.getHistory('019505a1-7c3e-7000-8000-abc123def456');
|
||||
expect(result).toEqual(mockHistory);
|
||||
expect(apiClient.get).toHaveBeenCalledWith(
|
||||
'/workflow-engine/instances/019505a1-7c3e-7000-8000-abc123def456/history'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty history array', async () => {
|
||||
(apiClient.get as any).mockResolvedValue({ data: { data: [] } });
|
||||
const result = await workflowEngineService.getHistory('019505a1-7c3e-7000-8000-abc123def456');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Helper Functions', () => {
|
||||
it('should normalize workflow type to RFA', () => {
|
||||
expect(normalizeWorkflowType('RFA_FLOW_V1')).toBe('RFA');
|
||||
expect(normalizeWorkflowType('rfa_flow_v1')).toBe('RFA');
|
||||
});
|
||||
|
||||
it('should normalize workflow type to DRAWING', () => {
|
||||
expect(normalizeWorkflowType('DRAWING_FLOW_V1')).toBe('DRAWING');
|
||||
expect(normalizeWorkflowType('drawing_flow_v1')).toBe('DRAWING');
|
||||
});
|
||||
|
||||
it('should normalize workflow type to CORRESPONDENCE by default', () => {
|
||||
expect(normalizeWorkflowType('CORR_FLOW_V1')).toBe('CORRESPONDENCE');
|
||||
expect(normalizeWorkflowType(undefined)).toBe('CORRESPONDENCE');
|
||||
});
|
||||
|
||||
it('should extract DSL definition from string', () => {
|
||||
const dsl = '{"workflowName": "Test"}';
|
||||
expect(extractDslDefinition(dsl)).toBe(dsl);
|
||||
});
|
||||
|
||||
it('should extract DSL definition from object', () => {
|
||||
const dsl = { dslDefinition: '{"workflowName": "Test"}' };
|
||||
expect(extractDslDefinition(dsl)).toBe('{"workflowName": "Test"}');
|
||||
});
|
||||
|
||||
it('should return empty string for invalid DSL', () => {
|
||||
expect(extractDslDefinition(null)).toBe('');
|
||||
expect(extractDslDefinition(undefined)).toBe('');
|
||||
expect(extractDslDefinition('')).toBe('');
|
||||
});
|
||||
|
||||
it('should extract array data from nested structure', () => {
|
||||
const data = { data: { data: [1, 2, 3] } };
|
||||
const result = extractArrayData(data);
|
||||
expect(result).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('should return empty array for non-array data', () => {
|
||||
const data = { data: 'not an array' };
|
||||
const result = extractArrayData(data);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should extract nested data', () => {
|
||||
const data = { data: { data: { id: 1 } } };
|
||||
const result = extractNestedData(data);
|
||||
expect(result).toEqual({ id: 1 });
|
||||
});
|
||||
|
||||
it('should map backend workflow to frontend workflow', () => {
|
||||
const backendWorkflow = {
|
||||
id: 1,
|
||||
workflow_code: 'RFA_FLOW_V1',
|
||||
description: 'Test',
|
||||
version: 1,
|
||||
is_active: true,
|
||||
dsl: { workflowName: 'RFA Flow' },
|
||||
compiled: { states: { DFT: {}, FAP: {} } },
|
||||
updated_at: '2026-01-01T00:00:00Z',
|
||||
};
|
||||
const result = mapWorkflow(backendWorkflow);
|
||||
expect(result.publicId).toBe('1');
|
||||
expect(result.workflowName).toBe('RFA Flow');
|
||||
expect(result.workflowType).toBe('RFA');
|
||||
expect(result.version).toBe(1);
|
||||
expect(result.isActive).toBe(true);
|
||||
expect(result.stepCount).toBe(2);
|
||||
});
|
||||
|
||||
it('should throw error when mapping null workflow', () => {
|
||||
expect(() => mapWorkflow(null as any)).toThrow('Workflow not found');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user