ServiceChannel webhook processor, proposal approval cards, attachment auto-post, CollabSupport commands, and Docker deployment configuration. Co-authored-by: Cursor <cursoragent@cursor.com>
153 lines
4.7 KiB
JavaScript
153 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Smoke-test CollabSupport HTTP endpoints used by ServChan.
|
||
*
|
||
* Usage:
|
||
* CS_API_BASE=https://bot.joesjavajoint.com/CollabSupport \
|
||
* SMOKE_WO_ID=356369551 SMOKE_STORE_NUM=2254 \
|
||
* node scripts/smoke-collab-support.js
|
||
*/
|
||
|
||
import 'dotenv/config';
|
||
|
||
const base = (process.env.CS_API_BASE_INTERNAL || process.env.CS_API_BASE || '').replace(/\/+$/, '');
|
||
const woId = process.env.SMOKE_WO_ID || '356369551';
|
||
const storeNum = process.env.SMOKE_STORE_NUM || '2254';
|
||
const timeoutMs = Number(process.env.SMOKE_TIMEOUT_MS) || 30_000;
|
||
|
||
const USAGE_MARKERS = [
|
||
'**Work Order Summary Usage:**',
|
||
'**Work Order History Usage:**',
|
||
'Please provide a 2–4 digit store number.',
|
||
];
|
||
|
||
function looksLikeUsage(text) {
|
||
return USAGE_MARKERS.some((m) => text.includes(m));
|
||
}
|
||
|
||
async function fetchCheck(label, url, { expectJson = false, validate, expectStatus } = {}) {
|
||
const started = Date.now();
|
||
const controller = new AbortController();
|
||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||
try {
|
||
const res = await fetch(url, {
|
||
signal: controller.signal,
|
||
headers: expectJson ? { Accept: 'application/json' } : undefined,
|
||
});
|
||
const elapsed = Date.now() - started;
|
||
const body = expectJson ? await res.json() : await res.text();
|
||
|
||
const issues = [];
|
||
if (expectStatus !== undefined) {
|
||
if (res.status !== expectStatus) issues.push(`expected HTTP ${expectStatus}, got ${res.status}`);
|
||
} else if (!res.ok) {
|
||
issues.push(`HTTP ${res.status}`);
|
||
}
|
||
if (expectStatus === undefined && !expectJson && typeof body === 'string' && body.trim().length === 0) {
|
||
issues.push('empty markdown body');
|
||
}
|
||
if (expectStatus === undefined && !expectJson && typeof body === 'string' && looksLikeUsage(body)) {
|
||
issues.push('looks like usage/help text, not data');
|
||
}
|
||
if (validate) {
|
||
const v = validate(body, res);
|
||
if (v) issues.push(v);
|
||
}
|
||
|
||
const pass = issues.length === 0;
|
||
console.log(`${pass ? 'PASS' : 'FAIL'} ${label}`);
|
||
console.log(` ${url}`);
|
||
console.log(` → ${res.status} (${elapsed}ms)${issues.length ? ` — ${issues.join('; ')}` : ''}`);
|
||
return pass;
|
||
} catch (err) {
|
||
const elapsed = Date.now() - started;
|
||
const msg = err.name === 'AbortError' ? 'timeout' : err.message;
|
||
console.log(`FAIL ${label}`);
|
||
console.log(` ${url}`);
|
||
console.log(` → error (${elapsed}ms): ${msg}`);
|
||
return false;
|
||
} finally {
|
||
clearTimeout(timer);
|
||
}
|
||
}
|
||
|
||
async function main() {
|
||
if (!base) {
|
||
console.error('CS_API_BASE is required (e.g. https://bot.joesjavajoint.com/CollabSupport)');
|
||
process.exit(1);
|
||
}
|
||
|
||
console.log(`CollabSupport smoke test → ${base}`);
|
||
console.log(`WO=${woId} store=${storeNum}\n`);
|
||
|
||
const results = [];
|
||
|
||
results.push(await fetchCheck(
|
||
'wosummary',
|
||
`${base}/wosummary?woId=${encodeURIComponent(woId)}`,
|
||
{
|
||
validate: (body) => body.includes('Work Order Summary') ? null : 'missing summary header',
|
||
}
|
||
));
|
||
|
||
results.push(await fetchCheck(
|
||
'wohistory (summary)',
|
||
`${base}/wohistory?storeNum=${encodeURIComponent(storeNum)}`,
|
||
{
|
||
validate: (body) => body.includes('Work Order History') ? null : 'missing history header',
|
||
}
|
||
));
|
||
|
||
results.push(await fetchCheck(
|
||
'wohistory (detailed)',
|
||
`${base}/wohistory?storeNum=${encodeURIComponent(storeNum)}&mode=detailed`,
|
||
{
|
||
validate: (body) => body.includes('Work Order History') ? null : 'missing history header',
|
||
}
|
||
));
|
||
|
||
results.push(await fetchCheck(
|
||
'avStatus (summary)',
|
||
`${base}/avStatus?storeNum=${encodeURIComponent(storeNum)}`,
|
||
{
|
||
validate: (body) => (body.length > 50 ? null : 'response too short'),
|
||
}
|
||
));
|
||
|
||
results.push(await fetchCheck(
|
||
'avStatus (detailed)',
|
||
`${base}/avStatus?storeNum=${encodeURIComponent(storeNum)}&mode=detailed`,
|
||
{
|
||
validate: (body) => (body.length > 50 ? null : 'response too short'),
|
||
}
|
||
));
|
||
|
||
results.push(await fetchCheck(
|
||
'woAttachments (JSON)',
|
||
`${base}/woAttachments?woId=${encodeURIComponent(woId)}`,
|
||
{
|
||
expectJson: true,
|
||
validate: (data) => {
|
||
if (typeof data.success !== 'boolean') return 'missing success field';
|
||
if (typeof data.count !== 'number') return 'missing count field';
|
||
if (!Array.isArray(data.attachments)) return 'missing attachments array';
|
||
return null;
|
||
},
|
||
}
|
||
));
|
||
|
||
// Validation endpoint — should 400
|
||
results.push(await fetchCheck(
|
||
'wosummary invalid (expect 400)',
|
||
`${base}/wosummary?woId=abc`,
|
||
{ expectStatus: 400 }
|
||
));
|
||
|
||
const passed = results.filter(Boolean).length;
|
||
const total = results.length;
|
||
console.log(`\n${passed}/${total} checks passed`);
|
||
|
||
process.exit(passed === total ? 0 : 1);
|
||
}
|
||
|
||
main();
|