@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.
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import '../node_modules/@pbvision/firestore-orm/environment.js'
2
+
3
+ process.env.NODE_ENV = 'localhost'