Closed (fixed)
Project:
JSON:API
Version:
8.x-1.x-dev
Component:
Code
Priority:
Major
Category:
Task
Assigned:
Issue tags:
Reporter:
Created:
15 Feb 2018 at 17:00 UTC
Updated:
2 Apr 2018 at 14:59 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
wim leers#2945093: Comprehensive JSON API integration test coverage phase 3: test JSON API-specific use cases: related/relationship routes, includes and sparse field sets landed!
I think that a good starting point could be to move much of
\Drupal\Tests\jsonapi\Functional\JsonApiFunctionalTestintoResourceTestBase, to e.g. dotestGetCollection().Comment #3
wim leersNote that I do not intend to take this on for the time being. I think this is more in @e0ipso and @gabesullice's ball park — they're more deeply familiar with how these are supposed to work, which edge cases are worth testing, and so on.
Comment #4
e0ipsoThe challenge here is the word comprehensive. There are just too many combinations to be able to do it for everything.
I'd choose some good representatives of each case and call it tested. We probably have much of these in the existing tests, we need to vet those and move them over first.
@gabesullice would you agree as a first step?
Comment #5
wim leersInterpret as . It's impossible to test 100% of possible cases. For example,
ResourceTestBase::testPatchIndividual()does this:That's testing an artificial field by first removing a particular item, and then adding one item before and item after. It's not doing this for every possible field. It's not doing this for an actual field. It's testing the mechanics.
I think that for the JSON API-specific capabilities, testing the mechanics is the most important thing.
Exactly :) This sounds perfect!
Comment #6
gabesullice@e0ipso, I really like the design pattern @Wim Leers came up with for testing all available types (by extending from a base class). If we could replicate that pattern to ensure that we're not missing subtle edge cases, that would be ideal. But I agree that any first step must necessarily start with just one resource type as a representative case.
Comment #7
gabesulliceyoink!
Comment #8
wim leersGo Gabe! 🤘
Comment #9
gabesulliceHere is a really small start, very simple testing of sparse field sets. Not sure how it'll play with all the types, but it works for nodes :P so we'll see!
Comment #10
gabesulliceWhoops, that wasn't supposed to be in there.
Comment #13
wim leersRather than regenerating a URL from scratch, you can pass the
$urlthat the caller was already working with.Then you can just do
$url->setOption('query', …);.Less code!
This is not necessary: it already happened int he caller's code.
😍
Comment #14
gabesulliceThis simplifies document assertions by recursively sorting the inputs. This means object key order cannot be guaranteed (but JSON object order isn't guaranteed anyway and arrays aren't affected).
It also makes the suggestions @Wim Leers had in #13.
This is still a WIP... Needs Review is just for tests for now.
Comment #15
wim leersRather than adding this, I think we should update
assertResourceResponse(), shouldn't we?You're also of course right that
assertResourceResponse()is a misnomer, it should be renamed toassertDocumentResponse(). Same for the error one.Comment #16
wim leers(FYI: I think #15 can also happen in a follow-up — probably that's even better, because then this patch is only adding test coverage rather than refactoring existing test coverage. Just add a
@todoto the newassertDocumentResponse()!)Comment #17
gabesullice#16: I did try that for a while, but we have things like this outside
assertResourceResponse()that I was trying to simplify. However, I agree there's probably a simpler generalization here, I just haven't been able to find it yet. Added a todo, to do that :PComment #19
gabesullicePosting some more progress now that other things have landed. This adds testing of the
includequery parameter. This patch does not yet cover "nested" includes.This has a one-line change to prevent adding the `included` member when the `included` array would be empty. This is only possible when/if the included resource was removed for access reasons.
Comment #21
gabesulliceI haven't been able to reproduce any of those errors. Trying to upload another patch, because WTH.
Comment #22
gabesulliceBy "de-duping" on the error detail, we lose error information when the same error happens for multiple included entities. This makes sure that we keep them.
FWIW, I actually think we should be combining a lot of these into more informative single error messages, but we should make that decision explicitly in #2943176: Spec Compliance: when error object 'id' key exists, it contains the individual resource URI, instead of "a unique identifier for this particular occurrence of the problem". This just resolves the inconsistent error behavior between
includedandrelatedroutes (which should mirror each other).Many of the previous errors are due to the fact that the
relatedendpoint does not throw/return cacheable exceptions. However, cache data is kept for included items.These tests operate on the basis of included being a mirror of related routes. Therefore, when related routes don't return cache metadata, we can't assert that includes have correct cache metadata (even though they do!).
Comment #23
wim leersNice progress! And fewer failures :)
Comment #24
gabesulliceAlright, I finally was able to replicate those errors locally (when
xdebug.show_error_trace = 1in php.ini, exceptions get caught before they make it to the functional tests!).That helped me narrow the cause down to 2 known issues:
Once those are resolved, we'll be able to remove the try catch block and add
includetest coverage forBlockContentandCommententities.If all tests pass. Next up, `related` route test coverage!
Comment #25
gabesulliceWhoops, left a few debugging things in there.
Comment #28
wim leersThis looks GREAT!
Is this a pure simplification or a bugfix?
The intent here was that if you need to do sorting/deep analysis of the response body, that you pass
FALSEto skip testing of the response body here (i.e. skip this assertion).Then you can just write your own assertions after doing
$this->assertResourceResponse():Would you rather have it not work that way? I can live with that.
This means that the expected cache contexts may be a superset of what you get back. I'd much prefer to update
getExpectedCacheContexts(), so that we can again do the simple straight comparison, so that what a specific test is expecting, is also actually what the response contains!But then we should just remove this altogether, and update
to:
Can we catch a more specific exception and then not mark this test as skipped, but rather complete the remainder of the test coverage?
Why does one have its own test method, and the other is just tested as part of
testGetIndividual()?This means that if content entity types get more base fields, the test coverage would also be testing different things. Probably okay, but not ideal. Of course, testing every possible combination makes even less sense.
However, wouldn't a more particularly selected subset make more sense? E.g. not listing the
uuidfield here should still result in theidentry per the JSON API standard, but then would probably have to omit theattributes.uuidentry?❤️
❤️
Nit:
$relationship_field_names.❤️
❤️
Nit: s/cacheable_metadata/cacheability/
Comment #29
gabesullice1. Bugfix. See #22.
2. That's absolutely fine. I think I misinterpreted your remark in #15 "Rather than adding this, I think we should update assertResourceResponse(), shouldn't we?"
3. That's fine.
4. Yeah, as I said in #17 the whole document assertion thing requires me to take some time to think about it. I'll do that in the next patch.
5. Unfortunately, no. It's a PHP fatal error and it's just a raw exception. I agree that I should have kept the tests going. I intended to but then forgot. BUT... it shouldn't matter anymore because #2930217: getRelated() not working when related entity field is empty is already RTBC and will fix these tests!
6. See #25, that was a leftover.
7. You're correct, changing the base fields will change these tests. But that's okay. The "empty" and "all" tests make sure we never lose coverage for any particular field and that the absence of a field (like UUID) doesn't break the output. I just wanted to ensure that one could select a subset of fields. I'm not following your remark about UUID and ID (I think you might be forgetting that the ID lives outside the attributes key).
10/13. Will fix!
Comment #30
wim leers#29.7: I'm saying that if you specify the sparse fieldset
title,field_tags, that you should get:Note how
attributes.uuidis absent (because not listed in the sparse fieldset), even thoughidis/must be always included. So my point was that that is probably an edge case worth testing.Comment #31
gabesulliceAlright, this refactors the assertDocument thing. I did do recursive sorting in assertResourceResponse. It makes sense to have it just run for every assertion as long as you can assert an _empty_ body too (which I added). One should only care about order for JSON arrays, but they are not affected by recursiveKSort because PHP decodes them as indexed arrays (without string keys). Thus, their order will be preserved.
I think this should also address all other nits and concerns from #28 too.
Comment #33
gabesulliceThis is the primary difference here.
Comment #34
wim leersThis is a bit too much of a refactor to include in this patch IMO: it's touching pretty much all existing integration test coverage! Let's split this off into a separate issue, and we can get it committed very quickly there.
Nit: s/be not be/not be/ :)
Other than that: ❤️
Nit:
assertNull()❤️
This means every subclass can test different sets of sparse field sets. Handy for regression tests. Cool!
Comment #35
wim leers@gabesullice created #2949136: Refactor assertResourceResponse test method to be more flexible. for #34.1.
Comment #36
wim leers#2949136: Refactor assertResourceResponse test method to be more flexible. is in!
That should make this patch quite a bit smaller and simpler!
Comment #37
gabesulliceThis is the updated patch with #2949136: Refactor assertResourceResponse test method to be more flexible. in.
The interdiff is from #25 now, and ignores #31.
Tests should pass when #2930217: getRelated() not working when related entity field is empty lands.
After that, I think we should commit this if @Wim Leers and @e0ipso agree. That will unblock #2940342: Cacheability metadata on an entity fields' properties is lost.
Then I can add coverage for related routes and collections.
Comment #38
wim leers👍
Final review time!
Nit: s/Cache MISS/Dynamic Page Cache miss/
Nit: s/cache HIT/Dynamic Page Cache hit/
Note that this will only return the
accessiblerelationship fields!For example,
UserTestdoes not listrolesin\Drupal\Tests\jsonapi\Functional\UserTest::getExpectedDocument()because theGETtest coverage can be performed with just theaccess user profilespermission. To view therolesfield, one must haveadminister userspermission.This is fine, but it does mean we're not testing everything. Wanted you to be aware of that.
Theoretically, that also means this should be renamed to
$accessible_relationship_field_names.no accessible relationships
Love this variable name :P
s/of/on/
s/individual response/individual responses/
I think?
Rather than this work-around for the fact that PHP is a limited language where you don't have the equivalent of
pair, you could also use the\Drupal\jsonapi\ResourceResponsevalue object which allows you to pass exactly this kind of data :)Can this now not use
\Drupal\Tests\jsonapi\Functional\ResourceTestBase::assertSameDocument()?s/included/includes/
s/cache metadata/cacheability/
s/cacheable metadata/CacheableMetadata/
The annotation does not match.
static👍Nice!
Same as above: you could use
\Drupal\jsonapi\ResourceResponseto make it easier to pass this around!Comment #39
gabesullice1. ✔️
2. ✔️
3. Yes, that's an excellent point. I updated the field names and added a
@todoto add explicit coverage for that. I just did a manual test of this to be sure that we are currently doing this right. That won't stop a regression of course.4. ✔️
5. 😀
6.
s/of/on/✔️s/individual response/individual responses/Nope, but I've updated the comment to be more clear (hopefully).7. This was an excellent suggestion. ✔️
8. Yep! Good thought. We still need to do some "prep" before hand, like updating the self link, but we weren't even checking that before! Now we are :)
9. ✔️
10. ✔️
11. ✔️
12. ✔️
13. :)
14. :)
15. ✔️
Comment #41
gabesulliceForgot to update some annotation comments.
Comment #42
wim leersThis looks lovely now!
❓ In
::request()we have aPerhaps we want the same here? Not sure.
🤐 Supernit: this is technically complying with the coding standards, but is hugely distracting. We put this on a single line everywhere else. I think we should do the same here.
Comment #43
wim leersThat means we need a issue and an issue title+summary update. The phase 4 issue should also be added to #2931785: The path for JSON API to core.
Comment #44
gabesulliceThat seems burdensome. I think we can just have multiple commits to the same issue. I would not classify my work here as a complete "phase".
I spoke with @Wim Leers in chat about this and he said:
We can add comment numbers to our commits to help track down history :) so let's save ourselves a little overhead.
Comment #45
wim leersWFM!
Pinged @e0ipso because #2930217: getRelated() not working when related entity field is empty is blocking a commit of this issue, and this issue is blocking #2940342: Cacheability metadata on an entity fields' properties is lost.
Comment #46
e0ipso#2930217: getRelated() not working when related entity field is empty just landed. Un-postponing.
Comment #47
e0ipsoComment #48
gabesullice@e0ipso, thanks! Since you're active, will you take a look at this one too? If tests pass on #41, perhaps #42 could be addressed on commit if you have no other concerns.
Comment #51
e0ipsoGetting really close! Thanks for all the hard work gentlemen 🎊
Comment #52
wim leersNice, thanks! :)
Moving back to NW per #44 — @gabesullice is planning more work here :)
Comment #53
gabesulliceThe attached covers
relatedroutes. It should pass for all test classes except BlockContent and Comments. That is due to a known bug: #2940339: Port reference field support for non-empty entity reference fields not pointing to an entity from #2543726Therefore, moving this to postponed and blocked. I'll manually queue tests. Feel free to review, however :)
Comment #54
wim leersLooking great!
Seems like an accidental change?
❤️
This is of course the central part of this patch, and it takes some reading, but then it totally makes sense! 👏
Glad to see this being called out explicitly!
But I think you meant to add something like "So we must request the individual resource for each resource related identifier, so that we can construct the expected 'related' response."
s/modifcations/modifications/
Also: which modifications?
It seems that this method along with the
toResourceResponse()etc helper method really would benefit from being moved out of this base test class.Because this class is growing to an enormous size.
What do you think about moving them all to a
ResourceResponseTestTrait?"since it implies support for the route" -> not sure how to interpret this. Could you try to clarify that comment?
Nit: Missing a verb. "and get derived", I think?
s/build/get/
❤️
It seems these could use one and the same helper: a
?
Could document a more specific type.
Also, why does one return
ResponseInterface[]and the otherResourceResponse[]?❤️
s/response/PSR response/
s/cacheability data/cacheable responses/
❤️
Comment #55
wim leersFor consistency with
testGetIndividual(), this should be renamed totestGetRelated().In the future, we'll also want to add
testPostRelated(),testPatchRelated()andtestDeleteRelated().Comment #56
gabesulliceNope, the spec only lets you modify the "relationships" not the "related" resources themselves.
"related" routes are essentially just collection routes filtered by a referencing resource. They're read-only.
"relationship" routes let you add/update/remove the relationships between resources (in Drupal-speak, this means updating the entity reference field).
Comment #57
gabesullice1. This is actually fixing a typo in the existing test coverage
getAuthenticationRequestOptionsdoesn't take an argument.2. 🤘
3. This comment is actually just a note to help whoever implements the "todo" not explain the code. I've moved it around.
4. Whoops, leftover comment. Removed.
5. I agree. I'll do that in the following comment so that the interdiff makes more sense.
6. Done.
7. Added more commentary.
8. Done.
9. 👍
10 + 11. Moved things around and I think it's much clearer now.
12. :)
13. Done
14. Some day, I'll get these wordings right.. some day.
15. :)
Comment #58
gabesulliceMoves all the static helpers into a
ResourceResponseTestTraitper @Wim Leers suggestion in #54.5.Comment #59
wim leers#56: 🤦♂️ — of course, I misread
\Drupal\jsonapi\Routing\Routes::routes(), sorry!#57++
#58++
Comment #60
gabesulliceThe attached applies some minor refactoring and determines which relationship field names to test the actual entity under test rather that relying on the output of the API we're testing to tell us which fields to test!
This is prep work for GET coverage of `relationship` routes.
Comment #61
gabesulliceThis adds relationship testing. Still blocked on #2940339: Port reference field support for non-empty entity reference fields not pointing to an entity from #2543726. After that lands, we can fill in some details.
Comment #62
wim leersNit: these lines can be removed.
😍 — so elegant!
This is SUCH A GOOD EXPLANATION! We should have this in
jsonapi.api.php, and do it for every non-trivial route ("individual" and "collection" seem trivial).s/getExpectedGetRel…/getExpectedRel…/, no?
😍 — so elegant!
These are strangely far apart.
Getting the list of relationship field names switched from introspecting JSON API responses to introspecting an entity in PHP. The latter is better for test coverage, because it would uncover anything that's consistently missing in JSON API. So: 👍
👏 — again, so elegant!
Don't these also belong on the
ResourceResponseTestTrait?Comment #63
gabesullice1. Done.
2. Thanks!
3. Absolutely!
4. I thought about the same thing, but I think we'll want something for DELETE.
5. 🤘
6. Fixed.
7. 👍
8. 🔥
9. Moved.
I also skipped the the tests that are blocked.
I added another skip for an issue I just opened: #2952506: User role relationships route returns NULL
Let's see what the testbot thinks, finally.
Comment #64
gabesulliceSince we added skipped tests and have linked issues, this is no longer blocked.
Comment #66
gabesulliceI forgot and put a skip in the wrong class. Fixing.
Testbot did turn up a valid failure. For some reason the file relationship is serializing metadata that I think should be limited to 'related' routes.
Comment #68
wim leers#63:
Excellent! This actually also makes #2940339: Port reference field support for non-empty entity reference fields not pointing to an entity from #2543726 simpler, because simply removing these overrides then gives us the necessary test coverage there!
EXCELLENT!
Comment #69
gabesulliceThis should pass! :)
Comment #71
gabesulliceDon't ever say this, because it guarantees that it will never be true.
Fixed... hopefully!
Comment #72
wim leersThere are still 2 CS violations… :P
Other than that, I think this looks great. Ideally, we get @e0ipso's +1. But because it doesn't change any logic, that's not a hard requirement I think?
At the very least, this is a huge step forward! :)
Comment #76
gabesulliceWhoops, just some leftovers.
Comment #78
e0ipsoThis was merged. I could not do a proper review, but I decided to merge anyways because: 1) it was RTBC-ed by a maintainer, 2) this is mostly adding test coverage (hence the possibility to break things is thin) and 3) so we can unblock things and keep momentum.
What I could review was as fantastic as usual. Thanks @gabesullice!
Comment #79
gabesulliceI think this issue has had enough activity. I'll do collections in a separate issue and nested includes/relationships in another.
Great work everybody, thank you!
Comment #80
wim leers🎉
Gabe opened #2953318: Comprehensive JSON API integration test coverage phase 4: collections, filtering and sorting and #2953321: Comprehensive JSON API integration test coverage phase 5: nested includes and sparse field sets.