Adds sanitizeCell(): cell values starting with = + - @ or a control char are prefixed with an apostrophe so spreadsheets render them as literal text instead of executing them as formulas. Applied to the names column. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
3.2 KiB
JavaScript
86 lines
3.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { buildScheduleColumns, sanitizeCell } from '../js/excel.js';
|
|
|
|
const employees = [
|
|
{ id: 'a1', name: 'Anna Müller' },
|
|
{ id: 'b1', name: 'Bernd Schulz' },
|
|
{ id: 'c1', name: 'Carla Weber' }
|
|
];
|
|
|
|
// Single employee per day
|
|
{
|
|
const assignments = [
|
|
{ date: '2026-04-01', employeeId: 'a1' },
|
|
{ date: '2026-04-02', employeeId: 'b1' }
|
|
];
|
|
const cols = buildScheduleColumns(assignments, employees);
|
|
assert.deepEqual(cols.names, ['Anna Müller', 'Bernd Schulz'], 'names per single-entry day');
|
|
assert.deepEqual(cols.dates, ['01.04.2026', '02.04.2026'], 'dates formatted DD.MM.YYYY');
|
|
}
|
|
|
|
// Two employees per day → joined with " & ", alphabetical
|
|
{
|
|
const assignments = [
|
|
{ date: '2026-04-01', employeeId: 'b1' }, // Bernd inserted first
|
|
{ date: '2026-04-01', employeeId: 'a1' } // Anna inserted second
|
|
];
|
|
const cols = buildScheduleColumns(assignments, employees);
|
|
assert.equal(cols.names.length, 1, 'one column per date');
|
|
assert.equal(cols.names[0], 'Anna Müller & Bernd Schulz', 'names alphabetical, joined with " & "');
|
|
assert.equal(cols.dates[0], '01.04.2026', 'one date entry');
|
|
}
|
|
|
|
// Three employees per day
|
|
{
|
|
const assignments = [
|
|
{ date: '2026-04-01', employeeId: 'c1' },
|
|
{ date: '2026-04-01', employeeId: 'a1' },
|
|
{ date: '2026-04-01', employeeId: 'b1' }
|
|
];
|
|
const cols = buildScheduleColumns(assignments, employees);
|
|
assert.equal(cols.names[0], 'Anna Müller & Bernd Schulz & Carla Weber', 'three names joined alphabetical');
|
|
}
|
|
|
|
// Multiple dates with multiple employees, dates ascending
|
|
{
|
|
const assignments = [
|
|
{ date: '2026-04-02', employeeId: 'a1' },
|
|
{ date: '2026-04-01', employeeId: 'b1' },
|
|
{ date: '2026-04-01', employeeId: 'a1' }
|
|
];
|
|
const cols = buildScheduleColumns(assignments, employees);
|
|
assert.deepEqual(cols.dates, ['01.04.2026', '02.04.2026'], 'dates sorted ascending');
|
|
assert.deepEqual(cols.names, ['Anna Müller & Bernd Schulz', 'Anna Müller'], 'names per column in date order');
|
|
}
|
|
|
|
// Unknown employeeId: empty string slot, sorted with other names
|
|
{
|
|
const assignments = [
|
|
{ date: '2026-04-01', employeeId: 'a1' },
|
|
{ date: '2026-04-01', employeeId: 'unknown' }
|
|
];
|
|
const cols = buildScheduleColumns(assignments, employees);
|
|
// Empty-string sorts before 'Anna Müller'
|
|
assert.equal(cols.names[0], ' & Anna Müller', 'unknown employee → empty string in join');
|
|
}
|
|
|
|
// sanitizeCell: formula-triggering leading chars get an apostrophe prefix
|
|
{
|
|
assert.equal(sanitizeCell('=1+1'), "'=1+1", 'leading = neutralized');
|
|
assert.equal(sanitizeCell('+cmd'), "'+cmd", 'leading + neutralized');
|
|
assert.equal(sanitizeCell('-2'), "'-2", 'leading - neutralized');
|
|
assert.equal(sanitizeCell('@x'), "'@x", 'leading @ neutralized');
|
|
assert.equal(sanitizeCell('Anna Müller'), 'Anna Müller', 'normal name unchanged');
|
|
}
|
|
|
|
// buildScheduleColumns: a malicious name is sanitized in the output cell
|
|
{
|
|
const cols = buildScheduleColumns(
|
|
[{ date: '2026-04-01', employeeId: 'x' }],
|
|
[{ id: 'x', name: '=HYPERLINK("http://evil")' }]
|
|
);
|
|
assert.ok(cols.names[0].startsWith("'="), 'formula name prefixed with apostrophe');
|
|
}
|
|
|
|
console.log('All excel tests passed.');
|