studio 生产侧首切片(创作者飞轮起点):新增 /market/publish 发布表单(MarketPublish + useMarketPublish hook), 保存发布草稿经唯一合法入口 POST /marketplace/publish-drafts → my-publish-records 真实回读; MarketBrowse 加「我要发布资产」入口。market-publish.spec.ts(chromium, MSW off, 真连 48080)正+负绿, DB 证 muse_market_publish_draft 落库(status=draft);命令幂等键按材料 hash 派生→自包含可重复跑、无需预种。 附带修真实潜伏后端 bug(链路从未活体跑过):publish draft/check/request 的 (tenant_id,command_id) 被 V15 建为部分唯一索引(WHERE command_id IS NOT NULL),而各 mapper insertIgnore 发无谓词 ON CONFLICT (tenant_id,command_id)→PostgreSQL 无法用部分索引作仲裁器→savePublishDraft 500 (BadSqlGrammarException: no unique or exclusion constraint matching the ON CONFLICT specification)。 修=部分→完整唯一索引,对齐 muse_market_command/purchase 既有约定(V22 迁移 + muse_slice_live 已应用, 零 Java 改动/零重启)。遗留:handoff/appeal 等 market 表同类部分索引待对应流程活体时收口。 验证:整套 e2e 10/10 绿、vitest 47/47 无回归、tsc 绿。进度总账 §五C/§四/per-BC 已就地更新。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
65 lines
3.0 KiB
TypeScript
65 lines
3.0 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)
|
||
});
|
||
});
|