fulfillment-process.e2e-spec.ts 9.4 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 {
  14. CreateFulfillment,
  15. ErrorCode,
  16. FulfillmentFragment,
  17. GetCustomerList,
  18. GetOrderFulfillments,
  19. TransitFulfillment,
  20. } from './graphql/generated-e2e-admin-types';
  21. import { AddItemToOrder } from './graphql/generated-e2e-shop-types';
  22. import {
  23. CREATE_FULFILLMENT,
  24. GET_CUSTOMER_LIST,
  25. GET_ORDER_FULFILLMENTS,
  26. TRANSIT_FULFILLMENT,
  27. } from './graphql/shared-definitions';
  28. import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions';
  29. import { addPaymentToOrder, proceedToArrangingPayment } from './utils/test-order-utils';
  30. const initSpy = jest.fn();
  31. const transitionStartSpy = jest.fn();
  32. const transitionEndSpy = jest.fn();
  33. const transitionEndSpy2 = jest.fn();
  34. const transitionErrorSpy = jest.fn();
  35. describe('Fulfillment process', () => {
  36. const fulfillmentGuard: ErrorResultGuard<FulfillmentFragment> = createErrorResultGuard(
  37. input => !!input.id,
  38. );
  39. const VALIDATION_ERROR_MESSAGE = 'Fulfillment must have a tracking code';
  40. const customOrderProcess: CustomFulfillmentProcess<'AwaitingPickup'> = {
  41. init(injector) {
  42. initSpy(injector.get(TransactionalConnection).rawConnection.name);
  43. },
  44. transitions: {
  45. Pending: {
  46. to: ['AwaitingPickup'],
  47. mergeStrategy: 'replace',
  48. },
  49. AwaitingPickup: {
  50. to: ['Shipped'],
  51. },
  52. },
  53. onTransitionStart(fromState, toState, data) {
  54. transitionStartSpy(fromState, toState, data);
  55. if (fromState === 'AwaitingPickup' && toState === 'Shipped') {
  56. if (!data.fulfillment.trackingCode) {
  57. return VALIDATION_ERROR_MESSAGE;
  58. }
  59. }
  60. },
  61. onTransitionEnd(fromState, toState, data) {
  62. transitionEndSpy(fromState, toState, data);
  63. },
  64. onTransitionError(fromState, toState, message) {
  65. transitionErrorSpy(fromState, toState, message);
  66. },
  67. };
  68. const customOrderProcess2: CustomFulfillmentProcess<'AwaitingPickup'> = {
  69. transitions: {
  70. AwaitingPickup: {
  71. to: ['Cancelled'],
  72. },
  73. },
  74. onTransitionEnd(fromState, toState, data) {
  75. transitionEndSpy2(fromState, toState, data);
  76. },
  77. };
  78. const { server, adminClient, shopClient } = createTestEnvironment(
  79. mergeConfig(testConfig(), {
  80. shippingOptions: {
  81. ...testConfig().shippingOptions,
  82. customFulfillmentProcess: [customOrderProcess as any, customOrderProcess2 as any],
  83. },
  84. paymentOptions: {
  85. paymentMethodHandlers: [testSuccessfulPaymentMethod],
  86. },
  87. }),
  88. );
  89. beforeAll(async () => {
  90. await server.init({
  91. initialData: {
  92. ...initialData,
  93. paymentMethods: [
  94. {
  95. name: testSuccessfulPaymentMethod.code,
  96. handler: { code: testSuccessfulPaymentMethod.code, arguments: [] },
  97. },
  98. ],
  99. },
  100. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  101. customerCount: 1,
  102. });
  103. await adminClient.asSuperAdmin();
  104. // Create a couple of orders to be queried
  105. const result = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(
  106. GET_CUSTOMER_LIST,
  107. {
  108. options: {
  109. take: 3,
  110. },
  111. },
  112. );
  113. const customers = result.customers.items;
  114. /**
  115. * Creates a Orders to test Fulfillment Process
  116. */
  117. await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
  118. // Add Items
  119. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  120. productVariantId: 'T_1',
  121. quantity: 1,
  122. });
  123. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  124. productVariantId: 'T_2',
  125. quantity: 1,
  126. });
  127. // Transit to payment
  128. await proceedToArrangingPayment(shopClient);
  129. await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  130. // Add a fulfillment without tracking code
  131. await adminClient.query<CreateFulfillment.Mutation, CreateFulfillment.Variables>(CREATE_FULFILLMENT, {
  132. input: {
  133. lines: [{ orderLineId: 'T_1', quantity: 1 }],
  134. handler: {
  135. code: manualFulfillmentHandler.code,
  136. arguments: [{ name: 'method', value: 'Test1' }],
  137. },
  138. },
  139. });
  140. // Add a fulfillment with tracking code
  141. await adminClient.query<CreateFulfillment.Mutation, CreateFulfillment.Variables>(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. GetOrderFulfillments.Query,
  165. GetOrderFulfillments.Variables
  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<TransitFulfillment.Mutation, TransitFulfillment.Variables>(
  175. TRANSIT_FULFILLMENT,
  176. {
  177. id: 'T_1',
  178. state: 'AwaitingPickup',
  179. },
  180. );
  181. transitionStartSpy.mockClear();
  182. transitionErrorSpy.mockClear();
  183. transitionEndSpy.mockClear();
  184. const { transitionFulfillmentToState } = await adminClient.query<
  185. TransitFulfillment.Mutation,
  186. TransitFulfillment.Variables
  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<TransitFulfillment.Mutation, TransitFulfillment.Variables>(
  207. TRANSIT_FULFILLMENT,
  208. {
  209. id: 'T_2',
  210. state: 'AwaitingPickup',
  211. },
  212. );
  213. transitionEndSpy.mockClear();
  214. const { transitionFulfillmentToState } = await adminClient.query<
  215. TransitFulfillment.Mutation,
  216. TransitFulfillment.Variables
  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. GetOrderFulfillments.Query,
  229. GetOrderFulfillments.Variables
  230. >(GET_ORDER_FULFILLMENTS, {
  231. id: 'T_1',
  232. });
  233. const [fulfillment] = order?.fulfillments || [];
  234. expect(fulfillment.nextStates).toEqual(['Shipped', 'Cancelled']);
  235. });
  236. });
  237. });