oh-my-muse/muse-studio/e2e/market-publish.spec.ts
lili 0ec0955779 feat(studio): 市场发布上架状态可视化(生产者飞轮进度反馈)
「我的发布记录」新增发布生命周期中文徽标(草稿/已提交/审核中/已通过/
已上架/已驳回/需补充材料/已撤回/已下架/已召回)+ 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>
2026-06-15 05:36:36 -07:00

102 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 校验失败。
// WHYpage.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 });
});
});