From c32442166c2d5148a3b6c4db48f11c39c717c3e1 Mon Sep 17 00:00:00 2001 From: Andreas Isler Date: Tue, 7 Jul 2026 10:45:47 +0200 Subject: [PATCH] rest call test (untested) --- .../work/model/FlowableExcelParser.java | 1 + .../work/model/FlowableModelTest.java | 1 + .../work/model/FlowableModelTestUtils.java | 90 +++++++++++- .../customer/work/model/RestRequestDto.java | 7 + .../customer/work/model/TestRestClient.java | 129 ++++++++++++++++++ .../customer/work/model/TestRestServer.java | 119 ++++++++++++++++ .../work/model/TestRestServerExtension.java | 23 ++++ .../customer/work/model/test/ModelTest.java | 32 +++++ 8 files changed, 398 insertions(+), 4 deletions(-) create mode 100644 customer-work/src/test/java/com/customer/work/model/RestRequestDto.java create mode 100644 customer-work/src/test/java/com/customer/work/model/TestRestClient.java create mode 100644 customer-work/src/test/java/com/customer/work/model/TestRestServer.java create mode 100644 customer-work/src/test/java/com/customer/work/model/TestRestServerExtension.java diff --git a/customer-work/src/test/java/com/customer/work/model/FlowableExcelParser.java b/customer-work/src/test/java/com/customer/work/model/FlowableExcelParser.java index 7028f3c..38a6ef1 100644 --- a/customer-work/src/test/java/com/customer/work/model/FlowableExcelParser.java +++ b/customer-work/src/test/java/com/customer/work/model/FlowableExcelParser.java @@ -13,6 +13,7 @@ import java.util.List; /** * Reads an .xlsx workbook (created with Excel or LibreOffice) from the classpath * and returns all cells as Strings: book > sheets > rows > cells. + * * Restrictions per sheet: * - The column count is defined by the first row: it starts with the 1st cell * and ends at the 1st empty cell diff --git a/customer-work/src/test/java/com/customer/work/model/FlowableModelTest.java b/customer-work/src/test/java/com/customer/work/model/FlowableModelTest.java index 514bffc..2c38f0f 100644 --- a/customer-work/src/test/java/com/customer/work/model/FlowableModelTest.java +++ b/customer-work/src/test/java/com/customer/work/model/FlowableModelTest.java @@ -40,6 +40,7 @@ import java.lang.annotation.*; @ExtendWith(TemplateExtension.class) @ExtendWith(TenantSetupExtension.class) @ExtendWith(TestMailServerExtension.class) +@ExtendWith(TestRestServerExtension.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Transactional @SpringBootTest diff --git a/customer-work/src/test/java/com/customer/work/model/FlowableModelTestUtils.java b/customer-work/src/test/java/com/customer/work/model/FlowableModelTestUtils.java index 949d8bb..b890b11 100644 --- a/customer-work/src/test/java/com/customer/work/model/FlowableModelTestUtils.java +++ b/customer-work/src/test/java/com/customer/work/model/FlowableModelTestUtils.java @@ -12,6 +12,8 @@ import com.flowable.audit.api.runtime.AuditInstance; import com.flowable.core.spring.security.SecurityUtils; import com.flowable.platform.service.task.CompleteFormRepresentation; import com.flowable.platform.service.task.PlatformTaskService; +import com.flowable.serviceregistry.api.runtime.ServiceInvocationResultResponse; +import com.flowable.serviceregistry.api.runtime.ServiceRegistryRuntimeService; import com.github.wnameless.json.flattener.JsonFlattener; import com.github.wnameless.json.unflattener.JsonUnflattener; import jakarta.mail.Address; @@ -35,6 +37,8 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.platform.commons.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; @@ -59,6 +63,9 @@ public class FlowableModelTestUtils { protected final FlowableExcelMapper flowableExcelMapper; protected static final Logger logger = LoggerFactory.getLogger(FlowableModelTestUtils.class); protected final TestMailServer testMailServer; + protected final TestRestServer testRestServer; + protected final TestRestClient testRestClient; + protected final ServiceRegistryRuntimeService serviceRegistryRuntimeService; protected final AuditService auditService; protected final ObjectMapper objectMapper = new ObjectMapper(); @@ -79,6 +86,9 @@ public class FlowableModelTestUtils { ManagementService managementService, FlowableExcelMapper flowableExcelMapper, TestMailServer testMailServer, + TestRestServer testRestServer, + TestRestClient testRestClient, + ServiceRegistryRuntimeService serviceRegistryRuntimeService, AuditService auditService) { this.processEngine = processEngine; this.cmmnEngine = cmmnEngine; @@ -87,6 +97,9 @@ public class FlowableModelTestUtils { this.managementService = managementService; this.flowableExcelMapper = flowableExcelMapper; this.testMailServer = testMailServer; + this.testRestServer = testRestServer; + this.testRestClient = testRestClient; + this.serviceRegistryRuntimeService = serviceRegistryRuntimeService; this.auditService = auditService; // Enable ObjectMapper for handling Instant as string objectMapper.registerModule(new JavaTimeModule()); @@ -157,6 +170,25 @@ public class FlowableModelTestUtils { .containsExactlyInAnyOrder(receivers.split("[,\\s]+")); } + public void checkAndAssertRestRequest(Map map, int restNumber, String hint, String method, String path) { + if (map != null) { + Object check = map.get("rest"); + if (check instanceof Integer && (Integer) check > 0) { + assertRestRequest(restNumber, hint, method, path); + } + } + } + + protected void assertRestRequest(int restNumber, String hint, String method, String path) { + if (restNumber <= 0) return; + List restRequests = getRestRequestList(); + Assertions.assertThat(restRequests.size()).as("%s: invalid rest request number %s", hint, restNumber).isGreaterThanOrEqualTo(restNumber); + + RestRequestDto restRequest = restRequests.get(restNumber - 1); + Assertions.assertThat(restRequest.method()).as("%s: invalid method %s", hint, restRequest.method()).isEqualTo(method); + Assertions.assertThat(restRequest.path()).as("%s: invalid path %s", hint, restRequest.path()).isEqualTo(path); + } + public Map getHistoryCasePayload(String caseInstanceId) { List historicVariableInstanceList = cmmnEngine.getCmmnHistoryService() .createHistoricVariableInstanceQuery() @@ -187,6 +219,15 @@ public class FlowableModelTestUtils { .start(); } + public CaseInstance getCaseInstance(String caseDefinitionKey) { + CaseInstance caseInstance = cmmnEngine.getCmmnRuntimeService() + .createCaseInstanceQuery() + .caseDefinitionKey(caseDefinitionKey) + .singleResult(); + Assertions.assertThat(caseInstance).as("No case instance with definition key %s found", caseDefinitionKey).isNotNull(); + return caseInstance; + } + public Task getOpenTask(String taskKey) { Task task = taskService.createTaskQuery().taskDefinitionKey(taskKey).singleResult(); Assertions.assertThat(task).as("No open task with key %s found", taskKey).isNotNull(); @@ -221,6 +262,31 @@ public class FlowableModelTestUtils { }).collect(Collectors.toList()); } + public List getRestRequestList() { + return testRestServer.getRequests(); + } + + public void stubRestResponse(String method, String path, int status, String body) { + testRestServer.stubResponse(method, path, status, body); + } + + public ResponseEntity sendRestEvent(String channelKey, ObjectNode payload) { + return testRestClient.sendChannelEvent(channelKey, payload); + } + + public ResponseEntity sendRestCall(HttpMethod method, String path, Object body) { + return testRestClient.exchange(method, path, body); + } + + public ServiceInvocationResultResponse invokeRestService(String serviceKey, String operationKey, Map serviceData) { + return serviceRegistryRuntimeService.createServiceInvocationBuilder() + .serviceKey(serviceKey) + .operationKey(operationKey) + .serviceData(serviceData) + .tenantId(TENANT_ID) // Must not be "default" + .invoke(); + } + public boolean isSubset(JsonNode root, JsonNode test) { // If test is null, it is always a subset of root if (test == null || test.isNull()) { @@ -326,9 +392,12 @@ public class FlowableModelTestUtils { Integer emailCount = null; JsonNode emailNode = row.get("email"); if (emailNode != null) emailCount = emailNode.asInt(); + Integer restCount = null; + JsonNode restNode = row.get("rest"); + if (restNode != null) restCount = restNode.asInt(); - logger.info("TestExcelRow file={} id={} rootParam={} inParam={} outParam={} timer={} test={} audit={} email={} ", - path, idParam, rootParam, inParam, outParam, timerCount, test, auditRecordCount, emailCount); + logger.info("TestExcelRow file={} id={} rootParam={} inParam={} outParam={} timer={} test={} audit={} email={} rest={} ", + path, idParam, rootParam, inParam, outParam, timerCount, test, auditRecordCount, emailCount, restCount); Assertions.assertThat(idParam).as("Column 'id' missing in row of %s", path).isNotNull(); ObjectNode processIds = createRootTestProcessInstance(idParam.asText(), convertJsonNodeToMap(vars)); for (int i = 0; i < timerCount; i++) { @@ -353,6 +422,11 @@ public class FlowableModelTestUtils { Assertions.assertThat(emailList.size()).as("Invalid number of emails: %s", emailList.size()).isEqualTo(emailCount); result.set("emails", objectMapper.valueToTree(Collections.singletonList(emailList))); } + if (restCount != null) { + List restRequests = getRestRequestList(); + Assertions.assertThat(restRequests.size()).as("Invalid number of rest requests: %s", restRequests.size()).isEqualTo(restCount); + result.set("restRequests", objectMapper.valueToTree(Collections.singletonList(restRequests))); + } return result; } @@ -381,9 +455,12 @@ public class FlowableModelTestUtils { Integer emailCount = null; Object emailNode = row.get("email"); if (emailNode != null) emailCount = (Integer) emailNode; + Integer restCount = null; + Object restNode = row.get("rest"); + if (restNode != null) restCount = (Integer) restNode; - logger.info("TestExcelRow file={} id={} rootParam={} inParam={} outParam={} timer={} test={} audit={} email={} ", - path, idParam, rootParam, inParam, outParam, timerCount, test, auditRecordCount, emailCount); + logger.info("TestExcelRow file={} id={} rootParam={} inParam={} outParam={} timer={} test={} audit={} email={} rest={} ", + path, idParam, rootParam, inParam, outParam, timerCount, test, auditRecordCount, emailCount, restCount); Assertions.assertThat(idParam).as("Column 'id' missing in row of %s", path).isNotNull(); ObjectNode processIds = createRootTestProcessInstance(idParam, vars); for (int i = 0; i < timerCount; i++) { @@ -408,6 +485,11 @@ public class FlowableModelTestUtils { Assertions.assertThat(emailList.size()).as("Invalid number of emails: %s", emailList.size()).isEqualTo(emailCount); result.put("email", emailCount); } + if (restCount != null) { + List restRequests = getRestRequestList(); + Assertions.assertThat(restRequests.size()).as("Invalid number of rest requests: %s", restRequests.size()).isEqualTo(restCount); + result.put("rest", restCount); + } return result; } diff --git a/customer-work/src/test/java/com/customer/work/model/RestRequestDto.java b/customer-work/src/test/java/com/customer/work/model/RestRequestDto.java new file mode 100644 index 0000000..81bf1d2 --- /dev/null +++ b/customer-work/src/test/java/com/customer/work/model/RestRequestDto.java @@ -0,0 +1,7 @@ +package com.customer.work.model; + +import java.util.List; +import java.util.Map; + +public record RestRequestDto(String method, String path, String query, Map> headers, String body) { +} diff --git a/customer-work/src/test/java/com/customer/work/model/TestRestClient.java b/customer-work/src/test/java/com/customer/work/model/TestRestClient.java new file mode 100644 index 0000000..021ac6b --- /dev/null +++ b/customer-work/src/test/java/com/customer/work/model/TestRestClient.java @@ -0,0 +1,129 @@ +package com.customer.work.model; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.Filter; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Test client simulating incoming REST calls which are processed in Flowable models. The + * requests are executed against the real Flowable REST API (including the security filter + * chain) but in-process via MockMvc, so they participate in the test transaction. Base URL + * and basic auth are taken from the application configuration (see 'test.rest.in.*'). + * + * The Flowable REST APIs are served by their own dispatcher servlets (/platform-api/*, + * /process-api/*, ...) which are never initialized in the mock web environment. Requests + * are therefore routed by path prefix to the matching servlet registration, whose child + * application context is refreshed on first use. + */ +@Component +public class TestRestClient { + private final WebApplicationContext webApplicationContext; + private final Filter springSecurityFilterChain; + private final String baseUrl; + private final String basicAuthHeader; + private final Map mockMvcByServletPath = new ConcurrentHashMap<>(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + public TestRestClient(WebApplicationContext webApplicationContext, + @Qualifier("springSecurityFilterChain") Filter springSecurityFilterChain, + @Value("${test.rest.in.base-url}") String baseUrl, + @Value("${test.rest.in.username}") String username, + @Value("${test.rest.in.password}") String password) { + this.webApplicationContext = webApplicationContext; + this.springSecurityFilterChain = springSecurityFilterChain; + this.baseUrl = baseUrl; + this.basicAuthHeader = "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)); + } + + public ResponseEntity get(String path) { + return exchange(HttpMethod.GET, path, null); + } + + public ResponseEntity post(String path, Object body) { + return exchange(HttpMethod.POST, path, body); + } + + public ResponseEntity exchange(HttpMethod method, String path, Object body) { + try { + String servletPath = findServletPath(path); + MockMvc mockMvc = mockMvcByServletPath.computeIfAbsent(servletPath, this::createMockMvc); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.request(method, URI.create(baseUrl + path)) + .servletPath(servletPath) + .header(HttpHeaders.AUTHORIZATION, basicAuthHeader); + if (body != null) { + builder.contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsBytes(body)); + } + MvcResult result = mockMvc.perform(builder).andReturn(); + HttpHeaders headers = new HttpHeaders(); + for (String name : result.getResponse().getHeaderNames()) { + headers.addAll(name, result.getResponse().getHeaders(name)); + } + return ResponseEntity.status(result.getResponse().getStatus()) + .headers(headers) + .body(new String(result.getResponse().getContentAsByteArray(), StandardCharsets.UTF_8)); + } catch (Exception e) { + throw new RuntimeException("Cannot execute " + method + " " + path, e); + } + } + + public ResponseEntity sendChannelEvent(String channelKey, Object payload) { + return post("/platform-api/channel-definitions/key/" + channelKey + "/events", payload); + } + + protected String findServletPath(String path) { + for (ServletRegistrationBean registration : webApplicationContext.getBeansOfType(ServletRegistrationBean.class).values()) { + for (String urlMapping : registration.getUrlMappings()) { + if (urlMapping.endsWith("/*") && path.startsWith(urlMapping.substring(0, urlMapping.length() - 1))) { + return urlMapping.substring(0, urlMapping.length() - 2); + } + } + } + return ""; + } + + protected MockMvc createMockMvc(String servletPath) { + WebApplicationContext context = webApplicationContext; + if (!servletPath.isEmpty()) { + context = findServletContext(servletPath); + } + return MockMvcBuilders.webAppContextSetup(context) + .addFilters(springSecurityFilterChain) + .build(); + } + + protected WebApplicationContext findServletContext(String servletPath) { + for (ServletRegistrationBean registration : webApplicationContext.getBeansOfType(ServletRegistrationBean.class).values()) { + if (registration.getUrlMappings().contains(servletPath + "/*") + && registration.getServlet() instanceof DispatcherServlet dispatcherServlet) { + WebApplicationContext servletContext = dispatcherServlet.getWebApplicationContext(); + if (servletContext instanceof ConfigurableWebApplicationContext configurableContext && !configurableContext.isActive()) { + configurableContext.setServletContext(webApplicationContext.getServletContext()); + configurableContext.refresh(); + } + return servletContext; + } + } + throw new IllegalStateException("No dispatcher servlet registered for " + servletPath); + } +} diff --git a/customer-work/src/test/java/com/customer/work/model/TestRestServer.java b/customer-work/src/test/java/com/customer/work/model/TestRestServer.java new file mode 100644 index 0000000..ff854fc --- /dev/null +++ b/customer-work/src/test/java/com/customer/work/model/TestRestServer.java @@ -0,0 +1,119 @@ +package com.customer.work.model; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpServer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Test server receiving outgoing REST calls sent from Flowable models. The models resolve + * their base URL from the application configuration (see 'test.rest.out.base-url'), so all + * outgoing calls end up here, get recorded and are answered with stubbed responses. + * Requests must carry basic auth matching the configured credentials, otherwise 401 is returned. + */ +@Component +public class TestRestServer { + private HttpServer server; + private final List requests = new CopyOnWriteArrayList<>(); + private final List stubs = new CopyOnWriteArrayList<>(); + + private final String baseUrl; + private final String username; + private final String password; + + public TestRestServer(@Value("${test.rest.out.base-url}") String baseUrl, + @Value("${test.rest.out.username}") String username, + @Value("${test.rest.out.password}") String password) { + this.baseUrl = baseUrl; + this.username = username; + this.password = password; + } + + public void setup() { + try { + URI uri = URI.create(baseUrl); + server = HttpServer.create(new InetSocketAddress(uri.getHost(), uri.getPort()), 0); + server.createContext("/", this::handle); + server.start(); + } catch (IOException e) { + throw new UncheckedIOException("Cannot start TestRestServer on " + baseUrl, e); + } + } + + public void stop() { + if (server != null) { + server.stop(0); + server = null; + } + requests.clear(); + stubs.clear(); + } + + public void stubResponse(String method, String path, int status, String body) { + stubs.add(new StubbedResponse(method, path, status, body)); + } + + public List getRequests() { + if (server == null) { + throw new IllegalStateException("TestRestServer is not started, is the TestRestServerExtension registered?"); + } + return new ArrayList<>(requests); + } + + protected void handle(HttpExchange exchange) throws IOException { + try { + String method = exchange.getRequestMethod(); + URI uri = exchange.getRequestURI(); + String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); + requests.add(new RestRequestDto(method, uri.getPath(), uri.getQuery(), new LinkedHashMap<>(exchange.getRequestHeaders()), body)); + + if (!isAuthorized(exchange)) { + send(exchange, 401, "{\"error\":\"unauthorized\"}"); + return; + } + StubbedResponse stub = findStub(method, uri.getPath()); + if (stub != null) { + send(exchange, stub.status(), stub.body()); + } else { + send(exchange, 200, "{}"); + } + } finally { + exchange.close(); + } + } + + protected boolean isAuthorized(HttpExchange exchange) { + if (username == null || username.isEmpty()) { + return true; + } + String expected = "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)); + return expected.equals(exchange.getRequestHeaders().getFirst("Authorization")); + } + + protected StubbedResponse findStub(String method, String path) { + return stubs.stream() + .filter(stub -> stub.method().equalsIgnoreCase(method) && path.startsWith(stub.path())) + .findFirst() + .orElse(null); + } + + private void send(HttpExchange exchange, int status, String body) throws IOException { + byte[] bytes = body.getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().set("Content-Type", "application/json"); + exchange.sendResponseHeaders(status, bytes.length); + exchange.getResponseBody().write(bytes); + } + + public record StubbedResponse(String method, String path, int status, String body) {} +} diff --git a/customer-work/src/test/java/com/customer/work/model/TestRestServerExtension.java b/customer-work/src/test/java/com/customer/work/model/TestRestServerExtension.java new file mode 100644 index 0000000..8d9ee4b --- /dev/null +++ b/customer-work/src/test/java/com/customer/work/model/TestRestServerExtension.java @@ -0,0 +1,23 @@ +package com.customer.work.model; + +import org.jspecify.annotations.NonNull; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +public class TestRestServerExtension implements BeforeEachCallback, AfterEachCallback { + @Override + public void beforeEach(@NonNull ExtensionContext context) { + getTestRestServer(context).setup(); + } + + @Override + public void afterEach(@NonNull ExtensionContext context) { + getTestRestServer(context).stop(); + } + + protected TestRestServer getTestRestServer(ExtensionContext context) { + return SpringExtension.getApplicationContext(context).getBean(TestRestServer.class); + } +} diff --git a/customer-work/src/test/java/com/customer/work/model/test/ModelTest.java b/customer-work/src/test/java/com/customer/work/model/test/ModelTest.java index 7a389e3..52e3fe4 100644 --- a/customer-work/src/test/java/com/customer/work/model/test/ModelTest.java +++ b/customer-work/src/test/java/com/customer/work/model/test/ModelTest.java @@ -3,8 +3,10 @@ package com.customer.work.model.test; import com.customer.work.model.EmailDto; import com.customer.work.model.FlowableModelTest; import com.customer.work.model.FlowableModelTestUtils; +import com.customer.work.model.RestRequestDto; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.flowable.serviceregistry.api.runtime.ServiceInvocationResultResponse; import org.assertj.core.api.Assertions; import org.flowable.cmmn.api.runtime.CaseInstance; import org.junit.jupiter.api.Disabled; @@ -13,6 +15,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import java.util.List; import java.util.Map; @@ -76,6 +79,35 @@ public class ModelTest { .hasMessageContaining("Implicit parameter 'param'"); } + @Test + public void c002RestInboundTest() { + // Simulate an incoming REST call to the inbound channel TST_CH001, processed by case TST_C002 + ObjectNode payload = flowableModelTest.emptyNode(); + payload.put("param", "my event payload"); + ResponseEntity response = flowableModelTest.sendRestEvent("TST_CH001", payload); + Assertions.assertThat(response.getStatusCode().is2xxSuccessful()).as("response: %s", response.getBody()).isTrue(); + + CaseInstance caseInstance = flowableModelTest.getCaseInstance("TST_C002"); + Map casePayload = flowableModelTest.getRuntimeCasePayload(caseInstance.getId()); + // param is mapped as full payload, so it contains the complete event body + Assertions.assertThat(String.valueOf(casePayload.get("param"))).contains("my event payload"); + } + + @Test + public void sv001RestOutgoingTest() { + // The outgoing REST call from service model TST_SV001 is answered by the test REST server + flowableModelTest.stubRestResponse("GET", "/idm-api/current-user", 200, + "{\"id\":\"admin\",\"firstName\":\"Test\",\"lastName\":\"Administrator\"}"); + ServiceInvocationResultResponse result = flowableModelTest.invokeRestService("TST_SV001", "getCurrentUser", Map.of()); + Assertions.assertThat(result.getValue("id")).isEqualTo("admin"); + Assertions.assertThat(result.getValue("lastName")).isEqualTo("Administrator"); + + List restRequests = flowableModelTest.getRestRequestList(); + Assertions.assertThat(restRequests).hasSize(1); + Assertions.assertThat(restRequests.get(0).method()).isEqualTo("GET"); + Assertions.assertThat(restRequests.get(0).path()).isEqualTo("/idm-api/current-user"); + } + private Stream p002TestData() { return Stream.of( Arguments.of("model/test/P002/boolean1.json", true, false),