import assert from "node:assert/strict"; import { fileURLToPath } from "node:url"; import { test } from "node:test"; import { build } from "esbuild"; import { parseHTML } from "linkedom"; import { createHash } from "node:crypto"; const out = await build({ stdin: { contents: `export * from './hash'; export * from './presets'; export * from './queue'; export * from './backends/swarm'; export * from './backends/a1111'; export * from './backends/comfy'; export * from '../player/renderer';`, resolveDir: fileURLToPath(new URL("../src/imager/", import.meta.url)), loader: "ts" }, bundle: true, write: false, format: "esm", platform: "node", logLevel: "silent" }); const { DEFAULT_PRESET: preset, validatePreset, presetHash, cacheKey, ImageQueue, SwarmBackend, A1111Backend, ComfyBackend, workflow, Renderer, sha256Fallback } = await import(`data:text/javascript;base64,${Buffer.from(out.outputFiles[0].text).toString("base64")}`); const tick = () => new Promise((r) => setTimeout(r, 0)); async function until(check) { for (let i = 0; i < 200; i++) { if (check()) return; await tick(); } assert.fail("timed out"); } function store(tasks = []) { const journal = new Map(tasks.map((t) => [t.key, structuredClone(t)])), images = new Map(); return { journal, images, get: async (k) => images.get(k), put: async (i) => { images.set(i.key, i); }, tasks: async () => [...journal.values()].map((t) => structuredClone(t)), save: async (t) => { journal.set(t.key, structuredClone(t)); } }; } const image = (t) => ({ key: t.key, version: t.version, path: t.path, blob: new Blob(['png']), scale: 2, width: 20, height: 10 }); test("plain HTTP hash fallback matches SHA-256 for empty, Unicode and multiple blocks", () => { for (const text of ['', 'abc', 'Апскейл 🎨', 'x'.repeat(55), 'x'.repeat(56), 'x'.repeat(64), 'x'.repeat(2000)]) { assert.equal(sha256Fallback(new TextEncoder().encode(text)), createHash('sha256').update(text).digest('hex')); } }); test("preset validation and stable cache identity include source, version, backend and actual parameters", async () => { const hash = await presetHash(preset); assert.equal(hash, await presetHash({ ...preset, name: "Renamed" })); for (const changed of [{ scale: 4 }, { url: "http://127.0.0.1:8000" }, { seed: 1 }, { creativity: .3 }, { backend: "a1111" }]) assert.notEqual(hash, await presetHash({ ...preset, ...changed })); assert.notEqual(cacheKey(1, 'a|b', 'c', hash), cacheKey(1, 'a', 'b|c', hash)); assert.notEqual(cacheKey(1, 'a', 'old', hash), cacheKey(1, 'a', 'new', hash)); for (const bad of [{ scale: 0 }, { scale: NaN }, { creativity: 2 }, { steps: 1.5 }, { seed: -1 }, { url: 'javascript:alert(1)' }, { backend: "unknown" }]) assert.throws(() => validatePreset({ ...preset, ...bad })); }); test("one GPU task, concurrent deduplication and cancelling does not overlap remote work", async () => { const s = store(); const releases = []; let active = 0, maximum = 0; const q = new ImageQueue(s, async (t) => { maximum = Math.max(maximum, ++active); await new Promise((r) => releases.push(r)); active--; return image(t); }); const [key, duplicate] = await Promise.all([q.enqueue(1, 'a.png', 'a', '/a', preset), q.enqueue(1, 'a.png', 'a', '/a', preset)]); assert.equal(key, duplicate); assert.equal(q.snapshot().length, 1); await until(() => releases.length === 1); const second = await q.enqueue(1, 'b.png', 'b', '/b', preset); q.cancel(key); await tick(); assert.equal(releases.length, 1); releases.shift()(); await until(() => releases.length === 1); releases.shift()(); await until(() => q.snapshot().find((t) => t.key === second).status === 'done'); assert.equal(maximum, 1); assert.equal(s.images.has(key), false); assert.equal(s.images.has(second), true); assert.equal(s.journal.get(key).status, 'cancelled'); }); test("failed generation and storage quota are retryable; reload never starts the GPU automatically", async () => { const s = store(); let fail = true, calls = 0; const q = new ImageQueue(s, async (t) => { calls++; if (fail) throw new Error('GPU offline'); return image(t); }); const key = await q.enqueue(1, 'a.png', 'a', '/a', preset); await until(() => q.snapshot()[0].status === 'failed'); assert.match(q.snapshot()[0].error, /offline/); fail = false; await q.retry(key); await until(() => q.snapshot()[0].status === 'done'); assert.equal(calls, 2); const task = { ...q.snapshot()[0], status: 'running' }; const recovered = new ImageQueue(store([task]), async () => assert.fail('must resume explicitly')); await recovered.restore(); assert.equal(recovered.snapshot()[0].status, 'cancelled'); const quota = store(); quota.put = async () => { throw new Error('QuotaExceededError'); }; const noSpace = new ImageQueue(quota, async (t) => image(t)); await noSpace.enqueue(1, 'x.png', 'x', '/x', preset); await until(() => noSpace.snapshot()[0].status === 'failed'); assert.match(noSpace.snapshot()[0].error, /Quota/); }); test("cached task bypasses GPU and cache loss permits regeneration", async () => { const s = store(); let calls = 0; const q = new ImageQueue(s, async (t) => { calls++; return image(t); }); const key = await q.enqueue(1, 'a.png', 'a', '/a', preset); await until(() => q.snapshot()[0].status === 'done'); await q.enqueue(1, 'a.png', 'a', '/a', preset); assert.equal(calls, 1); s.images.clear(); await q.enqueue(1, 'a.png', 'a', '/a', preset); await until(() => q.snapshot()[0].status === 'done'); assert.equal(calls, 2); }); test("Swarm creates a session, refreshes invalid session once and understands current model tuples", async () => { const original = globalThis.fetch; let sessions = 0, lists = 0; globalThis.fetch = async (url, options) => { const body = JSON.parse(options.body); if (url.endsWith('GetNewSession')) { sessions++; return Response.json({ session_id: 'session' + sessions }); } assert.ok(body.session_id); if (++lists === 1) return Response.json({ error: 'Session expired', error_id: 'invalid_session_id' }); return Response.json({ list: [{ id: 'refinerupscalemethod', values: ['pixel-lanczos///Lanczos', 'model-4x.pth'] }], models: { 'Stable-Diffusion': [['sdxl.safetensors', 'sdxl']] } }); }; try { const b = new SwarmBackend('http://127.0.0.1:7801'); const models = await b.models(); assert.equal(sessions, 2); assert.deepEqual(models.models, ['sdxl.safetensors']); assert.equal(models.upscalers[0], 'pixel-lanczos'); } finally { globalThis.fetch = original; } }); test("Comfy upscale is a pixel-only graph; img2img adds the sampling graph", () => { const upscale = workflow('input.png', { ...preset, upscaler: '4x.pth' }, 100, 50); assert.ok(Object.values(upscale).some((n) => n.class_type === 'ImageUpscaleWithModel')); assert.ok(!Object.values(upscale).some((n) => n.class_type === 'KSampler' || n.class_type === 'VAEEncode')); assert.equal(upscale['4'].inputs.width, 200); const diffusion = workflow('input.png', { ...preset, mode: 'img2img', model: 'sdxl' }, 100, 50); assert.equal(diffusion['9'].inputs.denoise, preset.creativity); assert.deepEqual(diffusion['11'].inputs.images, ['10', 0]); }); test("backend wire protocols send the source and return downloadable results", async () => { const original = { fetch: globalThis.fetch, FileReader: globalThis.FileReader, createImageBitmap: globalThis.createImageBitmap }; globalThis.FileReader = class { readAsDataURL(blob) { blob.arrayBuffer().then((buffer) => { this.result = 'data:image/png;base64,' + Buffer.from(buffer).toString('base64'); this.onload(); }); } }; globalThis.createImageBitmap = async () => ({ width: 64, height: 128, close() {} }); const calls = []; globalThis.fetch = async (url, options) => { const path = new URL(url).pathname; const body = typeof options?.body === 'string' ? JSON.parse(options.body) : options?.body; calls.push({ path, body }); if (String(url).startsWith('data:')) return original.fetch(url); if (path.endsWith('/GetNewSession')) return Response.json({ session_id: 's' }); if (path.endsWith('/GenerateText2Image')) return Response.json({ images: ['View/generated.png'] }); if (path.endsWith('/extra-single-image')) return Response.json({ image: 'AAAA' }); if (path.endsWith('/img2img')) return Response.json({ images: ['AAAA'] }); if (path.endsWith('/upload/image')) return Response.json({ name: 'source.png', subfolder: '', type: 'temp' }); if (path.endsWith('/prompt')) return Response.json({ prompt_id: 'job', node_errors: {} }); if (path.endsWith('/history/job')) return Response.json({ job: { outputs: { '11': { images: [{ filename: 'output.png', subfolder: '', type: 'temp' }] } } } }); return new Response(new Blob(['result'], { type: 'image/png' })); }; try { const source = new Blob(['original'], { type: 'image/png' }); const swarm = new SwarmBackend('http://127.0.0.1:7801'); assert.equal(await (await swarm.process(source, { ...preset, model: 'sdxl' })).text(), 'result'); const swarmBody = calls.find((c) => c.path.endsWith('/GenerateText2Image')).body; assert.equal(swarmBody.refinermethod, 'PostApply'); assert.equal(swarmBody.refinercontrolpercentage, 0); assert.equal(swarmBody.initimagecreativity, 0); assert.equal(swarmBody.width, 64); assert.equal(swarmBody.height, 128); assert.equal(swarmBody.refinerupscale, 2); assert.match(swarmBody.initimage, /^data:image\/png;base64,/); assert.equal(swarmBody.donotsave, true); const a = new A1111Backend('http://127.0.0.1:7860'); await a.process(source, { ...preset, upscaler: 'ESRGAN' }); assert.equal(calls.find((c) => c.path.endsWith('/extra-single-image')).body.upscaler_1, 'ESRGAN'); await a.process(source, { ...preset, mode: 'img2img', model: 'sdxl' }); const aBody = calls.find((c) => c.path.endsWith('/img2img')).body; assert.equal(aBody.width, 128); assert.equal(aBody.height, 256); assert.equal(aBody.denoising_strength, preset.creativity); assert.equal(aBody.override_settings.sd_model_checkpoint, 'sdxl'); const comfy = new ComfyBackend('http://127.0.0.1:8188'); await comfy.process(source, preset); const graph = calls.find((c) => c.path === '/prompt').body.prompt; assert.equal(graph['1'].inputs.image, 'source.png [temp]'); assert.equal(graph['4'].inputs.width, 128); assert.ok(calls.some((c) => c.path === '/view')); } finally { Object.assign(globalThis, original); } }); test("renderer compensates natural size but leaves explicit and ATL dimensions intact", () => { const { document } = parseHTML('
'); const previous = globalThis.document; globalThis.document = document; try { const renderer = new Renderer(document.querySelector('main'), 1920, 1080, { url: () => 'blob:improved', assetScale: () => 2 }); const img = renderer.displayable({ file: 'a.png' }); Object.defineProperty(img, 'naturalWidth', { value: 800 }); img.dispatchEvent(new document.defaultView.Event('load')); assert.equal(img.width, 400); const sized = renderer.displayable({ file: 'a.png', size: [80, 40] }); sized.dispatchEvent(new document.defaultView.Event('load')); assert.equal(sized.style.width, '80px'); const atl = renderer.displayable({ file: 'a.png' }); atl.style.width = '100px'; atl.dispatchEvent(new document.defaultView.Event('load')); assert.equal(atl.style.width, '100px'); assert.equal(atl.hasAttribute('width'), false); } finally { globalThis.document = previous; } });