Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Generate images via OpenAI, Google, OpenRouter, DashScope, Jimeng, Seedream, and Replicate APIs with batch support.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/providers/seedream.test.ts
1import assert from "node:assert/strict";2import fs from "node:fs/promises";3import os from "node:os";4import path from "node:path";5import test, { type TestContext } from "node:test";67import type { CliArgs } from "../types.ts";8import {9buildImageInput,10buildRequestBody,11generateImage,12getDefaultOutputExtension,13resolveSeedreamSize,14validateArgs,15} from "./seedream.ts";1617function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {18return {19prompt: null,20promptFiles: [],21imagePath: null,22provider: null,23model: null,24aspectRatio: null,25size: null,26quality: null,27imageSize: null,28referenceImages: [],29n: 1,30batchFile: null,31jobs: null,32json: false,33help: false,34...overrides,35};36}3738function useEnv(39t: TestContext,40values: Record<string, string | null>,41): void {42const previous = new Map<string, string | undefined>();43for (const [key, value] of Object.entries(values)) {44previous.set(key, process.env[key]);45if (value == null) {46delete process.env[key];47} else {48process.env[key] = value;49}50}5152t.after(() => {53for (const [key, value] of previous.entries()) {54if (value == null) {55delete process.env[key];56} else {57process.env[key] = value;58}59}60});61}6263async function makeTempPng(t: TestContext, name: string): Promise<string> {64const dir = await fs.mkdtemp(path.join(os.tmpdir(), "seedream-test-"));65t.after(() => fs.rm(dir, { recursive: true, force: true }));6667const filePath = path.join(dir, name);68const png1x1 =69"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+a7m0AAAAASUVORK5CYII=";70await fs.writeFile(filePath, Buffer.from(png1x1, "base64"));71return filePath;72}7374test("Seedream request body and default extensions follow official model capabilities", () => {75const five = buildRequestBody(76"A robot illustrator",77"doubao-seedream-5-0-260128",78makeArgs(),79);80assert.equal(five.size, "2K");81assert.equal(five.response_format, "url");82assert.equal(five.output_format, "png");83assert.equal(getDefaultOutputExtension("doubao-seedream-5-0-260128"), ".png");8485const fourFive = buildRequestBody(86"A robot illustrator",87"doubao-seedream-4-5-251128",88makeArgs(),89);90assert.equal(fourFive.size, "2K");91assert.equal(fourFive.response_format, "url");92assert.ok(!("output_format" in fourFive));93assert.equal(getDefaultOutputExtension("doubao-seedream-4-5-251128"), ".jpg");9495assert.throws(96() =>97buildRequestBody(98"Change the bubbles into hearts",99"doubao-seededit-3-0-i2i-250628",100makeArgs({ referenceImages: ["ref.png"] }),101"data:image/png;base64,AAAA",102),103/no longer supported/,104);105});106107test("Seedream size selection validates model-specific presets", () => {108assert.equal(109resolveSeedreamSize("doubao-seedream-4-0-250828", makeArgs({ quality: "normal" })),110"1K",111);112assert.equal(113resolveSeedreamSize("doubao-seedream-3-0-t2i-250415", makeArgs({ quality: "2k" })),114"2048x2048",115);116117assert.throws(118() =>119resolveSeedreamSize("doubao-seedream-5-0-260128", makeArgs({ size: "4K" })),120/only supports 2K, 3K/,121);122assert.throws(123() =>124resolveSeedreamSize("doubao-seedream-3-0-t2i-250415", makeArgs({ imageSize: "2K" })),125/only supports explicit WxH sizes/,126);127assert.throws(128() =>129resolveSeedreamSize("doubao-seededit-3-0-i2i-250628", makeArgs({ size: "1024x1024" })),130/no longer supported/,131);132});133134test("Seedream reference-image support is model-specific", () => {135assert.doesNotThrow(() =>136validateArgs(137"doubao-seedream-5-0-260128",138makeArgs({ referenceImages: ["a.png", "b.png"] }),139),140);141142assert.throws(143() =>144validateArgs(145"doubao-seedream-3-0-t2i-250415",146makeArgs({ referenceImages: ["a.png"] }),147),148/does not support reference images/,149);150151assert.throws(152() =>153validateArgs(154"doubao-seededit-3-0-i2i-250628",155makeArgs(),156),157/no longer supported/,158);159160assert.throws(161() =>162validateArgs(163"ep-20260315171508-t8br2",164makeArgs({ referenceImages: ["a.png"] }),165),166/require a known model ID/,167);168});169170test("Seedream image input encodes local references as data URLs", async (t) => {171const refOne = await makeTempPng(t, "one.png");172const refTwo = await makeTempPng(t, "two.png");173174const single = await buildImageInput("doubao-seedream-4-5-251128", [refOne]);175assert.match(String(single), /^data:image\/png;base64,/);176177const multiple = await buildImageInput("doubao-seedream-5-0-260128", [refOne, refTwo]);178assert.ok(Array.isArray(multiple));179assert.equal(multiple.length, 2);180});181182test("Seedream generateImage posts the documented response_format and downloads the returned URL", async (t) => {183useEnv(t, { ARK_API_KEY: "test-key", SEEDREAM_BASE_URL: null });184185const originalFetch = globalThis.fetch;186t.after(() => {187globalThis.fetch = originalFetch;188});189190const calls: Array<{191input: string;192init?: RequestInit;193}> = [];194195globalThis.fetch = async (input, init) => {196calls.push({197input: String(input),198init,199});200201if (calls.length === 1) {202return Response.json({203model: "doubao-seedream-4-5-251128",204created: 1740000000,205data: [206{207url: "https://example.com/generated-image",208size: "2048x2048",209},210],211usage: {212generated_images: 1,213output_tokens: 1,214total_tokens: 1,215},216});217}218219return new Response(Uint8Array.from([7, 8, 9]), {220status: 200,221headers: { "Content-Type": "image/jpeg" },222});223};224225const image = await generateImage(226"A robot illustrator",227"doubao-seedream-4-5-251128",228makeArgs(),229);230231assert.deepEqual([...image], [7, 8, 9]);232assert.equal(calls.length, 2);233assert.equal(234calls[0]?.input,235"https://ark.cn-beijing.volces.com/api/v3/images/generations",236);237238const requestBody = JSON.parse(String(calls[0]?.init?.body)) as Record<string, unknown>;239assert.equal(requestBody.model, "doubao-seedream-4-5-251128");240assert.equal(requestBody.size, "2K");241assert.equal(requestBody.response_format, "url");242assert.ok(!("output_format" in requestBody));243assert.equal(calls[1]?.input, "https://example.com/generated-image");244});245