1
0

feat: 增加简单的测试

This commit is contained in:
2025-08-14 19:05:25 +08:00
parent 222bb1e416
commit 89cdecef3d
10 changed files with 376 additions and 17 deletions

View File

@@ -32,6 +32,12 @@
<artifactId>fenix-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>

View File

@@ -6,7 +6,6 @@ import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
import com.lanyuanxiaoyao.service.template.helper.ObjectHelper;
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -22,16 +21,17 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
@PostMapping(SAVE)
@Override
public GlobalResponse<Long> save(@RequestBody SAVE_ITEM item) throws Exception {
SaveItemMapper<ENTITY, SAVE_ITEM> mapper = saveItemMapper();
var mapper = saveItemMapper();
return GlobalResponse.responseSuccess(service.save(mapper.from(item)));
}
@GetMapping(LIST)
@Override
public GlobalCrudResponse<LIST_ITEM> list() throws Exception {
ListItemMapper<ENTITY, LIST_ITEM> mapper = listItemMapper();
var mapper = listItemMapper();
var result = service.list();
return GlobalCrudResponse.responseCrudData(
service.list()
result
.stream()
.map(entity -> {
try {
@@ -40,7 +40,8 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
throw new RuntimeException(e);
}
})
.toList()
.toList(),
result.size()
);
}
@@ -50,8 +51,8 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
if (ObjectHelper.isNull(query)) {
return GlobalCrudResponse.responseCrudData(List.of(), 0);
}
ListItemMapper<ENTITY, LIST_ITEM> mapper = listItemMapper();
Page<ENTITY> result = service.list(query);
var mapper = listItemMapper();
var result = service.list(query);
return GlobalCrudResponse.responseCrudData(
result.get()
.map(entity -> {
@@ -69,7 +70,7 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
@GetMapping(DETAIL)
@Override
public GlobalResponse<DETAIL_ITEM> detail(@PathVariable("id") Long id) throws Exception {
DetailItemMapper<ENTITY, DETAIL_ITEM> mapper = detailItemMapper();
var mapper = detailItemMapper();
return GlobalResponse.responseSuccess(mapper.from(service.detailOrThrow(id)));
}

View File

@@ -39,6 +39,6 @@ public class SimpleEntity extends IdOnlyEntity {
return "SimpleEntity{" +
"createdTime=" + createdTime +
", modifiedTime=" + modifiedTime +
'}';
"} " + super.toString();
}
}

View File

@@ -14,12 +14,15 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implements SimpleService<ENTITY> {
private static final Logger log = LoggerFactory.getLogger(SimpleServiceSupport.class);
private static final Integer DEFAULT_PAGE_INDEX = 1;
private static final Integer DEFAULT_PAGE_SIZE = 10;
protected final SimpleRepository<ENTITY, Long> repository;
@@ -31,7 +34,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
@Transactional(rollbackOn = Throwable.class)
@Override
public Long save(ENTITY entity) {
if (!ObjectHelper.isNull(entity.getId())) {
if (ObjectHelper.isNotNull(entity.getId())) {
var id = entity.getId();
var targetEntity = repository.findById(entity.getId()).orElseThrow(() -> new IdNotFoundException(id));
BeanUtils.copyProperties(entity, targetEntity, "id", "created_time", "modified_time");
@@ -82,7 +85,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
if (ObjectHelper.isEmpty(column)) {
throw new IllegalArgumentException("Column cannot be blank");
}
String[] columns = column.split("/");
var columns = column.split("/");
Path<Y> path = root.get(columns[0]);
for (int i = 1; i < columns.length; i++) {
path = path.get(columns[i]);
@@ -92,15 +95,20 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
@SuppressWarnings({"unchecked", "rawtypes"})
private <Y> Object value(Path<Y> column, Object value) {
Class<?> javaType = column.getJavaType();
var javaType = column.getJavaType();
if (javaType.isEnum()) {
return Enum.valueOf((Class<Enum>) javaType, (String) value);
if (value instanceof String enumName) {
var enumType = (Class<Enum>) javaType;
return Enum.valueOf(enumType, enumName);
} else {
throw new IllegalArgumentException("枚举类型字段需要String类型的值");
}
}
return value;
}
protected List<Predicate> queryPredicates(Query.Queryable queryable, Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
var predicates = new ArrayList<Predicate>();
if (ObjectHelper.isNull(queryable)) {
return predicates;
}
@@ -118,13 +126,13 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
}
if (ObjectHelper.isNotEmpty(queryable.getEqual())) {
queryable.getEqual().forEach((column, value) -> {
Path<Object> path = column(root, column);
var path = column(root, column);
predicates.add(builder.equal(path, value(path, value)));
});
}
if (ObjectHelper.isNotEmpty(queryable.getNotEqual())) {
queryable.getEqual().forEach((column, value) -> {
Path<Object> path = column(root, column);
var path = column(root, column);
predicates.add(builder.notEqual(path, value(path, value)));
});
}
@@ -167,7 +175,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
@Override
public Page<ENTITY> list(Query listQuery) {
PageRequest pageRequest = PageRequest.of(DEFAULT_PAGE_INDEX - 1, DEFAULT_PAGE_SIZE, Sort.by("created_time").descending());
var pageRequest = PageRequest.of(DEFAULT_PAGE_INDEX - 1, DEFAULT_PAGE_SIZE, Sort.by("created_time").descending());
if (ObjectHelper.isNotNull(listQuery.getPage())) {
pageRequest = PageRequest.of(
ObjectHelper.defaultIfNull(listQuery.getPage().getIndex(), DEFAULT_PAGE_INDEX) - 1,

View File

@@ -0,0 +1,111 @@
package com.lanyuanxiaoyao.service.template;
import com.blinkfox.fenix.EnableFenix;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
/**
* @author lanyuanxiaoyao
* @version 20250814
*/
@SpringBootApplication
@EnableFenix
@EnableJpaAuditing
public class TestApplication {
private static final Logger log = LoggerFactory.getLogger(TestApplication.class);
private static final String BASE_URL = "http://localhost:2490";
private static final RestTemplate REST_CLIENT = new RestTemplate();
private static final ObjectMapper MAPPER = new ObjectMapper();
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
@EventListener(ApplicationReadyEvent.class)
public void runTests() throws JsonProcessingException {
// 增
var cid1 = saveItem("company", "{\"name\": \"Apple\"}").get("data").asLong();
var cid2 = saveItem("company", "{\"name\": \"Banana\"}").get("data").asLong();
var cid3 = saveItem("company", "{\"name\": \"Cheery\"}").get("data").asLong();
// 查
var companies = listItems("company");
Assert.isTrue(companies.at("/data/items").size() == 3, "数量错误");
Assert.isTrue(companies.at("/data/total").asLong() == 3, "返回数量错误");
var company1 = detailItem("company", cid1);
Assert.isTrue(cid1 == company1.at("/data/id").asLong(), "id错误");
Assert.isTrue("Apple".equals(company1.at("/data/name").asText()), "name错误");
// 改
var cid4 = saveItem("company", "{\"id\": %d, \"name\": \"Dog\"}".formatted(cid2)).get("data").asLong();
Assert.isTrue(cid2 == cid4, "id错误");
var company2 = detailItem("company", cid2);
Assert.isTrue("Dog".equals(company2.at("/data/name").asText()), "name错误");
// 删
removeItem("company", cid3);
Assert.isTrue(listItems("company").at("/data/items").size() == 2, "数量错误");
Assert.isTrue(listItems("company").at("/data/total").asLong() == 2, "返回数量错误");
System.exit(0);
}
private HttpHeaders headers() {
var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
private JsonNode saveItem(String path, String body) throws JsonProcessingException {
var response = REST_CLIENT.postForEntity(
"%s/%s/save".formatted(BASE_URL, path),
new HttpEntity<>(body, headers()),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private JsonNode listItems(String path) throws JsonProcessingException {
var response = REST_CLIENT.getForEntity(
"%s/%s/list".formatted(BASE_URL, path),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private JsonNode detailItem(String path, Long id) throws JsonProcessingException {
var response = REST_CLIENT.getForEntity(
"%s/%s/detail/%d".formatted(BASE_URL, path, id),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private void removeItem(String path, Long id) {
var response = REST_CLIENT.getForEntity(
"%s/%s/remove/%d".formatted(BASE_URL, path, id),
Void.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
}
}

View File

@@ -0,0 +1,158 @@
package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.entity.Company;
import com.lanyuanxiaoyao.service.template.service.CompanyService;
import java.time.LocalDateTime;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lanyuanxiaoyao
* @version 20250814
*/
@RestController
@RequestMapping("company")
public class CompanyController extends SimpleControllerSupport<Company, CompanyController.SaveItem, CompanyController.ListItem, CompanyController.DetailItem> {
public CompanyController(CompanyService service) {
super(service);
}
@Override
protected SaveItemMapper<Company, SaveItem> saveItemMapper() {
return item -> {
var company = new Company();
company.setId(item.getId());
company.setName(item.getName());
return company;
};
}
@Override
protected ListItemMapper<Company, ListItem> listItemMapper() {
return company -> {
var item = new ListItem();
item.setId(company.getId());
item.setName(company.getName());
return item;
};
}
@Override
protected DetailItemMapper<Company, DetailItem> detailItemMapper() {
return company -> {
var item = new DetailItem();
item.setId(company.getId());
item.setName(company.getName());
item.setCreatedTime(company.getCreatedTime());
item.setModifiedTime(company.getModifiedTime());
return item;
};
}
public static class SaveItem {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "SaveItem{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public static class ListItem {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "ListItem{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public static class DetailItem {
private Long id;
private String name;
private LocalDateTime createdTime;
private LocalDateTime modifiedTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDateTime getCreatedTime() {
return createdTime;
}
public void setCreatedTime(LocalDateTime createdTime) {
this.createdTime = createdTime;
}
public LocalDateTime getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(LocalDateTime modifiedTime) {
this.modifiedTime = modifiedTime;
}
@Override
public String toString() {
return "DetailItem{" +
"id=" + id +
", name='" + name + '\'' +
", createdTime=" + createdTime +
", modifiedTime=" + modifiedTime +
'}';
}
}
}

View File

@@ -0,0 +1,31 @@
package com.lanyuanxiaoyao.service.template.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Entity
@DynamicUpdate
@EntityListeners(AuditingEntityListener.class)
@Comment("企业")
public class Company extends SimpleEntity {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Company{" +
"name='" + name + '\'' +
"} " + super.toString();
}
}

View File

@@ -0,0 +1,12 @@
package com.lanyuanxiaoyao.service.template.repository;
import com.lanyuanxiaoyao.service.template.entity.Company;
import org.springframework.stereotype.Repository;
/**
* @author lanyuanxiaoyao
* @version 20250814
*/
@Repository
public interface CompanyRepository extends SimpleRepository<Company, Long> {
}

View File

@@ -0,0 +1,17 @@
package com.lanyuanxiaoyao.service.template.service;
import com.lanyuanxiaoyao.service.template.entity.Company;
import com.lanyuanxiaoyao.service.template.repository.CompanyRepository;
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
import org.springframework.stereotype.Service;
/**
* @author lanyuanxiaoyao
* @version 20250814
*/
@Service
public class CompanyService extends SimpleServiceSupport<Company> {
public CompanyService(CompanyRepository repository) {
super(repository);
}
}

View File

@@ -0,0 +1,15 @@
server:
port: 2490
spring:
application:
name: Test
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
username: test
password: test
driver-class-name: org.h2.Driver
jpa:
show-sql: true
generate-ddl: true
fenix:
print-banner: false