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,28imageApiDialect: null,29referenceImages: [],30n: 1,31batchFile: null,32jobs: null,33json: false,34help: false,35...overrides,36};37}3839function useEnv(40t: TestContext,41values: Record<string, string | null>,42): void {43const previous = new Map<string, string | undefined>();44for (const [key, value] of Object.entries(values)) {45previous.set(key, process.env[key]);46if (value == null) {47delete process.env[key];48} else {49process.env[key] = value;50}51}5253t.after(() => {54for (const [key, value] of previous.entries()) {55if (value == null) {56delete process.env[key];57} else {58process.env[key] = value;59}60}61});62}6364async function makeTempPng(t: TestContext, name: string): Promise<string> {65const dir = await fs.mkdtemp(path.join(os.tmpdir(), "seedream-test-"));66t.after(() => fs.rm(dir, { recursive: true, force: true }));6768const filePath = path.join(dir, name);69const png1x1 =70"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+a7m0AAAAASUVORK5CYII=";71await fs.writeFile(filePath, Buffer.from(png1x1, "base64"));72return filePath;73}7475test("Seedream request body and default extensions follow official model capabilities", () => {76const five = buildRequestBody(77"A robot illustrator",78"doubao-seedream-5-0-260128",79makeArgs(),80);81assert.equal(five.size, "2K");82assert.equal(five.response_format, "url");83assert.equal(five.output_format, "png");84assert.equal(getDefaultOutputExtension("doubao-seedream-5-0-260128"), ".png");8586const fourFive = buildRequestBody(87"A robot illustrator",88"doubao-seedream-4-5-251128",89makeArgs(),90);91assert.equal(fourFive.size, "2K");92assert.equal(fourFive.response_format, "url");93assert.ok(!("output_format" in fourFive));94assert.equal(getDefaultOutputExtension("doubao-seedream-4-5-251128"), ".jpg");9596assert.throws(97() =>98buildRequestBody(99"Change the bubbles into hearts",100"doubao-seededit-3-0-i2i-250628",101makeArgs({ referenceImages: ["ref.png"] }),102"data:image/png;base64,AAAA",103),104/no longer supported/,105);106});107108test("Seedream size selection validates model-specific presets", () => {109assert.equal(110resolveSeedreamSize("doubao-seedream-4-0-250828", makeArgs({ quality: "normal" })),111"1K",112);113assert.equal(114resolveSeedreamSize("doubao-seedream-3-0-t2i-250415", makeArgs({ quality: "2k" })),115"2048x2048",116);117118assert.throws(119() =>120resolveSeedreamSize("doubao-seedream-5-0-260128", makeArgs({ size: "4K" })),121/only supports 2K, 3K/,122);123assert.throws(124() =>125resolveSeedreamSize("doubao-seedream-3-0-t2i-250415", makeArgs({ imageSize: "2K" })),126/only supports explicit WxH sizes/,127);128assert.throws(129() =>130resolveSeedreamSize("doubao-seededit-3-0-i2i-250628", makeArgs({ size: "1024x1024" })),131/no longer supported/,132);133});134135test("Seedream reference-image support is model-specific", () => {136assert.doesNotThrow(() =>137validateArgs(138"doubao-seedream-5-0-260128",139makeArgs({ referenceImages: ["a.png", "b.png"] }),140),141);142143assert.throws(144() =>145validateArgs(146"doubao-seedream-3-0-t2i-250415",147makeArgs({ referenceImages: ["a.png"] }),148),149/does not support reference images/,150);151152assert.throws(153() =>154validateArgs(155"doubao-seededit-3-0-i2i-250628",156makeArgs(),157),158/no longer supported/,159);160161assert.throws(162() =>163validateArgs(164"ep-20260315171508-t8br2",165makeArgs({ referenceImages: ["a.png"] }),166),167/require a known model ID/,168);169});170171test("Seedream image input encodes local references as data URLs", async (t) => {172const refOne = await makeTempPng(t, "one.png");173const refTwo = await makeTempPng(t, "two.png");174175const single = await buildImageInput("doubao-seedream-4-5-251128", [refOne]);176assert.match(String(single), /^data:image\/png;base64,/);177178const multiple = await buildImageInput("doubao-seedream-5-0-260128", [refOne, refTwo]);179assert.ok(Array.isArray(multiple));180assert.equal(multiple.length, 2);181});182183test("Seedream generateImage posts the documented response_format and downloads the returned URL", async (t) => {184useEnv(t, { ARK_API_KEY: "test-key", SEEDREAM_BASE_URL: null });185186const originalFetch = globalThis.fetch;187t.after(() => {188globalThis.fetch = originalFetch;189});190191const calls: Array<{192input: string;193init?: RequestInit;194}> = [];195196globalThis.fetch = async (input, init) => {197calls.push({198input: String(input),199init,200});201202if (calls.length === 1) {203return Response.json({204model: "doubao-seedream-4-5-251128",205created: 1740000000,206data: [207{208url: "https://example.com/generated-image",209size: "2048x2048",210},211],212usage: {213generated_images: 1,214output_tokens: 1,215total_tokens: 1,216},217});218}219220return new Response(Uint8Array.from([7, 8, 9]), {221status: 200,222headers: { "Content-Type": "image/jpeg" },223});224};225226const image = await generateImage(227"A robot illustrator",228"doubao-seedream-4-5-251128",229makeArgs(),230);231232assert.deepEqual([...image], [7, 8, 9]);233assert.equal(calls.length, 2);234assert.equal(235calls[0]?.input,236"https://ark.cn-beijing.volces.com/api/v3/images/generations",237);238239const requestBody = JSON.parse(String(calls[0]?.init?.body)) as Record<string, unknown>;240assert.equal(requestBody.model, "doubao-seedream-4-5-251128");241assert.equal(requestBody.size, "2K");242assert.equal(requestBody.response_format, "url");243assert.ok(!("output_format" in requestBody));244assert.equal(calls[1]?.input, "https://example.com/generated-image");245});246