@dereekb/firebase-server 13.0.0 → 13.0.2
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/index.cjs.default.js +1 -0
- package/index.cjs.js +1923 -2001
- package/index.cjs.mjs +2 -0
- package/index.esm.js +1924 -2000
- package/mailgun/index.cjs.default.js +1 -0
- package/mailgun/index.cjs.js +28 -35
- package/mailgun/index.cjs.mjs +2 -0
- package/mailgun/index.esm.js +28 -35
- package/mailgun/package.json +17 -22
- package/model/index.cjs.default.js +1 -0
- package/model/index.cjs.js +4042 -4573
- package/model/index.cjs.mjs +2 -0
- package/model/index.esm.js +4042 -4573
- package/model/package.json +23 -27
- package/package.json +42 -61
- package/src/lib/function/error.d.ts +0 -8
- package/test/index.cjs.default.js +1 -0
- package/test/index.cjs.js +867 -933
- package/test/index.cjs.mjs +2 -0
- package/test/index.esm.js +867 -933
- package/test/package.json +22 -26
- package/zoho/index.cjs.default.js +1 -0
- package/zoho/index.cjs.js +82 -85
- package/zoho/index.cjs.mjs +2 -0
- package/zoho/index.esm.js +82 -85
- package/zoho/package.json +17 -21
package/test/index.esm.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import 'core-js/modules/es.json.parse.js';
|
|
2
|
-
import 'core-js/modules/es.json.stringify.js';
|
|
3
1
|
import { randomEmailFactory, randomPhoneNumberFactory, mapGetter, incrementingNumberFactory, asGetter, getValueFromGetter, cachedGetter, asArray, mapObjectMap } from '@dereekb/util';
|
|
4
2
|
import { AbstractChildTestContextFixture, useTestContextFixture, testContextBuilder, AbstractTestContextFixture, useTestFunctionFixture, useTestFunctionMapFixture, ExpectedErrorOfSpecificTypeError } from '@dereekb/util/test';
|
|
5
3
|
import { decode } from 'jsonwebtoken';
|
|
@@ -10,143 +8,133 @@ import { makeTestingFirestoreDrivers, TestFirestoreContextFixture, TestFirestore
|
|
|
10
8
|
import { googleCloudFirestoreDrivers, googleCloudFirebaseStorageDrivers, googleCloudStorageFromFirebaseAdminStorage, DefaultFirebaseServerEnvService, FirebaseServerEnvService, firebaseServerAppTokenProvider, firebaseServerStorageDefaultBucketIdTokenProvider } from '@dereekb/firebase-server';
|
|
11
9
|
import { Firestore } from '@google-cloud/firestore';
|
|
12
10
|
import { Storage } from '@google-cloud/storage';
|
|
13
|
-
import 'core-js/modules/es.iterator.constructor.js';
|
|
14
|
-
import 'core-js/modules/es.iterator.for-each.js';
|
|
15
11
|
import { Test } from '@nestjs/testing';
|
|
16
12
|
import { serverEnvTokenProvider, ServerEnvironmentService } from '@dereekb/nestjs';
|
|
17
13
|
import { HttpsError } from 'firebase-functions/v1/https';
|
|
18
14
|
import { BaseError } from 'make-error';
|
|
19
15
|
|
|
20
16
|
class AuthorizedUserTestContextFixture extends AbstractChildTestContextFixture {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
17
|
+
// MARK: AuthorizedUserTestContext (Forwarded)
|
|
18
|
+
get uid() {
|
|
19
|
+
return this.instance.uid;
|
|
20
|
+
}
|
|
21
|
+
loadUserRecord() {
|
|
22
|
+
return this.instance.loadUserRecord();
|
|
23
|
+
}
|
|
24
|
+
loadUserEmailAndPhone() {
|
|
25
|
+
return this.instance.loadUserEmailAndPhone();
|
|
26
|
+
}
|
|
27
|
+
loadIdToken() {
|
|
28
|
+
return this.instance.loadIdToken();
|
|
29
|
+
}
|
|
30
|
+
loadDecodedIdToken() {
|
|
31
|
+
return this.instance.loadDecodedIdToken();
|
|
32
|
+
}
|
|
33
|
+
makeContextOptions() {
|
|
34
|
+
return this.instance.makeContextOptions();
|
|
35
|
+
}
|
|
36
|
+
callWrappedFunction(fn, params, skipJsonConversion) {
|
|
37
|
+
return this.instance.callWrappedFunction(fn, params, skipJsonConversion);
|
|
38
|
+
}
|
|
39
|
+
callCloudFunction(fn, params, skipJsonConversion = false) {
|
|
40
|
+
return this.instance.callCloudFunction(fn, params, skipJsonConversion);
|
|
41
|
+
}
|
|
46
42
|
}
|
|
47
43
|
function convertParamsToParsedJsonObjectAndBack(object) {
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
const paramsAsJson = JSON.parse(JSON.stringify(object));
|
|
45
|
+
return paramsAsJson;
|
|
50
46
|
}
|
|
51
47
|
class AuthorizedUserTestContextInstance {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const parsedParams = params == null || skipJsonConversion ? params : convertParamsToParsedJsonObjectAndBack(params);
|
|
137
|
-
return this.makeContextOptions().then(options => fn(parsedParams, contextOptions ? {
|
|
138
|
-
...contextOptions,
|
|
139
|
-
...options
|
|
140
|
-
} : options));
|
|
141
|
-
}
|
|
48
|
+
uid;
|
|
49
|
+
testContext;
|
|
50
|
+
constructor(uid, testContext) {
|
|
51
|
+
this.uid = uid;
|
|
52
|
+
this.testContext = testContext;
|
|
53
|
+
}
|
|
54
|
+
loadUserRecord() {
|
|
55
|
+
return this.testContext.auth.getUser(this.uid);
|
|
56
|
+
}
|
|
57
|
+
async loadUserEmailAndPhone() {
|
|
58
|
+
const record = await this.loadUserRecord();
|
|
59
|
+
return {
|
|
60
|
+
email: record.email,
|
|
61
|
+
phone: record.phoneNumber
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
loadIdToken() {
|
|
65
|
+
return this.loadUserRecord().then((record) => createEncodedTestFirestoreTokenForUserRecord(this.testContext.auth, record));
|
|
66
|
+
}
|
|
67
|
+
loadDecodedIdToken() {
|
|
68
|
+
return this.loadIdToken().then(decodeEncodedCreateCustomTokenResult);
|
|
69
|
+
}
|
|
70
|
+
makeContextOptions() {
|
|
71
|
+
return this.loadUserRecord().then((record) => createTestFunctionContextOptions(this.testContext.auth, record));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Calls a wrapped function with the input params and the context from makeContextOptions().
|
|
75
|
+
*
|
|
76
|
+
* @param fn
|
|
77
|
+
* @param params
|
|
78
|
+
* @param skipJsonConversion
|
|
79
|
+
*/
|
|
80
|
+
callWrappedFunction(fn, params, skipJsonConversion) {
|
|
81
|
+
// Parse to JSON then back to simulate sending JSON to the server, and the server parsing it as a POJO.
|
|
82
|
+
const parsedParams = params == null || skipJsonConversion ? params : convertParamsToParsedJsonObjectAndBack(params);
|
|
83
|
+
return this.makeContextOptions().then((options) => fn(parsedParams, options));
|
|
84
|
+
}
|
|
85
|
+
callCloudFunction(fn, params, skipJsonConversion = false) {
|
|
86
|
+
if (params != null && params.scheduleTime) {
|
|
87
|
+
// Workaround for https://github.com/firebase/firebase-functions-test/issues/210
|
|
88
|
+
const scheduleTime = params.scheduleTime;
|
|
89
|
+
delete params.scheduleTime;
|
|
90
|
+
params.timestamp = scheduleTime;
|
|
91
|
+
}
|
|
92
|
+
return this.callWrappedFunction(fn, params, skipJsonConversion);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Calls a wrapped gen 2 auth blocking function with the input params and context options from makeContextOptions().
|
|
96
|
+
*
|
|
97
|
+
* @param fn
|
|
98
|
+
* @param userRecord
|
|
99
|
+
* @param eventType
|
|
100
|
+
* @param eventOverride
|
|
101
|
+
* @param skipJsonConversion
|
|
102
|
+
* @returns
|
|
103
|
+
*/
|
|
104
|
+
callAuthBlockingFunction(fn, userRecord, eventType, eventOverride, skipJsonConversion = false) {
|
|
105
|
+
const timestamp = new Date().toISOString();
|
|
106
|
+
const event = {
|
|
107
|
+
ipAddress: '127.0.0.1',
|
|
108
|
+
userAgent: 'testing',
|
|
109
|
+
eventId: '0',
|
|
110
|
+
params: {},
|
|
111
|
+
resource: { service: 'dbx-test', name: 'fake-resource' },
|
|
112
|
+
timestamp,
|
|
113
|
+
...eventOverride,
|
|
114
|
+
data: userRecord,
|
|
115
|
+
eventType
|
|
116
|
+
};
|
|
117
|
+
return this.callCloudFunction(fn, event, skipJsonConversion);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* @deprecated gen 1
|
|
121
|
+
*
|
|
122
|
+
* @param fn
|
|
123
|
+
* @param params
|
|
124
|
+
* @param contextOptions
|
|
125
|
+
* @param skipJsonConversion
|
|
126
|
+
* @returns
|
|
127
|
+
*/
|
|
128
|
+
callEventCloudFunction(fn, params, contextOptions, skipJsonConversion = false) {
|
|
129
|
+
const parsedParams = params == null || skipJsonConversion ? params : convertParamsToParsedJsonObjectAndBack(params);
|
|
130
|
+
return this.makeContextOptions().then((options) => fn(parsedParams, contextOptions ? { ...contextOptions, ...options } : options));
|
|
131
|
+
}
|
|
142
132
|
}
|
|
143
133
|
/**
|
|
144
134
|
* Convenience function for using authorizedUserContextFactory directly and passing buildTests.
|
|
145
135
|
*/
|
|
146
136
|
function authorizedUserContext(config, buildTests) {
|
|
147
|
-
|
|
148
|
-
f: config.f
|
|
149
|
-
}, buildTests);
|
|
137
|
+
authorizedUserContextFactory(config)({ f: config.f }, buildTests);
|
|
150
138
|
}
|
|
151
139
|
const AUTHORIZED_USER_RANDOM_EMAIL_FACTORY = randomEmailFactory();
|
|
152
140
|
const AUTHORIZED_USER_RANDOM_PHONE_NUMBER_FACTORY = randomPhoneNumberFactory();
|
|
@@ -154,86 +142,66 @@ const AUTHORIZED_USER_RANDOM_PHONE_NUMBER_FACTORY = randomPhoneNumberFactory();
|
|
|
154
142
|
* Creates a new Jest Context that has a random user for authorization for use in firebase server tests.
|
|
155
143
|
*/
|
|
156
144
|
function authorizedUserContextFactory(config) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
displayName: 'Test Person',
|
|
209
|
-
...details,
|
|
210
|
-
email,
|
|
211
|
-
phoneNumber,
|
|
212
|
-
...inputUser
|
|
145
|
+
const { uid: uidGetter, makeInstance = (uid, testInstance) => new AuthorizedUserTestContextInstance(uid, testInstance), makeFixture = (f) => new AuthorizedUserTestContextFixture(f), makeUserDetails = () => ({}), initUser } = config;
|
|
146
|
+
const makeUid = uidGetter ? asGetter(uidGetter) : testUidFactory;
|
|
147
|
+
return (params, buildTests) => {
|
|
148
|
+
const { f, user: inputUserGetterOrValue, addContactInfo: inputAddContactInfoGetterOrValue, template: inputTemplateGetterOrValue } = params;
|
|
149
|
+
const inputAddContactInfoGetter = asGetter(inputAddContactInfoGetterOrValue);
|
|
150
|
+
const inputUserGetter = asGetter(inputUserGetterOrValue);
|
|
151
|
+
const templateGetter = asGetter(inputTemplateGetterOrValue);
|
|
152
|
+
return useTestContextFixture({
|
|
153
|
+
fixture: makeFixture(f),
|
|
154
|
+
buildTests,
|
|
155
|
+
initInstance: async () => {
|
|
156
|
+
const inputAddContactInfo = await inputAddContactInfoGetter();
|
|
157
|
+
const inputUser = await inputUserGetter();
|
|
158
|
+
const inputTemplate = await templateGetter();
|
|
159
|
+
const uid = inputUser?.uid || makeUid();
|
|
160
|
+
const { details, claims, addContactInfo: userDetailsAddContactInfo } = { ...makeUserDetails(uid, params), ...inputTemplate };
|
|
161
|
+
const { phoneNumber: detailsPhoneNumber, email: detailsEmail } = details ?? {}; // keep details if provided
|
|
162
|
+
const addContactInfo = inputAddContactInfo || userDetailsAddContactInfo;
|
|
163
|
+
const auth = f.instance.auth;
|
|
164
|
+
let email;
|
|
165
|
+
let phoneNumber;
|
|
166
|
+
if (addContactInfo) {
|
|
167
|
+
email = detailsEmail ?? AUTHORIZED_USER_RANDOM_EMAIL_FACTORY();
|
|
168
|
+
phoneNumber = detailsPhoneNumber ?? AUTHORIZED_USER_RANDOM_PHONE_NUMBER_FACTORY();
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
email = detailsEmail;
|
|
172
|
+
phoneNumber = detailsPhoneNumber ?? undefined;
|
|
173
|
+
}
|
|
174
|
+
const userRecord = await auth.createUser({
|
|
175
|
+
uid,
|
|
176
|
+
displayName: 'Test Person',
|
|
177
|
+
...details,
|
|
178
|
+
email,
|
|
179
|
+
phoneNumber,
|
|
180
|
+
...inputUser
|
|
181
|
+
});
|
|
182
|
+
if (claims) {
|
|
183
|
+
await auth.setCustomUserClaims(uid, claims);
|
|
184
|
+
}
|
|
185
|
+
const instance = await makeInstance(uid, f.instance, params, userRecord);
|
|
186
|
+
if (initUser) {
|
|
187
|
+
await initUser(instance, params);
|
|
188
|
+
}
|
|
189
|
+
return instance;
|
|
190
|
+
},
|
|
191
|
+
destroyInstance: async (instance) => {
|
|
192
|
+
const app = instance.testContext.app;
|
|
193
|
+
const uid = instance.uid;
|
|
194
|
+
await app.auth().deleteUser(uid);
|
|
195
|
+
}
|
|
213
196
|
});
|
|
214
|
-
|
|
215
|
-
await auth.setCustomUserClaims(uid, claims);
|
|
216
|
-
}
|
|
217
|
-
const instance = await makeInstance(uid, f.instance, params, userRecord);
|
|
218
|
-
if (initUser) {
|
|
219
|
-
await initUser(instance, params);
|
|
220
|
-
}
|
|
221
|
-
return instance;
|
|
222
|
-
},
|
|
223
|
-
destroyInstance: async instance => {
|
|
224
|
-
const app = instance.testContext.app;
|
|
225
|
-
const uid = instance.uid;
|
|
226
|
-
await app.auth().deleteUser(uid);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
};
|
|
197
|
+
};
|
|
230
198
|
}
|
|
231
199
|
/**
|
|
232
200
|
* Incrementing number factory for generating test UID values.
|
|
233
201
|
*
|
|
234
202
|
* Has the format 'test-uid-<number>'
|
|
235
203
|
*/
|
|
236
|
-
const testUidFactory = mapGetter(incrementingNumberFactory(), i => `${new Date().getTime()}0${i}`);
|
|
204
|
+
const testUidFactory = mapGetter(incrementingNumberFactory(), (i) => `${new Date().getTime()}0${i}`);
|
|
237
205
|
/**
|
|
238
206
|
* Creates a CallableContextOptions with auth attached corresponding to the input UserRecord.
|
|
239
207
|
*
|
|
@@ -242,11 +210,11 @@ const testUidFactory = mapGetter(incrementingNumberFactory(), i => `${new Date()
|
|
|
242
210
|
* @returns
|
|
243
211
|
*/
|
|
244
212
|
async function createTestFunctionContextOptions(auth, userRecord) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
213
|
+
const authData = await createTestFunctionContextAuthData(auth, userRecord);
|
|
214
|
+
const contextOptions = {
|
|
215
|
+
auth: authData
|
|
216
|
+
};
|
|
217
|
+
return contextOptions;
|
|
250
218
|
}
|
|
251
219
|
/**
|
|
252
220
|
* Creates AuthData from the input auth and user record.
|
|
@@ -256,13 +224,13 @@ async function createTestFunctionContextOptions(auth, userRecord) {
|
|
|
256
224
|
* @returns
|
|
257
225
|
*/
|
|
258
226
|
async function createTestFunctionContextAuthData(auth, userRecord) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
227
|
+
const token = await createTestFirestoreTokenForUserRecord(auth, userRecord);
|
|
228
|
+
const authData = {
|
|
229
|
+
uid: token.uid,
|
|
230
|
+
token,
|
|
231
|
+
rawToken: ''
|
|
232
|
+
};
|
|
233
|
+
return authData;
|
|
266
234
|
}
|
|
267
235
|
/**
|
|
268
236
|
* Creates and decodes a firestore token used for testing.
|
|
@@ -272,7 +240,7 @@ async function createTestFunctionContextAuthData(auth, userRecord) {
|
|
|
272
240
|
* @returns
|
|
273
241
|
*/
|
|
274
242
|
function createTestFirestoreTokenForUserRecord(auth, userRecord) {
|
|
275
|
-
|
|
243
|
+
return createEncodedTestFirestoreTokenForUserRecord(auth, userRecord).then(decodeEncodedCreateCustomTokenResult);
|
|
276
244
|
}
|
|
277
245
|
/**
|
|
278
246
|
* Creates an encoded firestore token used for testing.
|
|
@@ -282,167 +250,158 @@ function createTestFirestoreTokenForUserRecord(auth, userRecord) {
|
|
|
282
250
|
* @returns
|
|
283
251
|
*/
|
|
284
252
|
function createEncodedTestFirestoreTokenForUserRecord(auth, userRecord) {
|
|
285
|
-
|
|
286
|
-
|
|
253
|
+
// TODO: Consider replacing createCustomToken, as the custom claims are put into an object called claims in the JWT, instead of spread over. The decodeEncodedCreateCustomTokenResult() function handles this issue, but it may not be expected.
|
|
254
|
+
return auth.createCustomToken(userRecord.uid, testFirestoreClaimsFromUserRecord(userRecord));
|
|
287
255
|
}
|
|
288
256
|
function decodeEncodedCreateCustomTokenResult(token) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
257
|
+
const decoded = decode(token);
|
|
258
|
+
const decodedToken = {
|
|
259
|
+
...decoded,
|
|
260
|
+
...decoded.claims,
|
|
261
|
+
auth_time: decoded.iat,
|
|
262
|
+
firebase: decoded.claims?.firebase ?? {}
|
|
263
|
+
};
|
|
264
|
+
delete decodedToken.claims; // remove the "claims" item if it exists.
|
|
265
|
+
return decodedToken;
|
|
298
266
|
}
|
|
299
267
|
function testFirestoreClaimsFromUserRecord(userRecord) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
268
|
+
// Copy claims to be similar to DecodedIdToken pieces.
|
|
269
|
+
const baseClaims = {
|
|
270
|
+
picture: userRecord.photoURL,
|
|
271
|
+
email: userRecord.email,
|
|
272
|
+
email_verified: userRecord.emailVerified ?? false,
|
|
273
|
+
firebase: {
|
|
274
|
+
sign_in_provider: '@dereekb/firebase-server/test',
|
|
275
|
+
identities: []
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
const customClaims = userRecord.customClaims;
|
|
279
|
+
const claims = {
|
|
280
|
+
...customClaims,
|
|
281
|
+
...baseClaims
|
|
282
|
+
};
|
|
283
|
+
return claims;
|
|
316
284
|
}
|
|
317
285
|
|
|
318
286
|
class ModelTestContextFixture extends AbstractChildTestContextFixture {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
287
|
+
// MARK: ModelTestContext (Forwarded)
|
|
288
|
+
get documentId() {
|
|
289
|
+
return this.instance.documentId;
|
|
290
|
+
}
|
|
291
|
+
get documentKey() {
|
|
292
|
+
return this.instance.documentKey;
|
|
293
|
+
}
|
|
294
|
+
get documentFlatKey() {
|
|
295
|
+
return this.instance.documentFlatKey;
|
|
296
|
+
}
|
|
297
|
+
get documentTwoWayFlatKey() {
|
|
298
|
+
return this.instance.documentTwoWayFlatKey;
|
|
299
|
+
}
|
|
300
|
+
get documentRef() {
|
|
301
|
+
return this.instance.documentRef;
|
|
302
|
+
}
|
|
303
|
+
get document() {
|
|
304
|
+
return this.instance.document;
|
|
305
|
+
}
|
|
338
306
|
}
|
|
339
307
|
class ModelTestContextInstance {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
308
|
+
collection;
|
|
309
|
+
ref;
|
|
310
|
+
testContext;
|
|
311
|
+
constructor(collection, ref, testContext) {
|
|
312
|
+
this.collection = collection;
|
|
313
|
+
this.ref = ref;
|
|
314
|
+
this.testContext = testContext;
|
|
315
|
+
}
|
|
316
|
+
get documentId() {
|
|
317
|
+
return this.ref.id;
|
|
318
|
+
}
|
|
319
|
+
get documentKey() {
|
|
320
|
+
return this.ref.path;
|
|
321
|
+
}
|
|
322
|
+
get documentFlatKey() {
|
|
323
|
+
return flatFirestoreModelKey(this.documentKey);
|
|
324
|
+
}
|
|
325
|
+
get documentTwoWayFlatKey() {
|
|
326
|
+
return twoWayFlatFirestoreModelKey(this.documentKey);
|
|
327
|
+
}
|
|
328
|
+
get documentRef() {
|
|
329
|
+
return this.ref;
|
|
330
|
+
}
|
|
331
|
+
get document() {
|
|
332
|
+
return this.collection.documentAccessor().loadDocument(this.ref);
|
|
333
|
+
}
|
|
366
334
|
}
|
|
367
335
|
/**
|
|
368
336
|
* Creates a new Test Context that has a random user for authorization for use in firebase server tests.
|
|
369
337
|
*/
|
|
370
338
|
function modelTestContextFactory(config) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const accessor = collection.documentAccessor();
|
|
376
|
-
if (accessor.newDocument == null) {
|
|
377
|
-
throw new Error('collection passed to makeRef() was not a full FirestoreCollection. Either supply a custom makeRef() function or a FirestoreCollection that has newDocument() available on the documentAccessor.');
|
|
378
|
-
}
|
|
379
|
-
return accessor.newDocument().documentRef;
|
|
380
|
-
},
|
|
381
|
-
makeInstance = (collection, ref, testInstance) => new ModelTestContextInstance(collection, ref, testInstance),
|
|
382
|
-
makeFixture = f => new ModelTestContextFixture(f),
|
|
383
|
-
initDocument,
|
|
384
|
-
destroyInstance
|
|
385
|
-
} = config;
|
|
386
|
-
return (params, buildTests) => {
|
|
387
|
-
const {
|
|
388
|
-
f
|
|
389
|
-
} = params;
|
|
390
|
-
return useTestContextFixture({
|
|
391
|
-
fixture: makeFixture(f),
|
|
392
|
-
buildTests,
|
|
393
|
-
initInstance: async () => {
|
|
394
|
-
const parentInstance = f.instance;
|
|
395
|
-
let ref;
|
|
396
|
-
let collection;
|
|
397
|
-
let init;
|
|
398
|
-
if (params.doc) {
|
|
399
|
-
const doc = await getValueFromGetter(params.doc);
|
|
400
|
-
if (!collectionForDocument) {
|
|
401
|
-
throw new Error('collectionForDocument() is required when using ModelTestContextDocumentRefParams values as input.');
|
|
402
|
-
}
|
|
403
|
-
collection = collectionForDocument(parentInstance, doc);
|
|
404
|
-
const expectedCollectionName = collection.documentAccessor().modelIdentity.collectionName;
|
|
405
|
-
if (expectedCollectionName !== doc.modelIdentity.collectionName) {
|
|
406
|
-
throw new Error(`Input doc is in a different collection (${doc.modelIdentity.collectionName}) than expected (${expectedCollectionName}).`);
|
|
407
|
-
}
|
|
408
|
-
ref = doc.documentRef;
|
|
409
|
-
init = false;
|
|
410
|
-
} else {
|
|
411
|
-
collection = getCollection(parentInstance, params);
|
|
412
|
-
ref = await makeRef(collection, params, parentInstance);
|
|
413
|
-
init = true;
|
|
414
|
-
}
|
|
415
|
-
const instance = await makeInstance(collection, ref, parentInstance);
|
|
416
|
-
if (init && initDocument) {
|
|
417
|
-
await initDocument(instance, params);
|
|
339
|
+
const { getCollection, collectionForDocument, makeRef = (collection) => {
|
|
340
|
+
const accessor = collection.documentAccessor();
|
|
341
|
+
if (accessor.newDocument == null) {
|
|
342
|
+
throw new Error('collection passed to makeRef() was not a full FirestoreCollection. Either supply a custom makeRef() function or a FirestoreCollection that has newDocument() available on the documentAccessor.');
|
|
418
343
|
}
|
|
419
|
-
return
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
344
|
+
return accessor.newDocument().documentRef;
|
|
345
|
+
}, makeInstance = (collection, ref, testInstance) => new ModelTestContextInstance(collection, ref, testInstance), makeFixture = (f) => new ModelTestContextFixture(f), initDocument, destroyInstance } = config;
|
|
346
|
+
return (params, buildTests) => {
|
|
347
|
+
const { f } = params;
|
|
348
|
+
return useTestContextFixture({
|
|
349
|
+
fixture: makeFixture(f),
|
|
350
|
+
buildTests,
|
|
351
|
+
initInstance: async () => {
|
|
352
|
+
const parentInstance = f.instance;
|
|
353
|
+
let ref;
|
|
354
|
+
let collection;
|
|
355
|
+
let init;
|
|
356
|
+
if (params.doc) {
|
|
357
|
+
const doc = await getValueFromGetter(params.doc);
|
|
358
|
+
if (!collectionForDocument) {
|
|
359
|
+
throw new Error('collectionForDocument() is required when using ModelTestContextDocumentRefParams values as input.');
|
|
360
|
+
}
|
|
361
|
+
collection = collectionForDocument(parentInstance, doc);
|
|
362
|
+
const expectedCollectionName = collection.documentAccessor().modelIdentity.collectionName;
|
|
363
|
+
if (expectedCollectionName !== doc.modelIdentity.collectionName) {
|
|
364
|
+
throw new Error(`Input doc is in a different collection (${doc.modelIdentity.collectionName}) than expected (${expectedCollectionName}).`);
|
|
365
|
+
}
|
|
366
|
+
ref = doc.documentRef;
|
|
367
|
+
init = false;
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
collection = getCollection(parentInstance, params);
|
|
371
|
+
ref = await makeRef(collection, params, parentInstance);
|
|
372
|
+
init = true;
|
|
373
|
+
}
|
|
374
|
+
const instance = await makeInstance(collection, ref, parentInstance);
|
|
375
|
+
if (init && initDocument) {
|
|
376
|
+
await initDocument(instance, params);
|
|
377
|
+
}
|
|
378
|
+
return instance;
|
|
379
|
+
},
|
|
380
|
+
destroyInstance
|
|
381
|
+
});
|
|
382
|
+
};
|
|
424
383
|
}
|
|
425
384
|
|
|
426
385
|
let adminEnvironmentInitialized = false;
|
|
427
386
|
function isAdminEnvironmentInitialized() {
|
|
428
|
-
|
|
387
|
+
return adminEnvironmentInitialized;
|
|
429
388
|
}
|
|
430
389
|
function generateNewProjectId() {
|
|
431
|
-
|
|
432
|
-
|
|
390
|
+
const projectId = 'firebase-test-' + new Date().getTime();
|
|
391
|
+
return projectId;
|
|
433
392
|
}
|
|
434
393
|
function rollNewGCloudProjectEnvironmentVariable() {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
394
|
+
const projectId = generateNewProjectId();
|
|
395
|
+
process.env.GCLOUD_TEST_PROJECT = projectId;
|
|
396
|
+
process.env.GCLOUD_PROJECT = projectId;
|
|
397
|
+
applyFirebaseGCloudTestProjectIdToFirebaseConfigEnv();
|
|
398
|
+
return projectId;
|
|
440
399
|
}
|
|
441
400
|
function getGCloudProjectId() {
|
|
442
|
-
|
|
401
|
+
return process.env.GCLOUD_PROJECT;
|
|
443
402
|
}
|
|
444
403
|
function getGCloudTestProjectId() {
|
|
445
|
-
|
|
404
|
+
return process.env.GCLOUD_TEST_PROJECT;
|
|
446
405
|
}
|
|
447
406
|
/**
|
|
448
407
|
* Applies the current GCLOUD_PROJECT to FIREBASE_CONFIG.
|
|
@@ -451,53 +410,55 @@ function getGCloudTestProjectId() {
|
|
|
451
410
|
* so that each component can also
|
|
452
411
|
*/
|
|
453
412
|
function applyFirebaseGCloudTestProjectIdToFirebaseConfigEnv() {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
413
|
+
// firebase-functions-test overwrites this each time.
|
|
414
|
+
// https://github.com/firebase/firebase-functions-test/blob/acb068f4c086f3355b2960b9e9e5895716c7f8cc/src/lifecycle.ts#L37
|
|
415
|
+
const testProjectId = getGCloudTestProjectId();
|
|
416
|
+
// console.log('Test project: ', testProjectId);
|
|
417
|
+
if (!testProjectId) {
|
|
418
|
+
throw new Error('No test project id was available in the environment. Did you call initFirebaseAdminTestEnvironment() first?');
|
|
419
|
+
}
|
|
420
|
+
const config = JSON.parse(process.env.FIREBASE_CONFIG ?? '{}');
|
|
421
|
+
config.projectId = testProjectId;
|
|
422
|
+
process.env.FIREBASE_CONFIG = JSON.stringify(config);
|
|
423
|
+
process.env.GCLOUD_PROJECT = testProjectId; // re-apply to GCLOUD_PROJECT too
|
|
424
|
+
return testProjectId;
|
|
466
425
|
}
|
|
467
426
|
/**
|
|
468
427
|
* Should be called before calling/using adminFirebaseTestBuilder(). This should only be called once.
|
|
469
428
|
*/
|
|
470
429
|
function initFirebaseAdminTestEnvironment(config) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
430
|
+
function crashForEmulator(emulator) {
|
|
431
|
+
throw new Error(`Emulator for ${emulator} was not set null or to a host. Crashing to prevent contamination.`);
|
|
432
|
+
}
|
|
433
|
+
function configureEmulator(emulator, envKey) {
|
|
434
|
+
const emulatorConfig = config.emulators[emulator];
|
|
435
|
+
if (emulatorConfig) {
|
|
436
|
+
process.env[envKey] = emulatorConfig;
|
|
437
|
+
}
|
|
438
|
+
else if (config.emulators.firestore !== null) {
|
|
439
|
+
crashForEmulator(emulator);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
rollNewGCloudProjectEnvironmentVariable();
|
|
443
|
+
configureEmulator('auth', 'FIREBASE_AUTH_EMULATOR_HOST');
|
|
444
|
+
configureEmulator('firestore', 'FIRESTORE_EMULATOR_HOST');
|
|
445
|
+
configureEmulator('storage', 'FIREBASE_STORAGE_EMULATOR_HOST');
|
|
446
|
+
applyFirebaseGCloudTestProjectIdToFirebaseConfigEnv();
|
|
447
|
+
adminEnvironmentInitialized = true;
|
|
488
448
|
}
|
|
489
449
|
|
|
490
450
|
function makeGoogleFirestoreContext(drivers, firestore) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
451
|
+
const context = firestoreContextFactory(drivers)(firestore);
|
|
452
|
+
context.drivers = drivers;
|
|
453
|
+
return context;
|
|
494
454
|
}
|
|
495
455
|
class GoogleCloudTestFirestoreInstance extends TestFirestoreInstance {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
456
|
+
constructor(drivers, firestore) {
|
|
457
|
+
super(makeGoogleFirestoreContext(drivers, firestore));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
class GoogleCloudTestFirestoreContextFixture extends TestFirestoreContextFixture {
|
|
499
461
|
}
|
|
500
|
-
class GoogleCloudTestFirestoreContextFixture extends TestFirestoreContextFixture {}
|
|
501
462
|
let COUNTER$1 = 0;
|
|
502
463
|
/**
|
|
503
464
|
* A TestContextBuilderFunction for building firestore test context factories using @google-cloud/firestore. This means SERVER TESTING ONLY. For client testing, look at @dereekb/firestore.
|
|
@@ -507,44 +468,43 @@ let COUNTER$1 = 0;
|
|
|
507
468
|
* If you need all of Firebase (firebase-admin library), look at adminFirebaseAdminTestBuilder() instead.
|
|
508
469
|
*/
|
|
509
470
|
const googleCloudTestFirestoreBuilder = testContextBuilder({
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
471
|
+
buildConfig: (input) => {
|
|
472
|
+
const config = {
|
|
473
|
+
host: input?.host ?? 'localhost',
|
|
474
|
+
port: input?.port ?? 0
|
|
475
|
+
};
|
|
476
|
+
return config;
|
|
477
|
+
},
|
|
478
|
+
buildFixture: () => new GoogleCloudTestFirestoreContextFixture(),
|
|
479
|
+
setupInstance: async (config) => {
|
|
480
|
+
const random = Math.floor(Math.random() * 10000);
|
|
481
|
+
const drivers = makeTestingFirestoreDrivers(googleCloudFirestoreDrivers());
|
|
482
|
+
const projectId = `test-${COUNTER$1++}-${Date.now()}-${random}`.substring(0, 30);
|
|
483
|
+
const firestore = new Firestore({
|
|
484
|
+
projectId,
|
|
485
|
+
host: config.host,
|
|
486
|
+
port: config.port,
|
|
487
|
+
maxIdleChannels: 0
|
|
488
|
+
});
|
|
489
|
+
return new GoogleCloudTestFirestoreInstance(drivers, firestore);
|
|
490
|
+
},
|
|
491
|
+
teardownInstance: async (instance, config) => {
|
|
492
|
+
await instance.firestore.terminate();
|
|
493
|
+
}
|
|
533
494
|
});
|
|
534
495
|
|
|
535
496
|
function makeGoogleFirebaseStorageContext(drivers, firebaseStorage, defaultBucketId) {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
context.drivers = drivers;
|
|
540
|
-
return context;
|
|
497
|
+
const context = firebaseStorageContextFactory(drivers)(firebaseStorage, { defaultBucketId });
|
|
498
|
+
context.drivers = drivers;
|
|
499
|
+
return context;
|
|
541
500
|
}
|
|
542
501
|
class GoogleCloudTestFirebaseStorageInstance extends TestFirebaseStorageInstance {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
502
|
+
constructor(drivers, firebaseStorage, defaultBucketId) {
|
|
503
|
+
super(makeGoogleFirebaseStorageContext(drivers, firebaseStorage, defaultBucketId));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
class GoogleCloudTestFirebaseStorageContextFixture extends TestFirebaseStorageContextFixture {
|
|
546
507
|
}
|
|
547
|
-
class GoogleCloudTestFirebaseStorageContextFixture extends TestFirebaseStorageContextFixture {}
|
|
548
508
|
let COUNTER = 0;
|
|
549
509
|
/**
|
|
550
510
|
* A TestContextBuilderFunction for building firebase storage test context factories using @google-cloud/storage. This means SERVER TESTING ONLY. For client testing, look at @dereekb/firestore.
|
|
@@ -554,136 +514,136 @@ let COUNTER = 0;
|
|
|
554
514
|
* If you need all of Firebase (firebase-admin library), look at adminFirebaseAdminTestBuilder() instead.
|
|
555
515
|
*/
|
|
556
516
|
const googleCloudTestFirebaseStorageBuilder = testContextBuilder({
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
517
|
+
buildConfig: (input) => {
|
|
518
|
+
const config = {
|
|
519
|
+
host: input?.host ?? 'localhost',
|
|
520
|
+
port: input?.port ?? 0
|
|
521
|
+
};
|
|
522
|
+
if (!config.port) {
|
|
523
|
+
throw new Error('Port for host is required.');
|
|
524
|
+
}
|
|
525
|
+
return config;
|
|
526
|
+
},
|
|
527
|
+
buildFixture: () => new GoogleCloudTestFirebaseStorageContextFixture(),
|
|
528
|
+
setupInstance: async (config) => {
|
|
529
|
+
const drivers = makeTestingFirebaseStorageDrivers(googleCloudFirebaseStorageDrivers());
|
|
530
|
+
const projectId = `firebase-storage-server-test-${new Date().getTime()}-${COUNTER++}`;
|
|
531
|
+
const firebaseStorage = new Storage({
|
|
532
|
+
projectId,
|
|
533
|
+
// ensure http:// is provided so the library doesn't default to/try https://
|
|
534
|
+
apiEndpoint: `http://${config.host}:${config.port}`
|
|
535
|
+
});
|
|
536
|
+
const defaultBucketId = projectId;
|
|
537
|
+
return new GoogleCloudTestFirebaseStorageInstance(drivers, firebaseStorage, defaultBucketId);
|
|
538
|
+
},
|
|
539
|
+
teardownInstance: async (instance, config) => {
|
|
540
|
+
// nothing to teardown
|
|
541
|
+
}
|
|
582
542
|
});
|
|
583
543
|
|
|
584
544
|
class FirebaseAdminTestContextFixture extends AbstractTestContextFixture {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
545
|
+
// MARK: FirebaseAdminTestContext (Forwarded)
|
|
546
|
+
get app() {
|
|
547
|
+
return this.instance.app;
|
|
548
|
+
}
|
|
549
|
+
get auth() {
|
|
550
|
+
return this.instance.auth;
|
|
551
|
+
}
|
|
552
|
+
get firestore() {
|
|
553
|
+
return this.instance.firestore;
|
|
554
|
+
}
|
|
555
|
+
get firestoreInstance() {
|
|
556
|
+
return this.instance.firestoreInstance;
|
|
557
|
+
}
|
|
558
|
+
get firestoreContext() {
|
|
559
|
+
return this.instance.firestoreContext;
|
|
560
|
+
}
|
|
561
|
+
get storage() {
|
|
562
|
+
return this.instance.storage;
|
|
563
|
+
}
|
|
564
|
+
get storageInstance() {
|
|
565
|
+
return this.instance.storageInstance;
|
|
566
|
+
}
|
|
567
|
+
get storageContext() {
|
|
568
|
+
return this.instance.storageContext;
|
|
569
|
+
}
|
|
570
|
+
get fnWrapper() {
|
|
571
|
+
return this.instance.fnWrapper;
|
|
572
|
+
}
|
|
613
573
|
}
|
|
614
574
|
// MARK: FirebaseAdminTestBuilder
|
|
615
575
|
class FirebaseAdminTestContextInstance {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
return new GoogleCloudTestFirestoreInstance(drivers, this.firestore);
|
|
576
|
+
app;
|
|
577
|
+
getTestFirestoreInstance = cachedGetter(() => {
|
|
578
|
+
const drivers = makeTestingFirestoreDrivers(googleCloudFirestoreDrivers());
|
|
579
|
+
return new GoogleCloudTestFirestoreInstance(drivers, this.firestore);
|
|
621
580
|
});
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
581
|
+
getTestFirebaseStorageInstance = cachedGetter(() => {
|
|
582
|
+
const drivers = makeTestingFirebaseStorageDrivers(googleCloudFirebaseStorageDrivers());
|
|
583
|
+
const defaultBucketId = this.app.options.storageBucket;
|
|
584
|
+
return new GoogleCloudTestFirebaseStorageInstance(drivers, this.storage, defaultBucketId);
|
|
626
585
|
});
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
586
|
+
constructor(app) {
|
|
587
|
+
this.app = app;
|
|
588
|
+
}
|
|
589
|
+
get auth() {
|
|
590
|
+
return this.app.auth();
|
|
591
|
+
}
|
|
592
|
+
get firestore() {
|
|
593
|
+
return this.app.firestore();
|
|
594
|
+
}
|
|
595
|
+
get firestoreInstance() {
|
|
596
|
+
return this.getTestFirestoreInstance();
|
|
597
|
+
}
|
|
598
|
+
get firestoreContext() {
|
|
599
|
+
return this.firestoreInstance.firestoreContext;
|
|
600
|
+
}
|
|
601
|
+
get storage() {
|
|
602
|
+
return googleCloudStorageFromFirebaseAdminStorage(this.app.storage());
|
|
603
|
+
}
|
|
604
|
+
get storageInstance() {
|
|
605
|
+
return this.getTestFirebaseStorageInstance();
|
|
606
|
+
}
|
|
607
|
+
get storageContext() {
|
|
608
|
+
return this.storageInstance.storageContext;
|
|
609
|
+
}
|
|
610
|
+
get fnWrapper() {
|
|
611
|
+
throw new Error('wrapCloudFunction is unsupported by this type.');
|
|
612
|
+
}
|
|
653
613
|
}
|
|
654
614
|
class AbstractFirebaseAdminTestContextInstanceChild {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
615
|
+
parent;
|
|
616
|
+
constructor(parent) {
|
|
617
|
+
this.parent = parent;
|
|
618
|
+
}
|
|
619
|
+
// MARK: FirebaseAdminTestContext (Forwarded)
|
|
620
|
+
get app() {
|
|
621
|
+
return this.parent.app;
|
|
622
|
+
}
|
|
623
|
+
get auth() {
|
|
624
|
+
return this.parent.auth;
|
|
625
|
+
}
|
|
626
|
+
get firestore() {
|
|
627
|
+
return this.parent.firestore;
|
|
628
|
+
}
|
|
629
|
+
get firestoreInstance() {
|
|
630
|
+
return this.parent.firestoreInstance;
|
|
631
|
+
}
|
|
632
|
+
get firestoreContext() {
|
|
633
|
+
return this.parent.firestoreContext;
|
|
634
|
+
}
|
|
635
|
+
get storage() {
|
|
636
|
+
return this.parent.storage;
|
|
637
|
+
}
|
|
638
|
+
get storageInstance() {
|
|
639
|
+
return this.parent.storageInstance;
|
|
640
|
+
}
|
|
641
|
+
get storageContext() {
|
|
642
|
+
return this.parent.storageContext;
|
|
643
|
+
}
|
|
644
|
+
get fnWrapper() {
|
|
645
|
+
return this.parent.fnWrapper;
|
|
646
|
+
}
|
|
687
647
|
}
|
|
688
648
|
/**
|
|
689
649
|
* A TestContextBuilderFunction for building firebase test context factories using firebase-admin.
|
|
@@ -691,28 +651,25 @@ class AbstractFirebaseAdminTestContextInstanceChild {
|
|
|
691
651
|
* This can be used to easily build a testing context that sets up RulesTestEnvironment for tests that sets itself up and tears itself down.
|
|
692
652
|
*/
|
|
693
653
|
const firebaseAdminTestBuilder = testContextBuilder({
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
654
|
+
buildConfig: (input) => {
|
|
655
|
+
const config = {
|
|
656
|
+
...input
|
|
657
|
+
};
|
|
658
|
+
return config;
|
|
659
|
+
},
|
|
660
|
+
buildFixture: () => new FirebaseAdminTestContextFixture(),
|
|
661
|
+
setupInstance: async (config) => {
|
|
662
|
+
if (!isAdminEnvironmentInitialized()) {
|
|
663
|
+
throw new Error('Call initFirebaseAdminTestEnvironment() from @dereekb/firebase-server was not called before using adminFirebaseTestBuilder().');
|
|
664
|
+
}
|
|
665
|
+
const projectId = generateNewProjectId();
|
|
666
|
+
const storageBucket = 'b-' + projectId;
|
|
667
|
+
const app = admin.initializeApp({ projectId, storageBucket });
|
|
668
|
+
return new FirebaseAdminTestContextInstance(app);
|
|
669
|
+
},
|
|
670
|
+
teardownInstance: async (instance, config) => {
|
|
671
|
+
await instance.app.delete(); // clean up the instance
|
|
704
672
|
}
|
|
705
|
-
const projectId = generateNewProjectId();
|
|
706
|
-
const storageBucket = 'b-' + projectId;
|
|
707
|
-
const app = admin.initializeApp({
|
|
708
|
-
projectId,
|
|
709
|
-
storageBucket
|
|
710
|
-
});
|
|
711
|
-
return new FirebaseAdminTestContextInstance(app);
|
|
712
|
-
},
|
|
713
|
-
teardownInstance: async (instance, config) => {
|
|
714
|
-
await instance.app.delete(); // clean up the instance
|
|
715
|
-
}
|
|
716
673
|
});
|
|
717
674
|
const firebaseAdminTestContextFactory = firebaseAdminTestBuilder({});
|
|
718
675
|
// MARK: Firestore Context
|
|
@@ -725,66 +682,66 @@ const firebaseAdminTestContextFactory = firebaseAdminTestBuilder({});
|
|
|
725
682
|
* @returns
|
|
726
683
|
*/
|
|
727
684
|
function firebaseAdminFirestoreContextFixture(factory) {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
685
|
+
return (buildTests) => {
|
|
686
|
+
factory((f) => firebaseAdminFirestoreContextWithFixture(f, buildTests));
|
|
687
|
+
};
|
|
731
688
|
}
|
|
732
689
|
function firebaseAdminFirestoreContextWithFixture(f, buildTests) {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
690
|
+
useTestContextFixture({
|
|
691
|
+
fixture: new TestFirestoreContextFixture(),
|
|
692
|
+
/**
|
|
693
|
+
* Build tests by passing the fixture to the testing functions.
|
|
694
|
+
*
|
|
695
|
+
* This will inject all tests and sub testing lifecycle items.
|
|
696
|
+
*/
|
|
697
|
+
buildTests,
|
|
698
|
+
initInstance: () => f.instance.getTestFirestoreInstance()
|
|
699
|
+
});
|
|
743
700
|
}
|
|
744
701
|
|
|
745
702
|
function firebaseAdminCloudFunctionWrapper(instance) {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
703
|
+
const wrapV1CloudFunction = (x) => {
|
|
704
|
+
return instance.wrap(x);
|
|
705
|
+
};
|
|
706
|
+
const wrapV2CallableRequest = (x) => {
|
|
707
|
+
const wrappedCloudFunction = instance.wrap(x);
|
|
708
|
+
// context is marked optional here to better match the gen 1 callable function signature
|
|
709
|
+
return async (data, context) => {
|
|
710
|
+
const request = {
|
|
711
|
+
...context,
|
|
712
|
+
data,
|
|
713
|
+
// NOTE: These will typically not be used/available as they are express.js properties that are not available or useful to the handlers
|
|
714
|
+
rawRequest: context?.rawRequest ?? {},
|
|
715
|
+
acceptsStreaming: false
|
|
716
|
+
};
|
|
717
|
+
const result = (await wrappedCloudFunction(request));
|
|
718
|
+
return result;
|
|
719
|
+
};
|
|
720
|
+
};
|
|
721
|
+
const wrapV2CloudFunction = (x) => {
|
|
722
|
+
return instance.wrap(x);
|
|
723
|
+
};
|
|
724
|
+
const wrapBlockingFunction = ((blockingFunction) => {
|
|
725
|
+
return instance.wrap(blockingFunction);
|
|
726
|
+
});
|
|
727
|
+
const wrapCloudFunction = (x) => {
|
|
728
|
+
return instance.wrap(x);
|
|
729
|
+
};
|
|
730
|
+
const wrapper = {
|
|
731
|
+
wrapV1CloudFunction,
|
|
732
|
+
wrapV2CloudFunction,
|
|
733
|
+
wrapV2CallableRequest,
|
|
734
|
+
wrapCallableRequest: wrapV2CallableRequest,
|
|
735
|
+
wrapBlockingFunction,
|
|
736
|
+
wrapCloudFunction
|
|
762
737
|
};
|
|
763
|
-
|
|
764
|
-
const wrapV2CloudFunction = x => {
|
|
765
|
-
return instance.wrap(x);
|
|
766
|
-
};
|
|
767
|
-
const wrapBlockingFunction = blockingFunction => {
|
|
768
|
-
return instance.wrap(blockingFunction);
|
|
769
|
-
};
|
|
770
|
-
const wrapCloudFunction = x => {
|
|
771
|
-
return instance.wrap(x);
|
|
772
|
-
};
|
|
773
|
-
const wrapper = {
|
|
774
|
-
wrapV1CloudFunction,
|
|
775
|
-
wrapV2CloudFunction,
|
|
776
|
-
wrapV2CallableRequest,
|
|
777
|
-
wrapCallableRequest: wrapV2CallableRequest,
|
|
778
|
-
wrapBlockingFunction,
|
|
779
|
-
wrapCloudFunction
|
|
780
|
-
};
|
|
781
|
-
return wrapper;
|
|
738
|
+
return wrapper;
|
|
782
739
|
}
|
|
783
740
|
function wrapCloudFunctionV1ForTests(wrapper, getter) {
|
|
784
|
-
|
|
741
|
+
return () => wrapper.wrapV1CloudFunction(getter());
|
|
785
742
|
}
|
|
786
743
|
function wrapCallableRequestForTests(wrapper, getter) {
|
|
787
|
-
|
|
744
|
+
return () => wrapper.wrapCallableRequest(getter());
|
|
788
745
|
}
|
|
789
746
|
|
|
790
747
|
// MARK: FirebaseAdminFunctionTestBuilder
|
|
@@ -794,68 +751,69 @@ let functionsInitialized = false;
|
|
|
794
751
|
*/
|
|
795
752
|
let firebaseFunctionsTestInstance;
|
|
796
753
|
function setupFirebaseAdminFunctionTestSingleton(reroll = false) {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
754
|
+
if (!isAdminEnvironmentInitialized()) {
|
|
755
|
+
throw new Error('initFirebaseAdminTestEnvironment() was not called.');
|
|
756
|
+
}
|
|
757
|
+
if (firebaseFunctionsTestInstance) {
|
|
758
|
+
firebaseFunctionsTestInstance.cleanup(); // destroy the old instance if it is up.
|
|
759
|
+
}
|
|
760
|
+
firebaseFunctionsTestInstance = functions();
|
|
761
|
+
if (reroll) {
|
|
762
|
+
rollNewGCloudProjectEnvironmentVariable();
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
applyFirebaseGCloudTestProjectIdToFirebaseConfigEnv();
|
|
766
|
+
}
|
|
767
|
+
functionsInitialized = true;
|
|
768
|
+
return firebaseFunctionsTestInstance;
|
|
811
769
|
}
|
|
812
770
|
function rerollFirebaseAdminFunctionTestSingleton() {
|
|
813
|
-
|
|
771
|
+
return setupFirebaseAdminFunctionTestSingleton(true);
|
|
814
772
|
}
|
|
815
773
|
class FirebaseAdminFunctionTestContextFixture extends AbstractTestContextFixture {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
774
|
+
// MARK: FirebaseAdminTestContext (Forwarded)
|
|
775
|
+
get app() {
|
|
776
|
+
return this.instance.app;
|
|
777
|
+
}
|
|
778
|
+
get auth() {
|
|
779
|
+
return this.instance.auth;
|
|
780
|
+
}
|
|
781
|
+
get firestore() {
|
|
782
|
+
return this.instance.firestore;
|
|
783
|
+
}
|
|
784
|
+
get firestoreInstance() {
|
|
785
|
+
return this.instance.firestoreInstance;
|
|
786
|
+
}
|
|
787
|
+
get firestoreContext() {
|
|
788
|
+
return this.instance.firestoreContext;
|
|
789
|
+
}
|
|
790
|
+
get storage() {
|
|
791
|
+
return this.instance.storage;
|
|
792
|
+
}
|
|
793
|
+
get storageInstance() {
|
|
794
|
+
return this.instance.storageInstance;
|
|
795
|
+
}
|
|
796
|
+
get storageContext() {
|
|
797
|
+
return this.instance.storageContext;
|
|
798
|
+
}
|
|
799
|
+
get fnWrapper() {
|
|
800
|
+
return this.instance.fnWrapper;
|
|
801
|
+
}
|
|
844
802
|
}
|
|
845
803
|
class FirebaseAdminFunctionTestContextInstance extends FirebaseAdminTestContextInstance {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
804
|
+
instance;
|
|
805
|
+
_fnWrapper = cachedGetter(() => firebaseAdminCloudFunctionWrapper(this.instance));
|
|
806
|
+
constructor(instance, app) {
|
|
807
|
+
super(app);
|
|
808
|
+
this.instance = instance;
|
|
809
|
+
}
|
|
810
|
+
get fnWrapper() {
|
|
811
|
+
return this._fnWrapper();
|
|
812
|
+
}
|
|
855
813
|
}
|
|
856
814
|
let DEFAULT_FIREBASE_ADMIN_FUNCTION_TEST_USE_FUNCTION_SINGLETON_CONTEXT = false;
|
|
857
815
|
function setDefaultFirebaseAdminFunctionTestUseFunctionSingleton(use) {
|
|
858
|
-
|
|
816
|
+
DEFAULT_FIREBASE_ADMIN_FUNCTION_TEST_USE_FUNCTION_SINGLETON_CONTEXT = use;
|
|
859
817
|
}
|
|
860
818
|
/**
|
|
861
819
|
* A TestContextBuilderFunction for building firebase test context factories using firebase-admin.
|
|
@@ -863,320 +821,296 @@ function setDefaultFirebaseAdminFunctionTestUseFunctionSingleton(use) {
|
|
|
863
821
|
* This can be used to easily build a testing context that sets up RulesTestEnvironment for tests that sets itself up and tears itself down.
|
|
864
822
|
*/
|
|
865
823
|
const firebaseAdminFunctionTestBuilder = testContextBuilder({
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
}
|
|
903
|
-
}
|
|
824
|
+
buildConfig: (input) => {
|
|
825
|
+
const config = {
|
|
826
|
+
...input,
|
|
827
|
+
useFunctionSingletonContext: input?.useFunctionSingletonContext ?? DEFAULT_FIREBASE_ADMIN_FUNCTION_TEST_USE_FUNCTION_SINGLETON_CONTEXT
|
|
828
|
+
};
|
|
829
|
+
return config;
|
|
830
|
+
},
|
|
831
|
+
buildFixture: () => new FirebaseAdminFunctionTestContextFixture(),
|
|
832
|
+
setupInstance: async (config) => {
|
|
833
|
+
if (!isAdminEnvironmentInitialized()) {
|
|
834
|
+
throw new Error('initFirebaseAdminTestEnvironment() (in @dereekb/firebase-server package) was not called before using adminFirebaseTestBuilder().');
|
|
835
|
+
}
|
|
836
|
+
if (config.useFunctionSingletonContext) {
|
|
837
|
+
if (!functionsInitialized) {
|
|
838
|
+
throw new Error('Call setupFirebaseAdminFunctionTestSingleton() (in @dereekb/firebase-server package) if using functions in a singleton context (useFunctionSingletonContext = true/undefined).');
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
else if (config.useFunctionSingletonContext === false) {
|
|
842
|
+
firebaseFunctionsTestInstance = rerollFirebaseAdminFunctionTestSingleton();
|
|
843
|
+
}
|
|
844
|
+
const projectId = getGCloudTestProjectId();
|
|
845
|
+
const storageBucket = 'b-' + projectId;
|
|
846
|
+
const app = admin.initializeApp({ projectId, storageBucket });
|
|
847
|
+
return new FirebaseAdminFunctionTestContextInstance(firebaseFunctionsTestInstance, app);
|
|
848
|
+
},
|
|
849
|
+
teardownInstance: async (instance, config) => {
|
|
850
|
+
if (config.useFunctionSingletonContext === false) {
|
|
851
|
+
try {
|
|
852
|
+
await instance.app.delete(); // will be called in cleanup
|
|
853
|
+
firebaseFunctionsTestInstance.cleanup();
|
|
854
|
+
}
|
|
855
|
+
catch (e) {
|
|
856
|
+
// do nothing
|
|
857
|
+
}
|
|
858
|
+
firebaseFunctionsTestInstance = undefined;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
904
861
|
});
|
|
905
862
|
const firebaseAdminFunctionTestContextFactory = firebaseAdminFunctionTestBuilder({});
|
|
906
863
|
|
|
907
864
|
class FirebaseAdminNestTestContextFixture extends AbstractChildTestContextFixture {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
865
|
+
// MARK: Forwarded
|
|
866
|
+
get nest() {
|
|
867
|
+
return this.instance.nest;
|
|
868
|
+
}
|
|
869
|
+
get nestAppPromiseGetter() {
|
|
870
|
+
return this.instance.nestAppPromiseGetter;
|
|
871
|
+
}
|
|
872
|
+
get(typeOrToken, options) {
|
|
873
|
+
return this.instance.get(typeOrToken, options);
|
|
874
|
+
}
|
|
918
875
|
}
|
|
919
876
|
class FirebaseAdminNestTestContextInstance extends AbstractFirebaseAdminTestContextInstanceChild {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
877
|
+
nest;
|
|
878
|
+
nestAppPromiseGetter = () => Promise.resolve(this.nest);
|
|
879
|
+
constructor(parent, nest) {
|
|
880
|
+
super(parent);
|
|
881
|
+
this.nest = nest;
|
|
882
|
+
}
|
|
883
|
+
get(typeOrToken, options) {
|
|
884
|
+
return options ? this.nest.get(typeOrToken, options) : this.nest.get(typeOrToken);
|
|
885
|
+
}
|
|
929
886
|
}
|
|
930
887
|
function firebaseAdminNestContextFixture(config, factory) {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
888
|
+
return (buildTests) => {
|
|
889
|
+
factory((f) => firebaseAdminNestContextWithFixture(config, f, buildTests));
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
class FirebaseAdminNestRootModule {
|
|
934
893
|
}
|
|
935
|
-
class FirebaseAdminNestRootModule {}
|
|
936
894
|
function firebaseAdminNestContextWithFixture(config, f, buildTests) {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
895
|
+
const { nestModules, makeProviders = () => [], forceStorageBucket = false, defaultStorageBucket: inputDefaultStorageBucket, envConfig, injectFirebaseServerAppTokenProvider, injectServerEnvServiceProvider, makeFixture = (parent) => new FirebaseAdminNestTestContextFixture(parent), makeInstance = (instance, nest) => new FirebaseAdminNestTestContextInstance(instance, nest), initInstance } = config;
|
|
896
|
+
useTestContextFixture({
|
|
897
|
+
fixture: makeFixture(f),
|
|
898
|
+
/**
|
|
899
|
+
* Build tests by passing the fixture to the testing functions.
|
|
900
|
+
*
|
|
901
|
+
* This will inject all tests and sub Jest lifecycle items.
|
|
902
|
+
*/
|
|
903
|
+
buildTests,
|
|
904
|
+
initInstance: async () => {
|
|
905
|
+
const imports = asArray(nestModules);
|
|
906
|
+
const providers = makeProviders(f.instance) ?? [];
|
|
907
|
+
const defaultStorageBucket = inputDefaultStorageBucket ?? f.instance.app.options.storageBucket;
|
|
908
|
+
// Inject the serverEnvTokenProvider and optionally the FirebaseServerEnvService
|
|
909
|
+
if (injectServerEnvServiceProvider !== false || envConfig != null) {
|
|
910
|
+
providers.push(serverEnvTokenProvider(envConfig || { production: false }));
|
|
911
|
+
if (injectServerEnvServiceProvider !== false) {
|
|
912
|
+
providers.push({
|
|
913
|
+
provide: FirebaseServerEnvService,
|
|
914
|
+
useClass: DefaultFirebaseServerEnvService
|
|
915
|
+
}, {
|
|
916
|
+
provide: ServerEnvironmentService,
|
|
917
|
+
useExisting: FirebaseServerEnvService
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
// Inject the firebaseServerAppTokenProvider
|
|
922
|
+
if (injectFirebaseServerAppTokenProvider) {
|
|
923
|
+
providers.push(firebaseServerAppTokenProvider(asGetter(f.instance.app)));
|
|
924
|
+
}
|
|
925
|
+
if (defaultStorageBucket) {
|
|
926
|
+
providers.push(firebaseServerStorageDefaultBucketIdTokenProvider({
|
|
927
|
+
defaultBucketId: defaultStorageBucket,
|
|
928
|
+
forceBucket: forceStorageBucket
|
|
929
|
+
}));
|
|
930
|
+
}
|
|
931
|
+
const rootModule = {
|
|
932
|
+
module: FirebaseAdminNestRootModule,
|
|
933
|
+
providers,
|
|
934
|
+
exports: providers,
|
|
935
|
+
global: true
|
|
936
|
+
};
|
|
937
|
+
const builder = Test.createTestingModule({
|
|
938
|
+
imports: [rootModule, ...imports]
|
|
939
|
+
});
|
|
940
|
+
const nest = await builder.compile();
|
|
941
|
+
const instance = makeInstance(f.instance, nest);
|
|
942
|
+
if (initInstance) {
|
|
943
|
+
await initInstance(instance);
|
|
944
|
+
}
|
|
945
|
+
return instance;
|
|
946
|
+
},
|
|
947
|
+
destroyInstance: async (instance) => {
|
|
948
|
+
await instance.nest.close();
|
|
974
949
|
}
|
|
975
|
-
|
|
976
|
-
// Inject the firebaseServerAppTokenProvider
|
|
977
|
-
if (injectFirebaseServerAppTokenProvider) {
|
|
978
|
-
providers.push(firebaseServerAppTokenProvider(asGetter(f.instance.app)));
|
|
979
|
-
}
|
|
980
|
-
if (defaultStorageBucket) {
|
|
981
|
-
providers.push(firebaseServerStorageDefaultBucketIdTokenProvider({
|
|
982
|
-
defaultBucketId: defaultStorageBucket,
|
|
983
|
-
forceBucket: forceStorageBucket
|
|
984
|
-
}));
|
|
985
|
-
}
|
|
986
|
-
const rootModule = {
|
|
987
|
-
module: FirebaseAdminNestRootModule,
|
|
988
|
-
providers,
|
|
989
|
-
exports: providers,
|
|
990
|
-
global: true
|
|
991
|
-
};
|
|
992
|
-
const builder = Test.createTestingModule({
|
|
993
|
-
imports: [rootModule, ...imports]
|
|
994
|
-
});
|
|
995
|
-
const nest = await builder.compile();
|
|
996
|
-
const instance = makeInstance(f.instance, nest);
|
|
997
|
-
if (initInstance) {
|
|
998
|
-
await initInstance(instance);
|
|
999
|
-
}
|
|
1000
|
-
return instance;
|
|
1001
|
-
},
|
|
1002
|
-
destroyInstance: async instance => {
|
|
1003
|
-
await instance.nest.close();
|
|
1004
|
-
}
|
|
1005
|
-
});
|
|
950
|
+
});
|
|
1006
951
|
}
|
|
1007
952
|
function firebaseAdminNestContextFactory(config) {
|
|
1008
|
-
|
|
953
|
+
return firebaseAdminNestContextFixture(config, firebaseAdminTestContextFactory);
|
|
1009
954
|
}
|
|
1010
955
|
|
|
1011
956
|
function wrapCloudFunctionForNestTestsGetter(wrapper, fn) {
|
|
1012
|
-
|
|
957
|
+
return wrapCloudFunctionV1ForTests(wrapper.fnWrapper, () => fn(wrapper.nestAppPromiseGetter));
|
|
1013
958
|
}
|
|
1014
959
|
function wrapCallableRequestForNestTestsGetter(wrapper, fn) {
|
|
1015
|
-
|
|
960
|
+
return wrapCallableRequestForTests(wrapper.fnWrapper, () => fn(wrapper.nestAppPromiseGetter));
|
|
1016
961
|
}
|
|
1017
962
|
class FirebaseAdminFunctionNestTestContextFixture extends FirebaseAdminNestTestContextFixture {
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
963
|
+
// MARK: FirebaseAdminTestContext (Forwarded)
|
|
964
|
+
wrapCloudFunctionForNestTests(fn) {
|
|
965
|
+
return this.wrapCloudFunctionForNestTestsGetter(fn)();
|
|
966
|
+
}
|
|
967
|
+
wrapCloudFunctionForNestTestsGetter(fn) {
|
|
968
|
+
return wrapCloudFunctionForNestTestsGetter(this, fn);
|
|
969
|
+
}
|
|
970
|
+
wrapCallableRequestForNestTests(fn) {
|
|
971
|
+
return this.wrapCallableRequestForNestTestsGetter(fn)();
|
|
972
|
+
}
|
|
973
|
+
wrapCallableRequestForNestTestsGetter(fn) {
|
|
974
|
+
return wrapCallableRequestForNestTestsGetter(this, fn);
|
|
975
|
+
}
|
|
976
|
+
// MARK: FirebaseAdminCloudFunctionWrapperSource
|
|
977
|
+
get fnWrapper() {
|
|
978
|
+
return this.parent.instance.fnWrapper;
|
|
979
|
+
}
|
|
1035
980
|
}
|
|
1036
981
|
class FirebaseAdminFunctionNestTestContextInstance extends FirebaseAdminNestTestContextInstance {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
982
|
+
// MARK: FirebaseAdminTestContext (Forwarded)
|
|
983
|
+
get fnWrapper() {
|
|
984
|
+
return this.parent.fnWrapper;
|
|
985
|
+
}
|
|
1041
986
|
}
|
|
1042
987
|
function firebaseAdminFunctionNestContextFixture(config, factory) {
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
988
|
+
return (buildTests) => {
|
|
989
|
+
factory((f) => firebaseAdminFunctionNestContextWithFixture(config, f, buildTests));
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
class FirebaseAdminFunctionNestRootModule {
|
|
1046
993
|
}
|
|
1047
|
-
class FirebaseAdminFunctionNestRootModule {}
|
|
1048
994
|
function firebaseAdminFunctionNestContextWithFixture(config, f, buildTests) {
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
995
|
+
const mergedConfig = {
|
|
996
|
+
makeFixture: (parent) => new FirebaseAdminFunctionNestTestContextFixture(parent),
|
|
997
|
+
makeInstance: (instance, nest) => new FirebaseAdminFunctionNestTestContextInstance(instance, nest),
|
|
998
|
+
...config
|
|
999
|
+
};
|
|
1000
|
+
return firebaseAdminNestContextWithFixture(mergedConfig, f, buildTests);
|
|
1055
1001
|
}
|
|
1056
1002
|
function firebaseAdminFunctionNestContextFactory(config) {
|
|
1057
|
-
|
|
1003
|
+
return firebaseAdminFunctionNestContextFixture(config, firebaseAdminFunctionTestContextFactory);
|
|
1058
1004
|
}
|
|
1059
1005
|
|
|
1060
1006
|
const CallableRequestTestMultipleFixtureSuffix = 'WrappedFn';
|
|
1061
1007
|
function isCallableRequestTestSingleConfig(config) {
|
|
1062
|
-
|
|
1063
|
-
|
|
1008
|
+
const isSingle = Boolean(config.fn);
|
|
1009
|
+
return isSingle;
|
|
1064
1010
|
}
|
|
1065
1011
|
function callableRequestTest(config, buildTests) {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
});
|
|
1087
|
-
useTestFunctionMapFixture({
|
|
1088
|
-
fns
|
|
1089
|
-
}, buildTests);
|
|
1090
|
-
}
|
|
1012
|
+
if (isCallableRequestTestSingleConfig(config)) {
|
|
1013
|
+
const { f, fn } = config;
|
|
1014
|
+
useTestFunctionFixture({
|
|
1015
|
+
fn: () => {
|
|
1016
|
+
const x = wrapCallableRequestForNestTestsGetter(f, fn)();
|
|
1017
|
+
return x;
|
|
1018
|
+
}
|
|
1019
|
+
}, buildTests);
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
const { f, fns: inputFns } = config;
|
|
1023
|
+
const mappedFns = mapObjectMap(inputFns, (fn) => () => wrapCallableRequestForNestTestsGetter(f, fn)());
|
|
1024
|
+
const fns = {};
|
|
1025
|
+
Object.keys(mappedFns).forEach((key) => {
|
|
1026
|
+
fns[`${key}${CallableRequestTestMultipleFixtureSuffix}`] = mappedFns[key];
|
|
1027
|
+
});
|
|
1028
|
+
useTestFunctionMapFixture({
|
|
1029
|
+
fns
|
|
1030
|
+
}, buildTests);
|
|
1031
|
+
}
|
|
1091
1032
|
}
|
|
1092
1033
|
function describeCallableRequestTest(label, config, buildTests) {
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1034
|
+
describe(label, () => {
|
|
1035
|
+
callableRequestTest(config, buildTests);
|
|
1036
|
+
});
|
|
1096
1037
|
}
|
|
1097
1038
|
|
|
1098
1039
|
function isCloudFunctionTestSingleConfig(config) {
|
|
1099
|
-
|
|
1100
|
-
|
|
1040
|
+
const isSingle = Boolean(config.fn);
|
|
1041
|
+
return isSingle;
|
|
1101
1042
|
}
|
|
1102
1043
|
function cloudFunctionTest(config, buildTests) {
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
});
|
|
1124
|
-
useTestFunctionMapFixture({
|
|
1125
|
-
fns
|
|
1126
|
-
}, buildTests);
|
|
1127
|
-
}
|
|
1044
|
+
if (isCloudFunctionTestSingleConfig(config)) {
|
|
1045
|
+
const { f, fn } = config;
|
|
1046
|
+
useTestFunctionFixture({
|
|
1047
|
+
fn: () => {
|
|
1048
|
+
const x = wrapCloudFunctionForNestTestsGetter(f, fn)();
|
|
1049
|
+
return x;
|
|
1050
|
+
}
|
|
1051
|
+
}, buildTests);
|
|
1052
|
+
}
|
|
1053
|
+
else {
|
|
1054
|
+
const { f, fns: inputFns } = config;
|
|
1055
|
+
const mappedFns = mapObjectMap(inputFns, (fn) => () => wrapCloudFunctionForNestTestsGetter(f, fn)());
|
|
1056
|
+
const fns = {};
|
|
1057
|
+
Object.keys(mappedFns).forEach((key) => {
|
|
1058
|
+
fns[`${key}CloudFn`] = mappedFns[key];
|
|
1059
|
+
});
|
|
1060
|
+
useTestFunctionMapFixture({
|
|
1061
|
+
fns
|
|
1062
|
+
}, buildTests);
|
|
1063
|
+
}
|
|
1128
1064
|
}
|
|
1129
1065
|
function describeCloudFunctionTest(label, config, buildTests) {
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1066
|
+
describe(label, () => {
|
|
1067
|
+
cloudFunctionTest(config, buildTests);
|
|
1068
|
+
});
|
|
1133
1069
|
}
|
|
1134
1070
|
|
|
1135
1071
|
function initFirebaseServerAdminTestEnvironment() {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1072
|
+
initFirebaseAdminTestEnvironment({
|
|
1073
|
+
emulators: {
|
|
1074
|
+
auth: '0.0.0.0:9903',
|
|
1075
|
+
firestore: '0.0.0.0:9904',
|
|
1076
|
+
storage: '0.0.0.0:9906'
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1143
1079
|
}
|
|
1144
1080
|
function describeFirestoreTest(s) {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1081
|
+
let collection;
|
|
1082
|
+
beforeEach(() => {
|
|
1083
|
+
collection = mockItemFirestoreCollection(s.firestoreContext);
|
|
1084
|
+
});
|
|
1085
|
+
describe('firestore', () => {
|
|
1086
|
+
it('should interact with the firestore.', async () => {
|
|
1087
|
+
const document = collection.documentAccessor().newDocument();
|
|
1088
|
+
const setData = {
|
|
1089
|
+
value: 'a',
|
|
1090
|
+
test: true
|
|
1091
|
+
};
|
|
1092
|
+
await document.accessor.set(setData);
|
|
1093
|
+
const exists = await document.accessor.exists();
|
|
1094
|
+
expect(exists).toBe(true);
|
|
1095
|
+
const snapshot = await document.accessor.get();
|
|
1096
|
+
const data = snapshot.data();
|
|
1097
|
+
expect(data).toBeDefined();
|
|
1098
|
+
});
|
|
1162
1099
|
});
|
|
1163
|
-
});
|
|
1164
1100
|
}
|
|
1165
1101
|
|
|
1166
1102
|
/**
|
|
1167
1103
|
* Error thrown when the error type was different than the expected type.
|
|
1168
1104
|
*/
|
|
1169
1105
|
class ExpectedHttpErrorWithSpecificServerErrorCode extends BaseError {
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
this.expectedErrorCode = expectedErrorCode;
|
|
1179
|
-
}
|
|
1106
|
+
httpError;
|
|
1107
|
+
expectedErrorCode;
|
|
1108
|
+
constructor(httpError, expectedErrorCode) {
|
|
1109
|
+
const { code } = httpError.details;
|
|
1110
|
+
super(`Expected HttpError with an error code of "${expectedErrorCode}", but recieved "${code}" instead.`);
|
|
1111
|
+
this.httpError = httpError;
|
|
1112
|
+
this.expectedErrorCode = expectedErrorCode;
|
|
1113
|
+
}
|
|
1180
1114
|
}
|
|
1181
1115
|
/**
|
|
1182
1116
|
* Creates a ExpectFailAssertionFunction that asserts the encountered error is of the expected type using the instanceof keyword.
|
|
@@ -1188,25 +1122,25 @@ class ExpectedHttpErrorWithSpecificServerErrorCode extends BaseError {
|
|
|
1188
1122
|
* @returns
|
|
1189
1123
|
*/
|
|
1190
1124
|
function expectFailAssertHttpErrorServerErrorCode(expectedCode) {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
};
|
|
1125
|
+
return (error) => {
|
|
1126
|
+
if (error instanceof HttpsError) {
|
|
1127
|
+
const { code } = error.details;
|
|
1128
|
+
if (code !== expectedCode) {
|
|
1129
|
+
throw new ExpectedHttpErrorWithSpecificServerErrorCode(error, expectedCode);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
throw new ExpectedErrorOfSpecificTypeError(error, HttpsError);
|
|
1134
|
+
}
|
|
1135
|
+
return true;
|
|
1136
|
+
};
|
|
1204
1137
|
}
|
|
1205
1138
|
// MARK: Compat
|
|
1206
1139
|
/**
|
|
1207
1140
|
* @deprecated Use ExpectedHttpErrorWithSpecificServerErrorCode from shared instead. This is kept for backwards compatibility.
|
|
1208
1141
|
*/
|
|
1209
|
-
class JestExpectedHttpErrorWithSpecificServerErrorCode extends ExpectedHttpErrorWithSpecificServerErrorCode {
|
|
1142
|
+
class JestExpectedHttpErrorWithSpecificServerErrorCode extends ExpectedHttpErrorWithSpecificServerErrorCode {
|
|
1143
|
+
}
|
|
1210
1144
|
/**
|
|
1211
1145
|
* @deprecated Use expectFailAssertHttpErrorServerErrorCode from shared instead. This is kept for backwards compatibility.
|
|
1212
1146
|
*/
|
|
@@ -1218,8 +1152,8 @@ const jestExpectFailAssertHttpErrorServerErrorCode = expectFailAssertHttpErrorSe
|
|
|
1218
1152
|
* Host of localhost, port 9904
|
|
1219
1153
|
*/
|
|
1220
1154
|
const adminFirestoreFactory = googleCloudTestFirestoreBuilder({
|
|
1221
|
-
|
|
1222
|
-
|
|
1155
|
+
host: 'localhost',
|
|
1156
|
+
port: 9904
|
|
1223
1157
|
});
|
|
1224
1158
|
/**
|
|
1225
1159
|
* Convenience mock instance for tests within an authorized context.
|
|
@@ -1234,8 +1168,8 @@ const dbxComponentsAdminTestWithMockItemCollection = testWithMockItemCollectionF
|
|
|
1234
1168
|
* Host of localhost, port 9906
|
|
1235
1169
|
*/
|
|
1236
1170
|
const adminFirebaseStorageFactory = googleCloudTestFirebaseStorageBuilder({
|
|
1237
|
-
|
|
1238
|
-
|
|
1171
|
+
host: '0.0.0.0',
|
|
1172
|
+
port: 9906
|
|
1239
1173
|
});
|
|
1240
1174
|
/**
|
|
1241
1175
|
* Convenience mock instance for tests within an authorized context.
|