order-merge.e2e-spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. /* tslint:disable:no-non-null-assertion */
  2. import {
  3. mergeConfig,
  4. MergedOrderLine,
  5. MergeOrdersStrategy,
  6. Order,
  7. OrderMergeStrategy,
  8. RequestContext,
  9. UseExistingStrategy,
  10. UseGuestIfExistingEmptyStrategy,
  11. UseGuestStrategy,
  12. } from '@vendure/core';
  13. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  14. import gql from 'graphql-tag';
  15. import path from 'path';
  16. import { initialData } from '../../../e2e-common/e2e-initial-data';
  17. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  18. import {
  19. AttemptLogin,
  20. AttemptLoginMutation,
  21. AttemptLoginMutationVariables,
  22. GetCustomerList,
  23. } from './graphql/generated-e2e-admin-types';
  24. import {
  25. AddItemToOrder,
  26. AddItemToOrderMutation,
  27. GetActiveOrderPaymentsQuery,
  28. GetNextOrderStatesQuery,
  29. TestOrderFragmentFragment,
  30. UpdatedOrderFragment,
  31. } from './graphql/generated-e2e-shop-types';
  32. import { ATTEMPT_LOGIN, GET_CUSTOMER_LIST } from './graphql/shared-definitions';
  33. import { GET_ACTIVE_ORDER_PAYMENTS, GET_NEXT_STATES, TEST_ORDER_FRAGMENT } from './graphql/shop-definitions';
  34. import { sortById } from './utils/test-order-utils';
  35. /**
  36. * Allows us to change the active OrderMergeStrategy per-test and delegates to the current
  37. * activeStrategy.
  38. */
  39. class DelegateMergeStrategy implements OrderMergeStrategy {
  40. static activeStrategy: OrderMergeStrategy = new MergeOrdersStrategy();
  41. merge(ctx: RequestContext, guestOrder: Order, existingOrder: Order): MergedOrderLine[] {
  42. return DelegateMergeStrategy.activeStrategy.merge(ctx, guestOrder, existingOrder);
  43. }
  44. }
  45. type AddItemToOrderWithCustomFields = AddItemToOrder.Variables & {
  46. customFields?: { inscription?: string };
  47. };
  48. describe('Order merging', () => {
  49. type OrderSuccessResult = UpdatedOrderFragment | TestOrderFragmentFragment;
  50. const orderResultGuard: ErrorResultGuard<OrderSuccessResult> = createErrorResultGuard(
  51. input => !!input.lines,
  52. );
  53. let customers: GetCustomerList.Items[];
  54. const { server, shopClient, adminClient } = createTestEnvironment(
  55. mergeConfig(testConfig(), {
  56. orderOptions: {
  57. mergeStrategy: new DelegateMergeStrategy(),
  58. },
  59. customFields: {
  60. OrderLine: [{ name: 'inscription', type: 'string' }],
  61. },
  62. }),
  63. );
  64. beforeAll(async () => {
  65. await server.init({
  66. initialData,
  67. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  68. customerCount: 10,
  69. });
  70. await adminClient.asSuperAdmin();
  71. const result = await adminClient.query<GetCustomerList.Query>(GET_CUSTOMER_LIST);
  72. customers = result.customers.items;
  73. }, TEST_SETUP_TIMEOUT_MS);
  74. afterAll(async () => {
  75. await server.destroy();
  76. });
  77. async function testMerge(options: {
  78. strategy: OrderMergeStrategy;
  79. customerEmailAddress: string;
  80. existingOrderLines: AddItemToOrderWithCustomFields[];
  81. guestOrderLines: AddItemToOrderWithCustomFields[];
  82. }): Promise<{ lines: any[] }> {
  83. const { strategy, customerEmailAddress, existingOrderLines, guestOrderLines } = options;
  84. DelegateMergeStrategy.activeStrategy = strategy;
  85. await shopClient.asUserWithCredentials(customerEmailAddress, 'test');
  86. for (const line of existingOrderLines) {
  87. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrderWithCustomFields>(
  88. ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS,
  89. line,
  90. );
  91. }
  92. await shopClient.asAnonymousUser();
  93. for (const line of guestOrderLines) {
  94. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrderWithCustomFields>(
  95. ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS,
  96. line,
  97. );
  98. }
  99. await shopClient.query<AttemptLogin.Mutation, AttemptLogin.Variables>(ATTEMPT_LOGIN, {
  100. username: customerEmailAddress,
  101. password: 'test',
  102. });
  103. const { activeOrder } = await shopClient.query(GET_ACTIVE_ORDER_WITH_CUSTOM_FIELDS);
  104. return activeOrder;
  105. }
  106. it('MergeOrdersStrategy adds new line', async () => {
  107. const result = await testMerge({
  108. strategy: new MergeOrdersStrategy(),
  109. customerEmailAddress: customers[0].emailAddress,
  110. existingOrderLines: [{ productVariantId: 'T_1', quantity: 1 }],
  111. guestOrderLines: [{ productVariantId: 'T_2', quantity: 1 }],
  112. });
  113. expect(
  114. result.lines.map(line => ({ productVariantId: line.productVariant.id, quantity: line.quantity })),
  115. ).toEqual([
  116. { productVariantId: 'T_1', quantity: 1 },
  117. { productVariantId: 'T_2', quantity: 1 },
  118. ]);
  119. });
  120. it('MergeOrdersStrategy uses guest quantity', async () => {
  121. const result = await testMerge({
  122. strategy: new MergeOrdersStrategy(),
  123. customerEmailAddress: customers[1].emailAddress,
  124. existingOrderLines: [{ productVariantId: 'T_1', quantity: 1 }],
  125. guestOrderLines: [{ productVariantId: 'T_1', quantity: 3 }],
  126. });
  127. expect(
  128. result.lines.map(line => ({ productVariantId: line.productVariant.id, quantity: line.quantity })),
  129. ).toEqual([{ productVariantId: 'T_1', quantity: 3 }]);
  130. });
  131. it('MergeOrdersStrategy accounts for customFields', async () => {
  132. const result = await testMerge({
  133. strategy: new MergeOrdersStrategy(),
  134. customerEmailAddress: customers[2].emailAddress,
  135. existingOrderLines: [
  136. { productVariantId: 'T_1', quantity: 1, customFields: { inscription: 'foo' } },
  137. ],
  138. guestOrderLines: [{ productVariantId: 'T_1', quantity: 3, customFields: { inscription: 'bar' } }],
  139. });
  140. expect(
  141. result.lines.sort(sortById).map(line => ({
  142. productVariantId: line.productVariant.id,
  143. quantity: line.quantity,
  144. customFields: line.customFields,
  145. })),
  146. ).toEqual([
  147. { productVariantId: 'T_1', quantity: 1, customFields: { inscription: 'foo' } },
  148. { productVariantId: 'T_1', quantity: 3, customFields: { inscription: 'bar' } },
  149. ]);
  150. });
  151. it('UseGuestStrategy', async () => {
  152. const result = await testMerge({
  153. strategy: new UseGuestStrategy(),
  154. customerEmailAddress: customers[3].emailAddress,
  155. existingOrderLines: [
  156. { productVariantId: 'T_1', quantity: 1 },
  157. { productVariantId: 'T_3', quantity: 1 },
  158. ],
  159. guestOrderLines: [{ productVariantId: 'T_5', quantity: 3 }],
  160. });
  161. expect(
  162. result.lines.sort(sortById).map(line => ({
  163. productVariantId: line.productVariant.id,
  164. quantity: line.quantity,
  165. })),
  166. ).toEqual([{ productVariantId: 'T_5', quantity: 3 }]);
  167. });
  168. it('UseGuestIfExistingEmptyStrategy with empty existing', async () => {
  169. const result = await testMerge({
  170. strategy: new UseGuestIfExistingEmptyStrategy(),
  171. customerEmailAddress: customers[4].emailAddress,
  172. existingOrderLines: [],
  173. guestOrderLines: [{ productVariantId: 'T_2', quantity: 3 }],
  174. });
  175. expect(
  176. result.lines.sort(sortById).map(line => ({
  177. productVariantId: line.productVariant.id,
  178. quantity: line.quantity,
  179. })),
  180. ).toEqual([{ productVariantId: 'T_2', quantity: 3 }]);
  181. });
  182. it('UseGuestIfExistingEmptyStrategy with non-empty existing', async () => {
  183. const result = await testMerge({
  184. strategy: new UseGuestIfExistingEmptyStrategy(),
  185. customerEmailAddress: customers[5].emailAddress,
  186. existingOrderLines: [{ productVariantId: 'T_5', quantity: 5 }],
  187. guestOrderLines: [{ productVariantId: 'T_2', quantity: 3 }],
  188. });
  189. expect(
  190. result.lines.sort(sortById).map(line => ({
  191. productVariantId: line.productVariant.id,
  192. quantity: line.quantity,
  193. })),
  194. ).toEqual([{ productVariantId: 'T_5', quantity: 5 }]);
  195. });
  196. it('UseExistingStrategy', async () => {
  197. const result = await testMerge({
  198. strategy: new UseExistingStrategy(),
  199. customerEmailAddress: customers[6].emailAddress,
  200. existingOrderLines: [{ productVariantId: 'T_8', quantity: 1 }],
  201. guestOrderLines: [{ productVariantId: 'T_2', quantity: 3 }],
  202. });
  203. expect(
  204. result.lines.sort(sortById).map(line => ({
  205. productVariantId: line.productVariant.id,
  206. quantity: line.quantity,
  207. })),
  208. ).toEqual([{ productVariantId: 'T_8', quantity: 1 }]);
  209. });
  210. // https://github.com/vendure-ecommerce/vendure/issues/1454
  211. it('does not throw FK error when merging with a cart with an existing session', async () => {
  212. await shopClient.asUserWithCredentials(customers[7].emailAddress, 'test');
  213. // Create an Order linked with the current session
  214. const { nextOrderStates } = await shopClient.query<GetNextOrderStatesQuery>(GET_NEXT_STATES);
  215. // unset last auth token to simulate a guest user in a different browser
  216. shopClient.setAuthToken('');
  217. await shopClient.query<AddItemToOrderMutation, AddItemToOrderWithCustomFields>(
  218. ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS,
  219. { productVariantId: '1', quantity: 2 },
  220. );
  221. const { login } = await shopClient.query<AttemptLoginMutation, AttemptLoginMutationVariables>(
  222. ATTEMPT_LOGIN,
  223. { username: customers[7].emailAddress, password: 'test' },
  224. );
  225. expect(login.id).toBe(customers[7].user?.id);
  226. });
  227. });
  228. export const ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS = gql`
  229. mutation AddItemToOrder(
  230. $productVariantId: ID!
  231. $quantity: Int!
  232. $customFields: OrderLineCustomFieldsInput
  233. ) {
  234. addItemToOrder(
  235. productVariantId: $productVariantId
  236. quantity: $quantity
  237. customFields: $customFields
  238. ) {
  239. ... on Order {
  240. id
  241. }
  242. ... on ErrorResult {
  243. errorCode
  244. message
  245. }
  246. }
  247. }
  248. `;
  249. export const GET_ACTIVE_ORDER_WITH_CUSTOM_FIELDS = gql`
  250. query GetActiveOrder {
  251. activeOrder {
  252. ...TestOrderFragment
  253. ... on Order {
  254. lines {
  255. customFields {
  256. inscription
  257. }
  258. }
  259. }
  260. }
  261. }
  262. ${TEST_ORDER_FRAGMENT}
  263. `;