Apache Camel: Unit testing for file and http components - junit

I am fairly new to Camel & just managed to implement a use case as below with 2 routes which is using file & http components. Looking for some leads on writing junits for the same. Have tried some sample test case below based on the inputs that i found on the net. Not sure if that suffices. Appreciate your help!
Implementation:
#Override
public void configure() throws Exception {
// Global Exception Handling block
onException(FileWatcherException.class).process(new Processor() {
public void process(Exchange exchange) throws Exception {
System.out.println("Exception handled");
}
}).to("file:C:/error?recursive=true").handled(true);
// Actively listen to the input folder for an incoming file
from("file:C:/input?noop=true&recursive=true&delete=true")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
String fileName = exchange.getIn().getHeader("CamelFileName").toString();
exchange.getIn().setHeader("fileName", fileName);
}
})
// Call the Get endpoint with fileName as input parameter
.setHeader(Exchange.HTTP_METHOD, simple("GET"))
.toD("http://localhost:8090/fileWatcher?fileName=${header.fileName}")
.choice()
// if the API returns true, move the file to the outbox folder
.when(header(Exchange.HTTP_RESPONSE_CODE).isEqualTo(constant(200)))
.to("file:C:/outbox?noop=true&recursive=true")
.endChoice()
// If the API's response code is other than 200, move the file to error folder
.otherwise()
.log("Moving the file to error folder")
.to("file:C:/error?recursive=true")
.end();
// Listen to the outbox folder for file arrival after it gets moved in the above step
from("file:C:/outbox?noop=true&recursive=true")
// Request Body for POST call is set in FileDetailsProcessor class
.process(new FileDetailsProcessor())
.marshal(jsonDataFormat)
.setHeader(Exchange.HTTP_METHOD, simple("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
// Call the Rest endpoint with fileName & filePath as RequestBody
.to("http://localhost:8090/fileWatcher")
.process(new MyProcessor())
.end();
}
Junit
#Test
public void checkFileWatcherFunctionality() throws Exception {
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
// mocking all endpoints. **QUESTION** - Is this required?
mockEndpointsAndSkip("http://localhost:8090:fileWatcher?fileName=loan.csv");
mockEndpointsAndSkip("file:C:/processing");
mockEndpointsAndSkip("file:C:/error");
mockEndpointsAndSkip("http://localhost:8090:fileWatcher");
}
});
context.start();
// **QUESTION** - This is a GET call. Expecting only the HTTP status code from it. How to check that?
getMockEndpoint("mock:http://localhost:8090:fileWatcher?fileName=abc.txt").expectedBodyReceived();
// **QUESTION** - This is a POST call. How to send request body along? Expecting only the HTTP status code from it. How to check that?
getMockEndpoint("mock:http://localhost:8090:fileWatcher").expectedBodyReceived();
// **QUESTION** - Is this the right way to check?
getMockEndpoint("mock:file:C:/processing").expectedFileExists("loan.csv");;
template.sendBodyAndHeader("file:C:/inbound", "", Exchange.FILE_NAME, "loan.csv");
// QUESTION - What can be asserted now?
}
Also - How to write test cases for negative flow (exception scenario)? Looking for suggestions.

I have managed to draft the test case. Is this the right approach or can there be a better way?
This might be more of an integration test i suppose.
The issue i see now is that the test case doesn't report at the end (success or failure), instead it keeps waiting for file arrival in the input folder. What am i missing?
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class FileWatcherRouteBuilderTest extends CamelTestSupport {
#Autowired
private TestRestTemplate restTemplate;
#Override
public RoutesBuilder createRouteBuilder() throws Exception {
return new FileWatcherRouteBuilder();
}
#Test
public void testFileCopy() throws Exception {
template.sendBodyAndHeader("file:C:/inbound", "", Exchange.FILE_NAME, "abc.csv");
// Call the GET endpoint
ResponseEntity<String> getResponse = restTemplate.getForEntity("http:localhost:8090/fileWatcher?fileName=abc.csv",
String.class);
assertTrue("Get call is unsuccessful", getResponse.getStatusCode().is2xxSuccessful());
String response = getResponse.getBody();
assertTrue(!response.isEmpty());
// The file would have moved to output folder now.
File targetFile = new File("C:/processing");
assertTrue(targetFile.isDirectory());
assertEquals(1, targetFile.listFiles().length);
// Since we need to extract the file name, doing the below step
Exchange exchange = consumer.receive("file:C:/processing");
String fileName = exchange.getIn().getHeader("CamleFileName").toString();
// RequestBody needed for POST call
FileDetails fileDetails = new FileDetails(fileName, "C:/processing/"+fileName);
HttpHeaders headers = new HttpHeaders();
HttpEntity<FileDetails> request = new HttpEntity<FileDetails>(fileDetails, headers);
// Call the POST endpoint
ResponseEntity<String> postResponse = restTemplate.postForEntity("http://localhost:8090/fileWatcher", request, String.class);
assertTrue("Post call is unsuccessful", postResponse.getStatusCode().is2xxSuccessful());
// Asserting that after both the web service calls, the file is still available in the output folder
assertEquals(1, targetFile.listFiles().length);
}
}

