promotion.e2e-spec.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  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 { TEST_SETUP_TIMEOUT_MS, testConfig } 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', type: 'int' }],
  69. },
  70. ],
  71. actions: [
  72. {
  73. code: promoAction.code,
  74. arguments: [
  75. {
  76. name: 'facetValueIds',
  77. value: '["T_1"]',
  78. type: 'facetValueIds',
  79. },
  80. ],
  81. },
  82. ],
  83. },
  84. },
  85. );
  86. promotion = result.createPromotion;
  87. expect(pick(promotion, snapshotProps)).toMatchSnapshot();
  88. });
  89. it(
  90. 'createPromotion throws with empty conditions and no couponCode',
  91. assertThrowsWithMessage(async () => {
  92. await adminClient.query<CreatePromotion.Mutation, CreatePromotion.Variables>(CREATE_PROMOTION, {
  93. input: {
  94. name: 'bad promotion',
  95. enabled: true,
  96. conditions: [],
  97. actions: [
  98. {
  99. code: promoAction.code,
  100. arguments: [
  101. {
  102. name: 'facetValueIds',
  103. value: '["T_1"]',
  104. type: 'facetValueIds',
  105. },
  106. ],
  107. },
  108. ],
  109. },
  110. });
  111. }, 'A Promotion must have either at least one condition or a coupon code set'),
  112. );
  113. it('updatePromotion', async () => {
  114. const result = await adminClient.query<UpdatePromotion.Mutation, UpdatePromotion.Variables>(
  115. UPDATE_PROMOTION,
  116. {
  117. input: {
  118. id: promotion.id,
  119. couponCode: 'TEST1235',
  120. startsAt: new Date('2019-05-30T22:00:00.000Z'),
  121. endsAt: new Date('2019-06-01T22:00:00.000Z'),
  122. conditions: [
  123. {
  124. code: promoCondition.code,
  125. arguments: [{ name: 'arg', value: '90', type: 'int' }],
  126. },
  127. {
  128. code: promoCondition2.code,
  129. arguments: [{ name: 'arg', value: '10', type: 'int' }],
  130. },
  131. ],
  132. },
  133. },
  134. );
  135. expect(pick(result.updatePromotion, snapshotProps)).toMatchSnapshot();
  136. });
  137. it(
  138. 'updatePromotion throws with empty conditions and no couponCode',
  139. assertThrowsWithMessage(async () => {
  140. await adminClient.query<UpdatePromotion.Mutation, UpdatePromotion.Variables>(UPDATE_PROMOTION, {
  141. input: {
  142. id: promotion.id,
  143. couponCode: '',
  144. conditions: [],
  145. },
  146. });
  147. }, 'A Promotion must have either at least one condition or a coupon code set'),
  148. );
  149. it('promotion', async () => {
  150. const result = await adminClient.query<GetPromotion.Query, GetPromotion.Variables>(GET_PROMOTION, {
  151. id: promotion.id,
  152. });
  153. expect(result.promotion!.name).toBe(promotion.name);
  154. });
  155. it('promotions', async () => {
  156. const result = await adminClient.query<GetPromotionList.Query, GetPromotionList.Variables>(
  157. GET_PROMOTION_LIST,
  158. {},
  159. );
  160. expect(result.promotions.totalItems).toBe(1);
  161. expect(result.promotions.items[0].name).toBe('test promotion');
  162. });
  163. it('adjustmentOperations', async () => {
  164. const result = await adminClient.query<
  165. GetAdjustmentOperations.Query,
  166. GetAdjustmentOperations.Variables
  167. >(GET_ADJUSTMENT_OPERATIONS);
  168. expect(result.promotionActions).toMatchSnapshot();
  169. expect(result.promotionConditions).toMatchSnapshot();
  170. });
  171. describe('deletion', () => {
  172. let allPromotions: GetPromotionList.Items[];
  173. let promotionToDelete: GetPromotionList.Items;
  174. beforeAll(async () => {
  175. const result = await adminClient.query<GetPromotionList.Query>(GET_PROMOTION_LIST);
  176. allPromotions = result.promotions.items;
  177. });
  178. it('deletes a promotion', async () => {
  179. promotionToDelete = allPromotions[0];
  180. const result = await adminClient.query<DeletePromotion.Mutation, DeletePromotion.Variables>(
  181. DELETE_PROMOTION,
  182. { id: promotionToDelete.id },
  183. );
  184. expect(result.deletePromotion).toEqual({ result: DeletionResult.DELETED });
  185. });
  186. it('cannot get a deleted promotion', async () => {
  187. const result = await adminClient.query<GetPromotion.Query, GetPromotion.Variables>(
  188. GET_PROMOTION,
  189. {
  190. id: promotionToDelete.id,
  191. },
  192. );
  193. expect(result.promotion).toBe(null);
  194. });
  195. it('deleted promotion omitted from list', async () => {
  196. const result = await adminClient.query<GetPromotionList.Query>(GET_PROMOTION_LIST);
  197. expect(result.promotions.items.length).toBe(allPromotions.length - 1);
  198. expect(result.promotions.items.map(c => c.id).includes(promotionToDelete.id)).toBe(false);
  199. });
  200. it(
  201. 'updatePromotion throws for deleted promotion',
  202. assertThrowsWithMessage(
  203. () =>
  204. adminClient.query<UpdatePromotion.Mutation, UpdatePromotion.Variables>(UPDATE_PROMOTION, {
  205. input: {
  206. id: promotionToDelete.id,
  207. enabled: false,
  208. },
  209. }),
  210. `No Promotion with the id '1' could be found`,
  211. ),
  212. );
  213. });
  214. });
  215. function generateTestCondition(code: string): PromotionCondition<any> {
  216. return new PromotionCondition({
  217. code,
  218. description: [{ languageCode: LanguageCode.en, value: `description for ${code}` }],
  219. args: { arg: { type: 'int' } },
  220. check: (order, args) => true,
  221. });
  222. }
  223. function generateTestAction(code: string): PromotionAction<any> {
  224. return new PromotionOrderAction({
  225. code,
  226. description: [{ languageCode: LanguageCode.en, value: `description for ${code}` }],
  227. args: { facetValueIds: { type: 'facetValueIds' } },
  228. execute: (order, args) => {
  229. return 42;
  230. },
  231. });
  232. }
  233. const DELETE_PROMOTION = gql`
  234. mutation DeletePromotion($id: ID!) {
  235. deletePromotion(id: $id) {
  236. result
  237. }
  238. }
  239. `;
  240. export const GET_PROMOTION_LIST = gql`
  241. query GetPromotionList($options: PromotionListOptions) {
  242. promotions(options: $options) {
  243. items {
  244. ...Promotion
  245. }
  246. totalItems
  247. }
  248. }
  249. ${PROMOTION_FRAGMENT}
  250. `;
  251. export const GET_PROMOTION = gql`
  252. query GetPromotion($id: ID!) {
  253. promotion(id: $id) {
  254. ...Promotion
  255. }
  256. }
  257. ${PROMOTION_FRAGMENT}
  258. `;
  259. export const UPDATE_PROMOTION = gql`
  260. mutation UpdatePromotion($input: UpdatePromotionInput!) {
  261. updatePromotion(input: $input) {
  262. ...Promotion
  263. }
  264. }
  265. ${PROMOTION_FRAGMENT}
  266. `;
  267. export const CONFIGURABLE_DEF_FRAGMENT = gql`
  268. fragment ConfigurableOperationDef on ConfigurableOperationDefinition {
  269. args {
  270. name
  271. type
  272. config
  273. }
  274. code
  275. description
  276. }
  277. `;
  278. export const GET_ADJUSTMENT_OPERATIONS = gql`
  279. query GetAdjustmentOperations {
  280. promotionActions {
  281. ...ConfigurableOperationDef
  282. }
  283. promotionConditions {
  284. ...ConfigurableOperationDef
  285. }
  286. }
  287. ${CONFIGURABLE_DEF_FRAGMENT}
  288. `;