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

Add JSON Payload coverage tests for REST endpoints #1890

Merged
merged 1 commit into from
Jul 24, 2024

Conversation

jcarranzan
Copy link
Contributor

@jcarranzan jcarranzan commented Jul 17, 2024

Summary

This PR adds coverage tests to the http/http-advanced-reactive Quarkus module to ensure proper handling of JSON data in REST endpoints. Test scenarios include:

  • Successful processing
  • Large, complex payloads
  • Invalid JSON files
  • Missing or incorrect data types
  • Verify appropriate errors handling

For more detailed info see the test plan for details: PR-179

Please select the relevant options.

  • Bug fix (non-breaking change which fixes an issue)
  • Dependency update
  • Refactoring
  • Backport
  • New scenario (non-breaking change which adds functionality)
  • This change requires a documentation update
  • This change requires execution against OCP (use run tests phrase in comment)

Checklist:

  • Methods and classes used in PR scenarios are meaningful
  • Commits are well encapsulated and follow the best practices

@jcarranzan jcarranzan force-pushed the QQE-379-JSON-payloads branch from 1472260 to 9e807be Compare July 17, 2024 11:05
@jcarranzan jcarranzan requested a review from jedla97 July 17, 2024 11:08
@jcarranzan jcarranzan force-pushed the QQE-379-JSON-payloads branch from df1bf77 to 50612a1 Compare July 17, 2024 11:35
Copy link
Member

@jedla97 jedla97 left a comment

Choose a reason for hiding this comment

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

I have looked at this and have some comments what can be improved.

@jedla97
Copy link
Member

jedla97 commented Jul 17, 2024

I also looked on the TP and see that for large JSON you want to use POST and GET. Will be GET method added or should be TP updated?

@jedla97
Copy link
Member

jedla97 commented Jul 17, 2024

Also one more question. In TP you state you want test it with quarkus-rest-jackson and quarkus-rest-jsonb how you want to test it with both? Or what's the plan?

@jcarranzan jcarranzan force-pushed the QQE-379-JSON-payloads branch 4 times, most recently from 633647c to 9d1c50e Compare July 19, 2024 06:45
@jcarranzan jcarranzan requested a review from jedla97 July 19, 2024 06:46
@jcarranzan
Copy link
Contributor Author

I also looked on the TP and see that for large JSON you want to use POST and GET. Will be GET method added or should be TP updated?

Currently is added to the PersonResource and it's used, thanks.

@jcarranzan
Copy link
Contributor Author

Also one more question. In TP you state you want test it with quarkus-rest-jackson and quarkus-rest-jsonb how you want to test it with both? Or what's the plan?

I've used the approach we talked about, so currently it's tested with both libraries. Thanks.

@jcarranzan jcarranzan marked this pull request as ready for review July 19, 2024 12:45
@jcarranzan jcarranzan force-pushed the QQE-379-JSON-payloads branch 3 times, most recently from 9d432f9 to dd80888 Compare July 19, 2024 13:26
@jcarranzan
Copy link
Contributor Author

run tests

Copy link
Member

@jedla97 jedla97 left a comment

Choose a reason for hiding this comment

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

I review it and it's look good. Just have few small suggestion.

@jedla97
Copy link
Member

jedla97 commented Jul 19, 2024

Also jsut a note before merge I would like to see full CI run. ATM it's blocked because problem with releasing new framework version.

@jcarranzan jcarranzan force-pushed the QQE-379-JSON-payloads branch from 45f0aff to 5697363 Compare July 20, 2024 07:14
Copy link
Member

@jedla97 jedla97 left a comment

Choose a reason for hiding this comment

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

I add final comment.
Also can you rebase it to main as the latest framework bump was merged. So we can see tests.

@jcarranzan jcarranzan force-pushed the QQE-379-JSON-payloads branch 2 times, most recently from 9e3a389 to 15e5924 Compare July 24, 2024 06:08
@jcarranzan jcarranzan force-pushed the QQE-379-JSON-payloads branch from 1cdb27e to 9fd51dd Compare July 24, 2024 07:31
Copy link
Member

@jedla97 jedla97 left a comment

Choose a reason for hiding this comment

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

Thanks for the update. It's look good to me. Hopefully CI will be fine!

@jcarranzan
Copy link
Contributor Author

run tests

1 similar comment
@jedla97
Copy link
Member

jedla97 commented Jul 24, 2024

run tests

@jedla97 jedla97 merged commit ca22d18 into quarkus-qe:main Jul 24, 2024
10 checks passed
Copy link
Member

@michalvavrik michalvavrik left a comment

Choose a reason for hiding this comment

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

@mjurc can you look at the FootballTeamResource and PersonResource and tell me if this implements quarkus-qe/quarkus-test-plans#179 ?

Because if so, IMHO the test plan was wrong.

