feat(admin): 网关治理小页后端——路由/目标/台账查询与行内编辑
/api/internal/admin/**(RoutingFilter 旁路前缀,零过滤器改动):
- GET page(routes/targets/logs) + PUT routes/{id} / targets/{id}
- 值域白名单(visibility/authMode/cacheScope)非法 400 不落库;写后热重载注册表
- OpenApiLogRepository.pageLatest 两可空过滤+倒序分页
- 10 用例(page/列表/合法编辑/三态 400/limit 截断),全量 75/75 绿
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
package com.yunhai.platform.gateway.admin;
|
||||
|
||||
import com.yunhai.platform.gateway.entity.OpenApiLog;
|
||||
import com.yunhai.platform.gateway.entity.OpenApiRoute;
|
||||
import com.yunhai.platform.gateway.entity.OpenApiTarget;
|
||||
import com.yunhai.platform.gateway.repository.OpenApiLogRepository;
|
||||
import com.yunhai.platform.gateway.repository.OpenApiRouteRepository;
|
||||
import com.yunhai.platform.gateway.repository.OpenApiTargetRepository;
|
||||
import com.yunhai.platform.gateway.route.RouteRegistry;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 治理小页后端(N02 TDD):page/routes/targets/logs 查询形态 + 路由/目标行内编辑的
|
||||
* 值域闸(非法值 400 且不落库)+ 写后热重载调用。MockMvc standalone + mock 仓储/注册表。
|
||||
*/
|
||||
class InternalAdminControllerTest {
|
||||
|
||||
private OpenApiRouteRepository routeRepo;
|
||||
private OpenApiTargetRepository targetRepo;
|
||||
private OpenApiLogRepository logRepo;
|
||||
private RouteRegistry registry;
|
||||
private MockMvc mvc;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
routeRepo = Mockito.mock(OpenApiRouteRepository.class);
|
||||
targetRepo = Mockito.mock(OpenApiTargetRepository.class);
|
||||
logRepo = Mockito.mock(OpenApiLogRepository.class);
|
||||
registry = Mockito.mock(RouteRegistry.class);
|
||||
|
||||
InternalAdminController controller = new InternalAdminController();
|
||||
controller.setRepos(routeRepo, targetRepo, logRepo, registry);
|
||||
mvc = MockMvcBuilders.standaloneSetup(controller).build();
|
||||
|
||||
when(routeRepo.findAll()).thenReturn(new ArrayList<>(Arrays.asList(route(1L))));
|
||||
when(targetRepo.findAll()).thenReturn(new ArrayList<>(Arrays.asList(target(5L))));
|
||||
when(registry.reload()).thenReturn(22);
|
||||
when(registry.reloadTargets()).thenReturn(13);
|
||||
when(logRepo.pageLatest(any(), any(), any(Pageable.class)))
|
||||
.thenReturn(new PageImpl<>(Arrays.asList(logRow())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void page_servesHtml() throws Exception {
|
||||
mvc.perform(get("/api/internal/admin/page"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(result -> {
|
||||
String body = result.getResponse().getContentAsString();
|
||||
org.assertj.core.api.Assertions.assertThat(body).contains("<html");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void routes_listSorted() throws Exception {
|
||||
mvc.perform(get("/api/internal/admin/routes"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[0].targetCode").value("userbase"))
|
||||
.andExpect(jsonPath("$[0].pathPattern").value("/oapi/user/**"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void routeUpdate_legalValue_savesAndReloads() throws Exception {
|
||||
when(routeRepo.findById(1L)).thenReturn(java.util.Optional.of(route(1L)));
|
||||
mvc.perform(put("/api/internal/admin/routes/1")
|
||||
.contentType("application/json")
|
||||
.content("{\"authMode\":\"ANONYMOUS\",\"cacheTtlSeconds\":30,\"enabled\":true}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.loaded").value(22));
|
||||
verify(routeRepo).save(any(OpenApiRoute.class));
|
||||
verify(registry).reload();
|
||||
}
|
||||
|
||||
@Test
|
||||
void routeUpdate_illegalAuthMode_400AndNoSave() throws Exception {
|
||||
when(routeRepo.findById(1L)).thenReturn(java.util.Optional.of(route(1L)));
|
||||
mvc.perform(put("/api/internal/admin/routes/1")
|
||||
.contentType("application/json")
|
||||
.content("{\"authMode\":\"OPEN\"}"))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.msg").value(org.hamcrest.Matchers.containsString("authMode")));
|
||||
verify(routeRepo, never()).save(any());
|
||||
verify(registry, never()).reload();
|
||||
}
|
||||
|
||||
@Test
|
||||
void routeUpdate_negativeTtl_400() throws Exception {
|
||||
when(routeRepo.findById(1L)).thenReturn(java.util.Optional.of(route(1L)));
|
||||
mvc.perform(put("/api/internal/admin/routes/1")
|
||||
.contentType("application/json")
|
||||
.content("{\"cacheTtlSeconds\":-1}"))
|
||||
.andExpect(status().isBadRequest());
|
||||
verify(routeRepo, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void routeUpdate_missingRow_400() throws Exception {
|
||||
when(routeRepo.findById(99L)).thenReturn(java.util.Optional.empty());
|
||||
mvc.perform(put("/api/internal/admin/routes/99")
|
||||
.contentType("application/json")
|
||||
.content("{\"enabled\":false}"))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
void targetUpdate_savesAndReloadsTargets() throws Exception {
|
||||
when(targetRepo.findById(5L)).thenReturn(java.util.Optional.of(target(5L)));
|
||||
mvc.perform(put("/api/internal/admin/targets/5")
|
||||
.contentType("application/json")
|
||||
.content("{\"baseUrl\":\"http://notice-service:10002\",\"enabled\":true}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.targets").value(13));
|
||||
verify(targetRepo).save(any(OpenApiTarget.class));
|
||||
verify(registry).reloadTargets();
|
||||
}
|
||||
|
||||
@Test
|
||||
void targetUpdate_blankBaseUrl_400NoSave() throws Exception {
|
||||
when(targetRepo.findById(5L)).thenReturn(java.util.Optional.of(target(5L)));
|
||||
mvc.perform(put("/api/internal/admin/targets/5")
|
||||
.contentType("application/json")
|
||||
.content("{\"baseUrl\":\" \"}"))
|
||||
.andExpect(status().isBadRequest());
|
||||
verify(targetRepo, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void logs_defaultLimitAndFilters() throws Exception {
|
||||
mvc.perform(get("/api/internal/admin/logs?targetCode=notice&callerAppKey=100001&limit=10"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.total").value(1))
|
||||
.andExpect(jsonPath("$.rows[0].targetCode").value("notice"));
|
||||
verify(logRepo).pageLatest(eq("notice"), eq("100001"), any(Pageable.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void logs_overLimitCappedTo500() throws Exception {
|
||||
mvc.perform(get("/api/internal/admin/logs?limit=9999"))
|
||||
.andExpect(status().isOk());
|
||||
verify(logRepo).pageLatest(any(), any(), Mockito.argThat(p -> p.getPageSize() == 500));
|
||||
}
|
||||
|
||||
private static OpenApiRoute route(Long id) {
|
||||
OpenApiRoute r = new OpenApiRoute();
|
||||
r.setId(id);
|
||||
r.setTargetCode("userbase");
|
||||
r.setPathPattern("/oapi/user/**");
|
||||
r.setHttpMethod("*");
|
||||
r.setVisibility("INTERNAL");
|
||||
r.setAuthMode("SIGNED");
|
||||
r.setCacheTtlSeconds(0);
|
||||
r.setCacheScope("CALLER");
|
||||
r.setRateLimitQps(0);
|
||||
r.setEnabled(true);
|
||||
return r;
|
||||
}
|
||||
|
||||
private static OpenApiTarget target(Long id) {
|
||||
OpenApiTarget t = new OpenApiTarget();
|
||||
t.setId(id);
|
||||
t.setTargetCode("notice");
|
||||
t.setBaseUrl("http://notice-service:10002");
|
||||
t.setEnabled(true);
|
||||
return t;
|
||||
}
|
||||
|
||||
private static OpenApiLog logRow() {
|
||||
OpenApiLog l = new OpenApiLog();
|
||||
l.setId(100L);
|
||||
l.setTargetCode("notice");
|
||||
l.setMethod("GET");
|
||||
l.setPath("/oapi/notice/getDict");
|
||||
l.setStatus(200);
|
||||
l.setCallerAppKey("100001");
|
||||
l.setCostMs(5);
|
||||
return l;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user