How do you unit test a servlet endpoint in apache camel? - java

I'm new to Camel and now have a simple route running in my Tomcat server. The route is built like this:
Processor generateWebResponse = new MySpecialProcessor();
from("servlet:///url?matchOnUriPrefix=true").process(generateWebResponse);
I tried a simple unit test like this:
Exchange lAuthRequest = createExchangeWithBody("[json body!]");
template.send("servlet:///url", lAuthRequest);
assertEquals("baseline body", lAuthRequest.getOut().getBody());
but get an exception indicating that I can't make a servlet endpoint. Here is the exception message:
org.apache.camel.FailedToCreateProducerException: Failed to create Producer for endpoint: Endpoint[servlet:///url]. Reason: java.lang.UnsupportedOperationException: You cannot create producer with servlet endpoint, please consider to use http or http4 endpoint.
This is new development so I don't have many constraints other than good design. I'm open to suggestions that require changes to the route. Also, if I'm doing something above that isn't idiomatic, I'm happy to revise the question with any suggested improvements.

You need to use a http client component to send a message to Tomcat, eg for example the camel--http component: http://camel.apache.org/http
You would then need to know the port number Tomcat runs the servlet at, eg
template.send("http://localhost:8080/myapp/myserver", lAuthRequest);
You would need to add camel-http to your classpath, eg if you use maven then add it as a dependency.

I solved my problem by breaking the route into two parts. Now the route declaration looks like this:
from("servlet:///auth?matchOnUriPrefix=true").inOut("direct:auth");
from("direct:auth").process(new AuthorizationProcessor());
And the test looks like this:
Exchange lAuthRequest = createExchangeWithBody("test body");
template.send("direct:auth", lAuthRequest);
assertEquals("processed body", lAuthRequest.getOut().getBody());
This isn't a complete test, but allows me to get coverage of all of the route excluding the incoming servlet part. I think it's sufficient for the time being.

Related

Quarkus GraphQL Client with Keycloak

I'm trying this for days right now and I'm not sure if i missed something.
I have a Quarkus GraphQL Service , like here : https://quarkus.io/guides/smallrye-graphql
And I have setup Keycloak to secure it.
Now I wanted to create a client with Qute and GraphQL Smallrye client like here : https://quarkus.io/guides/smallrye-graphql-client
The client can connect to the service, but I always get an "Data Fetching Error: io.quarkus.security.UnauthorizedException".
It seems like the GraphQL client is not sending the headers correctly or it doesn't send any ...
Does anyone know how I can tell the client to send the Authorization header from keycloak with every call?
PS: I tested it with a short react frontend and there it's working, so it seems to be an graphql client issue with the headers... Some ideas?
Not sure if you're using a dynamic or typesafe client, so I'll describe both.
For both types, if you have a key that doesn't change during the life of the application, you can configure that by adding a configuration property like this:
quarkus.smallrye-graphql-client.CLIENT_NAME.header.HEADER_NAME=HEADER_VALUE
(see https://quarkus.io/guides/all-config#quarkus-smallrye-graphql-client_quarkus-smallrye-graphql-client-smallrye-graphql-client)
If the value can change over time, I would probably recommend using the programmatic builder instead of using a statically configured client, like this:
DynamicGraphQLClientBuilder.newBuilder()
.header("KEY", "VALUE") // obtain the correct value from Keycloak and pass it here
....
and build a new client instance if the value changes.
For typesafe clients, an annotation-based way is described in https://smallrye.io/smallrye-graphql/1.4.3/typesafe-client-headers/
I don't know much about Keycloak though so I can't comment on the way how you obtain the header value from it.
Let me know some of this works for you
There doesn't seem to be a dynamic way of adding keycloak headers to the call.
I found a solution, to at least work as long as the token is valid.
For this I'm using a Client Class, which has an Interface Object of the GraphQL client and injects the header inside the constructor.I will need to add some code to renew the token, but it's enough to work with it right now :)
For those who want some code:
#Singleton
public class Client {
public IClient client;
public Client(JsonWebToken accessToken) {
client = TypesafeGraphQLClientBuilder.newBuilder()
.header("Authorization", "Bearer " + accessToken.getRawToken())
.build(IClient.class);
}
}
With this the Client can be build like shown at the Quarkus Guides :)

