How to set context from request header using graphql-java - java

I want to be able to set a context variable from the http request header I receive from the request. This will be a jwt token so I can identify my user on every query.
package br.com.b2breservas.api;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.net.URL;
import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring;
#Component
public class GraphQLProvider {
#Autowired
GraphQLDataFetchers graphQLDataFetchers;
private GraphQL graphQL;
#PostConstruct
public void init() throws IOException {
URL url = Resources.getResource("schema.graphqls");
String sdl = Resources.toString(url, Charsets.UTF_8);
GraphQLSchema graphQLSchema = buildSchema(sdl);
this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
}
private GraphQLSchema buildSchema(String sdl) {
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
RuntimeWiring runtimeWiring = buildWiring();
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
.type(newTypeWiring("Query")
.dataFetcher("books", graphQLDataFetchers.getBooks()))
.type(newTypeWiring("Query")
.dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
.type(newTypeWiring("Book")
.dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
.build();
}
#Bean
public GraphQL graphQL() {
return graphQL;
}
}

You can create a customized object which internally contain the JWT or simply the HttpServletRequest :
public class GraphQLContext {
private HttpServletRequest httpServletRequest;
}
When executing a GraphQL query , you create this context object and set it to the ExecutionInput. Most web framework should provide some ways to access the current HttpServletRequest easily :
GraphQLContext context = new GraphQLContext(httpServletRequest);
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(query)
.context(context)
.build();
ExecutionResult result = graphQL.execute(executionInput);
Then in the data fetcher , the context can be get by :
#Override
public Object get(DataFetchingEnvironment env) throws Exception {
GraphQLContext context = env.getContext();
HttpServletRequest httpServletRequest = context.getHttpServletRequest();
}

You can inject (or Autowire) your custom GraphQLInvocation instance which can act as an interceptor for all requests handled by GraphQL
import graphql.ExecutionInput
import graphql.ExecutionResult
import graphql.GraphQL
import graphql.spring.web.servlet.GraphQLInvocation
import graphql.spring.web.servlet.GraphQLInvocationData
import org.springframework.context.annotation.Primary
import org.springframework.stereotype.Component
import org.springframework.web.context.request.WebRequest
import java.util.concurrent.CompletableFuture
#Component
#Primary // <= Mark it as Primary to override the default one
class ErsanGraphQLInvocation(private val graphQL: GraphQL) : GraphQLInvocation {
override fun invoke(invocationData: GraphQLInvocationData,
webRequest: WebRequest): CompletableFuture<ExecutionResult> {
val context = "Context" //Basically any class you want <=====
val executionInput = ExecutionInput.newExecutionInput()
.query(invocationData.query)
.operationName(invocationData.operationName)
.variables(invocationData.variables)
.context(context)
.build()
return graphQL.executeAsync(executionInput)
}
}
and then in your DataFetcher, you can read the context from the DataFetchingEnvironment instance, eg.
fun appVersionFetcher(): DataFetcher<Boolean> {
return DataFetcher { dataFetchingEnvironment ->
val context = dataFetchingEnvironment.getContext<String>()
println("Context $context")
false
}
}

Related

Spring Mvc integration test failed

I have done a Spring Mvc application and I have been trying to write some integration tests, but I keep getting errors. Here is my AgentService:
#Service
public class AgentService {
#Autowired
private AgentRepository agentRepository;
#Autowired
private AgencyRepository repoAg;
#Autowired
private AgentMapper mapper;
public List<AgentDto> listByAgency(Long id) {
List<Agent> agents = agentRepository.findAll().stream().filter(ag -> ag.getAgency().getId().equals(id)).collect(Collectors.toList());
List<AgentDto> mappedAgents = agents.stream().map(agent -> mapper.mapToDto(agent)).collect(Collectors.toList());
return mappedAgents;
}
}
And my AgentController:
#Controller
public class AgentController {
#Autowired
private AgentService agentService;
#GetMapping("/viewAgentsPage/{id}")
public String viewAgentsPage(#PathVariable(value = "id") Long id, Model model) {
model.addAttribute("listAgents", agentService.listByAgency(id));
return "agents";
}
}
This is the test I've written:
package com.example.awbdproject.controller;
import com.example.awbdproject.models.Agency;
import com.example.awbdproject.models.Agent;
import com.example.awbdproject.services.AgentService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.ui.Model;
import static org.mockito.Mockito.doReturn;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
#SpringBootTest
#AutoConfigureMockMvc
public class AgentControllerTest {
#Autowired
MockMvc mockMvc;
#MockBean
AgentService agentService;
#MockBean
Model model;
#Test
#WithMockUser
public void showAgentsByAgency() throws Exception {
List<AgentDto> savedAgents = new ArrayList<AgentDto>();
Long id = 1L;
Agency agencyTest = new Agency();
agencyTest.setId(id);
AgentDto agentTest = new AgentDto();
agentTest.setId(1L);
agentTest.setFirstName("test");
agentTest.setLastName("test");
agentTest.setEmail("email#gmail.com");
agentTest.setAgency(agencyTest);
savedAgents.add(agentTest);
when(agentService.listByAgency(id)).thenReturn(savedAgents);
mockMvc.perform(get("/viewAgentsPage/{id}", "1"))
.andExpect(status().isOk())
.andExpect(view().name("agents"))
.andExpect(model().attribute("listAgents", savedAgents))
.andExpect(content().contentType("text/html;charset=UTF-8"));
}
When I run the test I'm getting this error:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /viewAgentsPage/1
Parameters = {}
Headers = []
Body = null
Session Attrs = {SPRING_SECURITY_SAVED_REQUEST=DefaultSavedRequest [http://localhost/viewAgentsPage/1]}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 302
Error message = null
Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY", Location:"http://localhost/login"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = http://localhost/login
Cookies = []
What seems to be the issue here? I have never used Mockito and can't seem to wrap my head around it. I think it might have something to do with Spring Security. If it helps, this is my security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception{
http.authorizeRequests().antMatchers(
"/register/**",
"/js/**",
"/css/**",
"/img/**"
).permitAll().anyRequest().authenticated()
.and().formLogin().loginPage("/login")
.permitAll()
.and().logout().invalidateHttpSession(true).clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout").permitAll();
}

JerseyExtension based test with JUnit5 not keeping session between requests

I have a self contained Jersey test using JerseyExtension (JerseyExtension) with JUnit5 (since JerseyTest does not work with JUnit5 unless you use the vintage engine) and subsequent calls to the container are getting different session. Is there a way to keep the session store same between the calls?
package com.test.jerseysession;
import com.github.hanleyt.JerseyExtension;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.RegisterExtension;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class JerseyTestWithGrizzly {
private final static TestContainerFactory testContainerFactory;
private final ServletContainer servletContainer;
private final ResourceConfig resourceConfig;
private final DeploymentContext deploymentContext;
static {
testContainerFactory = new GrizzlyWebTestContainerFactory();
}
#RegisterExtension
#SuppressWarnings("unused")
JerseyExtension jerseyExtension = new JerseyExtension(
this::getTestContainerFactory,
this::configureDeploymentContext,
this::configureJerseyClient);
public JerseyTestWithGrizzly() {
this.resourceConfig = new ResourceConfig()
.packages("com.test.jerseysession")
.register(getClass());
this.servletContainer = new ServletContainer(resourceConfig);
this.deploymentContext = ServletDeploymentContext.builder(resourceConfig)
.servlet(servletContainer)
.servletPath("api")
.build();
}
#Path("session")
public static class SessionResource {
#GET
public String get(#Context HttpServletRequest request) {
HttpSession session = request.getSession(true);
Object obj = session.getAttribute("name");
return session.getId() + ": " + obj;
}
#PUT
public String put(#Context HttpServletRequest request) {
HttpSession session = request.getSession(true);
session.setAttribute("name", "foo");
return session.getId()+": Set name attribute called";
}
}
protected ClientConfig configureJerseyClient(ExtensionContext extensionContext, ClientConfig clientConfig) {
assertNotNull(extensionContext);
assertNotNull(clientConfig);
return clientConfig;
}
protected DeploymentContext configureDeploymentContext(ExtensionContext extensionContext) {
assertNotNull(extensionContext);
return deploymentContext;
}
protected TestContainerFactory getTestContainerFactory(ExtensionContext extensionContext) {
assertNotNull(extensionContext);
return testContainerFactory;
}
#Test
public void testSessionSet(WebTarget target) {
// Call PUT which sets attribute called 'name'
Response response0 = target.path("session").request().put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE));
System.out.println("PUT: status="+response0.getStatus()+" response="+response0.readEntity(String.class));
// Call GET which should be able to find 'name' in session set by previous call
Response response1 = target.path("session").request().get();
System.out.println("GET: status="+response1.getStatus()+" response="+response1.readEntity(String.class));
}
}
Sample output:
PUT: status=200 response=8373522406385125383: Set name attribute called
GET: status=200 response=8264425692811867393: null
The session ID changed between the call to PUT and GET.
The client used by Jersey test framework, does not behave like a browser when it comes to Set-Cookie/Cookie headers. The two requests are not connected and JSESSIONID set by first response is not propagated to next request. While the framework is aware of the JSESSIONID if present, it does not span requests and needs to be manually copied forward.
Changing the test method to following works:
#Test
public void testSessionSet(WebTarget target) {
Response response0 = target.path("session").request().put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE));
System.out.println("PUT: status="+response0.getStatus()+" response="+response0.readEntity(String.class));
Invocation.Builder nextRequestBuilder = target.path("session").request();
NewCookie jsessionid = response0.getCookies().get("JSESSIONID");
if (jsessionid != null) {
nextRequestBuilder.cookie(jsessionid);
}
Response response1 = nextRequestBuilder.get();
System.out.println("GET: status="+response1.getStatus()+" response="+response1.readEntity(String.class));
}

