active-order-strategy.e2e-spec.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. import { LanguageCode } from '@vendure/common/lib/generated-types';
  2. import { mergeConfig, orderPercentageDiscount } from '@vendure/core';
  3. import { createTestEnvironment } from '@vendure/testing';
  4. import gql from 'graphql-tag';
  5. import path from 'path';
  6. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  7. import { initialData } from '../../../e2e-common/e2e-initial-data';
  8. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  9. import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
  10. import { TokenActiveOrderPlugin } from './fixtures/test-plugins/token-active-order-plugin';
  11. import {
  12. CreatePromotionMutation,
  13. CreatePromotionMutationVariables,
  14. GetCustomerListQuery,
  15. } from './graphql/generated-e2e-admin-types';
  16. import {
  17. AddItemToOrderMutation,
  18. AddItemToOrderMutationVariables,
  19. GetActiveOrderQuery,
  20. } from './graphql/generated-e2e-shop-types';
  21. import { CREATE_PROMOTION, GET_CUSTOMER_LIST } from './graphql/shared-definitions';
  22. import { ADD_ITEM_TO_ORDER, GET_ACTIVE_ORDER } from './graphql/shop-definitions';
  23. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  24. describe('custom ActiveOrderStrategy', () => {
  25. const { server, adminClient, shopClient } = createTestEnvironment(
  26. mergeConfig(testConfig(), {
  27. plugins: [TokenActiveOrderPlugin],
  28. paymentOptions: {
  29. paymentMethodHandlers: [testSuccessfulPaymentMethod],
  30. },
  31. customFields: {
  32. Order: [
  33. {
  34. name: 'message',
  35. type: 'string',
  36. nullable: true,
  37. },
  38. ],
  39. },
  40. }),
  41. );
  42. let customers: GetCustomerListQuery['customers']['items'];
  43. beforeAll(async () => {
  44. await server.init({
  45. initialData: {
  46. ...initialData,
  47. paymentMethods: [
  48. {
  49. name: testSuccessfulPaymentMethod.code,
  50. handler: { code: testSuccessfulPaymentMethod.code, arguments: [] },
  51. },
  52. ],
  53. },
  54. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  55. customerCount: 3,
  56. });
  57. await adminClient.asSuperAdmin();
  58. const result = await adminClient.query<GetCustomerListQuery>(GET_CUSTOMER_LIST);
  59. customers = result.customers.items;
  60. }, TEST_SETUP_TIMEOUT_MS);
  61. afterAll(async () => {
  62. await server.destroy();
  63. });
  64. it('activeOrder with no createActiveOrder defined returns null', async () => {
  65. const { activeOrder } = await shopClient.query<GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  66. expect(activeOrder).toBeNull();
  67. });
  68. it(
  69. 'addItemToOrder with no createActiveOrder throws',
  70. assertThrowsWithMessage(async () => {
  71. await shopClient.query<AddItemToOrderMutation, AddItemToOrderMutationVariables>(
  72. ADD_ITEM_TO_ORDER,
  73. {
  74. productVariantId: 'T_1',
  75. quantity: 1,
  76. },
  77. );
  78. }, 'No active Order could be determined nor created'),
  79. );
  80. it('activeOrder with valid input', async () => {
  81. const { createOrder } = await shopClient.query(gql`
  82. mutation CreateCustomOrder {
  83. createOrder(customerId: "${customers[1].id}") {
  84. id
  85. orderToken
  86. }
  87. }
  88. `);
  89. expect(createOrder).toEqual({
  90. id: 'T_1',
  91. orderToken: 'token-2',
  92. });
  93. await shopClient.asUserWithCredentials(customers[1].emailAddress, 'test');
  94. const { activeOrder } = await shopClient.query(ACTIVE_ORDER_BY_TOKEN, {
  95. input: {
  96. orderToken: { token: 'token-2' },
  97. },
  98. });
  99. expect(activeOrder).toEqual({
  100. id: 'T_1',
  101. orderToken: 'token-2',
  102. });
  103. });
  104. it('activeOrder with invalid input', async () => {
  105. await shopClient.asUserWithCredentials(customers[1].emailAddress, 'test');
  106. const { activeOrder } = await shopClient.query(ACTIVE_ORDER_BY_TOKEN, {
  107. input: {
  108. orderToken: { token: 'invalid' },
  109. },
  110. });
  111. expect(activeOrder).toBeNull();
  112. });
  113. it('activeOrder with invalid condition', async () => {
  114. // wrong customer logged in
  115. await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
  116. const { activeOrder } = await shopClient.query(ACTIVE_ORDER_BY_TOKEN, {
  117. input: {
  118. orderToken: { token: 'token-2' },
  119. },
  120. });
  121. expect(activeOrder).toBeNull();
  122. });
  123. describe('happy path', () => {
  124. const activeOrderInput = 'activeOrderInput: { orderToken: { token: "token-2" } }';
  125. const TEST_COUPON_CODE = 'TESTCOUPON';
  126. let firstOrderLineId: string;
  127. beforeAll(async () => {
  128. await shopClient.asUserWithCredentials(customers[1].emailAddress, 'test');
  129. const result = await adminClient.query<CreatePromotionMutation, CreatePromotionMutationVariables>(
  130. CREATE_PROMOTION,
  131. {
  132. input: {
  133. enabled: true,
  134. couponCode: TEST_COUPON_CODE,
  135. conditions: [],
  136. actions: [
  137. {
  138. code: orderPercentageDiscount.code,
  139. arguments: [{ name: 'discount', value: '100' }],
  140. },
  141. ],
  142. translations: [{ languageCode: LanguageCode.en, name: 'Free with test coupon' }],
  143. },
  144. },
  145. );
  146. });
  147. it('addItemToOrder', async () => {
  148. const { addItemToOrder } = await shopClient.query(gql`
  149. mutation {
  150. addItemToOrder(productVariantId: "T_1", quantity: 1, ${activeOrderInput}) {
  151. ...on Order {
  152. id
  153. orderToken
  154. lines {
  155. id
  156. productVariant { id }
  157. }
  158. }
  159. }
  160. }
  161. `);
  162. expect(addItemToOrder).toEqual({
  163. id: 'T_1',
  164. orderToken: 'token-2',
  165. lines: [
  166. {
  167. id: 'T_1',
  168. productVariant: { id: 'T_1' },
  169. },
  170. ],
  171. });
  172. firstOrderLineId = addItemToOrder.lines[0].id;
  173. });
  174. it('adjustOrderLine', async () => {
  175. const { adjustOrderLine } = await shopClient.query(gql`
  176. mutation {
  177. adjustOrderLine(orderLineId: "${firstOrderLineId}", quantity: 2, ${activeOrderInput}) {
  178. ...on Order {
  179. id
  180. orderToken
  181. lines {
  182. quantity
  183. productVariant { id }
  184. }
  185. }
  186. }
  187. }
  188. `);
  189. expect(adjustOrderLine).toEqual({
  190. id: 'T_1',
  191. orderToken: 'token-2',
  192. lines: [
  193. {
  194. quantity: 2,
  195. productVariant: { id: 'T_1' },
  196. },
  197. ],
  198. });
  199. });
  200. it('removeOrderLine', async () => {
  201. const { removeOrderLine } = await shopClient.query(gql`
  202. mutation {
  203. removeOrderLine(orderLineId: "${firstOrderLineId}", ${activeOrderInput}) {
  204. ...on Order {
  205. id
  206. orderToken
  207. lines {
  208. id
  209. }
  210. }
  211. }
  212. }
  213. `);
  214. expect(removeOrderLine).toEqual({
  215. id: 'T_1',
  216. orderToken: 'token-2',
  217. lines: [],
  218. });
  219. });
  220. it('removeAllOrderLines', async () => {
  221. const { addItemToOrder } = await shopClient.query(gql`
  222. mutation {
  223. addItemToOrder(productVariantId: "T_1", quantity: 1, ${activeOrderInput}) {
  224. ...on Order { lines { id } }
  225. }
  226. }
  227. `);
  228. expect(addItemToOrder.lines.length).toBe(1);
  229. const { removeAllOrderLines } = await shopClient.query(gql`
  230. mutation {
  231. removeAllOrderLines(${activeOrderInput}) {
  232. ...on Order {
  233. id
  234. orderToken
  235. lines { id }
  236. }
  237. }
  238. }
  239. `);
  240. expect(removeAllOrderLines.lines.length).toBe(0);
  241. });
  242. it('applyCouponCode', async () => {
  243. await shopClient.query(gql`
  244. mutation {
  245. addItemToOrder(productVariantId: "T_1", quantity: 1, ${activeOrderInput}) {
  246. ...on Order { lines { id } }
  247. }
  248. }
  249. `);
  250. const { applyCouponCode } = await shopClient.query(gql`
  251. mutation {
  252. applyCouponCode(couponCode: "${TEST_COUPON_CODE}", ${activeOrderInput}) {
  253. ...on Order {
  254. id
  255. orderToken
  256. couponCodes
  257. discounts {
  258. description
  259. }
  260. }
  261. }
  262. }
  263. `);
  264. expect(applyCouponCode).toEqual({
  265. id: 'T_1',
  266. orderToken: 'token-2',
  267. couponCodes: [TEST_COUPON_CODE],
  268. discounts: [{ description: 'Free with test coupon' }],
  269. });
  270. });
  271. it('removeCouponCode', async () => {
  272. const { removeCouponCode } = await shopClient.query(gql`
  273. mutation {
  274. removeCouponCode(couponCode: "${TEST_COUPON_CODE}", ${activeOrderInput}) {
  275. ...on Order {
  276. id
  277. orderToken
  278. couponCodes
  279. discounts {
  280. description
  281. }
  282. }
  283. }
  284. }
  285. `);
  286. expect(removeCouponCode).toEqual({
  287. id: 'T_1',
  288. orderToken: 'token-2',
  289. couponCodes: [],
  290. discounts: [],
  291. });
  292. });
  293. it('setOrderShippingAddress', async () => {
  294. const { setOrderShippingAddress } = await shopClient.query(gql`
  295. mutation {
  296. setOrderShippingAddress(input: {
  297. streetLine1: "Shipping Street"
  298. countryCode: "AT"
  299. }, ${activeOrderInput}) {
  300. ...on Order {
  301. id
  302. orderToken
  303. shippingAddress {
  304. streetLine1
  305. country
  306. }
  307. }
  308. }
  309. }
  310. `);
  311. expect(setOrderShippingAddress).toEqual({
  312. id: 'T_1',
  313. orderToken: 'token-2',
  314. shippingAddress: {
  315. streetLine1: 'Shipping Street',
  316. country: 'Austria',
  317. },
  318. });
  319. });
  320. it('setOrderBillingAddress', async () => {
  321. const { setOrderBillingAddress } = await shopClient.query(gql`
  322. mutation {
  323. setOrderBillingAddress(input: {
  324. streetLine1: "Billing Street"
  325. countryCode: "AT"
  326. }, ${activeOrderInput}) {
  327. ...on Order {
  328. id
  329. orderToken
  330. billingAddress {
  331. streetLine1
  332. country
  333. }
  334. }
  335. }
  336. }
  337. `);
  338. expect(setOrderBillingAddress).toEqual({
  339. id: 'T_1',
  340. orderToken: 'token-2',
  341. billingAddress: {
  342. streetLine1: 'Billing Street',
  343. country: 'Austria',
  344. },
  345. });
  346. });
  347. it('unsetOrderShippingAddress', async () => {
  348. const { unsetOrderShippingAddress } = await shopClient.query(gql`
  349. mutation {
  350. unsetOrderShippingAddress(${activeOrderInput}) {
  351. ...on Order {
  352. id
  353. orderToken
  354. shippingAddress {
  355. streetLine1
  356. country
  357. }
  358. }
  359. }
  360. }
  361. `);
  362. expect(unsetOrderShippingAddress).toEqual({
  363. id: 'T_1',
  364. orderToken: 'token-2',
  365. shippingAddress: {
  366. streetLine1: null,
  367. country: null,
  368. },
  369. });
  370. });
  371. it('unsetOrderBillingAddress', async () => {
  372. const { unsetOrderBillingAddress } = await shopClient.query(gql`
  373. mutation {
  374. unsetOrderBillingAddress(${activeOrderInput}) {
  375. ...on Order {
  376. id
  377. orderToken
  378. billingAddress {
  379. streetLine1
  380. country
  381. }
  382. }
  383. }
  384. }
  385. `);
  386. expect(unsetOrderBillingAddress).toEqual({
  387. id: 'T_1',
  388. orderToken: 'token-2',
  389. billingAddress: {
  390. streetLine1: null,
  391. country: null,
  392. },
  393. });
  394. });
  395. it('eligibleShippingMethods', async () => {
  396. const { eligibleShippingMethods } = await shopClient.query(gql`
  397. query {
  398. eligibleShippingMethods(${activeOrderInput}) {
  399. id
  400. name
  401. priceWithTax
  402. }
  403. }
  404. `);
  405. expect(eligibleShippingMethods).toEqual([
  406. {
  407. id: 'T_1',
  408. name: 'Standard Shipping',
  409. priceWithTax: 500,
  410. },
  411. {
  412. id: 'T_2',
  413. name: 'Express Shipping',
  414. priceWithTax: 1000,
  415. },
  416. {
  417. id: 'T_3',
  418. name: 'Express Shipping (Taxed)',
  419. priceWithTax: 1200,
  420. },
  421. ]);
  422. });
  423. it('setOrderShippingMethod', async () => {
  424. const { setOrderShippingMethod } = await shopClient.query(gql`
  425. mutation {
  426. setOrderShippingMethod(shippingMethodId: "T_1", ${activeOrderInput}) {
  427. ...on Order {
  428. id
  429. orderToken
  430. shippingLines {
  431. price
  432. }
  433. }
  434. }
  435. }
  436. `);
  437. expect(setOrderShippingMethod).toEqual({
  438. id: 'T_1',
  439. orderToken: 'token-2',
  440. shippingLines: [{ price: 500 }],
  441. });
  442. });
  443. it('setOrderCustomFields', async () => {
  444. const { setOrderCustomFields } = await shopClient.query(gql`
  445. mutation {
  446. setOrderCustomFields(input: { customFields: { message: "foo" } }, ${activeOrderInput}) {
  447. ...on Order {
  448. id
  449. orderToken
  450. customFields { message }
  451. }
  452. }
  453. }
  454. `);
  455. expect(setOrderCustomFields).toEqual({
  456. id: 'T_1',
  457. orderToken: 'token-2',
  458. customFields: { message: 'foo' },
  459. });
  460. });
  461. it('eligiblePaymentMethods', async () => {
  462. const { eligiblePaymentMethods } = await shopClient.query(gql`
  463. query {
  464. eligiblePaymentMethods(${activeOrderInput}) {
  465. id
  466. name
  467. code
  468. }
  469. }
  470. `);
  471. expect(eligiblePaymentMethods).toEqual([
  472. {
  473. id: 'T_1',
  474. name: 'test-payment-method',
  475. code: 'test-payment-method',
  476. },
  477. ]);
  478. });
  479. it('nextOrderStates', async () => {
  480. const { nextOrderStates } = await shopClient.query(gql`
  481. query {
  482. nextOrderStates(${activeOrderInput})
  483. }
  484. `);
  485. expect(nextOrderStates).toEqual(['ArrangingPayment', 'Cancelled']);
  486. });
  487. it('transitionOrderToState', async () => {
  488. const { transitionOrderToState } = await shopClient.query(gql`
  489. mutation {
  490. transitionOrderToState(state: "ArrangingPayment", ${activeOrderInput}) {
  491. ...on Order {
  492. id
  493. orderToken
  494. state
  495. }
  496. }
  497. }
  498. `);
  499. expect(transitionOrderToState).toEqual({
  500. id: 'T_1',
  501. orderToken: 'token-2',
  502. state: 'ArrangingPayment',
  503. });
  504. });
  505. it('addPaymentToOrder', async () => {
  506. const { addPaymentToOrder } = await shopClient.query(gql`
  507. mutation {
  508. addPaymentToOrder(input: { method: "test-payment-method", metadata: {}}, ${activeOrderInput}) {
  509. ...on Order {
  510. id
  511. orderToken
  512. state
  513. payments {
  514. state
  515. }
  516. }
  517. }
  518. }
  519. `);
  520. expect(addPaymentToOrder).toEqual({
  521. id: 'T_1',
  522. orderToken: 'token-2',
  523. payments: [
  524. {
  525. state: 'Settled',
  526. },
  527. ],
  528. state: 'PaymentSettled',
  529. });
  530. });
  531. });
  532. });
  533. export const ACTIVE_ORDER_BY_TOKEN = gql`
  534. query ActiveOrderByToken($input: ActiveOrderInput) {
  535. activeOrder(activeOrderInput: $input) {
  536. id
  537. orderToken
  538. }
  539. }
  540. `;