My opinion:

  • this is using Jackson library (not an extension!) everywhere, newly added tests (maybe with consumes/produces annotations) would work without rest-jackson and rest-jsonb extensions anyway
  • it is instantiating ObjectMapper instead of using managed one
  • it serializes and deserializes everything part of the things manually
  • so when test plan says https://github.com/quarkus-qe/quarkus-test-plans/blob/main/QQE-379.md?plain=1#L40 that you expect 400 for invalid paylod, well, these resources set 400 response for invalid payload, so of course you are going to receive it? what exactly is it testing, that Response object works?
  • I expect users to actually use message writers/readers instead of doing this manually, if we want to test com.fasterxml.jackson then it should be clear this is what these tests are about, not something like JsonBPayloadIT etc.


@Test
public void shouldPickJackson() {
assertTrue(app.logs().forQuarkus().installedFeatures().contains(REST_JACKSON));
Copy link
Member

@michalvavrik michalvavrik Jul 24, 2024

Choose a reason for hiding this comment

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

Isn't shouldPickJackson testing that rest-jackson feature is present? If you compare logs of JsonJacksonPayloadIT:

2024-07-24T08:07:28.0863296Z 08:07:28,083 INFO  [app] 08:07:26,210 Installed features: [cdi, keycloak-authorization, micrometer, oidc, reactive-routes, rest, rest-client, rest-jackson, rest-jaxb, rest-jsonb, security, smallrye-context-propagation, smallrye-health, smallrye-openapi, vertx]

with JsonBPayloadIT logs:

2024-07-24T08:07:19.4630743Z 08:07:19,461 INFO  [app] 08:07:17,767 Installed features: [cdi, keycloak-authorization, micrometer, oidc, reactive-routes, rest, rest-client, rest-jaxb, rest-jsonb, security, smallrye-context-propagation, smallrye-health, smallrye-openapi, vertx]

but both of these tests have rest-jsonbfeature present as well.

Based on what can you determine that these 2 tests are using different serializers? Let alone that you are not actually using these extensions to serialize/deserialize payload, but that's a different story.

Copy link
Member

@jedla97 jedla97 Jul 25, 2024

Choose a reason for hiding this comment

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

When reviewing this I didn't realized that @Dependency(artifactId = "quarkus-" + REST_JACKSON) add it but the extension defined in pom stay there as well (probably that I was looking at GreetingResourceIT and didn't look at the pom there).

Based on what can you determine that these 2 tests are using different serializers?

Now I can say we can't say for sure. As I described above I was thinking that there is only that one extension.

Copy link
Member

Choose a reason for hiding this comment

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

I think it still can be sure that Quarkus REST Jackson serializers have a higher priority, but I simply don't know it and we don't assert it, therefore we need a way to assert it. Thank you