Why requests are returned 404 status when using WebFilter corsFilter in webflux?

I want to build a REST api in Spring Webflux using functional endpoints. For CORS I use a WebFilter corsFilter method which sets the required headers. I do see that the method is called (I see the log messages from it) and I see that the headers on the response are indeed the ones I set in my Webflux api. However, as soon as I started to use the corsFilter the requests return 404 status (earlier they would return JSON). I suspect that corsFilter doesn't pass over the request to the router functions. Why would that be?
Specifically I'm wondering if this line is enough to connect the cors config with the routes:
HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(RouterFunctions.toWebHandler(route))
.applicationContext(ctx).build();
This is my main class:
package com.mypackage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import reactor.ipc.netty.http.server.HttpServer;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
import static org.springframework.web.reactive.function.server.RequestPredicates.method;
import static org.springframework.web.reactive.function.server.RequestPredicates.path;
import static org.springframework.web.reactive.function.server.RouterFunctions.nest;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
#SpringBootApplication
public class Server {
private static final Logger log = LogManager.getLogger(Server.class);
public static final String HOST = "localhost";
public static final int PORT = 8080;
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(CorsConfiguration.class);
Server server = new Server();
server.startReactorServer(ctx);
System.out.println("Press ENTER to exit.");
System.in.read();
}
public RouterFunction<ServerResponse> routingFunction() {
PersonRepository repository = new DummyPersonRepository();
PersonHandler handler = new PersonHandler(repository);
return nest(path("/person"),
nest(accept(APPLICATION_JSON),
route(GET("/{id}"), handler::getPerson)
.andRoute(method(HttpMethod.GET), handler::listPeople)
).andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::createPerson));
}
public void startReactorServer(AnnotationConfigApplicationContext ctx) {
RouterFunction<ServerResponse> route = this.routingFunction().filter((request, next) -> {
log.warn(request.path());
if (request.path().contains("person")) {
log.warn("calling next()");
return next.handle(request);
} else {
return ServerResponse.status(UNAUTHORIZED).build();
}
});
HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(RouterFunctions.toWebHandler(route))
.applicationContext(ctx).build();
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create(HOST, PORT);
server.newHandler(adapter).block();
}
}
and this is my CORS config class:
package com.mypackage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
#Configuration
#EnableWebFlux
public class CorsConfiguration {
private static final Logger log = LogManager.getLogger(CorsConfiguration.class);
private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN, mode";
private static final String ALLOWED_METHODS = "GET, PUT, POST, DELETE, OPTIONS";
private static final String ALLOWED_ORIGIN = "*";
private static final String MAX_AGE = "3600";
#Bean
public WebFilter corsFilter() {
log.warn("from CorsConfiguration!!!");
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
log.warn("after ServerHttpRequest");
if (CorsUtils.isCorsRequest(request)) {
log.warn("inside isCorsRequest");
ServerHttpResponse response = ctx.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
headers.add("Access-Control-Max-Age", MAX_AGE);
headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
}
To use the functional approach when defining your endpoints Spring Boot's official documentation has a very simple example.
FooBarApplication.class this is our main class.
#SpringBootApplication
public class FooBarApplication {
public static void main(String[] args) {
SpringApplication.run(FooBarApplication.class, args);
}
}
RoutingConfiguration.class (or whatever you wanna call it)
#Configuration
public class RoutingConfiguration {
#Bean
public RouterFunction<ServerResponse> monoRouterFunction(UserHandler userHandler) {
return route(GET("/{user}").and(accept(APPLICATION_JSON)), userHandler::getUser)
.andRoute(GET("/{user}/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers)
.andRoute(DELETE("/{user}").and(accept(APPLICATION_JSON)), userHandler::deleteUser);
}
}
#Component
public class UserHandler {
public Mono<ServerResponse> getUser(ServerRequest request) {
// ...
}
public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
// ...
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
// ...
}
}
any class annotated with #Configuration will be run at startup and run all #Bean annotated methods. So this will run the monoRouterFunction and set up all our routes for us.
Example taken from the official spring boot documentation Spring boot webflux scroll down a little bit.
EDIT:
and as a side note the #EnableWebFlux annotation means that you will disable the auto-configuration of webflux and set upp configuration manually. I do not recommend this if you are just starting out (i know the name is very misleading) you can read about the webflux auto-configuration here Spring WebFlux Auto-configuration
EDIT2:
WebFlux has a built in CorsFilter that you can use all you need is to configure it.
#Bean
CorsWebFilter corsWebFilter() {
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowedOrigins(Arrays.asList("http://allowed-origin.com"));
corsConfig.setMaxAge(8000L);
corsConfig.addAllowedMethod("PUT");
corsConfig.addAllowedHeader("Baeldung-Allowed");
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return new CorsWebFilter(source);
}
Example taken from Enabling CORS with a WebFilter

Spring Boot POST Request specific payload not being accepted

I have a post request handler setup in my spring boot app that is acting as a response entity. I have a hash map that contains a string value and string key. I am comparing the RequestBody param with the mapped key which should be the input that user is posting and then it spits out the mapped value.
When I do this curl command:
curl -d "ncs|56-2629193|1972-03-28|20190218|77067|6208|3209440|self|-123|-123|-123|0.0|0.0|0.0|0.0|0.0|0.0|0.0" -H 'Content-Type: text/plain' http://localhost:9119/prediction
It returns with the custom entity response error message that it is the wrong payload even though it matches up with my string input contained in the hash map.
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Inccorect payload");
Am I comparing the strings wrong?
Here is controller class:
import java.io.IOException;
import java.util.HashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#Validated
#RestController
public class MockController {
#Autowired
MockEndPoint mockendpoint;
#Autowired
MockConfig mockconfig;
#RequestMapping(value = "/", method = RequestMethod.GET)
public String index() {
return "hello!";
}
#RequestMapping(value = "/prediction", method = RequestMethod.POST, produces = {"application/json"},consumes= "text/plain")
public ResponseEntity<String> payloader(#RequestBody String params ) throws IOException{
HashMap<String,String> x = mockconfig.getHM();
if(params.equals((String) x.keySet().toArray()[0])) {
return ResponseEntity.ok(x.get(mockconfig.input1));
}
else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Inccorect payload amount(18 parameters required");
}
}
}
My config class:
import java.io.IOException;
import java.util.HashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
#Configuration
public class MockConfig {
String input1 = "ncs|56-2629193|1972-03-28|20190218|77067|6208|3209440|self|-123|-123|-123|0.0|0.0|0.0|0.0|0.0|0.0|0.0";
String input2 = "ncp|56-2629193|1955-11-28|20181213|73630|6404|182232|self|-123|-123|-123|0.0|0.0|0.0|0.0|0.0|0.0|33.35";
String input3 = "ncp|56-2629193|1955-11-28|20190103|73630|6404|182232|self|-123|-123|-123|0.0|0.0|0.0|0.0|0.0|0.0|33.35";
String input4 = "ncp|56-2629193|1955-11-28|20190213|73700|6404|182232|self|-123|-123|-123|0.0|20.0|325.0|0.0|0.0|269.28|269.28";
#Autowired
MockEndPoint mockendpoint;
private HashMap<String,String> hm = new HashMap<String,String>();
public HashMap<String,String> getHM() throws IOException {
hm = new HashMap<String,String>();
hm.put(input1,mockendpoint.Payload1());
hm.put(input2,mockendpoint.Payload2());
hm.put(input3,mockendpoint.Payload3());
hm.put(input4,mockendpoint.Payload4());
return hm;
}
}
My endpoint class:
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ResourceUtils;
#Configuration
public class MockEndPoint {
#Bean
public String Payload1() throws IOException {
File file = ResourceUtils.getFile("src/test/resources/Payload1.txt");
String content = new String(Files.readAllBytes(file.toPath()));
return content;
}
#Bean
public String Payload2() throws IOException {
File file = ResourceUtils.getFile("src/test/resources/Payload2.txt");
String content = new String(Files.readAllBytes(file.toPath()));
return content;
}
#Bean
public String Payload3() throws IOException {
File file = ResourceUtils.getFile("src/test/resources/Payload3.txt");
String content = new String(Files.readAllBytes(file.toPath()));
return content;
}
#Bean
public String Payload4() throws IOException {
File file = ResourceUtils.getFile("src/test/resources/Payload4.txt");
String content = new String(Files.readAllBytes(file.toPath()));
return content;
}
}
I'm not sure what's really causing this error but I have a feeling its coming from trying to compare the string param with the first key, maybe it doesn't like that I casted it?
I think issue with following code. you are getting keyset of HashMap x and checking with first key in it against your payload. but since you are using HashMap it may not give you the order in which you have inserted entries in it. you can replace the HashMap with LinkedHashMap and your code will work as expected since it maintains the insertion order.
if(params.equals((String) x.keySet().toArray()[0])) {
return ResponseEntity.ok(x.get(mockconfig.input1));
}

Spring MVC Test with HtmlUnit - How to test a JSON response

Background: Search form in a webapp, with an auto-complete/suggestion. Using jQuery's autocomplete, suggestions are shown after typing a few characters. These suggesitons are retrieved as JSON data from one of the webapp's controllers.
Issue: I am testing the application via the HtmlUnit Integration of Spring Test, which works fine for Text/Html Pages, but for the JSON responses here, the setup seems to fail (see error below).
Manually testing (the actual webapp) via Browser works and fetching JSON from "real" pages works as well ( see "json()" test) -> Should testing JSON responses work via HtmlUnit / Spring Test setup and if yes, what am I doing wrong?
Update (2017-06-21):
Using
#ResponseBody String
and building the JSON "manually" (not letting Spring automagically doing it) works; not really what I wanted, but at least I can properly test it this way ...
Test:
package my.project;
import com.gargoylesoftware.htmlunit.*;
import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.*;
import org.springframework.test.*;
import org.springframework.web.context.support.GenericWebApplicationContext;
import javax.servlet.ServletException;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestWebAppConfig.class,
// avoid context caching
MyControllerIT.class})
#WebAppConfiguration
public class MyControllerIT {
protected static final String SERVER_URL = "http://localhost";
protected WebClient webClient;
protected MockHttpServletRequest request;
protected MockHttpServletResponse response;
#Autowired
protected GenericWebApplicationContext webApplicationContext;
#Before
public void setUp() throws Exception {
this.request = new MockHttpServletRequest();
this.request.setServerName("Gondor.Osgiliath");
this.response = new MockHttpServletResponse();
this.webClient = this.initClient();
}
protected final WebClient initClient() throws ServletException {
DefaultMockMvcBuilder mockMvcBuilder = MockMvcBuilders.webAppContextSetup(this.webApplicationContext);
MockMvc mockMvc = mockMvcBuilder.build();
WebClient webClient = new WebClient();
webClient.setWebConnection(new MockMvcWebConnection(mockMvc));
return webClient;
}
// https://stackoverflow.com/questions/2932857/html-handling-a-json-response
#Test
public void json() throws Exception {
webClient = new WebClient(BrowserVersion.INTERNET_EXPLORER, "some.proxy", 7890);
Page page = webClient.getPage("https://stackoverflow.com/users/flair/97901.json");
WebResponse webResponse = page.getWebResponse();
String contentType = webResponse.getContentType();
String contentAsString = webResponse.getContentAsString();
}
#Test
public void suggestShouldReturnJSON() throws Exception {
Page page = webClient.getPage(SERVER_URL + MyController.SUGGEST_URL + "?term=asdf");
WebResponse webResponse = page.getWebResponse();
String contentType = webResponse.getContentType();
String contentAsString = webResponse.getContentAsString();
}
#Test
public void suggestShouldReturnJSONViaMockMvc() throws Exception {
MockMvc springMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
ResultActions resultActions = springMvc.perform(MockMvcRequestBuilders
.get(MyController.SUGGEST_URL + "?term=asdf")
.accept(MediaType.APPLICATION_JSON_VALUE));
resultActions.andDo(MockMvcResultHandlers.print());
}
}
Controller:
package my.project;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Arrays;
import java.util.List;
#Controller
public class MyController {
public static final String SUGGEST_URL = "/suggest";
//http://api.jqueryui.com/autocomplete/#option-source
#RequestMapping(value = {SUGGEST_URL,}, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody
List<Suggestion> suggest(#RequestParam(name = "term") String term) {
return Arrays.asList(new Suggestion("label1", "value1"), new Suggestion("label2", "value2"));
}
}
Config (Thymeleaf omitted for the moment):
package my.project;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#EnableWebMvc
#ComponentScan(basePackages = {"my.project.*",})
#Configuration
#Import(ThymeleafConfig.class)
abstract class TestWebAppConfig extends WebMvcConfigurerAdapter {
}
Error:
com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException: 406 Not Acceptable for http://localhost/suggest?term=asdf
at com.gargoylesoftware.htmlunit.WebClient.throwFailingHttpStatusCodeExceptionIfNecessary(WebClient.java:571)
at com.gargoylesoftware.htmlunit.WebClient.getPage(WebClient.java:396)
at com.gargoylesoftware.htmlunit.WebClient.getPage(WebClient.java:304)
at com.gargoylesoftware.htmlunit.WebClient.getPage(WebClient.java:451)
at com.gargoylesoftware.htmlunit.WebClient.getPage(WebClient.java:436)
at my.project.MyControllerIt.suggestShouldReturnJSON(MyControllerIt.java:
...

Categories

Resources