Skip to content

Commit

Permalink
fix(core): Fix serialization of ShippingMethod
Browse files Browse the repository at this point in the history
Relates to #3277
  • Loading branch information
michaelbromley committed Dec 17, 2024
1 parent 7ea0710 commit 0c122bc
Show file tree
Hide file tree
Showing 2 changed files with 239 additions and 0 deletions.
230 changes: 230 additions & 0 deletions packages/core/e2e/entity-serialization.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import {
Channel,
ChannelService,
discountOnItemWithFacets,
hasFacetValues,
Order,
OrderLine,
OrderService,
ProductVariant,
ProductVariantService,
Promotion,
PromotionService,
RequestContextService,
ShippingLine,
ShippingMethod,
ShippingMethodService,
TransactionalConnection,
} from '@vendure/core';
import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
import { fail } from 'assert';
import path from 'path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

import { initialData } from '../../../e2e-common/e2e-initial-data';
import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';

import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
import * as Codegen from './graphql/generated-e2e-admin-types';
import { LanguageCode } from './graphql/generated-e2e-admin-types';
import * as CodegenShop from './graphql/generated-e2e-shop-types';
import { CREATE_PROMOTION } from './graphql/shared-definitions';
import {
ADD_ITEM_TO_ORDER,
ADD_PAYMENT,
SET_SHIPPING_METHOD,
TRANSITION_TO_STATE,
} from './graphql/shop-definitions';

