1
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-20 09:54:17 +08:00
parent 67c584b4df
commit 50759f3c1e
159 changed files with 16938 additions and 16925 deletions

View File

@@ -1,6 +1,10 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
import {
createServer,
type IncomingMessage,
type ServerResponse,
} from 'node:http';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
@@ -48,10 +52,9 @@ function sendJson(res: ServerResponse, payload: unknown) {
}
async function withHttpServer<T>(
buildHandler: (baseUrl: string) => (
req: IncomingMessage,
res: ServerResponse,
) => void | Promise<void>,
buildHandler: (
baseUrl: string,
) => (req: IncomingMessage, res: ServerResponse) => void | Promise<void>,
run: (baseUrl: string) => Promise<T>,
) {
let handler: (
@@ -93,7 +96,9 @@ async function withHttpServer<T>(
}
test('generateSceneImage uses wan2.2-t2i-flash text-to-image payload and saves the generated scene', async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genarrative-scene-image-'));
const tempRoot = fs.mkdtempSync(
path.join(os.tmpdir(), 'genarrative-scene-image-'),
);
const capturedRequests: Array<{
pathname: string;
@@ -104,7 +109,9 @@ test('generateSceneImage uses wan2.2-t2i-flash text-to-image payload and saves t
(baseUrl) => async (req, res) => {
const url = new URL(req.url || '/', baseUrl);
const bodyText =
req.method === 'POST' ? (await readRequestBody(req)).toString('utf8') : undefined;
req.method === 'POST'
? (await readRequestBody(req)).toString('utf8')
: undefined;
capturedRequests.push({
pathname: url.pathname,
bodyText,
@@ -122,7 +129,10 @@ test('generateSceneImage uses wan2.2-t2i-flash text-to-image payload and saves t
return;
}
if (req.method === 'GET' && url.pathname === '/api/v1/tasks/scene-task-1') {
if (
req.method === 'GET' &&
url.pathname === '/api/v1/tasks/scene-task-1'
) {
sendJson(res, {
output: {
task_status: 'SUCCEEDED',
@@ -168,7 +178,8 @@ test('generateSceneImage uses wan2.2-t2i-flash text-to-image payload and saves t
assert.equal(result.actualPrompt, '整理后的场景提示词');
const createRequest = capturedRequests.find(
(entry) => entry.pathname === '/api/v1/services/aigc/text2image/image-synthesis',
(entry) =>
entry.pathname === '/api/v1/services/aigc/text2image/image-synthesis',
);
assert.ok(createRequest?.bodyText);
@@ -186,17 +197,20 @@ test('generateSceneImage uses wan2.2-t2i-flash text-to-image payload and saves t
assert.equal(createPayload.input.negative_prompt, '模糊');
assert.equal(createPayload.parameters.size, '1280*720');
const savedImagePath = path.join(tempRoot, 'public', result.imageSrc.slice(1));
const savedImagePath = path.join(
tempRoot,
'public',
result.imageSrc.slice(1),
);
assert.equal(fs.existsSync(savedImagePath), true);
},
);
});
test('generateSceneImage uses qwen-image-2.0 edit flow when a reference image is provided', async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'genarrative-scene-image-'));
const publicDir = path.join(tempRoot, 'public');
fs.mkdirSync(path.join(publicDir, 'scene_bg'), { recursive: true });
fs.writeFileSync(path.join(publicDir, 'scene_bg', 'reference-layout.png'), PNG_BUFFER);
test('generateSceneImage builds the scene prompt on the server when the client only submits world and landmark context', async () => {
const tempRoot = fs.mkdtempSync(
path.join(os.tmpdir(), 'genarrative-scene-image-'),
);
const capturedRequests: Array<{
pathname: string;
@@ -207,7 +221,9 @@ test('generateSceneImage uses qwen-image-2.0 edit flow when a reference image is
(baseUrl) => async (req, res) => {
const url = new URL(req.url || '/', baseUrl);
const bodyText =
req.method === 'POST' ? (await readRequestBody(req)).toString('utf8') : undefined;
req.method === 'POST'
? (await readRequestBody(req)).toString('utf8')
: undefined;
capturedRequests.push({
pathname: url.pathname,
bodyText,
@@ -215,7 +231,134 @@ test('generateSceneImage uses qwen-image-2.0 edit flow when a reference image is
if (
req.method === 'POST' &&
url.pathname === '/api/v1/services/aigc/multimodal-generation/generation'
url.pathname === '/api/v1/services/aigc/text2image/image-synthesis'
) {
sendJson(res, {
output: {
task_id: 'scene-task-2',
},
});
return;
}
if (
req.method === 'GET' &&
url.pathname === '/api/v1/tasks/scene-task-2'
) {
sendJson(res, {
output: {
task_status: 'SUCCEEDED',
results: [
{
url: `${baseUrl}/downloads/scene.png`,
actual_prompt: '服务端整理后的像素风提示词',
},
],
},
});
return;
}
if (req.method === 'GET' && url.pathname === '/downloads/scene.png') {
res.statusCode = 200;
res.setHeader('Content-Type', 'image/png');
res.end(PNG_BUFFER);
return;
}
res.statusCode = 404;
res.end('not found');
},
async (dashScopeBaseUrl) => {
const context = {
config: createTestConfig(tempRoot, `${dashScopeBaseUrl}/api/v1`),
} as AppContext;
const result = await generateSceneImage(context, {
worldName: '',
profileId: '',
landmarkName: '',
landmarkId: '',
userPrompt: '想让灯塔更偏暴风夜',
profile: {
id: 'world-3',
name: '潮雾群岛',
subtitle: '迷雾海界',
summary: '岛链被旧航道和风暴一起缠住。',
tone: '潮湿、压迫、带着未知回声',
playerGoal: '先找到断线的引路火',
settingText: '玩家在海雾和旧航道之间寻找可以靠岸的线索。',
},
landmark: {
id: 'landmark-3',
name: '旧港灯塔',
description: '灯塔外墙被海盐侵蚀,塔下平台还能勉强落脚。',
dangerLevel: 'high',
},
});
assert.equal(result.ok, true);
const createRequest = capturedRequests.find(
(entry) =>
entry.pathname === '/api/v1/services/aigc/text2image/image-synthesis',
);
assert.ok(createRequest?.bodyText);
const createPayload = JSON.parse(createRequest.bodyText) as {
input: {
prompt: string;
negative_prompt?: string;
};
};
assert.match(createPayload.input.prompt, //u);
assert.match(createPayload.input.prompt, //u);
assert.match(
createPayload.input.prompt,
//u,
);
assert.match(createPayload.input.prompt, //u);
assert.equal(
createPayload.input.negative_prompt,
'文字水印logoUI界面对话框边框人物近景特写多人合照模糊低清晰度畸形建筑现代车辆监控摄像头',
);
},
);
});
test('generateSceneImage uses qwen-image-2.0 edit flow when a reference image is provided', async () => {
const tempRoot = fs.mkdtempSync(
path.join(os.tmpdir(), 'genarrative-scene-image-'),
);
const publicDir = path.join(tempRoot, 'public');
fs.mkdirSync(path.join(publicDir, 'scene_bg'), { recursive: true });
fs.writeFileSync(
path.join(publicDir, 'scene_bg', 'reference-layout.png'),
PNG_BUFFER,
);
const capturedRequests: Array<{
pathname: string;
bodyText?: string;
}> = [];
await withHttpServer(
(baseUrl) => async (req, res) => {
const url = new URL(req.url || '/', baseUrl);
const bodyText =
req.method === 'POST'
? (await readRequestBody(req)).toString('utf8')
: undefined;
capturedRequests.push({
pathname: url.pathname,
bodyText,
});
if (
req.method === 'POST' &&
url.pathname ===
'/api/v1/services/aigc/multimodal-generation/generation'
) {
sendJson(res, {
output: {
@@ -235,7 +378,10 @@ test('generateSceneImage uses qwen-image-2.0 edit flow when a reference image is
return;
}
if (req.method === 'GET' && url.pathname === '/downloads/reference-scene.png') {
if (
req.method === 'GET' &&
url.pathname === '/downloads/reference-scene.png'
) {
res.statusCode = 200;
res.setHeader('Content-Type', 'image/png');
res.end(PNG_BUFFER);
@@ -273,7 +419,8 @@ test('generateSceneImage uses qwen-image-2.0 edit flow when a reference image is
const createRequest = capturedRequests.find(
(entry) =>
entry.pathname === '/api/v1/services/aigc/multimodal-generation/generation',
entry.pathname ===
'/api/v1/services/aigc/multimodal-generation/generation',
);
assert.ok(createRequest?.bodyText);