draft-order.e2e-spec.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import { LanguageCode } from '@vendure/common/lib/generated-types';
  3. import {
  4. DefaultLogger,
  5. DefaultOrderPlacedStrategy,
  6. mergeConfig,
  7. Order,
  8. orderPercentageDiscount,
  9. OrderState,
  10. RequestContext,
  11. } from '@vendure/core';
  12. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  13. import gql from 'graphql-tag';
  14. import path from 'path';
  15. import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
  16. import { initialData } from '../../../e2e-common/e2e-initial-data';
  17. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  18. import { singleStageRefundablePaymentMethod } from './fixtures/test-payment-methods';
  19. import { ORDER_WITH_LINES_FRAGMENT } from './graphql/fragments';
  20. import * as Codegen from './graphql/generated-e2e-admin-types';
  21. import {
  22. AddManualPaymentDocument,
  23. AdminTransitionDocument,
  24. CanceledOrderFragment,
  25. GetOrderDocument,
  26. GetOrderPlacedAtDocument,
  27. OrderWithLinesFragment,
  28. } from './graphql/generated-e2e-admin-types';
  29. import {
  30. GetActiveCustomerOrdersQuery,
  31. TestOrderFragmentFragment,
  32. TransitionToStateDocument,
  33. UpdatedOrderFragment,
  34. } from './graphql/generated-e2e-shop-types';
  35. import { CREATE_PROMOTION, GET_CUSTOMER_LIST } from './graphql/shared-definitions';
  36. import { GET_ACTIVE_CUSTOMER_ORDERS } from './graphql/shop-definitions';
  37. class TestOrderPlacedStrategy extends DefaultOrderPlacedStrategy {
  38. static spy = vi.fn();
  39. shouldSetAsPlaced(
  40. ctx: RequestContext,
  41. fromState: OrderState,
  42. toState: OrderState,
  43. order: Order,
  44. ): boolean {
  45. TestOrderPlacedStrategy.spy(order);
  46. return super.shouldSetAsPlaced(ctx, fromState, toState, order);
  47. }
  48. }
  49. describe('Draft Orders resolver', () => {
  50. const { server, adminClient, shopClient } = createTestEnvironment(
  51. mergeConfig(testConfig(), {
  52. logger: new DefaultLogger(),
  53. paymentOptions: {
  54. paymentMethodHandlers: [singleStageRefundablePaymentMethod],
  55. },
  56. orderOptions: {
  57. orderPlacedStrategy: new TestOrderPlacedStrategy(),
  58. },
  59. dbConnectionOptions: {
  60. logging: true,
  61. },
  62. }),
  63. );
  64. let customers: Codegen.GetCustomerListQuery['customers']['items'];
  65. let draftOrder: OrderWithLinesFragment;
  66. const freeOrderCouponCode = 'FREE';
  67. const orderGuard: ErrorResultGuard<
  68. TestOrderFragmentFragment | CanceledOrderFragment | UpdatedOrderFragment
  69. > = createErrorResultGuard(input => !!input.lines || !!input.state);
  70. beforeAll(async () => {
  71. await server.init({
  72. initialData: {
  73. ...initialData,
  74. paymentMethods: [
  75. {
  76. name: singleStageRefundablePaymentMethod.code,
  77. handler: { code: singleStageRefundablePaymentMethod.code, arguments: [] },
  78. },
  79. ],
  80. },
  81. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  82. customerCount: 3,
  83. });
  84. await adminClient.asSuperAdmin();
  85. // Create a couple of orders to be queried
  86. const result = await adminClient.query<
  87. Codegen.GetCustomerListQuery,
  88. Codegen.GetCustomerListQueryVariables
  89. >(GET_CUSTOMER_LIST, {
  90. options: {
  91. take: 3,
  92. },
  93. });
  94. customers = result.customers.items;
  95. // Create a coupon code promotion
  96. const { createPromotion } = await adminClient.query<
  97. Codegen.CreatePromotionMutation,
  98. Codegen.CreatePromotionMutationVariables
  99. >(CREATE_PROMOTION, {
  100. input: {
  101. enabled: true,
  102. conditions: [],
  103. couponCode: freeOrderCouponCode,
  104. actions: [
  105. {
  106. code: orderPercentageDiscount.code,
  107. arguments: [{ name: 'discount', value: '100' }],
  108. },
  109. ],
  110. translations: [{ languageCode: LanguageCode.en, name: 'Free Order' }],
  111. },
  112. });
  113. }, TEST_SETUP_TIMEOUT_MS);
  114. afterAll(async () => {
  115. await server.destroy();
  116. });
  117. it('create draft order', async () => {
  118. const { createDraftOrder } = await adminClient.query<Codegen.CreateDraftOrderMutation>(
  119. CREATE_DRAFT_ORDER,
  120. );
  121. expect(createDraftOrder.state).toBe('Draft');
  122. expect(createDraftOrder.active).toBe(false);
  123. draftOrder = createDraftOrder;
  124. });
  125. it('addItemToDraftOrder', async () => {
  126. const { addItemToDraftOrder } = await adminClient.query<
  127. Codegen.AddItemToDraftOrderMutation,
  128. Codegen.AddItemToDraftOrderMutationVariables
  129. >(ADD_ITEM_TO_DRAFT_ORDER, {
  130. orderId: draftOrder.id,
  131. input: {
  132. productVariantId: 'T_5',
  133. quantity: 2,
  134. },
  135. });
  136. orderGuard.assertSuccess(addItemToDraftOrder);
  137. expect(addItemToDraftOrder.lines.length).toBe(1);
  138. draftOrder = addItemToDraftOrder;
  139. });
  140. it('adjustDraftOrderLine up', async () => {
  141. const { adjustDraftOrderLine } = await adminClient.query<
  142. Codegen.AdjustDraftOrderLineMutation,
  143. Codegen.AdjustDraftOrderLineMutationVariables
  144. >(ADJUST_DRAFT_ORDER_LINE, {
  145. orderId: draftOrder.id,
  146. input: {
  147. orderLineId: draftOrder.lines[0]!.id,
  148. quantity: 5,
  149. },
  150. });
  151. orderGuard.assertSuccess(adjustDraftOrderLine);
  152. expect(adjustDraftOrderLine.lines[0].quantity).toBe(5);
  153. });
  154. it('adjustDraftOrderLine down', async () => {
  155. const { adjustDraftOrderLine } = await adminClient.query<
  156. Codegen.AdjustDraftOrderLineMutation,
  157. Codegen.AdjustDraftOrderLineMutationVariables
  158. >(ADJUST_DRAFT_ORDER_LINE, {
  159. orderId: draftOrder.id,
  160. input: {
  161. orderLineId: draftOrder.lines[0]!.id,
  162. quantity: 2,
  163. },
  164. });
  165. orderGuard.assertSuccess(adjustDraftOrderLine);
  166. expect(adjustDraftOrderLine.lines[0].quantity).toBe(2);
  167. });
  168. it('removeDraftOrderLine', async () => {
  169. const { removeDraftOrderLine } = await adminClient.query<
  170. Codegen.RemoveDraftOrderLineMutation,
  171. Codegen.RemoveDraftOrderLineMutationVariables
  172. >(REMOVE_DRAFT_ORDER_LINE, {
  173. orderId: draftOrder.id,
  174. orderLineId: draftOrder.lines[0]!.id,
  175. });
  176. orderGuard.assertSuccess(removeDraftOrderLine);
  177. expect(removeDraftOrderLine.lines.length).toBe(0);
  178. });
  179. it('setCustomerForDraftOrder', async () => {
  180. const { setCustomerForDraftOrder } = await adminClient.query<
  181. Codegen.SetCustomerForDraftOrderMutation,
  182. Codegen.SetCustomerForDraftOrderMutationVariables
  183. >(SET_CUSTOMER_FOR_DRAFT_ORDER, {
  184. orderId: draftOrder.id,
  185. customerId: customers[0].id,
  186. });
  187. orderGuard.assertSuccess(setCustomerForDraftOrder);
  188. expect(setCustomerForDraftOrder.customer?.id).toBe(customers[0].id);
  189. });
  190. it('custom does not see draft orders in history', async () => {
  191. await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
  192. const { activeCustomer } = await shopClient.query<GetActiveCustomerOrdersQuery>(
  193. GET_ACTIVE_CUSTOMER_ORDERS,
  194. );
  195. expect(activeCustomer?.orders.totalItems).toBe(0);
  196. expect(activeCustomer?.orders.items.length).toBe(0);
  197. });
  198. it('setDraftOrderShippingAddress', async () => {
  199. const { setDraftOrderShippingAddress } = await adminClient.query<
  200. Codegen.SetDraftOrderShippingAddressMutation,
  201. Codegen.SetDraftOrderShippingAddressMutationVariables
  202. >(SET_SHIPPING_ADDRESS_FOR_DRAFT_ORDER, {
  203. orderId: draftOrder.id,
  204. input: {
  205. streetLine1: 'Shipping Street',
  206. city: 'Wigan',
  207. province: 'Greater Manchester',
  208. postalCode: 'WN1 2DD',
  209. countryCode: 'GB',
  210. },
  211. });
  212. expect(setDraftOrderShippingAddress.shippingAddress).toEqual({
  213. company: null,
  214. fullName: null,
  215. phoneNumber: null,
  216. streetLine2: null,
  217. province: 'Greater Manchester',
  218. city: 'Wigan',
  219. country: 'United Kingdom',
  220. postalCode: 'WN1 2DD',
  221. streetLine1: 'Shipping Street',
  222. });
  223. });
  224. it('setDraftOrderBillingAddress', async () => {
  225. const { setDraftOrderBillingAddress } = await adminClient.query<
  226. Codegen.SetDraftOrderBillingAddressMutation,
  227. Codegen.SetDraftOrderBillingAddressMutationVariables
  228. >(SET_BILLING_ADDRESS_FOR_DRAFT_ORDER, {
  229. orderId: draftOrder.id,
  230. input: {
  231. streetLine1: 'Billing Street',
  232. city: 'Skelmerdale',
  233. province: 'Lancashire',
  234. postalCode: 'WN8 3QW',
  235. countryCode: 'GB',
  236. },
  237. });
  238. expect(setDraftOrderBillingAddress.billingAddress).toEqual({
  239. company: null,
  240. fullName: null,
  241. phoneNumber: null,
  242. streetLine2: null,
  243. province: 'Lancashire',
  244. city: 'Skelmerdale',
  245. country: 'United Kingdom',
  246. postalCode: 'WN8 3QW',
  247. streetLine1: 'Billing Street',
  248. });
  249. });
  250. it('applyCouponCodeToDraftOrder', async () => {
  251. const { addItemToDraftOrder } = await adminClient.query<
  252. Codegen.AddItemToDraftOrderMutation,
  253. Codegen.AddItemToDraftOrderMutationVariables
  254. >(ADD_ITEM_TO_DRAFT_ORDER, {
  255. orderId: draftOrder.id,
  256. input: {
  257. productVariantId: 'T_1',
  258. quantity: 1,
  259. },
  260. });
  261. orderGuard.assertSuccess(addItemToDraftOrder);
  262. expect(addItemToDraftOrder.totalWithTax).toBe(155880);
  263. const { applyCouponCodeToDraftOrder } = await adminClient.query<
  264. Codegen.ApplyCouponCodeToDraftOrderMutation,
  265. Codegen.ApplyCouponCodeToDraftOrderMutationVariables
  266. >(APPLY_COUPON_CODE_TO_DRAFT_ORDER, {
  267. orderId: draftOrder.id,
  268. couponCode: freeOrderCouponCode,
  269. });
  270. orderGuard.assertSuccess(applyCouponCodeToDraftOrder);
  271. expect(applyCouponCodeToDraftOrder.couponCodes).toEqual([freeOrderCouponCode]);
  272. expect(applyCouponCodeToDraftOrder.totalWithTax).toBe(0);
  273. });
  274. it('removeCouponCodeFromDraftOrder', async () => {
  275. const { removeCouponCodeFromDraftOrder } = await adminClient.query<
  276. Codegen.RemoveCouponCodeFromDraftOrderMutation,
  277. Codegen.RemoveCouponCodeFromDraftOrderMutationVariables
  278. >(REMOVE_COUPON_CODE_FROM_DRAFT_ORDER, {
  279. orderId: draftOrder.id,
  280. couponCode: freeOrderCouponCode,
  281. });
  282. expect(removeCouponCodeFromDraftOrder!.couponCodes).toEqual([]);
  283. expect(removeCouponCodeFromDraftOrder!.totalWithTax).toBe(155880);
  284. });
  285. it('eligibleShippingMethodsForDraftOrder', async () => {
  286. const { eligibleShippingMethodsForDraftOrder } = await adminClient.query<
  287. Codegen.DraftOrderEligibleShippingMethodsQuery,
  288. Codegen.DraftOrderEligibleShippingMethodsQueryVariables
  289. >(DRAFT_ORDER_ELIGIBLE_SHIPPING_METHODS, {
  290. orderId: draftOrder.id,
  291. });
  292. expect(eligibleShippingMethodsForDraftOrder).toEqual([
  293. {
  294. code: 'standard-shipping',
  295. description: '',
  296. id: 'T_1',
  297. metadata: null,
  298. name: 'Standard Shipping',
  299. price: 500,
  300. priceWithTax: 500,
  301. },
  302. {
  303. code: 'express-shipping',
  304. description: '',
  305. id: 'T_2',
  306. metadata: null,
  307. name: 'Express Shipping',
  308. price: 1000,
  309. priceWithTax: 1000,
  310. },
  311. ]);
  312. });
  313. it('setDraftOrderShippingMethod', async () => {
  314. const { setDraftOrderShippingMethod } = await adminClient.query<
  315. Codegen.SetDraftOrderShippingMethodMutation,
  316. Codegen.SetDraftOrderShippingMethodMutationVariables
  317. >(SET_DRAFT_ORDER_SHIPPING_METHOD, {
  318. orderId: draftOrder.id,
  319. shippingMethodId: 'T_2',
  320. });
  321. orderGuard.assertSuccess(setDraftOrderShippingMethod);
  322. expect(setDraftOrderShippingMethod.shippingWithTax).toBe(1000);
  323. expect(setDraftOrderShippingMethod.shippingLines.length).toBe(1);
  324. expect(setDraftOrderShippingMethod.shippingLines[0].shippingMethod.id).toBe('T_2');
  325. });
  326. // https://github.com/vendure-ecommerce/vendure/issues/2105
  327. it('sets order as placed when payment is settled', async () => {
  328. TestOrderPlacedStrategy.spy.mockClear();
  329. expect(TestOrderPlacedStrategy.spy.mock.calls.length).toBe(0);
  330. const { transitionOrderToState } = await adminClient.query(AdminTransitionDocument, {
  331. id: draftOrder.id,
  332. state: 'ArrangingPayment',
  333. });
  334. orderGuard.assertSuccess(transitionOrderToState);
  335. expect(transitionOrderToState.state).toBe('ArrangingPayment');
  336. const { addManualPaymentToOrder } = await adminClient.query(AddManualPaymentDocument, {
  337. input: {
  338. orderId: draftOrder.id,
  339. metadata: {},
  340. method: singleStageRefundablePaymentMethod.code,
  341. transactionId: '12345',
  342. },
  343. });
  344. orderGuard.assertSuccess(addManualPaymentToOrder);
  345. expect(addManualPaymentToOrder.state).toBe('PaymentSettled');
  346. const { order } = await adminClient.query(GetOrderPlacedAtDocument, {
  347. id: draftOrder.id,
  348. });
  349. expect(order?.orderPlacedAt).not.toBeNull();
  350. expect(TestOrderPlacedStrategy.spy.mock.calls.length).toBe(1);
  351. expect(TestOrderPlacedStrategy.spy.mock.calls[0][0].code).toBe(draftOrder.code);
  352. });
  353. });
  354. export const CREATE_DRAFT_ORDER = gql`
  355. mutation CreateDraftOrder {
  356. createDraftOrder {
  357. ...OrderWithLines
  358. }
  359. }
  360. ${ORDER_WITH_LINES_FRAGMENT}
  361. `;
  362. export const ADD_ITEM_TO_DRAFT_ORDER = gql`
  363. mutation AddItemToDraftOrder($orderId: ID!, $input: AddItemToDraftOrderInput!) {
  364. addItemToDraftOrder(orderId: $orderId, input: $input) {
  365. ...OrderWithLines
  366. ... on ErrorResult {
  367. errorCode
  368. message
  369. }
  370. }
  371. }
  372. ${ORDER_WITH_LINES_FRAGMENT}
  373. `;
  374. export const ADJUST_DRAFT_ORDER_LINE = gql`
  375. mutation AdjustDraftOrderLine($orderId: ID!, $input: AdjustDraftOrderLineInput!) {
  376. adjustDraftOrderLine(orderId: $orderId, input: $input) {
  377. ...OrderWithLines
  378. ... on ErrorResult {
  379. errorCode
  380. message
  381. }
  382. }
  383. }
  384. ${ORDER_WITH_LINES_FRAGMENT}
  385. `;
  386. export const REMOVE_DRAFT_ORDER_LINE = gql`
  387. mutation RemoveDraftOrderLine($orderId: ID!, $orderLineId: ID!) {
  388. removeDraftOrderLine(orderId: $orderId, orderLineId: $orderLineId) {
  389. ...OrderWithLines
  390. ... on ErrorResult {
  391. errorCode
  392. message
  393. }
  394. }
  395. }
  396. ${ORDER_WITH_LINES_FRAGMENT}
  397. `;
  398. export const SET_CUSTOMER_FOR_DRAFT_ORDER = gql`
  399. mutation SetCustomerForDraftOrder($orderId: ID!, $customerId: ID, $input: CreateCustomerInput) {
  400. setCustomerForDraftOrder(orderId: $orderId, customerId: $customerId, input: $input) {
  401. ...OrderWithLines
  402. ... on ErrorResult {
  403. errorCode
  404. message
  405. }
  406. }
  407. }
  408. ${ORDER_WITH_LINES_FRAGMENT}
  409. `;
  410. export const SET_SHIPPING_ADDRESS_FOR_DRAFT_ORDER = gql`
  411. mutation SetDraftOrderShippingAddress($orderId: ID!, $input: CreateAddressInput!) {
  412. setDraftOrderShippingAddress(orderId: $orderId, input: $input) {
  413. ...OrderWithLines
  414. }
  415. }
  416. ${ORDER_WITH_LINES_FRAGMENT}
  417. `;
  418. export const SET_BILLING_ADDRESS_FOR_DRAFT_ORDER = gql`
  419. mutation SetDraftOrderBillingAddress($orderId: ID!, $input: CreateAddressInput!) {
  420. setDraftOrderBillingAddress(orderId: $orderId, input: $input) {
  421. ...OrderWithLines
  422. billingAddress {
  423. ...ShippingAddress
  424. }
  425. }
  426. }
  427. ${ORDER_WITH_LINES_FRAGMENT}
  428. `;
  429. export const APPLY_COUPON_CODE_TO_DRAFT_ORDER = gql`
  430. mutation ApplyCouponCodeToDraftOrder($orderId: ID!, $couponCode: String!) {
  431. applyCouponCodeToDraftOrder(orderId: $orderId, couponCode: $couponCode) {
  432. ...OrderWithLines
  433. ... on Order {
  434. couponCodes
  435. }
  436. ... on ErrorResult {
  437. errorCode
  438. message
  439. }
  440. }
  441. }
  442. ${ORDER_WITH_LINES_FRAGMENT}
  443. `;
  444. export const REMOVE_COUPON_CODE_FROM_DRAFT_ORDER = gql`
  445. mutation RemoveCouponCodeFromDraftOrder($orderId: ID!, $couponCode: String!) {
  446. removeCouponCodeFromDraftOrder(orderId: $orderId, couponCode: $couponCode) {
  447. ...OrderWithLines
  448. ... on Order {
  449. couponCodes
  450. }
  451. }
  452. }
  453. ${ORDER_WITH_LINES_FRAGMENT}
  454. `;
  455. export const DRAFT_ORDER_ELIGIBLE_SHIPPING_METHODS = gql`
  456. query DraftOrderEligibleShippingMethods($orderId: ID!) {
  457. eligibleShippingMethodsForDraftOrder(orderId: $orderId) {
  458. id
  459. name
  460. code
  461. description
  462. price
  463. priceWithTax
  464. metadata
  465. }
  466. }
  467. `;
  468. export const SET_DRAFT_ORDER_SHIPPING_METHOD = gql`
  469. mutation SetDraftOrderShippingMethod($orderId: ID!, $shippingMethodId: ID!) {
  470. setDraftOrderShippingMethod(orderId: $orderId, shippingMethodId: $shippingMethodId) {
  471. ...OrderWithLines
  472. ... on ErrorResult {
  473. errorCode
  474. message
  475. }
  476. }
  477. }
  478. ${ORDER_WITH_LINES_FRAGMENT}
  479. `;
  480. export const GET_ORDER_PLACED_AT = gql`
  481. query GetOrderPlacedAt($id: ID!) {
  482. order(id: $id) {
  483. id
  484. createdAt
  485. updatedAt
  486. state
  487. orderPlacedAt
  488. }
  489. }
  490. `;