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 <[email protected]>
72 lines
2.6 KiB
TypeScript
72 lines
2.6 KiB
TypeScript
// @vitest-environment node
|
|
import { describe, expect, it } from 'vitest'
|
|
import { buildResponse, parseQuery, TYPE_TXT } from './dns-message.ts'
|
|
import { encodeQuery } from './test-query.ts'
|
|
|
|
const NAME = 'latest.technitium-console.pages.dev'
|
|
|
|
describe('parseQuery', () => {
|
|
it('parses the question Technitium sends', () => {
|
|
const q = parseQuery(encodeQuery('Latest.Technitium-Console.pages.dev', 16, 0, true, 1))
|
|
expect(q).toMatchObject({ id: 0, rd: true, name: NAME, type: 16 })
|
|
expect(q?.question.length).toBe(1 + 6 + 1 + 18 + 1 + 5 + 1 + 3 + 1 + 4)
|
|
})
|
|
|
|
it('refuses what it cannot read', () => {
|
|
expect(parseQuery(new Uint8Array(5))).toBeNull()
|
|
|
|
const two = encodeQuery(NAME, 16)
|
|
new DataView(two.buffer).setUint16(4, 2)
|
|
expect(parseQuery(two)).toBeNull()
|
|
|
|
const long = encodeQuery(NAME, 16)
|
|
long[12] = 60
|
|
expect(parseQuery(long)).toBeNull()
|
|
|
|
// RFC 1035: a name is at most 255 bytes on the wire.
|
|
expect(parseQuery(encodeQuery(Array(50).fill('abcdefgh').join('.'), 16))).toBeNull()
|
|
|
|
const pointer = encodeQuery(NAME, 16)
|
|
pointer[12] = 0xc0
|
|
expect(parseQuery(pointer)).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('buildResponse', () => {
|
|
const query = parseQuery(encodeQuery(NAME, TYPE_TXT, 0x1234, true, 1))!
|
|
|
|
it('answers with one TXT', () => {
|
|
const text = 'v=1 latest=1.3.2 server=15.5'
|
|
const r = buildResponse(query, 0, { text, ttl: 3600 })
|
|
const view = new DataView(r.buffer)
|
|
expect(view.getUint16(0)).toBe(0x1234)
|
|
expect(view.getUint16(2)).toBe(0x8580)
|
|
expect(view.getUint16(4)).toBe(1)
|
|
expect(view.getUint16(6)).toBe(1)
|
|
expect(view.getUint16(8)).toBe(0)
|
|
expect(view.getUint16(10)).toBe(0)
|
|
expect([...r.slice(12, 12 + query.question.length)]).toEqual([...query.question])
|
|
const at = 12 + query.question.length
|
|
expect([...r.slice(at, at + 10)]).toEqual([0xc0, 0x0c, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10])
|
|
expect(view.getUint16(at + 10)).toBe(29)
|
|
expect(r[at + 12]).toBe(28)
|
|
expect(new TextDecoder().decode(r.slice(at + 13))).toBe(text)
|
|
})
|
|
|
|
it('answers NXDOMAIN with no records', () => {
|
|
const r = buildResponse(query, 3)
|
|
const view = new DataView(r.buffer)
|
|
expect(view.getUint16(2) & 0x000f).toBe(3)
|
|
expect(view.getUint16(6)).toBe(0)
|
|
expect(r.length).toBe(12 + query.question.length)
|
|
})
|
|
|
|
it('splits long text', () => {
|
|
const r = buildResponse(query, 0, { text: 'x'.repeat(300), ttl: 60 })
|
|
const at = 12 + query.question.length + 12
|
|
expect(r[at]).toBe(255)
|
|
expect(r[at + 1 + 255]).toBe(45)
|
|
expect(r.length).toBe(at + 1 + 255 + 1 + 45)
|
|
})
|
|
})
|