order-fulfillment.e2e-spec.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import {
  2. defaultShippingCalculator,
  3. defaultShippingEligibilityChecker,
  4. FulfillmentHandler,
  5. LanguageCode,
  6. manualFulfillmentHandler,
  7. mergeConfig,
  8. } from '@vendure/core';
  9. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  10. import gql from 'graphql-tag';
  11. import path from 'path';
  12. import { initialData } from '../../../e2e-common/e2e-initial-data';
  13. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  14. import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
  15. import {
  16. CreateFulfillment,
  17. CreateFulfillmentError,
  18. CreateShippingMethod,
  19. ErrorCode,
  20. FulfillmentFragment,
  21. GetFulfillmentHandlers,
  22. TransitFulfillment,
  23. } from './graphql/generated-e2e-admin-types';
  24. import { AddItemToOrder, TestOrderWithPaymentsFragment } from './graphql/generated-e2e-shop-types';
  25. import {
  26. CREATE_FULFILLMENT,
  27. CREATE_SHIPPING_METHOD,
  28. TRANSIT_FULFILLMENT,
  29. } from './graphql/shared-definitions';
  30. import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions';
  31. import { addPaymentToOrder, proceedToArrangingPayment } from './utils/test-order-utils';
  32. const badTrackingCode = 'bad-code';
  33. const transitionErrorMessage = 'Some error message';
  34. const transitionSpy = jest.fn();
  35. const testFulfillmentHandler = new FulfillmentHandler({
  36. code: 'test-fulfillment-handler',
  37. description: [{ languageCode: LanguageCode.en, value: 'Test fulfillment handler' }],
  38. args: {
  39. trackingCode: {
  40. type: 'string',
  41. },
  42. },
  43. createFulfillment: (ctx, orders, items, args) => {
  44. if (args.trackingCode === badTrackingCode) {
  45. throw new Error('The code was bad!');
  46. }
  47. return {
  48. trackingCode: args.trackingCode,
  49. };
  50. },
  51. onFulfillmentTransition: (fromState, toState, { fulfillment }) => {
  52. transitionSpy(fromState, toState);
  53. if (toState === 'Shipped') {
  54. return transitionErrorMessage;
  55. }
  56. },
  57. });
  58. describe('Order fulfillments', () => {
  59. const orderGuard: ErrorResultGuard<TestOrderWithPaymentsFragment> = createErrorResultGuard(
  60. input => !!input.lines,
  61. );
  62. const fulfillmentGuard: ErrorResultGuard<FulfillmentFragment> = createErrorResultGuard(
  63. input => !!input.id,
  64. );
  65. let order: TestOrderWithPaymentsFragment;
  66. let f1Id: string;
  67. const { server, adminClient, shopClient } = createTestEnvironment(
  68. mergeConfig(testConfig(), {
  69. paymentOptions: {
  70. paymentMethodHandlers: [testSuccessfulPaymentMethod],
  71. },
  72. shippingOptions: {
  73. fulfillmentHandlers: [manualFulfillmentHandler, testFulfillmentHandler],
  74. },
  75. }),
  76. );
  77. beforeAll(async () => {
  78. await server.init({
  79. initialData: {
  80. ...initialData,
  81. paymentMethods: [
  82. {
  83. name: testSuccessfulPaymentMethod.code,
  84. handler: { code: testSuccessfulPaymentMethod.code, arguments: [] },
  85. },
  86. ],
  87. },
  88. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  89. customerCount: 2,
  90. });
  91. await adminClient.asSuperAdmin();
  92. await adminClient.query<CreateShippingMethod.Mutation, CreateShippingMethod.Variables>(
  93. CREATE_SHIPPING_METHOD,
  94. {
  95. input: {
  96. code: 'test-method',
  97. fulfillmentHandler: manualFulfillmentHandler.code,
  98. checker: {
  99. code: defaultShippingEligibilityChecker.code,
  100. arguments: [
  101. {
  102. name: 'orderMinimum',
  103. value: '0',
  104. },
  105. ],
  106. },
  107. calculator: {
  108. code: defaultShippingCalculator.code,
  109. arguments: [
  110. {
  111. name: 'rate',
  112. value: '500',
  113. },
  114. {
  115. name: 'taxRate',
  116. value: '0',
  117. },
  118. ],
  119. },
  120. translations: [{ languageCode: LanguageCode.en, name: 'test method', description: '' }],
  121. },
  122. },
  123. );
  124. await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
  125. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  126. productVariantId: 'T_1',
  127. quantity: 1,
  128. });
  129. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  130. productVariantId: 'T_2',
  131. quantity: 1,
  132. });
  133. await proceedToArrangingPayment(shopClient);
  134. const result = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  135. orderGuard.assertSuccess(result);
  136. order = result;
  137. }, TEST_SETUP_TIMEOUT_MS);
  138. afterAll(async () => {
  139. await server.destroy();
  140. });
  141. it('fulfillmentHandlers query', async () => {
  142. const { fulfillmentHandlers } = await adminClient.query<GetFulfillmentHandlers.Query>(
  143. GET_FULFILLMENT_HANDLERS,
  144. );
  145. expect(fulfillmentHandlers.map(h => h.code)).toEqual([
  146. 'manual-fulfillment',
  147. 'test-fulfillment-handler',
  148. ]);
  149. });
  150. it('creates fulfillment based on args', async () => {
  151. const { addFulfillmentToOrder } = await adminClient.query<
  152. CreateFulfillment.Mutation,
  153. CreateFulfillment.Variables
  154. >(CREATE_FULFILLMENT, {
  155. input: {
  156. lines: order.lines.slice(0, 1).map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  157. handler: {
  158. code: testFulfillmentHandler.code,
  159. arguments: [
  160. {
  161. name: 'trackingCode',
  162. value: 'abc123',
  163. },
  164. ],
  165. },
  166. },
  167. });
  168. fulfillmentGuard.assertSuccess(addFulfillmentToOrder);
  169. expect(addFulfillmentToOrder.trackingCode).toBe('abc123');
  170. f1Id = addFulfillmentToOrder.id;
  171. });
  172. it('onFulfillmentTransition is called', async () => {
  173. expect(transitionSpy).toHaveBeenCalledTimes(1);
  174. expect(transitionSpy).toHaveBeenCalledWith('Created', 'Pending');
  175. });
  176. it('onFulfillmentTransition can prevent state transition', async () => {
  177. const { transitionFulfillmentToState } = await adminClient.query<
  178. TransitFulfillment.Mutation,
  179. TransitFulfillment.Variables
  180. >(TRANSIT_FULFILLMENT, {
  181. id: f1Id,
  182. state: 'Shipped',
  183. });
  184. fulfillmentGuard.assertErrorResult(transitionFulfillmentToState);
  185. expect(transitionFulfillmentToState.errorCode).toBe(ErrorCode.FULFILLMENT_STATE_TRANSITION_ERROR);
  186. expect(transitionFulfillmentToState.transitionError).toBe(transitionErrorMessage);
  187. });
  188. it('throwing from createFulfillment returns CreateFulfillmentError result', async () => {
  189. const { addFulfillmentToOrder } = await adminClient.query<
  190. CreateFulfillment.Mutation,
  191. CreateFulfillment.Variables
  192. >(CREATE_FULFILLMENT, {
  193. input: {
  194. lines: order.lines.slice(1, 2).map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  195. handler: {
  196. code: testFulfillmentHandler.code,
  197. arguments: [
  198. {
  199. name: 'trackingCode',
  200. value: badTrackingCode,
  201. },
  202. ],
  203. },
  204. },
  205. });
  206. fulfillmentGuard.assertErrorResult(addFulfillmentToOrder);
  207. expect(addFulfillmentToOrder.errorCode).toBe(ErrorCode.CREATE_FULFILLMENT_ERROR);
  208. expect((addFulfillmentToOrder as CreateFulfillmentError).fulfillmentHandlerError).toBe(
  209. 'The code was bad!',
  210. );
  211. });
  212. });
  213. const GET_FULFILLMENT_HANDLERS = gql`
  214. query GetFulfillmentHandlers {
  215. fulfillmentHandlers {
  216. code
  217. description
  218. args {
  219. name
  220. type
  221. description
  222. label
  223. ui
  224. }
  225. }
  226. }
  227. `;