fix: Modified action execution endpoint (#9473)

This commit is contained in:
Nidhi 2022-03-02 21:31:50 +05:30 committed by GitHub
parent eaf1701d7e
commit 5c994975d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 341 additions and 49 deletions

View File

@ -166,11 +166,15 @@ class ActionAPI extends API {
}
static executeAction(
executeAction: ExecuteActionRequest,
executeAction: FormData,
timeout?: number,
): AxiosPromise<ActionExecutionResponse> {
return API.post(ActionAPI.url + "/execute", executeAction, undefined, {
timeout: timeout || DEFAULT_EXECUTE_ACTION_TIMEOUT_MS,
headers: {
accept: "application/json",
"Content-Type": "multipart/form-data",
},
});
}

View File

@ -17,7 +17,6 @@ import ActionAPI, {
ActionResponse,
ExecuteActionRequest,
PaginationField,
Property,
} from "api/ActionAPI";
import {
getAction,
@ -62,7 +61,7 @@ import { EMPTY_RESPONSE } from "components/editorComponents/ApiResponseView";
import { AppState } from "reducers";
import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS } from "@appsmith/constants/ApiConstants";
import { evaluateActionBindings } from "sagas/EvaluationsSaga";
import { isBlobUrl, mapToPropList, parseBlobUrl } from "utils/AppsmithUtils";
import { isBlobUrl, parseBlobUrl } from "utils/AppsmithUtils";
import { getType, Types } from "utils/TypeHelpers";
import { matchPath } from "react-router";
import {
@ -209,6 +208,7 @@ function* readBlob(blobUrl: string): any {
*/
function* evaluateActionParams(
bindings: string[] | undefined,
formData: FormData,
executionParams?: Record<string, any> | string,
) {
if (_.isNil(bindings) || bindings.length === 0) return [];
@ -220,8 +220,7 @@ function* evaluateActionParams(
executionParams,
);
// Convert to object and transform non string values
const actionParams: Record<string, string> = {};
// Add keys values to formData for the multipart submission
for (let i = 0; i < bindings.length; i++) {
const key = bindings[i];
let value = values[i];
@ -229,9 +228,9 @@ function* evaluateActionParams(
if (isBlobUrl(value)) {
value = yield call(readBlob, value);
}
actionParams[key] = value;
formData.append(encodeURIComponent(key), value);
}
return mapToPropList(actionParams);
}
export default function* executePluginActionTriggerSaga(
@ -735,18 +734,11 @@ function* executePluginActionSaga(
);
yield put(executePluginActionRequest({ id: actionId }));
const actionParams: Property[] = yield call(
evaluateActionParams,
pluginAction.jsonPathKeys,
params,
);
const appMode = yield select(getAppMode);
const timeout = yield select(getActionTimeout, actionId);
const executeActionRequest: ExecuteActionRequest = {
actionId: actionId,
params: actionParams,
viewMode: appMode === APP_MODE.PUBLISHED,
};
@ -754,8 +746,12 @@ function* executePluginActionSaga(
executeActionRequest.paginationField = paginationField;
}
const formData = new FormData();
formData.append("executeActionDTO", JSON.stringify(executeActionRequest));
yield call(evaluateActionParams, pluginAction.jsonPathKeys, formData, params);
const response: ActionExecutionResponse = yield ActionAPI.executeAction(
executeActionRequest,
formData,
timeout,
);
PerformanceTracker.stopAsyncTracking(

View File

@ -4,7 +4,6 @@ import com.appsmith.external.dtos.MultipartFormDataDTO;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
import com.appsmith.external.models.Property;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonSyntaxException;
@ -17,12 +16,14 @@ import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.MediaType;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@ -72,7 +73,7 @@ public class DataUtils {
case MediaType.MULTIPART_FORM_DATA_VALUE:
return parseMultipartFileData((List<Property>) body);
default:
return BodyInserters.fromValue(body);
return BodyInserters.fromValue(((String) body).getBytes(StandardCharsets.UTF_8));
}
}
@ -156,11 +157,18 @@ public class DataUtils {
final MultipartFormDataType multipartFormDataType =
MultipartFormDataType.valueOf(property.getType().toUpperCase(Locale.ROOT));
if (MultipartFormDataType.TEXT.equals(multipartFormDataType)) {
bodyBuilder.part(key, property.getValue());
byte[] valueBytesArray = new byte[0];
if (StringUtils.hasLength(String.valueOf(property.getValue()))) {
valueBytesArray = String.valueOf(property.getValue()).getBytes(StandardCharsets.ISO_8859_1);
}
bodyBuilder.part(
key,
valueBytesArray,
MediaType.TEXT_PLAIN);
} else if (MultipartFormDataType.FILE.equals(multipartFormDataType)) {
try {
populateFileTypeBodyBuilder(bodyBuilder, property, outputMessage);
} catch (JsonProcessingException e) {
} catch (IOException e) {
e.printStackTrace();
throw new AppsmithPluginException(
AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR,
@ -179,22 +187,23 @@ public class DataUtils {
}
private void populateFileTypeBodyBuilder(MultipartBodyBuilder bodyBuilder, Property property, ClientHttpRequest outputMessage)
throws JsonProcessingException {
final Object fileValue = property.getValue();
throws IOException {
final String fileValue = (String) property.getValue();
final String key = property.getKey();
List<MultipartFormDataDTO> multipartFormDataDTOs = new ArrayList<>();
if (String.valueOf(fileValue).startsWith("{")) {
if (fileValue.startsWith("{")) {
// Check whether the JSON string is an object
final MultipartFormDataDTO multipartFormDataDTO = objectMapper.readValue(String.valueOf(fileValue),
final MultipartFormDataDTO multipartFormDataDTO = objectMapper.readValue(
fileValue,
MultipartFormDataDTO.class);
multipartFormDataDTOs.add(multipartFormDataDTO);
} else if (String.valueOf(fileValue).startsWith("[")) {
} else if (fileValue.startsWith("[")) {
// Check whether the JSON string is an array
multipartFormDataDTOs = Arrays.asList(
objectMapper.readValue(
String.valueOf(fileValue),
(String) (fileValue),
MultipartFormDataDTO[].class));
} else {
throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR,
@ -204,9 +213,8 @@ public class DataUtils {
multipartFormDataDTOs.forEach(multipartFormDataDTO -> {
final MultipartFormDataDTO finalMultipartFormDataDTO = multipartFormDataDTO;
Flux<DataBuffer> data = DataBufferUtils.readInputStream(
() -> new ByteArrayInputStream(String
.valueOf(finalMultipartFormDataDTO.getData())
.getBytes(StandardCharsets.UTF_8)),
() -> new ByteArrayInputStream(String.valueOf(finalMultipartFormDataDTO.getData())
.getBytes(StandardCharsets.ISO_8859_1)),
outputMessage.bufferFactory(),
4096);

View File

@ -479,6 +479,7 @@ public class RestApiPlugin extends BasePlugin {
String encode = Base64.encode(body);
result.setBody(encode);
responseDataType = ResponseDataType.IMAGE;
} else if (binaryDataTypes.contains(contentType.toString())) {
String encode = Base64.encode(body);
result.setBody(encode);

View File

@ -4,6 +4,7 @@ import com.appsmith.external.models.Property;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.codec.ByteArrayEncoder;
import org.springframework.core.codec.ByteBufferEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.codec.DataBufferEncoder;
@ -48,6 +49,7 @@ public class DataUtilsTest {
public void createContext() {
final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
messageWriters.add(new EncoderHttpMessageWriter<>(new ByteArrayEncoder()));
messageWriters.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly()));
messageWriters.add(new ResourceHttpMessageWriter());
Jackson2JsonEncoder jsonEncoder = new Jackson2JsonEncoder();
@ -91,7 +93,7 @@ public class DataUtilsTest {
Mono<Void> result = bodyInserter.insert(request, this.context);
StepVerifier.create(result).expectComplete().verify();
StepVerifier.create(request.getBodyAsString())
.expectNext("\"\"")
.expectNext("")
.expectComplete()
.verify();
}
@ -123,9 +125,9 @@ public class DataUtilsTest {
"Content-Length: 8\r\n" +
"\r\n" +
"textData"));
Assert.assertTrue(content.contains("Content-Type: text/plain"));
Assert.assertTrue(content.contains(
"Content-Disposition: form-data; name=\"textType\"\r\n" +
"Content-Type: text/plain;charset=UTF-8\r\n" +
"Content-Length: 8\r\n" +
"\r\n" +
"textData"));

View File

@ -5,6 +5,7 @@ import com.appsmith.server.controllers.ce.ActionControllerCE;
import com.appsmith.server.services.ActionCollectionService;
import com.appsmith.server.services.LayoutActionService;
import com.appsmith.server.services.NewActionService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

View File

@ -1,6 +1,5 @@
package com.appsmith.server.controllers.ce;
import com.appsmith.external.dtos.ExecuteActionDTO;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.constants.Url;
@ -16,6 +15,8 @@ import com.appsmith.server.services.NewActionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.Part;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@ -28,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.validation.Valid;
@ -70,10 +72,10 @@ public class ActionControllerCE {
.map(updatedResource -> new ResponseDTO<>(HttpStatus.OK.value(), updatedResource, null));
}
@PostMapping("/execute")
public Mono<ResponseDTO<ActionExecutionResult>> executeAction(@RequestBody ExecuteActionDTO executeActionDTO,
@PostMapping(value = "/execute", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<ResponseDTO<ActionExecutionResult>> executeAction(@RequestBody Flux<Part> partFlux,
@RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName) {
return newActionService.executeAction(executeActionDTO, branchName)
return newActionService.executeAction(partFlux, branchName)
.map(updatedResource -> new ResponseDTO<>(HttpStatus.OK.value(), updatedResource, null));
}
@ -119,7 +121,7 @@ public class ActionControllerCE {
/**
* This function fetches all actions in edit mode.
* To fetch the actions in view mode, check the function `getActionsForViewMode`
*
* <p>
* The controller function is primarily used with param applicationId by the client to fetch the actions in edit
* mode.
*

View File

@ -10,6 +10,7 @@ import com.appsmith.server.dtos.ActionViewDTO;
import com.appsmith.server.dtos.LayoutActionUpdateDTO;
import com.appsmith.server.services.CrudService;
import org.springframework.data.domain.Sort;
import org.springframework.http.codec.multipart.Part;
import org.springframework.util.MultiValueMap;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -36,10 +37,10 @@ public interface NewActionServiceCE extends CrudService<NewAction, String> {
Mono<ActionExecutionResult> executeAction(ExecuteActionDTO executeActionDTO);
Mono<ActionExecutionResult> executeAction(Flux<Part> partsFlux, String branchName);
Mono<ActionDTO> getValidActionForExecution(ExecuteActionDTO executeActionDTO, String actionId, NewAction newAction);
Mono<ActionExecutionResult> executeAction(ExecuteActionDTO executeActionDTO, String branchName);
<T> T variableSubstitution(T configuration, Map<String, String> replaceParamsMap);
Mono<ActionDTO> findByUnpublishedNameAndPageId(String name, String pageId, AclPermission permission);

View File

@ -60,9 +60,11 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.bson.types.ObjectId;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.http.codec.multipart.Part;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.LinkedMultiValueMap;
@ -75,6 +77,9 @@ import reactor.util.function.Tuple2;
import javax.lang.model.SourceVersion;
import javax.validation.Validator;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
@ -529,7 +534,7 @@ public class NewActionServiceCEImpl extends BaseService<NewActionRepository, New
List<Param> params = executeActionDTO.getParams();
if (!CollectionUtils.isEmpty(params)) {
for (Param param : params) {
// In case the parameter values turn out to be null, set it to empty string instead to allow the
// In case the parameter values turn out to be null, set it to empty string instead to allow
// the execution to go through no matter what.
if (!StringUtils.isEmpty(param.getKey()) && param.getValue() == null) {
param.setValue("");
@ -748,6 +753,67 @@ public class NewActionServiceCEImpl extends BaseService<NewActionRepository, New
.map(result -> addDataTypesAndSetSuggestedWidget(result, executeActionDTO.getViewMode()));
}
@Override
public Mono<ActionExecutionResult> executeAction(Flux<Part> partFlux, String branchName) {
final ExecuteActionDTO dto = new ExecuteActionDTO();
return partFlux
.flatMap(part -> {
final String key = part.name();
if ("executeActionDTO".equals(key)) {
return DataBufferUtils
.join(part.content())
.flatMap(executeActionDTOBuffer -> {
byte[] byteData = new byte[executeActionDTOBuffer.readableByteCount()];
executeActionDTOBuffer.read(byteData);
DataBufferUtils.release(executeActionDTOBuffer);
try {
return Mono.just(objectMapper.readValue(byteData, ExecuteActionDTO.class));
} catch (IOException e) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, "executeActionDTO"));
}
})
.flatMap(executeActionDTO -> {
dto.setActionId(executeActionDTO.getActionId());
dto.setPaginationField(executeActionDTO.getPaginationField());
dto.setViewMode(executeActionDTO.getViewMode());
return Mono.empty();
});
}
return Mono.just(part);
})
.flatMap(part -> {
final Param param = new Param();
param.setKey(URLDecoder.decode(part.name(), StandardCharsets.UTF_8));
return DataBufferUtils
.join(part.content())
.map(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
param.setValue(new String(bytes, StandardCharsets.UTF_8));
return param;
});
})
.collectList()
.flatMap(params -> {
if(dto.getActionId() == null) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ACTION_ID));
}
dto.setParams(params);
return Mono.just(dto);
})
.flatMap(executeActionDTO -> this
.findByBranchNameAndDefaultActionId(branchName, executeActionDTO.getActionId(), EXECUTE_ACTIONS)
.map(branchedAction -> {
executeActionDTO.setActionId(branchedAction.getId());
return executeActionDTO;
})
)
.flatMap(this::executeAction);
}
@Override
public Mono<ActionDTO> getValidActionForExecution(ExecuteActionDTO executeActionDTO, String actionId, NewAction newAction) {
Mono<ActionDTO> actionDTOMono = Mono.just(newAction)
@ -783,15 +849,6 @@ public class NewActionServiceCEImpl extends BaseService<NewActionRepository, New
return actionDTOMono;
}
@Override
public Mono<ActionExecutionResult> executeAction(ExecuteActionDTO executeActionDTO, String branchName){
return this.findByBranchNameAndDefaultActionId(branchName, executeActionDTO.getActionId(), EXECUTE_ACTIONS)
.flatMap(branchedAction -> {
executeActionDTO.setActionId(branchedAction.getId());
return executeAction(executeActionDTO);
});
}
/*
* - Get label for request params.
* - Transform request params list: [""] to a map: {"label": {"value": ...}}

View File

@ -789,7 +789,7 @@ public class PageLoadActionsUtilCEImpl implements PageLoadActionsUtilCE {
for (Property x : dynamicBindingPathList) {
final String fieldPath = String.valueOf(x.getKey());
// Ignore pagination configuration since paginatio technically does not belong to dynamic binding list.
// Ignore pagination configuration since pagination technically does not belong to dynamic binding list.
if (fieldPath.equals("prev") || fieldPath.equals("next")) {
continue;
}
@ -823,7 +823,7 @@ public class PageLoadActionsUtilCEImpl implements PageLoadActionsUtilCE {
}
// After updating the parent, check for the types
if (parent == null) {
// path doesnt seem to exist. Ignore.
// path doesn't seem to exist. Ignore.
} else if (parent instanceof String) {
// If we get String value, then this is a leaf node
isLeafNode = true;

View File

@ -0,0 +1,220 @@
package com.appsmith.server.services.ce;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.server.acl.PolicyGenerator;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.PluginExecutorHelper;
import com.appsmith.server.helpers.PolicyUtils;
import com.appsmith.server.helpers.ResponseUtils;
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.ConfigService;
import com.appsmith.server.services.DatasourceContextService;
import com.appsmith.server.services.DatasourceService;
import com.appsmith.server.services.MarketplaceService;
import com.appsmith.server.services.NewActionService;
import com.appsmith.server.services.NewActionServiceImpl;
import com.appsmith.server.services.NewPageService;
import com.appsmith.server.services.PluginService;
import com.appsmith.server.services.SessionUserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.core.codec.ByteBufferDecoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
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.junit4.SpringRunner;
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.core.scheduler.Scheduler;
import reactor.test.StepVerifier;
import javax.validation.Validator;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@RunWith(SpringRunner.class)
@Slf4j
public class NewActionServiceImplTest {
NewActionService newActionService;
@MockBean
Scheduler scheduler;
@MockBean
Validator validator;
@MockBean
MongoConverter mongoConverter;
@MockBean
ReactiveMongoTemplate reactiveMongoTemplate;
@MockBean
AnalyticsService analyticsService;
@MockBean
DatasourceService datasourceService;
@MockBean
PluginService pluginService;
@MockBean
DatasourceContextService datasourceContextService;
@MockBean
PluginExecutorHelper pluginExecutorHelper;
@MockBean
MarketplaceService marketplaceService;
@MockBean
PolicyGenerator policyGenerator;
@MockBean
NewPageService newPageService;
@MockBean
ApplicationService applicationService;
@MockBean
SessionUserService sessionUserService;
@MockBean
PolicyUtils policyUtils;
@MockBean
AuthenticationValidator authenticationValidator;
@MockBean
ConfigService configService;
@MockBean
ResponseUtils responseUtils;
@MockBean
NewActionRepository newActionRepository;
private BodyExtractor.Context context;
private Map<String, Object> hints;
@Before
public void setup() {
newActionService = new NewActionServiceImpl(scheduler,
validator,
mongoConverter,
reactiveMongoTemplate,
newActionRepository,
analyticsService,
datasourceService,
pluginService,
datasourceContextService,
pluginExecutorHelper,
marketplaceService,
policyGenerator,
newPageService,
applicationService,
sessionUserService,
policyUtils,
authenticationValidator,
configService,
responseUtils
);
}
@Before
public void createContext() {
final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
messageReaders.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
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);
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\n" +
"Content-Disposition: form-data; name=\"executeActionDTO\"\r\n" + "\r\n" + "irrelevant content\r\n" +
"--boundary--\r\n");
final Flux<Part> partsFlux = BodyExtractors.toParts()
.extract(mock, this.context);
final Mono<ActionExecutionResult> actionExecutionResultMono = newActionService.executeAction(partsFlux, 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\n" +
"Content-Disposition: form-data; name=\"executeActionDTO\"\r\n" + "\r\n" + "{\"viewMode\":false}\r\n" +
"--boundary--\r\n");
final Flux<Part> partsFlux = BodyExtractors.toParts()
.extract(mock, this.context);
final Mono<ActionExecutionResult> actionExecutionResultMono = newActionService.executeAction(partsFlux, null);
StepVerifier
.create(actionExecutionResultMono)
.expectErrorMatches(e -> e instanceof AppsmithException &&
e.getMessage().equals(AppsmithError.INVALID_PARAMETER.getMessage(FieldName.ACTION_ID)))
.verify();
}
}