-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_code_review.js
More file actions
430 lines (366 loc) · 16.4 KB
/
Copy pathtest_code_review.js
File metadata and controls
430 lines (366 loc) · 16.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
/**
* 代码审查修复验证测试
* 验证20个问题修复的正确性
*/
const assert = require('assert');
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(` ✅ ${name}`);
passed++;
} catch (e) {
console.error(` ❌ ${name}: ${e.message}`);
failed++;
}
}
console.log('=== 代码审查修复验证测试 ===\n');
// ===== 问题2: UTC时区修复 =====
console.log('问题2: UTC时区修复');
test('workReport.js fmtLocalDate 返回本地日期', () => {
// 模workReport.js的fmtLocalDate函数
function fmtLocalDate(d) {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
}
const now = new Date();
const localDate = fmtLocalDate(now);
const utcDate = now.toISOString().split('T')[0];
// 本地日期格式正确
assert.match(localDate, /^\d{4}-\d{2}-\d{2}$/, '日期格式应为YYYY-MM-DD');
// 在UTC+8凌晨0-8点,本地日期应不同于UTC日期
// 至少验证本地日期是正确的当天
const expectedDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
assert.strictEqual(localDate, expectedDate, 'fmtLocalDate应返回本地日期');
});
// ===== 问题5: 状态字段使用key =====
console.log('\n问题5: 状态字段使用key');
test('schedule.js STATUS_KEYS 包含正确的UUID', () => {
const STATUS_KEYS = {
待生产: 'df12e87a-8e6a-457b-bcf8-50b35cafa072',
进行中: '0dfe36be-3f89-449d-b2e4-0a64fc1d1698',
已完成: 'f18c8c5d-97bc-46e8-9e2a-ac7acf98ea9e',
已暂停: 'a9bf20fc-f56b-4fd2-8818-53a48b491baf',
已失效: '54499633-8500-4a02-a66d-d7161509471d'
};
// 验证每个key是UUID格式
for (const [name, key] of Object.entries(STATUS_KEYS)) {
assert.match(key, /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, `${name}的key应为UUID格式`);
}
// 验证"待生产"不是字符串值
assert.notStrictEqual(STATUS_KEYS.待生产, '待生产', '待生产应使用UUID而非字符串');
});
// ===== 问题9: 换线次数计算 =====
console.log('\n问题9: 换线次数计算');
test('换线次数统计同一产线上产品/工序切换', () => {
// 模拟scheduler.js的换线次数计算逻辑
const schedules = [
{ lineId: 'L1', productName: '产品A', processName: 'SMT', date: '2026-07-25' },
{ lineId: 'L1', productName: '产品A', processName: 'SMT', date: '2026-07-26' },
{ lineId: 'L1', productName: '产品B', processName: 'SMT', date: '2026-07-27' }, // 换线
{ lineId: 'L1', productName: '产品B', processName: 'AOI', date: '2026-07-28' }, // 换线(工序变)
{ lineId: 'L2', productName: '产品C', processName: 'PACK', date: '2026-07-25' },
{ lineId: 'L2', productName: '产品C', processName: 'PACK', date: '2026-07-26' },
];
let changeoverCount = 0;
const lineSchedules = {};
schedules.forEach(s => {
if (!lineSchedules[s.lineId]) lineSchedules[s.lineId] = [];
lineSchedules[s.lineId].push(s);
});
for (const lineKey in lineSchedules) {
const lineScheds = lineSchedules[lineKey].sort((a, b) => a.date.localeCompare(b.date));
let prevProduct = '';
let prevProcess = '';
for (const s of lineScheds) {
if (prevProduct && (s.productName !== prevProduct || s.processName !== prevProcess)) {
changeoverCount++;
}
prevProduct = s.productName;
prevProcess = s.processName;
}
}
assert.strictEqual(changeoverCount, 2, `应有2次换线,实际${changeoverCount}`);
});
// ===== 问题10: db.run 仅INSERT查询last_insert_rowid =====
console.log('\n问题10: db.run 仅INSERT查询last_insert_rowid');
test('非INSERT语句不查询last_insert_rowid', () => {
// 验证SQL类型检测逻辑
function isInsertSql(sql) {
return /^\s*INSERT\s/i.test(sql);
}
assert.ok(isInsertSql('INSERT INTO foo VALUES (?)'), 'INSERT应检测为true');
assert.ok(isInsertSql(' INSERT INTO foo VALUES (?)'), '带前导空格的INSERT应检测为true');
assert.ok(!isInsertSql('UPDATE foo SET x=1'), 'UPDATE应检测为false');
assert.ok(!isInsertSql('DELETE FROM foo'), 'DELETE应检测为false');
assert.ok(!isInsertSql('CREATE TABLE IF NOT EXISTS foo (id INT)'), 'CREATE TABLE应检测为false');
});
// ===== 问题15: 仿真负数日期clamp =====
console.log('\n问题15: 仿真负数日期clamp');
test('deliveryDay被clamp为非负数', () => {
// 模拟simulator.js的clamp逻辑
function dateToDay(dateStr, baseDate) {
return Math.round((new Date(dateStr) - new Date(baseDate)) / 86400000);
}
const baseDate = '2026-07-25';
const earlyDelivery = '2026-07-20'; // 早于baseDate,会产出负数
const rawDeliveryDay = dateToDay(earlyDelivery, baseDate);
const deliveryDay = Math.max(0, rawDeliveryDay);
assert.ok(rawDeliveryDay < 0, '原始deliveryDay应为负数');
assert.strictEqual(deliveryDay, 0, 'clamp后应为0');
});
// ===== 问题16: 逆向排产逻辑 =====
console.log('\n问题16: 逆向排产逻辑');
test('逆向排产逾期时退化为正向', () => {
const direction = 'backward';
const today = '2026-07-25';
const deliveryDate = '2026-07-20'; // 已逾期
const effectiveDirection = (direction === 'backward' && deliveryDate < today) ? 'forward' : direction;
assert.strictEqual(effectiveDirection, 'forward', '逾期时应退化为forward');
});
test('逆向排产未逾期时保持backward', () => {
const direction = 'backward';
const today = '2026-07-25';
const deliveryDate = '2026-08-25'; // 未逾期
const effectiveDirection = (direction === 'backward' && deliveryDate < today) ? 'forward' : direction;
assert.strictEqual(effectiveDirection, 'backward', '未逾期时应保持backward');
});
test('逆向排产后续工序开始日期为前道工序前一天', () => {
const stepFirstDoneDate = '2026-08-10';
// 模拟addDays
function addDays(dateStr, n) {
const d = new Date(dateStr);
d.setDate(d.getDate() + n);
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}
const effectiveDirection = 'backward';
const dayBeforePrev = addDays(stepFirstDoneDate, -1);
assert.strictEqual(dayBeforePrev, '2026-08-09', '前一天应为2026-08-09');
});
// ===== 问题17: 产能利用率分母修正 =====
console.log('\n问题17: 产能利用率分母修正');
test('分母仅统计被排产的产线-工序组合的产能', () => {
const schedules = [
{ lineId: 'L1', processId: 'P1', date: '2026-07-25', dailyQty: 80 },
{ lineId: 'L1', processId: 'P1', date: '2026-07-26', dailyQty: 90 },
];
const calMap = {
'L1_P1_2026-07-25': { effectiveCapacity: 100, isWorkday: 'work' },
'L1_P1_2026-07-26': { effectiveCapacity: 100, isWorkday: 'work' },
'L2_P2_2026-07-25': { effectiveCapacity: 100, isWorkday: 'work' }, // 未被排产
};
const HOLIDAY_KEY = '3583037c-9312-4d3c-8b4a-927f69dcbf23';
let totalCapacity = 0;
let totalScheduled = 0;
const scheduledLineProcessDates = new Set();
schedules.forEach(s => {
totalScheduled += s.dailyQty;
scheduledLineProcessDates.add(`${s.lineId}_${s.processId}_${s.date}`);
});
for (const key of scheduledLineProcessDates) {
const cal = calMap[key];
if (cal && cal.isWorkday !== HOLIDAY_KEY) {
totalCapacity += cal.effectiveCapacity || 0;
}
}
// 分母应只包含L1_P1两天的产能(200),不含L2_P2
assert.strictEqual(totalCapacity, 200, `分母应为200,实际${totalCapacity}`);
assert.strictEqual(totalScheduled, 170, `分子应为170,实际${totalScheduled}`);
const util = totalCapacity > 0 ? totalScheduled / totalCapacity : 0;
assert.ok(util > 0.8 && util < 0.9, `利用率应在80%-90%之间,实际${(util * 100).toFixed(1)}%`);
});
// ===== 问题18: 去重key包含lplanRowId =====
console.log('\n问题18: 去重key包含lplanRowId');
test('同天同产线同工序不同lplanRowId不被去重', () => {
const activeSet = new Set();
// 第一条记录
const key1 = `L1_P1_2026-07-25_lplan001`;
activeSet.add(key1);
// 第二条记录(同天同产线同工序,但不同lplanRowId=不同产品)
const key2 = `L1_P1_2026-07-25_lplan002`;
const isDuplicate = activeSet.has(key2);
assert.ok(!isDuplicate, '不同lplanRowId的记录不应被去重');
});
// ===== 问题7: XSS转义 =====
console.log('\n问题7: XSS转义');
test('escapeHtml正确转义特殊字符', () => {
function escapeHtml(str) {
if (str == null) return '';
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
assert.strictEqual(escapeHtml('<script>alert(1)</script>'), '<script>alert(1)</script>');
assert.strictEqual(escapeHtml('正常文本'), '正常文本');
assert.strictEqual(escapeHtml('"引号"'), '"引号"');
assert.strictEqual(escapeHtml(null), '');
assert.strictEqual(escapeHtml(undefined), '');
});
// ===== 问题1: 报工状态校验 =====
console.log('\n问题1: 报工状态校验');
test('已完成的记录不允许报工', () => {
// 模拟后端校验逻辑
const dschedRow = { status: '已完成', completed_qty: 100 };
let rejected = false;
if (dschedRow && dschedRow.status === '已完成') {
rejected = true;
}
assert.ok(rejected, '已完成状态应被拒绝');
});
test('非已完成的记录允许报工', () => {
const dschedRow = { status: '待生产', completed_qty: 0 };
let rejected = false;
if (dschedRow && dschedRow.status === '已完成') {
rejected = true;
}
assert.ok(!rejected, '待生产状态应允许报工');
});
// ===== 问题8: 鉴权 =====
console.log('\n问题8: 鉴权');
test('未配置Token时跳过鉴权', () => {
const token = ''; // 未配置
let nextCalled = false;
let statusSent = null;
if (!token) {
nextCalled = true; // 跳过鉴权
}
assert.ok(nextCalled, '未配置Token时应跳过鉴权');
});
test('配置Token后校验', () => {
const token = 'my-secret-token';
const providedToken = 'wrong-token';
let authorized = false;
if (providedToken === token) {
authorized = true;
}
assert.ok(!authorized, '错误Token应拒绝');
});
// ===== N1: db.js saveSync 真正同步写入 =====
console.log('\nN1: db.js saveSync 真正同步写入');
test('saveSync应使用writeFileSync而非writeFile', () => {
const fs = require('fs');
// 验证 fs.writeFileSync 和 fs.writeFile 都是函数
assert.strictEqual(typeof fs.writeFileSync, 'function', 'fs.writeFileSync应为函数');
assert.strictEqual(typeof fs.writeFile, 'function', 'fs.writeFile应为函数');
// writeFileSync 比 writeFile 少一个回调参数
assert.ok(fs.writeFileSync.length < fs.writeFile.length,
`writeFileSync参数应少于writeFile,实际${fs.writeFileSync.length} vs ${fs.writeFile.length}`);
});
test('doSaveSync代码存在writeFileSync调用', () => {
// 读取db.js源码验证
const fs = require('fs');
const path = require('path');
const dbSource = fs.readFileSync(
path.join(__dirname, 'src', 'engine', 'db.js'),
'utf-8'
);
assert.ok(dbSource.includes('function doSaveSync'), 'doSaveSync函数应存在');
assert.ok(dbSource.includes('fs.writeFileSync'), '应使用fs.writeFileSync同步写入');
assert.ok(dbSource.includes('function saveSync'), 'saveSync函数应存在');
assert.ok(/saveSync\(\)\s*{[\s\S]*?doSaveSync\(\)/.test(dbSource), 'saveSync应调用doSaveSync');
// 验证异步doSave仍使用writeFile(带回调)
assert.ok(/function doSave\(\)[\s\S]*?fs\.writeFile/.test(dbSource), '异步doSave应仍使用fs.writeFile');
});
// ===== N2: workReport.js 旧方式报工 options 一致性 =====
console.log('\nN2: workReport.js 旧方式报工 options 一致性');
test('旧方式报工使用forward策略而非空options', () => {
const fs = require('fs');
const path = require('path');
const source = fs.readFileSync(
path.join(__dirname, 'src', 'routes', 'workReport.js'),
'utf-8'
);
// 验证不再有空options调用
assert.ok(!/scheduler\.scheduleAll\(\s*\{\s*\}\s*\)/.test(source), '不应再有空options调用');
// 验证两处都使用forward策略
const matches = source.match(/scheduler\.scheduleAll\(\s*\{\s*strategies:\s*\['forward'\]\s*\}\s*\)/g);
assert.ok(matches && matches.length >= 2, `应至少有2处forward策略调用,实际${matches ? matches.length : 0}`);
});
// ===== N3: settings.js /test 接口鉴权 =====
console.log('\nN3: settings.js /test 接口鉴权');
test('/test接口添加requireAuth中间件', () => {
const fs = require('fs');
const path = require('path');
const source = fs.readFileSync(
path.join(__dirname, 'src', 'routes', 'settings.js'),
'utf-8'
);
// 验证 /test 路由使用了 requireAuth
assert.ok(/router\.post\(['"]\/test['"],\s*requireAuth/.test(source), '/test应使用requireAuth中间件');
// 验证 requireAuth 函数定义存在
assert.ok(/function\s+requireAuth\s*\(/.test(source), 'requireAuth函数应存在');
// 验证 requireAuth 逻辑:未配置token时跳过,配置后校验
assert.ok(/if\s*\(!token\)\s*return\s*next\(\)/.test(source), '未配置token时应跳过鉴权');
});
// ===== N4: dashboard /capacity 性能优化 =====
console.log('\nN4: dashboard /capacity 性能优化');
test('capacity接口先过滤再构建utilMap', () => {
const fs = require('fs');
const path = require('path');
const source = fs.readFileSync(
path.join(__dirname, 'src', 'routes', 'dashboard.js'),
'utf-8'
);
// 验证存在先过滤的逻辑
assert.ok(/let filteredSchedules = schedules/.test(source), '应定义filteredSchedules变量');
assert.ok(/if\s*\(\s*type\s*===\s*['"]line['"]\s*&&\s*id\s*\)/.test(source), '应先按line+id过滤');
// 验证不再有重建utilMap的代码
assert.ok(!/Object\.keys\(utilMap\)\.forEach/.test(source), '不应再有重建utilMap的代码');
assert.ok(!/Object\.assign\(utilMap,\s*newUtilMap\)/.test(source), '不应再有Object.assign替换utilMap');
// 验证utilMap构建使用filteredSchedules而非schedules
assert.ok(/filteredSchedules\.forEach\(s\s*=>/.test(source), 'utilMap应基于filteredSchedules构建');
});
test('capacity接口功能等价性验证', () => {
// 模拟优化后的逻辑,验证结果正确
const schedules = [
{ line_id: 'L1', line_name: '产线1', process_name: 'SMT', schedule_date: '2026-07-25', daily_qty: 80, daily_capacity: 100 },
{ line_id: 'L1', line_name: '产线1', process_name: 'SMT', schedule_date: '2026-07-26', daily_qty: 90, daily_capacity: 100 },
{ line_id: 'L1', line_name: '产线1', process_name: 'AOI', schedule_date: '2026-07-25', daily_qty: 70, daily_capacity: 100 },
{ line_id: 'L2', line_name: '产线2', process_name: 'SMT', schedule_date: '2026-07-25', daily_qty: 60, daily_capacity: 100 },
];
const type = 'line';
const id = 'L1';
// 优化后逻辑:先过滤
let filteredSchedules = schedules;
if (type === 'line' && id) {
filteredSchedules = schedules.filter(s => s.line_id === id);
}
const utilMap = {};
filteredSchedules.forEach(s => {
if (!utilMap[s.schedule_date]) utilMap[s.schedule_date] = {};
const key = type === 'line' ? s.process_name : s.line_name;
if (!utilMap[s.schedule_date][key]) utilMap[s.schedule_date][key] = { qty: 0, cap: 0 };
utilMap[s.schedule_date][key].qty += s.daily_qty || 0;
if (s.daily_capacity > 0 && utilMap[s.schedule_date][key].cap === 0) {
utilMap[s.schedule_date][key].cap = s.daily_capacity;
}
});
// 验证只包含L1的数据
assert.strictEqual(filteredSchedules.length, 3, '过滤后应剩3条L1记录');
// 验证2026-07-25有SMT和AOI两个工序
assert.ok(utilMap['2026-07-25'].SMT, '7-25应有SMT');
assert.ok(utilMap['2026-07-25'].AOI, '7-25应有AOI');
assert.strictEqual(utilMap['2026-07-25'].SMT.qty, 80, 'SMT qty应为80');
assert.strictEqual(utilMap['2026-07-25'].AOI.qty, 70, 'AOI qty应为70');
// 验证2026-07-26只有SMT
assert.ok(utilMap['2026-07-26'].SMT, '7-26应有SMT');
assert.ok(!utilMap['2026-07-26'].AOI, '7-26不应有AOI');
// 验证不包含L2的数据
assert.ok(!utilMap['2026-07-25']['产线2'], '不应包含L2数据');
});
// ===== 结果汇总 =====
console.log('\n=== 测试结果 ===');
console.log(`通过: ${passed} 失败: ${failed} 总计: ${passed + failed}`);
if (failed > 0) {
console.error('\n❌ 有测试失败,请检查修复');
process.exit(1);
} else {
console.log('\n✅ 全部测试通过');
process.exit(0);
}