Hello,

I am testing the POST/PATCH of relationships for #2856715: Push form. For taxonomy or content reference: OK.

But for an image field it is broken. I also test with a dynamic entity reference field and in both cases I got the exception:

The provided type (%s) does not match the destination resource types (%s).

(I will provide a small patch for the typo ;))

I will try to found why.

Comments

Grimreaper created an issue. See original summary.

grimreaper’s picture

StatusFileSize
new1.11 KB
grimreaper’s picture

The problem is in jsonapi/src/Normalizer/EntityReferenceFieldNormalizer.php getAllowedResourceTypes(), The "item_definition" structure is different depending of the field type.

So currently it works only for basic entity reference fields.

I don't see how to get the types dynamically for all entity reference fields sub-class.

Maybe a temporary solution would be a switch on the field type. The field type machine name is contained in $item_definition->definition['type'] (maybe not accessible this way, it is a xdebug return that I copy pasted in the issue) field_item:dynamic_entity_reference

Or creating an eventdispatcher to get to be able to register other entity reference types.

e0ipso’s picture

@Grimreaper, would you be kind enough to check if this issue is still relevant? If not, please feel free to close it.

grimreaper’s picture

@e0ipso, thanks for the reply. But it still does not work.

I use the config from http://cgit.drupalcode.org/entity_share/tree/modules/entity_share_server...

I do not have jsonapi_files or file_entity enabled.

I retried for a relationship on a taxonomy term: OK

POST on http://entity-share.lxc/jsonapi/node/ess_test/178d4419-457e-40be-9f24-20...

Where 178d4419-457e-40be-9f24-20e2045e397e is the UUID of an ess_test node with only a title.

Authorization: basic auth using the admin user
Headers: Content-Type:application/vnd.api+json
Body (raw):

{
    "data": [{
        "type": "taxonomy_term--ess_test",
        "id": "dfb649ce-d4ca-4bc0-b3f0-f56e8c525912"
    }]
}

Where dfb649ce-d4ca-4bc0-b3f0-f56e8c525912 is the UUID of a pre-created taxonomy term.

Request OK.

Now with an image file with the file already created through the upload on another content.

POST on http://entity-share.lxc/jsonapi/node/ess_test/178d4419-457e-40be-9f24-20...

Authorization: basic auth using the admin user
Headers: Content-Type:application/vnd.api+json
Body (raw):

{
    "data": {
        "type": "file--file",
        "id": "7c60643f-34b0-4670-a0a3-7d63aa280132",
        "meta": {
            "alt": "test",
            "title": "",
            "width": "2913",
            "height": "1864"
        }
    },
}

where 7c60643f-34b0-4670-a0a3-7d63aa280132 is the UUID of the file entity created by upload on another content.

The result is the HTML a Drupal error page:

Client error: A client error happened

And the status is 422 Unprocessable Entity.

wim leers’s picture

Issue tags: +Needs tests
grimreaper’s picture

Status: Active » Needs review
StatusFileSize
new3.46 KB

Hello,

And here is a failing test.

Status: Needs review » Needs work

The last submitted patch, 7: jsonapi-fix_typo-2897257-7.patch, failed testing. View results

wim leers’s picture

Title: Relationships: Impossible to POST/PATCH on image field » Impossible to reference File entities that don't use the public:// stream wrapper
Status: Needs work » Needs review
Issue tags: -Needs tests +API-First Initiative
StatusFileSize
new677 bytes
new2.57 KB

Yay, a failing test, thank you! 👏

The failure says it's a 422 instead of a 201 response. If I print the exact error response:

diff --git a/tests/src/Functional/JsonApiFunctionalTest.php b/tests/src/Functional/JsonApiFunctionalTest.php
index c5ff8fd..8871064 100644
--- a/tests/src/Functional/JsonApiFunctionalTest.php
+++ b/tests/src/Functional/JsonApiFunctionalTest.php
@@ -471,6 +471,7 @@ class JsonApiFunctionalTest extends JsonApiFunctionalTestBase {
       'headers' => ['Content-Type' => 'application/vnd.api+json'],
     ]);
     $created_response = Json::decode($response->getBody()->__toString());
+    var_dump((string)$response->getBody());
     $this->assertEquals(201, $response->getStatusCode());
     $this->assertArrayHasKey('uuid', $created_response['data']['attributes']);
     $uuid = $created_response['data']['attributes']['uuid'];
wim.leers at acquia in ~/Work/d8/modules/jsonapi on 8.x-1.x*

… then I see:

string(330) "{"errors":[{"title":"Unprocessable Entity","status":422,"detail":"field_image.0: You do not have access to the referenced entity (\u003Cem class=\u0022placeholder\u0022\u003Efile\u003C\/em\u003E: \u003Cem class=\u0022placeholder\u0022\u003E1\u003C\/em\u003E).","code":0,"source":{"pointer":"\/data\/attributes\/field_image\/0"}}]}"

