promotion.e2e-spec.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. import { pick } from '@vendure/common/lib/pick';
  2. import { PromotionAction, PromotionCondition, PromotionOrderAction } from '@vendure/core';
  3. import { createTestEnvironment } from '@vendure/testing';
  4. import gql from 'graphql-tag';
  5. import path from 'path';
  6. import { initialData } from '../../../e2e-common/e2e-initial-data';
  7. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  8. import { PROMOTION_FRAGMENT } from './graphql/fragments';
  9. import {
  10. CreatePromotion,
  11. DeletePromotion,
  12. DeletionResult,
  13. GetAdjustmentOperations,
  14. GetPromotion,
  15. GetPromotionList,
  16. LanguageCode,
  17. Promotion,
  18. UpdatePromotion,
  19. } from './graphql/generated-e2e-admin-types';
  20. import { CREATE_PROMOTION } from './graphql/shared-definitions';
  21. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  22. // tslint:disable:no-non-null-assertion
  23. describe('Promotion resolver', () => {
  24. const promoCondition = generateTestCondition('promo_condition');
  25. const promoCondition2 = generateTestCondition('promo_condition2');
  26. const promoAction = generateTestAction('promo_action');
  27. const { server, adminClient, shopClient } = createTestEnvironment({
  28. ...testConfig,
  29. promotionOptions: {
  30. promotionConditions: [promoCondition, promoCondition2],
  31. promotionActions: [promoAction],
  32. },
  33. });
  34. const snapshotProps: Array<keyof Promotion.Fragment> = [
  35. 'name',
  36. 'actions',
  37. 'conditions',
  38. 'enabled',
  39. 'couponCode',
  40. 'startsAt',
  41. 'endsAt',
  42. ];
  43. let promotion: Promotion.Fragment;
  44. beforeAll(async () => {
  45. await server.init({
  46. initialData,
  47. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  48. customerCount: 1,
  49. });
  50. await adminClient.asSuperAdmin();
  51. }, TEST_SETUP_TIMEOUT_MS);
  52. afterAll(async () => {
  53. await server.destroy();
  54. });
  55. it('createPromotion', async () => {
  56. const result = await adminClient.query<CreatePromotion.Mutation, CreatePromotion.Variables>(
  57. CREATE_PROMOTION,
  58. {
  59. input: {
  60. name: 'test promotion',
  61. enabled: true,
  62. couponCode: 'TEST123',
  63. startsAt: new Date('2019-10-30T00:00:00.000Z'),
  64. endsAt: new Date('2019-12-01T00:00:00.000Z'),
  65. conditions: [
  66. {
  67. code: promoCondition.code,
  68. arguments: [{ name: 'arg', value: '500' }],
  69. },
  70. ],
  71. actions: [
  72. {
  73. code: promoAction.code,
  74. arguments: [
  75. {
  76. name: 'facetValueIds',
  77. value: '["T_1"]',
  78. },
  79. ],
  80. },
  81. ],
  82. },
  83. },
  84. );
  85. promotion = result.createPromotion;
  86. expect(pick(promotion, snapshotProps)).toMatchSnapshot();
  87. });
  88. it(
  89. 'createPromotion throws with empty conditions and no couponCode',
  90. assertThrowsWithMessage(async () => {
  91. await adminClient.query<CreatePromotion.Mutation, CreatePromotion.Variables>(CREATE_PROMOTION, {
  92. input: {
  93. name: 'bad promotion',
  94. enabled: true,
  95. conditions: [],
  96. actions: [
  97. {
  98. code: promoAction.code,
  99. arguments: [
  100. {
  101. name: 'facetValueIds',
  102. value: '["T_1"]',
  103. },
  104. ],
  105. },
  106. ],
  107. },
  108. });
  109. }, 'A Promotion must have either at least one condition or a coupon code set'),
  110. );
  111. it('updatePromotion', async () => {
  112. const result = await adminClient.query<UpdatePromotion.Mutation, UpdatePromotion.Variables>(
  113. UPDATE_PROMOTION,
  114. {
  115. input: {
  116. id: promotion.id,
  117. couponCode: 'TEST1235',
  118. startsAt: new Date('2019-05-30T22:00:00.000Z'),
  119. endsAt: new Date('2019-06-01T22:00:00.000Z'),
  120. conditions: [
  121. {
  122. code: promoCondition.code,
  123. arguments: [{ name: 'arg', value: '90' }],
  124. },
  125. {
  126. code: promoCondition2.code,
  127. arguments: [{ name: 'arg', value: '10' }],
  128. },
  129. ],
  130. },
  131. },
  132. );
  133. expect(pick(result.updatePromotion, snapshotProps)).toMatchSnapshot();
  134. });
  135. it(
  136. 'updatePromotion throws with empty conditions and no couponCode',
  137. assertThrowsWithMessage(async () => {
  138. await adminClient.query<UpdatePromotion.Mutation, UpdatePromotion.Variables>(UPDATE_PROMOTION, {
  139. input: {
  140. id: promotion.id,
  141. couponCode: '',
  142. conditions: [],
  143. },
  144. });
  145. }, 'A Promotion must have either at least one condition or a coupon code set'),
  146. );
  147. it('promotion', async () => {
  148. const result = await adminClient.query<GetPromotion.Query, GetPromotion.Variables>(GET_PROMOTION, {
  149. id: promotion.id,
  150. });
  151. expect(result.promotion!.name).toBe(promotion.name);
  152. });
  153. it('promotions', async () => {
  154. const result = await adminClient.query<GetPromotionList.Query, GetPromotionList.Variables>(
  155. GET_PROMOTION_LIST,
  156. {},
  157. );
  158. expect(result.promotions.totalItems).toBe(1);
  159. expect(result.promotions.items[0].name).toBe('test promotion');
  160. });
  161. it('adjustmentOperations', async () => {
  162. const result = await adminClient.query<
  163. GetAdjustmentOperations.Query,
  164. GetAdjustmentOperations.Variables
  165. >(GET_ADJUSTMENT_OPERATIONS);
  166. expect(result.promotionActions).toMatchSnapshot();
  167. expect(result.promotionConditions).toMatchSnapshot();
  168. });
  169. describe('deletion', () => {
  170. let allPromotions: GetPromotionList.Items[];
  171. let promotionToDelete: GetPromotionList.Items;
  172. beforeAll(async () => {
  173. const result = await adminClient.query<GetPromotionList.Query>(GET_PROMOTION_LIST);
  174. allPromotions = result.promotions.items;
  175. });
  176. it('deletes a promotion', async () => {
  177. promotionToDelete = allPromotions[0];
  178. const result = await adminClient.query<DeletePromotion.Mutation, DeletePromotion.Variables>(
  179. DELETE_PROMOTION,
  180. { id: promotionToDelete.id },
  181. );
  182. expect(result.deletePromotion).toEqual({ result: DeletionResult.DELETED });
  183. });
  184. it('cannot get a deleted promotion', async () => {
  185. const result = await adminClient.query<GetPromotion.Query, GetPromotion.Variables>(
  186. GET_PROMOTION,
  187. {
  188. id: promotionToDelete.id,
  189. },
  190. );
  191. expect(result.promotion).toBe(null);
  192. });
  193. it('deleted promotion omitted from list', async () => {
  194. const result = await adminClient.query<GetPromotionList.Query>(GET_PROMOTION_LIST);
  195. expect(result.promotions.items.length).toBe(allPromotions.length - 1);
  196. expect(result.promotions.items.map(c => c.id).includes(promotionToDelete.id)).toBe(false);
  197. });
  198. it(
  199. 'updatePromotion throws for deleted promotion',
  200. assertThrowsWithMessage(
  201. () =>
  202. adminClient.query<UpdatePromotion.Mutation, UpdatePromotion.Variables>(UPDATE_PROMOTION, {
  203. input: {
  204. id: promotionToDelete.id,
  205. enabled: false,
  206. },
  207. }),
  208. `No Promotion with the id '1' could be found`,
  209. ),
  210. );
  211. });
  212. });
  213. function generateTestCondition(code: string): PromotionCondition<any> {
  214. return new PromotionCondition({
  215. code,
  216. description: [{ languageCode: LanguageCode.en, value: `description for ${code}` }],
  217. args: { arg: { type: 'int' } },
  218. check: (order, args) => true,
  219. });
  220. }
  221. function generateTestAction(code: string): PromotionAction<any> {
  222. return new PromotionOrderAction({
  223. code,
  224. description: [{ languageCode: LanguageCode.en, value: `description for ${code}` }],
  225. args: { facetValueIds: { type: 'ID', list: true } },
  226. execute: (order, args) => {
  227. return 42;
  228. },
  229. });
  230. }
  231. const DELETE_PROMOTION = gql`
  232. mutation DeletePromotion($id: ID!) {
  233. deletePromotion(id: $id) {
  234. result
  235. }
  236. }
  237. `;
  238. export const GET_PROMOTION_LIST = gql`
  239. query GetPromotionList($options: PromotionListOptions) {
  240. promotions(options: $options) {
  241. items {
  242. ...Promotion
  243. }
  244. totalItems
  245. }
  246. }
  247. ${PROMOTION_FRAGMENT}
  248. `;
  249. export const GET_PROMOTION = gql`
  250. query GetPromotion($id: ID!) {
  251. promotion(id: $id) {
  252. ...Promotion
  253. }
  254. }
  255. ${PROMOTION_FRAGMENT}
  256. `;
  257. export const UPDATE_PROMOTION = gql`
  258. mutation UpdatePromotion($input: UpdatePromotionInput!) {
  259. updatePromotion(input: $input) {
  260. ...Promotion
  261. }
  262. }
  263. ${PROMOTION_FRAGMENT}
  264. `;
  265. export const CONFIGURABLE_DEF_FRAGMENT = gql`
  266. fragment ConfigurableOperationDef on ConfigurableOperationDefinition {
  267. args {
  268. name
  269. type
  270. ui
  271. }
  272. code
  273. description
  274. }
  275. `;
  276. export const GET_ADJUSTMENT_OPERATIONS = gql`
  277. query GetAdjustmentOperations {
  278. promotionActions {
  279. ...ConfigurableOperationDef
  280. }
  281. promotionConditions {
  282. ...ConfigurableOperationDef
  283. }
  284. }
  285. ${CONFIGURABLE_DEF_FRAGMENT}
  286. `;