import test, { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'fs'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const root = join(__dirname, '..'); const DASHBOARD = readFileSync(join(root, 'src/dashboard/index.html'), 'utf8'); const SKETCH = readFileSync(join(root, 'src/dashboard/index-sketch.html'), 'utf8'); const DOCS = readFileSync(join(root, 'docs/index.html'), 'utf8'); /** * Pull one or more methods out of a dashboard `App` object literal and hand back * a live object, so the shipped code can be CALLED instead of pattern-matched. * * The precedent is this file's own `new Function(source)` script-parse check: * parse the real source, then use it. Reimplementing a helper here would keep * passing while production degraded, which is the failure mode these assertions * exist to prevent. * * Only self-contained methods qualify: pure string/URL builders that touch no * DOM node and no instance state (beyond `this.*` defaults, which the returned * object supplies). */ function extractAppMethods(html, names, scope = {}) { const members = names.map((name) => { const at = html.indexOf(`\n ${name}(`); assert.notEqual(at, -1, `${name}() must exist as an App method`); // Walk the parameter list to its closing paren first: a destructured default // (`{ page = this.accountsPage } = {}`) puts braces in front of the body. let paren = 0; let bodyStart = -1; for (let i = html.indexOf('(', at); i < html.length; i++) { if (html[i] === '(') paren++; else if (html[i] === ')' && --paren === 0) { bodyStart = html.indexOf('{', i); break; } } assert.notEqual(bodyStart, -1, `could not delimit the parameter list of ${name}`); let depth = 0; let end = -1; for (let i = bodyStart; i < html.length; i++) { if (html[i] === '{') depth++; else if (html[i] === '}' && --depth === 0) { end = i + 1; break; } } assert.notEqual(end, -1, `could not delimit the body of ${name}`); return html.slice(at + 1, end); }); const scopeNames = Object.keys(scope); // eslint-disable-next-line no-new-func -- executing the shipped source is the point const build = new Function(...scopeNames, `return ({ ${members.join(',\n')} });`); return build(...scopeNames.map((k) => scope[k])); } const I18nStub = { t: (key) => `i18n:${key}` }; const app = extractAppMethods( DASHBOARD, ['accountsListUrl', 'systemPromptDomId', 'getWindsurfProxyLabel', 'buildWindsurfSigninUrl'], { I18n: I18nStub }, ); const sketchApp = extractAppMethods(SKETCH, ['isFirebaseOAuthOriginBlocked']); for (const skin of ['src/dashboard/index.html', 'src/dashboard/index-sketch.html']) { test(`${skin} inline scripts are syntactically valid`, () => { const html = readFileSync(join(root, skin), 'utf8'); const scripts = [...html.matchAll(/]*)>([\s\S]*?)<\/script>/gi)] .map((match, index) => ({ index, attrs: match[1] || '', source: match[2] || '' })) .filter(({ attrs }) => !/\bsrc\s*=/.test(attrs)) .filter(({ attrs }) => !/\btype\s*=\s*["']module["']/i.test(attrs)); assert.ok(scripts.length > 0, `expected at least one non-module inline script in ${skin}`); for (const { index, source } of scripts) { assert.doesNotThrow(() => new Function(source), `inline script #${index} in ${skin} should parse`); } }); } test('docs/index.html inline scripts are syntactically valid', () => { const scripts = [...DOCS.matchAll(/]*)>([\s\S]*?)<\/script>/gi)] .map((match, index) => ({ index, attrs: match[1] || '', source: match[2] || '' })) .filter(({ attrs }) => !/\bsrc\s*=/.test(attrs)) .filter(({ attrs }) => !/\btype\s*=\s*["']module["']/i.test(attrs)); assert.ok(scripts.length > 0, 'expected at least one non-module inline script in docs/index.html'); for (const { index, source } of scripts) { assert.doesNotThrow(() => new Function(source), `inline script #${index} in docs/index.html should parse`); } }); // ─── Executed helpers ─────────────────────────────────────────────────────── // These replace the former source greps for `this.systemPromptDomId(key)`, // `encodeURIComponent(key)`, `pageSize`, the windsurf sign-in URL and the // Firebase origin-block classifier. A grep was satisfied by the string still // being present; these assertions are only satisfied if the code still behaves. describe('dashboard helpers run', () => { it('accountsListUrl always asks for the summary view and clamps the page size', () => { const clamped = new URL(app.accountsListUrl({ page: 3, pageSize: 1000 }), 'http://x'); assert.equal(clamped.pathname, '/accounts'); assert.equal(clamped.searchParams.get('view'), 'summary'); assert.equal(clamped.searchParams.get('page'), '3'); // 200 is the server's clamp; asking for more is the 1000-row page load the // old `doesNotMatch(/pageSize=1000/)` guard could never see. assert.equal(clamped.searchParams.get('pageSize'), '200'); const tiny = new URL(app.accountsListUrl({ page: 0, pageSize: 0 }), 'http://x'); assert.equal(tiny.searchParams.get('page'), '1'); assert.equal(tiny.searchParams.get('pageSize'), '50'); const garbage = new URL(app.accountsListUrl({ page: 'abc', pageSize: 'abc' }), 'http://x'); assert.equal(garbage.searchParams.get('page'), '1'); assert.equal(garbage.searchParams.get('pageSize'), '50'); }); it('accountsListUrl forwards filters and omits empty ones', () => { const filtered = new URL(app.accountsListUrl({ page: 2, pageSize: 25, filter: 'flagged', status: 'active', q: 'a b' }), 'http://x'); assert.equal(filtered.searchParams.get('filter'), 'flagged'); assert.equal(filtered.searchParams.get('status'), 'active'); assert.equal(filtered.searchParams.get('q'), 'a b'); assert.equal(filtered.searchParams.get('tier'), null); const bare = new URL(app.accountsListUrl({}), 'http://x'); assert.deepEqual( [...bare.searchParams.keys()].sort(), ['page', 'pageSize', 'view'], 'an unfiltered list must not carry empty filter keys', ); }); it('systemPromptDomId maps every prompt key to a distinct, injection-safe id', () => { const keys = ['toolReinforcement', 'communicationWithTools', 'communicationNoTools', 'a b', 'a"b', 'ac', 'a%b']; const ids = keys.map((key) => app.systemPromptDomId(key)); assert.equal(new Set(ids).size, keys.length, 'distinct prompt keys must not collide on one textarea'); for (const [i, id] of ids.entries()) { assert.match(id, /^sp-/, `id for ${keys[i]} must keep the sp- prefix`); for (const ch of ['"', '<', '>', ' ', '%', '\n']) { assert.ok(!id.includes(ch), `id for ${JSON.stringify(keys[i])} must not contain ${JSON.stringify(ch)}: ${id}`); } } assert.equal(app.systemPromptDomId('a b'), 'sp-a_20b'); assert.equal(app.systemPromptDomId(null), 'sp-'); assert.equal(app.systemPromptDomId(undefined), 'sp-'); }); it('getWindsurfProxyLabel renders every proxy shape without undefined', () => { assert.equal(app.getWindsurfProxyLabel(null), 'i18n:proxy.global'); assert.equal(app.getWindsurfProxyLabel(undefined), 'i18n:proxy.global'); assert.equal(app.getWindsurfProxyLabel('http://proxy.example:8080'), 'http://proxy.example:8080'); assert.equal(app.getWindsurfProxyLabel({ type: 'http', host: 'p.example', port: 8080 }), 'http://p.example:8080'); // The batch-login bug was a history row rendering `undefined`; no input may. for (const input of [null, undefined, 'direct', { type: 'socks5', host: 'h', port: 1 }]) { assert.ok(!String(app.getWindsurfProxyLabel(input)).includes('undefined')); } }); it('buildWindsurfSigninUrl emits the token flow with a fresh state per call', () => { const first = new URL(app.buildWindsurfSigninUrl()); assert.equal(first.origin + first.pathname, 'https://windsurf.com/windsurf/signin'); assert.equal(first.searchParams.get('response_type'), 'token'); assert.equal(first.searchParams.get('redirect_uri'), 'show-auth-token'); assert.equal(first.searchParams.get('prompt'), 'login'); assert.ok((first.searchParams.get('state') || '').length > 0, 'a CSRF state is mandatory'); const second = new URL(app.buildWindsurfSigninUrl()); assert.notEqual(first.searchParams.get('state'), second.searchParams.get('state')); }); it('isFirebaseOAuthOriginBlocked separates origin blocks from real failures', () => { const blocked = [ { message: 'Firebase: Error (auth/unauthorized-domain).' }, { message: 'auth/requests-from-referer-https://x.are-blocked' }, 'unauthorized-domain', ]; for (const err of blocked) { assert.equal(sketchApp.isFirebaseOAuthOriginBlocked(err), true, `${JSON.stringify(err)} is an origin block`); } const notBlocked = [ { message: 'Firebase: Error (auth/wrong-password).' }, { message: 'auth/network-request-failed' }, null, undefined, '', ]; for (const err of notBlocked) { assert.equal(sketchApp.isFirebaseOAuthOriginBlocked(err), false, `${JSON.stringify(err)} must not be steered to the token fallback`); } }); }); // ─── Markup / data guards ─────────────────────────────────────────────────── // Everything below asserts the SHAPE of shipped markup or shipped JSON: DOM ids, // column counts, removed pages, locale keys, contributor records. `new Function` // cannot reach any of it — a `` count or a `doesNotMatch(/id="p-bans"/)` is // not a behaviour, it is the contract with the stylesheet and the router. describe('dashboard markup contract', () => { test('system prompt editor routes keys through both escapers', () => { // The escaping itself is executed in dashboard-escape-behaviour.test.js; this // file only owns "the call sites still go through the helpers", which is what // a grep is good at. assert.match(DASHBOARD, /const safeKey = this\.esc\(key\)/); assert.match(DASHBOARD, /const keyArg = this\.escJsAttr\(key\)/); // …and that neither raw interpolation form comes back. assert.doesNotMatch(DASHBOARD, /\$\{key\}<\/code>/); assert.doesNotMatch(DASHBOARD, /resetSystemPrompt\('\$\{key\}'\)/); }); test('dashboard batch login history uses each result proxy instead of an undefined local', () => { // getWindsurfProxyLabel's behaviour is executed above; this pins the CALL // SITE, which is where the original bug lived (`item.proxy` vs a stray `proxy`). assert.match(DASHBOARD, /proxy:\s*this\.getWindsurfProxyLabel\(item\.proxy\)/); assert.doesNotMatch(DASHBOARD, /proxy:\s*this\.getWindsurfProxyLabel\(proxy\),\s*\r?\n\s*status:\s*item\.success/); }); test('dashboard drought banners expose restriction fail-open state', () => { const en = JSON.parse(readFileSync(join(root, 'src/dashboard/i18n/en.json'), 'utf8')); const zh = JSON.parse(readFileSync(join(root, 'src/dashboard/i18n/zh-CN.json'), 'utf8')); assert.match(DASHBOARD, /d\.restrictionFailOpen/); assert.match(DASHBOARD, /I18n\.t\('drought\.restrictionFailOpen'\)/); assert.match(SKETCH, /d\.restrictionFailOpen/); assert.match(SKETCH, /id="drought-fail-open-message"/); assert.equal(typeof en.drought.restrictionFailOpen, 'string'); assert.equal(typeof zh.drought.restrictionFailOpen, 'string'); }); test('dashboard proxy table pages summaries; accounts page is the single merged list', () => { assert.match(DASHBOARD, /id="proxy-accounts-pagination"/); assert.match(DASHBOARD, /setProxyPage\(page\)/); // The list path must page through the bound properties; the clamping and the // summary view they produce are executed above. assert.match(DASHBOARD, /this\.accountsListUrl\(\{\s*page:\s*this\.proxyPage,\s*pageSize:\s*this\.proxyPageSize/s); assert.match(DASHBOARD, /filter:\s*this\.accountsFlagged\s*\?\s*'flagged'\s*:\s*''/); // The 异常监测 page and the 登录取号 page are gone: one account-management // destination, one table, one add/login entry point. assert.doesNotMatch(DASHBOARD, /id="p-bans"/); assert.doesNotMatch(DASHBOARD, /id="ban-pagination"/); assert.doesNotMatch(DASHBOARD, /id="p-windsurf-login"/); assert.doesNotMatch(DASHBOARD, /setBansPage\(/); assert.match(DASHBOARD, /id="accounts-kpis"/); assert.match(DASHBOARD, /id="accounts-batch-bar"/); assert.match(DASHBOARD, /id="acct-drawer"/); assert.match(DASHBOARD, /id="add-account-overlay"/); assert.doesNotMatch(DASHBOARD, /account-detail-row/); // 9-column table: checkbox, account, tier, status, quota, models, RPM, last // used, actions. The Key/ID/Error columns are folded into cells/drawer. const thead = DASHBOARD.match(/[\s\S]*?<\/thead>/)?.[0] || ''; assert.equal((thead.match(/ Number(m[1])) .filter(n => n > 200); assert.equal(bigPageSites.length, 1, `only the CSV export may hardcode a page size above 200; found ${bigPageSites.length} ` + `site(s): ${bigPageSites.join(', ')}. A list view must page via this.*PageSize.`); assert.match(DASHBOARD, /view: 'summary', page: '1', pageSize: '1000'/, 'and that one site is the export — if it moved, re-point this guard rather than widening it'); }); test('dashboard sketch proxy and abnormal-account tables use lightweight summaries', () => { assert.match(SKETCH, /\/accounts\?view=summary&pageSize=200/); assert.match(SKETCH, /\/accounts\?view=summary&filter=flagged&pageSize=200/); // index-sketch.html DOES use the query-string form, so here the literal check is // live rather than tautological (mutating `pageSize=200` to `pageSize=1000` fails // the two assertions above). Keep an explicit upper bound on every occurrence so a // new call site cannot reintroduce a heavy default. const sizes = [...SKETCH.matchAll(/pageSize=(\d+)/g)].map(m => Number(m[1])); assert.ok(sizes.length > 0, 'the sketch UI builds page size into the query string'); assert.deepEqual(sizes.filter(n => n > 200), [], `sketch list views must stay at or below 200 rows per request; found ${sizes.join(', ')}`); }); test('dashboard Quick Login uses the Windsurf sign-in + token flow (no dead Firebase popup)', () => { // The sign-in URL is built and executed above; the sketch's Firebase flow has // no importable seam, so its classifier is executed in the helper suite and // the two literals it must recognise stay here as a cheap regression pin. assert.match(SKETCH, /requests-from-referer-\.\*are-blocked/); assert.match(SKETCH, /unauthorized-domain/); // Both variants surface the windsurf.com token path. for (const source of [DASHBOARD, SKETCH]) { assert.match(source, /windsurf\.com\/show-auth-token/); } // index.html's Quick Login no longer relies on Firebase signInWithPopup — it is // permanently origin-blocked on a self-hosted public origin. It now drives the // Windsurf official sign-in + paste-token flow unconditionally (App.oauthLogin → // buildWindsurfSigninUrl → App.submitOAuthToken). assert.match(DASHBOARD, /submitOAuthToken/); // The Firebase Auth SDK (which provided signInWithPopup) must no longer be imported. assert.doesNotMatch(DASHBOARD, /firebasejs\/[\d.]+\/firebase-auth/); }); test('recent merged PR contributors are represented in dashboard and README credits', () => { const contributorData = JSON.parse(readFileSync(join(root, 'src/dashboard/data/contributors.json'), 'utf8')); const readme = readFileSync(join(root, 'README.md'), 'utf8'); const readmeEn = readFileSync(join(root, 'README.en.md'), 'utf8'); const required = [ { login: 'MatrixNeoKozak', pr: 195 }, { login: 'brandonedley', pr: 201 }, ]; for (const { login, pr } of required) { assert.ok( contributorData.contributors.some(entry => entry.login === login && entry.pr === pr), `contributors.json should include @${login} PR #${pr}`, ); assert.match(readme, new RegExp(`@${login}[\\s\\S]*PR #${pr}`)); assert.match(readmeEn, new RegExp(`@${login}[\\s\\S]*PR #${pr}`)); } }); test('docs homepage renders contributors from the published dashboard JSON', () => { const contributorsSection = DOCS.slice( DOCS.indexOf('
'), ); assert.match(DOCS, /id="contributors-grid" data-source="dashboard\/data\/contributors\.json"/); assert.match(DOCS, /fetch\(source,\{cache:'no-cache'\}\)/); assert.match(DOCS, /function renderContributors\(entries\)/); assert.match(DOCS, /id="contributors-footer"/); assert.doesNotMatch(contributorsSection, /href="https:\/\/github\.com\/aict666"/); assert.doesNotMatch(contributorsSection, /PR #192/); }); test('docs contributor JSON mirrors the canonical dashboard contributor data', () => { const source = readFileSync(join(root, 'src/dashboard/data/contributors.json'), 'utf8'); const published = readFileSync(join(root, 'docs/dashboard/data/contributors.json'), 'utf8'); const parsed = JSON.parse(published); assert.deepEqual(JSON.parse(source), parsed); assert.equal(published, source); }); });