Ctrl K
ComunidadGuías…

__tests__/routecards.test.js

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

Archivo original ↗Preguntar a KiBot
/**
 * routecards — the browser learning the API it was using all along. The disciplines a prompt cannot
 * enforce: only WRITES to the page's own API are learnable (not analytics, not assets, not reads);
 * a card stores where a token LIVES, never its value; an act with no auth marker is refused rather
 * than shipped to 401 forever; an unverified card never displaces the UI; and ONE failure quarantines
 * a card, because a stale internal API doing the wrong thing silently is the error this must not cause.
 * Pure module, no browser.
 */
import { describe, it, expect } from 'vitest';
import { isLearnable, originOf, shapeOf, distill, planFor, onVerified, onFailed, buildReplay, afterReplay, cardKey } from '../src/routecards.js';

const req = (over = {}) => ({ method: 'POST', url: 'https://www.facebook.com/api/graphql/', headers: { 'x-fb-lsd': 'AbC' }, postData: '{"text":"hi"}', ...over });

describe('what is worth learning from', () => {
  it('only WRITES to the page\'s own API — never GETs, analytics, or assets', () => {
    expect(isLearnable(req())).toBe(true);
    expect(isLearnable(req({ method: 'GET' }))).toBe(false);
    expect(isLearnable(req({ url: 'https://www.facebook.com/main.js' }))).toBe(false);
    expect(isLearnable(req({ url: 'https://www.google-analytics.com/collect', method: 'POST' }))).toBe(false);
    expect(isLearnable(req({ url: 'https://www.facebook.com/tr?ev=x', method: 'POST' }))).toBe(false);
  });

  it('originOf fences a card to one site', () => {
    expect(originOf('https://www.facebook.com/api/x?y=1')).toBe('https://www.facebook.com');
    expect(originOf('garbage')).toBe('');
  });
});

describe('a card stores SHAPE, never secrets', () => {
  it('json body → slots are its keys, auth is remembered by NAME and place', () => {
    const s = shapeOf(req({ postData: '{"text":"hello","audience":"public"}' }));
    expect(s.bodyKind).toBe('json');
    expect(s.slots).toEqual(['text', 'audience']);
    expect(s.authAt).toContainEqual({ in: 'header', name: 'x-fb-lsd' });
    // the actual token value never appears anywhere in the shape
    expect(JSON.stringify(s)).not.toMatch(/AbC/);
  });

  it('form body → csrf-ish fields are recognised as auth-at, not just data slots', () => {
    const s = shapeOf(req({ headers: {}, postData: 'message=hi&fb_dtsg=TOKEN123&jazoest=2' }));
    expect(s.bodyKind).toBe('form');
    expect(s.slots).toEqual(['message', 'fb_dtsg', 'jazoest']);
    expect(s.authAt).toContainEqual({ in: 'body', name: 'fb_dtsg' });
    expect(JSON.stringify(s)).not.toMatch(/TOKEN123/);
  });
});

describe('distilling a walk into a card', () => {
  const walk = [
    req({ url: 'https://www.facebook.com/ajax/bootload', headers: { 'x-fb-lsd': 'x' } }),
    req({ url: 'https://www.google-analytics.com/collect', method: 'POST', headers: {} }),
    req({ url: 'https://www.facebook.com/api/graphql/', headers: { 'x-fb-lsd': 'x' }, postData: '{"variables":{"message":"hi"}}' }),
  ];

  it('picks the LAST learnable write as the act, ignoring analytics between', () => {
    const out = distill({ intent: 'facebook.page.post', origin: 'https://www.facebook.com', requests: walk, now: 100 });
    expect(out.ok).toBe(true);
    expect(out.card.intent).toBe('facebook.page.post');
    expect(out.card.url).toMatch(/graphql/);
    expect(out.card.confidence).toBe(0);          // DISTILL proposes; VERIFY earns confidence
    expect(out.card.lastVerified).toBeNull();
  });

  it('refuses an act with no auth marker — a card that would 401 forever is worse than none', () => {
    const out = distill({ intent: 'x', origin: 'https://www.facebook.com', requests: [req({ headers: {}, postData: '{"a":1}' })] });
    expect(out.ok).toBe(false);
    expect(out.reason).toMatch(/no auth token/);
  });

  it('a walk with no learnable request yields no card', () => {
    expect(distill({ intent: 'x', requests: [req({ method: 'GET' })] })).toBeNull();
  });

  it('with a sealIndex, the act is the write AT the seal — not the last one', () => {
    // setup walk: [validate, CREATE, navigate, navigate]; the seal was dropped before the create.
    const reqs = [
      req({ postData: '{"validate":1}' }),
      req({ postData: '{"createPage":"PrintOps"}' }),
      req({ postData: '{"loadFeed":1}' }),
      req({ postData: '{"search":"PrintOps"}' }),
    ];
    const out = distill({ intent: 'facebook.page.setup', origin: 'https://www.facebook.com', requests: reqs, now: 1, sealIndex: 1 });
    expect(out.ok).toBe(true);
    expect(out.card.slots).toEqual(['createPage']);
  });

  it('without a sealIndex, it still falls back to the LAST write (an operate walk ends on its act)', () => {
    const reqs = [req({ postData: '{"a":1}' }), req({ postData: '{"theActuralPost":1}' })];
    const out = distill({ intent: 'facebook.page.post', origin: 'https://www.facebook.com', requests: reqs, now: 1 });
    expect(out.card.slots).toEqual(['theActuralPost']);
  });

  it('an out-of-range sealIndex falls back to the last write rather than crashing', () => {
    const out = distill({ intent: 'x', origin: 'https://www.facebook.com', requests: [req()], now: 1, sealIndex: 9 });
    expect(out.ok).toBe(true);
  });
});

