test(firstleg): harden pallet option page contract
This commit is contained in:
@@ -1328,10 +1328,10 @@ components:
|
||||
type: array
|
||||
maxItems: 100
|
||||
items: {$ref: '#/components/schemas/WeightPalletSnapshot'}
|
||||
page: {type: integer, format: int64, minimum: 1}
|
||||
size: {type: integer, format: int64, minimum: 1, maximum: 100}
|
||||
total: {type: integer, format: int64, minimum: 0}
|
||||
pages: {type: integer, format: int64, minimum: 0}
|
||||
page: {oneOf: [{type: integer, format: int64, minimum: 1}, {type: string, pattern: '^[1-9][0-9]*$'}]}
|
||||
size: {oneOf: [{type: integer, format: int64, minimum: 1, maximum: 100}, {type: string, pattern: '^(?:[1-9]|[1-9][0-9]|100)$'}]}
|
||||
total: {oneOf: [{type: integer, format: int64, minimum: 0}, {type: string, pattern: '^(?:0|[1-9][0-9]*)$'}]}
|
||||
pages: {oneOf: [{type: integer, format: int64, minimum: 0}, {type: string, pattern: '^(?:0|[1-9][0-9]*)$'}]}
|
||||
CreateWeightResultRequest:
|
||||
type: object
|
||||
required: [fileId, palletId]
|
||||
|
||||
@@ -254,6 +254,30 @@ class InboundMeasurementServiceTest {
|
||||
verify(warehousePallets).palletSnapshots("internal-token", 100L, 1L, 20L, "TP-5");
|
||||
}
|
||||
|
||||
@Test
|
||||
void weightPalletOptionsFailsClosedOnWarehouseNon2xxResponse() {
|
||||
InboundOrderEntity inbound = inbound("WAIT_MEASURE");
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
|
||||
when(warehousePallets.palletSnapshots("internal-token", 100L, 1L, 20L, "TP-5"))
|
||||
.thenThrow(mock(feign.FeignException.class));
|
||||
|
||||
assertThatThrownBy(() -> service.weightPalletOptions(1L, 10L, 1L, 20L, "TP-5"))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("仓库服务暂不可用");
|
||||
}
|
||||
|
||||
@Test
|
||||
void weightPalletOptionsFailsClosedOnWarehouseNetworkError() {
|
||||
InboundOrderEntity inbound = inbound("WAIT_MEASURE");
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
|
||||
when(warehousePallets.palletSnapshots("internal-token", 100L, 1L, 20L, null))
|
||||
.thenThrow(mock(feign.RetryableException.class));
|
||||
|
||||
assertThatThrownBy(() -> service.weightPalletOptions(1L, 10L, 1L, 20L, null))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("仓库服务暂不可用");
|
||||
}
|
||||
|
||||
@Test
|
||||
void creatingWeightResultRejectsPalletOutsideTheInboundWarehouse() {
|
||||
InboundOrderEntity inbound = inbound("WAIT_MEASURE");
|
||||
|
||||
@@ -7,6 +7,7 @@ const read = (relativeUrl) => readFileSync(new URL(relativeUrl, import.meta.url)
|
||||
const controller = read('../../firstleg/src/main/java/com/baiyue/firstleg/controller/PlatformInboundMeasurementController.java')
|
||||
const service = read('../../firstleg/src/main/java/com/baiyue/firstleg/service/InboundMeasurementService.java')
|
||||
const warehouseClient = read('../../firstleg/src/main/java/com/baiyue/firstleg/client/WarehousePalletLocationClient.java')
|
||||
const serviceTests = read('../../firstleg/src/test/java/com/baiyue/firstleg/service/InboundMeasurementServiceTest.java')
|
||||
const openapi = read('../../firstleg/src/main/resources/openapi/firstleg-v1.yaml')
|
||||
const adminApi = read('../../../apps/admin-web/src/modules/inbounds/weight/api/weightOcr.ts')
|
||||
const adminPanel = read('../../../apps/admin-web/src/modules/inbounds/weight/components/WeightOcrPanel.vue')
|
||||
@@ -112,3 +113,16 @@ test('historical pallet snapshot remains read-only visible when it is no longer
|
||||
assert.match(cards, /row\.palletTareWeightKg/)
|
||||
assert.match(adminBehavior, /(?:historical|history|历史|失去候选|no longer eligible).{0,220}(?:palletCode|托盘编码|TP-)/is)
|
||||
})
|
||||
|
||||
test('backend detail reads the stored historical pallet snapshot without revalidating current eligibility', () => {
|
||||
assert.match(serviceTests, /detailUsesStoredPalletSnapshotAfterCandidateBecomesUnavailable/)
|
||||
assert.match(serviceTests, /verify\(warehousePallets, never\(\)\)\.palletSnapshot/)
|
||||
assert.match(serviceTests, /verify\(warehousePallets, never\(\)\)\.palletSnapshots/)
|
||||
})
|
||||
|
||||
test('backend evidence fails closed for Warehouse non-2xx and network errors', () => {
|
||||
assert.match(serviceTests, /weightPalletOptionsFailsClosedOnWarehouseNon2xxResponse/)
|
||||
assert.match(serviceTests, /weightPalletOptionsFailsClosedOnWarehouseNetworkError/)
|
||||
assert.match(serviceTests, /FeignException/)
|
||||
assert.match(serviceTests, /RetryableException/)
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ const read = (relativeUrl) => readFileSync(new URL(relativeUrl, import.meta.url)
|
||||
const controller = read('../../firstleg/src/main/java/com/baiyue/firstleg/controller/PlatformInboundMeasurementController.java')
|
||||
const pageResult = read('../../common-core/src/main/java/com/baiyue/common/core/api/PageResult.java')
|
||||
const longJackson = read('../../common-web/src/main/java/com/baiyue/common/web/LongIdJacksonConfiguration.java')
|
||||
const serviceTests = read('../../firstleg/src/test/java/com/baiyue/firstleg/service/InboundMeasurementServiceTest.java')
|
||||
const openapi = read('../../firstleg/src/main/resources/openapi/firstleg-v1.yaml')
|
||||
const adminApi = read('../../../apps/admin-web/src/modules/inbounds/weight/api/weightOcr.ts')
|
||||
const adminApiTests = read('../../../apps/admin-web/src/modules/inbounds/weight/api/weightOcr.test.ts')
|
||||
@@ -84,3 +85,9 @@ test('Admin behavior separately covers a non-2xx HTTP or network error', () => {
|
||||
test('Admin aborts and ignores an expired candidate response', () => {
|
||||
assert.match(adminBehavior, /in-flight pallet request[\s\S]{0,900}signal\?\.aborted[\s\S]{0,300}过期托盘/)
|
||||
})
|
||||
|
||||
test('backend preserves failure evidence for Warehouse non-2xx and network errors', () => {
|
||||
assert.match(serviceTests, /weightPalletOptionsFailsClosedOnWarehouseNon2xxResponse/)
|
||||
assert.match(serviceTests, /weightPalletOptionsFailsClosedOnWarehouseNetworkError/)
|
||||
assert.match(serviceTests, /仓库服务暂不可用/)
|
||||
})
|
||||
|
||||
@@ -78,7 +78,7 @@ components:
|
||||
type: array
|
||||
maxItems: 100
|
||||
items: { $ref: '#/components/schemas/PalletSnapshot' }
|
||||
page: { type: integer, format: int64, minimum: 1 }
|
||||
size: { type: integer, format: int64, minimum: 1, maximum: 100 }
|
||||
total: { type: integer, format: int64, minimum: 0 }
|
||||
pages: { type: integer, format: int64, minimum: 0 }
|
||||
page: { oneOf: [{ type: integer, format: int64, minimum: 1 }, { type: string, pattern: '^[1-9][0-9]*$' }] }
|
||||
size: { oneOf: [{ type: integer, format: int64, minimum: 1, maximum: 100 }, { type: string, pattern: '^(?:[1-9]|[1-9][0-9]|100)$' }] }
|
||||
total: { oneOf: [{ type: integer, format: int64, minimum: 0 }, { type: string, pattern: '^(?:0|[1-9][0-9]*)$' }] }
|
||||
pages: { oneOf: [{ type: integer, format: int64, minimum: 0 }, { type: string, pattern: '^(?:0|[1-9][0-9]*)$' }] }
|
||||
|
||||
@@ -20,6 +20,10 @@ class PalletOpenApiContractTest {
|
||||
.contains("maximum: 100")
|
||||
.contains("maxItems: 100")
|
||||
.contains("PalletSnapshotPageResponse:")
|
||||
.contains("page: { oneOf:")
|
||||
.contains("size: { oneOf:")
|
||||
.contains("total: { oneOf:")
|
||||
.contains("pages: { oneOf:")
|
||||
.contains("required: [palletId, palletCode, palletType, capacityCbm, tareWeightKg]")
|
||||
.contains("additionalProperties: false")
|
||||
.doesNotContain("tenantId:")
|
||||
|
||||
Reference in New Issue
Block a user