完成 2.0.0 S4 的外部 AI、admin、account 与 solo SQL 收口,并推进 S5 studio 市场/handoff/旧编辑器入口裁剪。S5 的前端单测、构建与 route-only e2e 已留证;MSW-off 创作主线 e2e 仍因本地后端活体启动口径待修正,已在总账标为未完成验收。
109 lines
4.9 KiB
TypeScript
109 lines
4.9 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
test.skip(true, '2.0.0 S5 单人版已移除市场、handoff 或多用户前端入口,本 spec 隔离保留待恢复');
|
|
|
|
/**
|
|
* 跨空间 handoff → content(asset_use)兑现端到端 e2e(真实后端,反假绿)。
|
|
*
|
|
* 前置:真实 muse-server 48080(含 P3 asset_use + V28 迁移)、muse_slice_live;global-setup #14 清 work4 的 asset_use precheck。
|
|
*
|
|
* 正路(API 全链):bind-precheck(targetOwner=content)拿授权摘要 → createHandoff(content/asset_use)签一次性 token →
|
|
* asset-use-precheck(market_asset + token):后端 verify(targetOwner=content)+consume → asset-uses 落使用事实(单表转 consumed)。
|
|
* 证 P3 token 红线端到端真生效(真 PG + 真 token);asset_use 不绑目标实体、不需 seed fixture(sourceAssetId=真实 asset)。
|
|
* 负路(API 直打):伪造 handoffToken 调 asset-use-precheck → 后端 verify 拒(code≠0)。证红线在 content 兑现侧后端真拦截。
|
|
*/
|
|
const API = 'http://localhost:48080/app-api/muse';
|
|
const AUTH = { Authorization: 'Bearer test1', 'tenant-id': '1', 'X-API-Version': '1' };
|
|
const WORK_ID = 4;
|
|
|
|
test('正路:市场资产 asset_use 兑现到作品(真实后端)', async ({ request }) => {
|
|
// 前提:asset 1 已授权(幂等 best-effort)
|
|
await request
|
|
.post(`${API}/marketplace/assets/1/purchase`, { headers: AUTH, data: { commandId: `e2e-acq-${Date.now()}` } })
|
|
.catch(() => undefined);
|
|
|
|
// 1. 来源侧 bind-precheck(targetOwner=content) → authorizationSummaryId
|
|
const bp = await request.post(`${API}/marketplace/assets/1/bind-precheck`, {
|
|
headers: AUTH,
|
|
data: { commandId: `e2e-bp-${Date.now()}`, targetOwner: 'content', targetAction: 'asset_use', targetWorkId: WORK_ID },
|
|
});
|
|
const bpBody = await bp.json();
|
|
expect(bpBody.code, `bind-precheck: ${JSON.stringify(bpBody)}`).toBe(0);
|
|
const authorizationSummaryId = bpBody.data.authorizationSummaryId;
|
|
const authorizationSnapshot = bpBody.data.authorizationSnapshot;
|
|
|
|
// 2. createHandoff(targetOwner=content/asset_use)签一次性 token
|
|
const ho = await request.post(`${API}/marketplace/handoffs`, {
|
|
headers: AUTH,
|
|
data: {
|
|
commandId: `e2e-ho-${Date.now()}`,
|
|
assetId: '1',
|
|
targetOwner: 'content',
|
|
targetAction: 'asset_use',
|
|
targetWorkId: WORK_ID,
|
|
authorizationSummaryId,
|
|
returnUrl: 'http://localhost/market',
|
|
},
|
|
});
|
|
const hoBody = await ho.json();
|
|
expect(hoBody.code, `createHandoff: ${JSON.stringify(hoBody)}`).toBe(0);
|
|
const token = hoBody.data?.handoffToken;
|
|
expect(token).toBeTruthy();
|
|
|
|
// 3. asset-use-precheck(market_asset + token):后端 verify(content/asset_use)+consume
|
|
const pc = await request.post(`${API}/works/${WORK_ID}/asset-use-prechecks`, {
|
|
headers: AUTH,
|
|
data: {
|
|
commandId: `e2e-pc-${Date.now()}`,
|
|
sourceType: 'market_asset',
|
|
handoffToken: token,
|
|
sourceId: '1',
|
|
sourceVersion: 1,
|
|
sourceStatus: 'available',
|
|
authorizationSummaryId: String(authorizationSummaryId),
|
|
authorizationSnapshotId: authorizationSnapshot ?? 'snap-1',
|
|
purposes: ['reference'],
|
|
},
|
|
});
|
|
const pcBody = await pc.json();
|
|
expect(pcBody.code, `asset-use-precheck: ${JSON.stringify(pcBody)}`).toBe(0);
|
|
const assetUsePrecheckId = pcBody.data?.assetUsePrecheckId;
|
|
expect(assetUsePrecheckId).toBeTruthy();
|
|
|
|
// 4. 读作品 revision(create 段乐观锁)
|
|
const workResp = await request.get(`${API}/works/${WORK_ID}`, { headers: AUTH });
|
|
const workBody = await workResp.json();
|
|
const expectedWorkRevision = typeof workBody.data?.revision === 'number' ? workBody.data.revision : 1;
|
|
|
|
// 5. asset-uses(凭 precheckId,落使用事实,单表转 consumed)
|
|
const au = await request.post(`${API}/works/${WORK_ID}/asset-uses`, {
|
|
headers: AUTH,
|
|
data: { commandId: `e2e-au-${Date.now()}`, assetUsePrecheckId, expectedWorkRevision },
|
|
});
|
|
const auBody = await au.json();
|
|
expect(auBody.code, `asset-uses: ${JSON.stringify(auBody)}`).toBe(0);
|
|
// 单表设计:assetUseId = precheckId(precheck 即事实载体)
|
|
expect(auBody.data?.assetUseId).toBe(assetUsePrecheckId);
|
|
});
|
|
|
|
test('负路:伪造 handoffToken 调 asset-use-precheck 被后端核验拒绝(真实后端 API)', async ({ request }) => {
|
|
// 直打后端:伪造 token(从未由 Market 签发),后端 verify 应拒,不落任何使用事实。
|
|
const resp = await request.post(`${API}/works/${WORK_ID}/asset-use-prechecks`, {
|
|
headers: AUTH,
|
|
data: {
|
|
commandId: `e2e-forge-${Date.now()}`,
|
|
sourceType: 'market_asset',
|
|
handoffToken: 'handoff_forged_invalid_token_000000000000',
|
|
sourceId: '1',
|
|
sourceVersion: 1,
|
|
sourceStatus: 'available',
|
|
authorizationSummaryId: '45',
|
|
authorizationSnapshotId: 'snap-1',
|
|
purposes: ['reference'],
|
|
},
|
|
});
|
|
// 后端核验拒绝:HTTP 4xx 或 CommonResult.code 非 0(CONTENT_ASSET_HANDOFF_UNAVAILABLE)。
|
|
const body = await resp.json().catch(() => ({ code: -1 }));
|
|
expect(body.code).not.toBe(0);
|
|
});
|