mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 18:10:30 +08:00
feat(platform): I4 delivery batches, lines, and license SN APIs
Add Flyway V4 tables, delivery-batches and license-sns endpoints with validation, audit actions, controller tests, and OpenAPI snapshot update. Made-with: Cursor
This commit is contained in:
+250
@@ -0,0 +1,250 @@
|
||||
package cn.craftlabs.platform.api.delivery;
|
||||
|
||||
import cn.craftlabs.platform.api.support.JwtTestSupport;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Transactional
|
||||
class DeliveryBatchControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Test
|
||||
void deliveryBatchLinesStatusAuditAndGuards() throws Exception {
|
||||
String token = JwtTestSupport.obtainBearerToken(mockMvc, objectMapper);
|
||||
String auth = "Bearer " + token;
|
||||
|
||||
String customerBody = "{\"name\":\"交付客户\",\"creditCode\":\"DB001\",\"status\":\"ACTIVE\"}";
|
||||
String customerJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/customers")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(customerBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long customerId = objectMapper.readTree(customerJson).get("id").asLong();
|
||||
|
||||
String projectBody =
|
||||
String.format(
|
||||
"{\"customerId\":%d,\"name\":\"交付项目\",\"phase\":\"PLANNING\"}", customerId);
|
||||
String projectJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/projects")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(projectBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long projectId = objectMapper.readTree(projectJson).get("id").asLong();
|
||||
|
||||
String otherProjectBody =
|
||||
String.format(
|
||||
"{\"customerId\":%d,\"name\":\"其他项目\",\"phase\":\"PLANNING\"}", customerId);
|
||||
String otherProjectJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/projects")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(otherProjectBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long otherProjectId = objectMapper.readTree(otherProjectJson).get("id").asLong();
|
||||
|
||||
String contractBody =
|
||||
String.format(
|
||||
"{\"customerId\":%d,\"projectId\":%d,\"title\":\"交付合同\",\"remarks\":\"\"}",
|
||||
customerId, projectId);
|
||||
String contractJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/contracts")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(contractBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long contractId = objectMapper.readTree(contractJson).get("id").asLong();
|
||||
|
||||
String lineBody = "{\"itemName\":\"合同行A\",\"quantity\":1,\"unit\":\"套\",\"amount\":1,\"remark\":\"\"}";
|
||||
String lineJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/contracts/" + contractId + "/lines")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(lineBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long contractLineId = objectMapper.readTree(lineJson).get("id").asLong();
|
||||
|
||||
String wrongContractBody =
|
||||
String.format(
|
||||
"{\"customerId\":%d,\"projectId\":%d,\"title\":\"错项目合同\",\"remarks\":\"\"}",
|
||||
customerId, otherProjectId);
|
||||
String wrongContractJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/contracts")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(wrongContractBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long wrongContractId = objectMapper.readTree(wrongContractJson).get("id").asLong();
|
||||
|
||||
String batchMismatch =
|
||||
String.format(
|
||||
"{\"projectId\":%d,\"contractId\":%d,\"batchCode\":\"B-MISMATCH\","
|
||||
+ "\"plannedDeliveryDate\":\"2026-05-01\",\"remarks\":\"x\"}",
|
||||
projectId, wrongContractId);
|
||||
mockMvc.perform(
|
||||
post("/api/v1/delivery-batches")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(batchMismatch))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.message").value(containsString("projectId")));
|
||||
|
||||
String batchCreate =
|
||||
String.format(
|
||||
"{\"projectId\":%d,\"contractId\":%d,\"batchCode\":\"B-001\","
|
||||
+ "\"plannedDeliveryDate\":\"2026-04-01\",\"remarks\":\"首批\"}",
|
||||
projectId, contractId);
|
||||
String batchJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/delivery-batches")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(batchCreate))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.status").value("PENDING"))
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long batchId = objectMapper.readTree(batchJson).get("id").asLong();
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/delivery-batches")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(batchCreate))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(jsonPath("$.message").value(containsString("duplicate")));
|
||||
|
||||
mockMvc.perform(
|
||||
get("/api/v1/delivery-batches")
|
||||
.header("Authorization", auth)
|
||||
.param("page", "0")
|
||||
.param("size", "10")
|
||||
.param("keyword", "B-0"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content[0].id").value(batchId))
|
||||
.andExpect(jsonPath("$.content[0].lines").doesNotExist());
|
||||
|
||||
mockMvc.perform(get("/api/v1/delivery-batches/" + batchId).header("Authorization", auth))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.lines").isArray());
|
||||
|
||||
String dLine =
|
||||
String.format(
|
||||
"{\"description\":\"清单项\",\"quantity\":2,\"contractLineId\":%d}",
|
||||
contractLineId);
|
||||
mockMvc.perform(
|
||||
post("/api/v1/delivery-batches/" + batchId + "/lines")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(dLine))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
mockMvc.perform(
|
||||
put("/api/v1/delivery-batches/" + batchId)
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"remarks\":\"改备注\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.remarks").value("改备注"));
|
||||
|
||||
mockMvc.perform(
|
||||
patch("/api/v1/delivery-batches/" + batchId + "/status")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"status\":\"DELIVERED\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.status").value("DELIVERED"))
|
||||
.andExpect(jsonPath("$.finishedAt").exists());
|
||||
|
||||
mockMvc.perform(
|
||||
put("/api/v1/delivery-batches/" + batchId)
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"remarks\":\"迟了\"}"))
|
||||
.andExpect(status().isConflict());
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/delivery-batches/" + batchId + "/lines")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"description\":\"晚加\",\"quantity\":1}"))
|
||||
.andExpect(status().isConflict());
|
||||
|
||||
String auditBody =
|
||||
mockMvc.perform(
|
||||
get("/api/v1/audit-events")
|
||||
.header("Authorization", auth)
|
||||
.param("entityType", "DELIVERY_BATCH")
|
||||
.param("entityId", String.valueOf(batchId))
|
||||
.param("page", "0")
|
||||
.param("size", "50"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
|
||||
JsonNode root = objectMapper.readTree(auditBody);
|
||||
boolean hasCreated = false;
|
||||
boolean hasLine = false;
|
||||
for (JsonNode row : root.get("content")) {
|
||||
String action = row.get("action").asText();
|
||||
if ("DELIVERY_BATCH_CREATED".equals(action)) {
|
||||
hasCreated = true;
|
||||
}
|
||||
if ("DELIVERY_LINE_ADDED".equals(action)) {
|
||||
hasLine = true;
|
||||
}
|
||||
}
|
||||
assertThat(hasCreated).isTrue();
|
||||
assertThat(hasLine).isTrue();
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
package cn.craftlabs.platform.api.license;
|
||||
|
||||
import cn.craftlabs.platform.api.support.JwtTestSupport;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Transactional
|
||||
class LicenseSnControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Test
|
||||
void licenseSnCreateDuplicateStatusTransitionsAndRevoke() throws Exception {
|
||||
String token = JwtTestSupport.obtainBearerToken(mockMvc, objectMapper);
|
||||
String auth = "Bearer " + token;
|
||||
|
||||
String customerBody = "{\"name\":\"SN客户\",\"creditCode\":\"SN001\",\"status\":\"ACTIVE\"}";
|
||||
String customerJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/customers")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(customerBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long customerId = objectMapper.readTree(customerJson).get("id").asLong();
|
||||
|
||||
String projectBody =
|
||||
String.format(
|
||||
"{\"customerId\":%d,\"name\":\"SN项目\",\"phase\":\"PLANNING\"}", customerId);
|
||||
String projectJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/projects")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(projectBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long projectId = objectMapper.readTree(projectJson).get("id").asLong();
|
||||
|
||||
String contractBody =
|
||||
String.format(
|
||||
"{\"customerId\":%d,\"projectId\":%d,\"title\":\"SN合同\",\"remarks\":\"\"}",
|
||||
customerId, projectId);
|
||||
String contractJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/contracts")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(contractBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long contractId = objectMapper.readTree(contractJson).get("id").asLong();
|
||||
|
||||
String lineBody = "{\"itemName\":\"许可行\",\"quantity\":1,\"unit\":\"个\",\"amount\":1,\"remark\":\"\"}";
|
||||
String lineJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/contracts/" + contractId + "/lines")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(lineBody))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long contractLineId = objectMapper.readTree(lineJson).get("id").asLong();
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/license-sns")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"snCode\":\" SN-001 \",\"activationRemark\":\"a\"}"))
|
||||
.andExpect(status().isBadRequest());
|
||||
|
||||
String createByProject =
|
||||
String.format(
|
||||
"{\"snCode\":\"SN-001\",\"projectId\":%d,\"activationRemark\":\"备注\"}",
|
||||
projectId);
|
||||
String snJson =
|
||||
mockMvc.perform(
|
||||
post("/api/v1/license-sns")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(createByProject))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.status").value("REGISTERED"))
|
||||
.andExpect(jsonPath("$.snCode").value("SN-001"))
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
long snId = objectMapper.readTree(snJson).get("id").asLong();
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/license-sns")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(createByProject))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(jsonPath("$.message").value(containsString("duplicate")));
|
||||
|
||||
String createByLineOnly =
|
||||
String.format("{\"snCode\":\"SN-LINE\",\"contractLineId\":%d}", contractLineId);
|
||||
mockMvc.perform(
|
||||
post("/api/v1/license-sns")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(createByLineOnly))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.projectId").value(projectId))
|
||||
.andExpect(jsonPath("$.contractLineId").value(contractLineId));
|
||||
|
||||
mockMvc.perform(
|
||||
get("/api/v1/license-sns")
|
||||
.header("Authorization", auth)
|
||||
.param("page", "0")
|
||||
.param("size", "20")
|
||||
.param("projectId", String.valueOf(projectId))
|
||||
.param("keyword", "SN-")
|
||||
.param("status", "REGISTERED"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content.length()").value(2));
|
||||
|
||||
mockMvc.perform(
|
||||
patch("/api/v1/license-sns/" + snId + "/status")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"status\":\"ACTIVATED\"}"))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(jsonPath("$.message").value(containsString("illegal")));
|
||||
|
||||
mockMvc.perform(
|
||||
patch("/api/v1/license-sns/" + snId + "/status")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"status\":\"ISSUED\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.status").value("ISSUED"));
|
||||
|
||||
mockMvc.perform(
|
||||
patch("/api/v1/license-sns/" + snId + "/status")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"status\":\"ACTIVATED\"}"))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
mockMvc.perform(
|
||||
put("/api/v1/license-sns/" + snId)
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"activationRemark\":\"已激活\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.activationRemark").value("已激活"));
|
||||
|
||||
mockMvc.perform(
|
||||
patch("/api/v1/license-sns/" + snId + "/status")
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"status\":\"REVOKED\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.status").value("REVOKED"));
|
||||
|
||||
mockMvc.perform(
|
||||
put("/api/v1/license-sns/" + snId)
|
||||
.header("Authorization", auth)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"activationRemark\":\"no\"}"))
|
||||
.andExpect(status().isConflict());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user