Ctrl K
ComunidadGuías…

tests/github.test.ts

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

Archivo original ↗Preguntar a KiBot
import assert from 'node:assert/strict';
import test from 'node:test';
import { GitHub } from '../src/adapters/github/client.js';
import { finishReport } from '../src/application/publication.js';
import { configSchema } from '../src/domain/config.js';
import { RetryableError } from '../src/shared/control.js';
import { pr, store, verifiedReport } from './helpers.js';
test('stale commits, descriptions, drafts and forks prevent all GitHub mutations', async () => {
  for (const candidate of [
    { ...pr, head: { ...pr.head, sha: 'c'.repeat(40) } }, { ...pr, body: 'edited' },
    { ...pr, draft: true }, { ...pr, head: { ...pr.head, repo: { full_name: 'other/repo' } } }
  ]) {
    const client = new GitHub('owner/repo', 'test-placeholder'), methods: string[] = [];
    client.request = async <T>(_path: string, method = 'GET'): Promise<T> => { methods.push(method); return candidate as T; };
    assert.equal(await client.publish(verifiedReport()), undefined); assert.deepEqual(methods, ['GET']);
  }
});
test('post-merge outcome reads required evidence from the merge commit', async () => {
  const client = new GitHub('owner/repo', 'fixture'), merge = 'c'.repeat(40), paths: string[] = [];
  client.request = async <T>(path: string): Promise<T> => {
    paths.push(path);
    if (path === 'pulls/1') return { ...pr, state: 'closed', merged_at: '2026-01-01T00:00:00Z', merge_commit_sha: merge } as T;
    if (path.includes('check-runs')) return { total_count: 1, check_runs: [{ name: 'tests', status: 'completed', conclusion: 'success' }] } as T;
    if (path.includes('statuses')) return [] as T;
    throw new Error('Unexpected path: ' + path);
  };
  const outcome = await client.outcome(1);
  assert.equal(outcome.checks[0]?.conclusion, 'success');
  assert.ok(paths.some(path => path.startsWith(`commits/${merge}/check-runs`)));
  assert.ok(!paths.some(path => path.includes(pr.head.sha) && path.includes('check-runs')));
});
test('publication failures persist bounded retry attempts and permanent failures stop', async () => {
  const config = configSchema.parse({ repository: 'owner/repo', publish: true, retry: { baseDelayMs: 0, maxTaskExecutions: 2 } });
  const saved = await store(), report = verifiedReport(); let calls = 0;
  const github = { current: async () => pr, publish: async () => { calls++; throw new RetryableError('temporary'); } };
  await finishReport(report, config, saved, github);
  await finishReport(report, config, saved, github);
  await finishReport(report, config, saved, github);
  assert.equal(calls, 2); assert.equal((await saved.read(report.id))?.publication?.attempts, 2);
  const permanent = verifiedReport(); github.publish = async () => { calls++; throw new Error('denied'); };
  await finishReport(permanent, config, saved, github);
  await finishReport(permanent, config, saved, github);
  assert.equal(calls, 3); assert.equal(permanent.publication?.retryable, false);
});
test('unverified or unstructured evidence cannot create a branch', async () => {
  const client = new GitHub('owner/repo', 'test-placeholder');
  await assert.rejects(client.publish({ ...verifiedReport(), status: 'needs_attention' }), /Only verified/);
  const report = verifiedReport(); report.tests.repaired!.structured = false;
  await assert.rejects(client.publish(report), /Only verified/);
});
test('GitHub GET retries transient failures; writes are not blindly repeated and rate windows are respected', async () => {
  let calls = 0;
  const client = new GitHub('owner/repo', 'test-placeholder', {
    retry: { attempts: 2, baseDelayMs: 0, maxDelayMs: 1 },
    transport: async () => { calls++; return new Response(JSON.stringify(pr), { status: calls === 1 ? 503 : 200 }); }
  });
  assert.equal((await client.pull(1)).number, 1); assert.equal(calls, 2);
  calls = 0;
  await assert.rejects(client.request('pulls', 'POST', {}), RetryableError); assert.equal(calls, 1);
  const limited = new GitHub('owner/repo', 'test-placeholder', {
    retry: { attempts: 3, baseDelayMs: 0, maxDelayMs: 1 },
    transport: async () => { calls++; return new Response('{}', { status: 429, headers: { 'retry-after': '60' } }); }
  });
  calls = 0;
  await assert.rejects(limited.pull(1), (error: unknown) => error instanceof RetryableError && error.retryAfterMs === 60000);
  assert.equal(calls, 1);
});
test('publication preserves executable modes and attaches evidence', async () => {
  const client = new GitHub('owner/repo', 'test-placeholder'), writes: { path: string; body: any }[] = [];
  client.request = async <T>(path: string, method = 'GET', body?: unknown): Promise<T> => {
    if (method !== 'GET') writes.push({ path, body });
    let value: unknown;
    if (path === 'pulls/1') value = pr;
    else if (path.startsWith('pulls?')) value = [];
    else if (path === 'git/commits/' + pr.head.sha) value = { tree: { sha: 'source-tree' } };
    else if (path === 'git/trees/source-tree?recursive=1') value = { truncated: false, tree: [{ path: 'src/a.ts', mode: '100755', type: 'blob' }] };
    else if (path === 'git/trees') value = { sha: 'fixed-tree' };
    else if (path === 'git/commits') value = { sha: 'fixed-commit' };
    else if (path.startsWith('git/ref/')) throw new Error('HTTP 404');
    else if (path === 'git/refs') value = {};
    else if (path === 'pulls') value = { html_url: 'https://github.com/owner/repo/pull/2' };
    else throw new Error('Unexpected request: ' + path);
    return value as T;
  };
  assert.match((await client.publish(verifiedReport()))!, /pull\/2$/);
  assert.equal(writes.find(w => w.path === 'git/trees')?.body.tree[0].mode, '100755');
  assert.match(writes.find(w => w.path === 'pulls')?.body.body, /Runner evidence/);
});