order-fulfillment.e2e-spec.ts 8.6 KB

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