From 6bd90301a56a378425efced70451454d90bafdc5 Mon Sep 17 00:00:00 2001 From: lili Date: Thu, 25 Jun 2026 13:05:37 -0700 Subject: [PATCH] =?UTF-8?q?test(admin-e2e):=20governance=20config=20?= =?UTF-8?q?=E5=85=A8=E9=A1=B5=E5=B7=A1=E8=88=AA=E8=BD=AC=E7=9C=9F=E8=BF=9E?= =?UTF-8?q?=E2=80=94=E2=80=94=E6=8B=86=206=20=E5=AD=90=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E7=9C=9F=E8=BF=9E+=E7=81=8C=E7=B3=BB=E7=BB=9F=E7=BA=A7?= =?UTF-8?q?=E7=A7=8D=E5=AD=90(admin=20e2e=200=20skip=20=E5=85=A8=E6=94=B6?= =?UTF-8?q?=E5=8F=A3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 承用户选 B(补 admin config 断层)。前提纠正:原 skip 注释"切片库根本没有 muse_function_chain/muse_protection_node 表"误判——psql 实证真表名带 meta_ 前缀=muse_meta_function_chain/muse_meta_protection_node(V3 schema、都在 muse_slice_live、行数 0),真断层是缺系统级公共治理对象种子、非缺表/缺功能。 每锚点真表+真后端过滤契约(curl 实证):story.draft 元结构=muse_meta_schema(列表无 status 过滤);故事链路=muse_meta_function_chain(listQuery 只放行 status=active、displayName 取 chain_snapshot.displayName);事实校验保护节点=muse_meta_protection_node(status=active+policy_snapshot->>chainKey 绑定链路、irreplaceable);候选生成 Prompt=muse_prompt(非 muse_ai_prompt);系统故事智能体=muse_agent(非 muse_ai_agent、agent_type=system、scope=all 不过滤);系统世界观库=muse_knowledge_base(kb_type=global、owner_user_id=0)。 global-setup 加 resetMetaGovernanceFixture(7 幂等 upsert ON CONFLICT、tenant_id=1、照 resetMarketFixture/resetJobsFixture 范式)。spec 删 :456 skip→拆 6 条细粒度真连用例(元结构/功能编排/AI/全局知识/市场/New-API),修 2 处 strict-mode 多命中。New-API 锚点契约纠正(mock 期 user-e2e/usage-e2e 伪造值):真后端绑定行是用户数字 id 字符串、用量 recordId 是聚合派生 {userId}:{period},改对真锚点(活体用户+1:month)、无后端缺口。 独立核实:连跑全量 e2e x2 各 12 passed/0 skipped(account/market/jobs 既有 6 真连无回归、幂等可复现);psql 复核 6 锚点种子真灌(function_chain/protection_node active、muse_prompt/muse_agent system/global KB 各 1 行真实契约)。至此 admin governance e2e 从全 page.route mock 全转真后端:12 真连用例 0 skip。 Co-Authored-By: Claude Opus 4.8 (1M context) --- muse-admin/apps/web-antd/e2e/global-setup.ts | 155 ++++++++++++++++++ .../e2e/muse-admin-governance.spec.ts | 109 +++++++++--- 2 files changed, 242 insertions(+), 22 deletions(-) diff --git a/muse-admin/apps/web-antd/e2e/global-setup.ts b/muse-admin/apps/web-antd/e2e/global-setup.ts index 04df6680..c0630e1f 100644 --- a/muse-admin/apps/web-antd/e2e/global-setup.ts +++ b/muse-admin/apps/web-antd/e2e/global-setup.ts @@ -75,6 +75,9 @@ async function globalSetup(): Promise { // 复位 jobs/source-event 写命令子域 fixture(详见函数注释)。 await resetJobsFixture(client); + + // 灌核心治理配置只读巡航子域 fixture(元结构/功能编排/AI/全局知识/New-API 用量,详见函数注释)。 + await resetMetaGovernanceFixture(client); } finally { await client.end(); } @@ -188,6 +191,33 @@ async function resetMarketFixture(client: Client): Promise { const JOB_RETRY_FIXTURE_ID = 90_001; const JOB_CANCEL_FIXTURE_ID = 90_002; const SOURCE_EVENT_FIXTURE_ID = 90_003; + +/** + * 核心治理配置(元结构/功能编排/AI/全局知识/New-API)只读巡航子域 e2e 锚点。 + * + * 这些锚点都是**系统级公共治理对象**(非用户私有),切片库基础种子未灌(各列表端点真返 total=0,但表都在), + * 故本前置照 market/jobs 范式幂等灌入真实行,让"核心治理配置全页巡航"用例的只读断言能对真数据成立。 + * 全部用高位固定 id(901xx),tenant_id=1,ON CONFLICT 幂等;锚点业务键带 `.e2e` 后缀,避免与真实治理对象冲突。 + * + * 锚点与真实后端契约的对应关系(均 curl 实证 surface,见下方函数注释): + * - 元结构 story.draft(muse_meta_schema):列表无 status 过滤即可见;详情页 activeVersion=null 也能正常渲染。 + * - 功能链路「故事链路」(muse_meta_function_chain):列表 listQuery 只放行 status='active';displayName 取 chain_snapshot.displayName。 + * - 保护节点「事实校验」(muse_meta_protection_node):列表按 status='active' + policy_snapshot->>'chainKey' 过滤,故 policy_snapshot 必须带 chainKey 绑定到故事链路。 + * - AI Prompt「候选生成 Prompt」(muse_prompt):列表无 status 过滤,name 即展示名。 + * - 系统智能体「系统故事智能体」(muse_agent):scope=all 不过滤,前端「系统智能体」tab 按 agent_type=system 展示;name 即展示名。 + * - 全局知识库「系统世界观库」(muse_knowledge_base):列表按 kb_type='global' 过滤(owner_user_id=0);name 即展示名。 + * - New-API 锚点不需独立种子:网关绑定行由 account 用户投影(userId='1'/昵称'活体用户'已存在), + * 调用归属用量记录(muse_member_usage_record)灌 user1 一行 → recordId 真后端聚合为 '1:month'。 + */ +const META_SCHEMA_FIXTURE_ID = 90_101; +const FUNCTION_CHAIN_FIXTURE_ID = 90_102; +const PROTECTION_NODE_FIXTURE_ID = 90_103; +const PROMPT_FIXTURE_ID = 90_104; +const SYSTEM_AGENT_FIXTURE_ID = 90_105; +const GLOBAL_KB_FIXTURE_ID = 90_106; +const USAGE_RECORD_FIXTURE_ID = 90_107; +/** 功能链路与保护节点的关联键:保护节点 policy_snapshot.chainKey 指向它,前端按 chainKey 拉保护节点列表。 */ +const FUNCTION_CHAIN_FIXTURE_KEY = 'story.pipeline.e2e'; /** 重试 fixture 的 retry group;重试命令派生子 job 会复用同一 group,reset 据此清理上一轮派生行。 */ const JOB_RETRY_FIXTURE_GROUP = 'retry-group-e2e-fixture-retry'; /** 来源事件 fixture 的业务事件 id;来源重试派生的传播 job 在 input_summary.sourceEventId 里带它,reset 据此清理。 */ @@ -293,4 +323,129 @@ async function resetJobsFixture(client: Client): Promise { ); } +/** + * 灌核心治理配置只读巡航子域 e2e fixture(幂等、tenant_id=1 限定、直连真实治理表)。 + * + * 这些都是只读端点的展示锚点:本前置只**幂等 upsert**系统级公共治理对象,不做"清上一轮"(只读不产生增量), + * ON CONFLICT 复位 deleted=false / status / 展示名,保证锚点稳定可见。每行都按上面常量块注释里实证过的真实后端 + * 过滤契约灌真实字段(故事链路 status=active、保护节点 policy_snapshot.chainKey 绑定、全局 KB kb_type=global 等), + * 不是"伪造糊弄断言"——curl 复核过六个治理端点 + 两个 New-API 端点(account 投影)都真返这些锚点。 + * + * 锚点存在性是 best-effort 硬前置失败语义的反面:这些表本就空(切片库没灌),所以这里是"灌入",不是"复位已有行"; + * 若 upsert 任一行 rowCount=0(理论上不会,ON CONFLICT DO UPDATE 总会写),不致命,下方断言会真实暴露缺数据。 + * + * id 列是 GENERATED ALWAYS IDENTITY,显式写锚点 id 必须带 OVERRIDING SYSTEM VALUE。 + */ +async function resetMetaGovernanceFixture(client: Client): Promise { + // 1) 元结构 story.draft:列表端点不按 status 过滤,draft 也可见;详情页 activeVersion=null 能正常渲染「草稿 JSON 编辑」。 + // uk 是 (tenant_id, domain, scope, target_type, schema_key),冲突即复位展示名/状态。 + const metaSchema = await client.query( + `INSERT INTO muse_meta_schema + (id, schema_key, domain, scope, target_type, display_name, status, projection_version, + creator, updater, deleted, tenant_id) + OVERRIDING SYSTEM VALUE + VALUES ($1, 'story.draft', 'content', 'work', 'novel_work', '故事草稿元结构', 'draft', 1, + 'e2e-fixture', 'e2e-fixture', false, 1) + ON CONFLICT (tenant_id, domain, scope, target_type, schema_key) + DO UPDATE SET display_name = EXCLUDED.display_name, status = EXCLUDED.status, deleted = false`, + [META_SCHEMA_FIXTURE_ID], + ); + + // 2) 功能链路「故事链路」:列表 listQuery 只放行 status='active';displayName 取 chain_snapshot.displayName, + // activeVersion 取 active_version 字符串(无版本行时也能解析成 1)。uk 是 (tenant_id, chain_key)。 + const functionChain = await client.query( + `INSERT INTO muse_meta_function_chain + (id, chain_key, active_version, chain_snapshot, status, creator, updater, deleted, tenant_id) + OVERRIDING SYSTEM VALUE + VALUES ($1, $2, '1', + '{"displayName": "故事链路", "description": "e2e fixture 故事生成功能链路"}'::jsonb, + 'active', 'e2e-fixture', 'e2e-fixture', false, 1) + ON CONFLICT (tenant_id, chain_key) + DO UPDATE SET chain_snapshot = EXCLUDED.chain_snapshot, active_version = '1', + status = 'active', deleted = false`, + [FUNCTION_CHAIN_FIXTURE_ID, FUNCTION_CHAIN_FIXTURE_KEY], + ); + + // 3) 保护节点「事实校验」:列表按 status='active' + policy_snapshot->>'chainKey' 过滤,故 policy_snapshot 必须带 + // chainKey 绑定到故事链路;replaceable=false → 前端展示「不可替换」。uk 是 (tenant_id, node_key)。 + const protectionNode = await client.query( + `INSERT INTO muse_meta_protection_node + (id, node_key, display_name, owner_module, replaceable, policy_snapshot, status, node_type, + irreplaceable_reason, creator, updater, deleted, tenant_id) + OVERRIDING SYSTEM VALUE + VALUES ($1, 'protection.fact_check.e2e', '事实校验', 'meta', false, + ('{"chainKey": "' || $2 || '", "chainDisplayName": "故事链路", "auditSummary": "e2e 事实校验保护节点"}')::jsonb, + 'active', 'static_check', '事实校验为不可替换保护节点', 'e2e-fixture', 'e2e-fixture', false, 1) + ON CONFLICT (tenant_id, node_key) + DO UPDATE SET display_name = EXCLUDED.display_name, policy_snapshot = EXCLUDED.policy_snapshot, + status = 'active', deleted = false`, + [PROTECTION_NODE_FIXTURE_ID, FUNCTION_CHAIN_FIXTURE_KEY], + ); + + // 4) AI Prompt「候选生成 Prompt」:列表无 status 过滤;name 即前端展示名(默认「Prompt 模板」tab 可见)。 + // uk 是 (tenant_id, prompt_key)。 + const prompt = await client.query( + `INSERT INTO muse_prompt + (id, prompt_key, name, description, content, active_version, status, category, + creator, updater, deleted, tenant_id) + OVERRIDING SYSTEM VALUE + VALUES ($1, 'system.candidate.gen.e2e', '候选生成 Prompt', 'e2e fixture 候选生成 prompt', + '生成候选内容', '1', 'active', 'generation', 'e2e-fixture', 'e2e-fixture', false, 1) + ON CONFLICT (tenant_id, prompt_key) + DO UPDATE SET name = EXCLUDED.name, status = 'active', deleted = false`, + [PROMPT_FIXTURE_ID], + ); + + // 5) 系统智能体「系统故事智能体」:scope=all 不过滤,前端「系统智能体」tab 按 agent_type=system 展示; + // name 即展示名。uk 是 (tenant_id, agent_key)。 + const systemAgent = await client.query( + `INSERT INTO muse_agent + (id, agent_key, name, description, agent_type, status, creator, updater, deleted, tenant_id) + OVERRIDING SYSTEM VALUE + VALUES ($1, 'system:story.e2e', '系统故事智能体', 'e2e fixture 系统故事智能体', 'system', 'active', + 'e2e-fixture', 'e2e-fixture', false, 1) + ON CONFLICT (tenant_id, agent_key) + DO UPDATE SET name = EXCLUDED.name, agent_type = 'system', status = 'active', deleted = false`, + [SYSTEM_AGENT_FIXTURE_ID], + ); + + // 6) 全局知识库「系统世界观库」:列表按 kb_type='global' 过滤(系统级 owner_user_id=0);name 即展示名。 + // 无业务 uk(uk 仅在 command_id 非空时生效),按主键 id 幂等。 + const globalKb = await client.query( + `INSERT INTO muse_knowledge_base + (id, name, description, kb_type, owner_user_id, status, active_version, revision, + creator, updater, deleted, tenant_id) + OVERRIDING SYSTEM VALUE + VALUES ($1, '系统世界观库', 'e2e fixture 系统级世界观知识库', 'global', 0, 'active', 1, 1, + 'e2e-fixture', 'e2e-fixture', false, 1) + ON CONFLICT (id) + DO UPDATE SET name = EXCLUDED.name, kb_type = 'global', status = 'active', deleted = false`, + [GLOBAL_KB_FIXTURE_ID], + ); + + // 7) New-API 调用归属用量记录:account usage 端点把同用户同周期的用量聚合成一行,recordId='{userId}:{period}', + // 前端默认 period=month。灌 user1 一行 completed 用量(create_time=now 落在本月,month periodStart 过滤可见), + // 真后端聚合出 recordId='1:month' → New-API「调用归属」tab 可断言。create_time 复位为 now 保证永远落在当月。 + const usageRecord = await client.query( + `INSERT INTO muse_member_usage_record + (id, account_user_id, usage_type, token_input, token_output, cost_reference, correlation_id, + attribution_snapshot, status, creator, updater, deleted, tenant_id, create_time, update_time) + OVERRIDING SYSTEM VALUE + VALUES ($1, 1, 'ai_generation', 1200, 800, 'cost-e2e', 'corr-usage-e2e', + '{"attributedUserId": "1", "source": "e2e fixture"}'::jsonb, 'completed', + 'e2e-fixture', 'e2e-fixture', false, 1, now(), now()) + ON CONFLICT (id) + DO UPDATE SET token_input = EXCLUDED.token_input, token_output = EXCLUDED.token_output, + status = 'completed', deleted = false, create_time = now(), update_time = now()`, + [USAGE_RECORD_FIXTURE_ID], + ); + + console.log( + `[admin e2e globalSetup] reset meta-governance fixture: metaSchema=${metaSchema.rowCount} ` + + `functionChain=${functionChain.rowCount} protectionNode=${protectionNode.rowCount} ` + + `prompt=${prompt.rowCount} systemAgent=${systemAgent.rowCount} globalKb=${globalKb.rowCount} ` + + `usageRecord=${usageRecord.rowCount}(均为 upsert 写入条数)`, + ); +} + export default globalSetup; diff --git a/muse-admin/apps/web-antd/e2e/muse-admin-governance.spec.ts b/muse-admin/apps/web-antd/e2e/muse-admin-governance.spec.ts index c0125778..8d98fe05 100644 --- a/muse-admin/apps/web-antd/e2e/muse-admin-governance.spec.ts +++ b/muse-admin/apps/web-antd/e2e/muse-admin-governance.spec.ts @@ -518,68 +518,133 @@ test('市场申诉以 maintained 结论真打处理命令并落库', async ({ pa expectNoUnhandledMuseAdminApis(page); }); -// 断层标注(诚实记录,保留 skip):这条"核心治理配置全页巡航"用例当前**不能**转真后端。 -// 它的断言混了两类:① 系统边界静态文案(各页 chrome,与数据无关,真后端能渲染);② 一批 mock 期数据 -// 锚点——story.draft 元结构、"故事链路"功能链路、"事实校验"保护节点、"候选生成 Prompt"、"系统故事智能体"、 -// "系统世界观库"、new-api 的 user-e2e/usage-e2e。这些数据锚点在 muse_slice_live **不存在**,且其中: -// - 功能链路 / 保护节点:切片库**根本没有 muse_function_chain / muse_protection_node 表** -// (端点真返 total=0 仅因无后端支撑表),"故事链路""事实校验"断言无表可灌,无法真连; -// - AI Prompt(/ai/prompts total=0)、全局知识库(/knowledge/global-kbs total=0):切片库未灌对应种子; -// - 元结构现有 worldview/setting 而非 story.draft;系统智能体现有"活体测试智能体"而非"系统故事智能体"。 -// 用例还做数据依赖的交互(点"查看详情"跳 story.draft、点"保护节点"看"事实校验"、切 tab 看"系统故事智能体"), -// 这些行在缺数据(且保护节点/功能链路缺表)时无法定位 → 无法硬转。faithfully 转需跨 6 个子系统伪造数据, -// 远超本批"3 个 skip 转真连"范围,且功能链路/保护节点连表都没有、本质是后端切片支撑断层。 -// 故保留 skip。account/market/jobs/source-event 子域的系统边界已分别由各自真连用例覆盖;此页"全页巡航" -// 待后端补齐功能链路/保护节点表 + 切片灌全 AI Prompt/全局知识库种子后再转真连。下方 body 留作"完整覆盖应断言什么"的记录。 -test.skip('管理员可进入核心治理配置页面并看到系统边界', async ({ page }) => { +// 核心治理配置巡航 —— 转真后端(MSW-off,真打 48080 + 真治理数据种子)。 +// +// 前提纠正(对抗上一 agent 的"缺表"误判):此前把本用例标 skip 时误判"切片库根本没有 +// muse_function_chain / muse_protection_node 表"。经 psql 实证,真表名带 meta_ 前缀= +// muse_meta_function_chain(V3)+ muse_meta_protection_node(V3)+ 版本/槽位/节点(V10), +// **表都在 muse_slice_live、只是行数=0**(上一 agent 查无前缀表名才漏判)。元结构 / AI Prompt / +// 全局知识库 / 系统智能体同理:表都在,只是缺系统级公共治理对象的种子。故真断层是"切片库缺数据种子", +// **不是缺表、更不是后端缺功能**。globalSetup.resetMetaGovernanceFixture 已按各端点实证过的真实过滤契约 +// 幂等灌入这些系统级锚点(故事链路 status=active、保护节点 policy_snapshot.chainKey 绑定、全局 KB kb_type=global +// 等),八个端点 curl 复核均真返锚点,故把原"全页巡航"拆成各子系统独立真连用例(下面六条),按数据锚点定位真实行 + +// 做真实交互(查看详情跳转 / 点保护节点拉列表 / 切 tab),不再伪造数据。 +// +// 残留诚实标注(New-API 锚点契约纠正,见对应用例):原 mock 期锚点 'user-e2e'(网关绑定 userId 单元格)、 +// 'usage-e2e'(用量 recordId)在真后端契约下**不可能**出现——绑定行的 userId 单元格是用户数字 id 字符串 +// (toAdminSummary 取 stringId(user.getId())),用量 recordId 是后端聚合派生 '{userId}:{period}' +// (toAdminUsageRecord 里 accountUserId + ":" + period)。故 New-API 子系统改对真实锚点断言 +// (绑定行真实用户'活体用户' + 用量 recordId '1:month'),而非伪造一个数字 id 叫 user-e2e。 + +// 元结构治理:真后端列出 story.draft 元结构 + 查看详情跳转 + 新建草稿入口。 +test('元结构治理页对真后端列出 story.draft 并支持详情/新建草稿', async ({ page }) => { await page.goto('/muse/governance/meta-schemas'); await expect(page.getByText('元结构定义').first()).toBeVisible(); + // story.draft 由 resetMetaGovernanceFixture 灌入(列表端点不按 status 过滤,draft 也可见)。 await expect(page.getByText('story.draft')).toBeVisible(); - await page.getByRole('button', { name: /查看详情/ }).click(); + + // 查看详情跳转到 story.draft 详情页:详情端点 activeVersion=null 也能正常渲染「草稿 JSON 编辑」。 + await page.getByRole('button', { name: /查看详情/ }).first().click(); await expect(page).toHaveURL(/\/muse\/governance\/meta-schemas\/story\.draft$/); await expect(page.getByText('草稿 JSON 编辑')).toBeVisible(); + + // 新建草稿入口(纯前端路由,无需后端数据):跳 __create__ 创建模式。 await page.goto('/muse/governance/meta-schemas'); await page.getByRole('button', { name: /新建草稿/ }).click(); - await expect(page).toHaveURL(/\/muse\/governance\/meta-schemas\/__create__\?mode=create$/); + await expect(page).toHaveURL( + /\/muse\/governance\/meta-schemas\/__create__\?mode=create$/, + ); await expect(page.getByPlaceholder('例如 work.story')).toBeVisible(); + expectNoUnhandledMuseAdminApis(page); +}); + +// 功能编排治理:真后端列出「故事链路」+ 点「保护节点」拉出绑定的「事实校验」保护节点。 +test('功能编排页对真后端列出故事链路并展开事实校验保护节点', async ({ page }) => { await page.goto('/muse/governance/function-orchestration'); await expect( page.getByText('开放槽位只表示可替换的非保护节点').first(), ).toBeVisible(); + // 故事链路由 fixture 灌入(列表 listQuery 只放行 status='active';displayName 取 chain_snapshot.displayName)。 await expect(page.getByText('故事链路')).toBeVisible(); - await page.getByRole('button', { name: '保护节点' }).first().click(); - await expect(page.getByText('事实校验')).toBeVisible(); + // 点「保护节点」→ 前端按该链路 chainKey 拉保护节点列表;事实校验 policy_snapshot.chainKey 绑定到故事链路。 + // exact 收口:fixture 的不可替换原因/审计摘要列也含「事实校验」子串,精确匹配 nodeName 单元格避免多命中。 + await page.getByRole('button', { name: '保护节点' }).first().click(); + await expect( + page.getByRole('cell', { name: '事实校验', exact: true }), + ).toBeVisible(); + + expectNoUnhandledMuseAdminApis(page); +}); + +// AI 配置治理:真后端展示「候选生成 Prompt」(默认 Prompt tab)+ 切「系统智能体」tab 看「系统故事智能体」。 +test('AI 配置页对真后端展示候选生成 Prompt 与系统故事智能体', async ({ page }) => { await page.goto('/muse/ai'); await expect(page.getByText('AI 配置域治理边界')).toBeVisible(); + // 候选生成 Prompt 由 fixture 灌入(列表无 status 过滤,name 即展示名,默认「Prompt 模板」tab 可见)。 await expect(page.getByText('候选生成 Prompt')).toBeVisible(); + + // 切「系统智能体」tab:系统故事智能体 agent_type=system,scope=all 列表含它。 + // 收口到卡片标题 heading:fixture 的智能体描述「e2e fixture 系统故事智能体」也含同名子串,按 heading 避免多命中。 await page.getByRole('tab', { name: '系统智能体' }).click(); await expect(page.getByText('用户私有 Agent 边界')).toBeVisible(); - await expect(page.getByText('系统故事智能体')).toBeVisible(); + await expect( + page.getByRole('heading', { name: '系统故事智能体' }), + ).toBeVisible(); + expectNoUnhandledMuseAdminApis(page); +}); + +// 全局知识治理:真后端展示「系统世界观库」全局知识库 + 系统边界文案。 +test('全局知识页对真后端展示系统世界观库', async ({ page }) => { await page.goto('/muse/knowledge'); await expect(page.getByRole('heading', { name: '全局知识管理' })).toBeVisible(); await expect(page.getByText('不读取用户私有正文')).toBeVisible(); + // 系统世界观库由 fixture 灌入(列表按 kb_type='global' 过滤,owner_user_id=0,name 即展示名)。 await expect(page.getByText('系统世界观库').first()).toBeVisible(); + expectNoUnhandledMuseAdminApis(page); +}); + +// 市场治理巡航:真后端市场治理页边界 + 三个 tab 的 chrome(数据锚点由 market 真连用例另行覆盖,这里只巡航 chrome)。 +// market 子域已由 resetMarketFixture 灌真实资产/申诉,故资产名/申诉关联资产也能真断言。 +test('市场治理页对真后端巡航三大工作台边界', async ({ page }) => { await page.goto('/muse/market'); await expect(page.getByRole('heading', { name: '市场治理' })).toBeVisible(); + // 审核队列(默认 active)tab 头文案。 await expect(page.getByText('待审资产').first()).toBeVisible(); + await page.getByRole('tab', { name: '资产治理' }).click(); await expect(page.getByText('不改用户私有副本').first()).toBeVisible(); - await expect(page.getByText('市场资产').first()).toBeVisible(); + const assetPanel = page.getByRole('tabpanel', { name: '资产治理' }); + await expect(assetPanel.getByText('活体市场资产·测试')).toBeVisible(); + await page.getByRole('tab', { name: '申诉处理' }).click(); await expect(page.getByText('申诉人').first()).toBeVisible(); + expectNoUnhandledMuseAdminApis(page); +}); + +// New-API 治理对账巡航:真后端绑定/用量摘要 + 系统边界(不展示凭据)。 +// +// 锚点契约纠正(对抗 mock 假设):原用例断言单元格 'user-e2e' / 文案 'usage-e2e' 是 mock 期伪造值,真后端契约 +// 下不可能出现——网关绑定 userId 单元格是用户数字 id 字符串(toAdminSummary 取 stringId(user.getId())), +// 用量 recordId 是后端聚合派生 '{userId}:{period}'(默认 period=month → '1:month')。故改对真实锚点断言: +// 绑定 tab 看真实用户'活体用户'(account 用户投影,稳定存在);调用归属 tab 看 fixture 用量真聚合出的 recordId '1:month'。 +test('New-API 页对真后端展示绑定与调用归属对账摘要', async ({ page }) => { await page.goto('/muse/new-api'); await expect( page.getByText('New-API 页面只展示治理和对账摘要'), ).toBeVisible(); await expect(page.getByText('不展示凭据')).toBeVisible(); - await expect(page.getByRole('cell', { name: 'user-e2e' })).toBeVisible(); + + // 网关绑定 tab(默认):真实用户'活体用户'的网关绑定行(userId 单元格是数字 id 字符串,昵称唯一可断言)。 + await expect(page.getByRole('cell', { name: '活体用户' })).toBeVisible(); + + // 调用归属 tab:resetMetaGovernanceFixture 灌的 user1 用量记录被后端聚合成 recordId '1:month'。 await page.getByRole('tab', { name: '调用归属' }).click(); - await expect(page.getByText('usage-e2e')).toBeVisible(); + await expect(page.getByRole('cell', { name: '1:month' })).toBeVisible(); expectNoUnhandledMuseAdminApis(page); });