@@ -1,12 +1,12 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
||||
import fs from 'node:fs';
|
||||
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import type { AppContext } from '../context.js';
|
||||
import { type AppConfig } from '../config.js';
|
||||
import type { AppContext } from '../context.js';
|
||||
import { generateSceneImage } from './sceneImageService.js';
|
||||
|
||||
const PNG_BUFFER = Buffer.from(
|
||||
@@ -24,7 +24,7 @@ function createTestConfig(
|
||||
dashScope: {
|
||||
baseUrl: dashScopeBaseUrl,
|
||||
apiKey: 'test-dashscope-key',
|
||||
imageModel: 'wan2.7-image',
|
||||
imageModel: 'wan2.2-t2i-flash',
|
||||
requestTimeoutMs: 5_000,
|
||||
},
|
||||
} as AppConfig;
|
||||
@@ -92,11 +92,8 @@ async function withHttpServer<T>(
|
||||
}
|
||||
}
|
||||
|
||||
test('generateSceneImage uploads a public reference image as a data url and saves the generated scene', async () => {
|
||||
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 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;
|
||||
@@ -164,7 +161,6 @@ test('generateSceneImage uploads a public reference image as a data url and save
|
||||
profileId: 'world-1',
|
||||
landmarkName: '旧港灯塔',
|
||||
landmarkId: 'landmark-1',
|
||||
referenceImageSrc: '/scene_bg/reference-layout.png',
|
||||
});
|
||||
|
||||
assert.equal(result.ok, true);
|
||||
@@ -177,6 +173,7 @@ test('generateSceneImage uploads a public reference image as a data url and save
|
||||
assert.ok(createRequest?.bodyText);
|
||||
|
||||
const createPayload = JSON.parse(createRequest.bodyText) as {
|
||||
model: string;
|
||||
input: {
|
||||
messages: Array<{
|
||||
content: Array<{ text?: string; image?: string }>;
|
||||
@@ -188,8 +185,9 @@ test('generateSceneImage uploads a public reference image as a data url and save
|
||||
};
|
||||
|
||||
const content = createPayload.input.messages[0]?.content ?? [];
|
||||
assert.equal(createPayload.model, 'wan2.2-t2i-flash');
|
||||
assert.equal(content[0]?.text, '海雾港口像素风场景');
|
||||
assert.match(content[1]?.image ?? '', /^data:image\/png;base64,/u);
|
||||
assert.equal(content.length, 1);
|
||||
assert.equal(createPayload.parameters.negative_prompt, '模糊');
|
||||
|
||||
const savedImagePath = path.join(tempRoot, 'public', result.imageSrc.slice(1));
|
||||
@@ -197,3 +195,105 @@ test('generateSceneImage uploads a public reference image as a data url and save
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
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: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: [
|
||||
{
|
||||
image: `${baseUrl}/downloads/reference-scene.png`,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method === 'GET' && url.pathname === '/downloads/reference-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, {
|
||||
prompt: '废墟月台像素风场景',
|
||||
negativePrompt: '模糊',
|
||||
size: '1280*720',
|
||||
worldName: '碎轨边境',
|
||||
profileId: 'world-2',
|
||||
landmarkName: '裂轨月台',
|
||||
landmarkId: 'landmark-2',
|
||||
referenceImageSrc: '/scene_bg/reference-layout.png',
|
||||
});
|
||||
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.model, 'qwen-image-2.0');
|
||||
assert.match(result.taskId, /^scene-edit-/u);
|
||||
assert.equal(
|
||||
capturedRequests.some(
|
||||
(entry) => entry.pathname === '/api/v1/tasks/scene-task-1',
|
||||
),
|
||||
false,
|
||||
);
|
||||
|
||||
const createRequest = capturedRequests.find(
|
||||
(entry) =>
|
||||
entry.pathname === '/api/v1/services/aigc/multimodal-generation/generation',
|
||||
);
|
||||
assert.ok(createRequest?.bodyText);
|
||||
|
||||
const createPayload = JSON.parse(createRequest.bodyText) as {
|
||||
model: string;
|
||||
input: {
|
||||
messages: Array<{
|
||||
content: Array<{ text?: string; image?: string }>;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
const content = createPayload.input.messages[0]?.content ?? [];
|
||||
assert.equal(createPayload.model, 'qwen-image-2.0');
|
||||
assert.match(content[0]?.image ?? '', /^data:image\/png;base64,/u);
|
||||
assert.equal(content[1]?.text, '废墟月台像素风场景');
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user