In other words: the entity you're trying to reference is not accessible to you. That error is generated by \Drupal\Core\Entity\Plugin\Validation\Constraint\ReferenceAccessConstraint+\Drupal\Core\Entity\Plugin\Validation\Constraint\ReferenceAccessConstraintValidator.

I did some debugging and … the reason this test is failing is:

+++ b/tests/src/Functional/JsonApiFunctionalTestBase.php
@@ -129,6 +136,14 @@ abstract class JsonApiFunctionalTestBase extends BrowserTestBase {
+      'uri' => 'vfs://default_image.png',

because \Drupal\file\FileAccessControlHandler::checkAccess() only grants view access to public:// files. Updated the test to use that, and now it should be green.

So I'm going to bet that you're test with a private:// file.

Status: Needs review » Needs work

The last submitted patch, 9: 2897257-9.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Postponed (maintainer needs more info)

#9 now fails in testRead() because the expected normalization is now different. But it now does pass testWrite().

Needs confirmation from @Grimreaper that he's using private://.

wim leers’s picture

grimreaper’s picture

Hello,

Thanks @Wil Leers for the debug.

Unfortunately no, I was testing on a public:// image field before testing on private://

wim leers’s picture

Title: Impossible to reference File entities that don't use the public:// stream wrapper » Impossible to reference File entities
Issue tags: +Needs steps to reproduce, +Needs tests

#13: ok then this definitely Needs steps to reproduce at minimum, and Needs tests ideally (because then we don't need manual steps to reproduce anymore).

wim leers’s picture

#2932031: Comprehensive JSON API integration test coverage phase 2: for every entity type, complex use-cases added \Drupal\Tests\jsonapi\Functional\MediaTest. Updating its \Drupal\Tests\jsonapi\Functional\MediaTest::getPostDocument() would be sufficient to add test coverage for this!

wim leers’s picture

Status: Postponed (maintainer needs more info) » Needs work
wim leers’s picture

Status: Needs work » Needs review
Issue tags: -Needs steps to reproduce, -Needs tests
StatusFileSize
new4.01 KB

No movement, so I'm doing it for you. Passes tests just fine, so either this was indeed a permission problem as I said before, or JSON API fixed this in the mean time.

Ideally, this would have the client upload a file via a HTTP API. Support for that landed only days ago in Drupal core's REST module. Created #2958554: Allow creation of file entities from binary data via JSON API requests to port it to JSON API.

For now, this test can just have PHP create a File entity, just to test that referencing File entities works.

(No interdiff, because new patch.)

wim leers’s picture

StatusFileSize
new2.9 KB
new4.74 KB

Sadly, #17 fails due to a subtle difference in normalization. Not sure where/why/how this is happening, we need to dig into that.

wim leers’s picture

The last submitted patch, 17: 2897257-30.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 18: 2897257-18.patch, failed testing. View results

wim leers’s picture

Assigned: Unassigned » wim leers

Let's get this going again.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1 KB
new4.81 KB

The interdiff in #18 is completely wrong. Correct interdiff attached.

It's strange that #18 did not pass, because it passes locally for me, at least today. Maybe other changes in the past month made a difference.

Attached is also a rebased #18, which passes for me locally, on D8.6.

Status: Needs review » Needs work

The last submitted patch, 23: 2897257-18-rebased.patch, failed testing. View results

wim leers’s picture

Interesting! That failed. Let's see if the same happens on 8.6. Queued an 8.6 test.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new716 bytes
new5.23 KB

D'oh 😅

wim leers’s picture

Yay, green! We can't commit it just yet though, because:

+++ b/tests/src/Functional/ResourceTestBase.php
@@ -1693,7 +1693,8 @@ abstract class ResourceTestBase extends BrowserTestBase {
-        $this->assertSame($created_entity_document, $decoded_response_body);
+        // @todo remove the casts before comitting this!
+        $this->assertSame(static::castToString($created_entity_document), static::castToString($decoded_response_body));

This is that subtle difference in normalization that #18 referred to. Digging in…

wim leers’s picture

StatusFileSize
new1 KB
new4.54 KB

Locally, this seems to pass just fine now. Let's see if DrupalCI agrees with that.

I was stupidly testing MediaTest::testGetIndividual() instead of MediaTest::testPostIndividual() 😳

Status: Needs review » Needs work

The last submitted patch, 28: 2897257-28.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
Issue tags: +Entity Field API
StatusFileSize
new3.28 KB
new7.19 KB

Turns out the root cause is quite mundane: Entity/Field API. When it loads entities from the database, all field values of the in-memory EntityInterface object are strings! This is AFAIK even accepted as normal (even though it certainly is annoying).

The correct solution would be to make property normalization respect the underlying type, i.e. to do the appropriate casting. Even though Entity/Field API really should've taken care of that for us. So let's do that!

So I did.

And in doing so, found that several of HEAD's assertions also had incorrect values due to the same root cause, and those now are correct too! 🤘

wim leers’s picture

Title: Impossible to reference File entities » Add test coverage to ensure it's possible to reference File entities
Category: Bug report » Task

I think it's clear we've disproven the original bug report. Let's make this a task to add test coverage that proves this is possible. And to ensure we don't ever regress!

wim leers’s picture

StatusFileSize
new2.55 KB
new8.6 KB
  1. +++ b/src/Normalizer/EntityReferenceFieldNormalizer.php
    @@ -112,7 +113,9 @@ class EntityReferenceFieldNormalizer extends FieldNormalizer implements Denormal
    +          $metadata[$property_key] = $property instanceof PrimitiveInterface
    +          ? $property->getCastedValue()
    +          : $property->getValue();
    

    Indentation is off.

  2. There is actually one assertion in the PATCH test coverage that also does this string casting business … and I think it may be possible to remove that now!
    $this->assertArraySubset(static::castToString($field_normalization), $updated_entity->get($field_name)->getValue(), TRUE);
    

    (At which point \Drupal\Tests\jsonapi\Functional\ResourceTestBase::castToString() can be removed too, because zero callers remain!)

gabesullice’s picture

Status: Needs review » Needs work
  1. +++ b/src/Normalizer/EntityReferenceFieldNormalizer.php
    @@ -112,7 +113,9 @@ class EntityReferenceFieldNormalizer extends FieldNormalizer implements Denormal
    +          $metadata[$property_key] = $property instanceof PrimitiveInterface
    

    Do we need to be doing this type casting elsewhere too? Like FieldItemNormalizer? Perhaps it's only needed here because we're special casing entity reference?

    In FieldItemNormalizer, we pass field item properties into the serialization system:

    foreach ($field_item as $property_name => $property) {
      $values[$property_name] = $this->serializer->normalize($property, $format, $context);
    }
    
  2. +++ b/src/Normalizer/EntityReferenceFieldNormalizer.php
    @@ -112,7 +113,9 @@ class EntityReferenceFieldNormalizer extends FieldNormalizer implements Denormal
    +            ? $property->getCastedValue()
    

    I've always wondered when/where this method would actually come in handy. TIL.

  3. +++ b/tests/src/Functional/MediaTest.php
    @@ -60,9 +60,9 @@ class MediaTest extends ResourceTestBase {
    -          $this->grantPermissionsToTestedRole(['create media']);
    +          $this->grantPermissionsToTestedRole(['create media', 'access content']);
    ...
    -        $this->grantPermissionsToTestedRole(['create camelids media']);
    +        $this->grantPermissionsToTestedRole(['create camelids media', 'access content']);
    

    Can we add a comment explaining the new permission?

  4. +++ b/tests/src/Functional/MediaTest.php
    @@ -141,7 +148,7 @@ class MediaTest extends ResourceTestBase {
    -    $thumbnail = File::load(2);
    +    $thumbnail = File::load(3);
    

    Nit: Is there a way to make these integers dynamic or make then a static property?

  5. +++ b/tests/src/Functional/MediaTest.php
    @@ -203,8 +210,8 @@ class MediaTest extends ResourceTestBase {
    -                'width' => '180',
    -                'height' => '180',
    +                'width' => 180,
    +                'height' => 180,
    

    This is satisfying.

  6. +++ b/tests/src/Functional/ResourceTestBase.php
    @@ -2129,30 +2131,6 @@ abstract class ResourceTestBase extends BrowserTestBase {
    -  protected static function castToString(array $normalization) {
    

    😍

gabesullice’s picture

Status: Needs work » Active

I realized my review in #32 doesn't actually have any points that need work. It's mostly questions and suggestions, so it shouldn't technically be "Needs Work", but there's not a better status.

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Active » Needs review
StatusFileSize
new3.11 KB
new7.61 KB

#33:
Thanks for the review!

  1. ✔️ D'OH OF COURSE. Although doing this will make description: null (instead of description: '') and display: null (instead of display: false) return… But fixing that is arguably out of scope.
  2. Heh :)
  3. ✔️
  4. ✔️
  5. 🙂
  6. 🙂

Also fixed the CS violation.

gabesullice’s picture

Status: Needs review » Reviewed & tested by the community

  • Wim Leers committed 3f54ad2 on 8.x-1.x
    Issue #2897257 by Wim Leers, Grimreaper, gabesullice: Add test coverage...
  • Wim Leers committed 8a66340 on 8.x-2.x
    Issue #2897257 by Wim Leers, Grimreaper, gabesullice: Add test coverage...
wim leers’s picture

Status: Reviewed & tested by the community » Fixed

🍻

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

grimreaper’s picture

Hi,

Sorry for the delay, I just want to say thank you to all for your work on that.