fix: Modified action execution endpoint (#9473)
This commit is contained in:
parent
eaf1701d7e
commit
5c994975d7
|
|
@ -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",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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": ...}}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user