@@ -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,
|
||||
'文字,水印,logo,UI界面,对话框,边框,人物近景特写,多人合照,模糊,低清晰度,畸形建筑,现代车辆,监控摄像头',
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user