Kaynağa Gözat

fix(core): Fix entity hydration postgres edge-case

Fixes #2546
Michael Bromley 2 yıl önce
ebeveyn
işleme
9546d1b67f

+ 45 - 1
packages/core/e2e/entity-hydrator.e2e-spec.ts

@@ -8,6 +8,10 @@ import {
     ProductVariant,
     RequestContext,
     ActiveOrderService,
+    OrderService,
+    TransactionalConnection,
+    OrderLine,
+    RequestContextService,
 } from '@vendure/core';
 import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
 import gql from 'graphql-tag';
@@ -43,7 +47,7 @@ describe('Entity hydration', () => {
         await server.init({
             initialData,
             productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
-            customerCount: 1,
+            customerCount: 2,
         });
         await adminClient.asSuperAdmin();
     }, TEST_SETUP_TIMEOUT_MS);
@@ -290,6 +294,46 @@ describe('Entity hydration', () => {
             expect(order!.lines[1].productVariant.priceWithTax).toBeGreaterThan(0);
         });
     });
+
+    // https://github.com/vendure-ecommerce/vendure/issues/2546
+    it('Preserves ordering when merging arrays of relations', async () => {
+        await shopClient.asUserWithCredentials('trevor_donnelly96@hotmail.com', 'test');
+        await shopClient.query(AddItemToOrderDocument, {
+            productVariantId: '1',
+            quantity: 1,
+        });
+        const { addItemToOrder } = await shopClient.query(AddItemToOrderDocument, {
+            productVariantId: '2',
+            quantity: 2,
+        });
+        orderResultGuard.assertSuccess(addItemToOrder);
+        const internalOrderId = +addItemToOrder.id.replace(/^\D+/g, '');
+        const ctx = await server.app.get(RequestContextService).create({ apiType: 'admin' });
+        const order = await server.app
+            .get(OrderService)
+            .findOne(ctx, internalOrderId, ['lines.productVariant']);
+
+        for (const line of order?.lines ?? []) {
+            // Assert that things are as we expect before hydrating
+            expect(line.productVariantId).toBe(line.productVariant.id);
+        }
+
+        // modify the first order line to make postgres tend to return the lines in the wrong order
+        await server.app
+            .get(TransactionalConnection)
+            .getRepository(ctx, OrderLine)
+            .update(order!.lines[0].id, {
+                sellerChannelId: 1,
+            });
+
+        await server.app.get(EntityHydrator).hydrate(ctx, order!, {
+            relations: ['lines.sellerChannel'],
+        });
+
+        for (const line of order?.lines ?? []) {
+            expect(line.productVariantId).toBe(line.productVariant.id);
+        }
+    });
 });
 
 function getVariantWithName(product: Product, name: string) {

+ 21 - 0
packages/core/src/service/helpers/entity-hydrator/entity-hydrator.service.ts

@@ -302,6 +302,27 @@ export class EntityHydrator {
         if (!a) {
             return b;
         }
+        if (Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.length > 1) {
+            if (a[0].hasOwnProperty('id')) {
+                // If the array contains entities, we can use the id to match them up
+                // so that we ensure that we don't merge properties from different entities
+                // with the same index.
+                const aIds = a.map(e => e.id);
+                const bIds = b.map(e => e.id);
+                if (JSON.stringify(aIds) !== JSON.stringify(bIds)) {
+                    // The entities in the arrays are not in the same order, so we can't
+                    // safely merge them. We need to sort the `b` array so that the entities
+                    // are in the same order as the `a` array.
+                    const idToIndexMap = new Map();
+                    a.forEach((item, index) => {
+                        idToIndexMap.set(item.id, index);
+                    });
+                    b.sort((_a, _b) => {
+                        return idToIndexMap.get(_a.id) - idToIndexMap.get(_b.id);
+                    });
+                }
+            }
+        }
         for (const [key, value] of Object.entries(b)) {
             if (Object.getOwnPropertyDescriptor(b, key)?.writable) {
                 if (Array.isArray(value)) {