core-services-sdk 1.2.4 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -1
- package/src/index.js +8 -0
- package/src/mailer/index.js +9 -0
- package/src/mailer/mailer.service.js +28 -0
- package/src/mailer/transport.factory.js +45 -0
- package/src/templates/template-loader.js +70 -0
- package/tests/mailer.integration.test.js +46 -0
- package/tests/mailer.unit.test.js +68 -0
- package/tests/template-loader.integration.test.js +49 -0
- package/tests/template-loader.unit.test.js +63 -0
- package/tests/transport.factory.unit.test.js +97 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core-services-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -20,9 +20,13 @@
|
|
|
20
20
|
"description": "",
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"amqplib": "^0.10.8",
|
|
23
|
+
"aws-sdk": "^2.1692.0",
|
|
24
|
+
"dot": "^1.1.3",
|
|
23
25
|
"http-status": "^2.1.0",
|
|
24
26
|
"mongodb": "^6.17.0",
|
|
25
27
|
"node-fetch": "^3.3.2",
|
|
28
|
+
"nodemailer": "^7.0.5",
|
|
29
|
+
"nodemailer-sendgrid-transport": "^0.2.0",
|
|
26
30
|
"uuid": "^11.1.0",
|
|
27
31
|
"xml2js": "^0.6.2"
|
|
28
32
|
},
|
package/src/index.js
CHANGED
|
@@ -4,4 +4,12 @@ export * from './mongodb/connect.js'
|
|
|
4
4
|
export * from './rabbit-mq/index.js'
|
|
5
5
|
export * as http from './http/http.js'
|
|
6
6
|
export * from './http/responseType.js'
|
|
7
|
+
export { initMailer } from './mailer/index.js'
|
|
7
8
|
export { HttpError } from './http/HttpError.js'
|
|
9
|
+
export { Mailer } from './mailer/mailer.service.js'
|
|
10
|
+
export { TransportFactory } from './mailer/transport.factory.js'
|
|
11
|
+
export {
|
|
12
|
+
isItFile,
|
|
13
|
+
loadTemplates,
|
|
14
|
+
getTemplateContent,
|
|
15
|
+
} from './templates/template-loader.js'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export class Mailer {
|
|
2
|
+
/**
|
|
3
|
+
* @param {object} transporter - Nodemailer transporter instance
|
|
4
|
+
*/
|
|
5
|
+
constructor(transporter) {
|
|
6
|
+
if (!transporter || typeof transporter.sendMail !== 'function') {
|
|
7
|
+
throw new Error('Invalid transporter')
|
|
8
|
+
}
|
|
9
|
+
this.transporter = transporter
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Send an email
|
|
14
|
+
*/
|
|
15
|
+
async send({ to, subject, html, text, from, cc, bcc, replyTo, attachments }) {
|
|
16
|
+
return this.transporter.sendMail({
|
|
17
|
+
to,
|
|
18
|
+
cc,
|
|
19
|
+
bcc,
|
|
20
|
+
text,
|
|
21
|
+
from,
|
|
22
|
+
html,
|
|
23
|
+
subject,
|
|
24
|
+
replyTo,
|
|
25
|
+
attachments,
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import aws from 'aws-sdk'
|
|
2
|
+
import nodemailer from 'nodemailer'
|
|
3
|
+
import sgTransport from 'nodemailer-sendgrid-transport'
|
|
4
|
+
|
|
5
|
+
export class TransportFactory {
|
|
6
|
+
/**
|
|
7
|
+
* @param {object} config
|
|
8
|
+
* @returns {import('nodemailer').Transporter}
|
|
9
|
+
*/
|
|
10
|
+
static create(config) {
|
|
11
|
+
switch (config.type) {
|
|
12
|
+
case 'smtp':
|
|
13
|
+
return nodemailer.createTransport({
|
|
14
|
+
host: config.host,
|
|
15
|
+
port: config.port,
|
|
16
|
+
secure: config.secure,
|
|
17
|
+
auth: config.auth,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
case 'gmail':
|
|
21
|
+
return nodemailer.createTransport({
|
|
22
|
+
service: 'gmail',
|
|
23
|
+
auth: config.auth,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
case 'sendgrid':
|
|
27
|
+
return nodemailer.createTransport(
|
|
28
|
+
sgTransport({
|
|
29
|
+
auth: { api_key: config.apiKey },
|
|
30
|
+
}),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
case 'ses':
|
|
34
|
+
const ses = new aws.SES({
|
|
35
|
+
accessKeyId: config.accessKeyId,
|
|
36
|
+
secretAccessKey: config.secretAccessKey,
|
|
37
|
+
region: config.region,
|
|
38
|
+
})
|
|
39
|
+
return nodemailer.createTransport({ SES: { ses, aws } })
|
|
40
|
+
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(`Unsupported transport type: ${config.type}`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import dot from 'dot'
|
|
2
|
+
import { lstat, readFile } from 'fs/promises'
|
|
3
|
+
|
|
4
|
+
const { compile } = dot
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check if the input is a file path.
|
|
8
|
+
* @param {string} filePathOrString - Either a string of template content or a file path to a template.
|
|
9
|
+
* @returns {Promise<boolean>} - True if it's a valid file path, false otherwise.
|
|
10
|
+
*/
|
|
11
|
+
export const isItFile = async (filePathOrString) => {
|
|
12
|
+
try {
|
|
13
|
+
const stats = await lstat(filePathOrString)
|
|
14
|
+
return stats.isFile()
|
|
15
|
+
} catch {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get template content from string or file.
|
|
22
|
+
* @param {string} maybeFilePathOrString - Raw template string or path to template file.
|
|
23
|
+
* @returns {Promise<string>} - The resolved content of the template.
|
|
24
|
+
*/
|
|
25
|
+
export const getTemplateContent = async (maybeFilePathOrString) => {
|
|
26
|
+
return (await isItFile(maybeFilePathOrString))
|
|
27
|
+
? readFile(maybeFilePathOrString, 'utf-8')
|
|
28
|
+
: maybeFilePathOrString
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Load and compile templates using dot.js
|
|
33
|
+
*
|
|
34
|
+
* @param {Record<string, string>} templateSet - An object with keys as template names (e.g. "subject", "html") and values as either raw strings or file paths.
|
|
35
|
+
* @returns {Promise<Record<string, (params: Record<string, any>) => string>>} - A map of compiled template functions.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // Inline templates
|
|
39
|
+
* const templates = await loadTemplates({
|
|
40
|
+
* from: '"{{=it.name}}" <{{=it.email}}>',
|
|
41
|
+
* html: '<div>Hello {{=it.name}}, welcome aboard!</div>',
|
|
42
|
+
* subject: 'Welcome to {{=it.appName}}',
|
|
43
|
+
* })
|
|
44
|
+
*
|
|
45
|
+
* const result = {
|
|
46
|
+
* from: templates.from({ name: 'ChatGPT', email: 'chat@gpt.com' }),
|
|
47
|
+
* html: templates.html({ name: 'Haim' }),
|
|
48
|
+
* subject: templates.subject({ appName: 'Authenzify' }),
|
|
49
|
+
* }
|
|
50
|
+
* console.log(result)
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // Template loaded from files
|
|
54
|
+
* const templates = await loadTemplates({
|
|
55
|
+
* from: './email-templates/from.ejs',
|
|
56
|
+
* html: './email-templates/body.html',
|
|
57
|
+
* subject: './email-templates/subject.ejs',
|
|
58
|
+
* })
|
|
59
|
+
*
|
|
60
|
+
* const output = templates.subject({ appName: 'MyApp' })
|
|
61
|
+
*/
|
|
62
|
+
export const loadTemplates = async (templateSet) => {
|
|
63
|
+
const entries = await Promise.all(
|
|
64
|
+
Object.entries(templateSet).map(async ([key, value]) => {
|
|
65
|
+
const content = await getTemplateContent(value)
|
|
66
|
+
return [key, compile(content)]
|
|
67
|
+
}),
|
|
68
|
+
)
|
|
69
|
+
return Object.fromEntries(entries)
|
|
70
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import nodemailer from 'nodemailer'
|
|
3
|
+
import { Mailer } from '../src/mailer/mailer.service.js'
|
|
4
|
+
|
|
5
|
+
describe('Mailer (integration)', () => {
|
|
6
|
+
it('should send email using ethereal SMTP', async () => {
|
|
7
|
+
// Create ethereal test account
|
|
8
|
+
const testAccount = await nodemailer.createTestAccount()
|
|
9
|
+
|
|
10
|
+
// Create Nodemailer transporter for ethereal
|
|
11
|
+
const transporter = nodemailer.createTransport({
|
|
12
|
+
host: testAccount.smtp.host,
|
|
13
|
+
port: testAccount.smtp.port,
|
|
14
|
+
secure: testAccount.smtp.secure,
|
|
15
|
+
auth: {
|
|
16
|
+
user: testAccount.user,
|
|
17
|
+
pass: testAccount.pass,
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// Create Mailer instance
|
|
22
|
+
const mailer = new Mailer(transporter)
|
|
23
|
+
|
|
24
|
+
// Send test email
|
|
25
|
+
const result = await mailer.send({
|
|
26
|
+
to: 'recipient@example.com',
|
|
27
|
+
text: 'Hello from test - plain text',
|
|
28
|
+
from: '"My App" <no-reply@example.com>',
|
|
29
|
+
subject: 'core-service-sdk:Integration Test Email',
|
|
30
|
+
html: '<h1>Hello from core-service-sdk test</h1><p>This is a core-service-sdk</p>',
|
|
31
|
+
cc: '',
|
|
32
|
+
bcc: '',
|
|
33
|
+
replyTo: '',
|
|
34
|
+
attachments: '',
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// Assert response
|
|
38
|
+
expect(result.messageId).toBeDefined()
|
|
39
|
+
|
|
40
|
+
// Print preview URL (clickable in terminal)
|
|
41
|
+
const previewUrl = nodemailer.getTestMessageUrl(result)
|
|
42
|
+
console.log(`📨 Preview this email: ${previewUrl}`)
|
|
43
|
+
|
|
44
|
+
expect(previewUrl).toMatch(/^https:\/\/ethereal\.email/)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const mockTransport = {
|
|
4
|
+
sendMail: vi.fn(),
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Dynamically mock the TransportFactory module and turn .create into a spy
|
|
8
|
+
vi.mock('../src/mailer/transport.factory.js', async () => {
|
|
9
|
+
const actual = await vi.importActual('../src/mailer/transport.factory.js')
|
|
10
|
+
return {
|
|
11
|
+
...actual,
|
|
12
|
+
TransportFactory: {
|
|
13
|
+
create: vi.fn(() => mockTransport),
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
// Mock Mailer class
|
|
19
|
+
vi.mock('../src/mailer/mailer.service.js', async () => {
|
|
20
|
+
const Mailer = vi.fn().mockImplementation((transport) => ({
|
|
21
|
+
send: vi.fn((opts) => transport.sendMail(opts)),
|
|
22
|
+
}))
|
|
23
|
+
return { Mailer }
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// Only now import code under test
|
|
27
|
+
import { initMailer } from '../src/mailer/index.js'
|
|
28
|
+
import { TransportFactory } from '../src/mailer/transport.factory.js'
|
|
29
|
+
import { Mailer } from '../src/mailer/mailer.service.js'
|
|
30
|
+
|
|
31
|
+
describe('initMailer', () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.clearAllMocks()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should create a Mailer instance with correct transport', () => {
|
|
37
|
+
const config = {
|
|
38
|
+
type: 'smtp',
|
|
39
|
+
host: 'host',
|
|
40
|
+
port: 587,
|
|
41
|
+
secure: false,
|
|
42
|
+
auth: { user: 'u', pass: 'p' },
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const mailer = initMailer(config)
|
|
46
|
+
|
|
47
|
+
// Ensure TransportFactory.create was called
|
|
48
|
+
expect(TransportFactory.create).toHaveBeenCalledWith(config)
|
|
49
|
+
|
|
50
|
+
// Ensure Mailer was created with the mock transport
|
|
51
|
+
expect(Mailer).toHaveBeenCalledWith(mockTransport)
|
|
52
|
+
|
|
53
|
+
const emailOptions = {
|
|
54
|
+
to: 'a@b.com',
|
|
55
|
+
subject: 'Test',
|
|
56
|
+
html: '<b>Test</b>',
|
|
57
|
+
from: '"My App" <no-reply@example.com>',
|
|
58
|
+
cc: '',
|
|
59
|
+
bcc: '',
|
|
60
|
+
text: '',
|
|
61
|
+
replyTo: '',
|
|
62
|
+
attachments: '',
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
mailer.send(emailOptions)
|
|
66
|
+
expect(mockTransport.sendMail).toHaveBeenCalledWith(emailOptions)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { join } from 'path'
|
|
2
|
+
import { tmpdir } from 'os'
|
|
3
|
+
import { writeFile, rm, mkdir } from 'fs/promises'
|
|
4
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { loadTemplates } from '../src/templates/template-loader.js'
|
|
7
|
+
|
|
8
|
+
const tempDir = join(tmpdir(), 'template-tests')
|
|
9
|
+
|
|
10
|
+
const createTempTemplateFile = async (filename, content) => {
|
|
11
|
+
const filePath = join(tempDir, filename)
|
|
12
|
+
await writeFile(filePath, content, 'utf-8')
|
|
13
|
+
return filePath
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('loadTemplates with real files', () => {
|
|
17
|
+
let subjectPath, htmlPath
|
|
18
|
+
|
|
19
|
+
beforeAll(async () => {
|
|
20
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {})
|
|
21
|
+
await mkdir(tempDir, { recursive: true })
|
|
22
|
+
|
|
23
|
+
subjectPath = await createTempTemplateFile(
|
|
24
|
+
'subject.dot',
|
|
25
|
+
'Hello {{=it.name}}',
|
|
26
|
+
)
|
|
27
|
+
htmlPath = await createTempTemplateFile(
|
|
28
|
+
'body.dot',
|
|
29
|
+
'<div>{{=it.body}}</div>',
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
afterAll(async () => {
|
|
34
|
+
await rm(tempDir, { recursive: true, force: true })
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should correctly read and compile templates from file paths', async () => {
|
|
38
|
+
const templates = await loadTemplates({
|
|
39
|
+
subject: subjectPath,
|
|
40
|
+
html: htmlPath,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const subjectResult = templates.subject({ name: 'Alice' })
|
|
44
|
+
const htmlResult = templates.html({ body: 'Welcome!' })
|
|
45
|
+
|
|
46
|
+
expect(subjectResult).toBe('Hello Alice')
|
|
47
|
+
expect(htmlResult).toBe('<div>Welcome!</div>')
|
|
48
|
+
})
|
|
49
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import dot from 'dot'
|
|
2
|
+
import * as fs from 'fs/promises'
|
|
3
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import { loadTemplates } from '../src/templates/template-loader.js'
|
|
6
|
+
|
|
7
|
+
vi.mock('fs/promises')
|
|
8
|
+
|
|
9
|
+
describe('loadTemplates', () => {
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
const mockCompile = vi.spyOn(dot, 'compile')
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.resetAllMocks()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should compile inline template strings correctly', async () => {
|
|
18
|
+
const templates = {
|
|
19
|
+
subject: 'Hello {{=it.name}}',
|
|
20
|
+
html: '<div>{{=it.body}}</div>',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const compiled = await loadTemplates(templates)
|
|
24
|
+
|
|
25
|
+
expect(typeof compiled.subject).toBe('function')
|
|
26
|
+
expect(typeof compiled.html).toBe('function')
|
|
27
|
+
|
|
28
|
+
// test compiled output
|
|
29
|
+
const result = compiled.subject({ name: 'Haim' })
|
|
30
|
+
expect(result).toBe('Hello Haim')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should read from file path if input is a file', async () => {
|
|
34
|
+
const fakeFileContent = 'Welcome {{=it.username}}'
|
|
35
|
+
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
fs.lstat.mockResolvedValueOnce({
|
|
38
|
+
isFile: () => true,
|
|
39
|
+
})
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
fs.readFile.mockResolvedValueOnce(fakeFileContent)
|
|
42
|
+
|
|
43
|
+
const compiled = await loadTemplates({
|
|
44
|
+
subject: '/path/to/template.dot',
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
expect(fs.readFile).toHaveBeenCalledWith('/path/to/template.dot', 'utf-8')
|
|
48
|
+
expect(typeof compiled.subject).toBe('function')
|
|
49
|
+
expect(compiled.subject({ username: 'DotUser' })).toBe('Welcome DotUser')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should treat string as content if path is invalid', async () => {
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
fs.lstat.mockRejectedValueOnce(new Error('File not found'))
|
|
55
|
+
|
|
56
|
+
const compiled = await loadTemplates({
|
|
57
|
+
subject: 'Hi {{=it.x}}',
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
expect(typeof compiled.subject).toBe('function')
|
|
61
|
+
expect(compiled.subject({ x: 'there' })).toBe('Hi there')
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import aws from 'aws-sdk'
|
|
2
|
+
import nodemailer from 'nodemailer'
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
4
|
+
import sgTransport from 'nodemailer-sendgrid-transport'
|
|
5
|
+
|
|
6
|
+
vi.mock('nodemailer', () => {
|
|
7
|
+
return {
|
|
8
|
+
default: {
|
|
9
|
+
createTransport: vi.fn(() => ({ type: 'mock-transport' })),
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
vi.mock('aws-sdk', async () => {
|
|
15
|
+
const actual = await vi.importActual('aws-sdk')
|
|
16
|
+
return {
|
|
17
|
+
...actual,
|
|
18
|
+
SES: vi.fn().mockImplementation(() => 'mocked-ses'),
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
vi.mock('nodemailer-sendgrid-transport', () => ({
|
|
23
|
+
__esModule: true,
|
|
24
|
+
default: vi.fn().mockImplementation((opts) => {
|
|
25
|
+
const transport = { sendgrid: true, options: opts }
|
|
26
|
+
return transport
|
|
27
|
+
}),
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
import { TransportFactory } from '../src/mailer/transport.factory.js'
|
|
31
|
+
describe('TransportFactory', () => {
|
|
32
|
+
it('should create smtp transport', () => {
|
|
33
|
+
const config = {
|
|
34
|
+
type: 'smtp',
|
|
35
|
+
host: 'smtp.example.com',
|
|
36
|
+
port: 587,
|
|
37
|
+
secure: false,
|
|
38
|
+
auth: { user: 'user', pass: 'pass' },
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const transport = TransportFactory.create(config)
|
|
42
|
+
expect(nodemailer.createTransport).toHaveBeenCalledWith({
|
|
43
|
+
host: config.host,
|
|
44
|
+
port: config.port,
|
|
45
|
+
secure: config.secure,
|
|
46
|
+
auth: config.auth,
|
|
47
|
+
})
|
|
48
|
+
expect(transport.type).toBe('mock-transport')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should create gmail transport', () => {
|
|
52
|
+
const config = {
|
|
53
|
+
type: 'gmail',
|
|
54
|
+
auth: { user: 'user@gmail.com', pass: 'pass' },
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const transport = TransportFactory.create(config)
|
|
58
|
+
expect(nodemailer.createTransport).toHaveBeenCalledWith({
|
|
59
|
+
service: 'gmail',
|
|
60
|
+
auth: config.auth,
|
|
61
|
+
})
|
|
62
|
+
expect(transport.type).toBe('mock-transport')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should create sendgrid transport', () => {
|
|
66
|
+
const config = {
|
|
67
|
+
type: 'sendgrid',
|
|
68
|
+
apiKey: 'SG.xxxx',
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const transport = TransportFactory.create(config)
|
|
72
|
+
expect(transport.sendgrid).toBe(true)
|
|
73
|
+
expect(transport.options.auth.api_key).toBe(config.apiKey)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should create ses transport', () => {
|
|
77
|
+
const config = {
|
|
78
|
+
type: 'ses',
|
|
79
|
+
accessKeyId: 'AKIA...',
|
|
80
|
+
secretAccessKey: 'secret',
|
|
81
|
+
region: 'us-west-2',
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const transport = TransportFactory.create(config)
|
|
85
|
+
expect(nodemailer.createTransport).toHaveBeenCalled()
|
|
86
|
+
|
|
87
|
+
expect(nodemailer.createTransport.mock.calls[0][0]).toHaveProperty(
|
|
88
|
+
'SES.ses',
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should throw error for unsupported type', () => {
|
|
93
|
+
expect(() => {
|
|
94
|
+
TransportFactory.create({ type: 'unsupported' })
|
|
95
|
+
}).toThrow('Unsupported transport type: unsupported')
|
|
96
|
+
})
|
|
97
|
+
})
|