core-services-sdk 1.3.86 → 1.3.87
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 +1 -1
- package/src/http/http.js +67 -11
- package/src/postgresql/start-stop-postgres-docker.js +10 -0
- package/src/rabbit-mq/start-stop-rabbitmq.js +13 -0
- package/tests/http/http.int.test.js +141 -0
- package/tests/rabbit-mq/rabbit-mq.test.js +1 -1
- package/tests/repositories/BaseRepository.int.test.js +2 -2
- package/tests/repositories/TenantScopedRepository.int.test.js +2 -2
- package/types/http/http.d.ts +4 -3
- package/vitest.config.js +1 -1
package/package.json
CHANGED
package/src/http/http.js
CHANGED
|
@@ -183,6 +183,45 @@ const getResponsePayload = async (response, responseType) => {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Normalizes the request body before sending it via fetch.
|
|
188
|
+
*
|
|
189
|
+
* Ensures backward compatibility while allowing additional body types.
|
|
190
|
+
* - undefined/null → no body sent
|
|
191
|
+
* - string → sent as-is
|
|
192
|
+
* - URLSearchParams → sent as-is (fetch serializes automatically)
|
|
193
|
+
* - FormData → sent as-is (fetch sets multipart boundary automatically)
|
|
194
|
+
* - ArrayBuffer / Blob → sent as-is
|
|
195
|
+
* - object / array → JSON.stringify applied and Content-Type ensured
|
|
196
|
+
*
|
|
197
|
+
* @param {any} body
|
|
198
|
+
* @param {Record<string,string>} headers
|
|
199
|
+
* @returns {{ body:any, headers:Record<string,string> }}
|
|
200
|
+
*/
|
|
201
|
+
const prepareRequestBody = (body, headers = {}) => {
|
|
202
|
+
if (body === undefined || body === null) {
|
|
203
|
+
return { body: undefined, headers }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (
|
|
207
|
+
typeof body === 'string' ||
|
|
208
|
+
body instanceof URLSearchParams ||
|
|
209
|
+
body instanceof FormData ||
|
|
210
|
+
body instanceof ArrayBuffer ||
|
|
211
|
+
body instanceof Blob
|
|
212
|
+
) {
|
|
213
|
+
return { body, headers }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
body: JSON.stringify(body),
|
|
218
|
+
headers: {
|
|
219
|
+
'Content-Type': 'application/json',
|
|
220
|
+
...headers,
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
186
225
|
/**
|
|
187
226
|
* Sends an HTTP GET request.
|
|
188
227
|
*
|
|
@@ -219,11 +258,15 @@ export const post = async ({
|
|
|
219
258
|
extraParams = {},
|
|
220
259
|
expectedType = ResponseType.json,
|
|
221
260
|
}) => {
|
|
261
|
+
const { body: preparedBody, headers: preparedHeaders } = prepareRequestBody(
|
|
262
|
+
body,
|
|
263
|
+
headers,
|
|
264
|
+
)
|
|
222
265
|
const response = await fetch(url, {
|
|
223
266
|
...extraParams,
|
|
224
267
|
method: HTTP_METHODS.POST,
|
|
225
|
-
headers: { ...JSON_HEADER, ...
|
|
226
|
-
body:
|
|
268
|
+
headers: { ...JSON_HEADER, ...preparedHeaders },
|
|
269
|
+
body: preparedBody,
|
|
227
270
|
})
|
|
228
271
|
await checkStatus(response)
|
|
229
272
|
return getResponsePayload(response, expectedType)
|
|
@@ -243,11 +286,15 @@ export const put = async ({
|
|
|
243
286
|
extraParams = {},
|
|
244
287
|
expectedType = ResponseType.json,
|
|
245
288
|
}) => {
|
|
289
|
+
const { body: preparedBody, headers: preparedHeaders } = prepareRequestBody(
|
|
290
|
+
body,
|
|
291
|
+
headers,
|
|
292
|
+
)
|
|
246
293
|
const response = await fetch(url, {
|
|
247
294
|
...extraParams,
|
|
248
295
|
method: HTTP_METHODS.PUT,
|
|
249
|
-
headers: { ...JSON_HEADER, ...
|
|
250
|
-
body:
|
|
296
|
+
headers: { ...JSON_HEADER, ...preparedHeaders },
|
|
297
|
+
body: preparedBody,
|
|
251
298
|
})
|
|
252
299
|
await checkStatus(response)
|
|
253
300
|
return getResponsePayload(response, expectedType)
|
|
@@ -267,11 +314,15 @@ export const patch = async ({
|
|
|
267
314
|
extraParams = {},
|
|
268
315
|
expectedType = ResponseType.json,
|
|
269
316
|
}) => {
|
|
317
|
+
const { body: preparedBody, headers: preparedHeaders } = prepareRequestBody(
|
|
318
|
+
body,
|
|
319
|
+
headers,
|
|
320
|
+
)
|
|
270
321
|
const response = await fetch(url, {
|
|
271
322
|
...extraParams,
|
|
272
323
|
method: HTTP_METHODS.PATCH,
|
|
273
|
-
headers: { ...JSON_HEADER, ...
|
|
274
|
-
body:
|
|
324
|
+
headers: { ...JSON_HEADER, ...preparedHeaders },
|
|
325
|
+
body: preparedBody,
|
|
275
326
|
})
|
|
276
327
|
await checkStatus(response)
|
|
277
328
|
return getResponsePayload(response, expectedType)
|
|
@@ -291,11 +342,15 @@ export const deleteApi = async ({
|
|
|
291
342
|
extraParams = {},
|
|
292
343
|
expectedType = ResponseType.json,
|
|
293
344
|
}) => {
|
|
345
|
+
const { body: preparedBody, headers: preparedHeaders } = prepareRequestBody(
|
|
346
|
+
body,
|
|
347
|
+
headers,
|
|
348
|
+
)
|
|
294
349
|
const response = await fetch(url, {
|
|
295
350
|
...extraParams,
|
|
296
351
|
method: HTTP_METHODS.DELETE,
|
|
297
|
-
headers: { ...JSON_HEADER, ...
|
|
298
|
-
...(
|
|
352
|
+
headers: { ...JSON_HEADER, ...preparedHeaders },
|
|
353
|
+
...(preparedBody ? { body: preparedBody } : {}),
|
|
299
354
|
})
|
|
300
355
|
await checkStatus(response)
|
|
301
356
|
return getResponsePayload(response, expectedType)
|
|
@@ -328,8 +383,9 @@ export const head = async ({ url, headers = {}, extraParams = {} }) => {
|
|
|
328
383
|
* put: (options: HttpPutOptions) => Promise<any>,
|
|
329
384
|
* post: (options: HttpPostOptions) => Promise<any>,
|
|
330
385
|
* patch: (options: HttpPatchOptions) => Promise<any>,
|
|
331
|
-
*
|
|
332
|
-
*
|
|
386
|
+
* head: (options: HttpHeadOptions) => Promise<Response>,
|
|
387
|
+
* deleteApi: (options: HttpDeleteOptions) => Promise<any>
|
|
388
|
+
*
|
|
333
389
|
* }}
|
|
334
390
|
*/
|
|
335
391
|
|
|
@@ -337,7 +393,7 @@ export const http = {
|
|
|
337
393
|
get,
|
|
338
394
|
put,
|
|
339
395
|
post,
|
|
396
|
+
head,
|
|
340
397
|
patch,
|
|
341
398
|
deleteApi,
|
|
342
|
-
head,
|
|
343
399
|
}
|
|
@@ -39,6 +39,16 @@ export function startPostgres({
|
|
|
39
39
|
|
|
40
40
|
stopPostgres(containerName)
|
|
41
41
|
|
|
42
|
+
// Kill any container that might still be holding the port
|
|
43
|
+
try {
|
|
44
|
+
const containerId = execSync(`docker ps -q --filter "publish=${port}"`, {
|
|
45
|
+
encoding: 'utf8',
|
|
46
|
+
}).trim()
|
|
47
|
+
if (containerId) {
|
|
48
|
+
execSync(`docker rm -f ${containerId}`, { stdio: 'ignore' })
|
|
49
|
+
}
|
|
50
|
+
} catch {}
|
|
51
|
+
|
|
42
52
|
execSync(
|
|
43
53
|
`docker run -d \
|
|
44
54
|
--name ${containerName} \
|
|
@@ -35,6 +35,18 @@ export function startRabbit({ containerName, ...rest }) {
|
|
|
35
35
|
execSync(`docker rm -f ${containerName}`, { stdio: 'ignore' })
|
|
36
36
|
} catch {}
|
|
37
37
|
|
|
38
|
+
// Kill any containers that might still be holding the ports
|
|
39
|
+
for (const port of [rest.amqpPort, rest.uiPort]) {
|
|
40
|
+
try {
|
|
41
|
+
const id = execSync(`docker ps -q --filter "publish=${port}"`, {
|
|
42
|
+
encoding: 'utf8',
|
|
43
|
+
}).trim()
|
|
44
|
+
if (id) {
|
|
45
|
+
execSync(`docker rm -f ${id}`, { stdio: 'ignore' })
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
execSync(
|
|
39
51
|
`docker run -d \
|
|
40
52
|
--name ${containerName} \
|
|
@@ -42,6 +54,7 @@ export function startRabbit({ containerName, ...rest }) {
|
|
|
42
54
|
-e RABBITMQ_DEFAULT_PASS=${rest.pass} \
|
|
43
55
|
-p ${rest.amqpPort}:5672 \
|
|
44
56
|
-p ${rest.uiPort}:15672 \
|
|
57
|
+
--tmpfs /var/lib/rabbitmq \
|
|
45
58
|
--health-cmd="rabbitmq-diagnostics -q ping" \
|
|
46
59
|
--health-interval=5s \
|
|
47
60
|
--health-timeout=5s \
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
3
|
+
import httpServer from 'node:http'
|
|
4
|
+
import { http } from '../../src/http/http.js'
|
|
5
|
+
|
|
6
|
+
let server
|
|
7
|
+
let baseUrl
|
|
8
|
+
|
|
9
|
+
function collectBody(req) {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
const chunks = []
|
|
12
|
+
|
|
13
|
+
req.on('data', (chunk) => {
|
|
14
|
+
chunks.push(chunk)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
req.on('end', () => {
|
|
18
|
+
resolve(Buffer.concat(chunks).toString())
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
beforeAll(async () => {
|
|
24
|
+
server = httpServer.createServer(async (req, res) => {
|
|
25
|
+
const body = await collectBody(req)
|
|
26
|
+
|
|
27
|
+
res.setHeader('content-type', 'application/json')
|
|
28
|
+
|
|
29
|
+
res.end(
|
|
30
|
+
JSON.stringify({
|
|
31
|
+
method: req.method,
|
|
32
|
+
headers: req.headers,
|
|
33
|
+
body,
|
|
34
|
+
}),
|
|
35
|
+
)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
await new Promise((resolve) => {
|
|
39
|
+
server.listen(0, () => {
|
|
40
|
+
const { port } = server.address()
|
|
41
|
+
baseUrl = `http://localhost:${port}`
|
|
42
|
+
resolve()
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
afterAll(async () => {
|
|
48
|
+
await new Promise((resolve) => server.close(resolve))
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('http util integration', () => {
|
|
52
|
+
it('should send JSON body', async () => {
|
|
53
|
+
const result = await http.post({
|
|
54
|
+
url: `${baseUrl}/json`,
|
|
55
|
+
body: { hello: 'world' },
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
expect(result.method).toBe('POST')
|
|
59
|
+
expect(result.body).toBe(JSON.stringify({ hello: 'world' }))
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should send string body without stringify', async () => {
|
|
63
|
+
const result = await http.post({
|
|
64
|
+
url: `${baseUrl}/string`,
|
|
65
|
+
body: 'plain-text',
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
expect(result.body).toBe('plain-text')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should support base64 string', async () => {
|
|
72
|
+
const base64 = Buffer.from('hello').toString('base64')
|
|
73
|
+
|
|
74
|
+
const result = await http.post({
|
|
75
|
+
url: `${baseUrl}/base64`,
|
|
76
|
+
body: base64,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
expect(result.body).toBe(base64)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should support URLSearchParams', async () => {
|
|
83
|
+
const params = new URLSearchParams({
|
|
84
|
+
grant_type: 'client_credentials',
|
|
85
|
+
scope: 'read',
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const result = await http.post({
|
|
89
|
+
url: `${baseUrl}/form`,
|
|
90
|
+
body: params,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
expect(result.body).toBe(params.toString())
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('should support PUT with JSON', async () => {
|
|
97
|
+
const result = await http.put({
|
|
98
|
+
url: `${baseUrl}/put`,
|
|
99
|
+
body: { a: 1 },
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
expect(result.method).toBe('PUT')
|
|
103
|
+
expect(result.body).toBe(JSON.stringify({ a: 1 }))
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should support PATCH with JSON', async () => {
|
|
107
|
+
const result = await http.patch({
|
|
108
|
+
url: `${baseUrl}/patch`,
|
|
109
|
+
body: { b: 2 },
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
expect(result.method).toBe('PATCH')
|
|
113
|
+
expect(result.body).toBe(JSON.stringify({ b: 2 }))
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('should support DELETE with body', async () => {
|
|
117
|
+
const result = await http.deleteApi({
|
|
118
|
+
url: `${baseUrl}/delete`,
|
|
119
|
+
body: { remove: true },
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
expect(result.method).toBe('DELETE')
|
|
123
|
+
expect(result.body).toBe(JSON.stringify({ remove: true }))
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('should support GET request', async () => {
|
|
127
|
+
const result = await http.get({
|
|
128
|
+
url: `${baseUrl}/get`,
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
expect(result.method).toBe('GET')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should support HEAD request', async () => {
|
|
135
|
+
const response = await http.head({
|
|
136
|
+
url: `${baseUrl}/head`,
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
expect(response.status).toBe(200)
|
|
140
|
+
})
|
|
141
|
+
})
|
|
@@ -20,7 +20,7 @@ const log = pino({
|
|
|
20
20
|
level: 'silent',
|
|
21
21
|
})
|
|
22
22
|
// @ts-ignore
|
|
23
|
-
async function waitForRabbitConnection({ uri, log, timeoutMs =
|
|
23
|
+
async function waitForRabbitConnection({ uri, log, timeoutMs = 30000 }) {
|
|
24
24
|
const start = Date.now()
|
|
25
25
|
|
|
26
26
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
import { BaseRepository } from '../../src/postgresql/repositories/BaseRepository.js'
|
|
12
12
|
|
|
13
13
|
const PG_OPTIONS = {
|
|
14
|
-
port:
|
|
14
|
+
port: 5449,
|
|
15
15
|
db: 'testdb',
|
|
16
16
|
user: 'testuser',
|
|
17
17
|
pass: 'testpass',
|
|
18
|
-
containerName: 'postgres-base-repo-test-
|
|
18
|
+
containerName: 'postgres-base-repo-test-5449',
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const DATABASE_URI = buildPostgresUri(PG_OPTIONS)
|
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
import { TenantScopedRepository } from '../../src/postgresql/repositories/TenantScopedRepository.js'
|
|
12
12
|
|
|
13
13
|
const PG_OPTIONS = {
|
|
14
|
-
port:
|
|
14
|
+
port: 5448,
|
|
15
15
|
db: 'testdb',
|
|
16
16
|
user: 'testuser',
|
|
17
17
|
pass: 'testpass',
|
|
18
|
-
containerName: 'postgres-tenant-repo-test-
|
|
18
|
+
containerName: 'postgres-tenant-repo-test-5448',
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const DATABASE_URI = buildPostgresUri(PG_OPTIONS)
|
package/types/http/http.d.ts
CHANGED
|
@@ -45,8 +45,9 @@ export function head({
|
|
|
45
45
|
* put: (options: HttpPutOptions) => Promise<any>,
|
|
46
46
|
* post: (options: HttpPostOptions) => Promise<any>,
|
|
47
47
|
* patch: (options: HttpPatchOptions) => Promise<any>,
|
|
48
|
-
*
|
|
49
|
-
*
|
|
48
|
+
* head: (options: HttpHeadOptions) => Promise<Response>,
|
|
49
|
+
* deleteApi: (options: HttpDeleteOptions) => Promise<any>
|
|
50
|
+
*
|
|
50
51
|
* }}
|
|
51
52
|
*/
|
|
52
53
|
export const http: {
|
|
@@ -54,8 +55,8 @@ export const http: {
|
|
|
54
55
|
put: (options: HttpPutOptions) => Promise<any>
|
|
55
56
|
post: (options: HttpPostOptions) => Promise<any>
|
|
56
57
|
patch: (options: HttpPatchOptions) => Promise<any>
|
|
57
|
-
deleteApi: (options: HttpDeleteOptions) => Promise<any>
|
|
58
58
|
head: (options: HttpHeadOptions) => Promise<Response>
|
|
59
|
+
deleteApi: (options: HttpDeleteOptions) => Promise<any>
|
|
59
60
|
}
|
|
60
61
|
export type ResponseTypeValue = 'json' | 'xml' | 'text' | 'raw' | 'file'
|
|
61
62
|
export type HttpGetOptions = {
|