import { describe, expect, it } from 'vitest' import { applySchemaLabels, ensurePmStateRepo, repoExists, SCHEMA_LABELS } from './bootstrap.js' import { GiteaApiError } from './types.js' import type { FetchLike } from './types.js' describe('SCHEMA_LABELS', () => { it('covers the full vocabulary: 5 estimates + 4 priorities + hard deadline', () => { const names = SCHEMA_LABELS.map((l) => l.name) expect(names).toEqual(['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d', 'p/1', 'p/2', 'p/3', 'p/4', 'deadline/hard']) // colors are hex without '#' for (const l of SCHEMA_LABELS) expect(l.color).toMatch(/^[0-9a-f]{6}$/) }) }) describe('applySchemaLabels', () => { it('creates only the missing labels (idempotent)', async () => { const created: string[] = [] const client = { listLabels: async () => [ { id: 1, name: 'est/1d' }, { id: 2, name: 'p/1' }, ], createLabel: async (input: { name: string; color: string }) => { created.push(input.name) return { id: 99, name: input.name } }, } const res = await applySchemaLabels(client) expect(res.existing).toEqual(['est/1d', 'p/1']) expect(res.created).toEqual(['est/2d', 'est/3d', 'est/5d', 'est/8d', 'p/2', 'p/3', 'p/4', 'deadline/hard']) expect(created).toEqual(res.created) // exactly those createLabel calls, no more }) it('creates nothing when the schema is already applied', async () => { const client = { listLabels: async () => SCHEMA_LABELS.map((l, i) => ({ id: i, name: l.name })), createLabel: async () => { throw new Error('should not create') }, } const res = await applySchemaLabels(client) expect(res.created).toEqual([]) expect(res.existing).toHaveLength(SCHEMA_LABELS.length) }) }) /** A fetch stub routing repo-exists (GET) and repo-create (POST). */ function stub(opts: { exists?: boolean; createStatus?: number }): { fetch: FetchLike; posts: { url: string }[] } { const posts: { url: string }[] = [] const fetch: FetchLike = (url, init) => { const method = init?.method ?? 'GET' if (method === 'GET') { return Promise.resolve({ ok: !!opts.exists, status: opts.exists ? 200 : 404, json: () => Promise.resolve({}), text: () => Promise.resolve('') }) } posts.push({ url }) const status = opts.createStatus ?? 201 return Promise.resolve({ ok: status < 400, status, json: () => Promise.resolve({}), text: () => Promise.resolve('') }) } return { fetch, posts } } describe('ensurePmStateRepo', () => { const conn = { baseUrl: 'https://gitea.example.io', token: 'pat' } it('returns "exists" and creates nothing when the repo is already there', async () => { const { fetch, posts } = stub({ exists: true }) const r = await ensurePmStateRepo(conn, { owner: 'christian', repo: 'commitea-pm-state', underOrg: false }, fetch) expect(r).toBe('exists') expect(posts).toEqual([]) }) it('creates a personal repo via /user/repos when the owner is the token user', async () => { const { fetch, posts } = stub({ exists: false }) const r = await ensurePmStateRepo(conn, { owner: 'christian', repo: 'commitea-pm-state', underOrg: false }, fetch) expect(r).toBe('created') expect(posts[0].url).toBe('https://gitea.example.io/api/v1/user/repos') }) it('creates an org repo via /orgs/{owner}/repos when the owner is an org', async () => { const { fetch, posts } = stub({ exists: false }) const r = await ensurePmStateRepo(conn, { owner: 'NovelPad', repo: 'novelpad-pm-state', underOrg: true }, fetch) expect(r).toBe('created') expect(posts[0].url).toBe('https://gitea.example.io/api/v1/orgs/NovelPad/repos') }) it('surfaces a non-404 read failure as a GiteaApiError', async () => { const fetch: FetchLike = () => Promise.resolve({ ok: false, status: 403, json: () => Promise.resolve({}), text: () => Promise.resolve('') }) await expect(repoExists(conn, 'x', 'y', fetch)).rejects.toBeInstanceOf(GiteaApiError) }) })