fulfillment-process.e2e-spec.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /* tslint:disable:no-non-null-assertion */
  2. import { CustomFulfillmentProcess, FulfillmentState, mergeConfig } from '@vendure/core';
  3. import { createTestEnvironment } from '@vendure/testing';
  4. import gql from 'graphql-tag';
  5. import path from 'path';
  6. import { initialData } from '../../../e2e-common/e2e-initial-data';
  7. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  8. import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
  9. import {
  10. CreateFulfillment,
  11. GetCustomerList,
  12. GetOrderFulfillments,
  13. TransitFulfillment,
  14. } from './graphql/generated-e2e-admin-types';
  15. import { AddItemToOrder } from './graphql/generated-e2e-shop-types';
  16. import {
  17. CREATE_FULFILLMENT,
  18. GET_CUSTOMER_LIST,
  19. GET_ORDER_FULFILLMENTS,
  20. TRANSIT_FULFILLMENT,
  21. } from './graphql/shared-definitions';
  22. import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions';
  23. import { addPaymentToOrder, proceedToArrangingPayment } from './utils/test-order-utils';
  24. const initSpy = jest.fn();
  25. const transitionStartSpy = jest.fn();
  26. const transitionEndSpy = jest.fn();
  27. const transitionEndSpy2 = jest.fn();
  28. const transitionErrorSpy = jest.fn();
  29. describe('Fulfillment process', () => {
  30. const VALIDATION_ERROR_MESSAGE = 'Fulfillment must have a tracking code';
  31. const customOrderProcess: CustomFulfillmentProcess<'AwaitingPickup'> = {
  32. init(injector) {
  33. initSpy(injector.getConnection().name);
  34. },
  35. transitions: {
  36. Pending: {
  37. to: ['AwaitingPickup'],
  38. mergeStrategy: 'replace',
  39. },
  40. AwaitingPickup: {
  41. to: ['Shipped'],
  42. },
  43. },
  44. onTransitionStart(fromState, toState, data) {
  45. transitionStartSpy(fromState, toState, data);
  46. if (fromState === 'AwaitingPickup' && toState === 'Shipped') {
  47. if (!data.fulfillment.trackingCode) {
  48. return VALIDATION_ERROR_MESSAGE;
  49. }
  50. }
  51. },
  52. onTransitionEnd(fromState, toState, data) {
  53. transitionEndSpy(fromState, toState, data);
  54. },
  55. onTransitionError(fromState, toState, message) {
  56. transitionErrorSpy(fromState, toState, message);
  57. },
  58. };
  59. const customOrderProcess2: CustomFulfillmentProcess<'AwaitingPickup'> = {
  60. transitions: {
  61. AwaitingPickup: {
  62. to: ['Cancelled'],
  63. },
  64. },
  65. onTransitionEnd(fromState, toState, data) {
  66. transitionEndSpy2(fromState, toState, data);
  67. },
  68. };
  69. const { server, adminClient, shopClient } = createTestEnvironment(
  70. mergeConfig(testConfig, {
  71. shippingOptions: {
  72. ...testConfig.shippingOptions,
  73. customFulfillmentProcess: [customOrderProcess as any, customOrderProcess2 as any],
  74. },
  75. paymentOptions: {
  76. paymentMethodHandlers: [testSuccessfulPaymentMethod],
  77. },
  78. }),
  79. );
  80. beforeAll(async () => {
  81. await server.init({
  82. initialData,
  83. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  84. customerCount: 1,
  85. });
  86. await adminClient.asSuperAdmin();
  87. // Create a couple of orders to be queried
  88. const result = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(
  89. GET_CUSTOMER_LIST,
  90. {
  91. options: {
  92. take: 3,
  93. },
  94. },
  95. );
  96. const customers = result.customers.items;
  97. /**
  98. * Creates a Orders to test Fulfillment Process
  99. */
  100. await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
  101. // Add Items
  102. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  103. productVariantId: 'T_1',
  104. quantity: 1,
  105. });
  106. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  107. productVariantId: 'T_2',
  108. quantity: 1,
  109. });
  110. // Transit to payment
  111. await proceedToArrangingPayment(shopClient);
  112. await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  113. // Add a fulfillment without tracking code
  114. await adminClient.query<CreateFulfillment.Mutation, CreateFulfillment.Variables>(CREATE_FULFILLMENT, {
  115. input: {
  116. lines: [{ orderLineId: 'T_1', quantity: 1 }],
  117. method: 'Test1',
  118. },
  119. });
  120. // Add a fulfillment with tracking code
  121. await adminClient.query<CreateFulfillment.Mutation, CreateFulfillment.Variables>(CREATE_FULFILLMENT, {
  122. input: {
  123. lines: [{ orderLineId: 'T_2', quantity: 1 }],
  124. method: 'Test1',
  125. trackingCode: '222',
  126. },
  127. });
  128. }, TEST_SETUP_TIMEOUT_MS);
  129. afterAll(async () => {
  130. await server.destroy();
  131. });
  132. describe('CustomFulfillmentProcess', () => {
  133. it('replaced transition target', async () => {
  134. const { order } = await adminClient.query<
  135. GetOrderFulfillments.Query,
  136. GetOrderFulfillments.Variables
  137. >(GET_ORDER_FULFILLMENTS, {
  138. id: 'T_1',
  139. });
  140. const [fulfillment] = order?.fulfillments || [];
  141. expect(fulfillment.nextStates).toEqual(['AwaitingPickup']);
  142. });
  143. it('custom onTransitionStart handler returning error message', async () => {
  144. // First transit to AwaitingPickup
  145. await adminClient.query<TransitFulfillment.Mutation, TransitFulfillment.Variables>(
  146. TRANSIT_FULFILLMENT,
  147. {
  148. id: 'T_1',
  149. state: 'AwaitingPickup',
  150. },
  151. );
  152. transitionStartSpy.mockClear();
  153. transitionErrorSpy.mockClear();
  154. transitionEndSpy.mockClear();
  155. try {
  156. await adminClient.query<TransitFulfillment.Mutation, TransitFulfillment.Variables>(
  157. TRANSIT_FULFILLMENT,
  158. {
  159. id: 'T_1',
  160. state: 'Shipped',
  161. },
  162. );
  163. fail('Should have thrown');
  164. } catch (e) {
  165. expect(e.message).toContain(VALIDATION_ERROR_MESSAGE);
  166. }
  167. expect(transitionStartSpy).toHaveBeenCalledTimes(1);
  168. expect(transitionErrorSpy).toHaveBeenCalledTimes(1);
  169. expect(transitionEndSpy).not.toHaveBeenCalled();
  170. expect(transitionErrorSpy.mock.calls[0]).toEqual([
  171. 'AwaitingPickup',
  172. 'Shipped',
  173. VALIDATION_ERROR_MESSAGE,
  174. ]);
  175. });
  176. it('custom onTransitionStart handler allows transition', async () => {
  177. transitionEndSpy.mockClear();
  178. // First transit to AwaitingPickup
  179. await adminClient.query<TransitFulfillment.Mutation, TransitFulfillment.Variables>(
  180. TRANSIT_FULFILLMENT,
  181. {
  182. id: 'T_2',
  183. state: 'AwaitingPickup',
  184. },
  185. );
  186. transitionEndSpy.mockClear();
  187. const { transitionFulfillmentToState } = await adminClient.query<
  188. TransitFulfillment.Mutation,
  189. TransitFulfillment.Variables
  190. >(TRANSIT_FULFILLMENT, {
  191. id: 'T_2',
  192. state: 'Shipped',
  193. });
  194. expect(transitionEndSpy).toHaveBeenCalledTimes(1);
  195. expect(transitionEndSpy.mock.calls[0].slice(0, 2)).toEqual(['AwaitingPickup', 'Shipped']);
  196. expect(transitionFulfillmentToState?.state).toBe('Shipped');
  197. });
  198. it('composes multiple CustomFulfillmentProcesses', async () => {
  199. const { order } = await adminClient.query<
  200. GetOrderFulfillments.Query,
  201. GetOrderFulfillments.Variables
  202. >(GET_ORDER_FULFILLMENTS, {
  203. id: 'T_1',
  204. });
  205. const [fulfillment] = order?.fulfillments || [];
  206. expect(fulfillment.nextStates).toEqual(['Shipped', 'Cancelled']);
  207. });
  208. });
  209. });