Related

Spring AMQP RPC consumer and throw exception

I have a consumer (RabbitListner) in RPC mode and I would like to know if it is possible to throw exception that can be treated by the publisher.
To make more clear my explication the case is as follow :
The publisher send a message in RPC mode
The consumer receive the message, check the validity of the message and if the message can not be take in count, because of missing parameters, then I would like to throw Exception. The exception can be a specific business exception or a particular AmqpException but I want that the publisher can handle this exception if it is not go in timeout.
I try with the AmqpRejectAndDontRequeueException, but my publisher do not receive the exception, but just a response which is empty.
Is it possible to be done or may be it is not a good practice to implement like that ?
EDIT 1 :
After the #GaryRussel response here is the resolution of my question:
For the RabbitListner I create an error handler :
#Configuration
public class RabbitErrorHandler implements RabbitListenerErrorHandler {
#Override public Object handleError(Message message, org.springframework.messaging.Message<?> message1, ListenerExecutionFailedException e) {
throw e;
}
}
Define the bean into a configuration file :
#Configuration
public class RabbitConfig extends RabbitConfiguration {
#Bean
public RabbitTemplate getRabbitTemplate() {
Message.addWhiteListPatterns(RabbitConstants.CLASSES_TO_SEND_OVER_RABBITMQ);
return new RabbitTemplate(this.connectionFactory());
}
/**
* Define the RabbitErrorHandle
* #return Initialize RabbitErrorHandle bean
*/
#Bean
public RabbitErrorHandler rabbitErrorHandler() {
return new RabbitErrorHandler();
}
}
Create the #RabbitListner with parameters where rabbitErrorHandler is the bean that I defined previously :
#Override
#RabbitListener(queues = "${rabbit.queue}"
, errorHandler = "rabbitErrorHandler"
, returnExceptions = "true")
public ReturnObject receiveMessage(Message message) {
For the RabbitTemplate I set this attribute :
rabbitTemplate.setMessageConverter(new RemoteInvocationAwareMessageConverterAdapter());
When the messsage threated by the consumer, but it sent an error, I obtain a RemoteInvocationResult which contains the original exception into e.getCause().getCause().
See the returnExceptions property on #RabbitListener (since 2.0). Docs here.
The returnExceptions attribute, when true will cause exceptions to be returned to the sender. The exception is wrapped in a RemoteInvocationResult object.
On the sender side, there is an available RemoteInvocationAwareMessageConverterAdapter which, if configured into the RabbitTemplate, will re-throw the server-side exception, wrapped in an AmqpRemoteException. The stack trace of the server exception will be synthesized by merging the server and client stack traces.
Important
This mechanism will generally only work with the default SimpleMessageConverter, which uses Java serialization; exceptions are generally not "Jackson-friendly" so can’t be serialized to JSON. If you are using JSON, consider using an errorHandler to return some other Jackson-friendly Error object when an exception is thrown.
What worked for me was :
On "serving" side :
Service
#RabbitListener(id = "test1", containerFactory ="BEAN CONTAINER FACTORY",
queues = "TEST QUEUE", returnExceptions = "true")
DataList getData() {
// this exception will be transformed by rabbit error handler to a RemoteInvocationResult
throw new IllegalStateException("mon expecion");
//return dataHelper.loadAllData();
}
On "requesting" side :
Service
public void fetchData() throws AmqpRemoteException {
var response = (DataList) amqpTemplate.convertSendAndReceive("TEST EXCHANGE", "ROUTING NAME", new Object());
Optional.ofNullable(response)
.ifPresentOrElse(this::setDataContent, this::handleNoData);
}
Config
#Bean
AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
var rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
#Bean
MessageConverter jsonMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.registerModule(new JavaTimeModule());
var jsonConverter = new Jackson2JsonMessageConverter(objectMapper);
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = Map.of(
DataList.class.getName(), DataList.class,
RemoteInvocationResult.class.getName(), RemoteInvocationResult.class
);
classMapper.setIdClassMapping(idClassMapping);
jsonConverter.setClassMapper(classMapper);
// json converter with returned exception awareness
// this will transform RemoteInvocationResult into a AmqpRemoteException
return new RemoteInvocationAwareMessageConverterAdapter(jsonConverter);
}
You have to return a message as an error, which the consuming application can choose to treat as an exception. However, I don't think normal exception handling flows apply with messaging. Your publishing application (the consumer of the RPC service) needs to know what can go wrong and be programmed to deal with those possibilities.

