Feat v0.7.1 surface runner framework (#55)
All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m51s
CI/CD / test-sqlite (push) Successful in 3m2s
CI/CD / build-and-deploy (push) Successful in 1m26s

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:
2026-04-01 23:01:38 +00:00
committed by xcaliber
parent e916ed41ea
commit 829caa3b20
59 changed files with 2509 additions and 6525 deletions

View File

@@ -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; };
})();