fulfillment-process.e2e-spec.ts 9.2 KB

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