-
Notifications
You must be signed in to change notification settings - Fork 29
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
Added unit test to usage metrics endpoints #1133
Conversation
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.
Not completed done with my review but like to hear from you on my comments before moving on further. Thanks!
@@ -35,6 +35,7 @@ public final class Constants { | |||
|
|||
public static final String MONTH_USERS_USAGE_SUMMARY_FILE_PREFIX = "public-website/usage-analysis/month-user-summary_"; | |||
public static final String YEAR_USERS_USAGE_SUMMARY_FILE_PREFIX = "public-website/usage-analysis/year-user-summary_"; | |||
public static final String MONTH_RESOURCES_USAGE_SUMMARY_FILE_PREFIX = "public-website/usage-analysis/year-resource-summary_"; |
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.
Do you mean to use month-resource-summary_
?
public final X first; | ||
public final Y second; |
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.
Give some meaningful names here? It's used by this test anyway, may as well just use lastKey and jsonObject?
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.
It's use by the logic that creates the mock json file, but I'll change it to KeyValuePair
src/test/java/org/mskcc/cbio/oncokb/web/rest/UsageAnalysisControllerIT.java
Show resolved
Hide resolved
} | ||
|
||
private User createMockUser( | ||
int userIndex, |
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.
Nit picking, should this be userId?
user.setFirstName(String.valueOf(userName.charAt(userName.length() - 1))); | ||
user.setLastName(userName.substring(0, userName.length() - 1)); |
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 plan to make a comment about the logic of parsing username to F/L. But I guess it doesn't matter because we don't really care how user is named?
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.
It doesn't matter too much, but the way I have the user name setup is last name ending with the first initial. So doej
break down into doe
and j
.
The only reason why it sort of matter is that if the data is too random then it can be challenging to debug a test so I wanted to have realistic names.
return userDto; | ||
} | ||
|
||
private CompanyDTO createMockCompany(int companyIndex, String companyName) { |
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.
private CompanyDTO createMockCompany(int companyIndex, String companyName) { | |
private CompanyDTO createMockCompany(int companyId, String companyName) { |
return sampleUrls[Math.abs(random.nextInt() % sampleUrls.length)]; | ||
} | ||
|
||
private Tuple<String, JsonObject> fetchJsonObjectFromFilesObject( |
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'm a bit confused about this method. This is to fetch the last key json but backfill all keys if they do not exist in obj? Maybe add some comments here?
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.
That is correct, but I'll add some comments.
} | ||
|
||
// update expected response for user usage endpoint | ||
if (isThisYear) { |
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.
@zhx828 I know limiting the usage metrics to only "this" year isn't what we want, but it is currently how the code works.
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 expect to change this after finishing "3 years" requirement to make the test pass?
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.
Yes
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.
All places where you do keys[keys.length - 1]
, you should expect out of bound exception. I left a comment asking whether this is your intention. If not, we need to handle it. Out of bound exception is not the best way to show why the test fail I suppose?
And thank you for wrapping logics into methods, so much easier to review!
* @param value The value that is going to be added to value located in the JSON path. | ||
* @param filePath First key in the files object. | ||
* @param index Array index of the array the {@code filePath} parameter points to. |
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.
These should be deleted I suppose.
* Get value from files JSON object based on the JSON path created by the | ||
* {@code filePath}/[{@code index}]/{@code keys}. | ||
* If an object is missing in the JSON path then it's created. | ||
* @param filePath First key in the files object. |
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.
Is the description for filePath accurate?
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.
Yes, I'll rename it from filePath
to firstKey
} | ||
obj = (JsonObject) obj.get(key); | ||
} | ||
String lastKey = keys[keys.length - 1]; |
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.
Do you intend to let the test fails by throwing out of bound exception when no key been added to this method?
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.
It shouldn't happen since this code is just generating the mock data. I don't think people will touch this code very often, but I'll update the methods so that this exception cannot occur.
|
||
for (int i = 0; i < keys.length - 1; i++) { | ||
String key = keys[i]; | ||
if (!obj.has(key)) { |
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.
When key doesn't exist, should we directly return 0 instead of continuing?
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.
Basically I'm doing the equivalent of a mkdir -p
here. I want to make sure the entire path exists and also get the value from the path.
|
||
String yearKey = String.valueOf(year); | ||
|
||
for (int month = 1; month <= 12; month++) { |
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.
Interesting testing stratagy. I'm not testing expert. But would dynamic mock data introduces unpredictability?
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.
Yes, but I'm hard coding the seed for the random number generator so the data is always the same unless the calls to the random number change which would only happen if we make changes to the mock data.
I thought about making just a bunch of JSON files, but I felt like this would be easier to make changes to.
That said, I could switch this to reading a bunch of JSON files instead of dynamically generating the mock data if you feel it's better to maintain.
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 data is not always the same because you also use today
as timestamp. The file will be different every day. Is that ok given the use case here? If it's ok, fine to me to keep the current logic.
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 forgot about that part, but since the endpoint logic is dependent on today's date there isn't a way I can think of that wouldn't require changes to UsageAnalysisController
. You'd have to mock out all library calls to java.time
.
I don't think it's worth mocking that logic since this will only shift the data daily.
In my experience randomness is only worth addressing if it makes debugging tests harder or the tests randomly fail but failure cannot be reproduced.
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 figured it out. I can use the built-in java clock object.
* @param keys The keys in JSON path | ||
* @return The resulting {@code KeyValuePair} | ||
*/ | ||
private KeyValuePair<String, JsonObject> fetchJsonObjectFromFilesObject( |
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.
Two fetchJsonObjectFromFilesObject methods have the same name and same return but produce completely different JSONObject, one with last index included and set the initial value and one without last index.
Can we maybe update the comment and even the method name?
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'll remove the logic to set the initial value to avoid confusion.
if (!obj.has(lastKey)) { | ||
obj.addProperty(lastKey, 0); | ||
} |
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.
Why isn't this part of fetchJsonObjectFromFilesObject
? The other fetchJsonObjectFromFilesObject
method does have it.
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.
It's not really needed in this method so I'll remove it to avoid confusion.
* @param value Sets the specified value at the given JSON path. | ||
* @param keys The keys in JSON path. | ||
*/ | ||
private void safeSetNestedValueInFilesObject(String value, String... keys) { |
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 added safe
here. Do you imply a logic which could be not safe?
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 called it safe
because if there are missing objects then they are created for you instead of an exception being thrown.
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'll change fetchJsonObjectFromFilesObject
to safeFetchJsonObjectFromFilesObject
String usageResourcesEndpoint = "/api/usage/resources"; | ||
safeAddNestedValueInFilesObject( | ||
value, | ||
usageResourcesEndpoint + "?endpoint=" + oncokbEndpoint, |
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.
This can be a constant within this method.
} | ||
|
||
@Test | ||
public void shouldGetUsageSummaryResources() throws Exception { |
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.
Am I correct to say these tests are to test whether the endpoints will return previously generated files properly?
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.
Yeah they're to test what the current behavior of the endpoints are.
*/ | ||
private KeyValuePair<String, JsonObject> safeFetchJsonObjectFromFilesObject( | ||
String firstKey, | ||
int index, |
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.
Is it accurate to say this method is safeFetchJsonObjectFromFilesJsonArray
? Also do you assume all values of restOfTheKeys are JsonObject? Is there a way we can include number as key in restOfTheKeys and get JsonArray when the key is number? A bit risky to assume that I suppose.
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.
Kind of. It's assuming the first key points to an array of JSON objects.
Yes, it is assumed that the rest of the values are JSON objects.
It is risky to assume there will be an array, but it's only being used for this test code. The class is also scoped to just this test class so there isn't a risk someone will use it outside of this class unless they change the code. I don't it's worth making this code more generic if this is the only place it's used.
If we want absolute type safety then I should switch to classes instead of building out JSON.
for (int i = 0; i < restOfTheKeys.length - 1; i++) { | ||
String key = restOfTheKeys[i]; | ||
if (!obj.has(key)) { | ||
obj.add(key, new JsonObject()); | ||
} | ||
obj = (JsonObject) obj.get(key); | ||
} | ||
String lastKey = restOfTheKeys.length == 0 | ||
? firstKey | ||
: restOfTheKeys[restOfTheKeys.length - 1]; | ||
return new KeyValuePair<>(lastKey, obj); |
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.
This is the dup of 263-275
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.
Left few comments.
|
||
@Bean | ||
public Clock clock() { | ||
return Clock.systemDefaultZone(); |
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.
Can we make sure this is UTC, if not we should make it be.
I created a private
MockS3Data
class with afiles
property which generates and stores the mock JSON S3 files and the expected responses from all the endpoints. I hard coded a seed for the random number generator so that the tests always have the same data passed to them which should make it easier to debug any breaks.I made the class private since it seemed unnecessary to create file for a class that is only needed once.
I think I may have caught some bugs with some endpoints, but I opted to maintain the same behavior so the current expected response data may not look quite right. My goal is to make it obvious what the behavior changes are when I make the 3 year timeline change in
UsageAnalysisController
.