Socket
Socket
Sign inDemoInstall

cypress-firebase

Package Overview
Dependencies
205
Maintainers
1
Versions
99
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.3.0-alpha.2 to 3.0.0-beta.1

lib-esm/node-utils.d.ts

34

lib-esm/attachCustomCommands.d.ts

@@ -1,3 +0,2 @@

import type { FieldValue } from 'firebase-admin/firestore';
import type { AppOptions } from './types';
import type { firestore } from 'firebase-admin';
/**

@@ -23,3 +22,3 @@ * Params for attachCustomCommand function for

}
type WhereOptions = [string, FirebaseFirestore.WhereFilterOp, any];
export type WhereOptions = [string, FirebaseFirestore.WhereFilterOp, any];
/**

@@ -30,7 +29,2 @@ * Options for callFirestore custom Cypress command.

/**
* Name of Firebase app. Defaults to Firebase's internal setting
* of "[DEFAULT]".
*/
appName?: string;
/**
* Whether or not to include createdAt and createdBy

@@ -68,3 +62,3 @@ */

*/
statics?: typeof FieldValue;
statics?: typeof firestore;
}

@@ -80,7 +74,2 @@ /**

/**
* Name of Firebase app. Defaults to Firebase's internal setting
* of "[DEFAULT]".
*/
appName?: string;
/**
* Whether or not to include meta data

@@ -140,4 +129,2 @@ */

}
export type LoginOptions = AppOptions;
export type LogoutOptions = AppOptions;
declare global {

@@ -153,5 +140,3 @@ namespace Cypress {

* @param customClaims - Custom claims to attach to the custom token
* @param options - Options
* @param options.appName - Optional name of firebase-admin app. Defaults to Firebase's default app (i.e DEFAULT)
* @param options.tenantId - Optional ID of tenant used for multi-tenancy. Can also be set with environment variable TEST_TENANT_ID
* @param tenantId - Optional ID of tenant used for multi-tenancy. Can also be set with environment variable TEST_TENANT_ID
* @example <caption>Env Based Login (TEST_UID)</caption>

@@ -162,13 +147,10 @@ * cy.login()

*/
login: (uid?: string, customClaims?: any, options?: LoginOptions) => Chainable;
login: (uid?: string, customClaims?: any, tenantId?: string) => Chainable;
/**
* Log current user out of Firebase Auth
* @see https://github.com/prescottprue/cypress-firebase#cylogout
* @param options - Options object
* @param options.appName - Optional name of firebase-admin app. Defaults to Firebase's default app (i.e DEFAULT)
* @param options.tenantId - Optional ID of tenant used for multi-tenancy. Can also be set with environment variable TEST_TENANT_ID
* @example
* cy.logout()
*/
logout: (options?: LogoutOptions) => Chainable;
logout: () => Chainable;
/**

@@ -235,5 +217,5 @@ * Call Real Time Database path with some specified action. Authentication is through

* custom command attachment
* @param customCommandOptions - Custom command options
* @param options - Custom command options
*/
export default function attachCustomCommands(context: AttachCustomCommandParams, customCommandOptions?: CustomCommandOptions): void;
export default function attachCustomCommands(context: AttachCustomCommandParams, options?: CustomCommandOptions): void;
export {};

@@ -34,19 +34,15 @@ /**

* custom command attachment
* @param customCommandOptions - Custom command options
* @param options - Custom command options
*/
export default function attachCustomCommands(context, customCommandOptions) {
export default function attachCustomCommands(context, options) {
const { Cypress, cy, firebase, app } = context;
const defaultApp = app || firebase.app(); // select default app
/**
* Get firebase auth instance, with tenantId set if provided
* @param authOptions - Settings object
* @param authOptions.tenantId Optional tenant ID
* @param authOptions.appName - Name of app
* @param tenantId Optional tenant ID
* @returns firebase auth instance
*/
function getAuthWithTenantId(authOptions) {
const { tenantId = Cypress.env('TEST_TENANT_ID'), appName } = authOptions || {};
const browserAppInstance = app || firebase.app(appName); // select default app
const auth = browserAppInstance.auth();
// Check for undefined handles null values for removing tenant from instance
if (typeof tenantId !== 'undefined') {
function getAuth(tenantId) {
const auth = defaultApp.auth();
if (tenantId) {
auth.tenantId = tenantId;

@@ -62,3 +58,3 @@ }

*/
Cypress.Commands.add(customCommandOptions?.commandNames?.login || 'login', (uid, customClaims, options) => {
Cypress.Commands.add(options?.commandNames?.login || 'login', (uid, customClaims, tenantId = Cypress.env('TEST_TENANT_ID')) => {
const userUid = uid || Cypress.env('TEST_UID');

@@ -69,6 +65,6 @@ // Handle UID which is passed in

}
const auth = getAuthWithTenantId(options);
const auth = getAuth(tenantId);
// Resolve with current user if they already exist
if (auth.currentUser && userUid === auth.currentUser.uid) {
cy.log('Authenticated user already exists, login complete.');
cy.log('Authed user already exists, login complete.');
return undefined;

@@ -82,4 +78,3 @@ }

customClaims,
tenantId: auth.tenantId,
...options,
tenantId,
})

@@ -95,4 +90,4 @@ .then((customToken) => loginWithCustomToken(auth, customToken));

*/
Cypress.Commands.add(customCommandOptions?.commandNames?.logout || 'logout', (options) => new Promise((resolve, reject) => {
const auth = getAuthWithTenantId(options);
Cypress.Commands.add(options?.commandNames?.logout || 'logout', (tenantId = Cypress.env('TEST_TENANT_ID')) => new Promise((resolve, reject) => {
const auth = getAuth(tenantId);
auth.onAuthStateChanged((auth) => {

@@ -113,4 +108,3 @@ if (!auth) {

*/
Cypress.Commands.add(customCommandOptions?.commandNames?.callRtdb || 'callRtdb', (action, actionPath, dataOrOptions, options) => {
// TODO: Make exposed types dynamic to action (i.e. get has 3rd arg as options)
Cypress.Commands.add(options?.commandNames?.callRtdb || 'callRtdb', (action, actionPath, dataOrOptions, options) => {
const taskSettings = {

@@ -154,3 +148,3 @@ action,

*/
Cypress.Commands.add(customCommandOptions?.commandNames?.callFirestore || 'callFirestore', (action, actionPath, dataOrOptions, options) => {
Cypress.Commands.add(options?.commandNames?.callFirestore || 'callFirestore', (action, actionPath, dataOrOptions, options) => {
const taskSettings = {

@@ -193,3 +187,3 @@ action,

*/
Cypress.Commands.add(customCommandOptions?.commandNames?.getAuthUser || 'getAuthUser', (uid, options) => cy.task('getAuthUser', { uid, ...options }));
Cypress.Commands.add(options?.commandNames?.getAuthUser || 'getAuthUser', (uid) => cy.task('getAuthUser', uid));
}

@@ -1,3 +0,3 @@

import type { CollectionReference, DocumentReference, Query } from 'firebase-admin/firestore';
import { CallFirestoreOptions } from './attachCustomCommands';
import type { AppOptions, app, firestore } from 'firebase-admin';
import { CallFirestoreOptions, WhereOptions } from './attachCustomCommands';
/**

@@ -10,2 +10,10 @@ * Check whether a value is a string or not

/**
* Initialize Firebase instance from service account (from either local
* serviceAccount.json or environment variables)
* @returns Initialized Firebase instance
* @param adminInstance - firebase-admin instance to initialize
* @param overrideConfig - firebase-admin instance to initialize
*/
export declare function initializeFirebase(adminInstance: any, overrideConfig?: AppOptions): app.App;
/**
* Check with or not a slash path is the path of a document

@@ -17,5 +25,11 @@ * @param slashPath - Path to check for whether or not it is a doc

/**
*
* @param ref
* @param whereSetting
* @param firestoreStatics
*/
export declare function applyWhere(ref: firestore.CollectionReference | firestore.Query, whereSetting: WhereOptions, firestoreStatics: app.App['firestore']): firestore.Query;
/**
* Convert slash path to Firestore reference
* @param firestoreInstance - Instance on which to
* create ref
* @param firestoreStatics - Firestore instance statics (invoking gets instance)
* @param slashPath - Path to convert into firestore reference

@@ -25,3 +39,3 @@ * @param options - Options object

*/
export declare function slashPathToFirestoreRef(firestoreInstance: any, slashPath: string, options?: CallFirestoreOptions): CollectionReference | DocumentReference | Query;
export declare function slashPathToFirestoreRef(firestoreStatics: app.App['firestore'], slashPath: string, options?: CallFirestoreOptions): firestore.CollectionReference | firestore.DocumentReference | firestore.Query;
/**

@@ -28,0 +42,0 @@ * @param db - Firestore database instance

@@ -0,1 +1,3 @@

import { getServiceAccount } from './node-utils';
import { convertValueToTimestampOrGeoPointIfPossible } from './tasks';
/**

@@ -10,2 +12,122 @@ * Check whether a value is a string or not

/**
* Get settings for Firestore from environment. Loads port and servicePath from
* FIRESTORE_EMULATOR_HOST node environment variable if found, otherwise
* defaults to port 8080 and servicePath "localhost".
* @returns Firestore settings to be passed to firebase.firestore().settings
*/
function firestoreSettingsFromEnv() {
const { FIRESTORE_EMULATOR_HOST } = process.env;
if (typeof FIRESTORE_EMULATOR_HOST === 'undefined' ||
!isString(FIRESTORE_EMULATOR_HOST)) {
return {
servicePath: 'localhost',
port: 8080,
};
}
const [servicePath, portStr] = FIRESTORE_EMULATOR_HOST.split(':');
return {
servicePath,
port: parseInt(portStr, 10),
};
}
/**
* @param adminInstance - firebase-admin instance to initialize
* @returns Firebase admin credential
*/
function getFirebaseCredential(adminInstance) {
const serviceAccount = getServiceAccount();
// Add service account credential if it exists so that custom auth tokens can be generated
if (serviceAccount) {
return adminInstance.credential.cert(serviceAccount);
}
// Add default credentials if they exist
const defaultCredentials = adminInstance.credential.applicationDefault();
if (defaultCredentials) {
console.log('cypress-firebase: Using default credentials'); // eslint-disable-line no-console
return defaultCredentials;
}
}
/**
* Get default datbase url
* @param projectId - Project id
* @returns Default database url
*/
function getDefaultDatabaseUrl(projectId) {
const { FIREBASE_DATABASE_EMULATOR_HOST } = process.env;
return FIREBASE_DATABASE_EMULATOR_HOST
? `http://${FIREBASE_DATABASE_EMULATOR_HOST}?ns=${projectId || 'local'}`
: `https://${projectId}.firebaseio.com`;
}
/**
* Initialize Firebase instance from service account (from either local
* serviceAccount.json or environment variables)
* @returns Initialized Firebase instance
* @param adminInstance - firebase-admin instance to initialize
* @param overrideConfig - firebase-admin instance to initialize
*/
export function initializeFirebase(adminInstance, overrideConfig) {
try {
// TODO: Look into using @firebase/testing in place of admin here to allow for
// usage of clearFirestoreData (see https://github.com/prescottprue/cypress-firebase/issues/73 for more info)
const { FIREBASE_DATABASE_EMULATOR_HOST } = process.env;
const fbConfig = {
// Initialize RTDB with databaseURL pointed to emulator if FIREBASE_DATABASE_EMULATOR_HOST is set
...overrideConfig,
};
if (FIREBASE_DATABASE_EMULATOR_HOST) {
/* eslint-disable no-console */
console.log('cypress-firebase: Using RTDB emulator with host:', FIREBASE_DATABASE_EMULATOR_HOST);
/* eslint-enable no-console */
}
if (process.env.FIREBASE_AUTH_EMULATOR_HOST) {
/* eslint-disable no-console */
console.log('cypress-firebase: Using Auth emulator with port:', process.env.FIREBASE_AUTH_EMULATOR_HOST);
/* eslint-enable no-console */
}
// Add credentials if they do not already exist - starting with application default, falling back to SERVICE_ACCOUNT env variable
if (!fbConfig.credential) {
const credential = getFirebaseCredential(adminInstance);
if (credential) {
fbConfig.credential = credential;
}
}
// Add projectId to fb config if it doesn't already exist
if (!fbConfig.projectId) {
const projectId = process.env.GCLOUD_PROJECT || fbConfig.credential?.projectId; // eslint-disable-line camelcase
if (projectId) {
fbConfig.projectId = projectId;
}
}
// Add databaseURL if it doesn't already exist
if (!fbConfig.databaseURL) {
const databaseURL = getDefaultDatabaseUrl(fbConfig.projectId);
if (databaseURL) {
fbConfig.databaseURL = databaseURL;
}
}
const fbInstance = adminInstance.initializeApp(fbConfig);
// Initialize Firestore with emulator host settings
if (process.env.FIRESTORE_EMULATOR_HOST) {
const firestoreSettings = firestoreSettingsFromEnv();
/* eslint-disable no-console */
console.log('cypress-firebase: Using Firestore emulator with settings:', firestoreSettings);
/* eslint-enable no-console */
adminInstance.firestore().settings(firestoreSettings);
}
/* eslint-disable no-console */
const dbUrlLog = fbConfig.databaseURL
? ` and databaseURL "${fbConfig.databaseURL}"`
: '';
console.log(`cypress-firebase: Initialized Firebase app for project "${fbConfig.projectId}"${dbUrlLog}`);
/* eslint-enable no-console */
return fbInstance;
}
catch (err) {
/* eslint-disable no-console */
console.error('cypress-firebase: Error initializing firebase-admin instance:', err instanceof Error && err.message);
/* eslint-enable no-console */
throw err;
}
}
/**
* Check with or not a slash path is the path of a document

@@ -19,5 +141,14 @@ * @param slashPath - Path to check for whether or not it is a doc

/**
*
* @param ref
* @param whereSetting
* @param firestoreStatics
*/
export function applyWhere(ref, whereSetting, firestoreStatics) {
const [param, filterOp, val] = whereSetting;
return ref.where(param, filterOp, convertValueToTimestampOrGeoPointIfPossible(val, firestoreStatics));
}
/**
* Convert slash path to Firestore reference
* @param firestoreInstance - Instance on which to
* create ref
* @param firestoreStatics - Firestore instance statics (invoking gets instance)
* @param slashPath - Path to convert into firestore reference

@@ -27,9 +158,11 @@ * @param options - Options object

*/
export function slashPathToFirestoreRef(firestoreInstance, slashPath, options) {
export function slashPathToFirestoreRef(firestoreStatics, slashPath, options) {
if (!slashPath) {
throw new Error('Path is required to make Firestore Reference');
}
let ref = isDocPath(slashPath)
? firestoreInstance.doc(slashPath)
: firestoreInstance.collection(slashPath);
const firestoreInstance = firestoreStatics();
if (isDocPath(slashPath)) {
return firestoreInstance.doc(slashPath);
}
let ref = firestoreInstance.collection(slashPath);
// Apply orderBy to query if it exists

@@ -49,6 +182,7 @@ if (options?.orderBy && typeof ref.orderBy === 'function') {

if (Array.isArray(options.where[0])) {
ref = ref.where(...options.where[0]).where(...options.where[1]);
const [where1, where2] = options.where;
ref = applyWhere(applyWhere(ref, where1, options.statics || firestoreStatics), where2, options.statics || firestoreStatics);
}
else {
ref = ref.where(...options.where);
ref = applyWhere(ref, options.where, options.statics || firestoreStatics);
}

@@ -55,0 +189,0 @@ }

@@ -0,1 +1,2 @@

import type { AppOptions } from 'firebase-admin';
import { ExtendedCypressConfig } from './extendWithFirebaseConfig';

@@ -10,4 +11,6 @@ /**

* @param cypressConfig - Cypress config
* @param adminInstance - firebase-admin instance
* @param overrideConfig - Override config for firebase instance
* @returns Extended Cypress config
*/
export default function pluginWithTasks(cypressOnFunc: Cypress.PluginEvents, cypressConfig: Partial<Cypress.PluginConfigOptions>): ExtendedCypressConfig;
export default function pluginWithTasks(cypressOnFunc: Cypress.PluginEvents, cypressConfig: Partial<Cypress.PluginConfigOptions>, adminInstance: any, overrideConfig?: AppOptions): ExtendedCypressConfig;
import extendWithFirebaseConfig from './extendWithFirebaseConfig';
import * as tasks from './tasks';
import { initializeFirebase } from './firebase-utils';
/**

@@ -11,10 +12,25 @@ * Cypress plugin which attaches tasks used by custom commands

* @param cypressConfig - Cypress config
* @param adminInstance - firebase-admin instance
* @param overrideConfig - Override config for firebase instance
* @returns Extended Cypress config
*/
export default function pluginWithTasks(cypressOnFunc, cypressConfig) {
export default function pluginWithTasks(cypressOnFunc, cypressConfig, adminInstance, overrideConfig) {
// Only initialize admin instance if it hasn't already been initialized
if (adminInstance.apps?.length === 0) {
initializeFirebase(adminInstance, overrideConfig);
}
const tasksWithFirebase = Object.keys(tasks).reduce((acc, taskName) => {
acc[taskName] = (taskSettings) => {
if (taskSettings?.uid) {
return tasks[taskName](adminInstance, taskSettings.uid, taskSettings);
}
const { action, path: actionPath, options = {}, data } = taskSettings;
return tasks[taskName](adminInstance, action, actionPath, options, data);
};
return acc;
}, {});
// Attach tasks to Cypress using on function
// NOTE: any is used because cypress doesn't export Task or Tasks types
cypressOnFunc('task', tasks);
cypressOnFunc('task', tasksWithFirebase);
// Return extended config
return extendWithFirebaseConfig(cypressConfig);
}

@@ -1,5 +0,13 @@

import { UserRecord } from 'firebase-admin/auth';
import type { firestore, auth, app } from 'firebase-admin';
import { FixtureData, FirestoreAction, RTDBAction, CallRtdbOptions, CallFirestoreOptions } from './attachCustomCommands';
import { AppOptions } from './types';
/**
* Convert unique data types which have been stringified and parsed back
* into their original type.
* @param dataVal - Value of data
* @param firestoreStatics - Statics from firestore instance
* @returns Value converted into timestamp object if possible
*/
export declare function convertValueToTimestampOrGeoPointIfPossible(dataVal: any, firestoreStatics: typeof firestore): firestore.FieldValue;
/**
* @param adminInstance - firebase-admin instance
* @param action - Action to run

@@ -11,4 +19,5 @@ * @param actionPath - Path in RTDB

*/
export declare function callRtdb(action: RTDBAction, actionPath: string, options?: CallRtdbOptions, data?: FixtureData | string | boolean): Promise<any>;
export declare function callRtdb(adminInstance: any, action: RTDBAction, actionPath: string, options?: CallRtdbOptions, data?: FixtureData | string | boolean): Promise<any>;
/**
* @param adminInstance - firebase-admin instance
* @param action - Action to run

@@ -20,23 +29,18 @@ * @param actionPath - Path to collection or document within Firestore

*/
export declare function callFirestore(action: FirestoreAction, actionPath: string, options?: CallFirestoreOptions, data?: FixtureData): Promise<any>;
export interface CustomTokenTaskSettings extends AppOptions {
uid: string;
customClaims?: Record<string, unknown>;
}
export declare function callFirestore(adminInstance: app.App, action: FirestoreAction, actionPath: string, options?: CallFirestoreOptions, data?: FixtureData): Promise<any>;
/**
* Create a custom token
* @param adminInstance - Admin SDK instance
* @param uid - UID of user for which the custom token will be generated
* @param settings - Settings object
* @returns Promise which resolves with a custom Firebase Auth token
*/
export declare function createCustomToken(settings: CustomTokenTaskSettings): Promise<string>;
export interface GetAuthUserTaskSettings extends AppOptions {
uid: string;
}
export declare function createCustomToken(adminInstance: any, uid: string, settings?: any): Promise<string>;
/**
* Get Firebase Auth user based on UID
* @param settings - Task settings
* @param settings.uid - UID of user for which the custom token will be generated
* @param settings.tenantId - Optional ID of tenant used for multi-tenancy
* @param adminInstance - Admin SDK instance
* @param uid - UID of user for which the custom token will be generated
* @param tenantId - Optional ID of tenant used for multi-tenancy
* @returns Promise which resolves with a custom Firebase Auth token
*/
export declare function getAuthUser(settings: GetAuthUserTaskSettings): Promise<UserRecord>;
export declare function getAuthUser(adminInstance: any, uid: string, tenantId?: string): Promise<auth.UserRecord>;

@@ -1,5 +0,1 @@

import { getDatabase } from 'firebase-admin/database';
import { getAuth as getFirebaseAuth, } from 'firebase-admin/auth';
import { getFirestore, Timestamp, GeoPoint, FieldValue, } from 'firebase-admin/firestore';
import { getApp } from 'firebase-admin/app';
import { slashPathToFirestoreRef, deleteCollection, isDocPath, } from './firebase-utils';

@@ -40,13 +36,10 @@ /**

* Get Firebase Auth or TenantAwareAuth instance, based on tenantId being provided
* @param authSettings - Optional ID of tenant used for multi-tenancy
* @param authSettings.tenantId - Optional ID of tenant used for multi-tenancy
* @param authSettings.appName - Optional name of Firebase app. Defaults to "[DEFAULT]"
* @param adminInstance - Admin SDK instance
* @param tenantId - Optional ID of tenant used for multi-tenancy
* @returns Firebase Auth or TenantAwareAuth instance
*/
function getAdminAuthWithTenantId(authSettings) {
const { tenantId, appName } = authSettings || {};
const authInstance = getFirebaseAuth(appName ? getApp(appName) : undefined);
function getAuth(adminInstance, tenantId) {
const auth = tenantId
? authInstance.tenantManager().authForTenant(tenantId)
: authInstance;
? adminInstance.auth().tenantManager().authForTenant(tenantId)
: adminInstance.auth();
return auth;

@@ -58,5 +51,6 @@ }

* @param dataVal - Value of data
* @param firestoreStatics - Statics from firestore instance
* @returns Value converted into timestamp object if possible
*/
function convertValueToTimestampOrGeoPointIfPossible(dataVal) {
export function convertValueToTimestampOrGeoPointIfPossible(dataVal, firestoreStatics) {
/* eslint-disable no-underscore-dangle */

@@ -66,3 +60,3 @@ if (dataVal?._methodName === 'serverTimestamp' ||

) {
return FieldValue.serverTimestamp();
return firestoreStatics.FieldValue.serverTimestamp();
}

@@ -72,3 +66,3 @@ if (dataVal?._methodName === 'deleteField' ||

) {
return FieldValue.delete();
return firestoreStatics.FieldValue.delete();
}

@@ -78,7 +72,7 @@ /* eslint-enable no-underscore-dangle */

typeof dataVal?.nanoseconds === 'number') {
return new Timestamp(dataVal.seconds, dataVal.nanoseconds);
return new firestoreStatics.Timestamp(dataVal.seconds, dataVal.nanoseconds);
}
if (typeof dataVal?.latitude === 'number' &&
typeof dataVal?.longitude === 'number') {
return new GeoPoint(dataVal.latitude, dataVal.longitude);
return new firestoreStatics.GeoPoint(dataVal.latitude, dataVal.longitude);
}

@@ -89,5 +83,10 @@ return dataVal;

* @param data - Data to be set in firestore
* @param firestoreStatics - Statics from Firestore object
* @returns Data to be set in firestore with timestamp
*/
function getDataWithTimestampsAndGeoPoints(data) {
function getDataWithTimestampsAndGeoPoints(data, firestoreStatics) {
// Exit if no statics are passed
if (!firestoreStatics) {
return data;
}
return Object.entries(data).reduce((acc, [currKey, currData]) => {

@@ -104,3 +103,3 @@ // Convert nested timestamp if item is an object

...acc,
[currKey]: getDataWithTimestampsAndGeoPoints(currData),
[currKey]: getDataWithTimestampsAndGeoPoints(currData, firestoreStatics),
};

@@ -110,8 +109,8 @@ }

? currData.map((dataItem) => {
const result = convertValueToTimestampOrGeoPointIfPossible(dataItem);
const result = convertValueToTimestampOrGeoPointIfPossible(dataItem, firestoreStatics);
return result.constructor === Object
? getDataWithTimestampsAndGeoPoints(result)
? getDataWithTimestampsAndGeoPoints(result, firestoreStatics)
: result;
})
: convertValueToTimestampOrGeoPointIfPossible(currData);
: convertValueToTimestampOrGeoPointIfPossible(currData, firestoreStatics);
return {

@@ -124,2 +123,3 @@ ...acc,

/**
* @param adminInstance - firebase-admin instance
* @param action - Action to run

@@ -131,3 +131,3 @@ * @param actionPath - Path in RTDB

*/
export async function callRtdb(action, actionPath, options, data) {
export async function callRtdb(adminInstance, action, actionPath, options, data) {
// Handle actionPath not being set (see #244 for more info)

@@ -138,4 +138,3 @@ if (!actionPath) {

try {
const dbInstance = getDatabase(options?.appName ? getApp(options.appName) : undefined);
const dbRef = dbInstance.ref(actionPath);
const dbRef = adminInstance.database().ref(actionPath);
if (action === 'get') {

@@ -169,2 +168,3 @@ const snap = await optionsToRtdbRef(dbRef, options).once('value');

/**
* @param adminInstance - firebase-admin instance
* @param action - Action to run

@@ -176,7 +176,6 @@ * @param actionPath - Path to collection or document within Firestore

*/
export async function callFirestore(action, actionPath, options, data) {
const firestoreInstance = getFirestore(getApp(options?.appName));
export async function callFirestore(adminInstance, action, actionPath, options, data) {
try {
if (action === 'get') {
const snap = await slashPathToFirestoreRef(firestoreInstance, actionPath, options).get();
const snap = await slashPathToFirestoreRef(adminInstance.firestore, actionPath, options).get();
if (snap?.docs?.length && typeof snap.docs.map === 'function') {

@@ -195,4 +194,4 @@ return snap.docs.map((docSnap) => ({

const deletePromise = isDocPath(actionPath)
? slashPathToFirestoreRef(firestoreInstance, actionPath, options).delete()
: deleteCollection(firestoreInstance, slashPathToFirestoreRef(firestoreInstance, actionPath, options), options);
? slashPathToFirestoreRef(adminInstance.firestore, actionPath, options).delete()
: deleteCollection(adminInstance.firestore(), slashPathToFirestoreRef(adminInstance.firestore, actionPath, options), options);
await deletePromise;

@@ -206,5 +205,9 @@ // Returning null in the case of falsey value prevents Cypress error with message:

}
const dataToSet = getDataWithTimestampsAndGeoPoints(data);
const dataToSet = getDataWithTimestampsAndGeoPoints(data,
// Use static option if passed (tests), otherwise fallback to statics on adminInstance
// Tests do not have statics since they are using @firebase/testing
options?.statics || adminInstance.firestore);
if (action === 'set') {
return firestoreInstance
return adminInstance
.firestore()
.doc(actionPath)

@@ -215,4 +218,4 @@ .set(dataToSet, options?.merge

}
// "update" action
return slashPathToFirestoreRef(firestoreInstance, actionPath, options)[action](dataToSet);
// "update" and "add" action
return slashPathToFirestoreRef(adminInstance.firestore, actionPath, options)[action](dataToSet);
}

@@ -228,20 +231,22 @@ catch (err) {

* Create a custom token
* @param adminInstance - Admin SDK instance
* @param uid - UID of user for which the custom token will be generated
* @param settings - Settings object
* @returns Promise which resolves with a custom Firebase Auth token
*/
export function createCustomToken(settings) {
export function createCustomToken(adminInstance, uid, settings) {
// Use custom claims or default to { isTesting: true }
const customClaims = settings?.customClaims || { isTesting: true };
// Create auth token
return getAdminAuthWithTenantId(settings).createCustomToken(settings.uid, customClaims);
return getAuth(adminInstance, settings.tenantId).createCustomToken(uid, customClaims);
}
/**
* Get Firebase Auth user based on UID
* @param settings - Task settings
* @param settings.uid - UID of user for which the custom token will be generated
* @param settings.tenantId - Optional ID of tenant used for multi-tenancy
* @param adminInstance - Admin SDK instance
* @param uid - UID of user for which the custom token will be generated
* @param tenantId - Optional ID of tenant used for multi-tenancy
* @returns Promise which resolves with a custom Firebase Auth token
*/
export function getAuthUser(settings) {
return getAdminAuthWithTenantId(settings).getUser(settings.uid);
export function getAuthUser(adminInstance, uid, tenantId) {
return getAuth(adminInstance, tenantId).getUser(uid);
}

@@ -1,3 +0,2 @@

import type { FieldValue } from 'firebase-admin/firestore';
import type { AppOptions } from './types';
import type { firestore } from 'firebase-admin';
/**

@@ -23,3 +22,3 @@ * Params for attachCustomCommand function for

}
type WhereOptions = [string, FirebaseFirestore.WhereFilterOp, any];
export type WhereOptions = [string, FirebaseFirestore.WhereFilterOp, any];
/**

@@ -30,7 +29,2 @@ * Options for callFirestore custom Cypress command.

/**
* Name of Firebase app. Defaults to Firebase's internal setting
* of "[DEFAULT]".
*/
appName?: string;
/**
* Whether or not to include createdAt and createdBy

@@ -68,3 +62,3 @@ */

*/
statics?: typeof FieldValue;
statics?: typeof firestore;
}

@@ -80,7 +74,2 @@ /**

/**
* Name of Firebase app. Defaults to Firebase's internal setting
* of "[DEFAULT]".
*/
appName?: string;
/**
* Whether or not to include meta data

@@ -140,4 +129,2 @@ */

}
export type LoginOptions = AppOptions;
export type LogoutOptions = AppOptions;
declare global {

@@ -153,5 +140,3 @@ namespace Cypress {

* @param customClaims - Custom claims to attach to the custom token
* @param options - Options
* @param options.appName - Optional name of firebase-admin app. Defaults to Firebase's default app (i.e DEFAULT)
* @param options.tenantId - Optional ID of tenant used for multi-tenancy. Can also be set with environment variable TEST_TENANT_ID
* @param tenantId - Optional ID of tenant used for multi-tenancy. Can also be set with environment variable TEST_TENANT_ID
* @example <caption>Env Based Login (TEST_UID)</caption>

@@ -162,13 +147,10 @@ * cy.login()

*/
login: (uid?: string, customClaims?: any, options?: LoginOptions) => Chainable;
login: (uid?: string, customClaims?: any, tenantId?: string) => Chainable;
/**
* Log current user out of Firebase Auth
* @see https://github.com/prescottprue/cypress-firebase#cylogout
* @param options - Options object
* @param options.appName - Optional name of firebase-admin app. Defaults to Firebase's default app (i.e DEFAULT)
* @param options.tenantId - Optional ID of tenant used for multi-tenancy. Can also be set with environment variable TEST_TENANT_ID
* @example
* cy.logout()
*/
logout: (options?: LogoutOptions) => Chainable;
logout: () => Chainable;
/**

@@ -235,5 +217,5 @@ * Call Real Time Database path with some specified action. Authentication is through

* custom command attachment
* @param customCommandOptions - Custom command options
* @param options - Custom command options
*/
export default function attachCustomCommands(context: AttachCustomCommandParams, customCommandOptions?: CustomCommandOptions): void;
export default function attachCustomCommands(context: AttachCustomCommandParams, options?: CustomCommandOptions): void;
export {};

@@ -36,19 +36,15 @@ "use strict";

* custom command attachment
* @param customCommandOptions - Custom command options
* @param options - Custom command options
*/
function attachCustomCommands(context, customCommandOptions) {
function attachCustomCommands(context, options) {
const { Cypress, cy, firebase, app } = context;
const defaultApp = app || firebase.app(); // select default app
/**
* Get firebase auth instance, with tenantId set if provided
* @param authOptions - Settings object
* @param authOptions.tenantId Optional tenant ID
* @param authOptions.appName - Name of app
* @param tenantId Optional tenant ID
* @returns firebase auth instance
*/
function getAuthWithTenantId(authOptions) {
const { tenantId = Cypress.env('TEST_TENANT_ID'), appName } = authOptions || {};
const browserAppInstance = app || firebase.app(appName); // select default app
const auth = browserAppInstance.auth();
// Check for undefined handles null values for removing tenant from instance
if (typeof tenantId !== 'undefined') {
function getAuth(tenantId) {
const auth = defaultApp.auth();
if (tenantId) {
auth.tenantId = tenantId;

@@ -64,3 +60,3 @@ }

*/
Cypress.Commands.add(customCommandOptions?.commandNames?.login || 'login', (uid, customClaims, options) => {
Cypress.Commands.add(options?.commandNames?.login || 'login', (uid, customClaims, tenantId = Cypress.env('TEST_TENANT_ID')) => {
const userUid = uid || Cypress.env('TEST_UID');

@@ -71,6 +67,6 @@ // Handle UID which is passed in

}
const auth = getAuthWithTenantId(options);
const auth = getAuth(tenantId);
// Resolve with current user if they already exist
if (auth.currentUser && userUid === auth.currentUser.uid) {
cy.log('Authenticated user already exists, login complete.');
cy.log('Authed user already exists, login complete.');
return undefined;

@@ -84,4 +80,3 @@ }

customClaims,
tenantId: auth.tenantId,
...options,
tenantId,
})

@@ -97,4 +92,4 @@ .then((customToken) => loginWithCustomToken(auth, customToken));

*/
Cypress.Commands.add(customCommandOptions?.commandNames?.logout || 'logout', (options) => new Promise((resolve, reject) => {
const auth = getAuthWithTenantId(options);
Cypress.Commands.add(options?.commandNames?.logout || 'logout', (tenantId = Cypress.env('TEST_TENANT_ID')) => new Promise((resolve, reject) => {
const auth = getAuth(tenantId);
auth.onAuthStateChanged((auth) => {

@@ -115,4 +110,3 @@ if (!auth) {

*/
Cypress.Commands.add(customCommandOptions?.commandNames?.callRtdb || 'callRtdb', (action, actionPath, dataOrOptions, options) => {
// TODO: Make exposed types dynamic to action (i.e. get has 3rd arg as options)
Cypress.Commands.add(options?.commandNames?.callRtdb || 'callRtdb', (action, actionPath, dataOrOptions, options) => {
const taskSettings = {

@@ -156,3 +150,3 @@ action,

*/
Cypress.Commands.add(customCommandOptions?.commandNames?.callFirestore || 'callFirestore', (action, actionPath, dataOrOptions, options) => {
Cypress.Commands.add(options?.commandNames?.callFirestore || 'callFirestore', (action, actionPath, dataOrOptions, options) => {
const taskSettings = {

@@ -195,4 +189,4 @@ action,

*/
Cypress.Commands.add(customCommandOptions?.commandNames?.getAuthUser || 'getAuthUser', (uid, options) => cy.task('getAuthUser', { uid, ...options }));
Cypress.Commands.add(options?.commandNames?.getAuthUser || 'getAuthUser', (uid) => cy.task('getAuthUser', uid));
}
exports.default = attachCustomCommands;

@@ -1,3 +0,3 @@

import type { CollectionReference, DocumentReference, Query } from 'firebase-admin/firestore';
import { CallFirestoreOptions } from './attachCustomCommands';
import type { AppOptions, app, firestore } from 'firebase-admin';
import { CallFirestoreOptions, WhereOptions } from './attachCustomCommands';
/**

@@ -10,2 +10,10 @@ * Check whether a value is a string or not

/**
* Initialize Firebase instance from service account (from either local
* serviceAccount.json or environment variables)
* @returns Initialized Firebase instance
* @param adminInstance - firebase-admin instance to initialize
* @param overrideConfig - firebase-admin instance to initialize
*/
export declare function initializeFirebase(adminInstance: any, overrideConfig?: AppOptions): app.App;
/**
* Check with or not a slash path is the path of a document

@@ -17,5 +25,11 @@ * @param slashPath - Path to check for whether or not it is a doc

/**
*
* @param ref
* @param whereSetting
* @param firestoreStatics
*/
export declare function applyWhere(ref: firestore.CollectionReference | firestore.Query, whereSetting: WhereOptions, firestoreStatics: app.App['firestore']): firestore.Query;
/**
* Convert slash path to Firestore reference
* @param firestoreInstance - Instance on which to
* create ref
* @param firestoreStatics - Firestore instance statics (invoking gets instance)
* @param slashPath - Path to convert into firestore reference

@@ -25,3 +39,3 @@ * @param options - Options object

*/
export declare function slashPathToFirestoreRef(firestoreInstance: any, slashPath: string, options?: CallFirestoreOptions): CollectionReference | DocumentReference | Query;
export declare function slashPathToFirestoreRef(firestoreStatics: app.App['firestore'], slashPath: string, options?: CallFirestoreOptions): firestore.CollectionReference | firestore.DocumentReference | firestore.Query;
/**

@@ -28,0 +42,0 @@ * @param db - Firestore database instance

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.deleteCollection = exports.slashPathToFirestoreRef = exports.isDocPath = exports.isString = void 0;
exports.deleteCollection = exports.slashPathToFirestoreRef = exports.applyWhere = exports.isDocPath = exports.initializeFirebase = exports.isString = void 0;
const node_utils_1 = require("./node-utils");
const tasks_1 = require("./tasks");
/**

@@ -14,2 +16,123 @@ * Check whether a value is a string or not

/**
* Get settings for Firestore from environment. Loads port and servicePath from
* FIRESTORE_EMULATOR_HOST node environment variable if found, otherwise
* defaults to port 8080 and servicePath "localhost".
* @returns Firestore settings to be passed to firebase.firestore().settings
*/
function firestoreSettingsFromEnv() {
const { FIRESTORE_EMULATOR_HOST } = process.env;
if (typeof FIRESTORE_EMULATOR_HOST === 'undefined' ||
!isString(FIRESTORE_EMULATOR_HOST)) {
return {
servicePath: 'localhost',
port: 8080,
};
}
const [servicePath, portStr] = FIRESTORE_EMULATOR_HOST.split(':');
return {
servicePath,
port: parseInt(portStr, 10),
};
}
/**
* @param adminInstance - firebase-admin instance to initialize
* @returns Firebase admin credential
*/
function getFirebaseCredential(adminInstance) {
const serviceAccount = (0, node_utils_1.getServiceAccount)();
// Add service account credential if it exists so that custom auth tokens can be generated
if (serviceAccount) {
return adminInstance.credential.cert(serviceAccount);
}
// Add default credentials if they exist
const defaultCredentials = adminInstance.credential.applicationDefault();
if (defaultCredentials) {
console.log('cypress-firebase: Using default credentials'); // eslint-disable-line no-console
return defaultCredentials;
}
}
/**
* Get default datbase url
* @param projectId - Project id
* @returns Default database url
*/
function getDefaultDatabaseUrl(projectId) {
const { FIREBASE_DATABASE_EMULATOR_HOST } = process.env;
return FIREBASE_DATABASE_EMULATOR_HOST
? `http://${FIREBASE_DATABASE_EMULATOR_HOST}?ns=${projectId || 'local'}`
: `https://${projectId}.firebaseio.com`;
}
/**
* Initialize Firebase instance from service account (from either local
* serviceAccount.json or environment variables)
* @returns Initialized Firebase instance
* @param adminInstance - firebase-admin instance to initialize
* @param overrideConfig - firebase-admin instance to initialize
*/
function initializeFirebase(adminInstance, overrideConfig) {
try {
// TODO: Look into using @firebase/testing in place of admin here to allow for
// usage of clearFirestoreData (see https://github.com/prescottprue/cypress-firebase/issues/73 for more info)
const { FIREBASE_DATABASE_EMULATOR_HOST } = process.env;
const fbConfig = {
// Initialize RTDB with databaseURL pointed to emulator if FIREBASE_DATABASE_EMULATOR_HOST is set
...overrideConfig,
};
if (FIREBASE_DATABASE_EMULATOR_HOST) {
/* eslint-disable no-console */
console.log('cypress-firebase: Using RTDB emulator with host:', FIREBASE_DATABASE_EMULATOR_HOST);
/* eslint-enable no-console */
}
if (process.env.FIREBASE_AUTH_EMULATOR_HOST) {
/* eslint-disable no-console */
console.log('cypress-firebase: Using Auth emulator with port:', process.env.FIREBASE_AUTH_EMULATOR_HOST);
/* eslint-enable no-console */
}
// Add credentials if they do not already exist - starting with application default, falling back to SERVICE_ACCOUNT env variable
if (!fbConfig.credential) {
const credential = getFirebaseCredential(adminInstance);
if (credential) {
fbConfig.credential = credential;
}
}
// Add projectId to fb config if it doesn't already exist
if (!fbConfig.projectId) {
const projectId = process.env.GCLOUD_PROJECT || fbConfig.credential?.projectId; // eslint-disable-line camelcase
if (projectId) {
fbConfig.projectId = projectId;
}
}
// Add databaseURL if it doesn't already exist
if (!fbConfig.databaseURL) {
const databaseURL = getDefaultDatabaseUrl(fbConfig.projectId);
if (databaseURL) {
fbConfig.databaseURL = databaseURL;
}
}
const fbInstance = adminInstance.initializeApp(fbConfig);
// Initialize Firestore with emulator host settings
if (process.env.FIRESTORE_EMULATOR_HOST) {
const firestoreSettings = firestoreSettingsFromEnv();
/* eslint-disable no-console */
console.log('cypress-firebase: Using Firestore emulator with settings:', firestoreSettings);
/* eslint-enable no-console */
adminInstance.firestore().settings(firestoreSettings);
}
/* eslint-disable no-console */
const dbUrlLog = fbConfig.databaseURL
? ` and databaseURL "${fbConfig.databaseURL}"`
: '';
console.log(`cypress-firebase: Initialized Firebase app for project "${fbConfig.projectId}"${dbUrlLog}`);
/* eslint-enable no-console */
return fbInstance;
}
catch (err) {
/* eslint-disable no-console */
console.error('cypress-firebase: Error initializing firebase-admin instance:', err instanceof Error && err.message);
/* eslint-enable no-console */
throw err;
}
}
exports.initializeFirebase = initializeFirebase;
/**
* Check with or not a slash path is the path of a document

@@ -24,5 +147,15 @@ * @param slashPath - Path to check for whether or not it is a doc

/**
*
* @param ref
* @param whereSetting
* @param firestoreStatics
*/
function applyWhere(ref, whereSetting, firestoreStatics) {
const [param, filterOp, val] = whereSetting;
return ref.where(param, filterOp, (0, tasks_1.convertValueToTimestampOrGeoPointIfPossible)(val, firestoreStatics));
}
exports.applyWhere = applyWhere;
/**
* Convert slash path to Firestore reference
* @param firestoreInstance - Instance on which to
* create ref
* @param firestoreStatics - Firestore instance statics (invoking gets instance)
* @param slashPath - Path to convert into firestore reference

@@ -32,9 +165,11 @@ * @param options - Options object

*/
function slashPathToFirestoreRef(firestoreInstance, slashPath, options) {
function slashPathToFirestoreRef(firestoreStatics, slashPath, options) {
if (!slashPath) {
throw new Error('Path is required to make Firestore Reference');
}
let ref = isDocPath(slashPath)
? firestoreInstance.doc(slashPath)
: firestoreInstance.collection(slashPath);
const firestoreInstance = firestoreStatics();
if (isDocPath(slashPath)) {
return firestoreInstance.doc(slashPath);
}
let ref = firestoreInstance.collection(slashPath);
// Apply orderBy to query if it exists

@@ -54,6 +189,7 @@ if (options?.orderBy && typeof ref.orderBy === 'function') {

if (Array.isArray(options.where[0])) {
ref = ref.where(...options.where[0]).where(...options.where[1]);
const [where1, where2] = options.where;
ref = applyWhere(applyWhere(ref, where1, options.statics || firestoreStatics), where2, options.statics || firestoreStatics);
}
else {
ref = ref.where(...options.where);
ref = applyWhere(ref, options.where, options.statics || firestoreStatics);
}

@@ -60,0 +196,0 @@ }

@@ -0,1 +1,2 @@

import type { AppOptions } from 'firebase-admin';
import { ExtendedCypressConfig } from './extendWithFirebaseConfig';

@@ -10,4 +11,6 @@ /**

* @param cypressConfig - Cypress config
* @param adminInstance - firebase-admin instance
* @param overrideConfig - Override config for firebase instance
* @returns Extended Cypress config
*/
export default function pluginWithTasks(cypressOnFunc: Cypress.PluginEvents, cypressConfig: Partial<Cypress.PluginConfigOptions>): ExtendedCypressConfig;
export default function pluginWithTasks(cypressOnFunc: Cypress.PluginEvents, cypressConfig: Partial<Cypress.PluginConfigOptions>, adminInstance: any, overrideConfig?: AppOptions): ExtendedCypressConfig;

@@ -6,2 +6,3 @@ "use strict";

const tasks = tslib_1.__importStar(require("./tasks"));
const firebase_utils_1 = require("./firebase-utils");
/**

@@ -15,8 +16,23 @@ * Cypress plugin which attaches tasks used by custom commands

* @param cypressConfig - Cypress config
* @param adminInstance - firebase-admin instance
* @param overrideConfig - Override config for firebase instance
* @returns Extended Cypress config
*/
function pluginWithTasks(cypressOnFunc, cypressConfig) {
function pluginWithTasks(cypressOnFunc, cypressConfig, adminInstance, overrideConfig) {
// Only initialize admin instance if it hasn't already been initialized
if (adminInstance.apps?.length === 0) {
(0, firebase_utils_1.initializeFirebase)(adminInstance, overrideConfig);
}
const tasksWithFirebase = Object.keys(tasks).reduce((acc, taskName) => {
acc[taskName] = (taskSettings) => {
if (taskSettings?.uid) {
return tasks[taskName](adminInstance, taskSettings.uid, taskSettings);
}
const { action, path: actionPath, options = {}, data } = taskSettings;
return tasks[taskName](adminInstance, action, actionPath, options, data);
};
return acc;
}, {});
// Attach tasks to Cypress using on function
// NOTE: any is used because cypress doesn't export Task or Tasks types
cypressOnFunc('task', tasks);
cypressOnFunc('task', tasksWithFirebase);
// Return extended config

@@ -23,0 +39,0 @@ return (0, extendWithFirebaseConfig_1.default)(cypressConfig);

@@ -1,5 +0,13 @@

import { UserRecord } from 'firebase-admin/auth';
import type { firestore, auth, app } from 'firebase-admin';
import { FixtureData, FirestoreAction, RTDBAction, CallRtdbOptions, CallFirestoreOptions } from './attachCustomCommands';
import { AppOptions } from './types';
/**
* Convert unique data types which have been stringified and parsed back
* into their original type.
* @param dataVal - Value of data
* @param firestoreStatics - Statics from firestore instance
* @returns Value converted into timestamp object if possible
*/
export declare function convertValueToTimestampOrGeoPointIfPossible(dataVal: any, firestoreStatics: typeof firestore): firestore.FieldValue;
/**
* @param adminInstance - firebase-admin instance
* @param action - Action to run

@@ -11,4 +19,5 @@ * @param actionPath - Path in RTDB

*/
export declare function callRtdb(action: RTDBAction, actionPath: string, options?: CallRtdbOptions, data?: FixtureData | string | boolean): Promise<any>;
export declare function callRtdb(adminInstance: any, action: RTDBAction, actionPath: string, options?: CallRtdbOptions, data?: FixtureData | string | boolean): Promise<any>;
/**
* @param adminInstance - firebase-admin instance
* @param action - Action to run

@@ -20,23 +29,18 @@ * @param actionPath - Path to collection or document within Firestore

*/
export declare function callFirestore(action: FirestoreAction, actionPath: string, options?: CallFirestoreOptions, data?: FixtureData): Promise<any>;
export interface CustomTokenTaskSettings extends AppOptions {
uid: string;
customClaims?: Record<string, unknown>;
}
export declare function callFirestore(adminInstance: app.App, action: FirestoreAction, actionPath: string, options?: CallFirestoreOptions, data?: FixtureData): Promise<any>;
/**
* Create a custom token
* @param adminInstance - Admin SDK instance
* @param uid - UID of user for which the custom token will be generated
* @param settings - Settings object
* @returns Promise which resolves with a custom Firebase Auth token
*/
export declare function createCustomToken(settings: CustomTokenTaskSettings): Promise<string>;
export interface GetAuthUserTaskSettings extends AppOptions {
uid: string;
}
export declare function createCustomToken(adminInstance: any, uid: string, settings?: any): Promise<string>;
/**
* Get Firebase Auth user based on UID
* @param settings - Task settings
* @param settings.uid - UID of user for which the custom token will be generated
* @param settings.tenantId - Optional ID of tenant used for multi-tenancy
* @param adminInstance - Admin SDK instance
* @param uid - UID of user for which the custom token will be generated
* @param tenantId - Optional ID of tenant used for multi-tenancy
* @returns Promise which resolves with a custom Firebase Auth token
*/
export declare function getAuthUser(settings: GetAuthUserTaskSettings): Promise<UserRecord>;
export declare function getAuthUser(adminInstance: any, uid: string, tenantId?: string): Promise<auth.UserRecord>;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAuthUser = exports.createCustomToken = exports.callFirestore = exports.callRtdb = void 0;
const database_1 = require("firebase-admin/database");
const auth_1 = require("firebase-admin/auth");
const firestore_1 = require("firebase-admin/firestore");
const app_1 = require("firebase-admin/app");
exports.getAuthUser = exports.createCustomToken = exports.callFirestore = exports.callRtdb = exports.convertValueToTimestampOrGeoPointIfPossible = void 0;
const firebase_utils_1 = require("./firebase-utils");

@@ -43,13 +39,10 @@ /**

* Get Firebase Auth or TenantAwareAuth instance, based on tenantId being provided
* @param authSettings - Optional ID of tenant used for multi-tenancy
* @param authSettings.tenantId - Optional ID of tenant used for multi-tenancy
* @param authSettings.appName - Optional name of Firebase app. Defaults to "[DEFAULT]"
* @param adminInstance - Admin SDK instance
* @param tenantId - Optional ID of tenant used for multi-tenancy
* @returns Firebase Auth or TenantAwareAuth instance
*/
function getAdminAuthWithTenantId(authSettings) {
const { tenantId, appName } = authSettings || {};
const authInstance = (0, auth_1.getAuth)(appName ? (0, app_1.getApp)(appName) : undefined);
function getAuth(adminInstance, tenantId) {
const auth = tenantId
? authInstance.tenantManager().authForTenant(tenantId)
: authInstance;
? adminInstance.auth().tenantManager().authForTenant(tenantId)
: adminInstance.auth();
return auth;

@@ -61,5 +54,6 @@ }

* @param dataVal - Value of data
* @param firestoreStatics - Statics from firestore instance
* @returns Value converted into timestamp object if possible
*/
function convertValueToTimestampOrGeoPointIfPossible(dataVal) {
function convertValueToTimestampOrGeoPointIfPossible(dataVal, firestoreStatics) {
/* eslint-disable no-underscore-dangle */

@@ -69,3 +63,3 @@ if (dataVal?._methodName === 'serverTimestamp' ||

) {
return firestore_1.FieldValue.serverTimestamp();
return firestoreStatics.FieldValue.serverTimestamp();
}

@@ -75,3 +69,3 @@ if (dataVal?._methodName === 'deleteField' ||

) {
return firestore_1.FieldValue.delete();
return firestoreStatics.FieldValue.delete();
}

@@ -81,15 +75,21 @@ /* eslint-enable no-underscore-dangle */

typeof dataVal?.nanoseconds === 'number') {
return new firestore_1.Timestamp(dataVal.seconds, dataVal.nanoseconds);
return new firestoreStatics.Timestamp(dataVal.seconds, dataVal.nanoseconds);
}
if (typeof dataVal?.latitude === 'number' &&
typeof dataVal?.longitude === 'number') {
return new firestore_1.GeoPoint(dataVal.latitude, dataVal.longitude);
return new firestoreStatics.GeoPoint(dataVal.latitude, dataVal.longitude);
}
return dataVal;
}
exports.convertValueToTimestampOrGeoPointIfPossible = convertValueToTimestampOrGeoPointIfPossible;
/**
* @param data - Data to be set in firestore
* @param firestoreStatics - Statics from Firestore object
* @returns Data to be set in firestore with timestamp
*/
function getDataWithTimestampsAndGeoPoints(data) {
function getDataWithTimestampsAndGeoPoints(data, firestoreStatics) {
// Exit if no statics are passed
if (!firestoreStatics) {
return data;
}
return Object.entries(data).reduce((acc, [currKey, currData]) => {

@@ -106,3 +106,3 @@ // Convert nested timestamp if item is an object

...acc,
[currKey]: getDataWithTimestampsAndGeoPoints(currData),
[currKey]: getDataWithTimestampsAndGeoPoints(currData, firestoreStatics),
};

@@ -112,8 +112,8 @@ }

? currData.map((dataItem) => {
const result = convertValueToTimestampOrGeoPointIfPossible(dataItem);
const result = convertValueToTimestampOrGeoPointIfPossible(dataItem, firestoreStatics);
return result.constructor === Object
? getDataWithTimestampsAndGeoPoints(result)
? getDataWithTimestampsAndGeoPoints(result, firestoreStatics)
: result;
})
: convertValueToTimestampOrGeoPointIfPossible(currData);
: convertValueToTimestampOrGeoPointIfPossible(currData, firestoreStatics);
return {

@@ -126,2 +126,3 @@ ...acc,

/**
* @param adminInstance - firebase-admin instance
* @param action - Action to run

@@ -133,3 +134,3 @@ * @param actionPath - Path in RTDB

*/
async function callRtdb(action, actionPath, options, data) {
async function callRtdb(adminInstance, action, actionPath, options, data) {
// Handle actionPath not being set (see #244 for more info)

@@ -140,4 +141,3 @@ if (!actionPath) {

try {
const dbInstance = (0, database_1.getDatabase)(options?.appName ? (0, app_1.getApp)(options.appName) : undefined);
const dbRef = dbInstance.ref(actionPath);
const dbRef = adminInstance.database().ref(actionPath);
if (action === 'get') {

@@ -172,2 +172,3 @@ const snap = await optionsToRtdbRef(dbRef, options).once('value');

/**
* @param adminInstance - firebase-admin instance
* @param action - Action to run

@@ -179,7 +180,6 @@ * @param actionPath - Path to collection or document within Firestore

*/
async function callFirestore(action, actionPath, options, data) {
const firestoreInstance = (0, firestore_1.getFirestore)((0, app_1.getApp)(options?.appName));
async function callFirestore(adminInstance, action, actionPath, options, data) {
try {
if (action === 'get') {
const snap = await (0, firebase_utils_1.slashPathToFirestoreRef)(firestoreInstance, actionPath, options).get();
const snap = await (0, firebase_utils_1.slashPathToFirestoreRef)(adminInstance.firestore, actionPath, options).get();
if (snap?.docs?.length && typeof snap.docs.map === 'function') {

@@ -198,4 +198,4 @@ return snap.docs.map((docSnap) => ({

const deletePromise = (0, firebase_utils_1.isDocPath)(actionPath)
? (0, firebase_utils_1.slashPathToFirestoreRef)(firestoreInstance, actionPath, options).delete()
: (0, firebase_utils_1.deleteCollection)(firestoreInstance, (0, firebase_utils_1.slashPathToFirestoreRef)(firestoreInstance, actionPath, options), options);
? (0, firebase_utils_1.slashPathToFirestoreRef)(adminInstance.firestore, actionPath, options).delete()
: (0, firebase_utils_1.deleteCollection)(adminInstance.firestore(), (0, firebase_utils_1.slashPathToFirestoreRef)(adminInstance.firestore, actionPath, options), options);
await deletePromise;

@@ -209,5 +209,9 @@ // Returning null in the case of falsey value prevents Cypress error with message:

}
const dataToSet = getDataWithTimestampsAndGeoPoints(data);
const dataToSet = getDataWithTimestampsAndGeoPoints(data,
// Use static option if passed (tests), otherwise fallback to statics on adminInstance
// Tests do not have statics since they are using @firebase/testing
options?.statics || adminInstance.firestore);
if (action === 'set') {
return firestoreInstance
return adminInstance
.firestore()
.doc(actionPath)

@@ -218,4 +222,4 @@ .set(dataToSet, options?.merge

}
// "update" action
return (0, firebase_utils_1.slashPathToFirestoreRef)(firestoreInstance, actionPath, options)[action](dataToSet);
// "update" and "add" action
return (0, firebase_utils_1.slashPathToFirestoreRef)(adminInstance.firestore, actionPath, options)[action](dataToSet);
}

@@ -232,10 +236,12 @@ catch (err) {

* Create a custom token
* @param adminInstance - Admin SDK instance
* @param uid - UID of user for which the custom token will be generated
* @param settings - Settings object
* @returns Promise which resolves with a custom Firebase Auth token
*/
function createCustomToken(settings) {
function createCustomToken(adminInstance, uid, settings) {
// Use custom claims or default to { isTesting: true }
const customClaims = settings?.customClaims || { isTesting: true };
// Create auth token
return getAdminAuthWithTenantId(settings).createCustomToken(settings.uid, customClaims);
return getAuth(adminInstance, settings.tenantId).createCustomToken(uid, customClaims);
}

@@ -245,10 +251,10 @@ exports.createCustomToken = createCustomToken;

* Get Firebase Auth user based on UID
* @param settings - Task settings
* @param settings.uid - UID of user for which the custom token will be generated
* @param settings.tenantId - Optional ID of tenant used for multi-tenancy
* @param adminInstance - Admin SDK instance
* @param uid - UID of user for which the custom token will be generated
* @param tenantId - Optional ID of tenant used for multi-tenancy
* @returns Promise which resolves with a custom Firebase Auth token
*/
function getAuthUser(settings) {
return getAdminAuthWithTenantId(settings).getUser(settings.uid);
function getAuthUser(adminInstance, uid, tenantId) {
return getAuth(adminInstance, tenantId).getUser(uid);
}
exports.getAuthUser = getAuthUser;
{
"name": "cypress-firebase",
"version": "2.3.0-alpha.2",
"version": "3.0.0-beta.1",
"description": "Utilities to help testing Firebase projects with Cypress.",

@@ -31,18 +31,18 @@ "main": "lib/index.js",

"devDependencies": {
"@commitlint/cli": "17.1.2",
"@commitlint/config-conventional": "17.0.3",
"@firebase/rules-unit-testing": "2.0.4",
"@commitlint/cli": "17.3.0",
"@commitlint/config-conventional": "17.3.0",
"@firebase/rules-unit-testing": "2.0.5",
"@istanbuljs/nyc-config-typescript": "1.0.2",
"@size-limit/preset-small-lib": "8.0.1",
"@size-limit/preset-small-lib": "8.1.0",
"@size-limit/webpack": "8.1.0",
"@tsconfig/node16": "1.0.3",
"@types/chai": "4.3.4",
"@types/mocha": "9.1.1",
"@types/node": "16.18.10",
"@types/mocha": "10.0.1",
"@types/node": "16.18.11",
"@types/sinon-chai": "3.2.9",
"@typescript-eslint/eslint-plugin": "5.46.1",
"@typescript-eslint/parser": "5.46.1",
"@typescript-eslint/eslint-plugin": "5.47.1",
"@typescript-eslint/parser": "5.47.1",
"chai": "4.3.7",
"cypress": "12.2.0",
"eslint": "8.29.0",
"eslint": "8.31.0",
"eslint-config-airbnb-base": "15.0.0",

@@ -55,14 +55,14 @@ "eslint-config-prettier": "8.5.0",

"eslint-plugin-prettier": "4.2.1",
"firebase": "9.9.4",
"firebase": "9.15.0",
"firebase-admin": "11.4.1",
"firebase-tools": "11.8.0",
"husky": "8.0.1",
"lint-staged": "13.0.3",
"mocha": "10.0.0",
"firebase-tools": "11.19.0",
"husky": "8.0.2",
"lint-staged": "13.1.0",
"mocha": "10.2.0",
"nyc": "15.1.0",
"prettier": "2.8.1",
"rimraf": "3.0.2",
"sinon": "14.0.0",
"sinon": "15.0.1",
"sinon-chai": "3.7.0",
"size-limit": "8.0.1",
"size-limit": "8.1.0",
"ts-node": "10.9.1",

@@ -136,3 +136,3 @@ "typescript": "4.9.4"

"import": "{ plugin }",
"limit": "2.75kb",
"limit": "3kb",
"webpack": false

@@ -139,0 +139,0 @@ }

@@ -28,5 +28,4 @@ # cypress-firebase

1. If you do not already have it installed, install Cypress and firebase-admin and add them to your package file: `npm i --save-dev cypress firebase-admin` or `yarn add -D cypress firebase-admin`
1. If you do not already have it installed, install Cypress and add it to your package file: `npm i --save-dev cypress` or `yarn add -D cypress`
1. Make sure you have a `cypress` folder containing Cypress tests
1. `cypress-firebase` v3 uses firebase-admin v11 - if you want to use an earlier version of firebase-admin, use `^2` versions of `cypress-firebase`

@@ -42,9 +41,6 @@ ### Setup

```js
import { initializeApp } from 'firebase-admin/app';
import admin from 'firebase-admin';
import { defineConfig } from 'cypress';
import { plugin as cypressFirebasePlugin } from 'cypress-firebase';
// Initialize firebase-admin default app
initializeApp();
const cypressConfig = defineConfig({

@@ -71,7 +67,4 @@ e2e: {

const cypressFirebasePlugin = require('cypress-firebase').plugin;
const { initializeApp } = require('firebase-admin/app');
const admin = require('firebase-admin');
// Initialize firebase-admin default app
initializeApp();
module.exports = defineConfig({

@@ -415,3 +408,3 @@ e2e: {

Plugin attaches cypress tasks, which are called by custom commands, and initializes firebase-admin instance. By default cypress-firebase internally initializes firebase-admin using `GCLOUD_PROJECT` environment variable for project identification and application-default credentials (set by providing path to service account in `GOOGLE_APPLICATION_CREDENTIALS` environment variable) [matching Google documentation](https://firebase.google.com/docs/admin/setup#initialize-sdk).
Plugin attaches cypress tasks, which are called by custom commands, and initializes firebase-admin instance. By default cypress-firebase internally initializes firebase-admin using `GCLOUD_PROJECT` environment variable for project identification and application-default credentials (set by providing path to service account in `GOOGLE_APPLICATION_CREDENTIALS` environment variable) [matching Google documentation](https://firebase.google.com/docs/admin/setup#initialize-sdk). This default functionality can be overriden by passing a forth argument to the plugin - this argument is passed directly into the firebase-admin instance as [AppOptions](https://firebase.google.com/docs/reference/admin/dotnet/class/firebase-admin/app-options#constructors-and-destructors) on init which means any other config such as `databaseURL`, `credential`, or `databaseAuthVariableOverride` can be included.

@@ -429,3 +422,3 @@ ```js

// e2e testing node events setup code
return cypressFirebasePlugin(on, config);
return cypressFirebasePlugin(on, config, admin);
// NOTE: If not setting GCLOUD_PROJECT env variable, project can be set like so:

@@ -432,0 +425,0 @@ // return cypressFirebasePlugin(on, config, admin, { projectId: 'some-project' });

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc