| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- import { test, expect } from '../fixtures/auth';
- import type { Page } from '@playwright/test';
- /**
- * G-01 自动监控主链单页验证(手动触发分支)
- * 触发模型已澄清:定时(30 min)+ 手动 debug API 并存。本 spec 仅走 debug API。
- *
- * 主链断言(与 memory/project_s8_g01_mvp.md 对齐):
- * - 入口 POST /api/aidop/s8/watch-debug/run-once 返回 200 + 结构化结果
- * - 命中产出的新单:SourceType=AUTO_WATCH / SceneCode=S2S6_PRODUCTION / ExceptionType=EQUIP_FAULT / ModuleCode 为空
- * - 立即第二次 run-once:相同命中应进入"未闭环去重"分支(skipped 比例上升 / created=0),证明 EvaluateDedupAsync 生效
- */
- async function getToken(page: Page): Promise<string | null> {
- return page.evaluate(() => {
- for (let i = 0; i < localStorage.length; i++) {
- const k = localStorage.key(i)!;
- if (/access-token$/i.test(k) && !/x-access-token$/i.test(k)) {
- const raw = localStorage.getItem(k)!;
- try {
- const v = JSON.parse(raw);
- return typeof v === 'string' ? v : v?.value ?? raw;
- } catch {
- return raw;
- }
- }
- }
- return null;
- });
- }
- async function runOnce(page: Page) {
- const token = await getToken(page);
- expect(token, '未取到 access-token').toBeTruthy();
- const baseURL = new URL(page.url()).origin;
- const resp = await page.request.post(
- `${baseURL}/api/aidop/s8/watch-debug/run-once?tenantId=1&factoryId=1`,
- { headers: { Authorization: `Bearer ${token}` } },
- );
- return { status: resp.status(), body: resp.ok() ? ((await resp.json()) as any) : null };
- }
- async function fetchException(page: Page, id: number) {
- const token = await getToken(page);
- const baseURL = new URL(page.url()).origin;
- const resp = await page.request.get(
- `${baseURL}/api/aidop/s8/exceptions/${id}?tenantId=1&factoryId=1`,
- { headers: { Authorization: `Bearer ${token!}` } },
- );
- expect(resp.ok(), `详情接口非 200`).toBeTruthy();
- const j = (await resp.json()) as any;
- return j?.data ?? j;
- }
- test.describe.configure({ mode: 'serial' });
- test.describe('S8-G01 自动监控主链(手动触发)', () => {
- let firstRun: any | null = null;
- test('G01-RUN-1 调用 run-once,主链返回结构化结果', async ({ authedPage }) => {
- await authedPage.goto('/#/aidop/s8/exceptions', { waitUntil: 'domcontentloaded' });
- const { status, body } = await runOnce(authedPage);
- test.info().annotations.push({
- type: 'run1',
- description: `HTTP ${status}; count=${body?.count}; created=${
- body?.results?.filter((r: any) => r.created).length ?? 0
- }; skipped=${body?.results?.filter((r: any) => r.skipped).length ?? 0}`,
- });
- expect(status).toBe(200);
- expect(body, 'run-once 应返回 JSON').toBeTruthy();
- expect(typeof body.count, 'count 字段缺失').toBe('number');
- expect(Array.isArray(body.results), 'results 应为数组').toBeTruthy();
- for (const r of body.results) {
- expect(r).toHaveProperty('created');
- expect(r).toHaveProperty('skipped');
- expect(r).toHaveProperty('sourceRuleId');
- expect(r).toHaveProperty('relatedObjectCode');
- }
- firstRun = body;
- });
- test('G01-RUN-1-VERIFY 新建单据满足 G-01 口径', async ({ authedPage }) => {
- test.skip(!firstRun, '依赖 RUN-1 结果');
- const created = firstRun.results.filter((r: any) => r.created && r.createdExceptionId);
- test.skip(
- created.length === 0,
- '本轮 run-once 未产生新建(可能数据无命中或全部已去重),口径验证只能在有创建样本时进行',
- );
- const sample = created[0];
- const detail = await fetchException(authedPage, Number(sample.createdExceptionId));
- test.info().annotations.push({
- type: 'sample',
- description: JSON.stringify({
- id: detail.id,
- code: detail.code ?? detail.exceptionCode,
- sourceType: detail.sourceType,
- sceneCode: detail.sceneCode,
- exceptionType: detail.exceptionType,
- moduleCode: detail.moduleCode,
- }),
- });
- expect(String(detail.sourceType ?? '')).toBe('AUTO_WATCH');
- expect(String(detail.sceneCode ?? '')).toMatch(/S2S6_PRODUCTION|S2S6Production/);
- expect(String(detail.exceptionType ?? '')).toBe('EQUIP_FAULT');
- // ModuleCode 首版置空
- expect(detail.moduleCode == null || detail.moduleCode === '').toBeTruthy();
- });
- test('G01-RUN-2 紧随其后再触发,应命中未闭环去重', async ({ authedPage }) => {
- test.skip(!firstRun, '依赖 RUN-1 结果');
- const { status, body } = await runOnce(authedPage);
- test.info().annotations.push({
- type: 'run2',
- description: `HTTP ${status}; count=${body?.count}; created=${
- body?.results?.filter((r: any) => r.created).length ?? 0
- }; skipped=${body?.results?.filter((r: any) => r.skipped).length ?? 0}`,
- });
- expect(status).toBe(200);
- const created2 = body.results.filter((r: any) => r.created).length;
- const skipped2 = body.results.filter((r: any) => r.skipped).length;
- // 两次之间没有人为闭单 → 第二轮 created 应为 0;如有规则首次运行,至少 skipped 应 ≥ 第一次的 created 数
- expect(
- created2 === 0 || skipped2 >= firstRun.results.filter((r: any) => r.created).length,
- `第二次 run-once 仍有大量创建,去重未生效:created=${created2} skipped=${skipped2}`,
- ).toBeTruthy();
- });
- });
|