实现V4数据库目录与Lookup接口
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.control;
|
||||
|
||||
import static cn.nianxx.thhotel.support.MockMvcAuthTestSupport.loginToken;
|
||||
import static cn.nianxx.thhotel.support.MockMvcAuthTestSupport.performAuthorized;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import cn.nianxx.thhotel.ThHotelApplication;
|
||||
import cn.nianxx.thhotel.platform.hotel.repository.PlatformHotelRepository;
|
||||
import cn.nianxx.thhotel.platform.identity.common.enums.PlatformUserStatus;
|
||||
import cn.nianxx.thhotel.platform.identity.domain.PlatformUserEntity;
|
||||
import cn.nianxx.thhotel.platform.identity.repository.PlatformIdentityRepository;
|
||||
import cn.nianxx.thhotel.platform.identity.service.impl.AuthPasswordService;
|
||||
import java.time.LocalDateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
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.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@SpringBootTest(
|
||||
classes = ThHotelApplication.class,
|
||||
properties = {
|
||||
"spring.datasource.url=jdbc:h2:mem:reservation_v4_catalog_lookup_controller_test;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE",
|
||||
"auth.bootstrap.admin.username=v4-catalog-admin",
|
||||
"auth.bootstrap.admin.password=Admin@123456",
|
||||
"auth.bootstrap.admin.display-name=V4目录管理员",
|
||||
"auth.bootstrap.default-hotel-id=HOTEL-TEST",
|
||||
"auth.bootstrap.default-hotel-name=测试酒店",
|
||||
"auth.bootstrap.default-hotel-time-zone=Asia/Bangkok",
|
||||
"auth.session.ttl-minutes=720"
|
||||
})
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
class ReservationV4CatalogLookupControllerTest {
|
||||
|
||||
private static final String HOTEL_ID = "HOTEL-TEST";
|
||||
private static final String OTHER_HOTEL_ID = "HOTEL-OTHER";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private PlatformIdentityRepository identityRepository;
|
||||
|
||||
@Autowired
|
||||
private PlatformHotelRepository hotelRepository;
|
||||
|
||||
@Autowired
|
||||
private AuthPasswordService passwordService;
|
||||
|
||||
private String adminToken;
|
||||
private String noPermissionToken;
|
||||
|
||||
@BeforeEach
|
||||
void ensureNoPermissionUser() {
|
||||
PlatformUserEntity user = identityRepository.findUserByUsername("v4-catalog-no-permission")
|
||||
.orElseGet(() -> {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
PlatformUserEntity created = new PlatformUserEntity();
|
||||
created.setUsername("v4-catalog-no-permission");
|
||||
created.setPasswordHash(passwordService.hash("NoPerm@123456"));
|
||||
created.setDisplayName("V4 目录无权限用户");
|
||||
created.setUserStatus(PlatformUserStatus.ACTIVE.name());
|
||||
created.setSuperAdmin(false);
|
||||
created.setPasswordChangedAt(now);
|
||||
created.setCreatedAt(now);
|
||||
created.setUpdatedAt(now);
|
||||
identityRepository.insertUser(created);
|
||||
return created;
|
||||
});
|
||||
hotelRepository.ensureUserHotel(user.getId(), HOTEL_ID, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAccountLookupFromDatabaseSeed() throws Exception {
|
||||
performAuthorized(mockMvc, adminToken(), get("/api/reservation/lookups/accounts")
|
||||
.param("hotel_id", HOTEL_ID)
|
||||
.param("keyword", "QBD")
|
||||
.param("page_num", "1")
|
||||
.param("page_size", "20"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.hotel_id").value(HOTEL_ID))
|
||||
.andExpect(jsonPath("$.catalog_type").value("ACCOUNT"))
|
||||
.andExpect(jsonPath("$.catalog_source").value("FIXED_SEED_IMPORT"))
|
||||
.andExpect(jsonPath("$.stale").value(false))
|
||||
.andExpect(jsonPath("$.items[0].code").value("QBD_TRAVEL"))
|
||||
.andExpect(jsonPath("$.items[0].display_name").value("Q.B.D. TRAVEL GROUP CO., LTD"))
|
||||
.andExpect(jsonPath("$.items[0].market_code").value("LEISURE"))
|
||||
.andExpect(jsonPath("$.items[0].source_code").value("TRAVEL_AGENT"))
|
||||
.andExpect(jsonPath("$.page.page_num").value(1))
|
||||
.andExpect(jsonPath("$.page.page_size").value(20))
|
||||
.andExpect(jsonPath("$.page.total").value(1))
|
||||
.andExpect(jsonPath("$.warnings").isArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnRoomTypeAndRateCodeLookupFromDatabaseSeed() throws Exception {
|
||||
performAuthorized(mockMvc, adminToken(), get("/api/reservation/lookups/room-types")
|
||||
.param("hotel_id", HOTEL_ID)
|
||||
.param("keyword", "RM2"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.catalog_type").value("ROOM_TYPE"))
|
||||
.andExpect(jsonPath("$.catalog_source").value("FIXED_SEED_IMPORT"))
|
||||
.andExpect(jsonPath("$.items[0].code").value("RM2"))
|
||||
.andExpect(jsonPath("$.items[0].status").value("ACTIVE"));
|
||||
|
||||
performAuthorized(mockMvc, adminToken(), get("/api/reservation/lookups/rate-codes")
|
||||
.param("hotel_id", HOTEL_ID)
|
||||
.param("keyword", "GROUP"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.catalog_type").value("RATE_CODE"))
|
||||
.andExpect(jsonPath("$.catalog_source").value("FIXED_SEED_IMPORT"))
|
||||
.andExpect(jsonPath("$.items[0].code").value("GROUP"))
|
||||
.andExpect(jsonPath("$.items[0].pricing_available").value(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectCatalogLookupWithoutPermissionOrHotelAccess() throws Exception {
|
||||
performAuthorized(mockMvc, noPermissionToken(), get("/api/reservation/lookups/accounts")
|
||||
.param("hotel_id", HOTEL_ID))
|
||||
.andExpect(status().isForbidden())
|
||||
.andExpect(jsonPath("$.error_code").value("FRONTEND_PERMISSION_DENIED"));
|
||||
|
||||
performAuthorized(mockMvc, adminToken(), get("/api/reservation/lookups/accounts")
|
||||
.param("hotel_id", OTHER_HOTEL_ID))
|
||||
.andExpect(status().isForbidden())
|
||||
.andExpect(jsonPath("$.error_code").value("HOTEL_ACCESS_DENIED"));
|
||||
}
|
||||
|
||||
private String adminToken() throws Exception {
|
||||
if (adminToken == null) {
|
||||
adminToken = loginToken(mockMvc, "v4-catalog-admin", "Admin@123456");
|
||||
}
|
||||
return adminToken;
|
||||
}
|
||||
|
||||
private String noPermissionToken() throws Exception {
|
||||
if (noPermissionToken == null) {
|
||||
noPermissionToken = loginToken(mockMvc, "v4-catalog-no-permission", "NoPerm@123456");
|
||||
}
|
||||
return noPermissionToken;
|
||||
}
|
||||
}
|
||||
@@ -246,6 +246,32 @@ class ReservationV4CommandControllerTest {
|
||||
.andExpect(jsonPath("$.details[0]").value("business_fields.after.room_items.0.room_type_code: 房型代码不在第一版目录中。"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectBusinessCardConfirmWhenTraceTargetRoomTypeCodeUnknown() throws Exception {
|
||||
SeededOrderTask seeded = seedOrderTaskWithBusinessCard(
|
||||
HOTEL_ID,
|
||||
"mail-v4-command-confirm-unknown-trace-target-room-001",
|
||||
Instant.parse("2026-07-19T01:13:05Z"),
|
||||
null,
|
||||
"""
|
||||
{"card_type":"TRACE_RESERVATION_NOTES","event_type":"TRACE_RESERVATION_NOTES","business_fields":{"order_ref":"ORDER-COMMAND","event_type":"TRACE_RESERVATION_NOTES","trace_items":[{"item_type":"EXTRA_BED","text":"Please add one extra bed.","target_room_type_code":"UNKNOWN_ROOM","extra_bed_room_count":1,"department_code":"FO"}]}}
|
||||
""");
|
||||
confirmBasicCard(seeded);
|
||||
|
||||
performAuthorized(mockMvc, adminToken(), post("/api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/confirm",
|
||||
seeded.orderTask().id(),
|
||||
seeded.businessCard().id())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"version": 0
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error_code").value("V4_FIELD_VALIDATION_FAILED"))
|
||||
.andExpect(jsonPath("$.details[0]").value("business_fields.trace_items.0.target_room_type_code: 房型代码不在第一版目录中。"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfirmWrappedBusinessFieldsAndDropInjectedFields() throws Exception {
|
||||
SeededOrderTask seeded = seedOrderTaskWithBusinessCard(
|
||||
|
||||
@@ -1279,6 +1279,35 @@ class SuperAgentTaskResultControllerTest {
|
||||
.contains("房型代码不在第一版目录中");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMarkV4TraceReviewRequiredWhenTargetRoomTypeCodeUnknown() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-v4-trace-unknown-target-room-001");
|
||||
String body = v4CancelTraceRoomingListBody("mail-v4-trace-unknown-target-room-001")
|
||||
.replace("GRP-V4-CTR-001", "GRP-V4-TRACE-UNKNOWN-ROOM-001")
|
||||
.replace("\"item_type\": \"GENERAL\"", "\"item_type\": \"EXTRA_BED\"")
|
||||
.replace("\"text\": \"Guest requests quiet rooms.\"", "\"text\": \"Please add one extra bed.\"")
|
||||
.replace("\"department_code\": \"FO\"", "\"target_room_type_code\": \"UNKNOWN_ROOM\",\n"
|
||||
+ " \"extra_bed_room_count\": 1,\n"
|
||||
+ " \"department_code\": \"FO\"");
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-v4-trace-unknown-target-room-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.accepted_count").value(3));
|
||||
|
||||
String traceValidationErrors = jdbcTemplate.queryForObject("""
|
||||
SELECT validation_errors_json
|
||||
FROM workflow_reservation_v4_task_card
|
||||
WHERE source_message_id = ?
|
||||
AND card_type = 'TRACE_RESERVATION_NOTES'
|
||||
AND card_status = 'REVIEW_REQUIRED'
|
||||
AND review_status = 'PENDING'
|
||||
LIMIT 1
|
||||
""", String.class, source.inboxId());
|
||||
assertThat(traceValidationErrors)
|
||||
.contains("business_fields.trace_items.0.target_room_type_code")
|
||||
.contains("房型代码不在第一版目录中");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateV4CancelTraceAndRoomingListTasksInEventOrder() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-v4-cancel-trace-rooming-001");
|
||||
|
||||
Reference in New Issue
Block a user