describe('the plan: an unproven card never displaces the UI', () => {
  const base = distill({ intent: 'facebook.page.post', origin: 'https://www.facebook.com', requests: [req()], now: 1 }).card;

  it('no card → UI', () => { expect(planFor(null).mode).toBe('ui'); });
  it('freshly distilled but unverified → UI (ride along to verify)', () => {
    expect(planFor(base).mode).toBe('ui');
    expect(planFor(base).reason).toMatch(/not yet verified/);
  });
  it('verified → fast', () => {
    const v = onVerified(base, 2);
    expect(planFor(v).mode).toBe('fast');
    expect(planFor(v).card).toBe(v);
  });
  it('quarantined → UI even if it was once verified', () => {
    const q = onFailed(onVerified(base, 2), 3);
    expect(planFor(q).mode).toBe('ui');
    expect(planFor(q).reason).toMatch(/quarantined/);
  });
});

describe('verify raises, one failure quarantines', () => {
  const base = distill({ intent: 'i', origin: 'https://www.facebook.com', requests: [req()], now: 1 }).card;

  it('onVerified stamps the time, bumps confidence (capped at 5), clears fails', () => {
    let c = base;
    for (let i = 0; i < 8; i++) c = onVerified(c, i);
    expect(c.confidence).toBe(5);
    expect(c.lastVerified).toBe(7);
    expect(c.fails).toBe(0);
    expect(c.quarantined).toBe(false);
  });

  it('a SINGLE failure quarantines and drops confidence — never a slow decay', () => {
    const c = onFailed(onVerified(onVerified(base, 1), 2), 3);
    expect(c.quarantined).toBe(true);
    expect(c.fails).toBe(1);
    expect(c.confidence).toBeLessThan(2);
  });

  it('cardKey is one card per (origin, intent)', () => {
    expect(cardKey('https://www.facebook.com', 'facebook.page.post')).toBe('https://www.facebook.com::facebook.page.post');
  });
});

describe('the replay request and the fallback rule', () => {
  const card = onVerified(distill({
    intent: 'facebook.page.post', origin: 'https://www.facebook.com',
    requests: [{ method: 'POST', url: 'https://www.facebook.com/api/graphql/', headers: {}, postData: 'message=old&fb_dtsg=T&jazoest=2' }],
    now: 1,
  }).card, 2);

  it('buildReplay fills the real slots, leaves the auth-at fields for the live page, carries NO token', () => {
    const r = buildReplay(card, { message: 'our new post', jazoest: '2' });
    expect(r.url).toMatch(/graphql/);
    expect(r.method).toBe('POST');
    expect(r.values.message).toBe('our new post');
    // fb_dtsg is auth-at (body) — not required from the caller; the page merges the live token
    expect(JSON.stringify(r)).not.toMatch(/"fb_dtsg":"T"/);
  });

  it('buildReplay refuses when a required (non-auth) slot has no value — never posts a blank', () => {
    expect(buildReplay(card, { jazoest: '2' })).toBeNull();   // message missing
  });

  it("afterReplay: a FAILED replay quarantines AND returns fallback 'ui' — the same job retries by UI", () => {
    const out = afterReplay(card, { ok: false, now: 5 });
    expect(out.fallback).toBe('ui');
    expect(out.card.quarantined).toBe(true);
  });

  it('afterReplay: a verified replay raises the card and needs no fallback', () => {
    const out = afterReplay(card, { ok: true, now: 5 });
    expect(out.fallback).toBeNull();
    expect(out.card.confidence).toBeGreaterThan(card.confidence);
  });
});