How to correctly load Firebase ServiceAccount json resource with Spring MVC?

I'm trying to connect my Spring MVC (not Spring Boot) application to Firebase. My application's folder structure looks like this:
folder structure
The problem is that I don't know where to place the api key json file, how to load the resource, and the correct order of the method calls.
I tried loading the resource the way shown below. Before that I also tried using ClassLoader to load it from the WEB-INF folder and it worked, but changed the code and kept receiving NullPointer Exception (why not FileNotFound Exception?) for the InputStream and couldn't restore the previous state.
With the current state I keep receiving FileNotFound Exception as I'm am not able to load the resource no matter how much I googled "Spring MVC load resource" and as I checked the debugger the service account's "init" method with #PostConstruct isn't running at starting the server.
I understand that I should be able to load the resource and call the "init" method in order to make it work. (I suppose it's enough to call it once after creating the bean and before using firebase methods) But I just couldn't come up with a working implementation.
I used examples from here:
https://github.com/savicprvoslav/Spring-Boot-starter
(Bottom of the Page)
My Controller Class:
#Controller
#RequestMapping("/firebase")
public class FirebaseController {
#Autowired
private FirebaseService firebaseService;
#GetMapping(value="/upload/maincategories")
public void uploadMainRecordCategories() {
firebaseService.uploadMainRecordCategories();
}
My Service Class:
#Service
public class FirebaseServiceBean implements FirebaseService {
#Value("/api.json")
Resource apiKey;
#Override
public void uploadMainRecordCategories() {
// do something
}
#PostConstruct
public void init() {
try (InputStream serviceAccount = apiKey.getInputStream()) {
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.setDatabaseUrl(FirebaseStringValue.DB_URL).build();
FirebaseApp.initializeApp(options);
} catch (IOException e) {
e.printStackTrace();
}
}
}
how about saving value in a spring property and using #Value("${firebase.apiKey}")?
Alternatively, save path to file in property and reference that in #Value()
#Value("${service.account.path}")
private String serviceAccountPath;
In application.properties:
service.account.path = /path/to/service-account.json
then config code:
private String getAccessToken() throws IOException {
GoogleCredential googleCredential = GoogleCredential
.fromStream(getServiceAccountInputStream())
.createScoped(Collections.singletonList("https://www.googleapis.com/auth/firebase.messaging"));
googleCredential.refreshToken();
return googleCredential.getAccessToken();
}
private InputStream getServiceAccountInputStream() {
File file = new File(serviceAccountPath);
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new RuntimeException("Couldn't find service-account.json");
}
}

Apache HttpComponents CookieStore Not Storing Cookies

I'm using HttpComponents 4.5.2 and I'm trying to store cookies as I need to use them for login and other requests. The code works fine whilst the application is still running, but the problem here is when I restart it, the cookies that were supposed to be stored in CookieStore are not there. Here's what I've written:
public static void main( String[] args ) throws InterruptedException
{
RequestConfig globalConfig = RequestConfig.custom()
.setCookieSpec(CookieSpecs.STANDARD).build();
BasicCookieStore cookieStore = new BasicCookieStore();
HttpClientContext context = HttpClientContext.create();
context.setCookieStore(cookieStore);
CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom()
.setDefaultRequestConfig(globalConfig)
.setDefaultCookieStore(cookieStore)
.build();
httpclient.start();
login(httpclient, context);
}
public static void login(CloseableHttpAsyncClient httpClient, HttpClientContext context) throws InterruptedException
{
JSONObject json = new JSONObject("{ email : blahblahblah1, password : blahblahblah2 }");
StringEntity requestEntity = new StringEntity(
json.toString(),
ContentType.APPLICATION_JSON);
HttpPost postMethod = new HttpPost("http://localhost:8080/login");
postMethod.setEntity(requestEntity);
final CountDownLatch latch = new CountDownLatch(1);
httpClient.execute(postMethod, context, new FutureCallback<HttpResponse>() {
public void completed(final HttpResponse response) {
latch.countDown();
System.out.println(postMethod.getRequestLine() + "->" + response.getStatusLine());
//System.out.println(context.getCookieStore().getCookies().size());
}
public void failed(final Exception ex) {
latch.countDown();
System.out.println(postMethod.getRequestLine() + "->" + ex);
}
public void cancelled() {
latch.countDown();
System.out.println(postMethod.getRequestLine() + " cancelled");
}
});
latch.await();
}
I've read the HttpComponents documentation and the section 3.5 about cookies says:
HttpClient can work with any physical representation of a persistent cookie store that implements the CookieStore interface. The default CookieStore implementation called BasicCookieStore is a simple implementation backed by a java.util.ArrayList. Cookies stored in an BasicClientCookie object are lost when the container object get garbage collected. Users can provide more complex implementations if necessary
So I'm wondering if it's left to it's users to implement some kind of structure that can effectively store cookies or if I'm missing something.
Yes, using BasicCookieStore backed by ArrayList means that when your jvm exists, the data there is being lost just like any ArrayList in memory.
BasicCookieStore class also implements Serializable so you can use that to persist it to disk and restore back on your app startup if the file was there.
You can borrow some code from the tests verifying that flow TestBasicCookieStore#testSerialization.

Error with Null

I am working on a project here and am running into an issue. It was working fine before but what I am doing is sending a request to a url and am expecting a JSON response. However when I check the response it keeps coming back as null. This was not an issue before but randomly started happening. Any help would be greatly appreciate!'
Also I have another class that actually does the url requests.
This first bit is the onclick listener on the login page. It takes the text fields and converts them to strings. The way the login works is it has to push all the information into the URL which is why it is set up this way.
bt.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
String account = acct.getText().toString();
String uname = user.getText().toString();
String passWord = pass.getText().toString();
url = ap+"&t2mdeveloperid="+id+account+"&t2musername="+uname+"&t2mpassword="+passWord;
new Login().execute();
}
}
);
}
Alright here is the Async task that the request runs in.
private class Login extends AsyncTask<Void, Void, Void>{
#Override
protected void onPreExecute(){
super.onPreExecute();
//Here we will display our progress bar. This is dependent on each page.
pDialog = new ProgressDialog(MainActivity.this);
pDialog.setMessage("Loading Talk2M Account");
pDialog.setCancelable(true);
pDialog.show();
}
#Override
protected Void doInBackground(Void... arg0){
//We need to create an object to call our service handler to make our http request.
ServiceHandler login = new ServiceHandler();
//Now we need to make a request to the url as well as recieve the response.
String response = login.makeServiceCall(url, ServiceHandler.GET);
Log.d("Response: ", "> " + response);
try{
JSONObject jsonObj = new JSONObject(response);
String session = jsonObj.getString(TAG_SESSION);
}catch(JSONException e){
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result){
super.onPostExecute(result);
if(pDialog.isShowing())
pDialog.dismiss();
}
}
If the JSON you're getting back is null, my first guess would be to verify that you are hitting the right URL. If that is the case, then there may be an issue on the api side. You may want to test this HTTP request outside of the context of your application, in a REST client or web browser.
Regards.

Getting response status code 0 in SmartGWT webservice call using json

I have developed application using SmartGWT. Now I need to call webservice using json to another application which is deployed in another server for submitting username and password. When I make a request with url and POST method, getting the response status code as 0 and response text as blank.
Here is my code,
public void sendRequest() throws Exception {
// Get login json data to be sent to server.
String strData = createLoginReqPacket();
String url = "some url";
RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url);
builder.setHeader("Content-Type", "application/json");
builder.setHeader("Content-Length", strData.length() + "");
Request response = builder.sendRequest(strData, new RequestCallback() {
#Override
public void onResponseReceived(Request request, Response response) {
int statusCode = response.getStatusCode();
System.out.println("Response code ----"+response.getStatusCode()+"");
if (statusCode == Response.SC_OK) {
String responseBody = response.getText();
System.out.println("Respose :" + responseBody);
// do something with the response
} else {
GWT.log("Response error at server side ----",null);
// do in case of server error
}
}// end of method.
#Override
public void onError(Request request, Throwable exception) {
GWT.log("**** Error in service call ******",null);
}// end of method.
});
builder.send();
}// end of send request.
Please anybody knows the solution?
as i am new to GWT/SmartGwt i don't no much about it.
#ModeEngage
There is no reason to use GWT class(RequestBuilder), But i don't have any idea to use Data source. Can u give reference or stuffs to do this??
And when i run this in chrome browser i get the following in error console.
XMLHttpRequest cannot load http:// "requested Url" . Origin http:// "localhost:8888" is not allowed by Access-Control-Allow-Origin.
Any solutions???
I believe this is caused by Firewall. I've run some test, and this is the most likely explanation.