promotion.e2e-spec.ts 11 KB

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