Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async functional tests #35

Merged
merged 2 commits into from
Sep 14, 2016
Merged

Async functional tests #35

merged 2 commits into from
Sep 14, 2016

Conversation

archolewa
Copy link
Contributor

Adds the functional tests for asynchronous queries.

In order to support this, I had to slightly modify the TestDruidServlet to have a Producer<String> that generates a fake Druid response on demand, rather than having the Druid response directly. This was needed to support delaying the Druid response, so that we could test the path where results aren't yet ready from the backend.

The base spec for the asynchronous queries is rather complex, and I still don't think I explained it well. Suggestions on improving the documentation for that test are especially welcome.

job.put(JOB_TICKET.getName(), jobRow.get(JOB_TICKET));
job.put(DATE_CREATED.getName(), jobRow.get(DATE_CREATED));
job.put(DATE_UPDATED.getName(), jobRow.get(DATE_UPDATED));
job.put(USER_ID.getName(), jobRow.get(USER_ID));
Copy link

@venisa venisa Sep 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I am having a 'Why didn't I do this moment'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's almost always a better way to do something. That's why I love this review process, I get smarter by watching other people's work.

@venisa
Copy link

venisa commented Sep 8, 2016

I just had a bunch of silly comments about new lines and whitespace. We should probably update the Javadoc for AsyncResultsReadySpec to say result 'SUCCESS' instead of result 'READY'. But apart from that 👍

@@ -139,15 +143,28 @@ abstract class BaseDataServletComponentSpec extends Specification {
GroovyTestUtils.compareJson(result, expectedResult)
}


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra line

@dayamr
Copy link
Collaborator

dayamr commented Sep 13, 2016

Apart from new line and extra line comments, it looks good to me. 👍


