Files
technitium-console/update-check/src/source.test.ts
T
DenozordecandClaude Sonnet 5.5 f3531c64f9 Move the console onto ReUI PRO
Install ReUI (MCP, skill, @reui registry, Tailwind v4, shadcn base-nova) and move
the shared UI onto it: Button, Tag, Check, Input/Textarea, Segmented, Alert, Panel,
Dialog, Tooltip, Menu and Select (Popover), Toast (sonner), the Table frame
(DataGrid), the Dashboard KPI strip, the shnt Ops palette, Geist and the type scale.

Design only: behaviour, texts and roles are unchanged. Tests that asserted the old
DOM were updated; the tests now format numbers in English wherever they run.
docs/reui-migration-plan.md records the plan and its decisions.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:06:06 +07:00

109 lines
4.3 KiB
TypeScript

// @vitest-environment node
import { describe, expect, it } from 'vitest'
import { formatRecord, readRelease, type Release } from './source.ts'
const RELEASES = 'https://github.com/byGarcia/technitium-console/releases/latest'
const TOKEN = 'https://ghcr.io/token?scope=repository:bygarcia/technitium-console:pull'
interface Fake {
tag?: string
pkg?: unknown
pkgStatus?: number
image?: number
/** The digest `latest` points at; by default the same as the release's. */
latestDigest?: string
releases?: number
}
function fakeFetch({
tag = 'v1.4.0',
pkg = { technitiumServerMinimum: '15.5' },
pkgStatus = 200,
image = 200,
latestDigest = 'sha256:new',
releases = 302,
}: Fake) {
const calls: { url: string; init?: RequestInit }[] = []
const fn = (async (input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input)
calls.push({ url, init })
if (url === RELEASES) {
if (releases !== 302) return new Response('', { status: releases })
return new Response(null, {
status: 302,
headers: { location: `https://github.com/byGarcia/technitium-console/releases/tag/${tag}` },
})
}
if (url === `https://raw.githubusercontent.com/byGarcia/technitium-console/${tag}/package.json`) {
return pkgStatus === 200 ? Response.json(pkg) : new Response('', { status: pkgStatus })
}
if (url === TOKEN) return Response.json({ token: 'anon' })
if (url.endsWith('/manifests/latest')) {
return new Response(null, { status: 200, headers: { 'docker-content-digest': latestDigest } })
}
if (url.startsWith('https://ghcr.io/v2/bygarcia/technitium-console/manifests/')) {
return new Response(null, { status: image, headers: image === 200 ? { 'docker-content-digest': 'sha256:new' } : {} })
}
return new Response('', { status: 404 })
}) as typeof fetch
return { fn, calls }
}
const previous: Release = { latest: '1.3.2', server: '15.5' }
describe('readRelease', () => {
it('reads the latest release and its server minimum', async () => {
const { fn, calls } = fakeFetch({})
const r = await readRelease(fn, null)
expect(r).toEqual({ latest: '1.4.0', server: '15.5' })
expect(formatRecord(r!)).toBe('v=1 latest=1.4.0 server=15.5')
expect(calls.find((c) => c.url === RELEASES)?.init?.redirect).toBe('manual')
const head = calls.find((c) => c.url.endsWith('/manifests/1.4.0'))
expect(head?.init?.method).toBe('HEAD')
expect(new Headers(head?.init?.headers).get('authorization')).toBe('Bearer anon')
expect(calls.some((c) => c.url.includes('api.github.com'))).toBe(false)
})
it('announces the previous release while the image is missing', async () => {
const { fn } = fakeFetch({ image: 404 })
expect(await readRelease(fn, previous)).toEqual(previous)
})
/* The image job pushes X.Y.Z first and moves latest only after checking the
bytes of every platform; the README's compose file pulls latest. */
it('announces the previous release until latest points at its image', async () => {
const { fn } = fakeFetch({ latestDigest: 'sha256:old' })
expect(await readRelease(fn, previous)).toEqual(previous)
})
it('announces nothing for a tag without the field', async () => {
const { fn } = fakeFetch({ pkg: {} })
expect(await readRelease(fn, previous)).toBeNull()
})
it('refuses a malformed tag or minimum', async () => {
expect(await readRelease(fakeFetch({ tag: 'v1.4' }).fn, null)).toBeNull()
expect(await readRelease(fakeFetch({ pkg: { technitiumServerMinimum: '15.5; rm' } }).fn, null)).toBeNull()
})
it('throws when GitHub fails', async () => {
await expect(readRelease(fakeFetch({ releases: 503 }).fn, null)).rejects.toThrow()
})
/* A rate limit is a failure, not an answer: the caller must keep serving
what it had. raw.githubusercontent.com limits per IP, and Cloudflare's
outgoing addresses are shared. */
it('throws when GitHub rate-limits the releases page', async () => {
await expect(readRelease(fakeFetch({ releases: 429 }).fn, previous)).rejects.toThrow()
})
it('throws when GitHub rate-limits the raw file', async () => {
await expect(readRelease(fakeFetch({ pkgStatus: 429 }).fn, previous)).rejects.toThrow()
})
it('announces nothing when the tag has no package.json', async () => {
expect(await readRelease(fakeFetch({ pkgStatus: 404 }).fn, previous)).toBeNull()
})
})