@firebase/rules-unit-testing 3.0.4 → 4.0.0
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/dist/esm/index.esm.js +5 -6
- package/dist/esm/index.esm.js.map +1 -1
- package/dist/esm/src/impl/discovery.d.ts +1 -2
- package/dist/esm/src/public_types/index.d.ts +1 -1
- package/dist/index.cjs.js +243 -432
- package/dist/index.cjs.js.map +1 -1
- package/dist/src/impl/discovery.d.ts +1 -2
- package/dist/src/public_types/index.d.ts +1 -1
- package/package.json +3 -7
package/dist/index.cjs.js
CHANGED
|
@@ -5,78 +5,12 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
require('firebase/compat/database');
|
|
6
6
|
require('firebase/compat/firestore');
|
|
7
7
|
require('firebase/compat/storage');
|
|
8
|
-
var fetch = require('node-fetch');
|
|
9
8
|
var firebase = require('firebase/compat/app');
|
|
10
9
|
|
|
11
10
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
12
11
|
|
|
13
|
-
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
14
12
|
var firebase__default = /*#__PURE__*/_interopDefaultLegacy(firebase);
|
|
15
13
|
|
|
16
|
-
/******************************************************************************
|
|
17
|
-
Copyright (c) Microsoft Corporation.
|
|
18
|
-
|
|
19
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
20
|
-
purpose with or without fee is hereby granted.
|
|
21
|
-
|
|
22
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
23
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
24
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
25
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
26
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
27
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
28
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
29
|
-
***************************************************************************** */
|
|
30
|
-
|
|
31
|
-
var __assign = function() {
|
|
32
|
-
__assign = Object.assign || function __assign(t) {
|
|
33
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
34
|
-
s = arguments[i];
|
|
35
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
|
36
|
-
}
|
|
37
|
-
return t;
|
|
38
|
-
};
|
|
39
|
-
return __assign.apply(this, arguments);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
function __awaiter(thisArg, _arguments, P, generator) {
|
|
43
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
44
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
45
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
46
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
47
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
48
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function __generator(thisArg, body) {
|
|
53
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
54
|
-
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
55
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
56
|
-
function step(op) {
|
|
57
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
58
|
-
while (_) try {
|
|
59
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
60
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
61
|
-
switch (op[0]) {
|
|
62
|
-
case 0: case 1: t = op; break;
|
|
63
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
64
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
65
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
66
|
-
default:
|
|
67
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
68
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
69
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
70
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
71
|
-
if (t[2]) _.ops.pop();
|
|
72
|
-
_.trys.pop(); continue;
|
|
73
|
-
}
|
|
74
|
-
op = body.call(thisArg, _);
|
|
75
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
76
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
14
|
/**
|
|
81
15
|
* @license
|
|
82
16
|
* Copyright 2021 Google LLC
|
|
@@ -118,15 +52,15 @@ function fixHostname(host, fallbackHost) {
|
|
|
118
52
|
*/
|
|
119
53
|
function makeUrl(hostAndPort, path) {
|
|
120
54
|
if (typeof hostAndPort === 'object') {
|
|
121
|
-
|
|
55
|
+
const { host, port } = hostAndPort;
|
|
122
56
|
if (host.includes(':')) {
|
|
123
|
-
hostAndPort =
|
|
57
|
+
hostAndPort = `[${host}]:${port}`;
|
|
124
58
|
}
|
|
125
59
|
else {
|
|
126
|
-
hostAndPort =
|
|
60
|
+
hostAndPort = `${host}:${port}`;
|
|
127
61
|
}
|
|
128
62
|
}
|
|
129
|
-
|
|
63
|
+
const url = new URL(`http://${hostAndPort}/`);
|
|
130
64
|
url.pathname = path;
|
|
131
65
|
return url;
|
|
132
66
|
}
|
|
@@ -153,50 +87,38 @@ function makeUrl(hostAndPort, path) {
|
|
|
153
87
|
* @param hub the host and port where the Emulator Hub is running
|
|
154
88
|
* @private
|
|
155
89
|
*/
|
|
156
|
-
function discoverEmulators(hub, fetch) {
|
|
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
|
-
if (data.hub) {
|
|
191
|
-
emulators.hub = {
|
|
192
|
-
host: data.hub.host,
|
|
193
|
-
port: data.hub.port
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
return [2 /*return*/, emulators];
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
});
|
|
90
|
+
async function discoverEmulators(hub, fetchImpl = fetch) {
|
|
91
|
+
const res = await fetchImpl(makeUrl(hub, '/emulators'));
|
|
92
|
+
if (!res.ok) {
|
|
93
|
+
throw new Error(`HTTP Error ${res.status} when attempting to reach Emulator Hub at ${res.url}, are you sure it is running?`);
|
|
94
|
+
}
|
|
95
|
+
const emulators = {};
|
|
96
|
+
const data = await res.json();
|
|
97
|
+
if (data.database) {
|
|
98
|
+
emulators.database = {
|
|
99
|
+
host: data.database.host,
|
|
100
|
+
port: data.database.port
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (data.firestore) {
|
|
104
|
+
emulators.firestore = {
|
|
105
|
+
host: data.firestore.host,
|
|
106
|
+
port: data.firestore.port
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (data.storage) {
|
|
110
|
+
emulators.storage = {
|
|
111
|
+
host: data.storage.host,
|
|
112
|
+
port: data.storage.port
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (data.hub) {
|
|
116
|
+
emulators.hub = {
|
|
117
|
+
host: data.hub.host,
|
|
118
|
+
port: data.hub.port
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return emulators;
|
|
200
122
|
}
|
|
201
123
|
/**
|
|
202
124
|
* @private
|
|
@@ -204,14 +126,14 @@ function discoverEmulators(hub, fetch) {
|
|
|
204
126
|
function getEmulatorHostAndPort(emulator, conf, discovered) {
|
|
205
127
|
var _a, _b;
|
|
206
128
|
if (conf && ('host' in conf || 'port' in conf)) {
|
|
207
|
-
|
|
129
|
+
const { host, port } = conf;
|
|
208
130
|
if (host || port) {
|
|
209
131
|
if (!host || !port) {
|
|
210
|
-
throw new Error(
|
|
132
|
+
throw new Error(`Invalid configuration ${emulator}.host=${host} and ${emulator}.port=${port}. ` +
|
|
211
133
|
'If either parameter is supplied, both must be defined.');
|
|
212
134
|
}
|
|
213
135
|
if (discovered && !discovered[emulator]) {
|
|
214
|
-
console.warn(
|
|
136
|
+
console.warn(`Warning: config for the ${emulator} emulator is specified, but the Emulator hub ` +
|
|
215
137
|
'reports it as not running. This may lead to errors such as connection refused.');
|
|
216
138
|
}
|
|
217
139
|
return {
|
|
@@ -220,12 +142,12 @@ function getEmulatorHostAndPort(emulator, conf, discovered) {
|
|
|
220
142
|
};
|
|
221
143
|
}
|
|
222
144
|
}
|
|
223
|
-
|
|
224
|
-
|
|
145
|
+
const envVar = EMULATOR_HOST_ENV_VARS[emulator];
|
|
146
|
+
const fallback = (discovered === null || discovered === void 0 ? void 0 : discovered[emulator]) || emulatorFromEnvVar(envVar);
|
|
225
147
|
if (fallback) {
|
|
226
148
|
if (discovered && !discovered[emulator]) {
|
|
227
|
-
console.warn(
|
|
228
|
-
|
|
149
|
+
console.warn(`Warning: the environment variable ${envVar} is set, but the Emulator hub reports the ` +
|
|
150
|
+
`${emulator} emulator as not running. This may lead to errors such as connection refused.`);
|
|
229
151
|
}
|
|
230
152
|
return {
|
|
231
153
|
host: fixHostname(fallback.host, (_b = discovered === null || discovered === void 0 ? void 0 : discovered.hub) === null || _b === void 0 ? void 0 : _b.host),
|
|
@@ -234,30 +156,30 @@ function getEmulatorHostAndPort(emulator, conf, discovered) {
|
|
|
234
156
|
}
|
|
235
157
|
}
|
|
236
158
|
// Visible for testing.
|
|
237
|
-
|
|
159
|
+
const EMULATOR_HOST_ENV_VARS = {
|
|
238
160
|
'database': 'FIREBASE_DATABASE_EMULATOR_HOST',
|
|
239
161
|
'firestore': 'FIRESTORE_EMULATOR_HOST',
|
|
240
162
|
'hub': 'FIREBASE_EMULATOR_HUB',
|
|
241
163
|
'storage': 'FIREBASE_STORAGE_EMULATOR_HOST'
|
|
242
164
|
};
|
|
243
165
|
function emulatorFromEnvVar(envVar) {
|
|
244
|
-
|
|
166
|
+
const hostAndPort = process.env[envVar];
|
|
245
167
|
if (!hostAndPort) {
|
|
246
168
|
return undefined;
|
|
247
169
|
}
|
|
248
|
-
|
|
170
|
+
let parsed;
|
|
249
171
|
try {
|
|
250
|
-
parsed = new URL(
|
|
172
|
+
parsed = new URL(`http://${hostAndPort}`);
|
|
251
173
|
}
|
|
252
174
|
catch (_a) {
|
|
253
|
-
throw new Error(
|
|
175
|
+
throw new Error(`Invalid format in environment variable ${envVar}=${hostAndPort} (expected host:port)`);
|
|
254
176
|
}
|
|
255
|
-
|
|
256
|
-
|
|
177
|
+
let host = parsed.hostname;
|
|
178
|
+
const port = Number(parsed.port || '80');
|
|
257
179
|
if (!Number.isInteger(port)) {
|
|
258
|
-
throw new Error(
|
|
180
|
+
throw new Error(`Invalid port in environment variable ${envVar}=${hostAndPort}`);
|
|
259
181
|
}
|
|
260
|
-
return { host
|
|
182
|
+
return { host, port };
|
|
261
183
|
}
|
|
262
184
|
|
|
263
185
|
/**
|
|
@@ -281,172 +203,135 @@ function emulatorFromEnvVar(envVar) {
|
|
|
281
203
|
* which should never be directly called by the developer.
|
|
282
204
|
* @private
|
|
283
205
|
*/
|
|
284
|
-
|
|
285
|
-
|
|
206
|
+
class RulesTestEnvironmentImpl {
|
|
207
|
+
constructor(projectId, emulators) {
|
|
286
208
|
this.projectId = projectId;
|
|
287
209
|
this.emulators = emulators;
|
|
288
210
|
this.contexts = new Set();
|
|
289
211
|
this.destroyed = false;
|
|
290
212
|
}
|
|
291
|
-
|
|
213
|
+
authenticatedContext(user_id, tokenOptions) {
|
|
292
214
|
this.checkNotDestroyed();
|
|
293
|
-
return this.createContext(
|
|
294
|
-
}
|
|
295
|
-
|
|
215
|
+
return this.createContext(Object.assign(Object.assign({}, tokenOptions), { sub: user_id, user_id: user_id }));
|
|
216
|
+
}
|
|
217
|
+
unauthenticatedContext() {
|
|
296
218
|
this.checkNotDestroyed();
|
|
297
219
|
return this.createContext(/* authToken = */ undefined);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
case 4: return [2 /*return*/];
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
};
|
|
325
|
-
RulesTestEnvironmentImpl.prototype.createContext = function (authToken) {
|
|
326
|
-
var context = new RulesTestContextImpl(this.projectId, this.emulators, authToken);
|
|
220
|
+
}
|
|
221
|
+
async withSecurityRulesDisabled(callback) {
|
|
222
|
+
this.checkNotDestroyed();
|
|
223
|
+
// The "owner" token is recognized by the emulators as a special value that bypasses Security
|
|
224
|
+
// Rules. This should only ever be used in withSecurityRulesDisabled.
|
|
225
|
+
// If you're reading this and thinking about doing this in your own app / tests / scripts, think
|
|
226
|
+
// twice. Instead, just use withSecurityRulesDisabled for unit testing OR connect your Firebase
|
|
227
|
+
// Admin SDKs to the emulators for integration testing via environment variables.
|
|
228
|
+
// See: https://firebase.google.com/docs/emulator-suite/connect_firestore#admin_sdks
|
|
229
|
+
const context = this.createContext('owner');
|
|
230
|
+
try {
|
|
231
|
+
await callback(context);
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
// We eagerly clean up this context to actively prevent misuse outside of the callback, e.g.
|
|
235
|
+
// storing the context in a variable.
|
|
236
|
+
context.cleanup();
|
|
237
|
+
this.contexts.delete(context);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
createContext(authToken) {
|
|
241
|
+
const context = new RulesTestContextImpl(this.projectId, this.emulators, authToken);
|
|
327
242
|
this.contexts.add(context);
|
|
328
243
|
return context;
|
|
329
|
-
}
|
|
330
|
-
|
|
244
|
+
}
|
|
245
|
+
clearDatabase() {
|
|
331
246
|
this.checkNotDestroyed();
|
|
332
|
-
return this.withSecurityRulesDisabled(
|
|
247
|
+
return this.withSecurityRulesDisabled(context => {
|
|
333
248
|
return context.database().ref('/').set(null);
|
|
334
249
|
});
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
case 0:
|
|
342
|
-
this.checkNotDestroyed();
|
|
343
|
-
assertEmulatorRunning(this.emulators, 'firestore');
|
|
344
|
-
return [4 /*yield*/, fetch__default["default"](makeUrl(this.emulators.firestore, "/emulator/v1/projects/".concat(this.projectId, "/databases/(default)/documents")), {
|
|
345
|
-
method: 'DELETE'
|
|
346
|
-
})];
|
|
347
|
-
case 1:
|
|
348
|
-
resp = _b.sent();
|
|
349
|
-
if (!!resp.ok) return [3 /*break*/, 3];
|
|
350
|
-
_a = Error.bind;
|
|
351
|
-
return [4 /*yield*/, resp.text()];
|
|
352
|
-
case 2: throw new (_a.apply(Error, [void 0, _b.sent()]))();
|
|
353
|
-
case 3: return [2 /*return*/];
|
|
354
|
-
}
|
|
355
|
-
});
|
|
250
|
+
}
|
|
251
|
+
async clearFirestore() {
|
|
252
|
+
this.checkNotDestroyed();
|
|
253
|
+
assertEmulatorRunning(this.emulators, 'firestore');
|
|
254
|
+
const resp = await fetch(makeUrl(this.emulators.firestore, `/emulator/v1/projects/${this.projectId}/databases/(default)/documents`), {
|
|
255
|
+
method: 'DELETE'
|
|
356
256
|
});
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
257
|
+
if (!resp.ok) {
|
|
258
|
+
throw new Error(await resp.text());
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
clearStorage() {
|
|
360
262
|
this.checkNotDestroyed();
|
|
361
|
-
return this.withSecurityRulesDisabled(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
case 1:
|
|
367
|
-
items = (_a.sent()).items;
|
|
368
|
-
return [4 /*yield*/, Promise.all(items.map(function (item) {
|
|
369
|
-
return item.delete();
|
|
370
|
-
}))];
|
|
371
|
-
case 2:
|
|
372
|
-
_a.sent();
|
|
373
|
-
return [2 /*return*/];
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
}); });
|
|
377
|
-
};
|
|
378
|
-
RulesTestEnvironmentImpl.prototype.cleanup = function () {
|
|
379
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
380
|
-
return __generator(this, function (_a) {
|
|
381
|
-
this.destroyed = true;
|
|
382
|
-
this.contexts.forEach(function (context) {
|
|
383
|
-
context.envDestroyed = true;
|
|
384
|
-
context.cleanup();
|
|
385
|
-
});
|
|
386
|
-
this.contexts.clear();
|
|
387
|
-
return [2 /*return*/];
|
|
388
|
-
});
|
|
263
|
+
return this.withSecurityRulesDisabled(async (context) => {
|
|
264
|
+
const { items } = await context.storage().ref().listAll();
|
|
265
|
+
await Promise.all(items.map((item) => {
|
|
266
|
+
return item.delete();
|
|
267
|
+
}));
|
|
389
268
|
});
|
|
390
|
-
}
|
|
391
|
-
|
|
269
|
+
}
|
|
270
|
+
async cleanup() {
|
|
271
|
+
this.destroyed = true;
|
|
272
|
+
this.contexts.forEach(context => {
|
|
273
|
+
context.envDestroyed = true;
|
|
274
|
+
context.cleanup();
|
|
275
|
+
});
|
|
276
|
+
this.contexts.clear();
|
|
277
|
+
}
|
|
278
|
+
checkNotDestroyed() {
|
|
392
279
|
if (this.destroyed) {
|
|
393
280
|
throw new Error('This RulesTestEnvironment has already been cleaned up. ' +
|
|
394
281
|
'(This may indicate a leak or missing `await` in your test cases. If you do intend to ' +
|
|
395
282
|
'perform more tests, please call cleanup() later or create another RulesTestEnvironment.)');
|
|
396
283
|
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
}());
|
|
284
|
+
}
|
|
285
|
+
}
|
|
400
286
|
/**
|
|
401
287
|
* An implementation of {@code RulesTestContext}. This is private to hide the constructor,
|
|
402
288
|
* which should never be directly called by the developer.
|
|
403
289
|
* @private
|
|
404
290
|
*/
|
|
405
|
-
|
|
406
|
-
|
|
291
|
+
class RulesTestContextImpl {
|
|
292
|
+
constructor(projectId, emulators, authToken) {
|
|
407
293
|
this.projectId = projectId;
|
|
408
294
|
this.emulators = emulators;
|
|
409
295
|
this.authToken = authToken;
|
|
410
296
|
this.destroyed = false;
|
|
411
297
|
this.envDestroyed = false;
|
|
412
298
|
}
|
|
413
|
-
|
|
299
|
+
cleanup() {
|
|
414
300
|
var _a;
|
|
415
301
|
this.destroyed = true;
|
|
416
302
|
(_a = this.app) === null || _a === void 0 ? void 0 : _a.delete();
|
|
417
303
|
this.app = undefined;
|
|
418
|
-
}
|
|
419
|
-
|
|
304
|
+
}
|
|
305
|
+
firestore(settings) {
|
|
420
306
|
assertEmulatorRunning(this.emulators, 'firestore');
|
|
421
|
-
|
|
307
|
+
const firestore = this.getApp().firestore();
|
|
422
308
|
if (settings) {
|
|
423
309
|
firestore.settings(settings);
|
|
424
310
|
}
|
|
425
311
|
firestore.useEmulator(this.emulators.firestore.host, this.emulators.firestore.port, { mockUserToken: this.authToken });
|
|
426
312
|
return firestore;
|
|
427
|
-
}
|
|
428
|
-
|
|
313
|
+
}
|
|
314
|
+
database(databaseURL) {
|
|
429
315
|
assertEmulatorRunning(this.emulators, 'database');
|
|
430
316
|
if (!databaseURL) {
|
|
431
|
-
|
|
317
|
+
const url = makeUrl(this.emulators.database, '');
|
|
432
318
|
// Make sure to set the namespace equal to projectId -- otherwise the RTDB SDK will by default
|
|
433
319
|
// use `${projectId}-default-rtdb`, which is treated as a different DB by the RTDB emulator
|
|
434
320
|
// (and thus WON'T apply any rules set for the `projectId` DB during initialization).
|
|
435
321
|
url.searchParams.append('ns', this.projectId);
|
|
436
322
|
databaseURL = url.toString();
|
|
437
323
|
}
|
|
438
|
-
|
|
324
|
+
const database = this.getApp().database(databaseURL);
|
|
439
325
|
database.useEmulator(this.emulators.database.host, this.emulators.database.port, { mockUserToken: this.authToken });
|
|
440
326
|
return database;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
if (bucketUrl === void 0) { bucketUrl = "gs://".concat(this.projectId); }
|
|
327
|
+
}
|
|
328
|
+
storage(bucketUrl = `gs://${this.projectId}`) {
|
|
444
329
|
assertEmulatorRunning(this.emulators, 'storage');
|
|
445
|
-
|
|
330
|
+
const storage = this.getApp().storage(bucketUrl);
|
|
446
331
|
storage.useEmulator(this.emulators.storage.host, this.emulators.storage.port, { mockUserToken: this.authToken });
|
|
447
332
|
return storage;
|
|
448
|
-
}
|
|
449
|
-
|
|
333
|
+
}
|
|
334
|
+
getApp() {
|
|
450
335
|
if (this.envDestroyed) {
|
|
451
336
|
throw new Error('This RulesTestContext is no longer valid because its RulesTestEnvironment has been ' +
|
|
452
337
|
'cleaned up. (This may indicate a leak or missing `await` in your test cases.)');
|
|
@@ -457,22 +342,21 @@ var RulesTestContextImpl = /** @class */ (function () {
|
|
|
457
342
|
'return a Promise that resolves when the operations are done.');
|
|
458
343
|
}
|
|
459
344
|
if (!this.app) {
|
|
460
|
-
this.app = firebase__default["default"].initializeApp({ projectId: this.projectId },
|
|
345
|
+
this.app = firebase__default["default"].initializeApp({ projectId: this.projectId }, `_Firebase_RulesUnitTesting_${Date.now()}_${Math.random()}`);
|
|
461
346
|
}
|
|
462
347
|
return this.app;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
}());
|
|
348
|
+
}
|
|
349
|
+
}
|
|
466
350
|
function assertEmulatorRunning(emulators, emulator) {
|
|
467
351
|
if (!emulators[emulator]) {
|
|
468
352
|
if (emulators.hub) {
|
|
469
|
-
throw new Error(
|
|
353
|
+
throw new Error(`The ${emulator} emulator is not running (according to Emulator hub). To force ` +
|
|
470
354
|
'connecting anyway, please specify its host and port in initializeTestEnvironment({...}).');
|
|
471
355
|
}
|
|
472
356
|
else {
|
|
473
|
-
throw new Error(
|
|
357
|
+
throw new Error(`The host and port of the ${emulator} emulator must be specified. (You may wrap the test ` +
|
|
474
358
|
"script with `firebase emulators:exec './your-test-script'` to enable automatic " +
|
|
475
|
-
|
|
359
|
+
`discovery, or specify manually via initializeTestEnvironment({${emulator}: {host, port}}).`);
|
|
476
360
|
}
|
|
477
361
|
}
|
|
478
362
|
}
|
|
@@ -496,86 +380,52 @@ function assertEmulatorRunning(emulators, emulator) {
|
|
|
496
380
|
/**
|
|
497
381
|
* @private
|
|
498
382
|
*/
|
|
499
|
-
function loadDatabaseRules(hostAndPort, databaseName, rules) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
url.searchParams.append('ns', databaseName);
|
|
507
|
-
return [4 /*yield*/, fetch__default["default"](url, {
|
|
508
|
-
method: 'PUT',
|
|
509
|
-
headers: { Authorization: 'Bearer owner' },
|
|
510
|
-
body: rules
|
|
511
|
-
})];
|
|
512
|
-
case 1:
|
|
513
|
-
resp = _b.sent();
|
|
514
|
-
if (!!resp.ok) return [3 /*break*/, 3];
|
|
515
|
-
_a = Error.bind;
|
|
516
|
-
return [4 /*yield*/, resp.text()];
|
|
517
|
-
case 2: throw new (_a.apply(Error, [void 0, _b.sent()]))();
|
|
518
|
-
case 3: return [2 /*return*/];
|
|
519
|
-
}
|
|
520
|
-
});
|
|
383
|
+
async function loadDatabaseRules(hostAndPort, databaseName, rules) {
|
|
384
|
+
const url = makeUrl(hostAndPort, '/.settings/rules.json');
|
|
385
|
+
url.searchParams.append('ns', databaseName);
|
|
386
|
+
const resp = await fetch(url, {
|
|
387
|
+
method: 'PUT',
|
|
388
|
+
headers: { Authorization: 'Bearer owner' },
|
|
389
|
+
body: rules
|
|
521
390
|
});
|
|
391
|
+
if (!resp.ok) {
|
|
392
|
+
throw new Error(await resp.text());
|
|
393
|
+
}
|
|
522
394
|
}
|
|
523
395
|
/**
|
|
524
396
|
* @private
|
|
525
397
|
*/
|
|
526
|
-
function loadFirestoreRules(hostAndPort, projectId, rules) {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
method: 'PUT',
|
|
533
|
-
body: JSON.stringify({
|
|
534
|
-
rules: {
|
|
535
|
-
files: [{ content: rules }]
|
|
536
|
-
}
|
|
537
|
-
})
|
|
538
|
-
})];
|
|
539
|
-
case 1:
|
|
540
|
-
resp = _b.sent();
|
|
541
|
-
if (!!resp.ok) return [3 /*break*/, 3];
|
|
542
|
-
_a = Error.bind;
|
|
543
|
-
return [4 /*yield*/, resp.text()];
|
|
544
|
-
case 2: throw new (_a.apply(Error, [void 0, _b.sent()]))();
|
|
545
|
-
case 3: return [2 /*return*/];
|
|
398
|
+
async function loadFirestoreRules(hostAndPort, projectId, rules) {
|
|
399
|
+
const resp = await fetch(makeUrl(hostAndPort, `/emulator/v1/projects/${projectId}:securityRules`), {
|
|
400
|
+
method: 'PUT',
|
|
401
|
+
body: JSON.stringify({
|
|
402
|
+
rules: {
|
|
403
|
+
files: [{ content: rules }]
|
|
546
404
|
}
|
|
547
|
-
})
|
|
405
|
+
})
|
|
548
406
|
});
|
|
407
|
+
if (!resp.ok) {
|
|
408
|
+
throw new Error(await resp.text());
|
|
409
|
+
}
|
|
549
410
|
}
|
|
550
411
|
/**
|
|
551
412
|
* @private
|
|
552
413
|
*/
|
|
553
|
-
function loadStorageRules(hostAndPort, rules) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
},
|
|
563
|
-
body: JSON.stringify({
|
|
564
|
-
rules: {
|
|
565
|
-
files: [{ name: 'storage.rules', content: rules }]
|
|
566
|
-
}
|
|
567
|
-
})
|
|
568
|
-
})];
|
|
569
|
-
case 1:
|
|
570
|
-
resp = _b.sent();
|
|
571
|
-
if (!!resp.ok) return [3 /*break*/, 3];
|
|
572
|
-
_a = Error.bind;
|
|
573
|
-
return [4 /*yield*/, resp.text()];
|
|
574
|
-
case 2: throw new (_a.apply(Error, [void 0, _b.sent()]))();
|
|
575
|
-
case 3: return [2 /*return*/];
|
|
414
|
+
async function loadStorageRules(hostAndPort, rules) {
|
|
415
|
+
const resp = await fetch(makeUrl(hostAndPort, '/internal/setRules'), {
|
|
416
|
+
method: 'PUT',
|
|
417
|
+
headers: {
|
|
418
|
+
'Content-Type': 'application/json'
|
|
419
|
+
},
|
|
420
|
+
body: JSON.stringify({
|
|
421
|
+
rules: {
|
|
422
|
+
files: [{ name: 'storage.rules', content: rules }]
|
|
576
423
|
}
|
|
577
|
-
})
|
|
424
|
+
})
|
|
578
425
|
});
|
|
426
|
+
if (!resp.ok) {
|
|
427
|
+
throw new Error(await resp.text());
|
|
428
|
+
}
|
|
579
429
|
}
|
|
580
430
|
|
|
581
431
|
/**
|
|
@@ -615,67 +465,40 @@ function loadStorageRules(hostAndPort, rules) {
|
|
|
615
465
|
* });
|
|
616
466
|
* ```
|
|
617
467
|
*/
|
|
618
|
-
function initializeTestEnvironment(config) {
|
|
468
|
+
async function initializeTestEnvironment(config) {
|
|
619
469
|
var _a, _b, _c;
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
emulators[emulator] = hostAndPort;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
if (!((_a = config.database) === null || _a === void 0 ? void 0 : _a.rules)) return [3 /*break*/, 5];
|
|
654
|
-
assertEmulatorRunning(emulators, 'database');
|
|
655
|
-
return [4 /*yield*/, loadDatabaseRules(emulators.database, projectId, config.database.rules)];
|
|
656
|
-
case 4:
|
|
657
|
-
_f.sent();
|
|
658
|
-
_f.label = 5;
|
|
659
|
-
case 5:
|
|
660
|
-
if (!((_b = config.firestore) === null || _b === void 0 ? void 0 : _b.rules)) return [3 /*break*/, 7];
|
|
661
|
-
assertEmulatorRunning(emulators, 'firestore');
|
|
662
|
-
return [4 /*yield*/, loadFirestoreRules(emulators.firestore, projectId, config.firestore.rules)];
|
|
663
|
-
case 6:
|
|
664
|
-
_f.sent();
|
|
665
|
-
_f.label = 7;
|
|
666
|
-
case 7:
|
|
667
|
-
if (!((_c = config.storage) === null || _c === void 0 ? void 0 : _c.rules)) return [3 /*break*/, 9];
|
|
668
|
-
assertEmulatorRunning(emulators, 'storage');
|
|
669
|
-
return [4 /*yield*/, loadStorageRules(emulators.storage, config.storage.rules)];
|
|
670
|
-
case 8:
|
|
671
|
-
_f.sent();
|
|
672
|
-
_f.label = 9;
|
|
673
|
-
case 9: return [2 /*return*/, new RulesTestEnvironmentImpl(projectId, emulators)];
|
|
674
|
-
}
|
|
675
|
-
});
|
|
676
|
-
});
|
|
470
|
+
const projectId = config.projectId || process.env.GCLOUD_PROJECT;
|
|
471
|
+
if (!projectId) {
|
|
472
|
+
throw new Error('Missing projectId option or env var GCLOUD_PROJECT! Please specify the projectId either ' +
|
|
473
|
+
'way.\n(A demo-* projectId is strongly recommended for unit tests, such as "demo-test".)');
|
|
474
|
+
}
|
|
475
|
+
const hub = getEmulatorHostAndPort('hub', config.hub);
|
|
476
|
+
let discovered = hub ? Object.assign(Object.assign({}, (await discoverEmulators(hub))), { hub }) : undefined;
|
|
477
|
+
const emulators = {};
|
|
478
|
+
if (hub) {
|
|
479
|
+
emulators.hub = hub;
|
|
480
|
+
}
|
|
481
|
+
for (const emulator of SUPPORTED_EMULATORS) {
|
|
482
|
+
const hostAndPort = getEmulatorHostAndPort(emulator, config[emulator], discovered);
|
|
483
|
+
if (hostAndPort) {
|
|
484
|
+
emulators[emulator] = hostAndPort;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if ((_a = config.database) === null || _a === void 0 ? void 0 : _a.rules) {
|
|
488
|
+
assertEmulatorRunning(emulators, 'database');
|
|
489
|
+
await loadDatabaseRules(emulators.database, projectId, config.database.rules);
|
|
490
|
+
}
|
|
491
|
+
if ((_b = config.firestore) === null || _b === void 0 ? void 0 : _b.rules) {
|
|
492
|
+
assertEmulatorRunning(emulators, 'firestore');
|
|
493
|
+
await loadFirestoreRules(emulators.firestore, projectId, config.firestore.rules);
|
|
494
|
+
}
|
|
495
|
+
if ((_c = config.storage) === null || _c === void 0 ? void 0 : _c.rules) {
|
|
496
|
+
assertEmulatorRunning(emulators, 'storage');
|
|
497
|
+
await loadStorageRules(emulators.storage, config.storage.rules);
|
|
498
|
+
}
|
|
499
|
+
return new RulesTestEnvironmentImpl(projectId, emulators);
|
|
677
500
|
}
|
|
678
|
-
|
|
501
|
+
const SUPPORTED_EMULATORS = ['database', 'firestore', 'storage'];
|
|
679
502
|
|
|
680
503
|
/**
|
|
681
504
|
* @license
|
|
@@ -693,59 +516,47 @@ var SUPPORTED_EMULATORS = ['database', 'firestore', 'storage'];
|
|
|
693
516
|
* See the License for the specific language governing permissions and
|
|
694
517
|
* limitations under the License.
|
|
695
518
|
*/
|
|
696
|
-
function withFunctionTriggersDisabled(fnOrHub, maybeFn) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
makeUrl(hub, '/functions/disableBackgroundTriggers');
|
|
718
|
-
return [4 /*yield*/, fetch__default["default"](makeUrl(hub, '/functions/disableBackgroundTriggers'), {
|
|
719
|
-
method: 'PUT'
|
|
720
|
-
})];
|
|
721
|
-
case 1:
|
|
722
|
-
disableRes = _a.sent();
|
|
723
|
-
if (!disableRes.ok) {
|
|
724
|
-
throw new Error("HTTP Error ".concat(disableRes.status, " when disabling functions triggers, are you using firebase-tools 8.13.0 or higher?"));
|
|
725
|
-
}
|
|
726
|
-
result = undefined;
|
|
727
|
-
_a.label = 2;
|
|
728
|
-
case 2:
|
|
729
|
-
_a.trys.push([2, , 4, 6]);
|
|
730
|
-
return [4 /*yield*/, maybeFn()];
|
|
731
|
-
case 3:
|
|
732
|
-
result = _a.sent();
|
|
733
|
-
return [3 /*break*/, 6];
|
|
734
|
-
case 4: return [4 /*yield*/, fetch__default["default"](makeUrl(hub, '/functions/enableBackgroundTriggers'), {
|
|
735
|
-
method: 'PUT'
|
|
736
|
-
})];
|
|
737
|
-
case 5:
|
|
738
|
-
enableRes = _a.sent();
|
|
739
|
-
if (!enableRes.ok) {
|
|
740
|
-
throw new Error("HTTP Error ".concat(enableRes.status, " when enabling functions triggers, are you using firebase-tools 8.13.0 or higher?"));
|
|
741
|
-
}
|
|
742
|
-
return [7 /*endfinally*/];
|
|
743
|
-
case 6:
|
|
744
|
-
// Return the user's function result
|
|
745
|
-
return [2 /*return*/, result];
|
|
746
|
-
}
|
|
747
|
-
});
|
|
519
|
+
async function withFunctionTriggersDisabled(fnOrHub, maybeFn) {
|
|
520
|
+
let hub;
|
|
521
|
+
if (typeof fnOrHub === 'function') {
|
|
522
|
+
maybeFn = fnOrHub;
|
|
523
|
+
hub = getEmulatorHostAndPort('hub');
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
hub = getEmulatorHostAndPort('hub', fnOrHub);
|
|
527
|
+
if (!maybeFn) {
|
|
528
|
+
throw new Error('The callback function must be specified!');
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (!hub) {
|
|
532
|
+
throw new Error('Please specify the Emulator Hub host and port via arguments or set the environment ' +
|
|
533
|
+
`variable ${EMULATOR_HOST_ENV_VARS.hub}!`);
|
|
534
|
+
}
|
|
535
|
+
hub.host = fixHostname(hub.host);
|
|
536
|
+
makeUrl(hub, '/functions/disableBackgroundTriggers');
|
|
537
|
+
// Disable background triggers
|
|
538
|
+
const disableRes = await fetch(makeUrl(hub, '/functions/disableBackgroundTriggers'), {
|
|
539
|
+
method: 'PUT'
|
|
748
540
|
});
|
|
541
|
+
if (!disableRes.ok) {
|
|
542
|
+
throw new Error(`HTTP Error ${disableRes.status} when disabling functions triggers, are you using firebase-tools 8.13.0 or higher?`);
|
|
543
|
+
}
|
|
544
|
+
// Run the user's function
|
|
545
|
+
let result = undefined;
|
|
546
|
+
try {
|
|
547
|
+
result = await maybeFn();
|
|
548
|
+
}
|
|
549
|
+
finally {
|
|
550
|
+
// Re-enable background triggers
|
|
551
|
+
const enableRes = await fetch(makeUrl(hub, '/functions/enableBackgroundTriggers'), {
|
|
552
|
+
method: 'PUT'
|
|
553
|
+
});
|
|
554
|
+
if (!enableRes.ok) {
|
|
555
|
+
throw new Error(`HTTP Error ${enableRes.status} when enabling functions triggers, are you using firebase-tools 8.13.0 or higher?`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// Return the user's function result
|
|
559
|
+
return result;
|
|
749
560
|
}
|
|
750
561
|
/**
|
|
751
562
|
* Assert the promise to be rejected with a "permission denied" error.
|
|
@@ -764,20 +575,20 @@ function withFunctionTriggersDisabled(fnOrHub, maybeFn) {
|
|
|
764
575
|
* ```
|
|
765
576
|
*/
|
|
766
577
|
function assertFails(pr) {
|
|
767
|
-
return pr.then(
|
|
578
|
+
return pr.then(() => {
|
|
768
579
|
return Promise.reject(new Error('Expected request to fail, but it succeeded.'));
|
|
769
|
-
},
|
|
580
|
+
}, (err) => {
|
|
770
581
|
var _a, _b;
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
582
|
+
const errCode = ((_a = err === null || err === void 0 ? void 0 : err.code) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
|
|
583
|
+
const errMessage = ((_b = err === null || err === void 0 ? void 0 : err.message) === null || _b === void 0 ? void 0 : _b.toLowerCase()) || '';
|
|
584
|
+
const isPermissionDenied = errCode === 'permission-denied' ||
|
|
774
585
|
errCode === 'permission_denied' ||
|
|
775
586
|
errMessage.indexOf('permission_denied') >= 0 ||
|
|
776
587
|
errMessage.indexOf('permission denied') >= 0 ||
|
|
777
588
|
// Storage permission errors contain message: (storage/unauthorized)
|
|
778
589
|
errMessage.indexOf('unauthorized') >= 0;
|
|
779
590
|
if (!isPermissionDenied) {
|
|
780
|
-
return Promise.reject(new Error(
|
|
591
|
+
return Promise.reject(new Error(`Expected PERMISSION_DENIED but got unexpected error: ${err}`));
|
|
781
592
|
}
|
|
782
593
|
return err;
|
|
783
594
|
});
|