import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; const read = (path) => readFileSync(resolve(process.cwd(), path), 'utf8').replace(/\r\n/g, '\n'); const controller = read('../YD_Event.Admin/YD_Event.Application/Workbench/WorkbenchController.cs'); const dtos = read('../YD_Event.Admin/YD_Event.Application/Workbench/Dtos/WorkbenchDtos.cs'); const orderBook = read('../YD_Event.Admin/YD_Event.Application/OrderBook/OrderBookService.cs'); const calculation = read('../YD_Event.Admin/YD_Event.Application/Event/Scoring/EventScoreCalculationService.cs'); const sql = read('../YD_Event.Admin/二期需求/dbo.sql'); const launcher = read('src/views/scoring/launcher/index.vue'); const workbench = read('src/views/scoring/workbench/index.vue'); const orderBookPage = read('src/views/scoring/orderbook/index.vue'); const scoreScreen = read('src/views/scoring/screen/index.vue'); const checkinScreen = read('src/views/scoring/checkin-screen/index.vue'); const menuSeed = read('../YD_Event.Admin/YD_Event.Core/InitData/sysmenu.txt'); const roleMenuSeed = read('../YD_Event.Admin/YD_Event.Core/InitData/sysrolemenu.txt'); const backEndRoutes = read('src/router/backEnd.ts'); const checks = [ ['工作台接口不重复控制器前缀', controller, '[HttpGet("events")]'], ['秩序册接口使用文档约定路由', orderBook, 'Name = "order-book"'], ['投屏校验成绩与出场关联', controller, 'result.SessionEntryId != entry.Id'], ['投屏校验成绩版本', controller, 'input.ResultVersion.Value != result.ResultVersion'], ['投屏校验评分单收齐', controller, 'submittedJudgeCount < expectedJudgeCount'], ['投屏后进入状态30', controller, 'result.ResultStatus = 30'], ['成绩主表与版本表状态同步', controller, 'SyncResultVersionStatusAsync(result, 30'], ['更正成绩确认后进入40', controller, 'ScoreCorrectionId.HasValue == true ? 40 : 20'], ['聚合跳过成绩同步作废当前版本', calculation, 'version.ResultStatus = ResultStatusVoided'], ['排名排除DQ', controller, '!r.IsDisqualified'], ['排名不包含作废状态', controller, 'r.ResultStatus == 20 || r.ResultStatus == 30 || r.ResultStatus == 40'], ['投屏操作事务化', controller, '[UnitOfWork]\n public async Task CastResult'], ['重赛按组别定位末位', controller, 'e.GroupName == originalEntry.GroupName'], ['重赛后续出场顺延', controller, 'laterEntry.PlayOrder++'], ['批量确认必须显式选择', controller, 'results.Count != requestedIds.Count'], ['原因服务端规范化', controller, 'NormalizeReason(input.VoidReason, "作废原因")'], ['动态评分项接口', dtos, 'List ScoreItems'], ['秩序册支持xls和xlsx', orderBook, 'ext is not ".xlsx" and not ".xls"'], ['单一当前投屏唯一索引', sql, 'UX_EventResultScreenCast_Current'], ['SQL状态30为已投屏', sql, "WHEN 30 THEN N''已投屏''"], ['报名与秩序册数据源', launcher, "switchSource('orderbook')"], ['赛事列表分页', launcher, 'v-model:current-page="pageNo"'], ['工作台保存赛事上下文', launcher, "sessionStorage.setItem('scoring-context'"], ['秩序册具有独立页面', orderBookPage, 'orderBookImportPost'], ['成绩管理与投屏使用独立菜单路由', menuSeed, '"Path": "/scoring/cast"'], ['秩序册直达路由具有兼容兜底', backEndRoutes, 'ensureScoringOperationalRoutes'], ['超级管理员具有秩序册菜单授权', roleMenuSeed, '"MenuId": 1088'], ['工作台动态评分列', workbench, 'v-for="col in detailColumns"'], ['规定赛隐藏难度', scoreScreen, "includes('PRESCRIBED')"], ['评分卡列数随规则动态调整', scoreScreen, 'Math.max(scoreCards.length,1)'], ['成绩轮询防重入', scoreScreen, 'castRequesting'], ['检录轮询防重入', checkinScreen, 'requesting=false'], ['检录时间使用服务端基线', checkinScreen, 'serverBaseMs+(Date.now()-syncedAtMs)'], ]; for (const [name, source, expected] of checks) { assert.ok(source.includes(expected), `${name}:缺少 ${expected}`); process.stdout.write(`PASS ${name}\n`); } process.stdout.write(`工作台业务契约测试通过:${checks.length}/${checks.length}\n`);