@pbvision/fastify-firestore-service 0.0.9 → 0.0.11
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 +4 -3
- package/src/api/api.js +17 -24
- package/src/fetch-wrapper.js +35 -0
- package/test/base-test.js +30 -16
- package/src/got-wrapper.js +0 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pbvision/fastify-firestore-service",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Web Framework using Fastify and Firestore ORM",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -46,8 +46,7 @@
|
|
|
46
46
|
"@sentry/node": "^7.91.0",
|
|
47
47
|
"fastify": "^4.10.0",
|
|
48
48
|
"fastify-plugin": "^4.5.1",
|
|
49
|
-
"
|
|
50
|
-
"pino": "8.17.2",
|
|
49
|
+
"node-fetch": "^3.3.2",
|
|
51
50
|
"word-wrap": "^1.2.3"
|
|
52
51
|
},
|
|
53
52
|
"devDependencies": {
|
|
@@ -61,6 +60,7 @@
|
|
|
61
60
|
"eslint-config-standard": "17.1.0",
|
|
62
61
|
"eslint-import-resolver-webpack": "^0.13.8",
|
|
63
62
|
"eslint-plugin-import": "^2.22.0",
|
|
63
|
+
"eslint-plugin-n": "^16.6.2",
|
|
64
64
|
"eslint-plugin-node": "^11.1.0",
|
|
65
65
|
"eslint-plugin-promise": "^6.0.0",
|
|
66
66
|
"eslint-plugin-standard": "^5.0.0",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"license-webpack-plugin": "^4.0.2",
|
|
70
70
|
"nodemon": "^3.0.2",
|
|
71
71
|
"standard": "^17.1.0",
|
|
72
|
+
"superagent": "^8",
|
|
72
73
|
"superagent-defaults": "^0.1.14",
|
|
73
74
|
"supertest": "^6.3.3",
|
|
74
75
|
"tui-jsdoc-template": "^1.2.2",
|
package/src/api/api.js
CHANGED
|
@@ -3,7 +3,7 @@ import querystring from 'node:querystring'
|
|
|
3
3
|
|
|
4
4
|
import S from '@pbvision/schema'
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import fetchWrapper from '../fetch-wrapper.js'
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
BadRequestException,
|
|
@@ -117,15 +117,6 @@ class API {
|
|
|
117
117
|
return undefined
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
/**
|
|
121
|
-
* Returns the full HTTP path to this API.
|
|
122
|
-
* @param {String} service The service this API belongs to.
|
|
123
|
-
* @public
|
|
124
|
-
*/
|
|
125
|
-
static getFullPath (service) {
|
|
126
|
-
return `/${service}${this.PATH}`
|
|
127
|
-
}
|
|
128
|
-
|
|
129
120
|
/* istanbul ignore next */
|
|
130
121
|
/**
|
|
131
122
|
* A human-readable description of what this API does. It is only used when
|
|
@@ -362,8 +353,7 @@ class API {
|
|
|
362
353
|
headers,
|
|
363
354
|
method,
|
|
364
355
|
url,
|
|
365
|
-
|
|
366
|
-
throwHttpErrors: false
|
|
356
|
+
qsParams
|
|
367
357
|
}
|
|
368
358
|
// istanbul ignore if
|
|
369
359
|
if (body) {
|
|
@@ -373,19 +363,22 @@ class API {
|
|
|
373
363
|
request.json = body
|
|
374
364
|
}
|
|
375
365
|
}
|
|
376
|
-
const resp =
|
|
377
|
-
const resolvedResp = await resp
|
|
366
|
+
const resp = await fetchWrapper(request)
|
|
378
367
|
const ret = {
|
|
379
|
-
code:
|
|
368
|
+
code: resp.status,
|
|
369
|
+
isOk: resp.status >= 200 && resp.status <= 299
|
|
380
370
|
}
|
|
381
|
-
ret.isOk = (ret.code === 200)
|
|
382
371
|
const respBody = await resp.text()
|
|
383
372
|
if (respBody) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
373
|
+
if (resp.headers.get('content-type')?.startsWith('application/json')) {
|
|
374
|
+
try {
|
|
375
|
+
ret.data = JSON.parse(respBody)
|
|
376
|
+
} catch (e) {
|
|
377
|
+
// istanbul ignore next
|
|
378
|
+
throw new Error(`JSON.parse failed on ${respBody} with reason ${e}.`)
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
ret.data = respBody
|
|
389
382
|
}
|
|
390
383
|
}
|
|
391
384
|
return ret
|
|
@@ -482,14 +475,14 @@ class API {
|
|
|
482
475
|
* @package
|
|
483
476
|
*/
|
|
484
477
|
static registerAPI (registrar) {
|
|
485
|
-
const { app
|
|
478
|
+
const { app } = registrar
|
|
486
479
|
if (this.setup) {
|
|
487
480
|
app.register(this.setup)
|
|
488
481
|
}
|
|
489
482
|
const cls = this
|
|
490
483
|
app.register(async (fastify) => {
|
|
491
484
|
try {
|
|
492
|
-
await cls.registerAPIWithFastify(fastify, cls.
|
|
485
|
+
await cls.registerAPIWithFastify(fastify, cls.PATH)
|
|
493
486
|
} catch (e) {
|
|
494
487
|
if (e instanceof assert.AssertionError) {
|
|
495
488
|
e.message = `${cls.name}: ${e.message}`
|
|
@@ -502,7 +495,7 @@ class API {
|
|
|
502
495
|
if (this.CORS_ORIGIN) {
|
|
503
496
|
// create an OPTIONS method API to support CORS preflight requests from
|
|
504
497
|
// browsers
|
|
505
|
-
const path = cls.
|
|
498
|
+
const path = cls.PATH
|
|
506
499
|
const { params, querystring } = cls.swaggerSchema
|
|
507
500
|
const schema = { hide: true, params, querystring }
|
|
508
501
|
app.options(path, { schema: pruneUndefined(schema) },
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import querystring from 'node:querystring'
|
|
2
|
+
import zlib from 'node:zlib'
|
|
3
|
+
|
|
4
|
+
import realFetch from 'node-fetch'
|
|
5
|
+
|
|
6
|
+
async function fetchWrapper (options, mockedFetch) {
|
|
7
|
+
const { compress = true, method = 'POST', url, qsParams, json } = options
|
|
8
|
+
let { body, headers } = options
|
|
9
|
+
headers = { ...headers } // copy the headers before we change them
|
|
10
|
+
|
|
11
|
+
// compress the body using brotli
|
|
12
|
+
if (json) {
|
|
13
|
+
headers['content-type'] = 'application/json'
|
|
14
|
+
body = JSON.stringify(options.json)
|
|
15
|
+
}
|
|
16
|
+
if (body && compress) {
|
|
17
|
+
body = zlib.brotliCompressSync(body)
|
|
18
|
+
headers['content-encoding'] = 'br'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// istanbul ignore next
|
|
22
|
+
const fetch = mockedFetch ?? fetchWrapper.__mock ?? realFetch
|
|
23
|
+
|
|
24
|
+
// compute the full URL including search params
|
|
25
|
+
let fullURL = url
|
|
26
|
+
if (qsParams) {
|
|
27
|
+
const qsStr = querystring.stringify(qsParams)
|
|
28
|
+
if (qsStr) {
|
|
29
|
+
fullURL += `?${qsStr}`
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return fetch(fullURL, { body, headers, method, compress: false })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default fetchWrapper
|
package/test/base-test.js
CHANGED
|
@@ -48,23 +48,34 @@ class BaseAppTest extends BaseTest {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function makeHeadersObj (headers) {
|
|
52
|
+
return {
|
|
53
|
+
get: name => headers[name]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
// the promise input conveniently matches the promise produced by supertest
|
|
52
58
|
// so you can pass the output of app.post(), etc. as the promise here as is
|
|
53
59
|
function makeGotMockValueFromPromise (promise) {
|
|
54
60
|
const mockValue = new Promise(resolve => {
|
|
55
61
|
promise.then(desiredHttpResponse => {
|
|
56
62
|
let body = desiredHttpResponse.text || desiredHttpResponse.body || ''
|
|
63
|
+
const headers = {}
|
|
57
64
|
if (typeof body !== 'string') {
|
|
58
65
|
body = JSON.stringify(body)
|
|
66
|
+
headers['content-type'] = 'application/json'
|
|
59
67
|
}
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
resolve({
|
|
69
|
+
status: desiredHttpResponse.status || 200,
|
|
70
|
+
headers: makeHeadersObj(headers),
|
|
71
|
+
text: async () => body
|
|
72
|
+
})
|
|
62
73
|
})
|
|
63
74
|
})
|
|
64
75
|
return mockValue
|
|
65
76
|
}
|
|
66
77
|
|
|
67
|
-
function makeGotMockValue (body,
|
|
78
|
+
function makeGotMockValue (body, status, callback) {
|
|
68
79
|
const mockValue = new Promise(resolve => {
|
|
69
80
|
// setTimeout is used so that this promise does not synchronously resolve
|
|
70
81
|
// because unmocked got will NEVER return synchronously. This ensures
|
|
@@ -73,21 +84,23 @@ function makeGotMockValue (body, statusCode, callback) {
|
|
|
73
84
|
// instead of only rejecting later when await'ed).
|
|
74
85
|
// https://github.com/facebook/jest/issues/6028 (since jest 21.x)
|
|
75
86
|
setTimeout(() => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
const headers = {}
|
|
88
|
+
resolve({
|
|
89
|
+
status,
|
|
90
|
+
headers: makeHeadersObj(headers),
|
|
91
|
+
text: async () => {
|
|
92
|
+
if (callback) {
|
|
93
|
+
callback()
|
|
94
|
+
}
|
|
95
|
+
if (typeof body === 'string') {
|
|
96
|
+
return body
|
|
97
|
+
}
|
|
98
|
+
headers['content-type'] = 'application/json'
|
|
99
|
+
return JSON.stringify(body)
|
|
82
100
|
}
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
resolve({ statusCode })
|
|
101
|
+
})
|
|
86
102
|
}, 0)
|
|
87
103
|
})
|
|
88
|
-
mockValue.text = async () => {
|
|
89
|
-
throw new Error('cannot use text() before calling await() on the response')
|
|
90
|
-
}
|
|
91
104
|
return mockValue
|
|
92
105
|
}
|
|
93
106
|
|
|
@@ -109,11 +122,12 @@ function mockGot () {
|
|
|
109
122
|
*/
|
|
110
123
|
gotMock.mockRespWithCallback = (...callbacks) => {
|
|
111
124
|
gotMock.mockImplementation(request => {
|
|
125
|
+
const { url, ...options } = request
|
|
112
126
|
for (const callback of callbacks) {
|
|
113
127
|
const desiredHTTPResponse = callback(request)
|
|
114
128
|
if (desiredHTTPResponse === true) {
|
|
115
129
|
const unmockedGot = jest.requireActual('../src/got')
|
|
116
|
-
return unmockedGot(
|
|
130
|
+
return unmockedGot(url, options)
|
|
117
131
|
}
|
|
118
132
|
if (desiredHTTPResponse) {
|
|
119
133
|
if (desiredHTTPResponse.then) {
|
package/src/got-wrapper.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import zlib from 'node:zlib'
|
|
2
|
-
|
|
3
|
-
import realGot from 'got'
|
|
4
|
-
|
|
5
|
-
function gotWrapper (options, mockedGot) {
|
|
6
|
-
options = {
|
|
7
|
-
decompress: true,
|
|
8
|
-
...options
|
|
9
|
-
}
|
|
10
|
-
if (options.compress) {
|
|
11
|
-
// make a copy of the headers obj before we make changes to it
|
|
12
|
-
const headers = {
|
|
13
|
-
...options.headers
|
|
14
|
-
}
|
|
15
|
-
options.headers = headers
|
|
16
|
-
if (options.body) {
|
|
17
|
-
options.body = zlib.brotliCompressSync(options.body)
|
|
18
|
-
}
|
|
19
|
-
if (options.json) {
|
|
20
|
-
headers['content-type'] = 'application/json'
|
|
21
|
-
options.body = zlib.brotliCompressSync(JSON.stringify(options.json))
|
|
22
|
-
delete options.json
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (options.body) {
|
|
26
|
-
headers['content-encoding'] = 'br'
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
// istanbul ignore next
|
|
30
|
-
const got = mockedGot ?? gotWrapper.__mocked_got ?? realGot
|
|
31
|
-
return got(options)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export default gotWrapper
|