chore: Refactoring action execution flows to clean up files (#23265)
This task is simply dividing action execution related flows into separate files for clarity. It will allow us to pull this module out in the future.
This commit is contained in:
parent
44d1837678
commit
1337134cd4
|
|
@ -19,7 +19,7 @@ import java.util.Set;
|
|||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Document
|
||||
public class DatasourceStorage extends BaseDomain{
|
||||
public class DatasourceStorage extends BaseDomain {
|
||||
|
||||
@JsonView(Views.Public.class)
|
||||
String datasourceId;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.appsmith.server.constants.Url;
|
|||
import com.appsmith.server.controllers.ce.ActionControllerCE;
|
||||
import com.appsmith.server.services.LayoutActionService;
|
||||
import com.appsmith.server.services.NewActionService;
|
||||
import com.appsmith.server.solutions.ActionExecutionSolution;
|
||||
import com.appsmith.server.solutions.RefactoringSolution;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
|
@ -16,9 +17,10 @@ public class ActionController extends ActionControllerCE {
|
|||
|
||||
public ActionController(LayoutActionService layoutActionService,
|
||||
NewActionService newActionService,
|
||||
RefactoringSolution refactoringSolution) {
|
||||
RefactoringSolution refactoringSolution,
|
||||
ActionExecutionSolution actionExecutionSolution) {
|
||||
|
||||
super(layoutActionService, newActionService, refactoringSolution);
|
||||
super(layoutActionService, newActionService, refactoringSolution, actionExecutionSolution);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.appsmith.server.dtos.RefactorActionNameDTO;
|
|||
import com.appsmith.server.dtos.ResponseDTO;
|
||||
import com.appsmith.server.services.LayoutActionService;
|
||||
import com.appsmith.server.services.NewActionService;
|
||||
import com.appsmith.server.solutions.ActionExecutionSolution;
|
||||
import com.appsmith.server.solutions.RefactoringSolution;
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
|
||||
|
|
@ -45,14 +46,16 @@ public class ActionControllerCE {
|
|||
private final LayoutActionService layoutActionService;
|
||||
private final NewActionService newActionService;
|
||||
private final RefactoringSolution refactoringSolution;
|
||||
private final ActionExecutionSolution actionExecutionSolution;
|
||||
|
||||
@Autowired
|
||||
public ActionControllerCE(LayoutActionService layoutActionService,
|
||||
NewActionService newActionService,
|
||||
RefactoringSolution refactoringSolution) {
|
||||
RefactoringSolution refactoringSolution, ActionExecutionSolution actionExecutionSolution) {
|
||||
this.layoutActionService = layoutActionService;
|
||||
this.newActionService = newActionService;
|
||||
this.refactoringSolution = refactoringSolution;
|
||||
this.actionExecutionSolution = actionExecutionSolution;
|
||||
}
|
||||
|
||||
@JsonView(Views.Public.class)
|
||||
|
|
@ -82,7 +85,7 @@ public class ActionControllerCE {
|
|||
public Mono<ResponseDTO<ActionExecutionResult>> executeAction(@RequestBody Flux<Part> partFlux,
|
||||
@RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName,
|
||||
@RequestHeader(name = FieldName.ENVIRONMENT_NAME, required = false) String environmentName) {
|
||||
return newActionService.executeAction(partFlux, branchName, environmentName)
|
||||
return actionExecutionSolution.executeAction(partFlux, branchName, environmentName)
|
||||
.map(updatedResource -> new ResponseDTO<>(HttpStatus.OK.value(), updatedResource, null));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,15 +30,12 @@ public class NewActionServiceImpl extends NewActionServiceCEImpl implements NewA
|
|||
AnalyticsService analyticsService,
|
||||
DatasourceService datasourceService,
|
||||
PluginService pluginService,
|
||||
DatasourceContextService datasourceContextService,
|
||||
PluginExecutorHelper pluginExecutorHelper,
|
||||
MarketplaceService marketplaceService,
|
||||
PolicyGenerator policyGenerator,
|
||||
NewPageService newPageService,
|
||||
ApplicationService applicationService,
|
||||
SessionUserService sessionUserService,
|
||||
PolicyUtils policyUtils,
|
||||
AuthenticationValidator authenticationValidator,
|
||||
ConfigService configService,
|
||||
ResponseUtils responseUtils,
|
||||
PermissionGroupService permissionGroupService,
|
||||
|
|
@ -49,9 +46,9 @@ public class NewActionServiceImpl extends NewActionServiceCEImpl implements NewA
|
|||
ObservationRegistry observationRegistry) {
|
||||
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService,
|
||||
datasourceService, pluginService, datasourceContextService, pluginExecutorHelper, marketplaceService,
|
||||
policyGenerator, newPageService, applicationService, sessionUserService, policyUtils,
|
||||
authenticationValidator, configService, responseUtils, permissionGroupService, datasourcePermission,
|
||||
datasourceService, pluginService, pluginExecutorHelper, marketplaceService,
|
||||
policyGenerator, newPageService, applicationService, policyUtils,
|
||||
configService, responseUtils, permissionGroupService, datasourcePermission,
|
||||
applicationPermission, pagePermission, actionPermission, observationRegistry);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,14 +37,6 @@ public interface NewActionServiceCE extends CrudService<NewAction, String> {
|
|||
|
||||
Mono<ActionDTO> updateUnpublishedAction(String id, ActionDTO action);
|
||||
|
||||
Mono<ActionExecutionResult> executeAction(ExecuteActionDTO executeActionDTO, String environmentName);
|
||||
|
||||
Mono<ActionExecutionResult> executeAction(Flux<Part> partsFlux, String branchName, String environmentName);
|
||||
|
||||
Mono<ActionDTO> getValidActionForExecution(ExecuteActionDTO executeActionDTO, String actionId, NewAction newAction);
|
||||
|
||||
<T> T variableSubstitution(T configuration, Map<String, String> replaceParamsMap);
|
||||
|
||||
Mono<ActionDTO> findByUnpublishedNameAndPageId(String name, String pageId, AclPermission permission);
|
||||
|
||||
Mono<ActionDTO> findActionDTObyIdAndViewMode(String id, Boolean viewMode, AclPermission permission);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,6 @@
|
|||
package com.appsmith.server.solutions;
|
||||
|
||||
import com.appsmith.server.solutions.ce.ActionExecutionSolutionCE;
|
||||
|
||||
public interface ActionExecutionSolution extends ActionExecutionSolutionCE {
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.appsmith.server.solutions;
|
||||
|
||||
import com.appsmith.server.helpers.PluginExecutorHelper;
|
||||
import com.appsmith.server.repositories.NewActionRepository;
|
||||
import com.appsmith.server.services.AnalyticsService;
|
||||
import com.appsmith.server.services.ApplicationService;
|
||||
import com.appsmith.server.services.AuthenticationValidator;
|
||||
import com.appsmith.server.services.DatasourceContextService;
|
||||
import com.appsmith.server.services.DatasourceService;
|
||||
import com.appsmith.server.services.NewActionService;
|
||||
import com.appsmith.server.services.NewPageService;
|
||||
import com.appsmith.server.services.PluginService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.solutions.ce.ActionExecutionSolutionCEImpl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ActionExecutionSolutionImpl extends ActionExecutionSolutionCEImpl implements ActionExecutionSolution {
|
||||
public ActionExecutionSolutionImpl(NewActionService newActionService,
|
||||
ActionPermission actionPermission,
|
||||
ObservationRegistry observationRegistry,
|
||||
ObjectMapper objectMapper,
|
||||
NewActionRepository repository,
|
||||
DatasourceService datasourceService,
|
||||
PluginService pluginService,
|
||||
DatasourceContextService datasourceContextService,
|
||||
PluginExecutorHelper pluginExecutorHelper,
|
||||
NewPageService newPageService,
|
||||
ApplicationService applicationService,
|
||||
SessionUserService sessionUserService,
|
||||
AuthenticationValidator authenticationValidator,
|
||||
DatasourcePermission datasourcePermission,
|
||||
AnalyticsService analyticsService) {
|
||||
super(newActionService, actionPermission, observationRegistry, objectMapper, repository, datasourceService,
|
||||
pluginService, datasourceContextService, pluginExecutorHelper, newPageService, applicationService,
|
||||
sessionUserService, authenticationValidator, datasourcePermission, analyticsService);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.appsmith.server.solutions.ce;
|
||||
|
||||
import com.appsmith.external.dtos.ExecuteActionDTO;
|
||||
import com.appsmith.external.models.ActionDTO;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.server.domains.NewAction;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface ActionExecutionSolutionCE {
|
||||
Mono<ActionExecutionResult> executeAction(Flux<Part> partFlux, String branchName, String environmentName);
|
||||
|
||||
Mono<ActionExecutionResult> executeAction(ExecuteActionDTO executeActionDTO, String environmentName);
|
||||
|
||||
Mono<ActionDTO> getValidActionForExecution(ExecuteActionDTO executeActionDTO, String actionId, NewAction newAction);
|
||||
|
||||
<T> T variableSubstitution(T configuration, Map<String, String> replaceParamsMap);
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -124,13 +124,10 @@ public class NewActionServiceCEImplTest {
|
|||
ConfigService configService;
|
||||
@MockBean
|
||||
ResponseUtils responseUtils;
|
||||
|
||||
@MockBean
|
||||
PermissionGroupService permissionGroupService;
|
||||
|
||||
@MockBean
|
||||
NewActionRepository newActionRepository;
|
||||
|
||||
@MockBean
|
||||
DatasourcePermission datasourcePermission;
|
||||
@MockBean
|
||||
|
|
@ -142,10 +139,6 @@ public class NewActionServiceCEImplTest {
|
|||
@MockBean
|
||||
ObservationRegistry observationRegistry;
|
||||
|
||||
private BodyExtractor.Context context;
|
||||
|
||||
private Map<String, Object> hints;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
newActionService = new NewActionServiceCEImpl(scheduler,
|
||||
|
|
@ -156,15 +149,12 @@ public class NewActionServiceCEImplTest {
|
|||
analyticsService,
|
||||
datasourceService,
|
||||
pluginService,
|
||||
datasourceContextService,
|
||||
pluginExecutorHelper,
|
||||
marketplaceService,
|
||||
policyGenerator,
|
||||
newPageService,
|
||||
applicationService,
|
||||
sessionUserService,
|
||||
policyUtils,
|
||||
authenticationValidator,
|
||||
configService,
|
||||
responseUtils,
|
||||
permissionGroupService,
|
||||
|
|
@ -178,98 +168,6 @@ public class NewActionServiceCEImplTest {
|
|||
Mockito.when(observationRegistry.observationConfig()).thenReturn(mockObservationConfig);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void createContext() {
|
||||
final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
|
||||
messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
|
||||
messageReaders.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()));
|
||||
messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
|
||||
messageReaders.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
|
||||
messageReaders.add(new FormHttpMessageReader());
|
||||
DefaultPartHttpMessageReader partReader = new DefaultPartHttpMessageReader();
|
||||
messageReaders.add(partReader);
|
||||
messageReaders.add(new MultipartHttpMessageReader(partReader));
|
||||
|
||||
this.context = new BodyExtractor.Context() {
|
||||
@Override
|
||||
public List<HttpMessageReader<?>> messageReaders() {
|
||||
return messageReaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ServerHttpResponse> serverResponse() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> hints() {
|
||||
return hints;
|
||||
}
|
||||
};
|
||||
this.hints = new HashMap<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteAction_withoutExecuteActionDTOPart_failsValidation() {
|
||||
final Mono<ActionExecutionResult> actionExecutionResultMono = newActionService.executeAction(Flux.empty(), null, null);
|
||||
|
||||
StepVerifier
|
||||
.create(actionExecutionResultMono)
|
||||
.expectErrorMatches(e -> e instanceof AppsmithException &&
|
||||
e.getMessage().equals(AppsmithError.INVALID_PARAMETER.getMessage(FieldName.ACTION_ID)))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteAction_withMalformedExecuteActionDTO_failsValidation() {
|
||||
MockServerHttpRequest mock = MockServerHttpRequest
|
||||
.method(HttpMethod.POST, URI.create("https://example.com"))
|
||||
.contentType(new MediaType("multipart", "form-data", Map.of("boundary", "boundary")))
|
||||
.body("""
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="executeActionDTO"\r
|
||||
\r
|
||||
irrelevant content\r
|
||||
--boundary--\r
|
||||
""");
|
||||
|
||||
final Flux<Part> partsFlux = BodyExtractors.toParts()
|
||||
.extract(mock, this.context);
|
||||
|
||||
final Mono<ActionExecutionResult> actionExecutionResultMono = newActionService.executeAction(partsFlux, null, null);
|
||||
|
||||
StepVerifier
|
||||
.create(actionExecutionResultMono)
|
||||
.expectErrorMatches(e -> e instanceof AppsmithException &&
|
||||
e.getMessage().equals(AppsmithError.INVALID_PARAMETER.getMessage("executeActionDTO")))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteAction_withoutActionId_failsValidation() {
|
||||
MockServerHttpRequest mock = MockServerHttpRequest
|
||||
.method(HttpMethod.POST, URI.create("https://example.com"))
|
||||
.contentType(new MediaType("multipart", "form-data", Map.of("boundary", "boundary")))
|
||||
.body("""
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="executeActionDTO"\r
|
||||
\r
|
||||
{"viewMode":false}\r
|
||||
--boundary--\r
|
||||
""");
|
||||
|
||||
final Flux<Part> partsFlux = BodyExtractors.toParts()
|
||||
.extract(mock, this.context);
|
||||
|
||||
final Mono<ActionExecutionResult> actionExecutionResultMono = newActionService.executeAction(partsFlux, null, null);
|
||||
|
||||
StepVerifier
|
||||
.create(actionExecutionResultMono)
|
||||
.expectErrorMatches(e -> e instanceof AppsmithException &&
|
||||
e.getMessage().equals(AppsmithError.INVALID_PARAMETER.getMessage(FieldName.ACTION_ID)))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingPluginIdAndTypeFixForNonJSPluginType() {
|
||||
/* Mock `findById` method of pluginService to return `testPlugin` */
|
||||
|
|
@ -324,176 +222,4 @@ public class NewActionServiceCEImplTest {
|
|||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteAPIWithUsualOrderingOfTheParts() {
|
||||
String usualOrderOfParts = """
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="executeActionDTO"\r
|
||||
\r
|
||||
{"actionId":"63285a3388e48972c7519b18","viewMode":false,"paramProperties":{"k0":{"datatype": "string"}}}\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="parameterMap"\r
|
||||
\r
|
||||
{"Input1.text":"k0"}\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="k0"; filename="blob"\r
|
||||
Content-Type: text/plain\r
|
||||
\r
|
||||
xyz\r
|
||||
--boundary--""";
|
||||
|
||||
MockServerHttpRequest mock = MockServerHttpRequest
|
||||
.method(HttpMethod.POST, URI.create("https://example.com"))
|
||||
.contentType(new MediaType("multipart", "form-data", Map.of("boundary", "boundary")))
|
||||
.body(usualOrderOfParts);
|
||||
|
||||
final Flux<Part> partsFlux = BodyExtractors.toParts()
|
||||
.extract(mock, this.context);
|
||||
|
||||
NewActionServiceCE newActionServiceSpy = spy(newActionService);
|
||||
|
||||
Mono<ActionExecutionResult> actionExecutionResultMono = newActionServiceSpy.executeAction(partsFlux, null, null);
|
||||
|
||||
ActionExecutionResult mockResult = new ActionExecutionResult();
|
||||
mockResult.setIsExecutionSuccess(true);
|
||||
mockResult.setBody("test body");
|
||||
mockResult.setTitle("test title");
|
||||
|
||||
NewAction newAction = new NewAction();
|
||||
newAction.setId("63285a3388e48972c7519b18");
|
||||
doReturn(Mono.just(mockResult)).when(newActionServiceSpy).executeAction(any(), any());
|
||||
doReturn(Mono.just(newAction)).when(newActionServiceSpy).findByBranchNameAndDefaultActionId(any(), any(), any());
|
||||
|
||||
|
||||
StepVerifier
|
||||
.create(actionExecutionResultMono)
|
||||
.assertNext(response -> {
|
||||
assertNotNull(response);
|
||||
assertTrue(response.getIsExecutionSuccess());
|
||||
assertEquals(mockResult.getBody().toString(), response.getBody().toString());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteAPIWithParameterMapAsLastPart() {
|
||||
String parameterMapAtLast = """
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="executeActionDTO"\r
|
||||
\r
|
||||
{"actionId":"63285a3388e48972c7519b18","viewMode":false,"paramProperties":{"k0":{"datatype": "string"}}}\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="k0"; filename="blob"\r
|
||||
Content-Type: text/plain\r
|
||||
\r
|
||||
xyz\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="parameterMap"\r
|
||||
\r
|
||||
{"Input1.text":"k0"}\r
|
||||
--boundary--""";
|
||||
|
||||
MockServerHttpRequest mock = MockServerHttpRequest
|
||||
.method(HttpMethod.POST, URI.create("https://example.com"))
|
||||
.contentType(new MediaType("multipart", "form-data", Map.of("boundary", "boundary")))
|
||||
.body(parameterMapAtLast);
|
||||
|
||||
final Flux<Part> partsFlux = BodyExtractors.toParts()
|
||||
.extract(mock, this.context);
|
||||
|
||||
NewActionServiceCE newActionServiceSpy = spy(newActionService);
|
||||
|
||||
Mono<ActionExecutionResult> actionExecutionResultMono = newActionServiceSpy.executeAction(partsFlux, null, null);
|
||||
|
||||
ActionExecutionResult mockResult = new ActionExecutionResult();
|
||||
mockResult.setIsExecutionSuccess(true);
|
||||
mockResult.setBody("test body");
|
||||
mockResult.setTitle("test title");
|
||||
|
||||
NewAction newAction = new NewAction();
|
||||
newAction.setId("63285a3388e48972c7519b18");
|
||||
doReturn(Mono.just(mockResult)).when(newActionServiceSpy).executeAction(any(), any());
|
||||
doReturn(Mono.just(newAction)).when(newActionServiceSpy).findByBranchNameAndDefaultActionId(any(), any(), any());
|
||||
|
||||
|
||||
StepVerifier
|
||||
.create(actionExecutionResultMono)
|
||||
.assertNext(response -> {
|
||||
assertNotNull(response);
|
||||
assertTrue(response.getIsExecutionSuccess());
|
||||
assertEquals(mockResult.getBody().toString(), response.getBody().toString());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsePartsAndGetParamsFlux_withBlobIdentifiers_replacesValueInParam() {
|
||||
String partsWithBlobRefs = """
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="executeActionDTO"\r
|
||||
\r
|
||||
{"actionId":"63285a3388e48972c7519b18","viewMode":false,"paramProperties":{"k0":{"datatype": "string", "blobIdentifiers": ["blob:12345678-1234-1234-1234-123456781234"]}}}\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="parameterMap"\r
|
||||
\r
|
||||
{"Input1.text":"k0"}\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="k0"; filename="blob"\r
|
||||
Content-Type: text/plain\r
|
||||
\r
|
||||
{"name": "randomName", "data": "blob:12345678-1234-1234-1234-123456781234"}\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="blob:12345678-1234-1234-1234-123456781234"; filename="blob"\r
|
||||
Content-Type: text/plain\r
|
||||
\r
|
||||
xy\\nz\r
|
||||
--boundary--""";
|
||||
|
||||
MockServerHttpRequest mock = MockServerHttpRequest
|
||||
.method(HttpMethod.POST, URI.create("https://example.com"))
|
||||
.contentType(new MediaType("multipart", "form-data", Map.of("boundary", "boundary")))
|
||||
.body(partsWithBlobRefs);
|
||||
|
||||
final Flux<Part> partsFlux = BodyExtractors.toParts()
|
||||
.extract(mock, this.context);
|
||||
|
||||
AtomicLong atomicLong = new AtomicLong();
|
||||
ExecuteActionDTO executeActionDTO = new ExecuteActionDTO();
|
||||
Mono<List<Param>> paramsListMono = newActionService.parsePartsAndGetParamsFlux(partsFlux, atomicLong, executeActionDTO).collectList().cache();
|
||||
|
||||
StepVerifier.create(paramsListMono)
|
||||
.assertNext(paramsList -> {
|
||||
assertEquals(1, paramsList.size());
|
||||
Param param = paramsList.get(0);
|
||||
assertEquals("{\"name\": \"randomName\", \"data\": \"blob:12345678-1234-1234-1234-123456781234\"}", param.getValue());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnrichExecutionParams_withBlobReference_performsSubstitutionCorrectly() {
|
||||
AtomicLong atomicLong = new AtomicLong(45L);
|
||||
ExecuteActionDTO executeActionDTO = new ExecuteActionDTO();
|
||||
executeActionDTO.setActionId("testId");
|
||||
executeActionDTO.setViewMode(false);
|
||||
executeActionDTO.setParamProperties(Map.of("k0", new ParamProperty("string", List.of("blobId"))));
|
||||
executeActionDTO.setParameterMap(Map.of("Input1.text", "k0"));
|
||||
executeActionDTO.setBlobValuesMap(Map.of("blobId", "xy\\nz"));
|
||||
Param param1 = new Param();
|
||||
param1.setValue("{\"name\": \"randomName\", \"data\": \"blobId\"}");
|
||||
param1.setPseudoBindingName("k0");
|
||||
List<Param> params = List.of(param1);
|
||||
|
||||
Mono<ExecuteActionDTO> enrichedDto = newActionService.enrichExecutionParam(atomicLong, executeActionDTO, params);
|
||||
|
||||
StepVerifier.create(enrichedDto)
|
||||
.assertNext(dto -> {
|
||||
assertEquals(45, dto.getTotalReadableByteCount());
|
||||
|
||||
Param param = dto.getParams().get(0);
|
||||
assertEquals(ClientDataType.STRING, param.getClientDataType());
|
||||
assertEquals("{\"name\": \"randomName\", \"data\": \"xy\\\\nz\"}", param.getValue());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,404 @@
|
|||
package com.appsmith.server.solutions.ce;
|
||||
|
||||
import com.appsmith.external.datatypes.ClientDataType;
|
||||
import com.appsmith.external.dtos.ExecuteActionDTO;
|
||||
import com.appsmith.external.dtos.ParamProperty;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.Param;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.NewAction;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.PluginExecutorHelper;
|
||||
import com.appsmith.server.repositories.NewActionRepository;
|
||||
import com.appsmith.server.services.AnalyticsService;
|
||||
import com.appsmith.server.services.ApplicationService;
|
||||
import com.appsmith.server.services.AuthenticationValidator;
|
||||
import com.appsmith.server.services.DatasourceContextService;
|
||||
import com.appsmith.server.services.DatasourceService;
|
||||
import com.appsmith.server.services.NewActionService;
|
||||
import com.appsmith.server.services.NewPageService;
|
||||
import com.appsmith.server.services.PluginService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.services.ce.NewActionServiceCE;
|
||||
import com.appsmith.server.solutions.ActionPermission;
|
||||
import com.appsmith.server.solutions.DatasourcePermission;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.core.codec.ByteBufferDecoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.DecoderHttpMessageReader;
|
||||
import org.springframework.http.codec.FormHttpMessageReader;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||
import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader;
|
||||
import org.springframework.http.codec.multipart.MultipartHttpMessageReader;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.BodyExtractors;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SpringBootTest
|
||||
class ActionExecutionSolutionCEImplTest {
|
||||
|
||||
ActionExecutionSolutionCEImpl actionExecutionSolution;
|
||||
|
||||
@SpyBean
|
||||
NewActionService newActionService;
|
||||
@MockBean
|
||||
ActionPermission actionPermission;
|
||||
@MockBean
|
||||
ObservationRegistry observationRegistry;
|
||||
@SpyBean
|
||||
ObjectMapper objectMapper;
|
||||
@MockBean
|
||||
NewActionRepository repository;
|
||||
@MockBean
|
||||
DatasourceService datasourceService;
|
||||
@MockBean
|
||||
PluginService pluginService;
|
||||
@MockBean
|
||||
DatasourceContextService datasourceContextService;
|
||||
@MockBean
|
||||
PluginExecutorHelper pluginExecutorHelper;
|
||||
@MockBean
|
||||
NewPageService newPageService;
|
||||
@MockBean
|
||||
ApplicationService applicationService;
|
||||
@MockBean
|
||||
SessionUserService sessionUserService;
|
||||
@MockBean
|
||||
AuthenticationValidator authenticationValidator;
|
||||
@MockBean
|
||||
DatasourcePermission datasourcePermission;
|
||||
@MockBean
|
||||
AnalyticsService analyticsService;
|
||||
|
||||
private BodyExtractor.Context context;
|
||||
|
||||
private Map<String, Object> hints;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
actionExecutionSolution = new ActionExecutionSolutionCEImpl(
|
||||
newActionService,
|
||||
actionPermission,
|
||||
observationRegistry,
|
||||
objectMapper,
|
||||
repository,
|
||||
datasourceService,
|
||||
pluginService,
|
||||
datasourceContextService,
|
||||
pluginExecutorHelper,
|
||||
newPageService,
|
||||
applicationService,
|
||||
sessionUserService,
|
||||
authenticationValidator,
|
||||
datasourcePermission,
|
||||
analyticsService
|
||||
);
|
||||
|
||||
ObservationRegistry.ObservationConfig mockObservationConfig = Mockito.mock(ObservationRegistry.ObservationConfig.class);
|
||||
Mockito.when(observationRegistry.observationConfig()).thenReturn(mockObservationConfig);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void createContext() {
|
||||
final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
|
||||
messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
|
||||
messageReaders.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()));
|
||||
messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
|
||||
messageReaders.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
|
||||
messageReaders.add(new FormHttpMessageReader());
|
||||
DefaultPartHttpMessageReader partReader = new DefaultPartHttpMessageReader();
|
||||
messageReaders.add(partReader);
|
||||
messageReaders.add(new MultipartHttpMessageReader(partReader));
|
||||
|
||||
this.context = new BodyExtractor.Context() {
|
||||
@Override
|
||||
public List<HttpMessageReader<?>> messageReaders() {
|
||||
return messageReaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ServerHttpResponse> serverResponse() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> hints() {
|
||||
return hints;
|
||||
}
|
||||
};
|
||||
this.hints = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testExecuteAction_withoutExecuteActionDTOPart_failsValidation() {
|
||||
final Mono<ActionExecutionResult> actionExecutionResultMono = actionExecutionSolution.executeAction(Flux.empty(), null, null);
|
||||
|
||||
StepVerifier
|
||||
.create(actionExecutionResultMono)
|
||||
.expectErrorMatches(e -> e instanceof AppsmithException &&
|
||||
e.getMessage().equals(AppsmithError.INVALID_PARAMETER.getMessage(FieldName.ACTION_ID)))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteAction_withMalformedExecuteActionDTO_failsValidation() {
|
||||
MockServerHttpRequest mock = MockServerHttpRequest
|
||||
.method(HttpMethod.POST, URI.create("https://example.com"))
|
||||
.contentType(new MediaType("multipart", "form-data", Map.of("boundary", "boundary")))
|
||||
.body("""
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="executeActionDTO"\r
|
||||
\r
|
||||
irrelevant content\r
|
||||
--boundary--\r
|
||||
""");
|
||||
|
||||
final Flux<Part> partsFlux = BodyExtractors.toParts()
|
||||
.extract(mock, this.context);
|
||||
|
||||
final Mono<ActionExecutionResult> actionExecutionResultMono = actionExecutionSolution.executeAction(partsFlux, null, null);
|
||||
|
||||
StepVerifier
|
||||
.create(actionExecutionResultMono)
|
||||
.expectErrorMatches(e -> e instanceof AppsmithException &&
|
||||
e.getMessage().equals(AppsmithError.INVALID_PARAMETER.getMessage("executeActionDTO")))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteAction_withoutActionId_failsValidation() {
|
||||
MockServerHttpRequest mock = MockServerHttpRequest
|
||||
.method(HttpMethod.POST, URI.create("https://example.com"))
|
||||
.contentType(new MediaType("multipart", "form-data", Map.of("boundary", "boundary")))
|
||||
.body("""
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="executeActionDTO"\r
|
||||
\r
|
||||
{"viewMode":false}\r
|
||||
--boundary--\r
|
||||
""");
|
||||
|
||||
final Flux<Part> partsFlux = BodyExtractors.toParts()
|
||||
.extract(mock, this.context);
|
||||
|
||||
final Mono<ActionExecutionResult> actionExecutionResultMono = actionExecutionSolution.executeAction(partsFlux, null, null);
|
||||
|
||||
StepVerifier
|
||||
.create(actionExecutionResultMono)
|
||||
.expectErrorMatches(e -> e instanceof AppsmithException &&
|
||||
e.getMessage().equals(AppsmithError.INVALID_PARAMETER.getMessage(FieldName.ACTION_ID)))
|
||||
.verify();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testExecuteAPIWithUsualOrderingOfTheParts() {
|
||||
String usualOrderOfParts = """
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="executeActionDTO"\r
|
||||
\r
|
||||
{"actionId":"63285a3388e48972c7519b18","viewMode":false,"paramProperties":{"k0":{"datatype": "string"}}}\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="parameterMap"\r
|
||||
\r
|
||||
{"Input1.text":"k0"}\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="k0"; filename="blob"\r
|
||||
Content-Type: text/plain\r
|
||||
\r
|
||||
xyz\r
|
||||
--boundary--""";
|
||||
|
||||
MockServerHttpRequest mock = MockServerHttpRequest
|
||||
.method(HttpMethod.POST, URI.create("https://example.com"))
|
||||
.contentType(new MediaType("multipart", "form-data", Map.of("boundary", "boundary")))
|
||||
.body(usualOrderOfParts);
|
||||
|
||||
final Flux<Part> partsFlux = BodyExtractors.toParts()
|
||||
.extract(mock, this.context);
|
||||
|
||||
ActionExecutionSolutionCE executionSolutionSpy = spy(actionExecutionSolution);
|
||||
|
||||
Mono<ActionExecutionResult> actionExecutionResultMono = executionSolutionSpy.executeAction(partsFlux, null, null);
|
||||
|
||||
ActionExecutionResult mockResult = new ActionExecutionResult();
|
||||
mockResult.setIsExecutionSuccess(true);
|
||||
mockResult.setBody("test body");
|
||||
mockResult.setTitle("test title");
|
||||
|
||||
NewAction newAction = new NewAction();
|
||||
newAction.setId("63285a3388e48972c7519b18");
|
||||
doReturn(Mono.just(mockResult)).when(executionSolutionSpy).executeAction(any(), any());
|
||||
doReturn(Mono.just(newAction)).when(newActionService).findByBranchNameAndDefaultActionId(any(), any(), any());
|
||||
|
||||
|
||||
StepVerifier
|
||||
.create(actionExecutionResultMono)
|
||||
.assertNext(response -> {
|
||||
assertNotNull(response);
|
||||
assertTrue(response.getIsExecutionSuccess());
|
||||
assertEquals(mockResult.getBody().toString(), response.getBody().toString());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteAPIWithParameterMapAsLastPart() {
|
||||
String parameterMapAtLast = """
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="executeActionDTO"\r
|
||||
\r
|
||||
{"actionId":"63285a3388e48972c7519b18","viewMode":false,"paramProperties":{"k0":{"datatype": "string"}}}\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="k0"; filename="blob"\r
|
||||
Content-Type: text/plain\r
|
||||
\r
|
||||
xyz\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="parameterMap"\r
|
||||
\r
|
||||
{"Input1.text":"k0"}\r
|
||||
--boundary--""";
|
||||
|
||||
MockServerHttpRequest mock = MockServerHttpRequest
|
||||
.method(HttpMethod.POST, URI.create("https://example.com"))
|
||||
.contentType(new MediaType("multipart", "form-data", Map.of("boundary", "boundary")))
|
||||
.body(parameterMapAtLast);
|
||||
|
||||
final Flux<Part> partsFlux = BodyExtractors.toParts()
|
||||
.extract(mock, this.context);
|
||||
|
||||
ActionExecutionSolutionCE executionSolutionSpy = spy(actionExecutionSolution);
|
||||
|
||||
Mono<ActionExecutionResult> actionExecutionResultMono = executionSolutionSpy.executeAction(partsFlux, null, null);
|
||||
|
||||
ActionExecutionResult mockResult = new ActionExecutionResult();
|
||||
mockResult.setIsExecutionSuccess(true);
|
||||
mockResult.setBody("test body");
|
||||
mockResult.setTitle("test title");
|
||||
|
||||
NewAction newAction = new NewAction();
|
||||
newAction.setId("63285a3388e48972c7519b18");
|
||||
doReturn(Mono.just(mockResult)).when(executionSolutionSpy).executeAction(any(), any());
|
||||
doReturn(Mono.just(newAction)).when(newActionService).findByBranchNameAndDefaultActionId(any(), any(), any());
|
||||
|
||||
|
||||
StepVerifier
|
||||
.create(actionExecutionResultMono)
|
||||
.assertNext(response -> {
|
||||
assertNotNull(response);
|
||||
assertTrue(response.getIsExecutionSuccess());
|
||||
assertEquals(mockResult.getBody().toString(), response.getBody().toString());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsePartsAndGetParamsFlux_withBlobIdentifiers_replacesValueInParam() {
|
||||
String partsWithBlobRefs = """
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="executeActionDTO"\r
|
||||
\r
|
||||
{"actionId":"63285a3388e48972c7519b18","viewMode":false,"paramProperties":{"k0":{"datatype": "string", "blobIdentifiers": ["blob:12345678-1234-1234-1234-123456781234"]}}}\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="parameterMap"\r
|
||||
\r
|
||||
{"Input1.text":"k0"}\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="k0"; filename="blob"\r
|
||||
Content-Type: text/plain\r
|
||||
\r
|
||||
{"name": "randomName", "data": "blob:12345678-1234-1234-1234-123456781234"}\r
|
||||
--boundary\r
|
||||
Content-Disposition: form-data; name="blob:12345678-1234-1234-1234-123456781234"; filename="blob"\r
|
||||
Content-Type: text/plain\r
|
||||
\r
|
||||
xy\\nz\r
|
||||
--boundary--""";
|
||||
|
||||
MockServerHttpRequest mock = MockServerHttpRequest
|
||||
.method(HttpMethod.POST, URI.create("https://example.com"))
|
||||
.contentType(new MediaType("multipart", "form-data", Map.of("boundary", "boundary")))
|
||||
.body(partsWithBlobRefs);
|
||||
|
||||
final Flux<Part> partsFlux = BodyExtractors.toParts()
|
||||
.extract(mock, this.context);
|
||||
|
||||
AtomicLong atomicLong = new AtomicLong();
|
||||
ExecuteActionDTO executeActionDTO = new ExecuteActionDTO();
|
||||
Mono<List<Param>> paramsListMono = actionExecutionSolution.parsePartsAndGetParamsFlux(partsFlux, atomicLong, executeActionDTO).collectList().cache();
|
||||
|
||||
StepVerifier.create(paramsListMono)
|
||||
.assertNext(paramsList -> {
|
||||
assertEquals(1, paramsList.size());
|
||||
Param param = paramsList.get(0);
|
||||
assertEquals("{\"name\": \"randomName\", \"data\": \"blob:12345678-1234-1234-1234-123456781234\"}", param.getValue());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnrichExecutionParams_withBlobReference_performsSubstitutionCorrectly() {
|
||||
AtomicLong atomicLong = new AtomicLong(45L);
|
||||
ExecuteActionDTO executeActionDTO = new ExecuteActionDTO();
|
||||
executeActionDTO.setActionId("testId");
|
||||
executeActionDTO.setViewMode(false);
|
||||
executeActionDTO.setParamProperties(Map.of("k0", new ParamProperty("string", List.of("blobId"))));
|
||||
executeActionDTO.setParameterMap(Map.of("Input1.text", "k0"));
|
||||
executeActionDTO.setBlobValuesMap(Map.of("blobId", "xy\\nz"));
|
||||
Param param1 = new Param();
|
||||
param1.setValue("{\"name\": \"randomName\", \"data\": \"blobId\"}");
|
||||
param1.setPseudoBindingName("k0");
|
||||
List<Param> params = List.of(param1);
|
||||
|
||||
Mono<ExecuteActionDTO> enrichedDto = actionExecutionSolution.enrichExecutionParam(atomicLong, executeActionDTO, params);
|
||||
|
||||
StepVerifier.create(enrichedDto)
|
||||
.assertNext(dto -> {
|
||||
assertEquals(45, dto.getTotalReadableByteCount());
|
||||
|
||||
Param param = dto.getParams().get(0);
|
||||
assertEquals(ClientDataType.STRING, param.getClientDataType());
|
||||
assertEquals("{\"name\": \"randomName\", \"data\": \"xy\\\\nz\"}", param.getValue());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user