Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.apache.shenyu.admin.utils.ShenyuResultMessage;
import org.apache.shenyu.admin.validation.annotation.Existed;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand Down Expand Up @@ -96,7 +97,7 @@ public ShenyuAdminResult detailApi(@PathVariable("id")
*/
@PostMapping
@RequiresPermissions("system:api:add")
public ShenyuAdminResult createApi(@Valid @RequestBody final ApiDTO apiDTO) {
public ShenyuAdminResult createApi(@Validated(ApiDTO.Create.class) @RequestBody final ApiDTO apiDTO) {
return ShenyuAdminResult.success(apiService.createOrUpdate(apiDTO));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,11 @@ public class ApiDTO implements Serializable {
private String apiDesc;

/**
* 0-swagger,1-annotation generation,2-create manuallym,3-import swagger,4-import yapi.
* 0-swagger,1-annotation generation,2-create manually,3-import swagger.
*/
@NotNull(message = "apiSource not null")
@Min(value = 0, message = "state min 0")
@Max(value = 4, message = "state max 4")
@NotNull(message = "apiSource not null", groups = Create.class)
@Min(value = 0, message = "apiSource min 0", groups = Create.class)
@Max(value = 3, message = "apiSource max 3", groups = Create.class)
private Integer apiSource;

/**
Expand Down Expand Up @@ -481,4 +481,10 @@ public int hashCode() {
rpcType, state, ext, apiOwner, apiDesc, apiSource, document, documentMd5, dateCreated, dateUpdated, tagIds);
}

/**
* Creation validates provenance in addition to the common API fields.
*/
public interface Create extends jakarta.validation.groups.Default {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public class ApiDO {
private String apiDesc;

/**
* 0-swagger,1-annotation generation,2-create manuallym,3-import swagger,4-import yapi.
* 0-swagger,1-annotation generation,2-create manually,3-import swagger.
*/
private Integer apiSource;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public class ApiVO implements Serializable {
private String apiDesc;

/**
* 0-swagger,1-annotation generation,2-create manuallym,3-import swagger,4-import yapi.
* 0-swagger,1-annotation generation,2-create manually,3-import swagger.
*/
private Integer apiSource;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ public String createOrUpdate(final ApiDTO apiDTO) {
*/
private String update(final ApiDTO apiDTO) {
ApiDO apiDO = ApiDO.buildApiDO(apiDTO);
// Source records creation provenance, including historical retired sources.
// The selective update must retain it even when the client supplies another value.
apiDO.setApiSource(null);
final int updateRows = apiMapper.updateByPrimaryKeySelective(apiDO);
if (updateRows > 0) {
if (Objects.nonNull(apiDTO.getTagIds())) {
Expand Down
2 changes: 1 addition & 1 deletion shenyu-admin/src/main/resources/static/index.16dee401.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
Expand All @@ -50,6 +53,7 @@
import static org.hamcrest.core.Is.is;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
Expand Down Expand Up @@ -127,8 +131,9 @@ public void testDetailApi() throws Exception {
.andReturn();
}

@Test
public void testCreateApi() throws Exception {
@ParameterizedTest
@ValueSource(ints = {0, 1, 2, 3})
public void testCreateApi(final int apiSource) throws Exception {
ApiDTO apiDTO = new ApiDTO();
apiDTO.setContextPath("string");
apiDTO.setApiPath("string");
Expand All @@ -140,7 +145,7 @@ public void testCreateApi() throws Exception {
apiDTO.setState(0);
apiDTO.setApiOwner("string");
apiDTO.setApiDesc("string");
apiDTO.setApiSource(0);
apiDTO.setApiSource(apiSource);
apiDTO.setDocument("document");
apiDTO.setExt("ext");
given(this.apiService.createOrUpdate(apiDTO)).willReturn(ShenyuResultMessage.CREATE_SUCCESS);
Expand All @@ -152,6 +157,20 @@ public void testCreateApi() throws Exception {
.andReturn();
}

@Test
public void testCreateApiRejectsUnsupportedSource() throws Exception {
ApiDTO apiDTO = new ApiDTO();
apiDTO.setHttpMethod(0);
apiDTO.setState(0);
apiDTO.setApiSource(4);
this.mockMvc.perform(MockMvcRequestBuilders.post("/api")
.contentType(MediaType.APPLICATION_JSON)
.content(GsonUtils.getInstance().toJson(apiDTO)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message", is("Request error! invalid argument [apiSource: apiSource max 3]")));
verifyNoInteractions(apiService);
}

@Test
public void testCreateApiStateMinException() throws Exception {
ApiDTO apiDTO = new ApiDTO();
Expand Down Expand Up @@ -200,8 +219,9 @@ public void testCreateApiStateMaxException() throws Exception {
.andReturn();
}

@Test
public void testUpdateApi() throws Exception {
@ParameterizedTest
@ValueSource(ints = {0, 1, 2, 3})
public void testUpdateApi(final int apiSource) throws Exception {
ApiDTO apiDTO = new ApiDTO();
apiDTO.setContextPath("string");
apiDTO.setApiPath("string");
Expand All @@ -213,7 +233,7 @@ public void testUpdateApi() throws Exception {
apiDTO.setState(0);
apiDTO.setApiOwner("string");
apiDTO.setApiDesc("string");
apiDTO.setApiSource(0);
apiDTO.setApiSource(apiSource);
apiDTO.setDocument("document");
apiDTO.setExt("ext");
apiDTO.setId("123");
Expand All @@ -229,6 +249,25 @@ public void testUpdateApi() throws Exception {

}

@ParameterizedTest
@NullSource
@ValueSource(ints = {4})
public void testUpdateApiAcceptsHistoricalOrOmittedSource(final Integer apiSource) throws Exception {
ApiDTO apiDTO = new ApiDTO();
apiDTO.setId("123");
apiDTO.setHttpMethod(0);
apiDTO.setState(0);
apiDTO.setApiSource(apiSource);
when(SpringBeanUtils.getInstance().getBean(ApiMapper.class)).thenReturn(apiMapper);
when(apiMapper.existed(apiDTO.getId())).thenReturn(true);
given(this.apiService.createOrUpdate(apiDTO)).willReturn(ShenyuResultMessage.UPDATE_SUCCESS);
this.mockMvc.perform(MockMvcRequestBuilders.put("/api/{id}", apiDTO.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(GsonUtils.getInstance().toJson(apiDTO)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message", is(ShenyuResultMessage.UPDATE_SUCCESS)));
}

@Test
public void testDeleteApis() throws Exception {
given(this.apiService.delete(Collections.singletonList("123"))).willReturn(StringUtils.EMPTY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import org.springframework.test.context.TestPropertySource;

import java.io.InputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
Expand Down Expand Up @@ -79,18 +81,23 @@ public class ZookeeperClientDiscoveryIntegrationTest extends AbstractSpringInteg

private final List<ShenyuInstanceRegisterRepository> clients = new CopyOnWriteArrayList<>();

private final List<ServerSocket> backends = new CopyOnWriteArrayList<>();

private final List<DiscoverySyncData> snapshots = new CopyOnWriteArrayList<>();

private final HttpClient httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();

private WebSocket websocket;

@AfterEach
public void cleanup() {
public void cleanup() throws IOException {
if (Objects.nonNull(websocket)) {
websocket.abort();
}
clients.forEach(this::stopInstance);
for (ServerSocket backend : backends) {
backend.close();
}
List<DiscoveryDO> discoveries = registeredDiscoveries();
discoveries.forEach(discovery -> processorHolder.chooseProcessor("zookeeper").removeDiscovery(discovery));
jdbcTemplate.update("DELETE FROM discovery_upstream WHERE discovery_handler_id IN "
Expand Down Expand Up @@ -172,6 +179,9 @@ public CompletionStage<?> onText(final WebSocket socket, final CharSequence data
}

private ShenyuInstanceRegisterRepository startInstance(final TestingServer server, final String app, final int port) throws Exception {
// The Admin health checker remains active while testing registry removal.
// Keep registered backends reachable so health eviction cannot replace that event.
backends.add(new ServerSocket(port));
String appName = "zk-6525-" + app;
Properties properties = new Properties();
properties.setProperty("name", appName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.shenyu.admin.mapper;

import org.apache.shenyu.admin.AbstractSpringIntegrationTest;
import org.apache.shenyu.admin.model.dto.ApiDTO;
import org.apache.shenyu.admin.model.entity.ApiDO;
import org.apache.shenyu.admin.model.page.PageParameter;
import org.apache.shenyu.admin.model.query.ApiQuery;
Expand All @@ -27,6 +28,9 @@
import org.apache.shenyu.common.utils.UUIDUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;

import jakarta.annotation.Resource;
import java.sql.Timestamp;
Expand Down Expand Up @@ -59,6 +63,29 @@ public void before() {
assertEquals(1, count);
}

@ParameterizedTest
@NullSource
@ValueSource(ints = {2, 4})
public void testUpdateHistoricalApiRetainsProvenance(final Integer submittedSource) {
apiDO.setApiSource(4);
apiMapper.updateByPrimaryKeySelective(apiDO);
ApiDTO update = new ApiDTO();
update.setId(apiDO.getId());
update.setDocument(apiDO.getDocument());
update.setApiSource(submittedSource);
update.setApiDesc("edited historical API");
update.setApiOwner("new owner");
update.setState(1);

apiService.createOrUpdate(update);

ApiDO stored = apiMapper.selectByPrimaryKey(apiDO.getId());
assertEquals(4, stored.getApiSource());
assertEquals("edited historical API", stored.getApiDesc());
assertEquals("new owner", stored.getApiOwner());
assertEquals(1, stored.getState());
}

@Test
public void testApiListPaginationExcludesDocument() {
ApiDO first = buildApiDO();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,7 @@ public enum ApiSourceEnum {
/**
* import_swagger.
*/
IMPORT_SWAGGER("import_swagger", 3),

/**
* import_yapi.
*/
IMPORT_YAPI("import_yapi", 4);
IMPORT_SWAGGER("import_swagger", 3);

private final String name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ public void testGetName() {
assertEquals("annotation_generation", ApiSourceEnum.ANNOTATION_GENERATION.getName());
assertEquals("create_manually", ApiSourceEnum.CREATE_MANUALLY.getName());
assertEquals("import_swagger", ApiSourceEnum.IMPORT_SWAGGER.getName());
assertEquals("import_yapi", ApiSourceEnum.IMPORT_YAPI.getName());
}

@Test
Expand All @@ -41,6 +40,5 @@ public void testGetValue() {
assertEquals(1, ApiSourceEnum.ANNOTATION_GENERATION.getValue());
assertEquals(2, ApiSourceEnum.CREATE_MANUALLY.getValue());
assertEquals(3, ApiSourceEnum.IMPORT_SWAGGER.getValue());
assertEquals(4, ApiSourceEnum.IMPORT_YAPI.getValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.google.gson.JsonObject;
import org.apache.shenyu.common.dto.ConditionData;
import org.apache.shenyu.common.enums.PluginEnum;
import org.apache.shenyu.common.utils.GsonUtils;
import org.apache.shenyu.integratedtest.common.AbstractPluginDataInit;
import org.apache.shenyu.integratedtest.common.dto.AdminResponse;
import org.apache.shenyu.integratedtest.common.dto.UserDTO;
Expand Down Expand Up @@ -49,6 +50,8 @@
import static org.apache.shenyu.plugin.cryptor.strategy.MapTypeEnum.FIELD;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.containsString;

public class CryptorResponsePluginTest extends AbstractPluginDataInit {

Expand Down Expand Up @@ -118,17 +121,20 @@ public void testWhenRuleHandleIsNull() throws Exception {

}

@Test
@DisplayName("return original message when request doesnt exist filed")
public void testWhenDoesntExistFiled() throws Exception {
@ParameterizedTest
@ValueSource(strings = {"decrypt", "encrypt"})
@DisplayName("reject a response missing the configured field")
public void testWhenConfiguredFieldDoesNotExist(final String way) throws Exception {
initSelectorAndRules(PluginEnum.CRYPTOR_RESPONSE.getName(),
"", SINGLETON_CONDITION_LIST, buildRuleLocalDataList("data", "decrypt", FIELD.getMapType()));

UserDTO actualUser = HttpHelper.INSTANCE.postGateway(TEST_PATH, originalBody, UserDTO.class);

assertThat(actualUser.getUserId(), is(originalBody.getUserId()));
assertThat(actualUser.getUserName(), is(originalBody.getUserName()));

"", SINGLETON_CONDITION_LIST, buildRuleLocalDataList("data", way, FIELD.getMapType()));

String body = HttpHelper.INSTANCE.postGateway(TEST_PATH, originalBody, String.class);
AdminResponse response = GsonUtils.getInstance().fromJson(body, AdminResponse.class);
ShenyuResultEnum error = ShenyuResultEnum.CRYPTOR_RESPONSE_ERROR_CONFIGURATION;
assertThat(response.getCode(), is(error.getCode()));
assertThat(response.getMessage(), is(error.getMsg() + "[fieldNames]"));
assertThat(body, not(containsString(TEST_USER_ID)));
assertThat(body, not(containsString(TEST_USER_NAME)));
}

@DisplayName("return failed message when decrypt or encrypt failed")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,35 @@
import org.apache.shenyu.integrated.test.sdk.http.dto.SdkTestDto;
import org.apache.shenyu.integratedtest.common.AbstractPluginDataInit;
import org.apache.shenyu.integratedtest.common.helper.HttpHelper;
import org.junit.jupiter.api.BeforeAll;

import java.io.IOException;
import java.time.Duration;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.awaitility.Awaitility.await;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public class SdkHttpIntegratedTest extends AbstractPluginDataInit {

@BeforeAll
public static void awaitRegisteredRoutes() {
contextPortKeys().forEach(arguments -> {
Object[] values = arguments.get();
await().alias("SDK route registration for " + values[0]).atMost(Duration.ofSeconds(60))
.ignoreExceptionsMatching(error -> error instanceof IOException)
.pollInterval(Duration.ofMillis(500)).untilAsserted(() -> {
SdkTestDto response = HttpHelper.INSTANCE.getHttpService("http://localhost:" + values[1]
+ "/sdk/" + values[0] + "/findById?id=10", null, SdkTestDto.class);
assertNotNull(response);
assertEquals("sdk-currentToken", response.getName());
});
});
}

@ParameterizedTest(name = "{index} => test {0}")
@MethodSource("contextPortKeys")
public void testFindById(final String context, final String port) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public class ApiDocRegisterDTO implements DataTypeParent {
private String apiDesc;

/**
* 0-swagger,1-annotation generation,2-create manuallym,3-import swagger,4-import yapi.
* 0-swagger,1-annotation generation,2-create manually,3-import swagger.
*/
private Integer apiSource;

Expand Down
Loading