Feat v0.7.1 surface runner framework (#55)
All checks were successful
All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #55.
This commit is contained in:
@@ -1,27 +1,38 @@
|
||||
/**
|
||||
* SDK Test Runner — Framework
|
||||
*
|
||||
* Dual-path validation: every test runs through the SDK first. If it
|
||||
* fails, a raw fetch against the ICD-specified endpoint isolates the
|
||||
* fault:
|
||||
* Dual-path validation utilities. Each domain file registers suites
|
||||
* directly with sw.testing.suite(). This module provides:
|
||||
*
|
||||
* SDK pass → PASS (both layers good)
|
||||
* SDK fail + ICD pass → SDK_BUG (wrong method/path/unwrap)
|
||||
* SDK fail + ICD fail → ICD_BUG (backend broken)
|
||||
* Validate fail → SHAPE_BUG (response doesn't match ICD contract)
|
||||
* T.dualTest — run SDK + raw ICD, return verdict
|
||||
* T.raw — raw ICD fetch helpers
|
||||
* T.assert* — assertion utilities
|
||||
* T.unwrapList — envelope unwrapper
|
||||
* T.getAuthToken / T.safeDelete / T.registerCleanup / T.runCleanup
|
||||
*
|
||||
* Verdicts:
|
||||
* PASS — SDK call succeeded + shape valid
|
||||
* SDK_BUG — SDK failed, raw ICD succeeded
|
||||
* ICD_BUG — both failed
|
||||
* SHAPE_BUG — SDK call succeeded but validate() threw
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
var T = window.SDKR;
|
||||
if (!T) return;
|
||||
|
||||
// ─── Results ───────────────────────────────────────────────
|
||||
// ─── Cleanup ───────────────────────────────────────────────
|
||||
|
||||
T.results = [];
|
||||
T.cleanup = [];
|
||||
T.stats = { pass: 0, sdk_bug: 0, icd_bug: 0, shape_bug: 0, skip: 0 };
|
||||
T.running = false;
|
||||
T.aborted = false;
|
||||
T.cleanup = [];
|
||||
|
||||
T.registerCleanup = function (fn) { T.cleanup.push(fn); };
|
||||
|
||||
T.runCleanup = async function () {
|
||||
var fns = T.cleanup.splice(0);
|
||||
for (var i = fns.length - 1; i >= 0; i--) {
|
||||
try { await fns[i](); } catch (_) { /* swallow */ }
|
||||
}
|
||||
};
|
||||
|
||||
// ─── Assertion Library ────────────────────────────────────
|
||||
|
||||
@@ -161,17 +172,6 @@
|
||||
return _token;
|
||||
};
|
||||
|
||||
// ─── Cleanup ───────────────────────────────────────────────
|
||||
|
||||
T.registerCleanup = function (fn) { T.cleanup.push(fn); };
|
||||
|
||||
T.runCleanup = async function () {
|
||||
var fns = T.cleanup.splice(0);
|
||||
for (var i = fns.length - 1; i >= 0; i--) {
|
||||
try { await fns[i](); } catch (_) { /* swallow */ }
|
||||
}
|
||||
};
|
||||
|
||||
// ─── Dual-Path Test ────────────────────────────────────────
|
||||
//
|
||||
// sdkFn: async () => result — calls sw.api.*
|
||||
@@ -236,122 +236,4 @@
|
||||
};
|
||||
};
|
||||
|
||||
// ─── Test Harness ──────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Register and run a single test.
|
||||
*
|
||||
* @param {string} domain — e.g. 'channels', 'workflows'
|
||||
* @param {string} group — e.g. 'crud', 'list', 'update'
|
||||
* @param {string} name — human-readable test name
|
||||
* @param {object} spec — { sdk, raw, validate }
|
||||
* sdk: async () => result
|
||||
* raw: { method: 'GET'|'POST'|..., path: '/channels', body?: {...} }
|
||||
* validate: (result) => void (throw on failure)
|
||||
*
|
||||
* For tests that don't use dual-path (e.g. setup/cleanup), pass a
|
||||
* plain async function as the 4th argument instead of a spec object.
|
||||
*/
|
||||
T.test = async function (domain, group, name, specOrFn) {
|
||||
if (T.aborted) return;
|
||||
|
||||
var entry = {
|
||||
domain: domain, group: group, name: name,
|
||||
verdict: null, sdkErr: null, rawErr: null, note: null,
|
||||
durationMs: 0
|
||||
};
|
||||
|
||||
var t0 = performance.now();
|
||||
|
||||
try {
|
||||
// Skip support — mark as SKIP with reason
|
||||
if (typeof specOrFn === 'object' && specOrFn.skip) {
|
||||
entry.verdict = 'SKIP';
|
||||
entry.note = specOrFn.skip;
|
||||
} else if (typeof specOrFn === 'function') {
|
||||
// Simple test — no dual-path
|
||||
await specOrFn();
|
||||
entry.verdict = 'PASS';
|
||||
} else {
|
||||
// Dual-path test
|
||||
var r = await T.dualTest(specOrFn.sdk, specOrFn.raw, specOrFn.validate);
|
||||
entry.verdict = r.verdict;
|
||||
entry.sdkErr = r.sdkErr || null;
|
||||
entry.rawErr = r.rawErr || null;
|
||||
entry.note = r.note || null;
|
||||
}
|
||||
} catch (e) {
|
||||
entry.verdict = 'ERROR';
|
||||
entry.sdkErr = e.message;
|
||||
}
|
||||
|
||||
entry.durationMs = Math.round(performance.now() - t0);
|
||||
|
||||
// Update stats
|
||||
var v = entry.verdict.toLowerCase().replace('error', 'icd_bug');
|
||||
if (T.stats.hasOwnProperty(v)) T.stats[v]++;
|
||||
|
||||
T.results.push(entry);
|
||||
|
||||
// Live update
|
||||
if (typeof T.onResult === 'function') T.onResult(entry);
|
||||
};
|
||||
|
||||
// ─── Domain Registry ───────────────────────────────────────
|
||||
|
||||
T.domains = {};
|
||||
|
||||
T.registerDomain = function (name, fn) {
|
||||
T.domains[name] = fn;
|
||||
};
|
||||
|
||||
// ─── Run All ───────────────────────────────────────────────
|
||||
|
||||
T.runAll = async function (domainFilter) {
|
||||
T.running = true;
|
||||
T.aborted = false;
|
||||
T.results = [];
|
||||
T.cleanup = [];
|
||||
T.stats = { pass: 0, sdk_bug: 0, icd_bug: 0, shape_bug: 0, skip: 0 };
|
||||
|
||||
if (typeof T.onStart === 'function') T.onStart();
|
||||
|
||||
// Provision fixtures (policies, provider, team) before domains
|
||||
if (typeof T.provisionFixtures === 'function') {
|
||||
try { await T.provisionFixtures(); } catch (e) {
|
||||
console.warn('[SDKR] Fixture provisioning failed:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
var names = Object.keys(T.domains).sort();
|
||||
if (domainFilter) {
|
||||
names = names.filter(function (n) { return domainFilter.indexOf(n) !== -1; });
|
||||
}
|
||||
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
if (T.aborted) break;
|
||||
try {
|
||||
await T.domains[names[i]]();
|
||||
} catch (e) {
|
||||
T.results.push({
|
||||
domain: names[i], group: 'setup', name: 'domain crashed',
|
||||
verdict: 'ERROR', sdkErr: e.message, rawErr: null, note: null,
|
||||
durationMs: 0
|
||||
});
|
||||
}
|
||||
// Run cleanup between domains
|
||||
await T.runCleanup();
|
||||
}
|
||||
|
||||
// Teardown fixtures (revert policies, etc.)
|
||||
if (typeof T.teardownFixtures === 'function') {
|
||||
try { await T.teardownFixtures(); } catch (_) {}
|
||||
}
|
||||
|
||||
T.running = false;
|
||||
if (typeof T.onComplete === 'function') T.onComplete();
|
||||
};
|
||||
|
||||
T.abort = function () { T.aborted = true; };
|
||||
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user