* fix(draw-image): per-process output TMP in integration tests * fix(draw-image): per-process output TMP in e2e tests * fix(draw-image): per-process output TMP in cleanup test * docs(handoff): add handoff and ADR for beforeAll output TMP fix * docs(handoff): set PR number * docs(handoff): set PR number 186 --------- Co-authored-by: opencode-agent <agent@opencode.local>
75 lines
2.7 KiB
TypeScript
75 lines
2.7 KiB
TypeScript
import { describe, test, expect, beforeAll, afterAll } from "vitest"
|
|
import { existsSync, readFileSync, rmSync, mkdirSync, writeFileSync, mkdtempSync } from "node:fs"
|
|
import path from "node:path"
|
|
import os from "node:os"
|
|
import { fileURLToPath } from "node:url"
|
|
import { spawnSync } from "node:child_process"
|
|
import { loadBrand } from "../src/config"
|
|
import { loadTemplate, buildSvg } from "../src/render"
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const DRAW_IMAGE_DIR = path.resolve(__dirname, "..")
|
|
const TMP = path.join(os.tmpdir(), `draw-image-optional-integration-${process.pid}`)
|
|
|
|
beforeAll(() => {
|
|
if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true })
|
|
mkdirSync(TMP, { recursive: true })
|
|
})
|
|
|
|
afterAll(() => {
|
|
if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true })
|
|
})
|
|
|
|
function renderToPng(svg: string, outPath: string): void {
|
|
const tmpDir = mkdtempSync(path.join(os.tmpdir(), `draw-image-test-${process.pid}-`))
|
|
const tmpSvg = path.join(tmpDir, "input.svg")
|
|
try {
|
|
writeFileSync(tmpSvg, svg)
|
|
const r = spawnSync("node", [path.join(DRAW_IMAGE_DIR, "render.mjs"), tmpSvg, outPath], {
|
|
encoding: "utf-8",
|
|
})
|
|
if (r.status !== 0) throw new Error(`render.mjs failed: ${r.stderr || r.stdout}`)
|
|
} finally {
|
|
if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true, force: true })
|
|
}
|
|
}
|
|
|
|
function assertPng(filePath: string) {
|
|
expect(existsSync(filePath), `PNG not found: ${filePath}`).toBe(true)
|
|
const buf = readFileSync(filePath)
|
|
expect(buf[0]).toBe(0x89)
|
|
expect(buf[1]).toBe(0x50)
|
|
expect(buf[2]).toBe(0x4e)
|
|
expect(buf[3]).toBe(0x47)
|
|
const width = buf.readUInt32BE(16)
|
|
const height = buf.readUInt32BE(20)
|
|
expect(width, `PNG width should be 1024, got ${width}`).toBe(1024)
|
|
expect(height, `PNG height should be 1024, got ${height}`).toBe(1024)
|
|
}
|
|
|
|
describe("integration — optional subtitle and badge", () => {
|
|
test("render without subtitle and without badge → valid PNG", () => {
|
|
const brand = loadBrand(DRAW_IMAGE_DIR)
|
|
const templateSvg = loadTemplate(DRAW_IMAGE_DIR, "cover")
|
|
const args = { template: "cover", title: "Bare" }
|
|
const svg = buildSvg(templateSvg, brand, args, DRAW_IMAGE_DIR)
|
|
const outPath = path.join(TMP, "bare.png")
|
|
renderToPng(svg, outPath)
|
|
assertPng(outPath)
|
|
})
|
|
|
|
test("render with subtitle and badge → valid PNG", () => {
|
|
const brand = loadBrand(DRAW_IMAGE_DIR)
|
|
const templateSvg = loadTemplate(DRAW_IMAGE_DIR, "cover")
|
|
const args = {
|
|
template: "cover",
|
|
title: "Full",
|
|
subtitle: "Sub Text",
|
|
slots: { badge: "mic" },
|
|
}
|
|
const svg = buildSvg(templateSvg, brand, args, DRAW_IMAGE_DIR)
|
|
const outPath = path.join(TMP, "full.png")
|
|
renderToPng(svg, outPath)
|
|
assertPng(outPath)
|
|
})
|
|
})
|