From 43578d3e1cad4cc03028827992054848fc1c65b4 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 26 Feb 2020 10:01:55 +0100 Subject: [PATCH] Made Exam API asynchronous a, fix tests and fix monitoring activity --- .../gui/content/activity/ActivitiesPane.java | 12 +- .../weblayer/api/APIExceptionHandler.java | 21 + .../weblayer/api/ExamAPI_V1_Controller.java | 403 ++++++++++-------- .../api/exam/ExamAPIIntegrationTester.java | 49 ++- .../exam/SebExamConfigurationRequestTest.java | 4 +- 5 files changed, 294 insertions(+), 195 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java index a31cccc3..38ba8538 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java @@ -335,10 +335,14 @@ public class ActivitiesPane implements TemplateComposer { .create()); } - monitoring.setExpanded( - this.currentUser - .get() - .hasAnyRole(UserRole.EXAM_SUPPORTER)); + if (monitoring.getItemCount() > 0) { + monitoring.setExpanded( + this.currentUser + .get() + .hasAnyRole(UserRole.EXAM_SUPPORTER)); + } else { + monitoring.dispose(); + } } // ---- MONITORING --------------------------------------------------------------------- diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java index f80312e5..eafc0855 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.concurrent.CompletionException; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -205,4 +206,24 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler { HttpStatus.BAD_REQUEST); } + @ExceptionHandler(CompletionException.class) + public ResponseEntity handleCompletionException( + final CompletionException ex, + final WebRequest request) { + + final Throwable cause = ex.getCause(); + if (cause instanceof APIMessageException) { + return handleAPIMessageException((APIMessageException) cause, request); + } else if (cause instanceof APIConstraintViolationException) { + return handleIllegalAPIArgumentException((APIConstraintViolationException) cause, request); + } else if (cause instanceof ResourceNotFoundException) { + return APIMessage.ErrorMessage.RESOURCE_NOT_FOUND.createErrorResponse(cause.getMessage()); + } else if (cause instanceof RuntimeException) { + return APIMessage.ErrorMessage.UNEXPECTED.createErrorResponse(cause.getMessage()); + } + + return APIMessage.ErrorMessage.GENERIC.createErrorResponse(cause.getMessage()); + + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index 47886a31..eaaca281 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -13,6 +13,7 @@ import java.security.Principal; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.stream.Collectors; @@ -86,7 +87,7 @@ public class ExamAPI_V1_Controller { method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public Collection handshakeCreate( + public CompletableFuture> handshakeCreate( @RequestParam(name = API.PARAM_INSTITUTION_ID, required = false) final Long instIdRequestParam, @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examIdRequestParam, @RequestBody(required = false) final MultiValueMap formParams, @@ -94,217 +95,179 @@ public class ExamAPI_V1_Controller { final HttpServletRequest request, final HttpServletResponse response) { - final POSTMapper mapper = new POSTMapper(formParams); + return CompletableFuture.supplyAsync( + () -> { - final String remoteAddr = request.getRemoteAddr(); - final Long institutionId = (instIdRequestParam != null) - ? instIdRequestParam - : mapper.getLong(API.PARAM_INSTITUTION_ID); - final Long examId = (examIdRequestParam != null) - ? examIdRequestParam - : mapper.getLong(API.EXAM_API_PARAM_EXAM_ID); + final POSTMapper mapper = new POSTMapper(formParams); - // Create and get new ClientConnection if all integrity checks passes - final ClientConnection clientConnection = this.sebClientConnectionService - .createClientConnection(principal, institutionId, remoteAddr, examId) - .getOrThrow(); + final String remoteAddr = request.getRemoteAddr(); + final Long institutionId = (instIdRequestParam != null) + ? instIdRequestParam + : mapper.getLong(API.PARAM_INSTITUTION_ID); + final Long examId = (examIdRequestParam != null) + ? examIdRequestParam + : mapper.getLong(API.EXAM_API_PARAM_EXAM_ID); - response.setHeader( - API.EXAM_API_SEB_CONNECTION_TOKEN, - clientConnection.connectionToken); + // Create and get new ClientConnection if all integrity checks passes + final ClientConnection clientConnection = this.sebClientConnectionService + .createClientConnection(principal, institutionId, remoteAddr, examId) + .getOrThrow(); - // Crate list of running exams - List result; - if (examId == null) { - result = this.examSessionService.getRunningExamsForInstitution(institutionId) - .getOrThrow() - .stream() - .map(this::createRunningExamInfo) - .collect(Collectors.toList()); - } else { - final Exam exam = this.examSessionService.getExamDAO().byPK(examId) - .getOrThrow(); + response.setHeader( + API.EXAM_API_SEB_CONNECTION_TOKEN, + clientConnection.connectionToken); - result = Arrays.asList(createRunningExamInfo(exam)); - } + // Crate list of running exams + List result; + if (examId == null) { + result = this.examSessionService.getRunningExamsForInstitution(institutionId) + .getOrThrow() + .stream() + .map(this::createRunningExamInfo) + .collect(Collectors.toList()); + } else { + final Exam exam = this.examSessionService.getExamDAO().byPK(examId) + .getOrThrow(); - if (result.isEmpty()) { - log.warn("There are no currently running exams for institution: {}. SEB connection creation denied", - institutionId); - throw new IllegalStateException("There are no currently running exams"); - } + result = Arrays.asList(createRunningExamInfo(exam)); + } - return result; + if (result.isEmpty()) { + log.warn( + "There are no currently running exams for institution: {}. SEB connection creation denied", + institutionId); + throw new IllegalStateException("There are no currently running exams"); + } + + return result; + }, + this.executor); } @RequestMapping( path = API.EXAM_API_HANDSHAKE_ENDPOINT, method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - public void handshakeUpdate( + public CompletableFuture handshakeUpdate( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examId, @RequestParam(name = API.EXAM_API_USER_SESSION_ID, required = false) final String userSessionId, final Principal principal, final HttpServletRequest request) { - final String remoteAddr = request.getRemoteAddr(); - final Long institutionId = getInstitutionId(principal); + return CompletableFuture.runAsync( + () -> { - if (log.isDebugEnabled()) { - log.debug("Request received on SEB Client Connection update endpoint: " - + "institution: {} " - + "exam: {} " - + "userSessionId: {} " - + "client-address: {}", - institutionId, - examId, - userSessionId, - remoteAddr); - } + final String remoteAddr = request.getRemoteAddr(); + final Long institutionId = getInstitutionId(principal); - this.sebClientConnectionService.updateClientConnection( - connectionToken, - institutionId, - examId, - remoteAddr, - userSessionId) - .getOrThrow(); + if (log.isDebugEnabled()) { + log.debug("Request received on SEB Client Connection update endpoint: " + + "institution: {} " + + "exam: {} " + + "userSessionId: {} " + + "client-address: {}", + institutionId, + examId, + userSessionId, + remoteAddr); + } + + this.sebClientConnectionService.updateClientConnection( + connectionToken, + institutionId, + examId, + remoteAddr, + userSessionId) + .getOrThrow(); + }, + this.executor); } @RequestMapping( path = API.EXAM_API_HANDSHAKE_ENDPOINT, method = RequestMethod.PUT, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - public void handshakeEstablish( + public CompletableFuture handshakeEstablish( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examId, @RequestParam(name = API.EXAM_API_USER_SESSION_ID, required = false) final String userSessionId, final Principal principal, final HttpServletRequest request) { - final String remoteAddr = request.getRemoteAddr(); - final Long institutionId = getInstitutionId(principal); + return CompletableFuture.runAsync( + () -> { - if (log.isDebugEnabled()) { - log.debug("Request received on SEB Client Connection establish endpoint: " - + "institution: {} " - + "exam: {} " - + "client-address: {}", - institutionId, - examId, - remoteAddr); - } + final String remoteAddr = request.getRemoteAddr(); + final Long institutionId = getInstitutionId(principal); - this.sebClientConnectionService.establishClientConnection( - connectionToken, - institutionId, - examId, - remoteAddr, - userSessionId) - .getOrThrow(); + if (log.isDebugEnabled()) { + log.debug("Request received on SEB Client Connection establish endpoint: " + + "institution: {} " + + "exam: {} " + + "client-address: {}", + institutionId, + examId, + remoteAddr); + } + + this.sebClientConnectionService.establishClientConnection( + connectionToken, + institutionId, + examId, + remoteAddr, + userSessionId) + .getOrThrow(); + }, + this.executor); } @RequestMapping( path = API.EXAM_API_HANDSHAKE_ENDPOINT, method = RequestMethod.DELETE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - public void handshakeDelete( + public CompletableFuture handshakeDelete( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, final Principal principal, final HttpServletRequest request) { - final String remoteAddr = request.getRemoteAddr(); - final Long institutionId = getInstitutionId(principal); + return CompletableFuture.runAsync( + () -> { - if (log.isDebugEnabled()) { - log.debug("Request received on SEB Client Connection close endpoint: " - + "institution: {} " - + "client-address: {}", - institutionId, - remoteAddr); - } + final String remoteAddr = request.getRemoteAddr(); + final Long institutionId = getInstitutionId(principal); - this.sebClientConnectionService.closeConnection( - connectionToken, - institutionId, - remoteAddr) - .getOrThrow(); + if (log.isDebugEnabled()) { + log.debug("Request received on SEB Client Connection close endpoint: " + + "institution: {} " + + "client-address: {}", + institutionId, + remoteAddr); + } + + this.sebClientConnectionService.closeConnection( + connectionToken, + institutionId, + remoteAddr) + .getOrThrow(); + }, + this.executor); } @RequestMapping( path = API.EXAM_API_CONFIGURATION_REQUEST_ENDPOINT, method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - public void getConfig( + public CompletableFuture getConfig( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @RequestParam(required = false) final MultiValueMap formParams, final Principal principal, final HttpServletRequest request, final HttpServletResponse response) throws IOException { - // if an examId is provided with the request, update the connection first - if (formParams != null && formParams.containsKey(API.EXAM_API_PARAM_EXAM_ID)) { - final String examId = formParams.getFirst(API.EXAM_API_PARAM_EXAM_ID); - final Long institutionId = getInstitutionId(principal); - final ClientConnection connection = this.sebClientConnectionService.updateClientConnection( - connectionToken, - institutionId, - Long.valueOf(examId), - null, - null) - .getOrThrow(); - - if (log.isDebugEnabled()) { - log.debug("Updated connection: {}", connection); - } - } - - final ServletOutputStream outputStream = response.getOutputStream(); - - try { - - final ClientConnectionData connection = this.examSessionService - .getConnectionData(connectionToken) - .getOrThrow(); - - // exam integrity check - if (connection.clientConnection.examId == null || - !this.examSessionService.isExamRunning(connection.clientConnection.examId)) { - - log.error("Missing exam identifer or requested exam is not running for connection: {}", connection); - throw new IllegalStateException("Missing exam identider or requested exam is not running"); - } - } catch (final Exception e) { - - log.error("Unexpected error: ", e); - - final APIMessage errorMessage = APIMessage.ErrorMessage.GENERIC.of(e.getMessage()); - outputStream.write(Utils.toByteArray(this.jsonMapper.writeValueAsString(errorMessage))); - response.setStatus(HttpStatus.BAD_REQUEST.value()); - outputStream.flush(); - outputStream.close(); - return; - } - - try { - - this.examSessionService - .streamDefaultExamConfig( - connectionToken, - outputStream); - - response.setStatus(HttpStatus.OK.value()); - - } catch (final Exception e) { - final APIMessage errorMessage = APIMessage.ErrorMessage.GENERIC.of(e.getMessage()); - outputStream.write(Utils.toByteArray(this.jsonMapper.writeValueAsString(errorMessage))); - response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); - - } finally { - outputStream.flush(); - outputStream.close(); - } + return CompletableFuture.runAsync( + () -> streamExamConfig(connectionToken, formParams, principal, response), + this.executor); } private static final ResponseEntity EMPTY_PING_RESPONSE = ResponseEntity @@ -316,49 +279,38 @@ public class ExamAPI_V1_Controller { method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity ping( + public CompletableFuture> ping( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @RequestParam(name = API.EXAM_API_PING_TIMESTAMP, required = true) final long timestamp, @RequestParam(name = API.EXAM_API_PING_NUMBER, required = false) final int pingNumber) { - final String notifyPing = this.sebClientConnectionService - .notifyPing(connectionToken, timestamp, pingNumber); + return CompletableFuture.supplyAsync( + () -> { + final String notifyPing = this.sebClientConnectionService + .notifyPing(connectionToken, timestamp, pingNumber); + if (notifyPing == null) { + return EMPTY_PING_RESPONSE; + } - if (notifyPing == null) { - return EMPTY_PING_RESPONSE; - } - - return ResponseEntity - .ok() - .body(notifyPing); + return ResponseEntity + .ok() + .body(notifyPing); + }, + this.executor); } -// @RequestMapping( -// path = API.EXAM_API_PING_ENDPOINT, -// method = RequestMethod.POST, -// consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, -// produces = MediaType.APPLICATION_JSON_UTF8_VALUE) -// public CompletableFuture ping( -// @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, -// @RequestParam(name = API.EXAM_API_PING_TIMESTAMP, required = true) final long timestamp, -// @RequestParam(name = API.EXAM_API_PING_NUMBER, required = false) final int pingNumber) { -// -// return CompletableFuture.supplyAsync( -// () -> this.sebClientConnectionService -// .notifyPing(connectionToken, timestamp, pingNumber), -// this.executor); -// } - @RequestMapping( path = API.EXAM_API_EVENT_ENDPOINT, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) - public void event( + public CompletableFuture event( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @RequestBody(required = true) final ClientEvent event) { - this.sebClientConnectionService - .notifyClientEvent(connectionToken, event); + return CompletableFuture.runAsync( + () -> this.sebClientConnectionService + .notifyClientEvent(connectionToken, event), + this.executor); } private Long getInstitutionId(final Principal principal) { @@ -375,4 +327,91 @@ public class ExamAPI_V1_Controller { .getOr(null)); } + private void streamExamConfig( + final String connectionToken, + final MultiValueMap formParams, + final Principal principal, + final HttpServletResponse response) { + + try { + + // if an examId is provided with the request, update the connection first + if (formParams != null && formParams.containsKey(API.EXAM_API_PARAM_EXAM_ID)) { + final String examId = formParams.getFirst(API.EXAM_API_PARAM_EXAM_ID); + final Long institutionId = getInstitutionId(principal); + final ClientConnection connection = this.sebClientConnectionService.updateClientConnection( + connectionToken, + institutionId, + Long.valueOf(examId), + null, + null) + .getOrThrow(); + + if (log.isDebugEnabled()) { + log.debug("Updated connection: {}", connection); + } + } + + final ServletOutputStream outputStream = response.getOutputStream(); + + try { + + final ClientConnectionData connection = this.examSessionService + .getConnectionData(connectionToken) + .getOrThrow(); + + // exam integrity check + if (connection.clientConnection.examId == null || + !this.examSessionService.isExamRunning(connection.clientConnection.examId)) { + + log.error("Missing exam identifier or requested exam is not running for connection: {}", + connection); + throw new IllegalStateException("Missing exam identifier or requested exam is not running"); + } + } catch (final Exception e) { + + log.error("Unexpected error: ", e); + + final APIMessage errorMessage = APIMessage.ErrorMessage.GENERIC.of(e.getMessage()); + outputStream.write(Utils.toByteArray(this.jsonMapper.writeValueAsString(errorMessage))); + response.setStatus(HttpStatus.BAD_REQUEST.value()); + outputStream.flush(); + outputStream.close(); + return; + } + + try { + + this.examSessionService + .streamDefaultExamConfig( + connectionToken, + outputStream); + + response.setStatus(HttpStatus.OK.value()); + + } catch (final Exception e) { + final APIMessage errorMessage = APIMessage.ErrorMessage.GENERIC.of(e.getMessage()); + outputStream.write(Utils.toByteArray(this.jsonMapper.writeValueAsString(errorMessage))); + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + + } finally { + outputStream.flush(); + outputStream.close(); + } + + } catch (final Exception e) { + log.error("Unexpected error while trying to stream SEB Exam Configuration to client with connection: {}", + connectionToken, + e); + + final APIMessage errorMessage = APIMessage.ErrorMessage.GENERIC.of(e.getMessage()); + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + try { + response.getOutputStream().write(Utils.toByteArray(this.jsonMapper.writeValueAsString(errorMessage))); + } catch (final Exception e1) { + log.error("Failed to write error to response: ", e1); + } + } + } + } diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/ExamAPIIntegrationTester.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/ExamAPIIntegrationTester.java index 4204df31..3e253f74 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/ExamAPIIntegrationTester.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/ExamAPIIntegrationTester.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.integration.api.exam; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.Collections; @@ -39,8 +40,10 @@ import org.springframework.security.web.FilterChainProxy; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -153,7 +156,12 @@ public abstract class ExamAPIIntegrationTester { builder.content(body); - final ResultActions result = this.mockMvc.perform(builder) + final MvcResult mvcResult = this.mockMvc.perform(builder) + .andExpect(request().asyncStarted()) + .andDo(MockMvcResultHandlers.log()) + .andReturn(); + + final ResultActions result = this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)); return result.andReturn().getResponse(); @@ -209,7 +217,13 @@ public abstract class ExamAPIIntegrationTester { } builder.content(body); - final ResultActions result = this.mockMvc.perform(builder); + final MvcResult mvcResult = this.mockMvc + .perform(builder) + .andExpect(request().asyncStarted()) + .andDo(MockMvcResultHandlers.log()) + .andReturn(); + + final ResultActions result = this.mockMvc.perform(asyncDispatch(mvcResult)); return result.andReturn().getResponse(); } @@ -221,7 +235,13 @@ public abstract class ExamAPIIntegrationTester { .header("Authorization", "Bearer " + accessToken) .header(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken) .accept(MediaType.APPLICATION_JSON_UTF8_VALUE); - final ResultActions result = this.mockMvc.perform(builder); + + final MvcResult mvcResult = this.mockMvc + .perform(builder) + .andExpect(request().asyncStarted()) + .andDo(MockMvcResultHandlers.log()) + .andReturn(); + final ResultActions result = this.mockMvc.perform(asyncDispatch(mvcResult)); return result.andReturn().getResponse(); } @@ -239,7 +259,12 @@ public abstract class ExamAPIIntegrationTester { + "&" + API.EXAM_API_PING_NUMBER + "=" + num; builder.content(body); - final ResultActions result = this.mockMvc.perform(builder); + final MvcResult mvcResult = this.mockMvc + .perform(builder) + .andExpect(request().asyncStarted()) + .andDo(MockMvcResultHandlers.log()) + .andReturn(); + final ResultActions result = this.mockMvc.perform(asyncDispatch(mvcResult)); return result.andReturn().getResponse(); } @@ -259,7 +284,12 @@ public abstract class ExamAPIIntegrationTester { final String body = "{ \"type\": \"%s\", \"timestamp\": %s, \"numericValue\": %s, \"text\": \"%s\" }"; builder.content(String.format(body, type, timestamp, value, text)); - final ResultActions result = this.mockMvc.perform(builder); + final MvcResult mvcResult = this.mockMvc + .perform(builder) + .andExpect(request().asyncStarted()) + .andDo(MockMvcResultHandlers.log()) + .andReturn(); + final ResultActions result = this.mockMvc.perform(asyncDispatch(mvcResult)); return result.andReturn().getResponse(); } @@ -278,8 +308,13 @@ public abstract class ExamAPIIntegrationTester { builder.content("examId=" + examId); } - final ResultActions result = this.mockMvc - .perform(builder); + final MvcResult mvcResult = this.mockMvc + .perform(builder) + .andExpect(request().asyncStarted()) + .andDo(MockMvcResultHandlers.log()) + .andReturn(); + + final ResultActions result = this.mockMvc.perform(asyncDispatch(mvcResult)); return result.andReturn().getResponse(); } diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebExamConfigurationRequestTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebExamConfigurationRequestTest.java index c547759c..8b3fc6cf 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebExamConfigurationRequestTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebExamConfigurationRequestTest.java @@ -210,7 +210,7 @@ public class SebExamConfigurationRequestTest extends ExamAPIIntegrationTester { // check error final String contentAsString = configResponse.getContentAsString(); assertEquals( - "{\"messageCode\":\"0\",\"systemMessage\":\"Generic error message\",\"details\":\"Missing exam identider or requested exam is not running\",\"attributes\":[]}", + "{\"messageCode\":\"0\",\"systemMessage\":\"Generic error message\",\"details\":\"Missing exam identifier or requested exam is not running\",\"attributes\":[]}", contentAsString); // check connection cache @@ -253,7 +253,7 @@ public class SebExamConfigurationRequestTest extends ExamAPIIntegrationTester { assertTrue(HttpStatus.OK.value() != configResponse.getStatus()); final String contentAsString = configResponse.getContentAsString(); assertEquals( - "{\"messageCode\":\"0\",\"systemMessage\":\"Generic error message\",\"details\":\"Missing exam identider or requested exam is not running\",\"attributes\":[]}", + "{\"messageCode\":\"0\",\"systemMessage\":\"Generic error message\",\"details\":\"Missing exam identifier or requested exam is not running\",\"attributes\":[]}", contentAsString); }