feat(account): studio 个人中心补齐市场记录三件套之三「我的发布」+ e2e
P3 真缺口补全(6/7):PersonalCenter 此前只有购买记录+授权记录,缺发布记录(账户台账视角)——补齐三件套对称。useAccountPublishRecords→GET /account/publish-records(AppPublishRecordRespVO:reviewStatus/marketStatus/blockingReason);PersonalCenter 加「我的发布」区。 查重结论:不与 market 模块 useMyPublishRecords 重复——后者是发布工作流操作台(草稿/提交/申诉,my-publish-records),本接口是账户中心历史台账(account/publish-records,member 模块,与购买/授权并列)。 验证:vitest account 3/3(加 publish-records URL+reviewStatus 契约)、tsc 0 错;新增 e2e account-publish-records.spec.ts 真后端绿(GET 200+「我的发布」渲染),全量 29/29 无回归。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9c84c05e62
commit
3dff0e1735
37
muse-studio/e2e/account-publish-records.spec.ts
Normal file
37
muse-studio/e2e/account-publish-records.spec.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { expect, test, type Page } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* 账户中心「我的发布」记录 e2e(真实后端,MSW off)。
|
||||
*
|
||||
* 前置:vite VITE_API_MOCK=false、真实 muse-server 48080、muse_slice_live(market-publish 旅程已落发布记录)。
|
||||
* 目的:个人中心补齐市场记录三件套之三——发布记录(账户台账视角,GET /account/publish-records),
|
||||
* 验真后端读通 + 「我的发布」区渲染(反假绿)。不依赖记录条数,故只断言契约与区块存在。
|
||||
*/
|
||||
const TOKEN = 'test1';
|
||||
|
||||
async function seedToken(page: Page): Promise<void> {
|
||||
await page.addInitScript((t) => {
|
||||
window.localStorage.setItem('accessToken', t);
|
||||
window.localStorage.setItem('tenantId', '1');
|
||||
}, TOKEN);
|
||||
}
|
||||
|
||||
test('账户中心展示我的发布记录(真实后端)', async ({ page }) => {
|
||||
await seedToken(page);
|
||||
|
||||
// 进个人中心,捕获真实发布记录读响应(打真后端,非 mock)。
|
||||
const [resp] = await Promise.all([
|
||||
page.waitForResponse(
|
||||
(r) => /\/account\/publish-records/.test(r.url()) && r.request().method() === 'GET',
|
||||
{ timeout: 15_000 }
|
||||
),
|
||||
page.goto('/account'),
|
||||
]);
|
||||
|
||||
// Yudao 口径:成功为 HTTP 200 + CommonResult{code:0}。
|
||||
expect(resp.status()).toBe(200);
|
||||
expect((await resp.json()).code).toBe(0);
|
||||
|
||||
// 「我的发布」区(市场记录三件套之三:购买/授权/发布)渲染。
|
||||
await expect(page.getByRole('heading', { name: '我的发布' })).toBeVisible({ timeout: 15_000 });
|
||||
});
|
||||
@ -3,6 +3,7 @@ import {
|
||||
useAccountEntitlements,
|
||||
useAccountLicenses,
|
||||
useAccountProfile,
|
||||
useAccountPublishRecords,
|
||||
useAccountPurchases,
|
||||
useAccountSecurityEvents,
|
||||
useAccountUsage,
|
||||
@ -25,6 +26,7 @@ export default function PersonalCenter() {
|
||||
const { data: usage } = useAccountUsage('month');
|
||||
const { data: purchases } = useAccountPurchases();
|
||||
const { data: licenses } = useAccountLicenses();
|
||||
const { data: publishRecords } = useAccountPublishRecords();
|
||||
const { data: securityEvents } = useAccountSecurityEvents();
|
||||
const acknowledgeEvent = useAcknowledgeSecurityEvent();
|
||||
const updateProfile = useUpdateAccountProfile();
|
||||
@ -164,6 +166,35 @@ export default function PersonalCenter() {
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section className="mt-6 rounded-3xl border border-slate-100 bg-white p-6 shadow-sm">
|
||||
<h2 className="text-base font-bold text-slate-800">我的发布</h2>
|
||||
{(publishRecords?.list?.length ?? 0) === 0 ? (
|
||||
<p className="mt-3 text-sm text-slate-400">暂无发布记录</p>
|
||||
) : (
|
||||
<ul className="mt-3 divide-y divide-slate-50">
|
||||
{publishRecords?.list?.map((rec) => (
|
||||
<li key={rec.recordId} className="flex items-center justify-between gap-3 py-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="rounded-md bg-slate-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">
|
||||
{rec.assetType}
|
||||
</span>
|
||||
<strong className="truncate text-sm font-semibold text-slate-800">{rec.assetName}</strong>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-slate-400">
|
||||
市场状态:{rec.marketStatus ?? '—'}
|
||||
{rec.blockingReason ? ` · 阻塞:${rec.blockingReason}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<span className="shrink-0 rounded-full bg-amber-50 px-2.5 py-1 text-[11px] font-bold text-amber-700">
|
||||
{rec.reviewStatus}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="mt-6 rounded-3xl border border-slate-100 bg-white p-6 shadow-sm">
|
||||
<h2 className="text-base font-bold text-slate-800">安全事件</h2>
|
||||
{(securityEvents?.list?.length ?? 0) === 0 ? (
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import { resetMockDb } from '@/api/mocks/handlers/account';
|
||||
import { useAccountEntitlements, useAccountProfile, useAccountUsage, useUpdateAccountProfile } from './useAccount';
|
||||
import { server } from '@/test/setup';
|
||||
import {
|
||||
useAccountEntitlements,
|
||||
useAccountProfile,
|
||||
useAccountPublishRecords,
|
||||
useAccountUsage,
|
||||
useUpdateAccountProfile,
|
||||
} from './useAccount';
|
||||
|
||||
const createTestQueryClient = () =>
|
||||
new QueryClient({
|
||||
@ -52,4 +60,38 @@ describe('useAccount API Hooks 单元测试', () => {
|
||||
expect(entitlementHook.current.data?.planName).toBe('Muse Pro');
|
||||
expect(usageHook.current.data?.totalInputTokens).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('读账户发布记录(市场记录三件套之三)', async () => {
|
||||
const urls: string[] = [];
|
||||
server.use(
|
||||
http.get('/app-api/muse/account/publish-records', ({ request }) => {
|
||||
urls.push(request.url);
|
||||
return HttpResponse.json({
|
||||
code: 0,
|
||||
msg: '',
|
||||
data: {
|
||||
total: 1,
|
||||
pageNo: 1,
|
||||
pageSize: 50,
|
||||
list: [
|
||||
{
|
||||
recordId: 'pr-1',
|
||||
assetType: 'agent',
|
||||
assetName: '发布资产',
|
||||
version: '1',
|
||||
reviewStatus: 'submitted',
|
||||
marketStatus: 'not_listed',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useAccountPublishRecords(), { wrapper });
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
expect(result.current.data?.list[0]?.reviewStatus).toBe('submitted');
|
||||
// 契约:命中账户发布记录端点(账户台账视角,异于 market my-publish-records)。
|
||||
expect(urls[0]).toContain('/app-api/muse/account/publish-records');
|
||||
});
|
||||
});
|
||||
|
||||
@ -96,6 +96,34 @@ export function useAccountLicenses() {
|
||||
});
|
||||
}
|
||||
|
||||
/** 账户发布记录条目(对齐后端 AppPublishRecordRespVO 关键字段;账户台账视角,区别于 market 发布工作流的 my-publish-records)。 */
|
||||
export interface AccountPublishRecord {
|
||||
recordId: string;
|
||||
assetType: string;
|
||||
assetName: string;
|
||||
version?: string;
|
||||
/** 审核态:submitted/approved/rejected… */
|
||||
reviewStatus: string;
|
||||
/** 市场态:not_listed/listed/delisted… */
|
||||
marketStatus?: string;
|
||||
blockingReason?: string | null;
|
||||
submittedAt?: number;
|
||||
lastUpdatedAt?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取我的发布记录(账户中心市场记录三件套之三,补齐购买/授权之外的发布台账)。
|
||||
* 与 market 模块 useMyPublishRecords(发布工作流操作台:草稿/提交/申诉)不同——本接口是账户视角的发布历史台账(含 reviewStatus/marketStatus)。
|
||||
*/
|
||||
export function useAccountPublishRecords() {
|
||||
return useQuery<AccountRecordsPage<AccountPublishRecord>, Error>({
|
||||
queryKey: ['accountPublishRecords'],
|
||||
queryFn: () =>
|
||||
api.get<AccountRecordsPage<AccountPublishRecord>>('/account/publish-records?pageNo=1&pageSize=50'),
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
/** 账户安全事件摘要条目(对齐后端 SecurityEventSummaryRespVO)。 */
|
||||
export interface AccountSecurityEvent {
|
||||
eventId: string;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user