promotion.e2e-spec.ts 11 KB

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