return Uni.createFrom()
.item(() -> {
Map<String, Object> responseMap = new HashMap<>();
responseMap.put("teams", footballTeamSet);
Copy link
Member

Choose a reason for hiding this comment

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

here I can see object that is going to be serialized, I missed that one before and I am going to correct my comment above. @jedla97 @jcarranzan how do you know whether Jackson / JsonB serializers are going to be used?

if (personSet.isEmpty()) {
return Uni.createFrom().item(Response.status(Response.Status.NOT_FOUND).build());
} else {
return Uni.createFrom().item(Response.ok(personSet).build());
Copy link
Member

@michalvavrik michalvavrik Jul 24, 2024

Choose a reason for hiding this comment

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

@jedla97 @jcarranzan I also missed this one, apologizes. How do you know which serializer if going to be used? Jackson or JsonB?

@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Uni<Response> processJson(String jsonData) {
Copy link
Member

Choose a reason for hiding this comment

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

@jcarranzan @jedla97 TP https://github.com/quarkus-qe/quarkus-test-plans/blob/main/QQE-379.md?plain=1#L31 says Deserialization: Ensure accurate conversion of JSON payloads, that it's performed using the configured JSON libraries (quarkus-rest-jackson, quarkus-rest-jsonb).

I don't see any deserialization performed by quarkus-rest-jackson or quarkus-rest-jsonb. AFAICT it is always performed manually. Am I wrong?

@michalvavrik
Copy link
Member

@jcarranzan @jedla97 I have corrected my original comment as I can see that serialization is partially tested, I apologize. Still, I am unhappy with this PR. Please have a look at my comments and answer. I suppose it can be determined from writers/readers priority which one is picked, but you actually don't test that. I think the way to test this is to actually fail serialization/deserialization and inspect stacktrace in the exception mapper.

}

@Test
public void sendSpecialCharactersJSONPayload() throws Exception {
Copy link
Member

Choose a reason for hiding this comment

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

This test is sending plain text (it is a String) via client, received String in REST endpoint. Special characters or not, you are testing your own instance of ObjectMapper.

As far as extensions deserializers are concerned - none used
As far as extension serializers are concerned - teams should be serialized, but that is not tested at all, what is tested is that text containes Teams added successfully. So you are not testing whether special characters are handled properly by serializers, but you are testing that no exception is raised. If there was any encoding issue, you wouldn't know.

/**
*
* When a JSON extension is installed such as quarkus-rest-jackson or quarkus-rest-jsonb
* Quarkus will use the application/json media type by default for most return values
Copy link
Member

Choose a reason for hiding this comment

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

This comment is true https://quarkus.io/guides/rest#declaring-endpoints-representation-content-types however I can't see relation to this resource as there is just one endpoint that consumes/produces something else than HTTP status (no entity), that is uploadFootballJson and there, you explicitly delcare these annotations.

So, why to state what is default if you override it and don't test it?

@mjurc
Copy link
Member

mjurc commented Jul 24, 2024

@mjurc can you look at the FootballTeamResource and PersonResource and tell me if this implements quarkus-qe/quarkus-test-plans#179 ?

Because if so, IMHO the test plan was wrong.

My opinion:

* this is using Jackson library (not an extension!) everywhere, newly added tests (maybe with consumes/produces annotations) would work without rest-jackson and rest-jsonb extensions anyway

* it is instantiating `ObjectMapper` instead of using managed one

* it serializes and deserializes ~everything~ part of the things  manually

* so when test plan says https://github.com/quarkus-qe/quarkus-test-plans/blob/main/QQE-379.md?plain=1#L40 that you expect 400 for invalid paylod, well, these resources set 400 response for invalid payload, so of course you are going to receive it? what exactly is it testing, that `Response` object works?

* I expect users to actually use message writers/readers instead of doing this manually, if we want to test `com.fasterxml.jackson` then it should be clear this is what these tests are about, not something like `JsonBPayloadIT` etc.

This is definitely a correct assessment, as in we don't support standalone libs added to Quarkus. This is not an extension test. We need to add integration tests rather than unit tests for the library.

@jcarranzan
Copy link
Contributor Author

Ok, thank you @michalvavrik for your question points, I'll do a refactor of these tests.

@michalvavrik
Copy link
Member

Ok, thank you @michalvavrik for your question points, I'll do a refactor of these tests.

Cool, let's give me 10 more minutes, I'll add few comments that you can consider. It is optional.


private static final Logger LOG = Logger.getLogger(FootballTeamResource.class);

private Set<FootballTeam> footballTeamSet = Collections.synchronizedSet(new LinkedHashSet<>());
Copy link
Member

Choose a reason for hiding this comment

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

flow is:

  1. String data is deserialized with Set<FootballTeam> teamSet = objectMapper.readValue(data, new TypeReference<Set<FootballTeam>>() {});
  2. you have Set<FootballTeam> teamSet
  3. then you do validation (that part is ok)
  4. then you copy it into a class member synchronized set
  5. then you use the synchronized set to return the response
  6. then you have separate call that deletes a football team set

Things that I don't understand:

  • it's synchronized but there is no concurrency, you never call it more than once at time
  • you copy teamSet into footballTeamSet that is kept outside of /upload-football-json, but you never use it; instead you only use it to "delete it"; what is the point of copying it into the footballTeamSet
  • you need to manage footballTeamSet but it's not useful for you

} catch (JsonProcessingException e) {
LOG.error(e.getMessage());
return Uni.createFrom().item(
Response.status(400).entity("Invalid JSON - JsonProcessingException : " + e.getOriginalMessage()).build());
Copy link
Member

Choose a reason for hiding this comment

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

IMO your intention is to see that Quarkus responds with some response status. I say some, because I suspect that it is going to be 500 unless you customize that exception. For which reason, I think when test plan says HTTP Status Code: Expect 400 Bad request for invalid payloads. that is wrong expectation.

The point is, you know that you are able to set a response status based on business logic (like invalid argument), but this PR is verifying JSON payload handling and that is different then testing feature that allows you to set a response status.

this.favoriteFruit = favoriteFruit;
}

public String get_id() {
Copy link
Member

Choose a reason for hiding this comment

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

if you verify a JSON payload, you are probably interested in commonly used features like ignored field etc. Anyway, that's a side note. Why is this called get_id? I wonder into which field is it going to be turned by a serializer. Is it really going to be id field? You might want to check.

.post("/football/upload-football-json")
.then()
.statusCode(CREATION_STATUS_CODE)
.body(containsString("Teams added successfully"));
Copy link
Member

Choose a reason for hiding this comment

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

None of tests in this test class is testing that response was serialized correctly. You don't test that JSON response is valid, just that response contains some string. Additionally,if tests are sending special chars, you might want to check that they are serialized/deserialized correctly.

@michalvavrik
Copy link
Member

Done, I don't have more comments, you can start working on this. Please make sure that @jedla97 is a reviwer as he knows requirements best and @mjurc is OOO. Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants