Dataforth (projects/dataforth-dos/): - UI feature: row coloring + PUSH/RE-PUSH buttons + Website Status filter - Database dedup to one row per SN (2.89M -> 469K rows, UNIQUE constraint added) - Import logic handles FAIL -> PASS retest transition - Refactored upload-to-api.js to render datasheets in-memory (dropped For_Web filesystem dep) - Bulk pushed 170,984 records to Hoffman API - Statistical sanity check: 100/100 stamped SNs verified on Hoffman GuruRMM (projects/msp-tools/guru-rmm/): - ROADMAP.md: added Terminology (5-tier hierarchy), Tunnel Channels Phase 2, Logging/Audit/Observability, Multi-tenancy, Modular Architecture, Protocol Versioning, Certificates sections + Decisions Log - CONTEXT.md: hierarchy table, new anti-patterns (bootstrap sacred, no cross-module imports), revised next-steps priorities Session logs for both projects. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
80 lines
3.4 KiB
JavaScript
80 lines
3.4 KiB
JavaScript
const fs = require('fs');
|
|
const https = require('https');
|
|
const { URL } = require('url');
|
|
const db = require('./db');
|
|
const CREDS = JSON.parse(fs.readFileSync('C:/ProgramData/dataforth-uploader/credentials.json', 'utf8'));
|
|
|
|
function req(method, uri, headers) {
|
|
return new Promise((res, rej) => {
|
|
const u = new URL(uri);
|
|
const r = https.request({
|
|
hostname: u.hostname, port: u.port || 443, path: u.pathname + u.search,
|
|
method, headers, timeout: 20000,
|
|
}, rs => {
|
|
let d = '';
|
|
rs.on('data', c => d += c);
|
|
rs.on('end', () => res({ status: rs.statusCode, body: d }));
|
|
});
|
|
const t = setTimeout(() => { r.destroy(); rej(new Error('timeout')); }, 20000);
|
|
r.on('error', rej);
|
|
r.on('close', () => clearTimeout(t));
|
|
r.end();
|
|
});
|
|
}
|
|
|
|
(async () => {
|
|
const form = 'grant_type=client_credentials&client_id=' + encodeURIComponent(CREDS.CF_CLIENT_ID) +
|
|
'&client_secret=' + encodeURIComponent(CREDS.CF_CLIENT_SECRET) + '&scope=' + encodeURIComponent(CREDS.CF_SCOPE);
|
|
const tokR = await new Promise((r, j) => {
|
|
const u = new URL(CREDS.CF_TOKEN_URL);
|
|
const rq = https.request({
|
|
hostname: u.hostname, port: 443, path: u.pathname, method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(form) },
|
|
}, rs => {
|
|
let d = '';
|
|
rs.on('data', c => d += c);
|
|
rs.on('end', () => r({ status: rs.statusCode, body: d }));
|
|
});
|
|
rq.on('error', j);
|
|
rq.write(form);
|
|
rq.end();
|
|
});
|
|
const token = JSON.parse(tokR.body).access_token;
|
|
|
|
async function sample(label, sql, expect) {
|
|
console.log('=== ' + label + ' ===');
|
|
const rows = await db.query(sql);
|
|
let hit = 0, miss = 0, err = 0;
|
|
for (const r of rows) {
|
|
try {
|
|
const rr = await req('GET',
|
|
CREDS.CF_API_BASE + '/api/v1/TestReportDataFiles/' + encodeURIComponent(r.serial_number),
|
|
{ 'Authorization': 'Bearer ' + token });
|
|
if (rr.status === 200) hit++;
|
|
else if (rr.status === 404) miss++;
|
|
else { err++; console.log(' HTTP ' + rr.status + ' ' + r.serial_number); }
|
|
} catch (e) { err++; console.log(' ERR ' + r.serial_number + ' ' + e.message); }
|
|
}
|
|
console.log(' hit=' + hit + ' miss=' + miss + ' err=' + err + ' (' + expect + ')');
|
|
return { hit, miss, err };
|
|
}
|
|
|
|
await sample(
|
|
'Sample 1: 100 random stamped api_uploaded_at IS NOT NULL',
|
|
"SELECT serial_number FROM test_records WHERE api_uploaded_at IS NOT NULL ORDER BY random() LIMIT 100",
|
|
'expect hit=100',
|
|
);
|
|
await sample(
|
|
'Sample 2: 100 random unpushable PASS (NULL api_uploaded_at, PASS)',
|
|
"SELECT serial_number FROM test_records WHERE api_uploaded_at IS NULL AND overall_result='PASS' ORDER BY random() LIMIT 100",
|
|
'expect mostly miss (these are the 10K unpushables)',
|
|
);
|
|
await sample(
|
|
'Sample 3: 50 random FAIL',
|
|
"SELECT serial_number FROM test_records WHERE overall_result='FAIL' ORDER BY random() LIMIT 50",
|
|
'expect miss=50 (FAILs never reach Hoffman)',
|
|
);
|
|
|
|
await db.close();
|
|
})().catch(e => { console.error('FATAL', e.message); process.exit(1); });
|