fulfillment-process.e2e-spec.ts 9.7 KB

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