/**
* This suite tests to make sure entities can be serialized. It focuses on those
* entities that contain methods and/or properties which potentially reference
* non-serializable objects.
*
* See https://github.com/vendure-ecommerce/vendure/issues/3277
*/
describe('Entity serialization', () => {
type OrderSuccessResult =
| CodegenShop.UpdatedOrderFragment
| CodegenShop.TestOrderFragmentFragment
| CodegenShop.TestOrderWithPaymentsFragment
| CodegenShop.ActiveOrderCustomerFragment
| CodegenShop.OrderWithAddressesFragment;
const orderResultGuard: ErrorResultGuard<OrderSuccessResult> = createErrorResultGuard(
input => !!input.lines,
);

const { server, adminClient, shopClient } = createTestEnvironment({
...testConfig(),
paymentOptions: {
paymentMethodHandlers: [testSuccessfulPaymentMethod],
},
});

beforeAll(async () => {
await server.init({
initialData: {
...initialData,
paymentMethods: [
{
name: testSuccessfulPaymentMethod.code,
handler: { code: testSuccessfulPaymentMethod.code, arguments: [] },
},
],
},
productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
customerCount: 1,
});
await adminClient.asSuperAdmin();
}, TEST_SETUP_TIMEOUT_MS);

afterAll(async () => {
await server.destroy();
});

it('serialize ShippingMethod', async () => {
const ctx = await createCtx();
const result = await server.app.get(ShippingMethodService).findAll(ctx);

expect(result.items.length).toBeGreaterThan(0);
const shippingMethod = result.items[0];
expect(shippingMethod instanceof ShippingMethod).toBe(true);

assertCanBeSerialized(shippingMethod);
const json = JSON.stringify(shippingMethod);
const parsed = JSON.parse(json);
expect(parsed.createdAt).toBe(shippingMethod.createdAt.toISOString());
});

it('serialize Channel', async () => {
const result = await server.app.get(ChannelService).getDefaultChannel();
expect(result instanceof Channel).toBe(true);

assertCanBeSerialized(result);
});

it('serialize Order', async () => {
await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
await shopClient.query<
CodegenShop.AddItemToOrderMutation,
CodegenShop.AddItemToOrderMutationVariables
>(ADD_ITEM_TO_ORDER, {
productVariantId: 'T_1',
quantity: 1,
});
await shopClient.query<
CodegenShop.SetShippingMethodMutation,
CodegenShop.SetShippingMethodMutationVariables
>(SET_SHIPPING_METHOD, {
id: 'T_1',
});
const result = await shopClient.query<
CodegenShop.TransitionToStateMutation,
CodegenShop.TransitionToStateMutationVariables
>(TRANSITION_TO_STATE, { state: 'ArrangingPayment' });

const { addPaymentToOrder } = await shopClient.query<
CodegenShop.AddPaymentToOrderMutation,
CodegenShop.AddPaymentToOrderMutationVariables
>(ADD_PAYMENT, {
input: {
method: testSuccessfulPaymentMethod.code,
metadata: {
foo: 'bar',
},
},
});
orderResultGuard.assertSuccess(addPaymentToOrder);

const ctx = await createCtx();
const order = await server.app.get(OrderService).findOneByCode(ctx, addPaymentToOrder.code);

expect(order).not.toBeNull();
expect(order instanceof Order).toBe(true);
assertCanBeSerialized(order);
});

it('serialize OrderLine', async () => {
const ctx = await createCtx();
const orderLine = await server.app.get(TransactionalConnection).getEntityOrThrow(ctx, OrderLine, 1);

expect(orderLine instanceof OrderLine).toBe(true);
assertCanBeSerialized(orderLine);
});

it('serialize ProductVariant', async () => {
const ctx = await createCtx();
const productVariant = await server.app.get(ProductVariantService).findOne(ctx, 1);

expect(productVariant instanceof ProductVariant).toBe(true);
assertCanBeSerialized(productVariant);
});

it('serialize Promotion', async () => {
await adminClient.query<Codegen.CreatePromotionMutation, Codegen.CreatePromotionMutationVariables>(
CREATE_PROMOTION,
{
input: {
enabled: true,
startsAt: new Date('2019-10-30T00:00:00.000Z'),
endsAt: new Date('2019-12-01T00:00:00.000Z'),
translations: [
{
languageCode: LanguageCode.en,
name: 'test promotion',
description: 'a test promotion',
},
],
conditions: [
{
code: hasFacetValues.code,
arguments: [
{ name: 'minimum', value: '2' },
{ name: 'facets', value: `["T_1"]` },
],
},
],
actions: [
{
code: discountOnItemWithFacets.code,
arguments: [
{ name: 'discount', value: '50' },
{ name: 'facets', value: `["T_1"]` },
],
},
],
},
},
);

const ctx = await createCtx();
const promotion = await server.app.get(PromotionService).findOne(ctx, 1);

expect(promotion instanceof Promotion).toBe(true);
assertCanBeSerialized(promotion);
});

it('serialize ShippingLine', async () => {
const ctx = await createCtx();
const shippingLine = await server.app
.get(TransactionalConnection)
.getEntityOrThrow(ctx, ShippingLine, 1);

expect(shippingLine instanceof ShippingLine).toBe(true);
assertCanBeSerialized(shippingLine);
});

function assertCanBeSerialized(entity: any) {
try {
const json = JSON.stringify(entity);
} catch (e: any) {
fail(`Could not serialize entity: ${e.message as string}`);
}
}

async function createCtx() {
return server.app.get(RequestContextService).create({
apiType: 'admin',
});
}
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ConfigurableOperation } from '@vendure/common/lib/generated-types';
import { omit } from '@vendure/common/lib/omit';
import { DeepPartial } from '@vendure/common/lib/shared-types';
import { Column, Entity, JoinTable, ManyToMany, OneToMany } from 'typeorm';

Expand Down Expand Up @@ -96,4 +97,12 @@ export class ShippingMethod
return false;
}
}

/**
* This is a fix for https://github.com/vendure-ecommerce/vendure/issues/3277,
* to prevent circular references which cause the JSON.stringify() to fail.
*/
protected toJSON() {
return omit(this, ['allCheckers', 'allCalculators'] as any);
}
}

0 comments on commit 0c122bc

Please sign in to comment.