Skip to content

Commit

Permalink
fix(core): Fix entity hydration postgres edge-case
Browse files Browse the repository at this point in the history
Fixes #2546
  • Loading branch information
michaelbromley committed Nov 24, 2023
1 parent a9e67fe commit 9546d1b
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 1 deletion.
46 changes: 45 additions & 1 deletion packages/core/e2e/entity-hydrator.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down

0 comments on commit 9546d1b

Please sign in to comment.