Ctrl K
ComunidadGuías…

__tests__/api-tokens.test.js

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

Archivo original ↗Preguntar a KiBot
/*
 * SETTINGS → API TOKENS HAD NO SERVER.
 *
 * The console sheet called GET/POST /v1/tokens and DELETE /v1/tokens/:id, and no route existed: the
 * 404 page is not JSON, so "Generate" could only say "could not generate". These tests hold the
 * server half: a token is shown once and stored as a hash, it works as an API key on every route and
 * on /mcp, only the signed-in owner manages tokens, and a revoked token stops at once.
 */
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createRequire } from 'module';
import { readFileSync, mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import express from 'express';

process.env.PROFILE_DIR = mkdtempSync(join(tmpdir(), 'gb-apitokens-'));
const require = createRequire(import.meta.url);
const tokens = require('../src/apiTokens');
const { parseKeys, auth } = require('../src/keys');
const mcp = require('../src/mcp');
const server = readFileSync(fileURLToPath(new URL('../src/server.js', import.meta.url)), 'utf8');

describe('minting', () => {
  it('shows the token once and stores only its hash', () => {
    const t = tokens.mint('border-monitor connect');
    expect(t.token.startsWith('gbt_')).toBe(true);
    expect(t).toMatchObject({ name: 'border-monitor connect', preview: '…' + t.token.slice(-4) });
    const disk = readFileSync(join(process.env.PROFILE_DIR, 'api-tokens.json'), 'utf8');
    expect(disk).not.toContain(t.token);
    expect(tokens.list().map((r) => r.tokenId)).toContain(t.tokenId);
    expect(JSON.stringify(tokens.list())).not.toContain(t.token);
  });

  it('a revoked token stops working at once', () => {
    const t = tokens.mint('x');
    expect(tokens.lookup(t.token)).toBeTruthy();
    expect(tokens.revoke(t.tokenId)).toBe(true);
    expect(tokens.lookup(t.token)).toBeNull();
    expect(tokens.revoke(t.tokenId)).toBe(false);
  });
});

describe('a minted token is an API key', () => {
  let http, base, key;
  beforeAll(async () => {
    const keys = tokens.extend(parseKeys('env-key-1:team'));
    key = tokens.mint('script').token;
    const app = express(); app.use(express.json());
    app.get('/v1/sessions', auth(keys), (req, res) => res.json({ owner: req.client.owner, plan: req.client.plan }));
    mcp.mount(app, { keys, requestApi: async () => ({ ok: true, data: { sessions: [] } }) });
    http = await new Promise((r) => { const s = app.listen(0, '127.0.0.1', () => r(s)); });
    base = `http://127.0.0.1:${http.address().port}`;
  });
  afterAll(() => new Promise((r) => http.close(r)));

  it('on a REST route, with the install plan', async () => {
    const ok = await fetch(base + '/v1/sessions', { headers: { authorization: 'Bearer ' + key } });
    expect(ok.status).toBe(200);
    expect((await ok.json()).plan).toBe('team');
    expect((await fetch(base + '/v1/sessions', { headers: { authorization: 'Bearer gbt_invented' } })).status).toBe(401);
    expect((await fetch(base + '/v1/sessions', { headers: { authorization: 'Bearer env-key-1' } })).status).toBe(200);
  });

  it('on /mcp', async () => {
    const send = (k) => fetch(base + '/mcp', { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream', authorization: 'Bearer ' + k }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }) });
    expect((await send(key)).status).toBe(200);
    expect((await send('gbt_invented')).status).toBe(401);
  });
});

describe('the routes', () => {
  it('exist, and only the signed-in owner reaches them', () => {
    for (const r of ["app.get('/v1/tokens', authed, ownerOnly", "app.post('/v1/tokens', authed, ownerOnly", "app.delete('/v1/tokens/:id', authed, ownerOnly"]) expect(server).toContain(r);
    const fn = server.slice(server.indexOf('function ownerOnly('), server.indexOf('function ownerOnly(') + 300);
    expect(fn).toContain('req.client.console');
  });
  it('the key map is extended before anything reads it', () => {
    expect(server.indexOf('apiTokens.extend(keys)')).toBeGreaterThan(0);
    expect(server.indexOf('apiTokens.extend(keys)')).toBeLessThan(server.indexOf('const bearer = auth(keys)'));
    expect(server.indexOf('apiTokens.extend(keys)')).toBeLessThan(server.indexOf("require('./mcp').mount("));
  });
});