@Override
Map<String, Closure<String>> getResultsToTargetFunctions() {
return [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return not needed

/NotABlocker


@Override
Map<String, Closure<Void>> getResultAssertions() {
return [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return not needed

/NotABlocker


@Override
Map<String, Closure<Map<String, List<String>>>> getQueryParameters() {
return [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return not needed

/NotABlocker

]},
jobs: {[:]},
results: {[:]},
syncResults: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ordering of this map should (for clarity at least) match the ordering of the other closure maps.

* @param <T> The type to use for keys into the maps containing test-specific resources
*/
@Timeout(30)
abstract class AsyncFunctionalSpec extends Specification {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've not actually made this class generic, though the JavaDoc seems to indicate as such.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes. Originally it was generic. Then I decided that allowing the user to provide some arbitrary type for keys just made a (already very abstract) class more abstract than it needed to be.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled

JerseyTestBinder jtb

/**
* Returns a map of closures, each of which transforms a map of named Responses into request targets.
Copy link
Collaborator

@cdeszaq cdeszaq Sep 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Giving the expected method signature of these Closures (since the Closure type interface doesn't indicate it) would be good, I think. Based on this JavaDoc, it looks like it might be something like Map<String, Response> -> requestTarget, but it isn't clear.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this "give us the signature" sentiment applies to the rest of these Closure maps.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled

@archolewa archolewa force-pushed the async-functional-tests branch from e03ce89 to 8d46e20 Compare September 14, 2016 18:59
* The key for each query parameter generating closure must correspond to the key of the associated target
*
* @return The map of query parameters for each target.
*
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blank comment line not needed

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled

int statusCode = getDruidStatusCode()
jtb.nonUiDruidWebService.statusCode = statusCode
if (statusCode == 200) {
jtb.nonUiDruidWebService.jsonResponse = druidResponse
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be druidResponse.call()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. The jsonResponse in the DruidWebService is now a Function, not a String in order to support delaying the response.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, makes sense. Need changelog entry for this.

queryParameters.get(interactionName)(previousResponses)
)
response.bufferEntity()
resultValidations[interactionName](response)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments in here around what's happening would be very helpful, especially for those less familiar with what calling functions retrieved via map access or method calls looks like (ie. myMap[foo](bar) or myMap.get(foo)(bar))

Copy link
Collaborator

@cdeszaq cdeszaq Sep 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we should use 1 map access style: either the index accessor as used on this line, or the getter as used 3 lines up. I like index access, personally, but we should use just one.

)
response.bufferEntity()
resultValidations[interactionName](response)
previousResponses.put(interactionName, response)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also use index accessor to "set" it, if you opt for that style of access

@Override
Map<String, Closure<Void>> getResultAssertions() {
return [
data: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like having comments in here indicating the scenario and what the assertion is expecting would help aid in understanding both what behavior is being tested for, as well as what the queries are for which the behavior is expected.


@Override
Closure<String> getFakeDruidResponse() {
return {"I will never leave this curly prison."}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having to override a method when we don't care about the response, does it make sense to have the method be non-abstract with a default Druid response of an empty object, or list or something?

Copy link
Contributor Author

@archolewa archolewa Sep 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I think that depends on how often we expect to have tests that ignore the results from Druid. I don't like the idea of an exceptional case being the default. Every test that I've written except for this one do interact with Druid, so it's exceptional in this case, at least.

Now that I'm done using fancy speak that makes me sound like a craftsman, the real reason is that I'm lazy, and one nice thing about making it abstract is that with a few keystrokes in IntelliJ I can automatically generate method signatures for all the abstract methods. If I make this non-abstract, I can generate all the methods but one that I need to override, and then need to generate the getFakeDruidResponse method signature as a separate step.

Not a big deal, but I find it mildly annoying when overriding the BaseDataServletComponentSpec, and I figured that overriding the Druid responses would be common enough to justify making it abstract to save myself a few keystrokes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, I'm OK leaving it as abstract

import javax.ws.rs.core.Response

/**
* Verifies that if the result of an asynchronous query is not ready yet, then the user get back the exact same payload
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/get/gets

jobs: {AsyncTestUtils.buildTicketLookup(it.data.readEntity(String))},
results: {payloads ->
String dataPayload = payloads.data.readEntity(String)
"jobs/${AsyncTestUtils.getJobFieldValue(dataPayload, DefaultJobField.JOB_TICKET.name)}/results"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment here around what's being looked at would be good.

}

/**
* Given a String representation of the job information sent to the user, returns the URI path for a point lookup of
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/String/JSON

@@ -138,7 +138,7 @@ class ErrorDataServletSpec extends Specification {
setup:
DruidServiceConfig oldConfig = nonUiTestWebService.serviceConfig
nonUiTestWebService.serviceConfig = new DruidServiceConfig("Non-Ui Broker", oldConfig.url, 123, oldConfig.priority)
nonUiTestWebService.jsonResponse = standardGoodDruidResponse
nonUiTestWebService.jsonResponse ={standardGoodDruidResponse}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whitespace needed

@@ -39,7 +40,7 @@
);
private static ObjectWriter writer = mapper.writer();

public String jsonResponse = "[]";
public Producer<String> jsonResponse = () -> "[]";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely need Changelog entry for this, since it's a breaking change for anyone trying to use this.

.collectEntries { parameter ->
def (String key, String value) = parameter.tokenize("=")
return [(key): value.tokenize(",")]
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to extract this bit as a helper method as well? I think I saw it at least 2 places in this PR and it's not the most easy-to-understand code under the sun.

int statusCode = getDruidStatusCode()
jtb.nonUiDruidWebService.statusCode = statusCode
if (statusCode == 200) {
jtb.nonUiDruidWebService.jsonResponse = druidResponse
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, makes sense. Need changelog entry for this.

SAME AS ABOVE
*/

final CountDownLatch validationFinished = new CountDownLatch(3)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@archolewa archolewa force-pushed the async-functional-tests branch from 41d8dc5 to 31cb74d Compare September 14, 2016 21:15
Thread.sleep(30000)
//We don't want the backend to return until all the validation has been performed, to simulate the backend
//taking a while to respond.
validationFinished.await()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful.

Copy link
Collaborator

@cdeszaq cdeszaq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Just needs to be rebased, squashed, and (assuming tests pass), merged

@cdeszaq
Copy link
Collaborator

cdeszaq commented Sep 14, 2016

Also, not sure why, but GitHub's PRs seem to be rather borked today. Everything I've commented on has either been addressed (though it doesn't show it), or was mitigated via comments.

@archolewa
Copy link
Contributor Author

Yeah, I've noticed that too. It looks like there have been some changes to the UI since last I reviewed, so it's likely they've got some bugs because of their changes.

--Rather than storing the fake druid response as a JSON string, instead,
the TestDruidWebService stores a `Producer` that generates the JSON
string on demand. This allows tests to introduce delays (or perform
other tasks) before returning the druid response. Quite useful when you
want to test what happens when the results are not ready in an
asynchronous query.
@archolewa archolewa force-pushed the async-functional-tests branch from 31cb74d to b413b63 Compare September 14, 2016 21:44
--Also added a few missing fields (`userId` and `dateUpdated`) to the
job payload.
@archolewa archolewa force-pushed the async-functional-tests branch from b413b63 to f32a541 Compare September 14, 2016 22:03
@archolewa archolewa merged commit f32a541 into master Sep 14, 2016
@archolewa archolewa deleted the async-functional-tests branch September 14, 2016 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants