mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:05:19 +00:00
Decisão (2026-08-21): o GitHub passa a ser a fonte única — main, branches, PRs, CI via Actions E tracker de Issues. O Forgejo deixa de ser canal ativo (git.e issue tracker congelados pós-convergência). Data: 17 issues migradas do Forgejo para o GitHub (frontmatter id/depends/ serializes/priority/model preservados, labels mapeados, comment de origem em cada uma; tabela Forgejo→GitHub registrada). Tooling: github-api.mjs estendido com a camada de issues/labels/comments/ close/workflow-dispatch/contents (mesmo shape normalizado do forgejo-api); 18 scripts trocam o transporte forgejo-api → github-api; forgejo-api.mjs e seu teste (961 linhas) removidos; teste githubApi.unit.spec.ts novo (7 casos); issue-transition-on-merge.mjs renomeado e reapontado. Flips pós-merge: issue-done e plan-issue-ready agora usam o GITHUB_TOKEN com issues:write no repo (sem FORGEJO_API_TOKEN) e fecham/flipam Issues no GitHub. Docs: AGENTS.md, AGENT-OPS.md e skills do fluxo vivo (work-issue, plan-issue, project-status, capture-review-debts, worktree-next-issue, agent-work-issue) apontam o tracker para github.com/fsolla/teqo/issues. Gates: tsc/lint/format/knip/cycles/unit 2416 verdes.
135 lines
4.5 KiB
TypeScript
135 lines
4.5 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { createApi } from '../../scripts/lib/github-api.mjs'
|
|
|
|
type FetchCall = {
|
|
url: string
|
|
method?: string
|
|
init?: { headers?: Record<string, string>; method?: string; body?: string }
|
|
}
|
|
|
|
const ok = (body: unknown, status = 200) =>
|
|
new Response(typeof body === 'string' ? body : JSON.stringify(body), {
|
|
status,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
})
|
|
|
|
const issue = (over: Record<string, unknown> = {}) => ({
|
|
number: 1,
|
|
title: 'x',
|
|
body: '---\nid: T1\n---\n',
|
|
state: 'open',
|
|
created_at: '2026-08-20T10:00:00Z',
|
|
labels: [{ name: 'ready', color: '0e8a16' }],
|
|
...over,
|
|
})
|
|
|
|
describe('github-api (issue tracker layer)', () => {
|
|
afterEach(() => vi.restoreAllMocks())
|
|
|
|
it('listIssues filters out pull-request entries and normalizes to the contract', async () => {
|
|
const calls: FetchCall[] = []
|
|
const api = createApi({
|
|
base: 'https://api.github.com',
|
|
token: 'tok',
|
|
repository: 'fsolla/teqo',
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init })
|
|
return ok([issue({ number: 1 }), issue({ number: 2, pull_request: { url: 'x' } })])
|
|
},
|
|
})
|
|
const issues = await api.listIssues({ state: 'open' })
|
|
expect(issues).toHaveLength(1)
|
|
expect(issues[0].number).toBe(1)
|
|
expect(issues[0].state).toBe('OPEN')
|
|
expect(issues[0].createdAt).toBe('2026-08-20T10:00:00Z')
|
|
expect(issues[0].labels).toEqual([{ name: 'ready', color: '0e8a16' }])
|
|
expect(calls[0].url).toContain('/repos/fsolla/teqo/issues?state=open&per_page=100&page=1')
|
|
})
|
|
|
|
it('getIssue normalizes a single issue', async () => {
|
|
const api = createApi({
|
|
token: 'tok',
|
|
fetchImpl: async () => ok(issue({ number: 42 })),
|
|
})
|
|
const got = await api.getIssue(42)
|
|
expect(got?.number).toBe(42)
|
|
expect(got?.state).toBe('OPEN')
|
|
})
|
|
|
|
it('createIssue POSTs title+body and returns the normalized issue', async () => {
|
|
const calls: FetchCall[] = []
|
|
const api = createApi({
|
|
token: 'tok',
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init })
|
|
return ok(issue({ number: 7 }))
|
|
},
|
|
})
|
|
const created = await api.createIssue({ title: 'T', body: 'B' })
|
|
expect(created.number).toBe(7)
|
|
expect(calls[0].init?.method).toBe('POST')
|
|
expect(JSON.parse(calls[0].init?.body ?? '{}')).toEqual({ title: 'T', body: 'B' })
|
|
})
|
|
|
|
it('setLabels removes by name then adds', async () => {
|
|
const calls: FetchCall[] = []
|
|
const api = createApi({
|
|
token: 'tok',
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init })
|
|
return ok([])
|
|
},
|
|
})
|
|
await api.setLabels(5, { add: ['done'], remove: ['in-progress'] })
|
|
expect(calls.map((c) => c.url)).toEqual([
|
|
'https://api.github.com/repos/fsolla/teqo/issues/5/labels/in-progress',
|
|
'https://api.github.com/repos/fsolla/teqo/issues/5/labels',
|
|
])
|
|
expect(calls[0].init?.method).toBe('DELETE')
|
|
expect(calls[1].init?.method).toBe('POST')
|
|
expect(JSON.parse(calls[1].init?.body ?? '{}')).toEqual({ labels: ['done'] })
|
|
})
|
|
|
|
it('closeIssue PATCHes state=closed', async () => {
|
|
const calls: FetchCall[] = []
|
|
const api = createApi({
|
|
token: 'tok',
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init })
|
|
return ok(issue({ state: 'closed' }))
|
|
},
|
|
})
|
|
await api.closeIssue(9)
|
|
expect(calls[0].url).toContain('/issues/9')
|
|
expect(calls[0].init?.method).toBe('PATCH')
|
|
expect(JSON.parse(calls[0].init?.body ?? '{}')).toEqual({ state: 'closed' })
|
|
})
|
|
|
|
it('workflowDispatch posts inputs and ref', async () => {
|
|
const calls: FetchCall[] = []
|
|
const api = createApi({
|
|
token: 'tok',
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init })
|
|
return ok({})
|
|
},
|
|
})
|
|
await api.workflowDispatch('agent-pool.yml', { ref: 'main', inputs: { action: 'start' } })
|
|
expect(calls[0].url).toContain('/actions/workflows/agent-pool.yml/dispatches')
|
|
expect(JSON.parse(calls[0].init?.body ?? '{}')).toEqual({
|
|
ref: 'main',
|
|
inputs: { action: 'start' },
|
|
})
|
|
})
|
|
|
|
it('getFileContents base64-decodes content', async () => {
|
|
const api = createApi({
|
|
token: 'tok',
|
|
fetchImpl: async () => ok({ content: Buffer.from('olá mundo').toString('base64'), sha: 's' }),
|
|
})
|
|
const file = await api.getFileContents('a/b.md', 'main')
|
|
expect(file?.content).toBe('olá mundo')
|
|
expect(file?.sha).toBe('s')
|
|
})
|
|
})
|