「我的发布记录」新增发布生命周期中文徽标(草稿/已提交/审核中/已通过/ 已上架/已驳回/需补充材料/已撤回/已下架/已召回)+ nextAction/appealStatus 副文本。上架=审核通过自动 markListed(非生产者动作),生产者侧通过本徽标 看到 publish→review→list 进度反馈。 活体证(反假绿,MSW off 直连单体 48080): - market-publish.spec.ts 第 4 例 chromium 绿:断言 listed→「已上架」、 rejected→「已驳回」徽标渲染(种子 /tmp/SetReqStatus.java 置 req#2→listed、#3→rejected; curl my-publish-records 证状态反映)。 - market-publish 4/4 + vitest 47/47 + tsc 干净。 申诉(appeal)后端经 curl 验证为 real+fail-closed(自有已驳回资产→appealId/pending; 他人资产→1044000024 无权访问;不存在资产→1044000003 资产不存在),但申诉 UI 后置: 真实 gap——my-publish-records 暴露的 assetId 是 publish-record id,非 submitAppeal 所需的物化 muse_market_asset.id(资产仅在 admin 审核通过 markListed 时物化), UI 入口取不到正确 assetId,收口需后端额外暴露物化 assetId 或经真实审核通过物化流。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
102 lines
5.1 KiB
TypeScript
102 lines
5.1 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test';
|
||
|
||
/**
|
||
* 市场生产侧发布草稿 e2e(真实后端,MSW off)。
|
||
*
|
||
* 前置:vite 以 VITE_API_MOCK=false 运行(关 MSW)、真实 muse-server 在 48080。
|
||
* 目的:rendered studio 生产侧「发布到市场」表单 → 经唯一合法入口 POST /marketplace/publish-drafts
|
||
* 把资产写为 Shadow 发布草稿,并经「我的发布记录」(GET /marketplace/my-publish-records) 真实回读渲染(反假绿)。
|
||
* 自包含:savePublishDraft 按材料 hash 派生幂等 commandId,故同输入可重复跑(命中回放、不重复建),无需预置 fixture。
|
||
*/
|
||
const TOKEN = 'test1';
|
||
// 稳定标题 → 后端材料 hash 幂等键稳定 → 重复跑命中命令回放、不产生重复草稿。
|
||
const DRAFT_NAME = '活体发布草稿·e2e';
|
||
|
||
async function seedToken(page: Page): Promise<void> {
|
||
await page.addInitScript((t) => {
|
||
window.localStorage.setItem('accessToken', t);
|
||
window.localStorage.setItem('tenantId', '1');
|
||
}, TOKEN);
|
||
}
|
||
|
||
test.describe('市场生产侧发布草稿(真实后端, MSW off)', () => {
|
||
test('保存发布草稿→真打 publish-drafts→「我的发布记录」出现该资产', async ({ page }) => {
|
||
await seedToken(page);
|
||
await page.goto('/market/publish');
|
||
|
||
await page.getByPlaceholder(/在此处输入市场展示标题/).fill(DRAFT_NAME);
|
||
|
||
const [resp] = await Promise.all([
|
||
page.waitForResponse(
|
||
(r) => /\/marketplace\/publish-drafts$/.test(r.url()) && r.request().method() === 'POST',
|
||
{ timeout: 15_000 }
|
||
),
|
||
page.getByRole('button', { name: /保存发布草稿/ }).click(),
|
||
]);
|
||
|
||
// Yudao 口径:成功为 HTTP 200 + CommonResult{code:0};草稿落库返回 draftId + status=draft。
|
||
expect(resp.status()).toBe(200);
|
||
const body = await resp.json();
|
||
expect(body.code).toBe(0);
|
||
expect(typeof body.data?.draftId).toBe('number');
|
||
expect(body.data?.status).toBe('draft');
|
||
|
||
// 「我的发布记录」刷新后出现该草稿(证后端 my-publish-records 把草稿作为记录真实回读渲染)。
|
||
await expect(page.getByText(DRAFT_NAME).first()).toBeVisible({ timeout: 15_000 });
|
||
});
|
||
|
||
test('缺市场标题被后端拒绝(@NotBlank, 非成功业务码)', async ({ page }) => {
|
||
await seedToken(page);
|
||
// 用 page.request 直接打接口绕 UI:缺 name → 后端 @NotBlank 校验失败。
|
||
// WHY:page.request 绕开 client.ts,需手动带 X-API-Version / Authorization / tenant-id。
|
||
const resp = await page.request.post('/app-api/muse/marketplace/publish-drafts', {
|
||
headers: {
|
||
'X-API-Version': '1',
|
||
Authorization: `Bearer ${TOKEN}`,
|
||
'tenant-id': '1',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
data: { assetType: 'agent', sourceId: 1 }, // 故意缺 name
|
||
});
|
||
const body = await resp.json();
|
||
expect(body.code).not.toBe(0); // @NotBlank → 业务错误码(Yudao 包成 200+code≠0 或 400)
|
||
});
|
||
|
||
test('提交审核全链(保存草稿→运行检查→提交申请)→申请 status=submitted', async ({ page }) => {
|
||
await seedToken(page);
|
||
await page.goto('/market/publish');
|
||
|
||
// 唯一标题:全链会 consumePassedCheck(消费检查),用唯一名每次产新 draft/check/request → 可重复跑(不被回放卡死)。
|
||
const uniqueName = `活体上架·e2e-${Date.now()}`;
|
||
await page.getByPlaceholder(/在此处输入市场展示标题/).fill(uniqueName);
|
||
// licenseType + 权利声明 已有默认值(发布检查硬门槛),无需额外填写即可通过检查。
|
||
|
||
// 点「提交审核」触发 save→check→submit 三段链;等待最后一段 publish-requests 的真实写入响应。
|
||
const [submitResp] = await Promise.all([
|
||
page.waitForResponse(
|
||
(r) => /\/marketplace\/publish-requests$/.test(r.url()) && r.request().method() === 'POST',
|
||
{ timeout: 20_000 }
|
||
),
|
||
page.getByRole('button', { name: /提交审核/ }).click(),
|
||
]);
|
||
|
||
expect(submitResp.status()).toBe(200);
|
||
const submitBody = await submitResp.json();
|
||
expect(submitBody.code).toBe(0);
|
||
expect(submitBody.data?.status).toBe('submitted');
|
||
expect(String(submitBody.data?.requestId ?? '')).not.toBe('');
|
||
|
||
// 提交后该资产以 submitted 态进入「我的发布记录」(证 my-publish-records 真实回读已提交申请)。
|
||
await expect(page.getByText(uniqueName).first()).toBeVisible({ timeout: 15_000 });
|
||
});
|
||
|
||
// 上架状态可视化(读):上架=审核通过自动 markListed(生产者无主动上架动作),生产者侧通过「我的发布记录」
|
||
// 的生命周期徽标看到 已上架/已驳回 等终态。前置:/tmp/SetReqStatus.java 已把两条发布申请置为 listed / rejected。
|
||
test('我的发布记录渲染发布生命周期徽标(已上架/已驳回)', async ({ page }) => {
|
||
await seedToken(page);
|
||
await page.goto('/market/publish');
|
||
await expect(page.getByText('已上架').first()).toBeVisible({ timeout: 15_000 });
|
||
await expect(page.getByText('已驳回').first()).toBeVisible({ timeout: 15_000 });
|
||
});
|
||
});
|