@pbvision/fastify-firestore-service 0.0.50
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/LICENSE +204 -0
- package/README.md +187 -0
- package/docs/api.md +706 -0
- package/docs/build.sh +24 -0
- package/docs/jsdoc.config.json +22 -0
- package/package.json +93 -0
- package/src/api/api.js +852 -0
- package/src/api/db-api.js +109 -0
- package/src/api/exception.js +344 -0
- package/src/api/response.js +8 -0
- package/src/component-registrar.js +27 -0
- package/src/fetch-wrapper.js +30 -0
- package/src/index.js +13 -0
- package/src/make-app.js +223 -0
- package/src/make-logger.js +27 -0
- package/src/plugins/compress.js +28 -0
- package/src/plugins/content-parser.js +21 -0
- package/src/plugins/cookie.js +17 -0
- package/src/plugins/error-handler.js +180 -0
- package/src/plugins/health-check.js +19 -0
- package/src/plugins/latency-tracker.js +34 -0
- package/src/plugins/sentry-rate-limit.js +37 -0
- package/src/plugins/swagger.js +50 -0
- package/test/base-test.js +203 -0
- package/test/environment.js +3 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import querystring from 'node:querystring'
|
|
2
|
+
import zlib from 'node:zlib'
|
|
3
|
+
|
|
4
|
+
import { jest } from '@jest/globals'
|
|
5
|
+
import { BaseTest, runTests } from '@pbvision/jest-unit-test'
|
|
6
|
+
import superagentDefaults from 'superagent-defaults'
|
|
7
|
+
import supertest from 'supertest'
|
|
8
|
+
|
|
9
|
+
import fetchWrapper from '../src/fetch-wrapper.js'
|
|
10
|
+
|
|
11
|
+
let FASTIFY_CACHE
|
|
12
|
+
|
|
13
|
+
beforeAll(() => {
|
|
14
|
+
fetchWrapper.__mock = mockNodeFetch()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
afterAll(async () => {
|
|
18
|
+
await FASTIFY_CACHE?.close()
|
|
19
|
+
FASTIFY_CACHE = undefined
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
class BaseAppTest extends BaseTest {
|
|
23
|
+
async getMakeServiceFunc () {
|
|
24
|
+
const { default: func } = await import('../src/app.js')
|
|
25
|
+
return func
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
beforeEach () {
|
|
29
|
+
this.fetchMock = fetchWrapper.__mock
|
|
30
|
+
fetchWrapper.__mock.mockClear()
|
|
31
|
+
|
|
32
|
+
// default response is a weird error to help make it obvious the unit test
|
|
33
|
+
// author forgot to provide a custom mock value
|
|
34
|
+
this.fetchMock.mockResp('custom mock value not set', 555)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async beforeAll () {
|
|
38
|
+
const makeService = await this.getMakeServiceFunc()
|
|
39
|
+
this.fastify = FASTIFY_CACHE ?? await makeService()
|
|
40
|
+
FASTIFY_CACHE = this.fastify
|
|
41
|
+
|
|
42
|
+
await Promise.all([super.beforeAll(), this.fastify.ready()])
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Avoid having to consider compression in unit tests
|
|
46
|
+
* by removing the `accept-encoding` header,
|
|
47
|
+
* which is added by default from SuperTest
|
|
48
|
+
*/
|
|
49
|
+
const superTest = superagentDefaults(supertest(this.fastify.server))
|
|
50
|
+
superTest.set('accept-encoding', null)
|
|
51
|
+
this.app = new Proxy(superTest, {
|
|
52
|
+
get: (target, prop, receiver) => {
|
|
53
|
+
if (['get', 'post', 'put', 'delete'].includes(prop)) {
|
|
54
|
+
return (...requestParams) => {
|
|
55
|
+
const test = target[prop](...requestParams)
|
|
56
|
+
const originalExpect = test.expect
|
|
57
|
+
test.expect = async (...expectParams) => {
|
|
58
|
+
console.log(' \u2502 Expecting', expectParams[0])
|
|
59
|
+
return originalExpect.call(test, ...expectParams)
|
|
60
|
+
}
|
|
61
|
+
return test
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
return target[prop]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function makeHeadersObj (headers) {
|
|
72
|
+
return {
|
|
73
|
+
get: name => headers[name]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// the promise input conveniently matches the promise produced by supertest
|
|
78
|
+
// so you can pass the output of app.post(), etc. as the promise here as is
|
|
79
|
+
function makeNodeFetchMockValueFromPromise (promise) {
|
|
80
|
+
const mockValue = new Promise(resolve => {
|
|
81
|
+
promise.then(httpResponse => {
|
|
82
|
+
let body = httpResponse.text || httpResponse.body || ''
|
|
83
|
+
const headers = {}
|
|
84
|
+
if (typeof body !== 'string') {
|
|
85
|
+
body = JSON.stringify(body)
|
|
86
|
+
headers['content-type'] = 'application/json'
|
|
87
|
+
}
|
|
88
|
+
resolve({
|
|
89
|
+
status: httpResponse.status || 200,
|
|
90
|
+
headers: makeHeadersObj(headers),
|
|
91
|
+
text: async () => body
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
return mockValue
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function makeNodeFetchMockValue (body, status, callback) {
|
|
99
|
+
const mockValue = new Promise(resolve => {
|
|
100
|
+
// setTimeout is used so that this promise does not synchronously resolve
|
|
101
|
+
// because unmocked fetch will NEVER return synchronously. This ensures
|
|
102
|
+
// functions which call fetch() never resolve synchronously (which can
|
|
103
|
+
// change their behavior... e.g., allow them to throw when called,
|
|
104
|
+
// instead of only rejecting later when await'ed).
|
|
105
|
+
// https://github.com/facebook/jest/issues/6028 (since jest 21.x)
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
const headers = {}
|
|
108
|
+
resolve({
|
|
109
|
+
status,
|
|
110
|
+
headers: makeHeadersObj(headers),
|
|
111
|
+
text: async () => {
|
|
112
|
+
if (callback) {
|
|
113
|
+
callback()
|
|
114
|
+
}
|
|
115
|
+
if (typeof body === 'string') {
|
|
116
|
+
return body
|
|
117
|
+
}
|
|
118
|
+
headers['content-type'] = 'application/json'
|
|
119
|
+
return JSON.stringify(body)
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
}, 0)
|
|
123
|
+
})
|
|
124
|
+
return mockValue
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function mockNodeFetch () {
|
|
128
|
+
const nodeFetchMock = jest.fn().mockImplementation()
|
|
129
|
+
|
|
130
|
+
nodeFetchMock.mockResp = (body = '', statusCode = 200, callback) => {
|
|
131
|
+
nodeFetchMock.mockReturnValue(makeNodeFetchMockValue(body, statusCode, callback))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Determine the mock response to use when the request is made.
|
|
136
|
+
*
|
|
137
|
+
* @param {...function (request)} callbacks a list of callbacks in the order
|
|
138
|
+
* to check and see if they have a mock response to use; if no callback,
|
|
139
|
+
* provides a mock response then an error will be thrown
|
|
140
|
+
*/
|
|
141
|
+
nodeFetchMock.mockRespWithCallback = (...callbacks) => {
|
|
142
|
+
nodeFetchMock.mockImplementation((fullURL, options) => {
|
|
143
|
+
// construct the fetchWrapper's request parameter from the fullURL and
|
|
144
|
+
// options passed as parameters to node-fetch
|
|
145
|
+
const [baseUrl, qsParams] = fullURL.split('?', 2)
|
|
146
|
+
const request = {
|
|
147
|
+
compress: options.headers['content-encoding'] === 'br',
|
|
148
|
+
method: options.method,
|
|
149
|
+
url: baseUrl,
|
|
150
|
+
qsParams: querystring.parse(qsParams),
|
|
151
|
+
headers: options.headers
|
|
152
|
+
}
|
|
153
|
+
delete request.headers['content-encoding']
|
|
154
|
+
const body = options.body
|
|
155
|
+
if (options.headers['content-type'] === 'application/json') {
|
|
156
|
+
delete options.headers['content-type']
|
|
157
|
+
request.json = JSON.parse(body)
|
|
158
|
+
} else {
|
|
159
|
+
request.body = body // may be undefined
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const callback of callbacks) {
|
|
163
|
+
const desiredHTTPResponse = callback(request)
|
|
164
|
+
if (desiredHTTPResponse === true) {
|
|
165
|
+
return fetchWrapper(request, false)
|
|
166
|
+
}
|
|
167
|
+
if (desiredHTTPResponse) {
|
|
168
|
+
if (desiredHTTPResponse.then) {
|
|
169
|
+
return makeNodeFetchMockValueFromPromise(desiredHTTPResponse)
|
|
170
|
+
}
|
|
171
|
+
return makeNodeFetchMockValue(
|
|
172
|
+
// follows the names from supertest
|
|
173
|
+
desiredHTTPResponse.text || desiredHTTPResponse.body || '',
|
|
174
|
+
desiredHTTPResponse.status || 200)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
throw new Error(`un-mocked fetchWrapper() request: ${JSON.stringify(request)}`)
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// will respond to the next N requests with the specified N responses
|
|
182
|
+
nodeFetchMock.mockRespMulti = (...responses) => {
|
|
183
|
+
let idx = 0
|
|
184
|
+
function setupNextResponse () {
|
|
185
|
+
if (idx < responses.length) {
|
|
186
|
+
const [body, code] = responses[idx]
|
|
187
|
+
nodeFetchMock.mockResp(body ?? '', code ?? 200, setupNextResponse)
|
|
188
|
+
} else {
|
|
189
|
+
// exactly equal means we just got our last callback (okay)
|
|
190
|
+
nodeFetchMock.mockResp('ran out of mocked responses', 556)
|
|
191
|
+
}
|
|
192
|
+
idx += 1
|
|
193
|
+
}
|
|
194
|
+
setupNextResponse()
|
|
195
|
+
}
|
|
196
|
+
return nodeFetchMock
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export {
|
|
200
|
+
BaseAppTest,
|
|
201
|
+
BaseTest,
|
|
202
|
+
runTests
|
|
203
|
+
}
|