How to handle real API URL via mock server?

I have a real API (https://profiles.production.service/api/person). And want to mock it using MockServer.
I'm using JUnit 5 in my integration test:
#Rule
val mockServer = MockServerContainer(DockerImageName.parse("jamesdbloom/mockserver:mockserver-5.11.2"))
mockServer.start()
....
MockServerClient("profiles.production.service", mockServer.serverPort)
.`when`(
request()
.withPath("/api/person")
.withQueryStringParameter("name", "peter")
)
.respond(
response()
.withBody("Peter the person!")
)
But actually got error:
org.mockserver.client.SocketConnectionException: Unable to resolve host profiles.production.service/<unresolved>:55070
How can I fix it?
This answer arrives too late for sure. But I am writing, just in case others come here and have a similar problem.
Looking at the documentation of the mockserver you can see an example that would make it work. Your code would need to change to:
MockServerClient(mockServer.getHost(), mockServer.getServerPort())
MockServerContainer is a docker image running on its own. So it will have a different URL than the server you need to mock.
If the problem you have is that you have hardcoded the server name on your class, then you would need to change that class. You will need to inject the server name. In your case
"https://%s:%d/api/person".format(mockServer.getHost(), mockServer.getServerPort())

How to add matchOnUriPrefix on existing Camel Jetty REST routes?

We have existing REST routes working with Camel 2.23.1 and jetty. We redirect incoming calls to an appropriate server based on the uri, query, and the user's authentication. We want to handle this more generally.
How can we modify the following code to handle any uri with "/say" as the prefix?
In our RouteBuilder:
RestConfigurationDefinition rConfig = restConfiguration()
.component("jetty")
.port(webserverPort)
.contextPath("/")
.bindingMode(RestBindingMode.off)
.enableCORS(true)
.dataFormatProperty("prettyPrint", "true");
rest("/say")
.get().to("direct:test");
from("direct:test")
.bean(RouteRest.class, "getTestURI(*,*)")
.to("mock:output");
We have tried adding a property to the restConfiguration, ala
.componentProperty("matchOnUriPrefix", "true");
We have tried adding the same property to the rest route definition, ala
rest("/bye?matchOnUriPrefix=true")
We have tried creating a new from statement, which seems to break everything, ala
from("jetty://0.0.0.0:8123/now?matchOnUriPrefix=true").to("direct:test");
I am aware of this question and answer, but don't know how to apply it to my case:
stackoverflow.com/questions/39341784
Further, is it possible to match some incoming calls with explicitly defined uri's, like "/admin/status", and all other uri's to "direct:test"?
We ended up taking out the restConfiguration() entirely and configuring endpoints individually, which fit our expanding requirements anyway. Our oritinal restConfiguration() was limiting the messages that could get to the endpoints themselves. Perhaps we could have modified the restConfiguration directly to enable greater flexibility, including removal of .contextPath("/"). This directly allowed the following code to work:
from("jetty:http://{{ip}}:{{port}}?matchOnUriPrefix=true")
.bean(RestForward.class, "checkUserAuth(*)")
.bean(RestForward.class, "checkDevice(*)")
.bean(RestForward.class, "forward(*,*)")
.to("mock:output");

Spring Boot - postForEntity(): what is content type of the response?

I am attempting to set up and run integration tests on a set of Spring Boot microservices that communicate via HTTP REST. I am using the Citrus Framework for the integration test framework.
I have a test scenario that involves one "master" service calling two other services to do work. My test has the calls to start the process and "mock" the worker services. I'll include source below.
I'm running into an issue where I get an exception that seems to indicate that a message the test is expecting to receive (as application/json) is coming through as text/plain and it cannot find a message converter to use. The odd thing is that the message that's is being received should be JSON (or least look like JSON).
I encountered a similar issue on the sending end (the POSTer), where Citrus was having a problem with receiving a message. I traced it down to the fact that I had not been setting any HTTP headers, specifically Accept and Content-Type. Once I set these appropriately, Citrus was happy with what it received.
The service code:
HttpEntity<GenerateRouteCommand> entity =
(HttpEntity<GenerateRouteCommand>) HttpEntityBuilder.createHttpEntity(...);
ResponseEntity<GenerateRouteStatus> response =
rgTemplate.postForEntity(url, entity, GenerateRouteStatus.class);
The exception:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class edu.mit.ll.mission_services.messages.GenerateRouteStatus] and content type [text/plain;charset=utf-8]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:121)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:994)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:977)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:737)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:670)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:445)
at edu.mit.ll.mission_services.service.mission_planner.service.MissionPlanner.postGenerateRoute(MissionPlanner.java:214)
at edu.mit.ll.mission_services.service.mission_planner.service.MissionPlanner.planMission(MissionPlanner.java:144)
at edu.mit.ll.mission_services.service.mission_planner.controller.MissionPlannerController$Runner.executeTask(MissionPlannerController.java:51)
at edu.mit.ll.mission_services.common.util.ITask.run(ITask.java:37)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
The entity that's being sent in the POST request has the Accept and Content-Type headers set to application/json. I think I'm OK there. The stack trace above seems to be saying that the response message either has no headers set (or set to the wrong values) and since the test case expects JSON, fails the test.
When these services are run "normally" (i.e. not being driven via Citrus), everything works fine. No problems that I've been able to discern.
I've recently ran into this issue with spring-boot twice while trying to decode REST Respones. I solved this issue by explicitly setting HttpMessageConverters on the RestTemplate.
If I were you I would debug and see what the content of the response actually looks like. If it is JSON try using GsonHttpMessageConverter or if it really is just text, try StringHttpMessageConverter. Alternatively add both to the RestTemplate via the following:
private final RestTemplate restTemplate = new RestTemplateBuilder()
.messageConverters(Lists.newArrayList(new GsonHttpMessageConverter(GsonHelper.getInstance()), new StringHttpMessageConverter()))
.build();
If neither of these converters appear to solve your exception, take a look at all the different implementations of the HttpMessageConverter interface and deduce which one would suite your specific case. There are a fair number of implementations implementing this interface.
Figured out what I needed to do to set the expected content type of application/json for a message returned from a mocked server in Citrus. I was missing the following statement:
.contentType(ContentType.APPLICATION_JSON.getMimeType())
Some context:
// Set route assessor to return response.
runner.http(builder -> builder
.server(raServer)
.send()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.contentType(ContentType.APPLICATION_JSON.getMimeType())
.payload(new ClassPathResource("templates/assess-route-status.json")));
Evidently, it defaults to text/plain if not specified.

