order-fulfillment.e2e-spec.ts 7.7 KB

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