feat(studio): 资产详情页治理影响接入真后端(A 类:消费者治理 C14 读端)
A 类(后端就绪、前端 0 消费):02F GET /marketplace/assets/{id}/governance-impact 就绪
(curl code:0 + 真实 governanceResult:resultType/reason/操作限制 + affectedAuthorizations/
suggestedActions),但 studio 详情页无展示(盘点 C14 消费者治理缺口)。接入:
- useGovernanceImpact hook + openapi 导出 GovernanceImpactResult
- MarketAssetDetailPage「治理影响」区:结果类型(下架/召回/吊销/可申诉)+ 操作限制(停止获取/安装/绑定/生成)+ 建议动作
- mock handler + vitest 契约 + market-governance-impact.spec.ts 真后端 e2e
- 修 spec 块注释内 affected*/ 的 */ 意外闭合(tsc 不覆盖 e2e、playwright esbuild 暴露)
验证:全量 e2e 46→47/0;vitest 81→82;tsc/eslint 绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a2f2ba33fe
commit
ab83735f6f
53
muse-studio/e2e/market-governance-impact.spec.ts
Normal file
53
muse-studio/e2e/market-governance-impact.spec.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { expect, test, type Page } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* 市场资产治理影响 e2e(真实后端,MSW off)——A 类:后端 getGovernanceImpact 就绪、studio 此前 0 消费(C14 读端缺口)。
|
||||
*
|
||||
* 反假绿:盘点 C14 消费者治理缺口——后端 GET /marketplace/assets/{id}/governance-impact 就绪
|
||||
* (curl code:0 + 真实 governanceResult:resultType/reason/操作限制 + affected 列表与 suggestedActions),
|
||||
* 但 studio 详情页无任何展示。本测进资产详情页 → 等真实 GET governance-impact → 验 code:0 +
|
||||
* 「治理影响」区按真实 governanceResult 渲染结果类型。
|
||||
* 设计:不硬编码治理结果(随资产治理态变),依真实响应断言——有 governanceResult 则验区块 + 结果类型渲染。
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
const RESULT_TYPE_LABELS: Record<string, string> = {
|
||||
delist: '下架',
|
||||
recall: '召回',
|
||||
revoke: '吊销',
|
||||
appeal: '可申诉',
|
||||
};
|
||||
|
||||
test('资产详情页渲染治理影响(真实后端 governance-impact)', async ({ page }) => {
|
||||
await seedToken(page);
|
||||
|
||||
const govResp = page.waitForResponse(
|
||||
(r) =>
|
||||
/\/marketplace\/assets\/[^/]+\/governance-impact$/.test(new URL(r.url()).pathname) &&
|
||||
r.request().method() === 'GET',
|
||||
{ timeout: 15_000 }
|
||||
);
|
||||
// asset 1:curl 证 detail + governance-impact 均 code:0(governanceResult.reason=listed + affectedAuthorizations 有数据)。
|
||||
await page.goto('/market/assets/1');
|
||||
const resp = await govResp;
|
||||
expect(resp.status()).toBe(200);
|
||||
const body = await resp.json();
|
||||
expect(body.code).toBe(0); // 后端就绪。
|
||||
|
||||
// 有 governanceResult 时「治理影响」区渲染 + 结果类型按真实 resultType 映射(证真后端态被 UI 消费)。
|
||||
if (body.data?.governanceResult) {
|
||||
await expect(page.getByRole('heading', { name: '治理影响' })).toBeVisible({ timeout: 10_000 });
|
||||
const rt = body.data.governanceResult.resultType;
|
||||
const label = RESULT_TYPE_LABELS[rt] ?? rt;
|
||||
if (label) {
|
||||
await expect(page.getByText(label).first()).toBeVisible({ timeout: 10_000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -189,6 +189,39 @@ export const marketHandlers = [
|
||||
return HttpResponse.json({ code: 0, msg: 'success', data: asset });
|
||||
}),
|
||||
|
||||
// GET /app-api/muse/marketplace/assets/:assetId/governance-impact — 资产治理影响(消费者视角)
|
||||
http.get('/app-api/muse/marketplace/assets/:assetId/governance-impact', ({ params }) => {
|
||||
const asset = mockAssets.find((item) => item.assetId === params.assetId);
|
||||
if (!asset) {
|
||||
return HttpResponse.json({ code: 40420, msg: '市场资产不存在', data: null }, { status: 404 });
|
||||
}
|
||||
// 未上架资产模拟新操作受限;已上架资产仅提示可申诉、无限制。
|
||||
const restricted = asset.listingStatus !== 'listed';
|
||||
return HttpResponse.json({
|
||||
code: 0,
|
||||
msg: 'success',
|
||||
data: {
|
||||
assetId: asset.assetId,
|
||||
governanceResult: {
|
||||
resultType: 'appeal',
|
||||
reason: restricted ? 'not_listed' : 'listed',
|
||||
scope: asset.listingStatus ?? 'listed',
|
||||
newAcquireStopped: restricted,
|
||||
newInstallStopped: restricted,
|
||||
newBindStopped: restricted,
|
||||
generationUseStopped: false,
|
||||
},
|
||||
affectedAuthorizations: [],
|
||||
affectedInstallations: [],
|
||||
affectedBindings: [],
|
||||
affectedTasks: [],
|
||||
affectedCandidates: [],
|
||||
exportRestrictions: [],
|
||||
suggestedActions: ['review_governance_result', 'open_appeal_if_needed'],
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
// POST /app-api/muse/marketplace/assets/:assetId/purchase — 获取授权,不代表安装或绑定
|
||||
http.post('/app-api/muse/marketplace/assets/:assetId/purchase', ({ params }) => {
|
||||
const asset = mockAssets.find((item) => item.assetId === params.assetId);
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
useAcquireMarketAsset,
|
||||
useBindPrecheck,
|
||||
useFavoriteAsset,
|
||||
useGovernanceImpact,
|
||||
useInstallMarketAsset,
|
||||
useMarketAssetDetail,
|
||||
useMarketAssets,
|
||||
@ -55,6 +56,17 @@ describe('useMarket API Hooks 单元测试', () => {
|
||||
expect(result.current.data?.[0]?.recommendationReason).toBe('适合长篇结构检查');
|
||||
});
|
||||
|
||||
it('应能读取资产治理影响(消费者视角:结果类型 + 建议动作)', async () => {
|
||||
const { result } = renderHook(
|
||||
() => useGovernanceImpact('20000000-0000-4000-8000-000000000001'),
|
||||
{ wrapper }
|
||||
);
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
// mock governance-impact:listed 资产 → resultType=appeal,带建议动作(证治理影响被解出)。
|
||||
expect(result.current.data?.governanceResult?.resultType).toBe('appeal');
|
||||
expect(Array.isArray(result.current.data?.suggestedActions)).toBe(true);
|
||||
});
|
||||
|
||||
it('应能获取授权后安装智能体资产', async () => {
|
||||
const assetId = '20000000-0000-4000-8000-000000000002';
|
||||
const { result: acquireHook } = renderHook(() => useAcquireMarketAsset(), { wrapper });
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { api, request } from '@/api/client';
|
||||
import type { MarketAssetCard, MarketInstallResult, MarketLicenseResult, MarketplaceCategory } from '@/types/openapi';
|
||||
import type { GovernanceImpactResult, MarketAssetCard, MarketInstallResult, MarketLicenseResult, MarketplaceCategory } from '@/types/openapi';
|
||||
|
||||
export type MarketAssetType = 'all' | MarketAssetCard['assetType'];
|
||||
|
||||
@ -119,6 +119,20 @@ export function useMarketAssetDetail(assetId: string | null) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询资产治理影响(GET /marketplace/assets/{id}/governance-impact)。
|
||||
* 展示该资产被治理(下架/召回/吊销/申诉)后对消费者授权/安装/绑定/任务的影响 + 操作限制 + 建议动作。
|
||||
* C14 消费者治理读端缺口:后端就绪(curl code:0 + 真实 governanceResult/affected*)、studio 此前 0 消费。
|
||||
*/
|
||||
export function useGovernanceImpact(assetId: string | null) {
|
||||
return useQuery<GovernanceImpactResult, Error>({
|
||||
queryKey: ['governanceImpact', assetId],
|
||||
enabled: !!assetId,
|
||||
queryFn: () =>
|
||||
api.get<GovernanceImpactResult>(`/marketplace/assets/${assetId}/governance-impact`),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 收藏市场资产(POST /marketplace/assets/{id}/favorite,无 body)。
|
||||
* 注:favorite 的幂等键 commandId 走 `X-Command-Id` 请求头(market 端点契约,与 acquire/install 的 body commandId 不同),
|
||||
|
||||
@ -4,9 +4,33 @@ import {
|
||||
useAcquireMarketAsset,
|
||||
useBindPrecheck,
|
||||
useFavoriteAsset,
|
||||
useGovernanceImpact,
|
||||
useMarketAssetDetail,
|
||||
} from '@/features/market/hooks/useMarket';
|
||||
|
||||
/** 治理结果类型 → 中文文案。 */
|
||||
const governanceResultTypeLabels: Record<string, string> = {
|
||||
delist: '下架',
|
||||
recall: '召回',
|
||||
revoke: '吊销',
|
||||
appeal: '可申诉',
|
||||
};
|
||||
|
||||
/** 汇总治理对新操作的限制项;无任何限制时返回"无新操作限制"。 */
|
||||
function governanceRestrictionText(result: {
|
||||
newAcquireStopped?: boolean;
|
||||
newInstallStopped?: boolean;
|
||||
newBindStopped?: boolean;
|
||||
generationUseStopped?: boolean;
|
||||
}): string {
|
||||
const items: string[] = [];
|
||||
if (result.newAcquireStopped) items.push('停止新获取');
|
||||
if (result.newInstallStopped) items.push('停止新安装');
|
||||
if (result.newBindStopped) items.push('停止新绑定');
|
||||
if (result.generationUseStopped) items.push('停止生成使用');
|
||||
return items.length > 0 ? items.join('、') : '无新操作限制';
|
||||
}
|
||||
|
||||
/**
|
||||
* 市场资产详情页(消费端深化)。
|
||||
*
|
||||
@ -20,6 +44,7 @@ export default function MarketAssetDetailPage() {
|
||||
const favorite = useFavoriteAsset(assetId ?? '');
|
||||
const acquire = useAcquireMarketAsset();
|
||||
const bindPrecheck = useBindPrecheck(assetId ?? '');
|
||||
const governance = useGovernanceImpact(assetId ?? null);
|
||||
const [targetWorkId, setTargetWorkId] = useState('');
|
||||
|
||||
if (isLoading) {
|
||||
@ -74,6 +99,45 @@ export default function MarketAssetDetailPage() {
|
||||
<p className="mt-3 whitespace-pre-wrap text-sm leading-relaxed text-slate-500">{data.description}</p>
|
||||
)}
|
||||
|
||||
{/* 治理影响:该资产被治理(下架/召回/吊销/申诉)后对消费者授权/安装/绑定的影响 + 操作限制 + 建议动作。C14 读端,后端就绪、此前 0 消费。 */}
|
||||
{governance.data?.governanceResult && (
|
||||
<section aria-label="治理影响" className="mt-8 rounded-2xl border border-amber-200 bg-amber-50/50 p-6">
|
||||
<h2 className="text-base font-bold text-slate-800">治理影响</h2>
|
||||
<p className="mt-1 text-xs leading-relaxed text-slate-500">
|
||||
该资产的治理结果对你授权/安装/绑定的影响与建议动作。
|
||||
</p>
|
||||
<dl className="mt-4 space-y-2 text-sm">
|
||||
<div className="flex gap-2">
|
||||
<dt className="w-24 shrink-0 text-slate-400">结果类型</dt>
|
||||
<dd className="font-semibold text-amber-700">
|
||||
{governanceResultTypeLabels[governance.data.governanceResult.resultType ?? ''] ??
|
||||
governance.data.governanceResult.resultType}
|
||||
</dd>
|
||||
</div>
|
||||
{governance.data.governanceResult.reason && (
|
||||
<div className="flex gap-2">
|
||||
<dt className="w-24 shrink-0 text-slate-400">原因</dt>
|
||||
<dd className="font-medium text-slate-700">{governance.data.governanceResult.reason}</dd>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<dt className="w-24 shrink-0 text-slate-400">操作限制</dt>
|
||||
<dd className="font-medium text-slate-700">
|
||||
{governanceRestrictionText(governance.data.governanceResult)}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
{governance.data.suggestedActions && governance.data.suggestedActions.length > 0 && (
|
||||
<p className="mt-3 text-xs">
|
||||
<span className="text-slate-400">建议动作:</span>
|
||||
<span className="ml-2 font-medium text-indigo-600">
|
||||
{governance.data.suggestedActions.join('、')}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 许可详情:列表卡片只给 licenseSummary 单行摘要,此处呈现完整许可条款 */}
|
||||
{license && (
|
||||
<section aria-label="许可详情" className="mt-8 rounded-2xl border border-slate-100 bg-white p-6 shadow-sm">
|
||||
|
||||
@ -39,6 +39,7 @@ export type AgentTestRequest = AIComponents['schemas']['AgentTestRequest'];
|
||||
export type AgentTestResult = AIComponents['schemas']['AgentTestResult'];
|
||||
export type MarketAssetCard = MarketComponents['schemas']['MarketAssetCard'];
|
||||
export type MarketAssetDetail = MarketComponents['schemas']['MarketAssetDetail'];
|
||||
export type GovernanceImpactResult = MarketComponents['schemas']['GovernanceImpactResult'];
|
||||
export type MarketLicenseResult = MarketComponents['schemas']['MarketLicenseResult'];
|
||||
export type MarketInstallResult = MarketComponents['schemas']['MarketInstallResult'];
|
||||
export type MarketplaceCategory = MarketComponents['schemas']['MarketplaceCategory'];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user