Integration testing with spark server

I'm trying to IT my spark server. My intentions are to test all the controller functions.
I have thought about few options:
1. Set up a server that will start when running the tests, and terminate when the tests are over.
The problem with this solution is that I have to rewrite my whole server logic to the new server (we start server from scratch every time we set the server before the testing).
Initiate a controller from the test class (essential to initiate and not static call, in order to configure the right db to the controller) that will call the controller functions and check their answers.
This is my favorite one, but it means that I have to mock a spark request. I'm trying to build a spark request, and spark response objects, to send to my controller, and haven't found a single way to do that properly (and how to send parameters, set url routes etc..)
#Test
Public void testTry(){
String expectedName = "mark";
myController myCtl = new myController()
Request req = null;
Response res = null;
String childName = myCtl.getChildNameFromDB(req, res);
assertEquals(childName, expectedName);
}
The last one is to do the exact logic of the controller function in the test, and instead of getting the parameters from the request, ill initiate them myself.
For example, instead of:
String username = req.params(""usrName")
It will be:
Strimg username = "mark"
But that solution will demand copying a lot of code, and you might miss a little code line which might make the test succeed when in reality, the controller function fails (or doesn't deliver as wanted).
What do you think about Integratiom testing a spark driven server? I'm open minded to new solutions as well.
If you want to do integration testing, I would suggest to use your first approach, using a randomly chosen free TCP port and a HTTP client library (I often use the excellent HttpRequest library to that effect).
The main issue with this approach is that since Spark API is static, you won't be able to stop/start the server between test cases/suites.

Categories

Resources