1
This commit is contained in:
@@ -2030,10 +2030,169 @@ test('profile dashboard aggregates wallet, play time and played works at the acc
|
||||
});
|
||||
});
|
||||
|
||||
test('profile save archives list worlds by last played time and can resume a selected archive', async () => {
|
||||
await withTestServer('profile-save-archives', async ({ baseUrl }) => {
|
||||
const user = await authEntry(baseUrl, 'archive_user', 'secret123');
|
||||
|
||||
const firstSaveResponse = await httpRequest(
|
||||
`${baseUrl}/api/runtime/save/snapshot`,
|
||||
withBearer(user.token, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
savedAt: '2026-04-19T08:00:00.000Z',
|
||||
bottomTab: 'adventure',
|
||||
currentStory: {
|
||||
text: '潮声还在旧灯塔下回荡。',
|
||||
options: [],
|
||||
},
|
||||
gameState: {
|
||||
worldType: 'CUSTOM',
|
||||
playerCurrency: 120,
|
||||
runtimeStats: {
|
||||
playTimeMs: 5400000,
|
||||
},
|
||||
storyEngineMemory: {
|
||||
continueGameDigest: '回到裂潮边城的旧灯塔继续追查假航灯。',
|
||||
},
|
||||
customWorldProfile: {
|
||||
id: 'world-aurora',
|
||||
name: '裂潮边城',
|
||||
summary: '潮声与城线之间的冷铁边疆。',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
assert.equal(firstSaveResponse.status, 200);
|
||||
|
||||
const secondSaveResponse = await httpRequest(
|
||||
`${baseUrl}/api/runtime/save/snapshot`,
|
||||
withBearer(user.token, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
savedAt: '2026-04-19T10:15:00.000Z',
|
||||
bottomTab: 'inventory',
|
||||
currentStory: {
|
||||
text: '江湖新章的风雨夜刚刚开始。',
|
||||
options: [],
|
||||
},
|
||||
gameState: {
|
||||
worldType: 'WUXIA',
|
||||
playerCurrency: 86,
|
||||
runtimeStats: {
|
||||
playTimeMs: 900000,
|
||||
},
|
||||
currentScenePreset: {
|
||||
name: '江湖新章',
|
||||
summary: '雨夜客栈里的新委托。',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
assert.equal(secondSaveResponse.status, 200);
|
||||
|
||||
const listResponse = await httpRequest(
|
||||
`${baseUrl}/api/runtime/profile/save-archives`,
|
||||
withBearer(user.token),
|
||||
);
|
||||
const listPayload = (await listResponse.json()) as {
|
||||
entries: Array<{
|
||||
worldKey: string;
|
||||
worldName: string;
|
||||
summaryText: string;
|
||||
lastPlayedAt: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
assert.equal(listResponse.status, 200);
|
||||
assert.deepEqual(
|
||||
listPayload.entries.map((entry) => entry.worldKey),
|
||||
['builtin:WUXIA', 'custom:world-aurora'],
|
||||
);
|
||||
assert.equal(listPayload.entries[0]?.worldName, '江湖新章');
|
||||
assert.equal(
|
||||
listPayload.entries[1]?.summaryText,
|
||||
'回到裂潮边城的旧灯塔继续追查假航灯。',
|
||||
);
|
||||
assert.equal(
|
||||
listPayload.entries[0]?.lastPlayedAt,
|
||||
'2026-04-19T10:15:00.000Z',
|
||||
);
|
||||
|
||||
const resumeResponse = await httpRequest(
|
||||
`${baseUrl}/api/runtime/profile/save-archives/${encodeURIComponent('custom:world-aurora')}`,
|
||||
withBearer(user.token, {
|
||||
method: 'POST',
|
||||
}),
|
||||
);
|
||||
const resumePayload = (await resumeResponse.json()) as {
|
||||
entry: {
|
||||
worldKey: string;
|
||||
};
|
||||
snapshot: {
|
||||
bottomTab: string;
|
||||
gameState: {
|
||||
playerCurrency: number;
|
||||
customWorldProfile: {
|
||||
id: string;
|
||||
name: string;
|
||||
} | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
assert.equal(resumeResponse.status, 200);
|
||||
assert.equal(resumePayload.entry.worldKey, 'custom:world-aurora');
|
||||
assert.equal(resumePayload.snapshot.bottomTab, 'adventure');
|
||||
assert.equal(resumePayload.snapshot.gameState.playerCurrency, 120);
|
||||
assert.equal(
|
||||
resumePayload.snapshot.gameState.customWorldProfile?.id,
|
||||
'world-aurora',
|
||||
);
|
||||
|
||||
const currentSnapshotResponse = await httpRequest(
|
||||
`${baseUrl}/api/runtime/save/snapshot`,
|
||||
withBearer(user.token),
|
||||
);
|
||||
const currentSnapshotPayload = (await currentSnapshotResponse.json()) as {
|
||||
bottomTab: string;
|
||||
gameState: {
|
||||
playerCurrency: number;
|
||||
customWorldProfile: {
|
||||
id: string;
|
||||
} | null;
|
||||
};
|
||||
};
|
||||
|
||||
assert.equal(currentSnapshotResponse.status, 200);
|
||||
assert.equal(currentSnapshotPayload.bottomTab, 'adventure');
|
||||
assert.equal(currentSnapshotPayload.gameState.playerCurrency, 120);
|
||||
assert.equal(
|
||||
currentSnapshotPayload.gameState.customWorldProfile?.id,
|
||||
'world-aurora',
|
||||
);
|
||||
|
||||
const dashboardResponse = await httpRequest(
|
||||
`${baseUrl}/api/runtime/profile/dashboard`,
|
||||
withBearer(user.token),
|
||||
);
|
||||
const dashboardPayload = (await dashboardResponse.json()) as {
|
||||
walletBalance: number;
|
||||
totalPlayTimeMs: number;
|
||||
playedWorldCount: number;
|
||||
};
|
||||
|
||||
assert.equal(dashboardResponse.status, 200);
|
||||
assert.equal(dashboardPayload.walletBalance, 86);
|
||||
assert.equal(dashboardPayload.totalPlayTimeMs, 6300000);
|
||||
assert.equal(dashboardPayload.playedWorldCount, 2);
|
||||
});
|
||||
});
|
||||
|
||||
test('custom worlds stay private until published and then appear in the public gallery', async () => {
|
||||
await withTestServer('custom-world-gallery', async ({ baseUrl }) => {
|
||||
const owner = await authEntry(baseUrl, 'gallery_owner', 'secret123');
|
||||
const viewer = await authEntry(baseUrl, 'gallery_viewer', 'secret123');
|
||||
|
||||
const upsertResponse = await httpRequest(
|
||||
`${baseUrl}/api/runtime/custom-world-library/world-a`,
|
||||
@@ -2084,15 +2243,11 @@ test('custom worlds stay private until published and then appear in the public g
|
||||
|
||||
const galleryBeforePublish = await httpRequest(
|
||||
`${baseUrl}/api/runtime/custom-world-gallery`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${viewer.token}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
const galleryBeforePayload = (await galleryBeforePublish.json()) as {
|
||||
entries: unknown[];
|
||||
};
|
||||
assert.equal(galleryBeforePublish.status, 200);
|
||||
assert.deepEqual(galleryBeforePayload.entries, []);
|
||||
|
||||
const publishResponse = await httpRequest(
|
||||
@@ -2114,11 +2269,6 @@ test('custom worlds stay private until published and then appear in the public g
|
||||
|
||||
const galleryAfterPublish = await httpRequest(
|
||||
`${baseUrl}/api/runtime/custom-world-gallery`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${viewer.token}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
const galleryAfterPayload = (await galleryAfterPublish.json()) as {
|
||||
entries: Array<{
|
||||
@@ -2139,11 +2289,6 @@ test('custom worlds stay private until published and then appear in the public g
|
||||
|
||||
const galleryDetail = await httpRequest(
|
||||
`${baseUrl}/api/runtime/custom-world-gallery/${encodeURIComponent(galleryAfterPayload.entries[0]?.ownerUserId || '')}/${encodeURIComponent(galleryAfterPayload.entries[0]?.profileId || '')}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${viewer.token}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
const galleryDetailPayload = (await galleryDetail.json()) as {
|
||||
entry: {
|
||||
@@ -2175,11 +2320,6 @@ test('custom worlds stay private until published and then appear in the public g
|
||||
|
||||
const galleryAfterUnpublish = await httpRequest(
|
||||
`${baseUrl}/api/runtime/custom-world-gallery`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${viewer.token}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
const galleryAfterUnpublishPayload =
|
||||
(await galleryAfterUnpublish.json()) as {
|
||||
|
||||
Reference in New Issue
Block a user