plugin.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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. .setFrom('"test from" <noreply@test.com>')
  52. .setSubject('test subject');
  53. const module = await initPluginWithHandlers([handler]);
  54. eventBus.publish(new MockEvent(ctx, false));
  55. await pause();
  56. expect(onSend).not.toHaveBeenCalled();
  57. eventBus.publish(new MockEvent(ctx, true));
  58. await pause();
  59. expect(onSend).toHaveBeenCalledTimes(1);
  60. await module.close();
  61. });
  62. it('multiple filters', async () => {
  63. const handler = new EmailEventListener('test')
  64. .on(MockEvent)
  65. .filter(event => event.shouldSend === true)
  66. .filter(event => !!event.ctx.user)
  67. .setFrom('"test from" <noreply@test.com>')
  68. .setRecipient(() => 'test@test.com')
  69. .setSubject('test subject');
  70. const module = await initPluginWithHandlers([handler]);
  71. eventBus.publish(new MockEvent(ctx, true));
  72. await pause();
  73. expect(onSend).not.toHaveBeenCalled();
  74. eventBus.publish(new MockEvent({ ...ctx, user: 'joe' }, true));
  75. await pause();
  76. expect(onSend).toHaveBeenCalledTimes(1);
  77. await module.close();
  78. });
  79. });
  80. describe('templateVars', () => {
  81. const ctx = {
  82. channel: { code: DEFAULT_CHANNEL_CODE },
  83. languageCode: LanguageCode.en,
  84. } as any;
  85. it('interpolates subject', async () => {
  86. const handler = new EmailEventListener('test')
  87. .on(MockEvent)
  88. .setFrom('"test from" <noreply@test.com>')
  89. .setRecipient(() => 'test@test.com')
  90. .setSubject('Hello {{ subjectVar }}')
  91. .setTemplateVars(event => ({ subjectVar: 'foo' }));
  92. const module = await initPluginWithHandlers([handler]);
  93. eventBus.publish(new MockEvent(ctx, true));
  94. await pause();
  95. expect(onSend.mock.calls[0][0].subject).toBe('Hello foo');
  96. await module.close();
  97. });
  98. it('interpolates body', async () => {
  99. const handler = new EmailEventListener('test')
  100. .on(MockEvent)
  101. .setFrom('"test from" <noreply@test.com>')
  102. .setRecipient(() => 'test@test.com')
  103. .setSubject('Hello')
  104. .setTemplateVars(event => ({ testVar: 'this is the test var' }));
  105. const module = await initPluginWithHandlers([handler]);
  106. eventBus.publish(new MockEvent(ctx, true));
  107. await pause();
  108. expect(onSend.mock.calls[0][0].body).toContain('this is the test var');
  109. await module.close();
  110. });
  111. it('interpolates globalTemplateVars', async () => {
  112. const handler = new EmailEventListener('test')
  113. .on(MockEvent)
  114. .setFrom('"test from" <noreply@test.com>')
  115. .setRecipient(() => 'test@test.com')
  116. .setSubject('Hello {{ globalVar }}');
  117. const module = await initPluginWithHandlers([handler], {
  118. globalTemplateVars: { globalVar: 'baz' },
  119. });
  120. eventBus.publish(new MockEvent(ctx, true));
  121. await pause();
  122. expect(onSend.mock.calls[0][0].subject).toBe('Hello baz');
  123. await module.close();
  124. });
  125. it('interpolates from', async () => {
  126. const handler = new EmailEventListener('test')
  127. .on(MockEvent)
  128. .setFrom('"test from {{ globalVar }}" <noreply@test.com>')
  129. .setRecipient(() => 'test@test.com')
  130. .setSubject('Hello');
  131. const module = await initPluginWithHandlers([handler], {
  132. globalTemplateVars: { globalVar: 'baz' },
  133. });
  134. eventBus.publish(new MockEvent(ctx, true));
  135. await pause();
  136. expect(onSend.mock.calls[0][0].from).toBe('"test from baz" <noreply@test.com>');
  137. await module.close();
  138. });
  139. it('globalTemplateVars available in setTemplateVars method', async () => {
  140. const handler = new EmailEventListener('test')
  141. .on(MockEvent)
  142. .setFrom('"test from" <noreply@test.com>')
  143. .setRecipient(() => 'test@test.com')
  144. .setSubject('Hello {{ testVar }}')
  145. .setTemplateVars((event, globals) => ({ testVar: globals.globalVar + ' quux' }));
  146. const module = await initPluginWithHandlers([handler], {
  147. globalTemplateVars: { globalVar: 'baz' },
  148. });
  149. eventBus.publish(new MockEvent(ctx, true));
  150. await pause();
  151. expect(onSend.mock.calls[0][0].subject).toBe('Hello baz quux');
  152. await module.close();
  153. });
  154. it('setTemplateVars overrides globals', async () => {
  155. const handler = new EmailEventListener('test')
  156. .on(MockEvent)
  157. .setFrom('"test from" <noreply@test.com>')
  158. .setRecipient(() => 'test@test.com')
  159. .setSubject('Hello {{ name }}')
  160. .setTemplateVars((event, globals) => ({ name: 'quux' }));
  161. const module = await initPluginWithHandlers([handler], { globalTemplateVars: { name: 'baz' } });
  162. eventBus.publish(new MockEvent(ctx, true));
  163. await pause();
  164. expect(onSend.mock.calls[0][0].subject).toBe('Hello quux');
  165. await module.close();
  166. });
  167. });
  168. describe('multiple configs', () => {
  169. const ctx = {
  170. channel: { code: DEFAULT_CHANNEL_CODE },
  171. languageCode: LanguageCode.en,
  172. } as any;
  173. it('additional LanguageCode', async () => {
  174. const handler = new EmailEventListener('test')
  175. .on(MockEvent)
  176. .setFrom('"test from" <noreply@test.com>')
  177. .setSubject('Hello, {{ name }}!')
  178. .setRecipient(() => 'test@test.com')
  179. .setTemplateVars(() => ({ name: 'Test' }))
  180. .addTemplate({
  181. channelCode: 'default',
  182. languageCode: LanguageCode.de,
  183. templateFile: 'body.de.hbs',
  184. subject: 'Servus, {{ name }}!',
  185. });
  186. const module = await initPluginWithHandlers([handler]);
  187. eventBus.publish(new MockEvent({ ...ctx, languageCode: LanguageCode.ta }, true));
  188. await pause();
  189. expect(onSend.mock.calls[0][0].subject).toBe('Hello, Test!');
  190. expect(onSend.mock.calls[0][0].body).toContain('Default body.');
  191. eventBus.publish(new MockEvent({ ...ctx, languageCode: LanguageCode.de }, true));
  192. await pause();
  193. expect(onSend.mock.calls[1][0].subject).toBe('Servus, Test!');
  194. expect(onSend.mock.calls[1][0].body).toContain('German body.');
  195. await module.close();
  196. });
  197. });
  198. describe('orderConfirmationHandler', () => {
  199. let module: TestingModule;
  200. beforeEach(async () => {
  201. module = await initPluginWithHandlers([orderConfirmationHandler], {
  202. templatePath: path.join(__dirname, '../templates'),
  203. });
  204. });
  205. afterEach(async () => {
  206. await module.close();
  207. });
  208. const ctx = {
  209. channel: { code: DEFAULT_CHANNEL_CODE },
  210. languageCode: LanguageCode.en,
  211. } as any;
  212. const order = ({
  213. code: 'ABCDE',
  214. customer: {
  215. emailAddress: 'test@test.com',
  216. },
  217. } as Partial<Order>) as any;
  218. it('filters events with wrong order state', async () => {
  219. eventBus.publish(new OrderStateTransitionEvent('AddingItems', 'ArrangingPayment', ctx, order));
  220. await pause();
  221. expect(onSend).not.toHaveBeenCalled();
  222. eventBus.publish(new OrderStateTransitionEvent('AddingItems', 'Cancelled', ctx, order));
  223. await pause();
  224. expect(onSend).not.toHaveBeenCalled();
  225. eventBus.publish(new OrderStateTransitionEvent('AddingItems', 'PaymentAuthorized', ctx, order));
  226. await pause();
  227. expect(onSend).not.toHaveBeenCalled();
  228. eventBus.publish(new OrderStateTransitionEvent('ArrangingPayment', 'PaymentSettled', ctx, order));
  229. await pause();
  230. expect(onSend).toHaveBeenCalledTimes(1);
  231. });
  232. it('sets the Order Customer emailAddress as recipient', async () => {
  233. eventBus.publish(new OrderStateTransitionEvent('ArrangingPayment', 'PaymentSettled', ctx, order));
  234. await pause();
  235. expect(onSend.mock.calls[0][0].recipient).toBe(order.customer!.emailAddress);
  236. });
  237. it('sets the subject', async () => {
  238. eventBus.publish(new OrderStateTransitionEvent('ArrangingPayment', 'PaymentSettled', ctx, order));
  239. await pause();
  240. expect(onSend.mock.calls[0][0].subject).toBe(`Order confirmation for #${order.code}`);
  241. });
  242. });
  243. });
  244. const pause = () => new Promise(resolve => setTimeout(resolve, 50));
  245. class MockEvent extends VendureEvent {
  246. constructor(public ctx: any, public shouldSend: boolean) {
  247. super();
  248. }
  249. }