Ctrl K
ComunidadGuías…

test/dashboard-syntax.test.js

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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(/<script([^>]*)>([\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(/<script([^>]*)>([\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', 'a<b>c', '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 `<th>` 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(/<table id="accounts-table">[\s\S]*?<\/thead>/)?.[0] || '';
    assert.equal((thead.match(/<th\b/g) || []).length, 9, 'accounts table must have exactly 9 columns');
    assert.match(DASHBOARD, /accounts\.filter\.searchPlaceholder/);
    assert.match(DASHBOARD, /accounts\.status\.quotaCooled/);
    // These two used to read `doesNotMatch(html, /pageSize=1000/)` — TAUTOLOGIES.
    // index.html never writes the page size in the query-string form `pageSize=N`; it
    // builds it as an object property (`pageSize: this.proxyPageSize`), so no page-size
    // regression could ever make those regexes match. Worse, the thing they existed to
    // prevent — a 1000-row page load — IS present as `pageSize: '1000'`, and they sat
    // green over it. The intent was right; the form could not detect anything.
    //
    // Bite the real shape instead: the LIST paths must page through the bound
    // properties, and the one legitimate bulk read (the CSV export at ~6301, which
    // deliberately exceeds the 200 clamp) must stay the ONLY hardcoded large size.
    // The clamp itself is executed above (accountsListUrl caps at 200).
    const bigPageSites = [...DASHBOARD.matchAll(/pageSize:\s*'?(\d+)'?/g)]
      .map(m => 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('<section class="contributors-section"'),
      DOCS.indexOf('<!-- ── Footer ── -->'),
    );

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