Testing Firebase Cloud Functions: Using admin SDK with jest - firebase

What is the best way to use the Firebase admin SDK in jest script to get Firestore information?
I am unable to use the admin SDK in my jest scripts. Running admin.firestore() throws:
Total timeout of API google.firestore.v1.Firestore exceeded 600000 milliseconds before any response was received.
My code:
let issueRefundWix: HttpsCallable<unknown, unknown>,
initApp = async () => {
const serAcc: ServiceAccount = await import(GOOGLE_APPLICATION_CREDENTIALS);
const app = initializeApp(firebaseConfig);
const functions = getFunctions(app, "us-central1");
const db = getFirestore(app);
const auth = getAuth(app);
admin.initializeApp({
credential: admin.credential.cert(serAcc),
storageBucket: "gs://**.appspot.com/",
});
return {
app: app,
functions: functions,
db: db,
auth: auth,
};
};
beforeAll(async () => {
const app = await initApp();
const functions = app.functions;
issueRefundWix = httpsCallable(functions, "issueRefundWix");
db = admin.firestore();
uid = await createWixUser();
exampleSubscription.wixId = uid;
console.log("Finished beforeAll()");
});
This set up works fine when I am running tests on the Firebase Emulator. Can I test functions the same on the emulator as when they are deployed? All tests work fine if I remove any dependencies on admin.firestore().
What is the best way to use the admin SDK to query firestore in jest tests?
Thank you

Related

Add admin sdk to an existing initialized app

