core-services-sdk 1.3.7 → 1.3.9
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 +6 -5
- package/src/fastify/error-codes.js +11 -0
- package/src/http/HttpError.js +84 -10
- package/src/http/http.js +41 -31
- package/src/http/index.js +4 -0
- package/src/http/responseType.js +10 -0
- package/src/ids/index.js +2 -0
- package/src/index.js +7 -21
- package/src/mailer/transport.factory.js +28 -14
- package/src/mongodb/initialize-mongodb.js +9 -7
- package/src/rabbit-mq/index.js +1 -186
- package/src/rabbit-mq/rabbit-mq.js +189 -0
- package/src/templates/index.js +1 -0
- package/tests/fastify/error-handler.unit.test.js +39 -0
- package/tests/{with-error-handling.test.js → fastify/error-handlers/with-error-handling.test.js} +4 -3
- package/tests/http/HttpError.unit.test.js +112 -0
- package/tests/http/http-method.unit.test.js +29 -0
- package/tests/http/http.unit.test.js +167 -0
- package/tests/http/responseType.unit.test.js +45 -0
- package/tests/ids/prefixes.unit.test.js +1 -0
- package/tests/mailer/mailer.integration.test.js +95 -0
- package/tests/{mailer.unit.test.js → mailer/mailer.unit.test.js} +7 -11
- package/tests/mailer/transport.factory.unit.test.js +204 -0
- package/tests/mongodb/connect.unit.test.js +60 -0
- package/tests/mongodb/initialize-mongodb.unit.test.js +98 -0
- package/tests/mongodb/validate-mongo-uri.unit.test.js +52 -0
- package/tests/{rabbit-mq.test.js → rabbit-mq/rabbit-mq.test.js} +3 -2
- package/tests/{template-loader.integration.test.js → templates/template-loader.integration.test.js} +1 -1
- package/tests/{template-loader.unit.test.js → templates/template-loader.unit.test.js} +1 -1
- package/vitest.config.js +3 -0
- package/index.js +0 -3
- package/tests/HttpError.test.js +0 -80
- package/tests/core-util.js +0 -24
- package/tests/mailer.integration.test.js +0 -46
- package/tests/mongodb.test.js +0 -70
- package/tests/transport.factory.unit.test.js +0 -128
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { vi, it, expect, describe, afterEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import fetch from 'node-fetch'
|
|
4
|
+
import { http } from '../../src/http/http.js'
|
|
5
|
+
import { HttpError } from '../../src/http/HttpError.js'
|
|
6
|
+
import { HTTP_METHODS } from '../../src/http/http-method.js'
|
|
7
|
+
import { ResponseType } from '../../src/http/responseType.js'
|
|
8
|
+
|
|
9
|
+
vi.mock('node-fetch', async () => {
|
|
10
|
+
const actual = await vi.importActual('node-fetch')
|
|
11
|
+
return {
|
|
12
|
+
...actual,
|
|
13
|
+
default: vi.fn(),
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const mockFetch = /** @type {typeof fetch} */ (fetch)
|
|
18
|
+
|
|
19
|
+
const createMockResponse = ({
|
|
20
|
+
body = '',
|
|
21
|
+
status = 200,
|
|
22
|
+
statusText = 'OK',
|
|
23
|
+
headers = {},
|
|
24
|
+
} = {}) => {
|
|
25
|
+
return {
|
|
26
|
+
ok: status >= 200 && status < 300,
|
|
27
|
+
status,
|
|
28
|
+
statusText,
|
|
29
|
+
text: vi.fn().mockResolvedValue(body),
|
|
30
|
+
headers: { get: vi.fn().mockImplementation((k) => headers[k]) },
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('http client', () => {
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
vi.clearAllMocks()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('GET', () => {
|
|
40
|
+
it('should return parsed JSON response', async () => {
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
mockFetch.mockResolvedValueOnce(
|
|
43
|
+
createMockResponse({ body: JSON.stringify({ hello: 'world' }) }),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
const result = await http.get({ url: 'http://test.com' })
|
|
47
|
+
expect(result).toEqual({ hello: 'world' })
|
|
48
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
49
|
+
'http://test.com',
|
|
50
|
+
expect.objectContaining({ method: HTTP_METHODS.GET }),
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('should throw HttpError on non-2xx status', async () => {
|
|
55
|
+
// @ts-ignore
|
|
56
|
+
mockFetch.mockResolvedValueOnce(
|
|
57
|
+
createMockResponse({
|
|
58
|
+
status: 404,
|
|
59
|
+
statusText: 'Not Found',
|
|
60
|
+
body: JSON.stringify({ error: 'not found' }),
|
|
61
|
+
}),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
await expect(http.get({ url: 'http://fail.com' })).rejects.toThrow(
|
|
65
|
+
HttpError,
|
|
66
|
+
)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('POST', () => {
|
|
71
|
+
it('should send a JSON body and return parsed JSON', async () => {
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
mockFetch.mockResolvedValueOnce(
|
|
74
|
+
createMockResponse({ body: JSON.stringify({ ok: true }) }),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const result = await http.post({
|
|
78
|
+
url: 'http://test.com',
|
|
79
|
+
body: { test: 123 },
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
expect(result).toEqual({ ok: true })
|
|
83
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
84
|
+
'http://test.com',
|
|
85
|
+
expect.objectContaining({
|
|
86
|
+
method: HTTP_METHODS.POST,
|
|
87
|
+
body: JSON.stringify({ test: 123 }),
|
|
88
|
+
}),
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
describe('PUT', () => {
|
|
94
|
+
it('should send a PUT request and return JSON', async () => {
|
|
95
|
+
// @ts-ignore
|
|
96
|
+
mockFetch.mockResolvedValueOnce(
|
|
97
|
+
createMockResponse({ body: JSON.stringify({ updated: true }) }),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const result = await http.put({
|
|
101
|
+
url: 'http://test.com',
|
|
102
|
+
body: { name: 'Updated' },
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
expect(result).toEqual({ updated: true })
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('PATCH', () => {
|
|
110
|
+
it('should send a PATCH request and return JSON', async () => {
|
|
111
|
+
// @ts-ignore
|
|
112
|
+
mockFetch.mockResolvedValueOnce(
|
|
113
|
+
createMockResponse({ body: JSON.stringify({ patched: true }) }),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
const result = await http.patch({
|
|
117
|
+
url: 'http://test.com',
|
|
118
|
+
body: { field: 'value' },
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
expect(result).toEqual({ patched: true })
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('DELETE', () => {
|
|
126
|
+
it('should send a DELETE request with body and return JSON', async () => {
|
|
127
|
+
// @ts-ignore
|
|
128
|
+
mockFetch.mockResolvedValueOnce(
|
|
129
|
+
createMockResponse({ body: JSON.stringify({ deleted: true }) }),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
const result = await http.deleteApi({
|
|
133
|
+
url: 'http://test.com',
|
|
134
|
+
body: { id: 1 },
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
expect(result).toEqual({ deleted: true })
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
describe('ResponseType', () => {
|
|
142
|
+
it('should return text if expectedType is text', async () => {
|
|
143
|
+
// @ts-ignore
|
|
144
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ body: 'hello' }))
|
|
145
|
+
|
|
146
|
+
const result = await http.get({
|
|
147
|
+
url: 'http://test.com',
|
|
148
|
+
expectedType: ResponseType.text,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
expect(result).toBe('hello')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('should return XML as parsed object if expectedType is xml', async () => {
|
|
155
|
+
const xml = `<note><to>User</to><from>ChatGPT</from></note>`
|
|
156
|
+
// @ts-ignore
|
|
157
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ body: xml }))
|
|
158
|
+
|
|
159
|
+
const result = await http.get({
|
|
160
|
+
url: 'http://test.com',
|
|
161
|
+
expectedType: ResponseType.xml,
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
expect(result).toHaveProperty('note')
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { describe, it, expect } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { ResponseType } from '../../src/http/responseType.js'
|
|
5
|
+
|
|
6
|
+
describe('ResponseType', () => {
|
|
7
|
+
it('should contain correct response type mappings', () => {
|
|
8
|
+
expect(ResponseType).toEqual({
|
|
9
|
+
xml: 'xml',
|
|
10
|
+
json: 'json',
|
|
11
|
+
text: 'text',
|
|
12
|
+
file: 'file',
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should be frozen (immutable)', () => {
|
|
17
|
+
expect(Object.isFrozen(ResponseType)).toBe(true)
|
|
18
|
+
|
|
19
|
+
// Try to add new property
|
|
20
|
+
expect(() => {
|
|
21
|
+
ResponseType.newType = 'something'
|
|
22
|
+
}).toThrow(TypeError)
|
|
23
|
+
|
|
24
|
+
// Try to overwrite existing property
|
|
25
|
+
expect(() => {
|
|
26
|
+
ResponseType.json = 'override'
|
|
27
|
+
}).toThrow(TypeError)
|
|
28
|
+
|
|
29
|
+
// Ensure original value remains unchanged
|
|
30
|
+
expect(ResponseType.json).toBe('json')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should include expected keys', () => {
|
|
34
|
+
const keys = Object.keys(ResponseType)
|
|
35
|
+
expect(keys).toEqual(['xml', 'json', 'text', 'file'])
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should include expected values', () => {
|
|
39
|
+
const values = Object.values(ResponseType)
|
|
40
|
+
expect(values).toContain('json')
|
|
41
|
+
expect(values).toContain('xml')
|
|
42
|
+
expect(values).toContain('text')
|
|
43
|
+
expect(values).toContain('file')
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import nodemailer from 'nodemailer'
|
|
2
|
+
import { describe, it, expect, beforeAll } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { Mailer } from '../../src/mailer/mailer.service.js'
|
|
5
|
+
|
|
6
|
+
describe('Mailer (integration)', () => {
|
|
7
|
+
let transporter
|
|
8
|
+
let mailer
|
|
9
|
+
let testAccount
|
|
10
|
+
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
testAccount = await nodemailer.createTestAccount()
|
|
13
|
+
|
|
14
|
+
transporter = nodemailer.createTransport({
|
|
15
|
+
host: testAccount.smtp.host,
|
|
16
|
+
port: testAccount.smtp.port,
|
|
17
|
+
secure: testAccount.smtp.secure,
|
|
18
|
+
auth: {
|
|
19
|
+
user: testAccount.user,
|
|
20
|
+
pass: testAccount.pass,
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
mailer = new Mailer(transporter)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should send email with HTML + text', async () => {
|
|
28
|
+
const result = await mailer.send({
|
|
29
|
+
to: 'recipient@example.com',
|
|
30
|
+
from: '"My App" <no-reply@example.com>',
|
|
31
|
+
subject: 'Integration Test: HTML + text',
|
|
32
|
+
text: 'Hello from plain text',
|
|
33
|
+
html: '<strong>Hello HTML</strong>',
|
|
34
|
+
cc: '',
|
|
35
|
+
bcc: '',
|
|
36
|
+
replyTo: '',
|
|
37
|
+
attachments: '',
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
expect(result.messageId).toBeDefined()
|
|
41
|
+
expect(result.envelope).toBeDefined()
|
|
42
|
+
expect(result.accepted).toContain('recipient@example.com')
|
|
43
|
+
|
|
44
|
+
const previewUrl = nodemailer.getTestMessageUrl(result)
|
|
45
|
+
console.log(`📨 Preview (HTML+text): ${previewUrl}`)
|
|
46
|
+
expect(previewUrl).toMatch(/^https:\/\/ethereal\.email/)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should send HTML-only email', async () => {
|
|
50
|
+
const result = await mailer.send({
|
|
51
|
+
to: 'recipient2@example.com',
|
|
52
|
+
from: '"My App" <no-reply@example.com>',
|
|
53
|
+
subject: 'Integration Test: HTML only',
|
|
54
|
+
html: '<h2>Only HTML</h2>',
|
|
55
|
+
cc: '',
|
|
56
|
+
bcc: '',
|
|
57
|
+
text: '',
|
|
58
|
+
replyTo: '',
|
|
59
|
+
attachments: '',
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
expect(result.messageId).toBeDefined()
|
|
63
|
+
expect(result.accepted).toContain('recipient2@example.com')
|
|
64
|
+
|
|
65
|
+
const previewUrl = nodemailer.getTestMessageUrl(result)
|
|
66
|
+
console.log(`📨 Preview (HTML only): ${previewUrl}`)
|
|
67
|
+
expect(previewUrl).toMatch(/^https:\/\/ethereal\.email/)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should send email with attachment', async () => {
|
|
71
|
+
const result = await mailer.send({
|
|
72
|
+
to: 'recipient3@example.com',
|
|
73
|
+
from: '"My App" <no-reply@example.com>',
|
|
74
|
+
subject: 'Integration Test: With Attachment',
|
|
75
|
+
text: 'See attached file.',
|
|
76
|
+
html: '<p>See attached file.</p>',
|
|
77
|
+
attachments: [
|
|
78
|
+
{
|
|
79
|
+
filename: 'test.txt',
|
|
80
|
+
content: 'This is the content of the attachment.',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
cc: '',
|
|
84
|
+
bcc: '',
|
|
85
|
+
replyTo: '',
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
expect(result.messageId).toBeDefined()
|
|
89
|
+
expect(result.accepted).toContain('recipient3@example.com')
|
|
90
|
+
|
|
91
|
+
const previewUrl = nodemailer.getTestMessageUrl(result)
|
|
92
|
+
console.log(`📨 Preview (With Attachment): ${previewUrl}`)
|
|
93
|
+
expect(previewUrl).toMatch(/^https:\/\/ethereal\.email/)
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -4,9 +4,9 @@ const mockTransport = {
|
|
|
4
4
|
sendMail: vi.fn(),
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
vi.mock('
|
|
9
|
-
const actual = await vi.importActual('
|
|
7
|
+
// Mock the TransportFactory with a spy
|
|
8
|
+
vi.mock('../../src/mailer/transport.factory.js', async () => {
|
|
9
|
+
const actual = await vi.importActual('../../src/mailer/transport.factory.js') // fixed path
|
|
10
10
|
return {
|
|
11
11
|
...actual,
|
|
12
12
|
TransportFactory: {
|
|
@@ -16,17 +16,16 @@ vi.mock('../src/mailer/transport.factory.js', async () => {
|
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
// Mock Mailer class
|
|
19
|
-
vi.mock('
|
|
19
|
+
vi.mock('../../src/mailer/mailer.service.js', async () => {
|
|
20
20
|
const Mailer = vi.fn().mockImplementation((transport) => ({
|
|
21
21
|
send: vi.fn((opts) => transport.sendMail(opts)),
|
|
22
22
|
}))
|
|
23
23
|
return { Mailer }
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
import {
|
|
28
|
-
import { TransportFactory } from '
|
|
29
|
-
import { Mailer } from '../src/mailer/mailer.service.js'
|
|
26
|
+
import { initMailer } from '../../src/mailer/index.js'
|
|
27
|
+
import { Mailer } from '../../src/mailer/mailer.service.js'
|
|
28
|
+
import { TransportFactory } from '../../src/mailer/transport.factory.js'
|
|
30
29
|
|
|
31
30
|
describe('initMailer', () => {
|
|
32
31
|
beforeEach(() => {
|
|
@@ -44,10 +43,7 @@ describe('initMailer', () => {
|
|
|
44
43
|
|
|
45
44
|
const mailer = initMailer(config)
|
|
46
45
|
|
|
47
|
-
// Ensure TransportFactory.create was called
|
|
48
46
|
expect(TransportFactory.create).toHaveBeenCalledWith(config)
|
|
49
|
-
|
|
50
|
-
// Ensure Mailer was created with the mock transport
|
|
51
47
|
expect(Mailer).toHaveBeenCalledWith(mockTransport)
|
|
52
48
|
|
|
53
49
|
const emailOptions = {
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import nodemailer from 'nodemailer'
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
3
|
+
|
|
4
|
+
// ===================
|
|
5
|
+
// SAFELY MOCK MODULES
|
|
6
|
+
// ===================
|
|
7
|
+
|
|
8
|
+
vi.mock('nodemailer', () => {
|
|
9
|
+
const createTransport = vi.fn((options) => ({
|
|
10
|
+
type: 'mock-transport',
|
|
11
|
+
options,
|
|
12
|
+
}))
|
|
13
|
+
return {
|
|
14
|
+
__esModule: true,
|
|
15
|
+
default: { createTransport },
|
|
16
|
+
createTransport,
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
vi.mock('nodemailer-sendgrid-transport', () => ({
|
|
21
|
+
__esModule: true,
|
|
22
|
+
default: vi.fn((opts) => ({
|
|
23
|
+
sendgrid: true,
|
|
24
|
+
options: opts,
|
|
25
|
+
})),
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
vi.mock('@aws-sdk/client-ses', () => {
|
|
29
|
+
const mockSesInstance = { mocked: true } // consistent reference
|
|
30
|
+
const sesClient = vi.fn(() => mockSesInstance)
|
|
31
|
+
|
|
32
|
+
globalThis.__mockedSesClient__ = sesClient
|
|
33
|
+
globalThis.__mockedSesInstance__ = mockSesInstance
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
SESClient: sesClient,
|
|
37
|
+
SendRawEmailCommand: 'mocked-command',
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
vi.mock('@aws-sdk/credential-provider-node', () => {
|
|
42
|
+
const defaultProvider = vi.fn(() => 'mocked-default-provider')
|
|
43
|
+
globalThis.__mockedDefaultProvider__ = defaultProvider
|
|
44
|
+
return {
|
|
45
|
+
defaultProvider,
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// ==========================
|
|
50
|
+
// NOW IMPORT MODULE UNDER TEST
|
|
51
|
+
// ==========================
|
|
52
|
+
|
|
53
|
+
import { TransportFactory } from '../../src/mailer/transport.factory.js'
|
|
54
|
+
|
|
55
|
+
describe('TransportFactory', () => {
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
vi.clearAllMocks()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should create smtp transport', () => {
|
|
61
|
+
const config = {
|
|
62
|
+
type: 'smtp',
|
|
63
|
+
host: 'smtp.example.com',
|
|
64
|
+
port: 587,
|
|
65
|
+
secure: false,
|
|
66
|
+
auth: { user: 'user', pass: 'pass' },
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
const transport = TransportFactory.create(config)
|
|
71
|
+
|
|
72
|
+
expect(nodemailer.createTransport).toHaveBeenCalledWith({
|
|
73
|
+
host: config.host,
|
|
74
|
+
port: config.port,
|
|
75
|
+
secure: config.secure,
|
|
76
|
+
auth: config.auth,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// @ts-ignore
|
|
80
|
+
expect(transport.type).toBe('mock-transport')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should create gmail transport', () => {
|
|
84
|
+
const config = {
|
|
85
|
+
type: 'gmail',
|
|
86
|
+
auth: { user: 'user@gmail.com', pass: 'pass' },
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// @ts-ignore
|
|
90
|
+
const transport = TransportFactory.create(config)
|
|
91
|
+
|
|
92
|
+
expect(nodemailer.createTransport).toHaveBeenCalledWith({
|
|
93
|
+
service: 'gmail',
|
|
94
|
+
auth: config.auth,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// @ts-ignore
|
|
98
|
+
expect(transport.type).toBe('mock-transport')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should create sendgrid transport', () => {
|
|
102
|
+
const config = {
|
|
103
|
+
type: 'sendgrid',
|
|
104
|
+
apiKey: 'SG.xxxx',
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// @ts-ignore
|
|
108
|
+
const transport = TransportFactory.create(config)
|
|
109
|
+
|
|
110
|
+
// @ts-ignore
|
|
111
|
+
const args = nodemailer.createTransport.mock.calls[0][0]
|
|
112
|
+
|
|
113
|
+
expect(args).toEqual({
|
|
114
|
+
sendgrid: true,
|
|
115
|
+
options: {
|
|
116
|
+
auth: { api_key: config.apiKey },
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
expect(transport).toEqual({
|
|
121
|
+
type: 'mock-transport',
|
|
122
|
+
options: {
|
|
123
|
+
sendgrid: true,
|
|
124
|
+
options: {
|
|
125
|
+
auth: { api_key: config.apiKey },
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('should create ses transport with credentials', () => {
|
|
132
|
+
const config = {
|
|
133
|
+
type: 'ses',
|
|
134
|
+
accessKeyId: 'AKIA...',
|
|
135
|
+
secretAccessKey: 'secret',
|
|
136
|
+
region: 'us-west-2',
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// @ts-ignore
|
|
140
|
+
const transport = TransportFactory.create(config)
|
|
141
|
+
|
|
142
|
+
expect(globalThis.__mockedSesClient__).toHaveBeenCalledWith({
|
|
143
|
+
region: config.region,
|
|
144
|
+
credentials: {
|
|
145
|
+
accessKeyId: config.accessKeyId,
|
|
146
|
+
secretAccessKey: config.secretAccessKey,
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// @ts-ignore
|
|
151
|
+
const args = nodemailer.createTransport.mock.calls[0][0]
|
|
152
|
+
|
|
153
|
+
expect(args).toEqual({
|
|
154
|
+
SES: {
|
|
155
|
+
ses: globalThis.__mockedSesInstance__,
|
|
156
|
+
aws: {
|
|
157
|
+
SendRawEmailCommand: 'mocked-command',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// @ts-ignore
|
|
163
|
+
expect(transport.type).toBe('mock-transport')
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('should create ses transport with defaultProvider fallback', () => {
|
|
167
|
+
const config = {
|
|
168
|
+
type: 'ses',
|
|
169
|
+
region: 'us-east-1',
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// @ts-ignore
|
|
173
|
+
const transport = TransportFactory.create(config)
|
|
174
|
+
|
|
175
|
+
expect(globalThis.__mockedDefaultProvider__).toHaveBeenCalled()
|
|
176
|
+
|
|
177
|
+
expect(globalThis.__mockedSesClient__).toHaveBeenCalledWith({
|
|
178
|
+
region: config.region,
|
|
179
|
+
credentials: 'mocked-default-provider',
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// @ts-ignore
|
|
183
|
+
const args = nodemailer.createTransport.mock.calls[0][0]
|
|
184
|
+
|
|
185
|
+
expect(args).toEqual({
|
|
186
|
+
SES: {
|
|
187
|
+
ses: globalThis.__mockedSesInstance__,
|
|
188
|
+
aws: {
|
|
189
|
+
SendRawEmailCommand: 'mocked-command',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// @ts-ignore
|
|
195
|
+
expect(transport.type).toBe('mock-transport')
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('should throw error for unsupported type', () => {
|
|
199
|
+
expect(() => {
|
|
200
|
+
// @ts-ignore
|
|
201
|
+
TransportFactory.create({ type: 'invalid' })
|
|
202
|
+
}).toThrow('Unsupported transport type: invalid')
|
|
203
|
+
})
|
|
204
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { MongoClient, ServerApiVersion } from 'mongodb'
|
|
3
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import { mongoConnect } from '../../src/mongodb/connect.js'
|
|
6
|
+
|
|
7
|
+
vi.mock('mongodb', async () => {
|
|
8
|
+
const actual = await vi.importActual('mongodb')
|
|
9
|
+
return {
|
|
10
|
+
...actual,
|
|
11
|
+
MongoClient: {
|
|
12
|
+
connect: vi.fn(),
|
|
13
|
+
},
|
|
14
|
+
ServerApiVersion: actual.ServerApiVersion, // use real enum
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('mongoConnect', () => {
|
|
19
|
+
const fakeClient = { db: vi.fn() }
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.clearAllMocks()
|
|
23
|
+
MongoClient.connect.mockResolvedValue(fakeClient)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should call MongoClient.connect with default serverApi options', async () => {
|
|
27
|
+
const uri = 'mongodb://localhost:27017'
|
|
28
|
+
await mongoConnect({ uri })
|
|
29
|
+
|
|
30
|
+
expect(MongoClient.connect).toHaveBeenCalledWith(uri, {
|
|
31
|
+
serverApi: {
|
|
32
|
+
version: ServerApiVersion.v1,
|
|
33
|
+
strict: true,
|
|
34
|
+
deprecationErrors: true,
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should merge custom serverApi options', async () => {
|
|
40
|
+
const uri = 'mongodb://localhost:27017'
|
|
41
|
+
const serverApi = { strict: false }
|
|
42
|
+
|
|
43
|
+
await mongoConnect({ uri, serverApi })
|
|
44
|
+
|
|
45
|
+
expect(MongoClient.connect).toHaveBeenCalledWith(uri, {
|
|
46
|
+
serverApi: {
|
|
47
|
+
version: ServerApiVersion.v1,
|
|
48
|
+
strict: false,
|
|
49
|
+
deprecationErrors: true,
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('should return the connected client', async () => {
|
|
55
|
+
const uri = 'mongodb://localhost:27017'
|
|
56
|
+
const client = await mongoConnect({ uri })
|
|
57
|
+
|
|
58
|
+
expect(client).toBe(fakeClient)
|
|
59
|
+
})
|
|
60
|
+
})
|