Merge remote-tracking branch 'origin/test' into dev
# Conflicts: # scripts/admin-test-env-modules/phase4-overseas-pda.mjs # scripts/agents-index-contract.ps1
This commit is contained in:
@@ -133,6 +133,7 @@ node scripts/create-pending-review-orders.mjs
|
||||
| Mapper 注册静态合同 | `node scripts/mapper-scan-contract.mjs`、`node --test scripts/mapper-scan-contract.test.mjs`;自动核对全部持久化模块的 `@Mapper`、服务根包注解过滤扫描和唯一扫描入口,不启动 Spring、不连接数据库;`backend-full-regression.ps1` 在 Maven 前强制执行 |
|
||||
| Java 质量聚合摘要 | `node --test scripts/java-quality-summary.test.mjs`、`node scripts/java-quality-summary.mjs --output target/java-quality-summary.json`;聚合 Maven/服务拓扑、OpenAPI 覆盖、JaCoCo 和 SpotBugs 报告,默认只报告存量缺口,CI 用显式 `--require-jacoco --require-spotbugs` 检查报告完整性且当前允许失败 |
|
||||
| 单服务部署失败回滚合同 | `scripts/deploy-built-service-rollback.test.mjs`;模拟成功、健康失败和启动失败,确认仅恢复同服务先前健康镜像且部署任务仍失败 |
|
||||
| 容器日志单写合同 | `scripts/container-logging-contract.test.mjs`;离线确认应用只写标准输出、Compose 使用有界 `local` 驱动且不挂载应用日志目录 |
|
||||
|
||||
Admin/Client HTTP 脚本的离线合同只验证脚本结构、参数和模块边界,不访问测试环境:
|
||||
|
||||
|
||||
@@ -195,12 +195,16 @@ export async function run(context, matrix = context.createdResources.manualState
|
||||
await verify(context, OCEAN_BASE, status, resourceId)
|
||||
}
|
||||
|
||||
const releasedOcean = matrix.pages.oceanBookings.fixtures.RELEASED
|
||||
if (!releasedOcean.id || !releasedOcean.warehouseId) {
|
||||
throw new Error('RELEASED 订舱缺少拖车所需的仓库归属')
|
||||
}
|
||||
matrix.pages.truckAppointments = { requiredStatuses: TRUCK_STATUSES, fixtures: {} }
|
||||
for (const [index, status] of TRUCK_STATUSES.entries()) {
|
||||
const released = await buildOcean(context, template, 'RELEASED', index + 100)
|
||||
const releasedOcean = {
|
||||
id: id(released?.oceanBooking?.id),
|
||||
warehouseId: id(released?.oceanBooking?.warehouseId),
|
||||
}
|
||||
if (!releasedOcean.id || !releasedOcean.warehouseId) {
|
||||
throw new Error(`拖车状态样本 ${index} 缺少独立 RELEASED 订舱`)
|
||||
}
|
||||
const value = await buildTruck(context, releasedOcean, status, index)
|
||||
const resourceId = id(value?.id)
|
||||
matrix.pages.truckAppointments.fixtures[status] = { id: resourceId, status: value?.status, detailPath: `/truck-appointments/${resourceId}` }
|
||||
|
||||
@@ -43,7 +43,7 @@ async function loginMerchant(context) {
|
||||
const password = account?.password || process.env.MERCHANT_TEST_PASSWORD || ''
|
||||
if (!username || !password) throw new Error('缺少客户登录凭据')
|
||||
const response = await context.request('POST', '/api/auth/login', {
|
||||
username, password,
|
||||
loginId: username, password,
|
||||
}, '')
|
||||
const token = success(response) ? response.body?.data?.accessToken : null
|
||||
if (!token) throw new Error(`本轮客户登录失败: requestId=${response?.requestId || 'missing'}, traceId=${response?.traceId || 'missing'}`)
|
||||
@@ -134,6 +134,16 @@ function routes(context, merchantToken) {
|
||||
|
||||
export async function prepare(context) {
|
||||
const outerMarker = context.marker
|
||||
const isolatedResourceKeys = ['phase3Loading', 'phase3LoadingCompletedProducer', 'orchestrationFixtures']
|
||||
const isolatedResourceSnapshot = new Map(isolatedResourceKeys.map((name) => [name, {
|
||||
present: Object.hasOwn(context.createdResources, name), value: context.createdResources[name],
|
||||
}]))
|
||||
const restoreIsolatedResources = () => {
|
||||
for (const [name, snapshot] of isolatedResourceSnapshot) {
|
||||
if (snapshot.present) context.createdResources[name] = snapshot.value
|
||||
else delete context.createdResources[name]
|
||||
}
|
||||
}
|
||||
const restore = await context.preparePhase3LoadingFixture('-CUSTOMS-MIXED', false, {
|
||||
primary: 'BUY_DECLARATION', incremental: 'TAX_REFUND',
|
||||
})
|
||||
@@ -154,6 +164,7 @@ export async function prepare(context) {
|
||||
if (!source?.loadingPlanId || !source?.containerBookingId || source?.finalStatus !== 'LOADED') {
|
||||
context.marker = outerMarker
|
||||
restore()
|
||||
restoreIsolatedResources()
|
||||
throw new Error(`混合柜未进入LOADED: planId=${id(source?.loadingPlanId) || 'missing'}, status=${source?.finalStatus || 'missing'}`)
|
||||
}
|
||||
const platform = await context.request('GET', `${PLATFORM}/${encodeURIComponent(id(source.loadingPlanId))}`)
|
||||
@@ -169,6 +180,7 @@ export async function prepare(context) {
|
||||
if (!buy.length || !refund.length) {
|
||||
context.marker = outerMarker
|
||||
restore()
|
||||
restoreIsolatedResources()
|
||||
throw new Error(`正式装柜API未形成混合柜: buy=${buy.length}, tax=${refund.length}`)
|
||||
}
|
||||
const merchantToken = await loginMerchant(context)
|
||||
@@ -208,5 +220,6 @@ export async function prepare(context) {
|
||||
delete context.context.phase3ContainerCustomsRoutes
|
||||
context.marker = outerMarker
|
||||
restore()
|
||||
restoreIsolatedResources()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ test('outbound-created query supports automated or manual carrier then reaches P
|
||||
assert.match(pda, /convertReceivingContainers\(context, producers\)/)
|
||||
assert.match(pda, /last-leg\/orders\/container-summaries/)
|
||||
assert.match(pda, /last-leg\/inbound-plans\/convert-container/)
|
||||
assert.match(pda, /Number\(summary\?\.missingInboundCodeCount\) === 0/)
|
||||
assert.doesNotMatch(pda, /summary\?\.eligible/)
|
||||
assert.match(pda, /item\?\.shippingReady === true/)
|
||||
assert.match(pda, /nonBlank\(item\?\.carrierScheduleQueryId\)/)
|
||||
assert.match(pda, /targetCorrelated/)
|
||||
|
||||
@@ -171,6 +171,7 @@ Assert-Contains $firstlegAgents '/loading-plans/{planId}/lifecycle' 'Firstleg
|
||||
$orderOpenApi = Read-RequiredText (Join-Path $backendRoot 'order/src/main/resources/openapi/order-v1.yaml')
|
||||
$firstlegOpenApi = Read-RequiredText (Join-Path $backendRoot 'firstleg/src/main/resources/openapi/firstleg-v1.yaml')
|
||||
$orderDeclarationService = Read-RequiredText (Join-Path $backendRoot 'order/src/main/java/com/baiyue/order/service/OrderDeclarationUpdateService.java')
|
||||
$unifiedOrderQueryService = Read-RequiredText (Join-Path $backendRoot 'order/src/main/java/com/baiyue/order/service/UnifiedOrderQueryService.java')
|
||||
$firstlegDeclarationService = Read-RequiredText (Join-Path $backendRoot 'firstleg/src/main/java/com/baiyue/firstleg/service/OrderDeclarationSyncService.java')
|
||||
$adminDeclarationEditor = Read-RequiredText (Join-Path $workspaceRoot 'apps/admin-web/src/modules/unified-orders/components/OrderDeclarationEditorDialog.vue')
|
||||
Assert-Contains $rootAgents '平台修改客户原始申报与履约渠道' '根订单申报快速路由'
|
||||
@@ -186,6 +187,11 @@ Assert-Contains $orderDeclarationService 'ORDER_DECLARATION_UPDATED_V1' 'Order
|
||||
Assert-Contains $firstlegDeclarationService 'mapper.byEvent(event.tenantId(), event.eventId())' 'Firstleg 申报 eventId 幂等'
|
||||
Assert-Contains $firstlegDeclarationService 'event.version() < current.orderVersion()' 'Firstleg 申报版本单调性'
|
||||
Assert-Contains $adminDeclarationEditor '仓库/PDA 实测数据不会被覆盖' 'Admin 申报事实边界提示'
|
||||
Assert-Contains $rootAgents '统一列表仍固定显示 `PENDING_REVIEW` 并提供审核动作' '根待审核订单优先状态'
|
||||
Assert-Contains $orderAgents '统一主状态/下一动作仍固定为 `PENDING_REVIEW/APPROVE_ORDER`' 'Order 待审核优先状态'
|
||||
Assert-Contains $unifiedOrderQueryService 'order.getStatus() == OrderStatus.SUBMITTED' 'Order 待审核状态判断'
|
||||
Assert-Contains $unifiedOrderQueryService 'pendingReview ? "PENDING_REVIEW" : summary.mainStatus()' 'Order 待审核主状态优先'
|
||||
Assert-Contains $unifiedOrderQueryService 'pendingReview ? "APPROVE_ORDER" : summary.nextAction()' 'Order 待审核动作优先'
|
||||
|
||||
# 企业联系信息与账号登录标识已经解耦,资质审核不得重新把法人身份证设为必填。
|
||||
$authAgents = Read-RequiredText (Join-Path $backendRoot 'auth/AGENTS.md')
|
||||
@@ -209,9 +215,18 @@ Assert-Contains $adminCustomersView '不会修改企业管理员登录手机号'
|
||||
Assert-Contains $clientCompanyProfile '不会修改任何成员的登录手机号' 'Client 公司联系电话提示'
|
||||
Assert-Contains $clientMembersView '用于该成员手机号登录;修改后会同步认证账号并退出该成员现有会话' 'Client 成员登录手机号提示'
|
||||
$fileAgents = Read-RequiredText (Join-Path $backendRoot 'file/AGENTS.md')
|
||||
$qualificationOpenApi = Read-RequiredText (Join-Path $backendRoot 'file/src/main/resources/openapi/qualification-required-package-v1.yaml')
|
||||
Assert-Contains $rootAgents '法人身份证选填,可在已有营业执照后单独补传或替换' '根法人身份证单独上传语义'
|
||||
Assert-Contains $fileAgents '法人身份证选填,客户可在已有营业执照后通过 `/api/merchant/v1/files/qualifications` 单独新增或替换' 'File 法人身份证单独上传入口'
|
||||
Assert-Contains $qualificationOpenApi '/api/merchant/v1/files/qualifications:' '客户单文件资质 OpenAPI'
|
||||
Assert-Contains $qualificationOpenApi 'LEGAL_PERSON_ID 可在已有营业执照后单独新增或替换' '客户法人身份证单独上传契约'
|
||||
Assert-Contains $qualificationOpenApi 'BUSINESS_LICENSE 不允许走此入口' '营业执照整包上传边界'
|
||||
Assert-Contains $clientCompanyProfile '法人身份证已保存,尚未提交审核' 'Client 法人身份证单独上传反馈'
|
||||
Assert-NotContains $clientCompanyProfile '当前文件服务尚未开放法人身份证单独上传或替换' 'Client 已移除法人身份证旧阻断'
|
||||
$truckOpenApi = Read-RequiredText (Join-Path $backendRoot 'firstleg/src/main/resources/openapi/truck-appointments-v1.yaml')
|
||||
$loadingListsOpenApi = Read-RequiredText (Join-Path $backendRoot 'firstleg/src/main/resources/openapi/loading-lists-v1.yaml')
|
||||
$pdaLoadingApi = Read-RequiredText (Join-Path $workspaceRoot 'apps/pda-uniapp/api/pda-loading.js')
|
||||
$cabinetEvidenceService = Read-RequiredText (Join-Path $backendRoot 'firstleg/src/main/java/com/baiyue/firstleg/loading/service/CabinetLoadingEvidenceService.java')
|
||||
Assert-Contains $rootAgents '拖车预约由 `RELEASED` 订舱拥有,不按客户、柜次、装柜计划或真实柜号归属' '根订舱级拖车语义'
|
||||
Assert-Contains $rootAgents '证据以 `taskId + warehouseId` 进入任务专用 API' '根柜级证据入口'
|
||||
Assert-Contains $adminAgents '订舱级拖车预约' 'Admin 拖车快速入口'
|
||||
@@ -236,6 +251,11 @@ Assert-Contains $adminAgents 'PDA 最终完成前必须补齐' 'Admin 真实柜
|
||||
Assert-Contains $pdaAgents '当前 `taskId + warehouseId` 走任务专用 API' '国内 PDA 柜级证据入口'
|
||||
Assert-Contains $pdaLoadingApi '/first-leg/pda-loading/tasks/${encodeURIComponent(taskId)}/evidence?warehouseId=' '国内 PDA 证据查询 API'
|
||||
Assert-NotContains $pdaLoadingApi '/inbounds/${encodeURIComponent(inboundId)}/loading/evidence' '国内 PDA 旧入仓证据 API'
|
||||
Assert-Contains $rootAgents '上传以幂等键绑定包含文件内容的请求哈希' '根装柜证据上传重放边界'
|
||||
Assert-Contains $firstlegAgents '完全重放返回当前视图且不得再次上传 File' 'Firstleg 装柜证据重放边界'
|
||||
Assert-Contains $cabinetEvidenceService 'uploadHash(warehouseId, expectedVersion, evidenceType, sealNo, remark, multipart)' '装柜证据上传请求哈希'
|
||||
Assert-Contains $cabinetEvidenceService 'digest.update(multipart.getBytes())' '装柜证据文件内容哈希'
|
||||
Assert-Matches $cabinetEvidenceService '(?s)mapper\.byOperation\(taskId, key\).{0,500}?return view\(scope\);.{0,500}?files\.upload' '装柜证据上传先查重放再调用 File'
|
||||
$mapperModuleAgents = @(
|
||||
@{ Module = 'audit-service'; Application = 'BaiyueAuditApplication.java' },
|
||||
@{ Module = 'auth'; Application = 'BaiyueAuthApplication.java' },
|
||||
@@ -501,7 +521,11 @@ Assert-Contains $lastlegBusinessOpenApi '/api/platform/v1/last-leg/orders/contai
|
||||
Assert-Contains $lastlegBusinessOpenApi '/api/platform/v1/last-leg/inbound-plans/convert-container:' 'Lastleg 整柜计划转换契约'
|
||||
Assert-Contains $lastlegBusinessOpenApi 'sourceContainerBookingId' 'Lastleg 稳定来源柜身份'
|
||||
Assert-Contains $adminAgents '/orders/container-summaries' 'Admin 柜级摘要目标入口'
|
||||
Assert-Contains $adminAgents 'allPlansReady' 'Admin 柜级计划就绪事实'
|
||||
Assert-Contains $rootAgents '只分页返回仍有入仓码待生成有效独立计划的柜' '根柜级计划待处理筛选'
|
||||
Assert-Contains $backendAgents '分页前排除全部入仓码均已生成有效独立计划的柜' '后端柜级计划待处理筛选'
|
||||
Assert-Contains $adminAgents '返回仍有待生成计划入仓码的柜' 'Admin 柜级计划待处理筛选'
|
||||
Assert-Contains $lastlegAgents '分页前排除全部入仓码均已生成有效独立计划的柜' 'Lastleg 柜级计划待处理筛选'
|
||||
Assert-Contains $lastlegBusinessOpenApi '仅返回仍有入仓码待生成有效独立计划的柜' 'Lastleg 柜级计划待处理 OpenAPI'
|
||||
Assert-Contains $adminAgents 'ACTIVE + FBA' 'Admin FBA 权威仓候选'
|
||||
Assert-Contains $adminAgents '/inbound-plans/convert-container' 'Admin 整柜转换目标入口'
|
||||
Assert-Contains $adminLastMileApi '/orders/container-summaries' 'Admin 柜级摘要真实调用'
|
||||
@@ -837,5 +861,5 @@ if ($failures.Count -gt 0) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "AGENTS 索引合同通过:$checked 项检查,覆盖快速表唯一性、部署单元、开发/测试任务登记隔离、前端模块、Client active 路由、脚本目录治理、企业联系信息与登录标识解耦、订单申报版本同步与渠道门禁、业务编号与内部 ID 隔离、装柜清单生命周期与真实柜号后补、订舱级拖车与柜级证据、四/五期范围边界、仓库主档唯一权威、订舱候选与柜量语义、整柜出仓与船节点单调性、Gateway 覆盖、PDA 页面/运行配置/独立更新/语言预留/能力开关、人工分段验收链、Maven 模块、Lastleg 海外安全投影、同步 Trace、关键入口和排除目录。" -ForegroundColor Green
|
||||
Write-Host "AGENTS 索引合同通过:$checked 项检查,覆盖快速表唯一性、部署单元、开发/测试任务登记隔离、前端模块、Client active 路由、脚本目录治理、企业联系信息与登录标识解耦、选填法人身份证独立上传、待审核订单状态优先、订单申报版本同步与渠道门禁、装柜证据内容哈希重放、业务编号与内部 ID 隔离、装柜清单生命周期与真实柜号后补、订舱级拖车与柜级证据、四/五期范围边界、四期柜级待处理计划筛选与范围漂移、仓库主档唯一权威、订舱候选与柜量语义、整柜出仓与船节点单调性、Gateway 覆盖、PDA 页面/运行配置/独立更新/语言预留/能力开关、人工分段验收链、Maven 模块、Lastleg 海外安全投影、同步 Trace、关键入口和排除目录。" -ForegroundColor Green
|
||||
exit 0
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { test } from 'node:test';
|
||||
|
||||
const applicationConfigs = [
|
||||
'auth/src/main/resources/application.yml',
|
||||
'audit-service/src/main/resources/application.yml',
|
||||
'event-bridge-service/src/main/resources/application.yml',
|
||||
'file/src/main/resources/application.yml',
|
||||
'finance/src/main/resources/application.yml',
|
||||
'firstleg/src/main/resources/application.yml',
|
||||
'gateway/src/main/resources/application.yml',
|
||||
'gateway-overseas/src/main/resources/application.yml',
|
||||
'iam/src/main/resources/application.yml',
|
||||
'integration-service/src/main/resources/application.yml',
|
||||
'lastleg-service/src/main/resources/application.yml',
|
||||
'order/src/main/resources/application.yml',
|
||||
'print/src/main/resources/application.yml',
|
||||
'warehouse/src/main/resources/application.yml',
|
||||
'warehouse-overseas-service/src/main/resources/application.yml',
|
||||
'config/nacos/cn/baiyue-common.yml',
|
||||
'config/nacos/local/baiyue-common.yml',
|
||||
'config/nacos/overseas/baiyue-common.yml',
|
||||
];
|
||||
|
||||
const composeFiles = [
|
||||
'deploy/docker-compose.yml',
|
||||
'deploy/docker-compose.overseas.yml',
|
||||
];
|
||||
|
||||
test('deployable applications log only to stdout and stderr', () => {
|
||||
for (const path of applicationConfigs) {
|
||||
const source = readFileSync(path, 'utf8');
|
||||
assert.doesNotMatch(source, /LOG_PATH|LOGGING_FILE_NAME|LOGGING_ROLLING/, path);
|
||||
}
|
||||
});
|
||||
|
||||
test('Compose uses one bounded local log stream without application log mounts', () => {
|
||||
for (const path of composeFiles) {
|
||||
const source = readFileSync(path, 'utf8');
|
||||
assert.match(source, /driver:\s+local/, path);
|
||||
assert.match(source, /DOCKER_LOG_MAX_SIZE/, path);
|
||||
assert.match(source, /DOCKER_LOG_MAX_FILE/, path);
|
||||
assert.doesNotMatch(source, /LOG_PATH|LOG_ROOT|\/app\/logs/, path);
|
||||
}
|
||||
|
||||
const domesticCompose = readFileSync(composeFiles[0], 'utf8');
|
||||
assert.match(domesticCompose, /FILE_UPLOADS_HOST_DIR[^\n]+:\/data\/files/);
|
||||
});
|
||||
|
||||
test('runtime image does not prepare an application log directory', () => {
|
||||
const dockerfile = readFileSync('deploy/docker/Dockerfile', 'utf8');
|
||||
assert.doesNotMatch(dockerfile, /\/app\/logs/);
|
||||
});
|
||||
Reference in New Issue
Block a user