Is there a way to add admin sdk to an app that's initialized by client firebase already? When I tried to initialize both, it throws an error saying that I can't initialize default app twice. I need client side firebase for login etc, but admin for managing users, both in the same app/website.
What I did so far:
const firebaseConfig = {
...
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
var admin = require("firebase-admin");
admin.initializeApp({
'credentrial': ...
}); //error
var serviceAccount = require("./api_key.json");
var admin_auth = admin.auth();
export { auth, db, admin_auth };
Thanks!

Firebase CLI: execute arbitrary Firebase Authentication requests to Firebase Emulator

Using the Firebase CLI, I'm executing arbitrary JavaScript that's stored in a file. In that script, I'm trying to make calls to admin.auth().something(), but it doesn't work against the Firebase Emulator. This is in contrast to making calls to Firestore, which works perfectly fine with the Emulator.
Firestore (everything works)
GCP
This makes calls to Firestore on GCP and it succeeds:
const admin = require('firebase-admin');
admin.initializeApp({ projectId: 'my-project' });
const db = admin.firestore();
(async () => {
const widget = await db.doc('/widgets/123456789').get();
console.log(widget.data().name);
})();
Emulator
This also succeeds:
const admin = require('#firebase/testing');
const db = admin
.initializeAdminApp({ projectId: 'my-project' })
.firestore();
(async () => {
const widget = await db.doc('/widgets/123456789').get();
console.log(widget.data().name);
})();
Firebase Auth (GCP works but Emulator does not)
GCP
This makes calls to Firebase Auth on GCP and it succeeds:
const admin = require('firebase-admin');
admin.initializeApp({ projectId: 'my-project' });
(async () => {
const user = await admin
.auth()
.getUser('user123456789');
console.log(user.email);
})();
Emulator
This fails:
const admin = require('#firebase/testing');
const auth = admin
.initializeAdminApp({ projectId: 'my-project' })
.auth();
(async () => {
const user = await auth.getUser('user123456789');
console.log(user.email);
})();
The error message is:
C:\Users\...\node_modules\#firebase\testing\node_modules\#firebase\component\dist\index.cjs.js:134
throw e;
^
[t [Error]: Your API key is invalid, please check you have copied it correctly.] {
code: 'auth/invalid-api-key',
a: null
}
I'm not sure what API key they're referring to, as the request is against the Emulator. How can I execute Firebase Auth requests against the Emulator using Firebase CLI?
To send requests against Firebase Emulator Auth, set the following environment variables and use the standard Firebase Admin SDK (firebase-admin) instead of using #firebase/testing:
process.env.FIREBASE_AUTH_EMULATOR_HOST = 'localhost:9099';
process.env.FIRESTORE_EMULATOR_HOST = 'localhost:8080';
const admin = require('firebase-admin');
admin.initializeApp({ projectId: 'emulator projectId' });
Now this works:
(async () => {
const user = await auth.getUser('user123456789');
console.log(user.email);
})();

How to write to firestore emulator?

I am developing a firebase cloud function that writes to a firestore database.
During development I want the function to write to a local database. So I've started a firestore emulator. But the data is still written to the actual database.
How can I configure the cloud functions to use the local database?
This is my setup:
import * as functions from 'firebase-functions';
import * as cors from "cors";
import * as admin from "firebase-admin";
const REGION = "europe-west1";
const COLLECTION_CONTACT_FORM = "contact_form";
const serviceAccount = require("../keys/auth-key.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
const corsMiddleware = cors({origin: true});
export const sendContactForm = functions.region(REGION).https.onRequest((request, response) => corsMiddleware(request, response, async () => {
let {text} = request.body;
let result = await admin.firestore().collection(COLLECTION_CONTACT_FORM).add({text});
response.send((result.id));
}));
This is the console output when starting the emulator:
[1] i firestore: Serving WebChannel traffic on at http://localhost:8081
[1] i firestore: Emulator logging to firestore-debug.log
[1] ✔ functions: Emulator started at http://localhost:5000
[1] ✔ firestore: Emulator started at http://localhost:8080
[1] i functions: Watching "path/functions" for Cloud Functions...
[1] ⚠ functions: Your GOOGLE_APPLICATION_CREDENTIALS environment variable points to path/keys/auth-key.json. Non-emulated services will access production using these credentials. Be careful!
[1] ✔ functions[sendContactForm]: http function initialized (http://localhost:5000/project/europe-west1/sendContactForm).
When triggering the local endpoint, the production database is written to.
The firestore admin initializeApp() will correctly handle switching between local emulator and production database depending on where it is running. So if you simply remove the service account credentials it should work properly:
import * as functions from 'firebase-functions';
import * as cors from "cors";
import * as admin from "firebase-admin";
const REGION = "europe-west1";
const COLLECTION_CONTACT_FORM = "contact_form";
admin.initializeApp();
const corsMiddleware = cors({origin: true});
export const sendContactForm = functions.region(REGION).https.onRequest((request, response) => corsMiddleware(request, response, async () => {
let {text} = request.body;
let result = await admin.firestore().collection(COLLECTION_CONTACT_FORM).add({text});
response.send((result.id));
}));
But if for some reason you're trying to write to a firestore database outside of the one that the project is created in, you can use firestore/grpc separately from the firebase classes and then use the environment to either include your service account credentials or location emulator credentials. A local emulator example:
const {Firestore} = require('#google-cloud/firestore');
const {credentials} = require('#grpc/grpc-js');
const db = new Firestore({
projectId: 'my-project-id',
servicePath: 'localhost',
port: 5100,
sslCreds: credentials.createInsecure(),
customHeaders: {
"Authorization": "Bearer owner"
}
});
await db.collection("mycollection").doc("someid").set({ test: "value" });
Same answer, but with the docId set dynamically.
exports.makeUppercase = functions.firestore.document('Messages/{docId}').onCreate((snap, context) => {
const original = snap.data().original;
functions.logger.log('Uppercasing', context.params.docId, original);
const uppercase = original.toUpperCase();
// return snap.ref.set({ uppercase }, { merge: true });
return admin.firestore().collection('AnotherCollection').doc(context.params.docId).set({ uppercase }, { merge: true });
});
This grabs the docId that was set dynamically and uses it to write to a document with the same name but in a different collection.
Also I left in commented code for writing to the same document in the same collection. Beware that using onUpdate or onWrite instead of onCreate makes an infinite loop as each write triggers the function again!

Firestore Cloud Function Times Out When called

I have a custom endpoint setup for my FireStore database.
For now, all I want is to print all values to console, but when I call it from a client, the request times out and the console only says:
#firebase/database: FIREBASE WARNING: The Firebase database
'project-name' has been disabled by a database owner.
(https://project-name-de56eb8.firebaseio.com)
Here's my code. Can anyone tell me what is (what thins are) wrong with it?
const util = require('util');
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const language = require('#google-cloud/language');
const client = new language.LanguageServiceClient();
const express = require('express');
const app = express();
app.post('/calculateAverage', async (request, response) => {
const bodyUserId = request.body.id
let query = admin.database().ref(`/user_info/`);
try {
const snapshot = await query.once('value');
snapshot.forEach((childSnapshot) => {
console.log("key: " + childSnapshot.key + " value: " + childSnapshot.val())
});
response.send({"snapshot await": "ok"});
} catch(error) {
console.log('Error getting messages', error.message);
response.send({"snapshot await error": error.message});
}
});
exports.api = functions.https.onRequest(app);
The problem is that you no use firebase realtime data.
in the options of firebase you have database and next *Cloud Firestore and
*Realtime Database, select Realtime Database and after, active this option and with this the solution

Cloud Storage API doesn't work when deploy on Google Cloud Functions using Firebase

This work perfectly in local serve with firebase :
const gCloudConfig = {
projectId: 'XXXX-X1234',
keyFilename: './key.json'
}
const Storage = require('#google-cloud/storage')(gCloudConfig);
const storageBucket = Storage.bucket(bucketUrl);
storageBucket.upload(file.path, {destination: file.name})
.then(() => {
//
});
But this doesn't work when i deploy to firebase :
const Storage = require('#google-cloud/storage')();
const storageBucket = Storage.bucket(bucketUrl);
storageBucket.upload(file.path, {destination: file.name})
.then(() => {
//
});
I put this line after the admin.initializeApp(...), since i saw that it fixed the problem for someone, but it still doesn't work.
I've tried a lot of stuff :
const gCloudConfig = { projectId: 'XXXX-X1234' };
const gCloudConfig = { key: API_KEY };
const gCloudConfig = { key: API_KEY, projectId: 'XXXX-X1234' };
const gCloudConfig = functions.config().firebase;
I'm kinda lost, please help me !
It's easier if you just initialize the Firebase Admin SDK with its default credentials, then access the Cloud Storage APIs via that. There's no need to initialize Storage on its own.
const admin = require('firebase-admin')
admin.initializeApp()
const bucket = admin.storage().bucket()
bucket.upload(localPath, {
destination: remotePath
})
Here, bucket is your project default storage bucket, just like you would have gotten it from the Cloud Storage API.
Note that the no-argument init of the Admin SDK is available when using firebase-functions#1.0.0 or later (current 1.0.2).

Resources