-
Notifications
You must be signed in to change notification settings - Fork 96
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
Conversation
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)); |
There was a problem hiding this comment.
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'
There was a problem hiding this comment.
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.
I just had a bunch of silly comments about new lines and whitespace. We should probably update the Javadoc for |
@@ -139,15 +143,28 @@ abstract class BaseDataServletComponentSpec extends Specification { | |||
GroovyTestUtils.compareJson(result, expectedResult) | |||
} | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra line
Apart from new line and extra line comments, it looks good to me. 👍 |
a2142cb
to
e03ce89
Compare
|
||
@Override | ||
Map<String, Closure<String>> getResultsToTargetFunctions() { | ||
return [ |
There was a problem hiding this comment.
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 [ |
There was a problem hiding this comment.
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 [ |
There was a problem hiding this comment.
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: { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handled
e03ce89
to
8d46e20
Compare
* 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. | ||
* |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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()
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)
)
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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: { |
There was a problem hiding this comment.
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."} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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 = () -> "[]"; |
There was a problem hiding this comment.
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(",")] | ||
} |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
41d8dc5
to
31cb74d
Compare
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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beautiful.
There was a problem hiding this 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
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. |
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.
31cb74d
to
b413b63
Compare
--Also added a few missing fields (`userId` and `dateUpdated`) to the job payload.
b413b63
to
f32a541
Compare
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.