1
This commit is contained in:
@@ -4,96 +4,15 @@ import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
||||
import type { AppConfig } from '../config.js';
|
||||
import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js';
|
||||
import { CustomWorldAgentAutoAssetService } from './customWorldAgentAutoAssetService.js';
|
||||
import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js';
|
||||
import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js';
|
||||
import { createInMemoryRpgWorldRepositoryPorts } from './customWorldAgentRepositoryTestHelpers.js';
|
||||
import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js';
|
||||
import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js';
|
||||
import { listCustomWorldWorkSummaries } from './customWorldWorkSummaryService.js';
|
||||
|
||||
function createRuntimeRepositoryStub(): RuntimeRepositoryPort {
|
||||
const sessionsByUser = new Map<
|
||||
string,
|
||||
Map<string, CustomWorldSessionRecord>
|
||||
>();
|
||||
const profilesByUser = new Map<string, Record<string, unknown>[]>();
|
||||
|
||||
const getSessionBucket = (userId: string) => {
|
||||
const existing = sessionsByUser.get(userId);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const nextBucket = new Map<string, CustomWorldSessionRecord>();
|
||||
sessionsByUser.set(userId, nextBucket);
|
||||
return nextBucket;
|
||||
};
|
||||
|
||||
return {
|
||||
async getSnapshot(_userId) {
|
||||
return null;
|
||||
},
|
||||
async putSnapshot(_userId, _payload) {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
async deleteSnapshot(_userId) {
|
||||
return undefined;
|
||||
},
|
||||
async getSettings() {
|
||||
return {
|
||||
musicVolume: 0.42,
|
||||
platformTheme: 'light',
|
||||
};
|
||||
},
|
||||
async putSettings(_userId, settings) {
|
||||
return settings;
|
||||
},
|
||||
async listCustomWorldProfiles(userId) {
|
||||
return [...(profilesByUser.get(userId) ?? [])];
|
||||
},
|
||||
async upsertCustomWorldProfile(userId, profileId, profile) {
|
||||
const current = [...(profilesByUser.get(userId) ?? [])].filter(
|
||||
(item) => String(item.id ?? '') !== profileId,
|
||||
);
|
||||
current.unshift({
|
||||
...profile,
|
||||
id: profileId,
|
||||
});
|
||||
profilesByUser.set(userId, current);
|
||||
return current;
|
||||
},
|
||||
async deleteCustomWorldProfile(userId, profileId) {
|
||||
const current = [...(profilesByUser.get(userId) ?? [])].filter(
|
||||
(item) => String(item.id ?? '') !== profileId,
|
||||
);
|
||||
profilesByUser.set(userId, current);
|
||||
return current;
|
||||
},
|
||||
async listProfileSaveArchives() {
|
||||
return [];
|
||||
},
|
||||
async resumeProfileSaveArchive() {
|
||||
return null;
|
||||
},
|
||||
async listCustomWorldSessions(userId) {
|
||||
return [...getSessionBucket(userId).values()];
|
||||
},
|
||||
async getCustomWorldSession(userId, sessionId) {
|
||||
return getSessionBucket(userId).get(sessionId) ?? null;
|
||||
},
|
||||
async upsertCustomWorldSession(userId, sessionId, session) {
|
||||
getSessionBucket(userId).set(
|
||||
sessionId,
|
||||
JSON.parse(JSON.stringify(session)),
|
||||
);
|
||||
return JSON.parse(JSON.stringify(session));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createAutoAssetTestConfig(testName: string): AppConfig {
|
||||
const projectRoot = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), `genarrative-agent-phase3-${testName}-`),
|
||||
@@ -259,13 +178,28 @@ async function createReadySession(
|
||||
|
||||
assert.equal(readySession?.stage, 'foundation_review');
|
||||
assert.equal(readySession?.creatorIntentReadiness.isReady, true);
|
||||
assert.equal(readySession?.resultPreview, null);
|
||||
assert.equal(
|
||||
readySession?.supportedActions?.find(
|
||||
(entry) => entry.action === 'draft_foundation',
|
||||
)?.enabled,
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
readySession?.supportedActions?.find(
|
||||
(entry) => entry.action === 'sync_result_profile',
|
||||
)?.enabled,
|
||||
false,
|
||||
);
|
||||
|
||||
return readySession!;
|
||||
}
|
||||
|
||||
test('phase3 ready session can execute draft_foundation and expose card detail', async () => {
|
||||
const runtimeRepository = createRuntimeRepositoryStub();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(runtimeRepository);
|
||||
const { rpgAgentSessionRepository } = createInMemoryRpgWorldRepositoryPorts();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(
|
||||
rpgAgentSessionRepository,
|
||||
);
|
||||
const orchestrator = new CustomWorldAgentOrchestrator(sessionStore, null, {
|
||||
singleTurnLlmClient: createTestCustomWorldAgentSingleTurnLlmClient(),
|
||||
autoAssetService: createFallbackAutoAssetService('draft'),
|
||||
@@ -301,6 +235,21 @@ test('phase3 ready session can execute draft_foundation and expose card detail',
|
||||
assert.equal(operation?.status, 'completed');
|
||||
assert.equal(snapshot?.stage, 'object_refining');
|
||||
assert.ok(snapshot?.draftCards.length);
|
||||
assert.equal(snapshot?.resultPreview?.source, 'session_preview');
|
||||
assert.equal(
|
||||
snapshot?.resultPreview?.preview.name,
|
||||
typeof (snapshot?.draftProfile as Record<string, unknown>)?.name === 'string'
|
||||
? ((snapshot?.draftProfile as Record<string, unknown>).name as string)
|
||||
: '未命名世界底稿',
|
||||
);
|
||||
assert.ok(Array.isArray(snapshot?.resultPreview?.blockers));
|
||||
assert.ok((snapshot?.resultPreview?.blockers?.length ?? 0) >= 0);
|
||||
assert.equal(snapshot?.resultPreview?.publishReady, false);
|
||||
assert.equal(snapshot?.resultPreview?.canEnterWorld, false);
|
||||
assert.equal(
|
||||
snapshot?.resultPreview?.qualityFindings?.length,
|
||||
snapshot?.qualityFindings.length,
|
||||
);
|
||||
assert.ok(snapshot?.draftCards.some((card) => card.kind === 'world'));
|
||||
assert.ok(snapshot?.draftCards.some((card) => card.kind === 'faction'));
|
||||
assert.ok(snapshot?.draftCards.some((card) => card.kind === 'character'));
|
||||
@@ -335,6 +284,24 @@ test('phase3 ready session can execute draft_foundation and expose card detail',
|
||||
message.text.includes('第一版世界底稿整理出来了'),
|
||||
),
|
||||
);
|
||||
assert.equal(
|
||||
snapshot?.supportedActions?.find(
|
||||
(entry) => entry.action === 'update_draft_card',
|
||||
)?.enabled,
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
snapshot?.supportedActions?.find(
|
||||
(entry) => entry.action === 'generate_role_assets',
|
||||
)?.enabled,
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
snapshot?.supportedActions?.find(
|
||||
(entry) => entry.action === 'publish_world',
|
||||
)?.enabled,
|
||||
true,
|
||||
);
|
||||
|
||||
const worldCard = snapshot?.draftCards.find((card) => card.kind === 'world');
|
||||
assert.ok(worldCard);
|
||||
@@ -352,8 +319,10 @@ test('phase3 ready session can execute draft_foundation and expose card detail',
|
||||
});
|
||||
|
||||
test('phase3 draft_foundation rejects not-ready session', async () => {
|
||||
const runtimeRepository = createRuntimeRepositoryStub();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(runtimeRepository);
|
||||
const { rpgAgentSessionRepository } = createInMemoryRpgWorldRepositoryPorts();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(
|
||||
rpgAgentSessionRepository,
|
||||
);
|
||||
const orchestrator = new CustomWorldAgentOrchestrator(sessionStore, null, {
|
||||
singleTurnLlmClient: createTestCustomWorldAgentSingleTurnLlmClient(),
|
||||
autoAssetService: createFallbackAutoAssetService('not-ready'),
|
||||
@@ -373,8 +342,11 @@ test('phase3 draft_foundation rejects not-ready session', async () => {
|
||||
});
|
||||
|
||||
test('phase3 work summaries prefer compiled foundation draft fields', async () => {
|
||||
const runtimeRepository = createRuntimeRepositoryStub();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(runtimeRepository);
|
||||
const { rpgAgentSessionRepository, rpgWorldProfileRepository } =
|
||||
createInMemoryRpgWorldRepositoryPorts();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(
|
||||
rpgAgentSessionRepository,
|
||||
);
|
||||
const orchestrator = new CustomWorldAgentOrchestrator(sessionStore, null, {
|
||||
singleTurnLlmClient: createTestCustomWorldAgentSingleTurnLlmClient(),
|
||||
autoAssetService: createFallbackAutoAssetService('summary'),
|
||||
@@ -397,7 +369,7 @@ test('phase3 work summaries prefer compiled foundation draft fields', async () =
|
||||
);
|
||||
|
||||
const items = await listCustomWorldWorkSummaries(userId, {
|
||||
runtimeRepository,
|
||||
rpgWorldProfiles: rpgWorldProfileRepository,
|
||||
customWorldAgentSessions: sessionStore,
|
||||
});
|
||||
const draft = items.find((item) => item.sessionId === readySession.sessionId);
|
||||
@@ -423,8 +395,10 @@ test('phase3 work summaries prefer compiled foundation draft fields', async () =
|
||||
});
|
||||
|
||||
test('phase3 draft foundation still completes when auto asset generation fails', async () => {
|
||||
const runtimeRepository = createRuntimeRepositoryStub();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(runtimeRepository);
|
||||
const { rpgAgentSessionRepository } = createInMemoryRpgWorldRepositoryPorts();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(
|
||||
rpgAgentSessionRepository,
|
||||
);
|
||||
const autoAssetService = new CustomWorldAgentAutoAssetService(
|
||||
createAutoAssetTestConfig('asset-failure'),
|
||||
async () => {
|
||||
|
||||
Reference in New Issue
Block a user