Problem/Motivation
Time and time again, we keep running into bizarre problems that we are not supposed to run into. These problems prevent small, focused patches in the REST and serialization modules from being viable: in virtually every single REST issue, we encounter bizarre behavior and mind-bending bugs, which simply explode the original/intended issue scope out of pure necessity.
Also, all the existing test coverage is centered on the fairly artificial entity_test entity type. There are incomplete, superficial tests for some entity types, but they each test only a small subset, and every time they test it incompletely (e.g. they assert that the proper response code is sent, but they do not assert the response body). There's @todos in almost all existing tests to remind ourselves to add test coverage for other entity types. So, those things never got resolved before the 8.0.0 release.
Examples:
- #2631774-25: Impossible to update Comment entity with REST (HTTP PATCH): bundle field not allowed to be updated, but EntityNormalizer::denormalize() requires it
- #2662284-31: Return complete entity after successful PATCH
// @todo Expand this at least to users.// @todo Test all other entity types here as well.
Proposed resolution
Steps:
Refactor/rewrite all ofCreateTest,ReadTest,UpdateTest,DeleteTestto provide an entire CRUD flow including testing of the edge cases to verify the DX (e.g. forgettingX-CSRF-Tokenheader). Put all that in a base class, and then subclass it for every entity type. That is then much like\Drupal\system\Tests\Entity\EntityCacheTagsTestBase, which has been very successful. That then means any module providing a new entity type can just subclass that test and get full REST test coverage.Figure out a way how to test all serialization formats, including those added by additional modules. That means it's two-dimensional extensibility … which means it's kinda tricky to make that work. We'll see.- Delete the old
(Read|Create|Update|Delete)Testfiles. Deprecate their base class. But this too is fairly large, so to make reviews easier, it's probably better to delete the old, incomplete test coverage in a follow-up: #2824576: Delete old REST test coverage: (Read|Create|Update|Delete)Test, deprecate RESTTestBase. - Review/get to RTBC
- Commit
- Work on follow-ups: #2824576: Delete old REST test coverage: (Read|Create|Update|Delete)Test, deprecate RESTTestBase + #2824572: Write EntityResourceTestBase subclasses for every other entity type..
Crucial background information for patch reviewers & core commiters
Probably the best proof/most convincing argument for having these improved tests is the fact that working on the patch for this issue uncovered more than 20 issues. That's >20 issues that get in the way while communicating with Drupal via APIs. Several of those 20 issues are not in the rest.module component, but in the routing system, request processing system and serialization.module components. Hence they also affect https://www.drupal.org/project/jsonapi — which we are trying to bring to Drupal core as an experimental module. So this test coverage being much more strict helps uncover problems we didn't know about, and problems we would certainly run into.
At ~190 KB, this is a very large patch. It's 100% test coverage. It's 100% additions. This does not modify any code, hence there's zero risk. There's ~70 KB of test code removal in #2824576: Delete old REST test coverage: (Read|Create|Update|Delete)Test, deprecate RESTTestBase (which I felt would be better to do in a follow-up to make patch reviews easier, but I'd be totally fine with merging it into this patch if that's what reviewers/core committers prefer).
However, what is key, is that 90% of what needs to be reviewed lives in only two files: ResourceTestBase and EntityResourceTestBase.
ResourceTestBase- It extends
BrowserTestBasebecause it does functional testing: it performs actual requests, just like front end developers would. The whole point is indeed that we test from the perspective of the (front end) developer who is trying to use the REST API. And in case they make a mistake in their request (i.e. it's not well-formed), the server should provide a helpful response. The test coverage must assert this helpful response. - Talking more concretely, a concrete subclass of
ResourceTestBasemust specify astatic::$format(e.g.json,hal_json), astatic::$mimeType(e.g.application/hal+json), astatic::$expectedErrorMimeType(e.g.application/json) and astatic::$auth. ItssetUp()method ensures that a user for testing is created, and it also ensures the anonymous user and the authenticated user both have no permissions at all. This allows REST tests to test responses when the user does not have sufficient permissions. The only other noteworthy thing it provides is theprovisionResource()method, which provisions a resource by creating the necessary REST resource config entity for a particular test. - The intent is for every REST resource plugin to provide its own abstract subclass of
ResourceTestBase, testing all expected responses (behavior). It is then a simple matter of subclassing that class with astatic::$format,static::$authetc so that in a few lines, you have a concrete test for that particular REST resource plugin + format + authentication provider combination. EntityResourceTestBase- This is the abstract base class of
ResourceTestBasefor the\Drupal\rest\Plugin\rest\resource\EntityResourceREST resource plugin. - It has
testGet(),testPost(),testPatch()andtestDelete(). Each of those run through extensive scenarios with detailed assertions. - Because each entity type has different fields and its own peculiarities, there is again a subclass for every entity type:
NodeResourceTestBase,BlockResourceTestBase, and so on. - This class then has dozens of subclasses, to test concrete format+auth combinations:
NodeHalJsonAnonTest,NodeHalJsonCookieTest,NodeJsonBasicAuthTest, and so on. - Some of these subclasses of
EntityResourceTestBasethen have their own additional test methods, to test further special cases. For example:CommentResourceTestBase::testPostDxWithoutCriticalBaseFields()andUserResourceTestBase::testPatchDxForSecuritySensitiveBaseFields().
You may wonder Why can't this use data providers?
— the answer is simple: these tests are designed to be usable/extensible by contrib modules. Contrib modules that:
- provide new REST resource plugins can subclass
ResourceTestBase - provide new entity types can subclass
EntityResourceTestBase - provide new formats can subclass
NodeResourceTestBase/BlockResourceTestBase/… and get strong assurances that their format behaves as expected for each of those entity types, with minimal effort. See for example\Drupal\Tests\hal\Functional\EntityResource\Term\TermHalJsonAnonTest. - provide new authentication providers can subclass
NodeResourceTestBase/BlockResourceTestBase/… and get strong assurances that their format behaves as expected for each of those entity types
So we can't use data providers for:
- REST resource plugins, because they behave differently
- formats, because contrib might add more, and we could not possibly know what their special cases are — for example HAL+JSON moves reference fields to
_linksand_embedded, addslangattributes to translatable fields, and so on. We couldn't possibly know those special cases for all formats - authentication providers, because contrib might add more, and we could not possibly know what their particular mechanisms are — for example Basic Auth with incorrect login info causes 403s to become 401s, and Cookie requires a CSRF token to be sent with every request
I hope this was useful!
Remaining tasks
Phase 1: infrastructure (base classes with scenarios)-
PortReadTestPortCreateTestPortUpdateTestPortDeleteTest
Phase 1B: apply this to all formats & authentication providers- Formats:
,json. (Not doinghal_jsonxmlbecause #2800873: Add XML GET REST test coverage, work around XML encoder quirks.)Authentication providers:.anon,,cookiebasic_auth Phase 2: apply this to all entity types- Doing all entity types in this issue would make this patch impossibly large. It was deemed wiser to only apply this new test coverage to all entity types that already had some test coverage in the existing REST tests:
Node,User,Term,EntityTest,Comment,Block,ConfigTest,RoleandVocabulary.
All other entity types are deferred to a follow-up: #2824572: Write EntityResourceTestBase subclasses for every other entity type..- Content entity types:
NodeUserMenuLinkContentBlockContentFileShortcutItemContentModerationStateTermEntityTestFeedMessageComment
Config entity types:
BlockFieldStorageConfigFilterFormatViewSearchPageConfigurableLanguageActionContentLanguageSettingsEntityFormDisplayEntityViewDisplayFieldConfigBaseFieldOverrideConfigTestTourResponsiveImageStyleRdfMappingRoleDateFormatRestResourceConfigBlockContentTypeCommentTypeEntityTestBundleNodeTypeContactFormShortcutSetVocabularyEntityFormModeEntityViewModeEditorMenuModerationStateTransitionModerationStateImageStyle
Phase 3: refinements: entity type-specific additions, format-specific additions, authentication mechanism-specific additionsThe existing edge case test coverage from(Read|Create|Update|Delete)Testshould be ported and where possible/sensible, it should be generalized to all entity types. - Content entity types:
User interface changes
None.
API changes
None.
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #164 | ultra_llama_mode_on.png | 91.26 KB | Anonymous (not verified) |
| #162 | finally.JPG | 208.76 KB | wim leers |
| #158 | interdiff-8.3.x.txt | 3.87 KB | wim leers |
| #158 | rest_comprehensive_tests-2737719-157--8.3.x.patch | 190.01 KB | wim leers |
| #158 | rest_comprehensive_tests-2737719-156--8.2.x.patch | 188.73 KB | wim leers |
Comments
Comment #2
wim leers#2662284-31: Return complete entity after successful PATCH is another example.
Comment #3
larowlanBonus points: switch to BTB at same time :)
Comment #4
dawehnerThere is also the conversion to guzzle which ensures we don't need this uggly hack any longer: #1841474: Convert REST tests to Guzzle (would also help with core moving from WTB to BTB)
Comment #5
andypostAlso would be great to extend test coverage when *page_cache* modules enabled for https://www.drupal.org/node/2541358/revisions/view/9428580/9735215
Comment #6
wim leers#5: Sure, but that's of the least concern right now. Plus, there are no known bugs wrt REST + (Dynamic) Page Cache.
Comment #7
tedbowDoes this that we would have 1 test class for every subclass of \Drupal\Core\Entity\Entity? So 30+ test files?
I am going to assign this to me to start working on it. @Wim Leers let me know if you have already started on it.
Comment #8
wim leersI've not started on it. And yes, one for every entity type, but not the two dozen or so "test" entity types. So, probably about a dozen unique tests. But look at e.g.
\Drupal\node\Tests\NodeCacheTagsTest+\Drupal\comment\Tests\CommentCacheTagsTest+\Drupal\user\Tests\UserCacheTagsTest+ … — there's almost zero code in there :)Comment #9
tedbow@Wim Leers ok thanks for the clarification
Comment #11
wim leersThis is blocked on #1841474: Convert REST tests to Guzzle (would also help with core moving from WTB to BTB). I'm going to be working on that issue, as well as this one in the coming days.
Comment #12
wim leersWhile #1841474: Convert REST tests to Guzzle (would also help with core moving from WTB to BTB) is waiting to be committed, I've already begun working on this one. This will not be finished before that one lands anyway.
I've got a start here now. This is testing the
Nodeentity type against multiple formats (hal_json+json,xmlis omitted for now — see next comment), and for now only with a single authentication mechanism (basic_auth). They follow a shared, standardized test scenario, that is specified inEntityResourceTestBase— which is the REST equivalent for the comprehensive cache tags test coverage at\Drupal\system\Tests\Entity\EntityCacheTagsTestBase.For now, this only testing
GET: it includes a superset of the test scenario that\Drupal\rest\Tests\ReadTestuses.(The
individual-commitspatch is included to show how I got to this point. From now on, I'll omit that, every interdiff will correspond to a local commit.)Comment #13
wim leersThis is all that it takes to test an additional format. Pretty nice, is it not? :)
Except… this is something HEAD does not test. And so the very first thing I test that HEAD is not testing… is broken: the attached patch will fail. I opened an issue for it: #2800873: Add XML GET REST test coverage, work around XML encoder quirks.
Comment #16
wim leersApparently I've had the base URL configured incorrectly in my unit tests for a very long time. Hence wrong code, hence #12 failed. This fixes it. Now you'll see the 2 fails in #13 more clearly.
Comment #18
wim leersI failed to include all my changes.
Comment #20
wim leersNew URL-related fail. This time for the error message that we asse
rt. Apparently
$entity->toUrl()generates a relative URL that is relative to the Drupal installation, and not relative to the domain. This should solve that.(I lifted the solution from the CDN module for Drupal 8.)
Comment #22
wim leersI've been interpreting that error message incorrectly. Sorry for the noise. Fingers crossed.
Comment #24
wim leersThere we are! Finally.
This is what I alluded to in #13, where I wrote:
So, for now, reverting the XML test coverage. We don't have that in HEAD either. And because we don't, we didn't know this didn't work. This patch/test coverage is already learning us what does and doesn't work, and it's only in the early stages!
No need to be sad, we are learning where our flaws are :)
Comment #26
wim leersGah.
Comment #27
wim leersMoving all the things to the right places.
Comment #28
wim leersNow no longer testing only
Node: addedTermResourceTestBase,TermJsonBasicAuthTestandTermHalJsonBasicAuthTest.Comment #30
wim leersSame mistake as in
NodeResourceTestBase, which I fixed in #26. Same fix.Comment #31
dawehnerNitpick: Did you tried using " {...} " to make this maybe a bit more readable?
A bit nicer assertion:
assertJsonStringEqualsJsonFileMaybe use camelCase here? Otherwise it reads a lot like
attest, which has a total different meaningIt is a bit weird that we are not using guzzle here
If you care about that, why not use $this->assertInstanceOf?
Comment #32
wim leers@dawehner: Thanks for the review! Will address in the next round.
While working on porting over
user_roletesting from HEAD'sReadTest, I made a mistake (I forgot to enable thehalmodule for a test that was testinghal_json, which caused itsGETroute to not exist). This caused me to run into another bug: theserializer.formatscontainer parameter is used to create RESTGETroutes. So even if you only allowhal_json, it'll still create GET routes forjsonandxml(i.e. the default formats).I predicted this bug almost 4 months ago at #2737751: Refactor REST routing to address its brittleness:
Attached is a reroll that adds test coverage for
user_roleandblockconfig entities.So, the patch currently ports a superset of the test logic in
ReadTest, fornode,taxonomy_term,blockanduser_role.termwasn't tested before. That leavesentity_test,config_testandtaxonomy_vocabularyto be ported to have all of the test coverage inReadTest.Now we're really getting up to speed :)
Comment #33
wim leersUpdated the issue summary to list all the currently known/planned todos.
Comment #34
wim leersSince this is about providing test coverage to ensure that REST in Drupal 8.2 is as reliable as possible, this really should go in 8.2. This won't cause API changes. At most, it will cause bug fixes. Which is the whole point.
Comment #35
wim leersMarking the already completed items as completed in the IS.
Comment #37
wim leersI had it ready, but I failed to include a small change in #32 to correctly generate URLs for config entities. Here's the fix.
Comment #39
almaudoh commentedThis looks like a huuuge scope to squeeze into one issue. Can this be split up into smaller issues (maybe phases 2 & 3 after phase 1 is done).
Comment #40
wim leersI have no idea what's going on in #37, because all those tests pass fine locally. It's the same assertion that (only) testbot keeps failing on in every scenario, but only when using
basic_auth: instead of Drupal returningtext/html; charset=UTF-8, it's returningtext/plain; charset=UTF-8. No idea how that is possible.For now, commenting that assertion, along with a
@todocomment. Let's hope there's not another assertion further in thetestGet()scenario that also fails…Comment #41
wim leersSo far, I've labeled all tests as "basic auth". But actually, in reality, they're testing the behavior in case of no authentication, i.e. the anonymous case. Given sufficient permissions to the anonymous user, this is also possible. Which makes sense, because some REST APIs are intended for public (authless) consumption.
After quite a struggle, I got that working!
In this reroll, every existing test is split up in a "basic auth" test and an "anon" test. Any contrib module that provides a new authentication mechanism can then provide similar, simple test classes with barely any code, and hence run the entire test suite!
The struggle is because of the weird interaction/design of the routing + authentication system, which causes things to fail miserably in an unfriendly way when the user forgets to specify
?_format=nonsense, or has a typo (for example?_format=haljsonon a URL that already has a HTML route.It's very very very confusing how Symfony's routing system first grants access because a user is authenticated via basic auth, and so
AccessAwareRoutersays everything is okay, and then afterwards, duringAuthenticationSubscriber::onKenrelRequestFilterProvider(), we choose to deny access after all becausebasic_authisn't a globally allowed access provider, and the matched route (which matches the HTML, non-REST route due to the missing?format=) is lacking an_authroute option. You can either get a 406 (which is what we want), or a 403 with HTML (in case of non-specified or invalid format) or a 403 in the expected format (in case of non-specified or invalid format plusAcceptheader, even though we don't actually supportAcceptheaders… thanks toDefaultExceptionSubscribercallingRequest::getAcceptableContentTypes()).Worse, it's even possible to get the appropriate 406 response in case of an invalid format, but for that response to be sent with
Content-Type: text/html! (In case of anonymous.)More edge cases than I can count.
Seems like the authentication system and routing system are integrated in a brittle, confusing, backward manner. And the content negotiation system makes it that tiny bit extra unpredictable. This is no one's fault. This is complex software with (too) many layers, and without comprehensive integration tests, this sort of thing is to be expected.
So, the updated test coverage verifies this crazy behavior is at least consistent. And then when we fix it, in #2805279: Routing system + authentication system + format-specific routes (e.g. those in rest.module) = frustrating, unhelpful 403 responses instead of 406 responses, we can massively simplify this crazy test logic. It's better to embrace and accept the status quo, because that means we are better prepared to improve it :)
Finally, less important, but still a WTF: the error response body in case of missing Basic Authentication is different for the
jsonandhal_jsonformats. In the former case, it's actually in JSON, but with a strange prefix, and in the latter case, it's in plain text… opened an issue for that too: #2805281: ?_format=hal_json error responses are application/json, yet should be application/hal+json.Finally^2 (I found this just before posting), I found another absolutely fascinating edge case. I'll let the comment in the test speak for itself:
Comment #42
wim leersI just postponed #2135829: EntityResource: translations support on this — without this issue, that issue is very likely to introduce even more edge cases.
Comment #43
wim leers#39: Yes, the vast majority of phase 2 will definitely be handled by other issues. But as the issue summary says, this must provide at least parity with (i.e. a superset of) the existing tests. Which will then allow us to remove those tests.
Comment #45
wim leersQuoting myself in #40:
Clearly, my fear came true. Sigh :(
What's interesting, is that 100% of the fails are for the
hal_jsontests. The tests for thejsonformat are unaffected. Even more interesting: I observed in #41 that the 401 error response for thehal_jsonformat marks itself asContent-Type: application/hal+json, yet the response body is plain text. Funny enough… on testbot it apparently does sayContent-Type: text/plain, which is consistent with the response body!I can't reproduce this when
http://tres/) and--group haltests with PHPUnitrun-tests.sh--module haltests withrun-tests.shhttp://localhost/tres/and--group haltests with PHPUnitrun-tests.sh--module haltests withrun-tests.shSo I'm completely out of ideas. I'm going to have to comment out all the
Content-Typeresponse header assertions. We'll need to sort this out before this eventually gets committed. I officially give up.The final idea I had is that perhaps the response contains two
Content-Typeheaders, and that somehow testbot parses the first one and my local installation parses the second one (or vice versa). But, no such luck:Comment #46
wim leersComment #47
wim leersSo now that we have a decent starting point, moving the test coverage to the modules responsible for the biggest divergence, and hence the biggest reason for them to have explicit comprehensive test coverage: those providing additional formats.
In this reroll, I moved all
hal_jsontests (filenames matching*HalJson*Test.php) to thehalmodule. The https://www.drupal.org/project/jsonapi module will be able to basically copy/paste and rename these.Comment #48
wim leersAdded
entity_testcontent entity test coverage. (BecauseReadTest::testRead()is testing this, so we need it for test coverage parity.)Comment #49
wim leersWhew! After a >24 hour detour on new bugs, here I am again with another update!
Added
taxonomy_vocabularyconfig entity test coverage.Discovered another problem: #2807263: Impossible to write unit tests involving Vocabulary, because TAXONOMY_HIERARCHY_(DISABLED|SINGLE|MULTIPLE) are defined in taxonomy.module instead of VocabularyInterface. Which uncovered yet another problem, which also happens to improve anonymous REST response performance significantly: #2807501: ResourceResponse::$responseData is serialized, but is never used: unserializing it on every Page Cache hit is a huge performance penalty.
So, the anonymous vocabulary tests have their GET test coverage disabled for now, until #2807263: Impossible to write unit tests involving Vocabulary, because TAXONOMY_HIERARCHY_(DISABLED|SINGLE|MULTIPLE) are defined in taxonomy.module instead of VocabularyInterface lands.
Comment #50
wim leersAdded
config_testconfig entity test coverage.This didn't uncover new bugs — hurray! But some special extra sauce was necessary to make
config_testproperly testable. See the tinyconfig_test_resttest-only module for why.This marks the completion of porting 100% of the test coverage of
ReadTestover to this new test coverage! It now not only does a superset of the test scenario, it now also tests all the entity types thatReadTestwas testing.Next:
CreateTest.Comment #51
wim leersThe experience I gained with working on
Vocabulary-related things for this issue, and the fact that I got the HTTP GET tests for it to pass in #49 allowed me to answer #2772413: REST GET fails on entity/taxonomy_vocabulary/{id} 403 Forbidden with error with confidence. The problem must've been somewhere else.And sure enough, the problem turns out to have been that they did not grant the
administer taxonomypermission. But of course, doing that would also be quite dangerous, if all you need is the ability to read vocabularies!So, we need to solve that on two levels:
Vocabularyentities: #2808217: To be able to view Vocabulary config entities via REST, one should not have to grant the 'administer taxonomy' permission (patch posted!)Those are the two newest child issues for this issue!
Comment #52
wim leersI've got the
POSTtest coverage working (ported most ofCreateTest). It took a lot of effort, and I uncovered lots of bugs. But, before getting to that point, and posting that interdiff+patch, I'm posting some refactoring that was necessary to get there. That'll make the POST test coverage easier to review.setUpAuthentication()tosetUpAuthorization(): this was not handling authentication, but authorization: I named it poorly when I introduced it.setUpAuthorization()create accounts, but only in case of non-anon tests, I moved that logic toResourceTestBase. Less code in actual tests, so less that can go wrong. Now it only needs to grant permissions to either the anonymous or authenticated role.ResourceTestBase::assertResourceResponse().EntityResourceTestBase::assertErrorResponse()toResourceTestBase::assertResourceErrorResponse().ResourceTestBase::grantPermissionsToTestedRole(), which callsgrantPermissionsToAuthenticatedRole()when an authentication provider is being used, and callsgrantPermissionsToAnonymousRole()otherwise. This allows us to remove thesetUpAuthorization()method in every single concrete test class, and move it in the per-entity type base classes. So much simpler! This helps reduce patch size too :)Comment #53
dawehnerTo be honest, in order to be able to review that patch, but pushing everything into one patch makes it really hard to do so.
To be honest, this really reads like a good usecase for a dataprovider.
Comment #54
wim leersDiscussed with dawehner in IRC.
EntityResourceTestBase).Comment #55
wim leersAnd here it is! The test of all the fundamental things in
CreateTest(which testsPOSTing of entities). This is not yet a superset ofCreateTest.Necessary to achieve superset:
GETtest coverage)X-CSRF-Tokenheader testing (but this requires the cookie authentication provider, which I've yet to add test coverage for)Commententity typeUserentity typeAlready surpassing existing test coverage in that it tests
Termand has far stricter assertions, hence stronger guarantees.While working on that, I encountered no less than seven bugs/WTFs that resulted in new issues, and which slowed me down on the order of many days:
Content-Typerequest header: #2811133: Confusing error response set by ContentTypeHeaderMatcher when forgetting the Content-Type request headerComment #57
wim leersI simply uploaded the wrong patch. They were listed just above each other, I ended up selecting the wrong one, because my computer is super laggy.
Comment #59
almaudoh commentedI was wondering about the sudden change in patch size...
Comment #60
wim leersComment #62
wim leers24 different failures this time. Failed because #2811133: Confusing error response set by ContentTypeHeaderMatcher when forgetting the Content-Type request header landed while I was working on this, which means one
@todoin this patch can be fixed :)Comment #63
wim leersUpdate
testGet()to match the improvements intestPost().Comment #64
wim leersFixing mistake in IS: there's no test coverage yet for using the
cookieauthentication provider.Comment #65
wim leersTest what happens before provisioning a REST resource and before granting the necessary authorizations. Both for
testGet()andtestPost().Comment #66
wim leersTest BC permissions. Both for
testGet()andtestPost(). One less@todo!.Comment #67
wim leers#65 contains a flaw that will cause failures for
basic_authtests (it will only work for anon===authless tests).Comment #71
wim leersApparently another fail sneaked in there. In all
Blockconfig entities. The root cause is that same problem that was discovered before (see #41):But #65 introduced additional assertions to ensure a 403 is returned before the necessary authorization is set up. The work-around I had in place didn't account for that. So, now I have changed the work-around to disallow access by any user until the necessary permission is granted. We still need to fix the root cause though.
Created an issue for that now: #2820315: Blocks' 'view' operation is interpreted as "render on page" instead of "read this config entity".
Comment #72
wim leersTest coverage for the
Commententity, for parity withCreateTest.Comment #73
klausiThis test should be marked as skipped with $this->markTestSkipped().
And the doc block should use /** ... */ comments.
In Drupal when we use the terminology "create entity" then we mean create an entity object but do not save it. So I would name this "insertEntity()" or "createAndSaveEntity()" or similar.
use $this->assertInstanceOf() instead.
You are also setting up node types and whatever is necessary to actually save one entity. Should this be named "setupEntity()"?
entity_create() is deprecated, let's not introduce more of it. Use the class instead.
Should use $this->assertSame().
This is quite an expensive operation you do in quite a few places. Can you create a helper method that just clears the config cache and routing cache? That's probably what the @todo issue is about :)
I'm a bit concerned about test runtime. What is the runtime of all old REST tests vs. all the new Functional tests you introduced? Would be interesting to just get a feeling of how much we are adding here.
Can't say much about the overall approach with all the sub-classing, the patch is a bit too large for my taste. I would argue to do this in smaller chunks, but whatever works for you.
Comment #74
wim leers#73:
\Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity(). This is simply about creating an entity … and if it needs additional setup, then that too. I think "create entity" captures the spirit/intent best.assert(), not a test.ConfigTestentity types, with the exact same name, that is sadly impossible.@todois about. See the issue that it mentions: #2815845: Importing (deploying) REST resource config entities should automatically do the necessary route rebuilding. Once that issue is fixed, thesedrupal_flush_all_caches()calls can simply be removed. The only reason they're there is because after hours of debugging, that was the only solution that worked. They were introduced in #55.The old REST tests will be removed. Test run time is less important than developers (both core developers and front end developers actually using D8's REST API) wasting countless hours on bizarre errors. This test suite helps prevent that. And considering how many new issues I've already opened because of the test coverage written so far, it's definitely helping.
I agree that the subclassing approach is clunky/verbose. But it's the only way that works: this is how we can test every entity type + format + authentication provider combination, and allow contrib modules that add more of either of those three to get the same solid test coverage. (It also means these tests can easily be run in parallel btw!)
+1000! I wish I could keep it smaller. But… this is a necessary evil when you're developing one generic test with scenarios that exercise all edge cases (
EntityResourceTestBase'stestGet()+testPost()). You need to have a sufficient variety of entity types to test against, because if you start with only a single one, then you'll need to keep modifying that base class in every issue where you add a new entity type, hence resulting in many conflicting patches, but also a less cohesiveEntityResourceTestBasebase class.Note that I'm only including those entity types that are already covered in the existing REST test coverage. All others I'll defer to follow-ups!
I will address #73.1!
Comment #75
wim leers#72 added test coverage for
Comment, but did not yet include test coverage for the insane edge cases/DX problems I encountered.Opened an issue for that too: #2820364: Entity + Field + Property validation constraints are processed in the incorrect order.
Comment #76
wim leersClean-up patch, that addresses #73.1 too. All of dawehner's feedback in #31 has already been addressed while posting all those intermediary steps.
Comment #77
wim leersTest coverage for the
Userentity, for parity withCreateTest.Comment #78
wim leersComment #80
wim leers#75 apparently had some issues. This should fix some of those fails. The ones that say are a bit problematic: I can't reproduce those locally. So I added additional debug output.
Comment #84
wim leersOkay… so this is yet another case of "DrupalCI has result X, locally it's result Y, and there's no way to debug DrupalCI". So I have to give up, and just make the assertions less strict.
Comment #85
wim leersCreateTestwas asserting (for a few entity types) that a too long UUID resulted in a 404. This now does that for all entity types.Comment #87
wim leers#85 introduced a failure because HAL's
FieldNormalizeris very unforgiving when you use{field_name: 'abc'}: you must always specify{field_name: [{value: 'abc'}]}.I opened #2820743: FieldNormalizers are very unforgiving, impossible to debug error response given for fixing that.
Comment #88
wim leers\Drupal\rest\Tests\CreateTest::testCreateEntityTest()was testing an access-protected configured field: a field that cannot be edited should result in a 403 and a response body with a meaningful message.This brings that to
EntityResourceTestBaseand verifies this works for all entity types!Comment #89
wim leersThis adds test coverage for using the
cookieauthentication provider. It also adds theX-CSRF-Tokenheader missing/invalid edge case DX testing to thePOSTtest case scenario.While working on this, I discovered a bug in the "login" RPC endpoint: #2820888: Cookie authentication: the user.login.http route never supports the 'hal_json' format or some other format, depending on module order.
This was the final thing to achieve , which means as of this patch, we have a superset of
CreateTest's test coverage! Marking that one as done in the IS.Next:
UpdateTest+DeleteTest.Comment #90
wim leersPrevious patches introduced a bit of cruft, cleaning that up.
Comment #91
wim leersWhile working on
testPatch()(for portingUpdateTest), I noticed thatgetNormalizedEntityToCreate()was a bit of an unfortunate name. This renames it togetNormalizedPostEntity(). Which then opens the door forgetNormalizedPatchEntity().Comment #92
wim leersAgain while working on
testPatch()(for portingUpdateTest), I noticed thatverifyResponseWhenMissingAuthentication()was misnamed, it should have been namedassertResponseWhenMissingAuthentication().Comment #93
wim leersThis ports the majority of
UpdateTest. Some edge cases inUpdateTeststill need to be brought over and generalized (particularly those for Comments, i.e. the coverage added in #2631774: Impossible to update Comment entity with REST (HTTP PATCH): bundle field not allowed to be updated, but EntityNormalizer::denormalize() requires it). But in most respects, this is already testing a vast superset of what HEAD's test coverage does.No new bugs found for once!Bugs were found, I just forgot about that over the weekend. They are:
Comment #94
wim leersThis ports all of
DeleteTestand in fact covers a superset already! :)One more bug found, this time not in Drupal but in
SymfonyPHP! #2821711: Apache always sets Content-Type: text/html, even for DELETE requestsComment #95
dawehnerI'm wondering when we should use exceptions vs. assertions.
I think this one filter could be in one line
This could be an
array_filteras wellI'm happy to investigate that
should that maybe be abstract?
One small trick I've beeing applying recently is that I typehint properties in subclasses, when you have more knowldge about them. In this case you could typehint the entity with the block configuration entity, so this method call is known. I'm just making a general comment here, feel free to ignore that.
wow
I would like to review that more clearly, but I don't have the physical and mental power for that right now
I'm wondering whether you could split this up into methods, so the code is a bit easier to read:
Should we make the other ResourceTestBase deprecated?
You can also just use
$request_options['http_errors'] = FALSEand get the exact same outputComment #96
wim leersThanks, will address these tomorrow! :)
Comment #97
wim leers#95:
getExpectedNormalizedEntity()implementations. I only kept two assertions: those in\Drupal\Tests\rest\Functional\ResourceTestBase::setUp(). I think it makes sense to useassert()there because it's asserting that the test environment itself is being set up correctly.ResourceTestBase:)That being said, if you see a way to make this clearer without breaking up a scenario, then I'm all for it!
\Drupal\rest\Tests\RESTTestBase.Thanks again for your review! :) This patch is already better for it. Looking forward to your review of
EntityResourceTestBase.Comment #98
wim leersPer #94,
DeleteTestwas fully ported. Updating IS to reflect that.Per #93,
UpdateTestwas mostly ported: all of the generic stuff was ported, but now there's still the specialized test coverage that needs to be ported (for example that added by #2631774: Impossible to update Comment entity with REST (HTTP PATCH): bundle field not allowed to be updated, but EntityNormalizer::denormalize() requires it). I'm working on generalizing it.Once that is done, I consider this issue done. I will file follow-ups to add test coverage for all other entity types. And then I'd like to add a test that ensures that every entity type has this test coverage at least for core's formats+auth providers, so that one can be certain that every entity type can in fact be interacted with via REST. (Look at
\Drupal\Tests\help\Kernel\HelpEmptyPageTest::testEmptyHookHelp()for inspiration on how to do that.)Comment #99
wim leersOf course, the
cookieauthentication provider's test coverage was also already completed, back in #89.Comment #100
wim leersSo, the 3 things that still need to be ported:
\Drupal\rest\Tests\UpdateTest::testPatchUpdate()'s portion exists in the new test coverage (the rest of it is already done, and more)\Drupal\rest\Tests\UpdateTest::testUpdateUser()'s test coverage that asserts some fields can only be modified when given the current password.\Drupal\rest\Tests\UpdateTest::testUpdateComment()'s test coverage for read-only fields (which BTW differs between HAL+JSON vs JSON…), which was introduced at #2631774: Impossible to update Comment entity with REST (HTTP PATCH): bundle field not allowed to be updated, but EntityNormalizer::denormalize() requires it.This reroll addresses the first one. In doing so, I stumbled upon two problems:
Comment #101
wim leersDELETED (pasted my comment intended for another issue)
Comment #102
wim leersSmall reroll for free beer: https://twitter.com/tstoeckler/status/791769722941480968.
Comment #103
wim leersThis addresses #100.2:
I ran into #2813755: JSON responses encoded inconsistently: make them all RFC4627-compliant once again while working on this one.
Comment #104
tstoecklerThere is no "Beverage transferral approved" value in the Status field on d.o but I guess this comment will suffice. Maybe I should open an issue in the webmasters queue... ;-)
Comment #105
wim leers#104: :D
Comment #106
wim leersAnd this addresses #100.3:
Doing so resurfaced the existing problems that were already documented in #2631774: Impossible to update Comment entity with REST (HTTP PATCH): bundle field not allowed to be updated, but EntityNormalizer::denormalize() requires it. But thanks to the test comprehensive test coverage introduced here, it is much clearer/scarier now how the HAL normalization causes aberrant behavior. So, opened #2824271: HAL causes 'pid' and 'homepage' fields on Comment entity to _not_ be forbidden to send in PATCH requests to fix that.
Comment #107
wim leersA round of self-review.
Needs docs.
Needs docs.
Merge these comments.
Fix this.
Add docs.
Improve docs.
Again.
Needs docs.
Document why this does not use
ConfigTest::create().Add @see examples to these docs.
s/$entityType/$entityTypeId/
s/Fields/FieldNames/
s/Field/FieldName/
Mention these in the class docblock.
Add docblocks to each of these, and ensure there's no other methods listed in between these.
Two newlines, should be one.
s/array()/[]/
Actually create this issue.
The docblock is both wrong and incomplete. Rewrite.
Comment #108
wim leersAddressed that. Opened #2824408: To be able to create/update/delete Term entities via REST, one should not have to grant the 'administer taxonomy' permission for point 18.
Comment #109
wim leersFirst round of issue summary update: to reflect the current status and scope. Scope-related follow-up created: #2824572: Write EntityResourceTestBase subclasses for every other entity type..
Comment #110
wim leersSecond round of issue summary update: to reflect what to do with the old tests. Related follow-up created: #2824576: Delete old REST test coverage: (Read|Create|Update|Delete)Test, deprecate RESTTestBase.
Now all necessary follow-ups have been created.
Comment #111
wim leersThird and final round of issue summary update. Added more context for reviewers & committers.
Comment #112
wim leersFix typo.
Comment #113
wim leersWhile working on #2824576, I found one more thing that makes sense to assert: more cacheability aspects. The cache tags + contexts for the GET/HEAD response. And the absence of the
X-Drupal-Cacheheader for unsafe methods (POST/PATCH/DELETE).See #2824576-2: Delete old REST test coverage: (Read|Create|Update|Delete)Test, deprecate RESTTestBase, point 4.
This adds those.
Comment #114
wim leersAnd also while working on #2824576, there is one last thing I found that makes sense to add to the test coverage here. I missed it because we have
NodeTestwhich duplicates big portions of(Read|Create|Update|Delete)Test. But the portions it doesn't duplicate, is for asserting the error response when the developer forgets to specify the bundle for the entity (or specifies an incorrect one). This is a common, easy mistake, and it's very much worth testing that this works correctly for all entity types, to ensure a good DX/RX.See #2824576-2: Delete old REST test coverage: (Read|Create|Update|Delete)Test, deprecate RESTTestBase, point 3.
This adds that test coverage. In doing so, I generalized it, so that it doesn't run just for
Nodeentities using thejsonformat, but for all entity types (that have bundles) and for all formats. This led me to discover that thehalnormalization actually breaks ungracefully when you specify an invalid bundle (it fails gracefully when omitting a bundle completely): with a PHP error and without useful DX feedback. Opened #2824827: \Drupal\hal\Normalizer\ContentEntityNormalizer::denormalize() fails with fatal PHP error when bundles are missing from link relation types for that.And with that, this is 100% ready for final review.
Comment #115
wim leersTagging with RX & DX, since this test coverage will help prevent regressions in RX & DX.
Comment #116
borisson_I didn't get trough the entire patch on my lunchbreak, but I did find some small things. Almost all of them are docs related.
I don't think is allowed, to have both an inheritdoc and actual documentation in the same docblock, so the docs should be copied over.
Needs docblock
See above.
80 cols.
Can we document that this array has to be keyed by field name?
I think we could remove the second foreach by wrapping this in additional
array_filter(. But that wouldn't help with readability I think. So nevermind.80 cols
/s/mergeTags/mergeContexts
double space
/s/mergeTags/mergeContexts
Double space
/s/mergeTags/mergeContexts/
80 cols.
docs + inheritdoc
80 cols
I think the @file block here needs to stay, we can only remove them for files that contain one namespaced thing (class/interface/...)
80 cols
Missing newline
80 cols.
double blank lines
Let's create an issue and link to it?
80 cols
80 cols
80 cols
80 cols
80 cols
I had to read this 3 times before I figured out that
GETtingwas not misspelled. Can we rewrite this toTests a GET request for an entity, ...80 cols
Comment #117
wim leers\Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator::getDefinition(),\Drupal\Core\Access\AccessResult::isAllowed,\Drupal\Core\Datetime\Element\Datelist::valueCallback(), and so on. 290 occurrences of the regex\* \{\@inheritdoc\}\n \*\n \*NodeResourceTestBaseetc, because it's simply overkill/unnecessary.EntityResourceTestBase::test(Get|Post|Patch|Delete)(). And you'll see that it's very necessary there.test(Post|Patch|Delete)()Comment #118
wim leersFiled issues for the remaining things I encountered (for which I had commits in my local branch, to address later):
Comment #119
wim leersFinal round of self-review & clean-up:
@todoto ensure they're correct. Two didn't have issues. One of those two I was able to remove+address. And the other I was able to simplify and link to an issue I had created prior (#2813755: JSON responses encoded inconsistently: make them all RFC4627-compliant).@todoor #118.1, in the place that made me discover it.Comment #120
dawehnerthis comment is a bit weird. It seems to explain something different, than what is done below (setting some protected field names)
Is there a reason we don't use
$this->entity->getOwner()?Nitpick: I think you could rewrite this to
($field->getName() !== $bundle_key) && $field instanceof EntityReferenceFieldItemListInterface.Here I my quick nodes:
I'm wondering whether you could get rid of this custom cookie handling by specifing a location for a cookie jar, see
\GuzzleHttp\Middleware::cookiesShould we include 'OPTIONS' here as well?
Feel free to ignore: It feels more readable to move this unset below the line of documentation, so you know what this line of code is doing.
Epic documentation!
These two cases have the same documentation even if the first one mostly deals with the case of having no request format. I guess we could document that even without a request method, we need the resource to be provisioned.
array_merge_recursivehad some issues, which is why we haveNesedArray::merge. Should we maybe switch to that one, just to not run into the same issues as well?Do you need this explicit empty check? Isn't
explode(' ', '') === []That sounds like weird english. What about using: 'are the same; The only different is that there's no body'
Here is also one of these cases where moving down the line would improve readability IMHO.
So we could use assertContains here?
Another array_merge_recursive instance ... I mostly care about custom/contrib code also implementing these test classes.
I really like these kind of detailed test coverage
can't something have bundles but no related bundle entity types? These could still valid usecases to test
Honestly, I'd have somehow expected a 422 here
hahaha
Nice!
Nitpick ^10: There should be a empty space after the casting, so
(string) $response->getBody()Comment #121
wim leersgetOwner()meant we were calling\Drupal\user\Entity\User::getAnonymousUser(), which generates an in-memoryUserentity. Which has no UUID set, which meant that the code further down that sets the expected_linksnormalization would fail, because it expects['uuid' => ['value' => NULL]], which is of course wrong. See\Drupal\comment\Entity\Comment::getOwner(). Documented this now.\Drupal\Tests\rest\Functional\CookieResourceTestTrait::getAuthenticationRequestOptions()with\Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait, and see what exactly is expected of each request. Contrib modules likesimple_oauthwill add their own, and it'll again be clear to compare. If we start using Guzzle's built-in "cookie tracking", then we lose that explicitness, that clarity.TRACEthen. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html. Note this is exactly what Django does too: https://docs.djangoproject.com/en/dev/ref/csrf/#how-it-works.EntityResourceTestBase::test(Get|Post|Patch|Delete)()explode(' ', '') === [''], sadly.\Drupal\serialization\Normalizer\EntityNormalizer::denormalize()is also wrong, and we'd need to fix that. (In a separate issue of course, since this one is test-only.) Note how it does:(i.e. any entity type definition that has a
bundlekey can have that bundle type unexpected error response occur, as long as there is a bundle entity type)… oh but wait, it's still possible to get the
A string must be provided as a bundle valueerror response even when there is no bundle entity type! So you're partially right :) Or maybe even entirely right — it's not clear what everything was that you were pointing at.Comment #122
dawehnerThank you @Wim Leers for your quick response and astonishing test coverage in this issue. I'll RTBC especially because test coverage is a process, not a state. You should continue working on tests, they aren't finished.
Wow, good to know!
oh PHP!
Well bundles are seeded by
hook_entity_bundle_info(), entity types is just one of the possible storages for those bundles.Comment #124
wim leersFFS. Fail caused by #2828045: Tests failing with unrelated „Translation: The French translation is not available”. Infra problem.
Comment #125
MixologicComment #127
wim leersRandom fail in
OutsideInBlockFormTest: https://www.drupal.org/pift-ci-job/535605Comment #128
catchI found some minor code style/comment nits but lost them before I could post anything, and nothing important.
I had some questions which were all answered in the issue summary, so just recording here my internal conversation:
1.
Q. Why isn't this removing any tests?
A. Because there's a follow-up for that to keep the patch under 250kb at #2824576: Delete old REST test coverage: (Read|Create|Update|Delete)Test, deprecate RESTTestBase. I asked one question on there about whether we can additionally remove some HAL tests, but given this is a significant refactoring, that's fair enough to do it in two issues. However it'd be good to get the removal in quickly to avoid duplicate test coverage.
2.
Q. Why isn't it using data providers so we can have fewer test classes?
A. One to make it easy for contrib/new core modules to re-use the tests. Also not everything could be done with a data-provider anyway.
It feels like there still might be a way to handle #2, but a lot of sub-classes is still a lot better than lots of one-off tests that are incomplete anyway, and I can see Wim already tried this at one point in the issue.
So while the scope of the patch is large, since this adds test coverage for pending bug reports (albeit commented out) and allows further testing improvements down the line, I think it's reasonable to get it in.
However, phpcs isn't happy at all, and it's too much to do on commit, so cnw for that.
Comment #129
wim leersFixed all
phpcsviolations!+1
:) :) :) This will help so much with making a reality rather than an aspiration!
Comment #131
wim leersAlready did this: #2824576-7: Delete old REST test coverage: (Read|Create|Update|Delete)Test, deprecate RESTTestBase.
Comment #132
wim leersGah, again that random fail in
OutsideInBlockFormTest. Issue for that: #2830485: \Drupal\Tests\outside_in\FunctionalJavascript\OutsideInBlockFormTest fails randomly. Retesting.Comment #133
wim leersSigh. #2789315: Create EntityPublishedInterface and use for Node and Comment landed despite breaking BC. Testing #129 against 8.3. It should fail now.
Comment #134
wim leers#2789315: Create EntityPublishedInterface and use for Node and Comment was reverted. That removed several failures.
But, testing #129 made it clear that 8.3.x already deviated from 8.2.x in several ways. So, rolling a separate 8.3.x patch…
Content-languagebecamecontent-language,X-Drupal-Cachebecamex-drupal-cacheetc. Opened #2831388: Minor regression: ResourceResponseSubscriber lowercases all header names for that.statusfield ofCommententities is validated before other fields in 8.3.x, due to #2810381: Add generic status field to ContentEntityBase. (This does not constitute a BC break.)Commentresponses have theurl.sitecache context, but not in 8.3.x. The root cause is\Drupal\Core\EventSubscriber\RedirectResponseSubscriberrunning after\Drupal\Core\EventSubscriber\FinishResponseSubscriber: theurl.sitecache context is on the response object, but it's there too late. This is a gap in the test coverage of #2807501: ResourceResponse::$responseData is serialized, but is never used: unserializing it on every Page Cache hit is a huge performance penalty, which landed against 8.3… except that that patch does have test coverage:ResourceResponseSubscriberTest. The problem is that there's no proper functional test coverage that tests the integration; the interaction with other parts of the system. And that's exactly what this patch provides! So, I'm including the fix here (since I can't actually add it in a separate issue; that'd require me duplicating big portions of the test coverage being introduced here, which defeats the purpose of this issue/patch providing comprehensive test coverage).So, attached:
rest_comprehensive_tests-2737719-129--8.2.x.patch, this is identical to the patch in #129, but renamed to clarify that this must be committed to 8.2.x only.interdiff-8.3.x.txt: contains the changes to compensate for the changes in 8.3.x versus 8.2.xrest_comprehensive_tests-2737719-134--8.3.x.patch, this is identical to the patch in #129, plus the 8.3.x-specific interdiff.Comment #135
wim leersThe interdiff explained:
This is for
<a href="#comment-11801731">#134</a>.3.This is for #134.4.
This is for #134.2.
This is for #134.1.
Comment #137
wim leersI dug deeper into #134.3, i.e. I finished the investigation proposed by #2831388: Minor regression: ResourceResponseSubscriber lowercases all header names. Turns out this is also caused by #2807501: ResourceResponse::$responseData is serialized, but is never used: unserializing it on every Page Cache hit is a huge performance penalty. And just like it makes sense to fix #134.4 (that other regression caused by #2831388), it makes sense for this too. No need for excessive explicit test coverage… because that's exactly what the purpose of this very issue is.
The only mistake that we made is committing #2831388 before this patch.
We really, truly, desperately need this patch to land.
Redoing what I did in #134 with this new knowledge. You must manually compare the two
interdiff-8.3.x.txtfiles. But they're tiny, so that's okay. That way, the interdiff here continues to show the difference between the 8.2.x and the 8.3.x patch.Comment #141
wim leers#137 was cross-posted with @catch already committing this, even before the patch came back green. He got distracted/confused.
I can see that it already came back green, so we're good :)
So, moving #137 to a follow-up. Reopening #2831388: Minor regression: ResourceResponseSubscriber lowercases all header names for that.
(Naturally, this explains the fails for #137's patches: they don't apply anymore.)
Comment #143
wim leers#137's 8.3.x patch failed. The commits need to be reverted.
EDIT: even though it's all green locally… this may be hard to figure out. I'm currently suspecting a random bot fail.
Comment #145
wim leersRetesting all patches in #134 and #137…
Comment #146
wim leersFails reproduced. I can only reproduce this fail when I specify the
BROWSERTEST_OUTPUT_FILEenvironment variable while running the test. It fails to determine the caller by introspecting the backtrace. Investigating that.Comment #149
wim leersRoot cause found. #2763401: PHPunit browser tests should log all Mink requests was committed to 8.3.x only (so far, there's a patch for 8.2.x too that's not yet committed), and had one major flaw, which is the one that's causing exactly the failures that we see in the failures against 8.3.x here:
The solution is #2822387: Undefined offset 1 in BrowserTestBase::getMethodCaller(). So this patch is now blocked on #2822387.
Comment #150
wim leers#2822387: Undefined offset 1 in BrowserTestBase::getMethodCaller() landed. This is now unblocked. Retesting the last two patches.
Comment #153
wim leersRequeueing again, because #2822387: Undefined offset 1 in BrowserTestBase::getMethodCaller()'s commit had not yet been pushed. Canceled the retests (hence the two fails), now restarted them. Fingers crossed!
Comment #156
wim leersWell, from 106 fails to 34. That's better. But not the zero we were expecting.
Most fails are caused by the infuriating insanity that is
\Psr\Http\Message\StreamInterface::getContents(). It doesn't return the contents, like the method says. It returns the remaining contents.So apparently something in HEAD has changed since yesterday, that causes the response body to already have been read.
Changing all occurrences (there are five) of
$response->getBody()->getContents()to(string) $response->getBody()to fix that. I could change it only for the 8.3.x patch, but it's a unnecessary divergence, unlike all the other things ininterdiff-8.3.x.txt. So, updating the 8.2.x patch too.The attached interdiff applies to both patches. The
interdiff-8.3.x.txtinterdiff at #137 still shows the difference between the 8.2.x and 8.3.x patches.Comment #158
wim leersI found the root cause for the failing
PageCacheTest.The
PageCachemiddleware expects theExpiresheader to be set, always (which seems like a bug, but an out of scope one for sure). It is normally set byFinishResponseSubscriber::onRespond()becausesetResponseCacheable()is called becauseisCacheControlCustomized()returnsFALSE.The changes made to
ResourceResponseSubscriberininterdiff-8.3.x.txtcause that to change. Well, they don't cause it, they expose it. The problem is with HEAD's\Drupal\rest\EventSubscriber\ResourceResponseSubscriber::flattenResponse(), specificallySpecifically,
ResponseHeaderBag::add()callsResponseHeaderBag::set()which callsResponseHeaderBag::add()which callsResponseHeaderBag::computeCacheControlValue(). That code does this:Because it decided unilaterally to add the
, privatesuffix (despite it being told not to!), this causesFinishResponseSubscriber::isCacheControlCustomized()to conclude that cache control was customized:Man, what a nightmare. Recap:
Cache-Control: no-cache(despite not specifying thatno-cachevalue)Cache-Control: no-cache, private, simply because we passed in theno-cachevalue we got from the original responseThis makes no sense at all.
This explains why Symfony itself doesn't use
add()+all(), they useclone. Switching tocloneing the headers fixes the problem.In this reroll, which should finally be green on 8.3.x:
interdiff-8.3.x.txtinterdiff is what changes, specifically the line related to headers. This line was already being modified (since #137), as of now it's being modified differently.Comment #161
catchOK the interdiffs are quite minor even if the issues they expose aren't. Committed/pushed to 8.3.x and 8.2.x, nice to finally get this in (again).
Comment #162
wim leersComment #163
wim leersUnpostponed:
We should quickly commit #2824576: Delete old REST test coverage: (Read|Create|Update|Delete)Test, deprecate RESTTestBase, to avoid having two separate sets of test coverage. That patch is currently being tested.
Comment #164
Anonymous (not verified) commentedStunning performance!
Comment #165
dawehnerCongrats @Wim Leers!
Comment #166
almaudoh commented@WimLeers +10^10000! Nicely done :)
Comment #167
klausiThis introduced a permanent test fail: #2832013: CommentResourceTestBase::testPostDxWithoutCriticalBaseFields() always fails on PHP 5.6 & MySQL 5.5 (works fine on other PHP versions)
Comment #168
wim leers@vaplas: OMG! :D Thanks so much, I've added that to my permanent collection of llama pictures! Where did you find this? Did you create it yourself?
@dawehner: Thanks! And thanks for your help here in the form of reviews!
@almaudoh: :) Thanks!
@klausi: I'm on it.
Comment #169
e0ipso@Wim Leers thanks for the unmeasurable amount of work dedicated to this! We are so much stronger after this.
I hope you enjoyed your Graham's!
Comment #170
wim leersThanks, and yes I did :)
Comment #171
Anonymous (not verified) commented#168, only slightly corrected the existing Llama.
And a couple of Friday's analogies for the llamas of the Drupal core:
Postponed:
Comment #173
wim leersA successor to this: #2845384: Every normalizer must have strict test coverage.
Comment #174
webchick