plugin.spec.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. /* tslint:disable:no-non-null-assertion */
  2. import { Test, TestingModule } from '@nestjs/testing';
  3. import path from 'path';
  4. import { LanguageCode } from '../../common/lib/generated-types';
  5. import { DEFAULT_CHANNEL_CODE } from '../../common/lib/shared-constants';
  6. import { Order } from '../../core/dist/entity/order/order.entity';
  7. import { EventBus } from '../../core/dist/event-bus/event-bus';
  8. import { OrderStateTransitionEvent } from '../../core/dist/event-bus/events/order-state-transition-event';
  9. import { VendureEvent } from '../../core/dist/event-bus/vendure-event';
  10. import { orderConfirmationHandler } from './default-email-handlers';
  11. import { EmailEventHandler, EmailEventListener } from './event-listener';
  12. import { EmailPlugin } from './plugin';
  13. import { EmailPluginOptions } from './types';
  14. describe('EmailPlugin', () => {
  15. let plugin: EmailPlugin;
  16. let eventBus: EventBus;
  17. let onSend: jest.Mock;
  18. async function initPluginWithHandlers(
  19. handlers: Array<EmailEventHandler<string, any>>,
  20. options?: Partial<EmailPluginOptions>,
  21. ) {
  22. onSend = jest.fn();
  23. const module = await Test.createTestingModule({
  24. imports: [
  25. EmailPlugin.init({
  26. templatePath: path.join(__dirname, '../test-templates'),
  27. transport: {
  28. type: 'testing',
  29. onSend,
  30. },
  31. handlers,
  32. ...options,
  33. }),
  34. ],
  35. }).compile();
  36. plugin = module.get(EmailPlugin);
  37. eventBus = module.get(EventBus);
  38. await plugin.onVendureBootstrap();
  39. return module;
  40. }
  41. describe('event filtering', () => {
  42. const ctx = {
  43. channel: { code: DEFAULT_CHANNEL_CODE },
  44. languageCode: LanguageCode.en,
  45. } as any;
  46. it('single filter', async () => {
  47. const handler = new EmailEventListener('test')
  48. .on(MockEvent)
  49. .filter(event => event.shouldSend === true)
  50. .setRecipient(() => 'test@test.com')
  51. .setSubject('test subject');
  52. const module = await initPluginWithHandlers([handler]);
  53. eventBus.publish(new MockEvent(ctx, false));
  54. await pause();
  55. expect(onSend).not.toHaveBeenCalled();
  56. eventBus.publish(new MockEvent(ctx, true));
  57. await pause();
  58. expect(onSend).toHaveBeenCalledTimes(1);
  59. await module.close();
  60. });
  61. it('multiple filters', async () => {
  62. const handler = new EmailEventListener('test')
  63. .on(MockEvent)
  64. .filter(event => event.shouldSend === true)
  65. .filter(event => !!event.ctx.user)
  66. .setRecipient(() => 'test@test.com')
  67. .setSubject('test subject');
  68. const module = await initPluginWithHandlers([handler]);
  69. eventBus.publish(new MockEvent(ctx, true));
  70. await pause();
  71. expect(onSend).not.toHaveBeenCalled();
  72. eventBus.publish(new MockEvent({ ...ctx, user: 'joe' }, true));
  73. await pause();
  74. expect(onSend).toHaveBeenCalledTimes(1);
  75. await module.close();
  76. });
  77. });
  78. describe('templateVars', () => {
  79. const ctx = {
  80. channel: { code: DEFAULT_CHANNEL_CODE },
  81. languageCode: LanguageCode.en,
  82. } as any;
  83. it('interpolates subject', async () => {
  84. const handler = new EmailEventListener('test')
  85. .on(MockEvent)
  86. .setRecipient(() => 'test@test.com')
  87. .setSubject('Hello {{ subjectVar }}')
  88. .setTemplateVars(event => ({ subjectVar: 'foo' }));
  89. const module = await initPluginWithHandlers([handler]);
  90. eventBus.publish(new MockEvent(ctx, true));
  91. await pause();
  92. expect(onSend.mock.calls[0][0].subject).toBe('Hello foo');
  93. await module.close();
  94. });
  95. it('interpolates body', async () => {
  96. const handler = new EmailEventListener('test')
  97. .on(MockEvent)
  98. .setRecipient(() => 'test@test.com')
  99. .setSubject('Hello')
  100. .setTemplateVars(event => ({ testVar: 'this is the test var' }));
  101. const module = await initPluginWithHandlers([handler]);
  102. eventBus.publish(new MockEvent(ctx, true));
  103. await pause();
  104. expect(onSend.mock.calls[0][0].body).toContain('this is the test var');
  105. await module.close();
  106. });
  107. it('interpolates globalTemplateVars', async () => {
  108. const handler = new EmailEventListener('test')
  109. .on(MockEvent)
  110. .setRecipient(() => 'test@test.com')
  111. .setSubject('Hello {{ globalVar }}');
  112. const module = await initPluginWithHandlers([handler], {
  113. globalTemplateVars: { globalVar: 'baz' },
  114. });
  115. eventBus.publish(new MockEvent(ctx, true));
  116. await pause();
  117. expect(onSend.mock.calls[0][0].subject).toBe('Hello baz');
  118. await module.close();
  119. });
  120. it('globalTemplateVars available in setTemplateVars method', async () => {
  121. const handler = new EmailEventListener('test')
  122. .on(MockEvent)
  123. .setRecipient(() => 'test@test.com')
  124. .setSubject('Hello {{ testVar }}')
  125. .setTemplateVars((event, globals) => ({ testVar: globals.globalVar + ' quux' }));
  126. const module = await initPluginWithHandlers([handler], {
  127. globalTemplateVars: { globalVar: 'baz' },
  128. });
  129. eventBus.publish(new MockEvent(ctx, true));
  130. await pause();
  131. expect(onSend.mock.calls[0][0].subject).toBe('Hello baz quux');
  132. await module.close();
  133. });
  134. it('setTemplateVars overrides globals', async () => {
  135. const handler = new EmailEventListener('test')
  136. .on(MockEvent)
  137. .setRecipient(() => 'test@test.com')
  138. .setSubject('Hello {{ name }}')
  139. .setTemplateVars((event, globals) => ({ name: 'quux' }));
  140. const module = await initPluginWithHandlers([handler], { globalTemplateVars: { name: 'baz' } });
  141. eventBus.publish(new MockEvent(ctx, true));
  142. await pause();
  143. expect(onSend.mock.calls[0][0].subject).toBe('Hello quux');
  144. await module.close();
  145. });
  146. });
  147. describe('multiple configs', () => {
  148. const ctx = {
  149. channel: { code: DEFAULT_CHANNEL_CODE },
  150. languageCode: LanguageCode.en,
  151. } as any;
  152. it('additional LanguageCode', async () => {
  153. const handler = new EmailEventListener('test')
  154. .on(MockEvent)
  155. .setSubject('Hello, {{ name }}!')
  156. .setRecipient(() => 'test@test.com')
  157. .setTemplateVars(() => ({ name: 'Test' }))
  158. .addTemplate({
  159. channelCode: 'default',
  160. languageCode: LanguageCode.de,
  161. templateFile: 'body.de.hbs',
  162. subject: 'Servus, {{ name }}!',
  163. });
  164. const module = await initPluginWithHandlers([handler]);
  165. eventBus.publish(new MockEvent({ ...ctx, languageCode: LanguageCode.ta }, true));
  166. await pause();
  167. expect(onSend.mock.calls[0][0].subject).toBe('Hello, Test!');
  168. expect(onSend.mock.calls[0][0].body).toContain('Default body.');
  169. eventBus.publish(new MockEvent({ ...ctx, languageCode: LanguageCode.de }, true));
  170. await pause();
  171. expect(onSend.mock.calls[1][0].subject).toBe('Servus, Test!');
  172. expect(onSend.mock.calls[1][0].body).toContain('German body.');
  173. await module.close();
  174. });
  175. });
  176. describe('orderConfirmationHandler', () => {
  177. let module: TestingModule;
  178. beforeEach(async () => {
  179. module = await initPluginWithHandlers([orderConfirmationHandler], {
  180. templatePath: path.join(__dirname, '../templates'),
  181. });
  182. });
  183. afterEach(async () => {
  184. await module.close();
  185. });
  186. const ctx = {
  187. channel: { code: DEFAULT_CHANNEL_CODE },
  188. languageCode: LanguageCode.en,
  189. } as any;
  190. const order = ({
  191. code: 'ABCDE',
  192. customer: {
  193. emailAddress: 'test@test.com',
  194. },
  195. } as Partial<Order>) as any;
  196. it('filters events with wrong order state', async () => {
  197. eventBus.publish(new OrderStateTransitionEvent('AddingItems', 'ArrangingPayment', ctx, order));
  198. await pause();
  199. expect(onSend).not.toHaveBeenCalled();
  200. eventBus.publish(new OrderStateTransitionEvent('AddingItems', 'Cancelled', ctx, order));
  201. await pause();
  202. expect(onSend).not.toHaveBeenCalled();
  203. eventBus.publish(new OrderStateTransitionEvent('AddingItems', 'PaymentAuthorized', ctx, order));
  204. await pause();
  205. expect(onSend).not.toHaveBeenCalled();
  206. eventBus.publish(new OrderStateTransitionEvent('ArrangingPayment', 'PaymentSettled', ctx, order));
  207. await pause();
  208. expect(onSend).toHaveBeenCalledTimes(1);
  209. });
  210. it('sets the Order Customer emailAddress as recipient', async () => {
  211. eventBus.publish(new OrderStateTransitionEvent('ArrangingPayment', 'PaymentSettled', ctx, order));
  212. await pause();
  213. expect(onSend.mock.calls[0][0].recipient).toBe(order.customer!.emailAddress);
  214. });
  215. it('sets the subject', async () => {
  216. eventBus.publish(new OrderStateTransitionEvent('ArrangingPayment', 'PaymentSettled', ctx, order));
  217. await pause();
  218. expect(onSend.mock.calls[0][0].subject).toBe(`Order confirmation for #${order.code}`);
  219. });
  220. });
  221. });
  222. const pause = () => new Promise(resolve => setTimeout(resolve, 50));
  223. class MockEvent extends VendureEvent {
  224. constructor(public ctx: any, public shouldSend: boolean) {
  225. super();
  226. }
  227. }