Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
Please refer the problem/motivation section of #2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method
Proposed resolution
- Write
EntityResourceTestBase
subclass for theMessage
entity. - See #28:
Message
entities can only bePOST
ed — therefore the REST resource plugin forMessage
entities should indicate that only thePOST
method is available.
Remaining tasks
References
1. Follow-up of #2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method
2. Subtask of #2824572: Write EntityResourceTestBase subclasses for every other entity type.
Comment | File | Size | Author |
---|---|---|---|
#49 | 2843755-49.patch | 16.96 KB | Wim Leers |
Comments
Comment #3
shadcn CreditAttribution: shadcn at Chapter Three commentedWorking on this.
Comment #4
Wim LeersYay!
Comment #5
shadcn CreditAttribution: shadcn at Chapter Three commented@Wim Leers given that core does not store Message entities, what do we test here?
Comment #6
Wim LeersI think the purpose here is to be able to send messages as if you were submitting a "contact" form, but via REST, so you can have your own interface.
Which means we need to test only
POST
, notGET
, norPATCH
norDELETE
. We can override those test methods and callself::markTestSkipped();
.To be able to test the
POST
method, we'll need to modify\Drupal\contact\MessageForm::save()
. It currently does this:The entity type annotation has this:
We need most of the logic in
MessageForm::save()
to be moved to the storage handler, so that the e-mail is sent from there, and so that the flood control is also handled there. Then only the redirecting and message setting is left inMessageForm::save()
.Of course, this will need sign-off from the
contact.module
maintainers. So please don't start working on it just yet.Comment #7
shadcn CreditAttribution: shadcn at Chapter Three commentedDo we need a separate issue for this?
Comment #8
andypostThere was intend to make mail send optional #2325805: Make email sending optional on a per-contact form basis
So probably mail send better to move out of form submit to pre|post save somehow
Flood control could be moved to entity validation level but looks that too late.
IMO flood control should fire early, so kinda #2431357: Extract a reusable UserAuthFlood service and reuse it in UserLoginForm and BasicAuth and UserAuthenticationController better to decouple it and use at form level & a rest plugin (not sure I understand in which plugin that may live)
Comment #9
larowlanSo believe it or not, contact storage was formed from a test module in core. So you should be able to turn that on in your test and have message storage.
With regards to email, yeah splitting that out is good. And the flood stuff, validation makes sense, but making that generic by way of the issue andypost linked is probably best
Comment #10
jibran+1 to what @larowlan and @andypost said. We all want contact storage in the core and we are constantly improving that in contrib as well but in this case, I'd agree with @andypost and @larowlan we are better of moving the functionality than introducing storage. This means we need a title update so tagging it.
Comment #11
Wim LeersThe issue title is still accurate, the implementations simply changed :)
Note that this blocks REST test coverage and of course functionality from being completed.
Comment #12
andypostbtw Since 4.x in action we can decide how properly "Decouple mail sending" into configurable action
It just needs test for allowed tokens and UI
Comment #13
andypostComment #14
benelori CreditAttribution: benelori commentedComment #15
benelori CreditAttribution: benelori commentedComment #16
benelori CreditAttribution: benelori commentedComment #17
Wim LeersCan one of the
contact.module
maintainers provide a status update for this?Comment #18
larowlanHey, the 'contact as an 80% replacement for webform' initiative never got buy in.
Not sure what you need from us (contact module maintainers) here?
Comment #19
Wim LeersWell, if no messages are stored, then there are no
\Drupal\contact\Entity\Message
entities one canGET
via REST either.Per #18, that will not happen, at least not in the foreseeable future.
Consequently, we should:
\Drupal\contact\ContactMessageAccessControlHandler
to forbidview
,update
anddelete
, just like\Drupal\content_moderation\ContentModerationStateAccessControlHandler
does since #2779931: Add storage exception that enforces unique content_entity_type_id and content_entity_id on the content moderation state content entity, and add access control handler to forbid all access.POST
case, because that is supported by core, and is also explicitly allowed by\Drupal\contact\ContactMessageAccessControlHandler::checkCreateAccess()
. But doing so requires #6 to be solved. Which is what we need from thecontact.module
maintainers.Comment #20
andypostI filed #2878513: Forbid view/edit/delete in ContactMessageAccessControlHandler
but create access could be fixed here, maybe re-title the issue again?
Comment #21
Wim LeersCurrent create access is correct/not a problem… so I'm not sure what you mean?
Comment #22
larowlanYeah, I'm fine with the plan in comment 6.
Ideally it would be an event to avoid the coupling, but we don't have entity events - so in the storage handler is fine.
Can you add those new features via composition instead of inheritance, because we'll need to do similar in contact_storage.
i.e. if you extend ContentEntityNullStorage and add the functionality via a trait, we can do the same in contact_storage, where we extend SqlContentEntityStorage.
Thanks
Comment #23
Wim Leers#22: thanks for the confirmation! It sounds like the contact module maintainers are not interested in taking this on? (Which is fine! Just checking.)
Comment #24
larowlanI'm interested, but that shouldn't stop anyone else grabbing it - there's only so much time in the day :)
Comment #25
Wim LeersOk! :)
Comment #27
Wim LeersTaking a first stab at this. This is one of the last few blockers of #2824572: Write EntityResourceTestBase subclasses for every other entity type..
Comment #28
Wim LeersI had to take some special measures because
Message
entities have special limitations:$this->markTestSkipped()
Message
entities are not stored, I had to make 3 changes toEntityResourceTestBase
:getExpectedNormalizedEntity()
does not need to be implementedlabel
entity key, entity validation didn't work as expected: it'd fail to validate thesubject
field, of which there must evidently only be oneThis should be green :) Next step: expanding this to all other authentication mechanisms, and to also test HAL+JSON.
Comment #29
Wim LeersDone.
Comment #30
Wim LeersRather than allowing
GET
,PATCH
andDELETE
routes to be created forMessage
entities, and those routes then not being able to work at all, we should remove those routes.Comment #31
Wim LeersI think the title can actually now be just this. The scope in #6 was broader: it also aimed to move the "send e-mail" logic. But that's something that's really pretty much out of scope. The patches above are testing the REST aspect. Whether e-mail is sent or not, that's something that should be fixed and tested within the Contact module.
Comment #32
andypostNit, ContactMessageResource::class
why that needed?
Comment #33
BerdirThe label thing is a good question. Note that this might not be visible here because there is no storage, but adding entity keys changes the schema (makes fields required), which would be a problem for contact_storage in two ways:
a) It would require an update function (which core could do, even though default storage is null, it would just do nothing then),
b) hiding the subject field, which is a very common thing to do, would no longer work as it would result in an error.
Comment #34
jibranJust one nit and rest of it looking really solid. Thanks, @Wim Leers reviewing this was fun. Let's address #32 and this one nit before RTBC.
Let's add a comment here as well.
Indeed!
lol
Comment #35
Wim Leers#31
#33: Hm, so what's the conclusion? Does this patch need to do something extra? It sounds like only
contact_storage
would need to be updated. Would that be acceptable? Do you want me to create an issue?#34:
Comment #36
jibranThanks for addressing the feedback. Can we also update the IS please so that we can remove the tag? I think we can RTBC it whenever @Berdir is ready.
Comment #37
BerdirI don't understand the validation thing, why wouldn't it be validated if the entity label isn't there? user name is also not defined as entity label, how is that different?
What I mentioned as b) is a blocker. We can not make label an entity key because label can not be required as it can be hidden in the UI. This is the same for node, but it was like that since 8.0, this would break tons of existing contact forms.
Comment #38
Wim LeersAttached is a patch that reverts this change.
If this passes, great, then we don't need that change. Would love to be wrong :)
Comment #40
Wim LeersAssigning to @Berdir for feedback.
Comment #41
BerdirAt least the first test fail has nothing to do with validation that's a problem with your test setup.
You already had that once with user entities and added a workaround:
$label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName;
But static::$labelFieldName is empty, so you end up sending a structure with an empty array key and \Drupal\serialization\Normalizer\FieldableEntityNormalizerTrait::denormalizeFieldData() explodes on that.
Beside setting that, I'd suggest two additional things:
* Add a hasField() check in \Drupal\serialization\Normalizer\FieldableEntityNormalizerTrait::denormalizeFieldData() to avoid an exception or at least provide a more meaningful exception or maybe ignore that data and leave it to custom normalizer subclasses to deal with it?
* Wrap that getKey() code in a helper method, it exists a few times and throw an exception when you end up with an empty label key. Then you could also directly override that method instead of the property in theory.
After that it fails with a different error:
1) Drupal\Tests\hal\Functional\EntityResource\Message\MessageHalJsonAnonTest::testPost
Failed asserting that 201 is identical to 422.
/core/modules/rest/tests/src/Functional/ResourceTestBase.php:353
core/modules/rest/tests/src/Functional/ResourceTestBase.php:372
/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php:801
I manually verified that a Message object with more than one subject values fails validation, so possibly it is another test setup problem?
Also, we actually have a test storage backend for message, and it might actually make sense to have at least optionally a version of that test that runs with that module enabled?
Comment #42
Wim LeersThanks for the super fast feedback! 👏
I'll look at this ASAP, might be a while because DrupalCon.
Comment #43
Wim LeersOnce again, Berdir++ Can't believe I missed that.
Comment #44
Wim LeersAlso, one
\n
too many, caused a coding standard fail.Comment #45
Wim LeersNo more failures after that single tiny change for me. IMHO this patch is now completely ready: it's 99% test coverage, 1% code addition (to ensure
Message
entities can only bePOST
ed). 0 API changes, 0 data structure changes.This seems safe to RTBC to me.
Comment #46
jibranThanks, for addressing the feedback @Wim Leers.
Comment #47
larowlanShould we use
$this->fail()
here - it's more explicitor alternatively
$this->setExpectedException()
https://thephp.cc/news/2016/02/questioning-phpunit-best-practices
Comment #48
Wim Leers❤️
PHPStorm--
I thought there was something like that, but PHPStorm's autocomplete is apparently unable to find
->fail()
… Much better!Comment #49
Wim Leers#48 was already stupid of me, but I can't believe I didn't even think about
$this->setExpectedException()
is … kind of inexplicable/unforgivable. That's of course by far the better solution!Comment #50
jibranRTBC +1
Comment #51
larowlanAdding review credits
Comment #53
larowlanCommitted as 4d43652 and pushed to 8.5.x. Thanks!
Comment #54
Anonymous (not verified) CreditAttribution: Anonymous commentedGreat works and cool news! 🚀🚀🚀
Good thing I did not bet on the "order-fixed" in #2824572-55: Write EntityResourceTestBase subclasses for every other entity type. :P
Comment #55
Wim LeersHurray! One less blocker for #2824572: Write EntityResourceTestBase subclasses for every other entity type., #2868035: Test that all core content+config entity types have functional REST test coverage and #2910883: Move all entity type REST tests to the providing modules.