alpe-temp 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -1
- package/backend-project/server-err.txt +0 -0
- package/backend-project/server-out.txt +6 -0
- package/backend-project/src/modules/reports/reports.controller.js +147 -0
- package/backend-project/src/modules/reports/reports.routes.js +1 -0
- package/backend-project/test-all-routes.js +294 -0
- package/bin/epms.js +57 -92
- package/frontend-project/dist/assets/index-CRG9iE0k.css +1 -0
- package/frontend-project/dist/assets/{index-Bo0aORq7.js → index-LpBGz8lQ.js} +8 -8
- package/frontend-project/dist/index.html +2 -3
- package/frontend-project/index.html +0 -1
- package/frontend-project/src/Auth/Login.jsx +1 -1
- package/frontend-project/src/Auth/Register.jsx +0 -1
- package/frontend-project/src/Intro.jsx +1 -1
- package/frontend-project/src/api/ApiClient.js +2 -1
- package/frontend-project/src/config.js +4 -4
- package/frontend-project/src/layouts/BottomNav.jsx +1 -1
- package/frontend-project/src/layouts/TopNav.jsx +0 -1
- package/frontend-project/src/pages/Reports.jsx +38 -6
- package/package.json +2 -14
- package/frontend-project/dist/assets/index-BXwcQ8Za.css +0 -1
package/.env.example
CHANGED
|
File without changes
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const ExcelJS = require('exceljs');
|
|
1
2
|
const Employee = require('../employee/employee.model');
|
|
2
3
|
const res_ = require('../../utils/response');
|
|
3
4
|
|
|
@@ -24,6 +25,152 @@ const ReportsController = {
|
|
|
24
25
|
return res_.error(res, err.message, 500);
|
|
25
26
|
}
|
|
26
27
|
},
|
|
28
|
+
|
|
29
|
+
async employeesOnLeaveExcel(req, res) {
|
|
30
|
+
try {
|
|
31
|
+
const employees = await Employee.find({ empStatus: 'on leave' })
|
|
32
|
+
.populate('department')
|
|
33
|
+
.populate('position')
|
|
34
|
+
.sort({ 'department.departName': 1, empFirstName: 1 });
|
|
35
|
+
|
|
36
|
+
const workbook = new ExcelJS.Workbook();
|
|
37
|
+
workbook.creator = 'DAB Enterprise HRMS';
|
|
38
|
+
workbook.created = new Date();
|
|
39
|
+
|
|
40
|
+
const ws = workbook.addWorksheet('Employees on Leave', {
|
|
41
|
+
pageSetup: { orientation: 'landscape', fitToPage: true },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const primaryColor = '1F4E79';
|
|
45
|
+
const accentColor = 'D6E4F0';
|
|
46
|
+
const borderStyle = {
|
|
47
|
+
style: 'thin',
|
|
48
|
+
color: { argb: 'B0B0B0' },
|
|
49
|
+
};
|
|
50
|
+
const allBorders = {
|
|
51
|
+
top: borderStyle,
|
|
52
|
+
bottom: borderStyle,
|
|
53
|
+
left: borderStyle,
|
|
54
|
+
right: borderStyle,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const mergeAndStyle = (ws, rowNum, colStart, colEnd, value, opts = {}) => {
|
|
58
|
+
const cell = ws.getCell(`${colStart}${rowNum}`);
|
|
59
|
+
if (colStart !== colEnd) ws.mergeCells(`${colStart}${rowNum}:${colEnd}${rowNum}`);
|
|
60
|
+
cell.value = value;
|
|
61
|
+
cell.font = { name: 'Calibri', size: opts.fontSize || 11, bold: opts.bold || false, color: opts.color ? { argb: opts.color } : undefined };
|
|
62
|
+
cell.alignment = { vertical: 'middle', horizontal: opts.align || 'left', wrapText: opts.wrap || false };
|
|
63
|
+
if (opts.fill) cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: opts.fill } };
|
|
64
|
+
if (opts.border !== false) cell.border = allBorders;
|
|
65
|
+
return cell;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
ws.getColumn(1).width = 5;
|
|
69
|
+
ws.getColumn(2).width = 16;
|
|
70
|
+
ws.getColumn(3).width = 16;
|
|
71
|
+
ws.getColumn(4).width = 12;
|
|
72
|
+
ws.getColumn(5).width = 14;
|
|
73
|
+
ws.getColumn(6).width = 20;
|
|
74
|
+
ws.getColumn(7).width = 16;
|
|
75
|
+
ws.getColumn(8).width = 20;
|
|
76
|
+
|
|
77
|
+
const row1 = ws.getRow(1);
|
|
78
|
+
row1.height = 36;
|
|
79
|
+
mergeAndStyle(ws, 1, 'A', 'H', 'DAB Enterprise LTD — Employee Status Report', {
|
|
80
|
+
fontSize: 16, bold: true, color: 'FFFFFF', fill: primaryColor, align: 'center',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
ws.getRow(2).height = 22;
|
|
84
|
+
mergeAndStyle(ws, 2, 'A', 'H', `Employees currently on leave — Generated: ${new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'long', yyyy: 'numeric' })} at ${new Date().toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}`, {
|
|
85
|
+
fontSize: 10, color: primaryColor, fill: accentColor, align: 'center',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
let rowNum = 4;
|
|
89
|
+
|
|
90
|
+
const grouped = {};
|
|
91
|
+
for (const emp of employees) {
|
|
92
|
+
const deptName = emp.department ? emp.department.departName : 'Unassigned';
|
|
93
|
+
if (!grouped[deptName]) grouped[deptName] = [];
|
|
94
|
+
grouped[deptName].push(emp);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const deptNames = Object.keys(grouped);
|
|
98
|
+
if (deptNames.length === 0) {
|
|
99
|
+
mergeAndStyle(ws, rowNum, 'A', 'H', 'No employees are currently on leave.', {
|
|
100
|
+
fontSize: 11, align: 'center', border: true,
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
deptNames.forEach((deptName, di) => {
|
|
104
|
+
const emps = grouped[deptName];
|
|
105
|
+
|
|
106
|
+
if (di > 0) rowNum += 1;
|
|
107
|
+
|
|
108
|
+
ws.getRow(rowNum).height = 24;
|
|
109
|
+
mergeAndStyle(ws, rowNum, 'A', 'E', deptName, {
|
|
110
|
+
fontSize: 12, bold: true, color: 'FFFFFF', fill: primaryColor, align: 'left',
|
|
111
|
+
});
|
|
112
|
+
mergeAndStyle(ws, rowNum, 'F', 'H', `${emps.length} employee${emps.length > 1 ? 's' : ''}`, {
|
|
113
|
+
fontSize: 12, bold: true, color: 'FFFFFF', fill: primaryColor, align: 'right',
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
rowNum += 1;
|
|
117
|
+
ws.getRow(rowNum).height = 20;
|
|
118
|
+
|
|
119
|
+
const headers = ['#', 'First Name', 'Last Name', 'Gender', 'Position', 'Email', 'Phone', 'Department'];
|
|
120
|
+
const cols = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
|
|
121
|
+
const headerFill = '2E75B6';
|
|
122
|
+
headers.forEach((h, i) => {
|
|
123
|
+
mergeAndStyle(ws, rowNum, cols[i], cols[i], h, {
|
|
124
|
+
fontSize: 10, bold: true, color: 'FFFFFF', fill: headerFill, align: 'center',
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
emps.forEach((emp, idx) => {
|
|
129
|
+
rowNum += 1;
|
|
130
|
+
ws.getRow(rowNum).height = 20;
|
|
131
|
+
const rowFill = idx % 2 === 0 ? 'F2F7FB' : undefined;
|
|
132
|
+
|
|
133
|
+
const data = [
|
|
134
|
+
(idx + 1).toString(),
|
|
135
|
+
emp.empFirstName || '',
|
|
136
|
+
emp.empLastName || '',
|
|
137
|
+
emp.empGender ? emp.empGender.charAt(0).toUpperCase() + emp.empGender.slice(1) : '',
|
|
138
|
+
emp.position ? emp.position.posName : '\u2014',
|
|
139
|
+
emp.empEmail || '\u2014',
|
|
140
|
+
emp.empTelephone || '\u2014',
|
|
141
|
+
emp.department ? emp.department.departName : 'Unassigned',
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
data.forEach((val, i) => {
|
|
145
|
+
mergeAndStyle(ws, rowNum, cols[i], cols[i], val, {
|
|
146
|
+
fontSize: 10, fill: rowFill, align: i === 0 ? 'center' : 'left',
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
rowNum += 2;
|
|
153
|
+
ws.getRow(rowNum).height = 22;
|
|
154
|
+
mergeAndStyle(ws, rowNum, 'A', 'H', `Total employees on leave: ${employees.length}`, {
|
|
155
|
+
fontSize: 11, bold: true, color: primaryColor, fill: accentColor, align: 'right',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
rowNum += 2;
|
|
159
|
+
ws.getRow(rowNum).height = 18;
|
|
160
|
+
mergeAndStyle(ws, rowNum, 'A', 'H', 'Report generated by DAB Enterprise HRMS System', {
|
|
161
|
+
fontSize: 9, color: '808080', align: 'center', border: false,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
166
|
+
res.setHeader('Content-Disposition', `attachment; filename=employees-on-leave-report-${new Date().toISOString().slice(0, 10)}.xlsx`);
|
|
167
|
+
|
|
168
|
+
await workbook.xlsx.write(res);
|
|
169
|
+
res.end();
|
|
170
|
+
} catch (err) {
|
|
171
|
+
return res_.error(res, err.message, 500);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
27
174
|
};
|
|
28
175
|
|
|
29
176
|
module.exports = ReportsController;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
|
|
3
|
+
const BASE = 'http://localhost:3000';
|
|
4
|
+
let sessionCookie = '';
|
|
5
|
+
|
|
6
|
+
async function request(method, path, body = null) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const url = new URL(path, BASE);
|
|
9
|
+
const opts = {
|
|
10
|
+
method,
|
|
11
|
+
hostname: url.hostname,
|
|
12
|
+
port: url.port,
|
|
13
|
+
path: url.pathname + url.search,
|
|
14
|
+
headers: { 'Content-Type': 'application/json' },
|
|
15
|
+
};
|
|
16
|
+
if (sessionCookie) opts.headers.Cookie = sessionCookie;
|
|
17
|
+
|
|
18
|
+
const req = http.request(opts, (res) => {
|
|
19
|
+
let data = '';
|
|
20
|
+
const setCookie = res.headers['set-cookie'];
|
|
21
|
+
if (setCookie) {
|
|
22
|
+
sessionCookie = setCookie.map(c => c.split(';')[0]).join('; ');
|
|
23
|
+
}
|
|
24
|
+
res.on('data', (chunk) => data += chunk);
|
|
25
|
+
res.on('end', () => {
|
|
26
|
+
try {
|
|
27
|
+
resolve({ status: res.statusCode, body: JSON.parse(data) });
|
|
28
|
+
} catch {
|
|
29
|
+
resolve({ status: res.statusCode, body: data });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
req.on('error', reject);
|
|
34
|
+
if (body) req.write(JSON.stringify(body));
|
|
35
|
+
req.end();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let testsPassed = 0;
|
|
40
|
+
let testsFailed = 0;
|
|
41
|
+
|
|
42
|
+
function test(name, fn) {
|
|
43
|
+
return fn().then((result) => {
|
|
44
|
+
if (result.pass) {
|
|
45
|
+
console.log(` ✓ ${name}`);
|
|
46
|
+
testsPassed++;
|
|
47
|
+
} else {
|
|
48
|
+
console.log(` ✗ ${name}: ${result.reason}`);
|
|
49
|
+
testsFailed++;
|
|
50
|
+
}
|
|
51
|
+
}).catch((err) => {
|
|
52
|
+
console.log(` ✗ ${name}: threw — ${err.message}`);
|
|
53
|
+
testsFailed++;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function run() {
|
|
58
|
+
console.log('\n══════════════════════════════════════════');
|
|
59
|
+
console.log(' COMPREHENSIVE API ROUTE TEST');
|
|
60
|
+
console.log('══════════════════════════════════════════\n');
|
|
61
|
+
|
|
62
|
+
// ─── 1. Health ─────────────────────────────────────
|
|
63
|
+
console.log('── Health ────────────────────────────────');
|
|
64
|
+
await test('GET /health returns ok', async () => {
|
|
65
|
+
const res = await request('GET', '/health');
|
|
66
|
+
return res.status === 200 && res.body.status === 'ok'
|
|
67
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ─── 2. Auth Routes ────────────────────────────────
|
|
71
|
+
console.log('\n── Auth ──────────────────────────────────');
|
|
72
|
+
const testUser = { userName: 'testuser_' + Date.now(), password: 'test1234' };
|
|
73
|
+
let createdUser = null;
|
|
74
|
+
|
|
75
|
+
await test('POST /api/auth/register creates a user', async () => {
|
|
76
|
+
const res = await request('POST', '/api/auth/register', testUser);
|
|
77
|
+
if (res.status === 201 && res.body.success) {
|
|
78
|
+
createdUser = res.body.data;
|
|
79
|
+
return { pass: true };
|
|
80
|
+
}
|
|
81
|
+
return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await test('POST /api/auth/register rejects duplicate username', async () => {
|
|
85
|
+
const res = await request('POST', '/api/auth/register', testUser);
|
|
86
|
+
return res.status === 409
|
|
87
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await test('POST /api/auth/login succeeds', async () => {
|
|
91
|
+
const res = await request('POST', '/api/auth/login', testUser);
|
|
92
|
+
return res.status === 200 && res.body.success
|
|
93
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await test('POST /api/auth/login rejects bad password', async () => {
|
|
97
|
+
const res = await request('POST', '/api/auth/login', { userName: testUser.userName, password: 'wrong' });
|
|
98
|
+
return res.status === 401
|
|
99
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await test('GET /api/auth/me returns logged-in user', async () => {
|
|
103
|
+
const res = await request('GET', '/api/auth/me');
|
|
104
|
+
return res.status === 200 && res.body.data && res.body.data.user
|
|
105
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ─── 3. Employee Routes ────────────────────────────
|
|
109
|
+
console.log('\n── Employees ─────────────────────────────');
|
|
110
|
+
let createdEmployeeId = null;
|
|
111
|
+
|
|
112
|
+
await test('GET /api/employees lists employees', async () => {
|
|
113
|
+
const res = await request('GET', '/api/employees');
|
|
114
|
+
if (res.status === 200 && res.body.success) {
|
|
115
|
+
if (res.body.data.employees.length > 0) createdEmployeeId = res.body.data.employees[0]._id;
|
|
116
|
+
return { pass: true };
|
|
117
|
+
}
|
|
118
|
+
return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await test('POST /api/employees creates employee', async () => {
|
|
122
|
+
const res = await request('POST', '/api/employees', {
|
|
123
|
+
empFirstName: 'Test', empLastName: 'User',
|
|
124
|
+
empGender: 'male', empDateOfBirth: '1990-01-01',
|
|
125
|
+
empEmail: 'test@test.com', empTelephone: '0788000000',
|
|
126
|
+
empHireDate: '2024-01-01', empStatus: 'on mission',
|
|
127
|
+
});
|
|
128
|
+
if (res.status === 201 && res.body.success) {
|
|
129
|
+
createdEmployeeId = res.body.data._id;
|
|
130
|
+
return { pass: true };
|
|
131
|
+
}
|
|
132
|
+
return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await test('GET /api/employees/:id returns employee', async () => {
|
|
136
|
+
if (!createdEmployeeId) return { pass: false, reason: 'No employee ID' };
|
|
137
|
+
const res = await request('GET', `/api/employees/${createdEmployeeId}`);
|
|
138
|
+
return res.status === 200 && res.body.success
|
|
139
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
await test('PUT /api/employees/:id updates employee', async () => {
|
|
143
|
+
if (!createdEmployeeId) return { pass: false, reason: 'No employee ID' };
|
|
144
|
+
const res = await request('PUT', `/api/employees/${createdEmployeeId}`, { empFirstName: 'Updated' });
|
|
145
|
+
return res.status === 200 && res.body.success
|
|
146
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await test('DELETE /api/employees/:id deletes employee', async () => {
|
|
150
|
+
if (!createdEmployeeId) return { pass: false, reason: 'No employee ID' };
|
|
151
|
+
const res = await request('DELETE', `/api/employees/${createdEmployeeId}`);
|
|
152
|
+
return res.status === 200 && res.body.success
|
|
153
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await test('GET /api/employees?search= filters results', async () => {
|
|
157
|
+
const res = await request('GET', '/api/employees?search=alice');
|
|
158
|
+
return res.status === 200 && res.body.success
|
|
159
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// ─── 4. Department Routes ─────────────────────────
|
|
163
|
+
console.log('\n── Departments ───────────────────────────');
|
|
164
|
+
let createdDeptId = null;
|
|
165
|
+
|
|
166
|
+
await test('GET /api/departments lists departments', async () => {
|
|
167
|
+
const res = await request('GET', '/api/departments');
|
|
168
|
+
if (res.status === 200 && res.body.success) {
|
|
169
|
+
if (res.body.data.departments.length > 0) createdDeptId = res.body.data.departments[0]._id;
|
|
170
|
+
return { pass: true };
|
|
171
|
+
}
|
|
172
|
+
return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
await test('POST /api/departments creates department', async () => {
|
|
176
|
+
const res = await request('POST', '/api/departments', { departName: 'Test Dept ' + Date.now() });
|
|
177
|
+
if (res.status === 201 && res.body.success) {
|
|
178
|
+
createdDeptId = res.body.data._id;
|
|
179
|
+
return { pass: true };
|
|
180
|
+
}
|
|
181
|
+
return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
await test('GET /api/departments/:id returns department', async () => {
|
|
185
|
+
if (!createdDeptId) return { pass: false, reason: 'No dept ID' };
|
|
186
|
+
const res = await request('GET', `/api/departments/${createdDeptId}`);
|
|
187
|
+
return res.status === 200 && res.body.success
|
|
188
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await test('PUT /api/departments/:id updates department', async () => {
|
|
192
|
+
if (!createdDeptId) return { pass: false, reason: 'No dept ID' };
|
|
193
|
+
const res = await request('PUT', `/api/departments/${createdDeptId}`, { departName: 'Updated Dept' });
|
|
194
|
+
return res.status === 200 && res.body.success
|
|
195
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
await test('DELETE /api/departments/:id deletes department', async () => {
|
|
199
|
+
if (!createdDeptId) return { pass: false, reason: 'No dept ID' };
|
|
200
|
+
const res = await request('DELETE', `/api/departments/${createdDeptId}`);
|
|
201
|
+
return res.status === 200 && res.body.success
|
|
202
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// ─── 5. Position Routes ───────────────────────────
|
|
206
|
+
console.log('\n── Positions ─────────────────────────────');
|
|
207
|
+
let createdPosId = null;
|
|
208
|
+
|
|
209
|
+
await test('GET /api/positions lists positions', async () => {
|
|
210
|
+
const res = await request('GET', '/api/positions');
|
|
211
|
+
if (res.status === 200 && res.body.success) {
|
|
212
|
+
if (res.body.data.positions.length > 0) createdPosId = res.body.data.positions[0]._id;
|
|
213
|
+
return { pass: true };
|
|
214
|
+
}
|
|
215
|
+
return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
await test('POST /api/positions creates position', async () => {
|
|
219
|
+
const res = await request('POST', '/api/positions', { posName: 'Test Pos ' + Date.now(), requiredQualification: 'Test' });
|
|
220
|
+
if (res.status === 201 && res.body.success) {
|
|
221
|
+
createdPosId = res.body.data._id;
|
|
222
|
+
return { pass: true };
|
|
223
|
+
}
|
|
224
|
+
return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
await test('GET /api/positions/:id returns position', async () => {
|
|
228
|
+
if (!createdPosId) return { pass: false, reason: 'No pos ID' };
|
|
229
|
+
const res = await request('GET', `/api/positions/${createdPosId}`);
|
|
230
|
+
return res.status === 200 && res.body.success
|
|
231
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
await test('PUT /api/positions/:id updates position', async () => {
|
|
235
|
+
if (!createdPosId) return { pass: false, reason: 'No pos ID' };
|
|
236
|
+
const res = await request('PUT', `/api/positions/${createdPosId}`, { posName: 'Updated Pos' });
|
|
237
|
+
return res.status === 200 && res.body.success
|
|
238
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
await test('DELETE /api/positions/:id deletes position', async () => {
|
|
242
|
+
if (!createdPosId) return { pass: false, reason: 'No pos ID' };
|
|
243
|
+
const res = await request('DELETE', `/api/positions/${createdPosId}`);
|
|
244
|
+
return res.status === 200 && res.body.success
|
|
245
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// ─── 6. Report Routes ─────────────────────────────
|
|
249
|
+
console.log('\n── Reports ───────────────────────────────');
|
|
250
|
+
|
|
251
|
+
await test('GET /api/reports/employees-on-leave returns data', async () => {
|
|
252
|
+
const res = await request('GET', '/api/reports/employees-on-leave');
|
|
253
|
+
return res.status === 200 && res.body.success
|
|
254
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
await test('GET /api/reports/employees-on-leave/excel returns file', async () => {
|
|
258
|
+
const res = await request('GET', '/api/reports/employees-on-leave/excel');
|
|
259
|
+
return res.status === 200 && res.body.includes('workbook')
|
|
260
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// ─── 7. Auth Logout ───────────────────────────────
|
|
264
|
+
console.log('\n── Auth Logout ───────────────────────────');
|
|
265
|
+
|
|
266
|
+
await test('POST /api/auth/logout succeeds', async () => {
|
|
267
|
+
const res = await request('POST', '/api/auth/logout');
|
|
268
|
+
return res.status === 200 && res.body.success
|
|
269
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
await test('GET /api/employees fails after logout (no auth)', async () => {
|
|
273
|
+
const res = await request('GET', '/api/employees');
|
|
274
|
+
return res.status === 401
|
|
275
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status} (expected 401)` };
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// ─── 8. 404 Handler ───────────────────────────────
|
|
279
|
+
console.log('\n── 404 Handling ──────────────────────────');
|
|
280
|
+
|
|
281
|
+
await test('GET /api/nonexistent returns 404', async () => {
|
|
282
|
+
const res = await request('GET', '/api/nonexistent');
|
|
283
|
+
return res.status === 404
|
|
284
|
+
? { pass: true } : { pass: false, reason: `Got ${res.status}` };
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// ─── Summary ──────────────────────────────────────
|
|
288
|
+
console.log('\n══════════════════════════════════════════');
|
|
289
|
+
console.log(` Results: ${testsPassed} passed, ${testsFailed} failed, ${testsPassed + testsFailed} total`);
|
|
290
|
+
console.log('══════════════════════════════════════════\n');
|
|
291
|
+
process.exit(testsFailed > 0 ? 1 : 0);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
run();
|