修复V4复核指针部署证明和运行时诊断
This commit is contained in:
@@ -25,6 +25,10 @@ class HealthControllerTest {
|
||||
mockMvc.perform(get("/api/health"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.status").value("UP"))
|
||||
.andExpect(jsonPath("$.service").value("th-hotel-server"));
|
||||
.andExpect(jsonPath("$.service").value("th-hotel-server"))
|
||||
.andExpect(jsonPath("$.runtime_marker").value("m002_v4_review_pointer_deployment_proof_v1"))
|
||||
.andExpect(jsonPath("$.build_commit").isNotEmpty())
|
||||
.andExpect(jsonPath("$.build_time").isNotEmpty())
|
||||
.andExpect(jsonPath("$.build_version").isNotEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package cn.nianxx.thhotel.platform.system.service.impl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import cn.nianxx.thhotel.platform.system.common.result.SystemBuildInfoResult;
|
||||
import java.util.Properties;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.info.BuildProperties;
|
||||
|
||||
class SystemBuildInfoServiceImplTest {
|
||||
|
||||
@Test
|
||||
void shouldReturnEmbeddedBuildCommitAsDeploymentProof() {
|
||||
Properties entries = new Properties();
|
||||
entries.setProperty("commit", "4bf5376");
|
||||
entries.setProperty("version", "0.0.1-SNAPSHOT");
|
||||
entries.setProperty("time", "2026-07-21T05:00:00Z");
|
||||
SystemBuildInfoServiceImpl service = new SystemBuildInfoServiceImpl(
|
||||
buildPropertiesProvider(new BuildProperties(entries)));
|
||||
|
||||
SystemBuildInfoResult result = service.getBuildInfo();
|
||||
|
||||
assertThat(result.runtimeMarker()).isEqualTo("m002_v4_review_pointer_deployment_proof_v1");
|
||||
assertThat(result.buildCommit()).isEqualTo("4bf5376");
|
||||
assertThat(result.buildTime()).isEqualTo("2026-07-21T05:00:00Z");
|
||||
assertThat(result.buildVersion()).isEqualTo("0.0.1-SNAPSHOT");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnUnknownWhenEmbeddedBuildCommitIsUnknown() {
|
||||
Properties entries = new Properties();
|
||||
entries.setProperty("commit", "UNKNOWN");
|
||||
entries.setProperty("version", "0.0.1-SNAPSHOT");
|
||||
SystemBuildInfoServiceImpl service = new SystemBuildInfoServiceImpl(
|
||||
buildPropertiesProvider(new BuildProperties(entries)));
|
||||
|
||||
SystemBuildInfoResult result = service.getBuildInfo();
|
||||
|
||||
assertThat(result.buildCommit()).isEqualTo("UNKNOWN");
|
||||
}
|
||||
|
||||
private ObjectProvider<BuildProperties> buildPropertiesProvider(BuildProperties buildProperties) {
|
||||
return new ObjectProvider<>() {
|
||||
@Override
|
||||
public BuildProperties getIfAvailable() {
|
||||
return buildProperties;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -44,9 +44,12 @@ import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
@@ -67,6 +70,7 @@ import org.springframework.test.web.servlet.MvcResult;
|
||||
})
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class ReservationV4CommandControllerTest {
|
||||
|
||||
private static final String HOTEL_ID = "HOTEL-TEST";
|
||||
@@ -1248,7 +1252,7 @@ class ReservationV4CommandControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectRoomInformationReviewWhenPointerTargetsDerivedReadonlyField() throws Exception {
|
||||
void shouldRejectRoomInformationReviewWhenPointerTargetsDerivedReadonlyField(CapturedOutput output) throws Exception {
|
||||
SeededOrderTask seeded = seedReviewOrderTaskWithBusinessCard(
|
||||
HOTEL_ID,
|
||||
"mail-v4-command-review-room-info-readonly-001",
|
||||
@@ -1294,9 +1298,25 @@ class ReservationV4CommandControllerTest {
|
||||
{"field_pointer": "/room_information/final_values/nights", "value": 7}
|
||||
]
|
||||
}
|
||||
"""))
|
||||
"""))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error_code").value("V4_REVIEW_POINTER_READONLY"));
|
||||
|
||||
org.assertj.core.api.Assertions.assertThat(output.getOut())
|
||||
.contains("review_pointer_policy=m002_v4_review_pointer_runtime_trace_v1")
|
||||
.contains("order_task_id=" + seeded.orderTask().id())
|
||||
.contains("card_id=" + seeded.businessCard().id())
|
||||
.contains("incoming_pointer=/room_information/final_values/nights")
|
||||
.contains("query_side_editable_pointers=")
|
||||
.contains("/room_information/final_values/room_items/0/room_type_code")
|
||||
.contains("command_side_allowed_pointers=")
|
||||
.contains("reject_reason=DERIVED_OR_SYSTEM_FIELD_READONLY")
|
||||
.doesNotContain("target_order")
|
||||
.doesNotContain("business_fields")
|
||||
.doesNotContain("raw_evidence")
|
||||
.doesNotContain("html_body")
|
||||
.doesNotContain("http://")
|
||||
.doesNotContain("https://");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1483,7 +1503,7 @@ class ReservationV4CommandControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectReviewResolutionForIllegalPointer() throws Exception {
|
||||
void shouldRejectReviewResolutionForIllegalPointer(CapturedOutput output) throws Exception {
|
||||
SeededOrderTask seeded = seedReviewOrderTask(
|
||||
HOTEL_ID,
|
||||
"mail-v4-command-review-illegal-pointer-001",
|
||||
@@ -1508,6 +1528,54 @@ class ReservationV4CommandControllerTest {
|
||||
"""))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error_code").value("V4_REVIEW_POINTER_INVALID"));
|
||||
|
||||
org.assertj.core.api.Assertions.assertThat(output.getOut())
|
||||
.contains("review_pointer_policy=m002_v4_review_pointer_runtime_trace_v1")
|
||||
.contains("order_task_id=" + seeded.orderTask().id())
|
||||
.contains("card_id=" + seeded.businessCard().id())
|
||||
.contains("incoming_pointer=business_fields/room_items/0/pms_room_type_code")
|
||||
.contains("reject_reason=V4_REVIEW_POINTER_INVALID")
|
||||
.doesNotContain("target_order")
|
||||
.doesNotContain("raw_evidence")
|
||||
.doesNotContain("html_body")
|
||||
.doesNotContain("http://")
|
||||
.doesNotContain("https://");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSanitizeUnsafeReviewPointerInRejectedLogs(CapturedOutput output) throws Exception {
|
||||
SeededOrderTask seeded = seedReviewOrderTask(
|
||||
HOTEL_ID,
|
||||
"mail-v4-command-review-unsafe-pointer-log-001",
|
||||
Instant.parse("2026-07-19T01:23:30Z"),
|
||||
990000000000070115L,
|
||||
ReservationV4CardStatus.PENDING_CONFIRM.name(),
|
||||
ReservationV4CardStatus.REVIEW_REQUIRED.name());
|
||||
confirmBasicCard(seeded);
|
||||
|
||||
performAuthorized(mockMvc, adminToken(), post(
|
||||
"/api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/review-resolution",
|
||||
seeded.orderTask().id(),
|
||||
seeded.businessCard().id())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"version": 0,
|
||||
"field_overrides": [
|
||||
{"field_pointer": "bad/user@example.test/abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGH/https://example.test/raw", "value": "RM2"}
|
||||
]
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error_code").value("V4_REVIEW_POINTER_INVALID"));
|
||||
|
||||
org.assertj.core.api.Assertions.assertThat(output.getOut())
|
||||
.contains("review_pointer_policy=m002_v4_review_pointer_runtime_trace_v1")
|
||||
.contains("incoming_pointer=bad/[EMAIL]/[TOKEN]/[URL]")
|
||||
.contains("reject_reason=V4_REVIEW_POINTER_INVALID")
|
||||
.doesNotContain("user@example.test")
|
||||
.doesNotContain("abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGH")
|
||||
.doesNotContain("https://example.test");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user