|
|
@@ -0,0 +1,249 @@
|
|
|
+/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
|
+import { mergeConfig, OrderService } from '@vendure/core';
|
|
|
+import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
|
|
|
+import { multivendorPaymentMethodHandler } from 'dev-server/example-plugins/multivendor-plugin/config/mv-payment-handler';
|
|
|
+import { CONNECTED_PAYMENT_METHOD_CODE } from 'dev-server/example-plugins/multivendor-plugin/constants';
|
|
|
+import { MultivendorPlugin } from 'dev-server/example-plugins/multivendor-plugin/multivendor.plugin';
|
|
|
+import gql from 'graphql-tag';
|
|
|
+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 {
|
|
|
+ AssignProductsToChannelDocument,
|
|
|
+ GetOrderWithSellerOrdersDocument,
|
|
|
+} from './graphql/generated-e2e-admin-types';
|
|
|
+import * as CodegenShop from './graphql/generated-e2e-shop-types';
|
|
|
+import { SetShippingMethodDocument } from './graphql/generated-e2e-shop-types';
|
|
|
+import {
|
|
|
+ ADD_ITEM_TO_ORDER,
|
|
|
+ ADD_PAYMENT,
|
|
|
+ GET_ELIGIBLE_SHIPPING_METHODS,
|
|
|
+ SET_SHIPPING_ADDRESS,
|
|
|
+ TRANSITION_TO_STATE,
|
|
|
+} from './graphql/shop-definitions';
|
|
|
+
|
|
|
+declare module '@vendure/core/dist/entity/custom-entity-fields' {
|
|
|
+ interface CustomShippingMethodFields {
|
|
|
+ minPrice: number;
|
|
|
+ maxPrice: number;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+describe('Multi-vendor orders', () => {
|
|
|
+ const { server, adminClient, shopClient } = createTestEnvironment(
|
|
|
+ mergeConfig(testConfig(), {
|
|
|
+ plugins: [
|
|
|
+ MultivendorPlugin.init({
|
|
|
+ platformFeePercent: 10,
|
|
|
+ platformFeeSKU: 'FEE',
|
|
|
+ }),
|
|
|
+ ],
|
|
|
+ }),
|
|
|
+ );
|
|
|
+
|
|
|
+ let bobsPartsChannel: { id: string; token: string; variantIds: string[] };
|
|
|
+ let alicesWaresChannel: { id: string; token: string; variantIds: string[] };
|
|
|
+ let orderId: string;
|
|
|
+
|
|
|
+ type OrderSuccessResult =
|
|
|
+ | CodegenShop.UpdatedOrderFragment
|
|
|
+ | CodegenShop.TestOrderFragmentFragment
|
|
|
+ | CodegenShop.TestOrderWithPaymentsFragment
|
|
|
+ | CodegenShop.ActiveOrderCustomerFragment;
|
|
|
+ const orderResultGuard: ErrorResultGuard<OrderSuccessResult> = createErrorResultGuard(
|
|
|
+ input => !!input.lines,
|
|
|
+ );
|
|
|
+
|
|
|
+ beforeAll(async () => {
|
|
|
+ await server.init({
|
|
|
+ initialData,
|
|
|
+ productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
|
|
|
+ customerCount: 3,
|
|
|
+ });
|
|
|
+ await adminClient.asSuperAdmin();
|
|
|
+ }, TEST_SETUP_TIMEOUT_MS);
|
|
|
+
|
|
|
+ afterAll(async () => {
|
|
|
+ await server.destroy();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('setup sellers', async () => {
|
|
|
+ const result1 = await shopClient.query(REGISTER_SELLER, {
|
|
|
+ input: {
|
|
|
+ shopName: "Bob's Parts",
|
|
|
+ seller: {
|
|
|
+ firstName: 'Bob',
|
|
|
+ lastName: 'Dobalina',
|
|
|
+ emailAddress: 'bob@bobs-parts.com',
|
|
|
+ password: 'test',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ });
|
|
|
+ bobsPartsChannel = result1.registerNewSeller;
|
|
|
+ expect(bobsPartsChannel.token).toBe('bobs-parts-token');
|
|
|
+
|
|
|
+ const result2 = await shopClient.query(REGISTER_SELLER, {
|
|
|
+ input: {
|
|
|
+ shopName: "Alice's Wares",
|
|
|
+ seller: {
|
|
|
+ firstName: 'Alice',
|
|
|
+ lastName: 'Smith',
|
|
|
+ emailAddress: 'alice@alices-wares.com',
|
|
|
+ password: 'test',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ });
|
|
|
+ alicesWaresChannel = result2.registerNewSeller;
|
|
|
+ expect(alicesWaresChannel.token).toBe('alices-wares-token');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('assign products to sellers', async () => {
|
|
|
+ const { assignProductsToChannel } = await adminClient.query(AssignProductsToChannelDocument, {
|
|
|
+ input: {
|
|
|
+ channelId: bobsPartsChannel.id,
|
|
|
+ productIds: ['T_1'],
|
|
|
+ priceFactor: 1,
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(assignProductsToChannel[0].channels.map(c => c.code)).toEqual([
|
|
|
+ '__default_channel__',
|
|
|
+ 'bobs-parts',
|
|
|
+ ]);
|
|
|
+ bobsPartsChannel.variantIds = assignProductsToChannel[0].variants.map(v => v.id);
|
|
|
+
|
|
|
+ expect(bobsPartsChannel.variantIds).toEqual(['T_1', 'T_2', 'T_3', 'T_4']);
|
|
|
+
|
|
|
+ const { assignProductsToChannel: result2 } = await adminClient.query(
|
|
|
+ AssignProductsToChannelDocument,
|
|
|
+ {
|
|
|
+ input: {
|
|
|
+ channelId: alicesWaresChannel.id,
|
|
|
+ productIds: ['T_11'],
|
|
|
+ priceFactor: 1,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ );
|
|
|
+ expect(result2[0].channels.map(c => c.code)).toEqual(['__default_channel__', 'alices-wares']);
|
|
|
+ alicesWaresChannel.variantIds = result2[0].variants.map(v => v.id);
|
|
|
+
|
|
|
+ expect(alicesWaresChannel.variantIds).toEqual(['T_22']);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('adds items and sets shipping methods', async () => {
|
|
|
+ await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
|
|
|
+ await shopClient.query<
|
|
|
+ CodegenShop.AddItemToOrderMutation,
|
|
|
+ CodegenShop.AddItemToOrderMutationVariables
|
|
|
+ >(ADD_ITEM_TO_ORDER, {
|
|
|
+ productVariantId: bobsPartsChannel.variantIds[0],
|
|
|
+ quantity: 1,
|
|
|
+ });
|
|
|
+ await shopClient.query<
|
|
|
+ CodegenShop.AddItemToOrderMutation,
|
|
|
+ CodegenShop.AddItemToOrderMutationVariables
|
|
|
+ >(ADD_ITEM_TO_ORDER, {
|
|
|
+ productVariantId: alicesWaresChannel.variantIds[0],
|
|
|
+ quantity: 1,
|
|
|
+ });
|
|
|
+
|
|
|
+ await shopClient.query<
|
|
|
+ CodegenShop.SetShippingAddressMutation,
|
|
|
+ CodegenShop.SetShippingAddressMutationVariables
|
|
|
+ >(SET_SHIPPING_ADDRESS, {
|
|
|
+ input: {
|
|
|
+ streetLine1: '12 the street',
|
|
|
+ postalCode: '123456',
|
|
|
+ countryCode: 'US',
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ const { eligibleShippingMethods } = await shopClient.query<CodegenShop.GetShippingMethodsQuery>(
|
|
|
+ GET_ELIGIBLE_SHIPPING_METHODS,
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(eligibleShippingMethods.map(m => m.code).sort()).toEqual([
|
|
|
+ 'alices-wares-shipping',
|
|
|
+ 'bobs-parts-shipping',
|
|
|
+ 'express-shipping',
|
|
|
+ 'standard-shipping',
|
|
|
+ ]);
|
|
|
+
|
|
|
+ const { setOrderShippingMethod } = await shopClient.query(SetShippingMethodDocument, {
|
|
|
+ id: [
|
|
|
+ eligibleShippingMethods.find(m => m.code === 'bobs-parts-shipping')!.id,
|
|
|
+ eligibleShippingMethods.find(m => m.code === 'alices-wares-shipping')!.id,
|
|
|
+ ],
|
|
|
+ });
|
|
|
+
|
|
|
+ orderResultGuard.assertSuccess(setOrderShippingMethod);
|
|
|
+ expect(setOrderShippingMethod.shippingLines.map(l => l.shippingMethod.code).sort()).toEqual([
|
|
|
+ 'alices-wares-shipping',
|
|
|
+ 'bobs-parts-shipping',
|
|
|
+ ]);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('completing checkout splits order', async () => {
|
|
|
+ const { transitionOrderToState } = await shopClient.query<
|
|
|
+ CodegenShop.TransitionToStateMutation,
|
|
|
+ CodegenShop.TransitionToStateMutationVariables
|
|
|
+ >(TRANSITION_TO_STATE, { state: 'ArrangingPayment' });
|
|
|
+
|
|
|
+ orderResultGuard.assertSuccess(transitionOrderToState);
|
|
|
+
|
|
|
+ const { addPaymentToOrder } = await shopClient.query<
|
|
|
+ CodegenShop.AddPaymentToOrderMutation,
|
|
|
+ CodegenShop.AddPaymentToOrderMutationVariables
|
|
|
+ >(ADD_PAYMENT, {
|
|
|
+ input: {
|
|
|
+ method: CONNECTED_PAYMENT_METHOD_CODE,
|
|
|
+ metadata: {},
|
|
|
+ },
|
|
|
+ });
|
|
|
+ orderResultGuard.assertSuccess(addPaymentToOrder);
|
|
|
+
|
|
|
+ expect(addPaymentToOrder.state).toBe('PaymentSettled');
|
|
|
+
|
|
|
+ const { order } = await adminClient.query(GetOrderWithSellerOrdersDocument, {
|
|
|
+ id: addPaymentToOrder.id,
|
|
|
+ });
|
|
|
+ orderId = order!.id;
|
|
|
+
|
|
|
+ expect(order?.sellerOrders?.length).toBe(2);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('order lines get split', async () => {
|
|
|
+ const { order } = await adminClient.query(GetOrderWithSellerOrdersDocument, {
|
|
|
+ id: orderId,
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(order?.sellerOrders?.[0].lines.map(l => l.productVariant.name)).toEqual([
|
|
|
+ 'Laptop 13 inch 8GB',
|
|
|
+ ]);
|
|
|
+ expect(order?.sellerOrders?.[1].lines.map(l => l.productVariant.name)).toEqual(['Road Bike']);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('shippingLines get split', async () => {
|
|
|
+ const { order } = await adminClient.query(GetOrderWithSellerOrdersDocument, {
|
|
|
+ id: orderId,
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(order?.sellerOrders?.[0]?.shippingLines.length).toBe(1);
|
|
|
+ expect(order?.sellerOrders?.[1]?.shippingLines.length).toBe(1);
|
|
|
+ expect(order?.sellerOrders?.[0]?.shippingLines[0].shippingMethod.code).toBe('bobs-parts-shipping');
|
|
|
+ expect(order?.sellerOrders?.[1]?.shippingLines[0].shippingMethod.code).toBe('alices-wares-shipping');
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+export const REGISTER_SELLER = gql`
|
|
|
+ mutation RegisterSeller($input: RegisterSellerInput!) {
|
|
|
+ registerNewSeller(input: $input) {
|
|
|
+ id
|
|
|
+ code
|
|
|
+ token
|
|
|
+ }
|
|
|
+ }
|
|
|
+`;
|