Some checks failed
CI / verify (push) Has been cancelled
Weekday 10am Eastern cron exports all Webex Calling locations whose name starts with "Store" to a legacy-format CSV (+E164,Store XXXX), diffs against the previous baseline, and posts the file plus a change summary to a configured Webex space. - src/webex/storePhones.js: paginated GET /telephony/config/locations - src/services/storePhoneExport.js: CSV format, filename, diff, message - src/webex/messages.js: bot-token multipart POST /messages with file - src/jobs/storePhoneExport.js + cron in src/index.js - scripts/exportStorePhones.js for manual runs (npm run export-store-phones) - Persist baseline + archives under data/store-phone-export/ (gitignored) - Dockerfile: create /app/data for volume mount in prod - CI: align node-version with Dockerfile (20) Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
4.6 KiB
JavaScript
135 lines
4.6 KiB
JavaScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import {
|
|
diffStorePhones,
|
|
formatExportFilename,
|
|
formatExportMessage,
|
|
parseStorePhoneCsv,
|
|
rowsToCsv,
|
|
} from '../src/services/storePhoneExport.js';
|
|
|
|
describe('rowsToCsv / parseStorePhoneCsv', () => {
|
|
it('round-trips rows with no header', () => {
|
|
const rows = [
|
|
{ phoneNumber: '+14044708802', name: 'Store 7311' },
|
|
{ phoneNumber: '+17245537090', name: 'Store 0006 - TestLab' },
|
|
];
|
|
const csv = rowsToCsv(rows);
|
|
assert.equal(csv, '+14044708802,Store 7311\n+17245537090,Store 0006 - TestLab\n');
|
|
const map = parseStorePhoneCsv(csv);
|
|
assert.equal(map.size, 2);
|
|
assert.equal(map.get('Store 7311'), '+14044708802');
|
|
assert.equal(map.get('Store 0006 - TestLab'), '+17245537090');
|
|
});
|
|
|
|
it('returns empty string for no rows', () => {
|
|
assert.equal(rowsToCsv([]), '');
|
|
assert.equal(parseStorePhoneCsv('').size, 0);
|
|
});
|
|
|
|
it('ignores blank and malformed lines when parsing', () => {
|
|
const text = '+15551234567,Store 0499\n\nbadline\n,+1\n';
|
|
const map = parseStorePhoneCsv(text);
|
|
assert.equal(map.size, 1);
|
|
assert.equal(map.get('Store 0499'), '+15551234567');
|
|
});
|
|
});
|
|
|
|
describe('formatExportFilename', () => {
|
|
it('matches StorePhone-YYYY-MM-DDT…csv with underscores in time and offset', () => {
|
|
// Jul 15 2026 09:10:03.455 EDT = UTC-4
|
|
const date = new Date('2026-07-15T13:10:03.455Z');
|
|
const name = formatExportFilename(date, 'America/New_York');
|
|
assert.match(
|
|
name,
|
|
/^StorePhone-\d{4}-\d{2}-\d{2}T\d{2}_\d{2}_\d{2}\.\d{3}[+-]\d{2}_\d{2}\.csv$/,
|
|
);
|
|
assert.ok(name.includes('2026-07-15'), name);
|
|
assert.ok(name.includes('09_10_03'), name);
|
|
});
|
|
});
|
|
|
|
describe('diffStorePhones', () => {
|
|
const prev = new Map([
|
|
['Store 0499', '+15551111111'],
|
|
['Store 0500', '+15552222222'],
|
|
['Store 0999', '+15559999999'],
|
|
]);
|
|
|
|
it('detects added, removed, and changed rows', () => {
|
|
const curr = new Map([
|
|
['Store 0499', '+15559876543'],
|
|
['Store 0500', '+15552222222'],
|
|
['Store 7311', '+14044708802'],
|
|
]);
|
|
const diff = diffStorePhones(prev, curr);
|
|
assert.equal(diff.added.length, 1);
|
|
assert.equal(diff.added[0].name, 'Store 7311');
|
|
assert.equal(diff.removed.length, 1);
|
|
assert.equal(diff.removed[0].name, 'Store 0999');
|
|
assert.equal(diff.changed.length, 1);
|
|
assert.equal(diff.changed[0].name, 'Store 0499');
|
|
assert.equal(diff.unchangedCount, 1);
|
|
});
|
|
|
|
it('reports no changes when maps are identical', () => {
|
|
const diff = diffStorePhones(prev, new Map(prev));
|
|
assert.equal(diff.added.length, 0);
|
|
assert.equal(diff.removed.length, 0);
|
|
assert.equal(diff.changed.length, 0);
|
|
assert.equal(diff.unchangedCount, 3);
|
|
});
|
|
});
|
|
|
|
describe('formatExportMessage', () => {
|
|
const date = new Date('2026-07-15T14:00:00.000Z');
|
|
|
|
it('notes first run when there is no baseline', () => {
|
|
const msg = formatExportMessage({
|
|
date,
|
|
timeZone: 'America/New_York',
|
|
rowCount: 1081,
|
|
diff: { added: [], removed: [], changed: [], unchangedCount: 0 },
|
|
isFirstRun: true,
|
|
});
|
|
assert.match(msg, /First export/);
|
|
assert.match(msg, /1,081/);
|
|
});
|
|
|
|
it('lists changes when present', () => {
|
|
const msg = formatExportMessage({
|
|
date,
|
|
timeZone: 'America/New_York',
|
|
rowCount: 3,
|
|
isFirstRun: false,
|
|
diff: {
|
|
added: [{ name: 'Store 7311', phoneNumber: '+14044708802' }],
|
|
removed: [],
|
|
changed: [
|
|
{
|
|
name: 'Store 0499',
|
|
oldPhone: '+15551111111',
|
|
newPhone: '+15559876543',
|
|
},
|
|
],
|
|
unchangedCount: 1,
|
|
},
|
|
});
|
|
assert.match(msg, /Added \(1\)/);
|
|
assert.match(msg, /Store 7311/);
|
|
assert.match(msg, /Changed \(1\)/);
|
|
assert.match(msg, /0499/);
|
|
});
|
|
|
|
it('says no changes when diff is empty', () => {
|
|
const msg = formatExportMessage({
|
|
date,
|
|
timeZone: 'America/New_York',
|
|
rowCount: 10,
|
|
isFirstRun: false,
|
|
diff: { added: [], removed: [], changed: [], unchangedCount: 10 },
|
|
});
|
|
assert.match(msg, /No changes since last run/);
|
|
});
|
|
});
|