TypeError in "path" when running Firebase Functions Test - javascript

I'm writing a test for a google cloud function which'll write some information to a firestore database. The test uses firebase-functions-test and jest. The function I'm writing works successfully when I deploy it but when I try to run the test I get:
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type object
at GrpcClient.loadProto (node_modules/google-gax/src/grpc.ts:182:23)
at new FirestoreClient (node_modules/#google-cloud/firestore/build/src/v1/firestore_client.js:113:32)
at ClientPool.Firestore._clientPool.pool_1.ClientPool [as clientFactory] (node_modules/#google-cloud/firestore/build/src/index.js:319:26)
at ClientPool.acquire (node_modules/#google-cloud/firestore/build/src/pool.js:81:35)
at ClientPool.run (node_modules/#google-cloud/firestore/build/src/pool.js:155:29)
at Firestore.request (node_modules/#google-cloud/firestore/build/src/index.js:885:33)
at WriteBatch.commit_ (node_modules/#google-cloud/firestore/build/src/write-batch.js:450:14)
My function:
const admin = require('firebase-admin');
const functions = require('firebase-functions');
const db = admin.firestore();
const saveCheckup = functions.pubsub.topic('save-test').onPublish((message) => {
const {url, ...dataToSave} = message.attributes;
let current = db.collection('current').doc(url);
current.set(dataToSave, {merge: true})
return true;
});
module.exports = saveCheckup;
My test:
import * as admin from 'firebase-admin';
const testEnv = require('firebase-functions-test')(
{
databaseURL: "https://my-project.firebaseio.com",
projectId: 'my-project',
storageBucket: 'my-project.appspot.com'
}, "./my-project-firebase-adminsdk.json"
);
describe('saveCheckup', () => {
let adminStub, saveCheckup;
beforeAll(() => {
adminStub = jest.spyOn(admin, "initializeApp");
saveCheckup = require('../functions/save_checkup');
});
afterAll(() => {
adminStub.mockRestore();
testEnv.cleanup();
admin.database().ref("current").remove();
});
it("should save the user", async () => {
const wrapped = testEnv.wrap(saveCheckup);
await wrapped({attributes: {
date: "test date",
url: "testurl",
status: "200"
}});
const record = await admin.database().ref('/current/testurl').once('value');
expect(record.val()).toHaveProperty("status", "200");
})
});
Update: We were not able to solve this problem and ended up just writing offline tests for firestore instead.

The error output you posted shows that the error is within the Google Firebase node module files.
It even shows the line and character location:
at new FirestoreClient (node_modules/.../v1/firestore_client.js:113:32)
// Error location here ^
If you are trying to deploy locally please read this: https://firebase.google.com/docs/hosting/deploying
and follow the directions according to your situation.

Add a "jest.config.js" into the root directory keep the following code into the file
module.exports = {
testPathIgnorePatterns: ['lib/', 'node_modules/'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
testEnvironment: 'node'
};

Related

How can I create a logging function in firebase for auth.user().onCreate() trigger?

I am trying to log each time an account is created and deleted.
I created a trigger functions.auth.user().onCreate() and as I understand it returns an user object as in the docs: here, and here.
The functions deploy without trouble but when the trigger is called it throws an error:
Error: Process exited with code 16
at process.<anonymous> (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/invoker.js:92:22)
at process.emit (events.js:314:20)
at process.EventEmitter.emit (domain.js:483:12)
at process.exit (internal/process/per_thread.js:168:15)
at sendCrashResponse (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/logger.js:44:9)
at process.<anonymous> (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/invoker.js:88:44)
at process.emit (events.js:314:20)
at process.EventEmitter.emit (domain.js:483:12)
at processPromiseRejections (internal/process/promises.js:209:33)
at processTicksAndRejections (internal/process/task_queues.js:98:32)
Error which I cannot understand.
Here is my code
// functions/index.js
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const { google } = require('googleapis')
const { firestore } = require("firebase-admin");
exports.logging = require('./logging');
admin.initializeApp()
// And other working functions
The actual functions
// functions/logging.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const { firestore } = require('firebase-admin');
const authUserTrigger = functions.auth.user()
exports.userSignup = authUserTrigger.onCreate((user) => {
storeUser(user)
})
exports.userDelete = authUserTrigger.onDelete((user) => {
storeUser(user)
})
async function storeUser(user) {
// functions.logger.log(user.email) -> this works
// Destructured original object
let updatedUser = (({ displayName, email }) => ({ displayName, email }))(user);
functions.logger.log('updatedUser', updatedUser )
await admin
.firestore()
.collection('logs')
.doc('users')
.collection('signup')
.set({
user: {updatedUser}, // I think this is the culprint
// user, This doesn't work either
createTimestamp: firestore.FieldValue.serverTimestamp()
}, { merge: true })
};
Thank you in advance
EDIT ==========
#Tanay was right. Needed to change set to add.
As #Tanay stated, you cannot use set() in a collection in Firebase, it must be a document. If you want to add a document to the collection with an auto ID then you can use add() on the collection with the data.

Trying to test firebase rules with emulator

I'm trying to setup my testEnvironment for testing my firestore rules with my locally running emulator.
When i try to access testEnv it is undefined, and I guess that has something to do with initializeTestEnvironment being asynchronous, but how do I get around that?
import {
assertFails,
assertSucceeds,
initializeTestEnvironment,
RulesTestEnvironment,
} from "#firebase/rules-unit-testing"
import fs from 'fs'
const projectId = 'comment-section-e9c09';
let testEnv:RulesTestEnvironment;
beforeAll( async () => {
let testEnv = await initializeTestEnvironment({
projectId: projectId,
firestore: {
rules: fs.readFileSync("firestore.rules", "utf8"),
host:'localhost',
port:8080
},
});
})
test("should clear db",() => {
testEnv.clearDatabase()
})

Pub Sub Cloud Function - Async Await

Trying to accomplish the following via a scheduled Firebase Function:
Fetch Firestore Collection
Delete Collection
Make Get Request to External API
Write Get Request results to Firestore Collection.
The function is failing to deploy. Error logs only say {"code":3,"message":"Function failed on loading user code. This is likely due to a bug in the user code.
When I run the emulator suite locally, I get no errors, but the DB does not update.
I'm getting a linting error on the "=>" that's attached to the "onRun" method. Not sure if this is a code problem or an ESLint problem for ES6 in Firebase Functions.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const axios = require('axios');
admin.initializeApp();
const db = admin.firestore();
exports.scheduledFunction = functions.pubsub
.schedule("every 2 minutes")
.timeZone("America/New_York")
.onRun(async (context) => {
try {
const querySnapshot = await db.collection("my_collection").get();
console.log(querySnapshot.docs)
const promises = querySnapshot.docs.map(doc => db.collection("my_collection").doc(doc.id).delete());
await Promise.all(promises);
const options = {
"method": "get",
"url": "www.myUrl.com",
"headers": {
"Cookie": "...",
},
};
const axiosResponse = await axios(options);
const apiResponse = JSON.parse(axiosResponse.data);
const parsedResponse = apiResponse["news_results"];
const docCreationPromises = parsedResponse.map(response => db.collection("my_collection").add(parsedResponse))
await Promise.all(docCreationPromises);
return null;
} catch (error) {
console.log(error);
return null;
}
});
For the ESLint issue with =>, try setting the ecmaVersion to 8 in your ESLint config.
module.exports = {
root: true,
env: {
es6: true,
node: true,
},
extends: [
"eslint:recommended",
"google",
],
parserOptions: {
ecmaVersion: 8
}
};
When I run the emulator suite locally, I get no errors, but the DB does not update.
Are you using Firestore emulator? If yes, then data will be added there and not in production so you can see the data in emulator only.

Typeorm Connection "default" was not found when connection is created in jest globalSetup

I'm having a similar problem as in #5164 and this question. Consider the following working test code:
// AccountResolver.test.ts
describe('Account entity', () => {
it('add account', async () => {
await createConnections()
const defaultConnection = getConnection('default')
const actual = await callGraphql(
`mutation {
addAccount(options: {
accountIdentifier: "7csdcd8-8a5f-49c3-ab9a-0198d42dd253"
name: "Jake, Bob (Braine-l’Alleud) JAM"
userName: "Bob.Marley#contoso.com"
}) {
accountIdentifier
name
userName
}
}`
)
expect(actual.data).toMatchObject({
data: {
addAccount: {
accountIdentifier: '7csdcd8-8a5f-49c3-ab9a-0198d42dd253',
name: 'Jake, Bob (Braine-l’Alleud) JAM',
userName: 'Bob.Marley#contoso.com',
},
},
})
await defaultConnection.query(`DELETE FROM Account`)
await defaultConnection.close()
})
})
The code to create a connection and close it should be executed before all tests and after all tests are done, that's why we've added it to globalSetup.ts and globalTeardown.ts:
// globalSetup.ts
require('ts-node/register')
import { createConnections } from 'typeorm'
module.exports = async () => {
// console.log('jest setup')
await createConnections()
}
// globalTeardown.ts
require('ts-node/register')
import { getConnection } from 'typeorm'
module.exports = async () => {
const defaultConnection = getConnection('default')
await defaultConnection.close()
}
// AccountResolver.test.ts
describe('Account entity', () => {
it('add account', async () => {
const defaultConnection = getConnection('default')
await defaultConnection.query(`DELETE FROM Account`)
const actual = await callGraphql(
`mutation {
addAccount(options: {
accountIdentifier: "7csdcd8-8a5f-49c3-ab9a-0198d42dd253"
name: "Jake, Bob (Braine-l’Alleud) JAM"
userName: "Bob.Marley#contoso.com"
}) {
accountIdentifier
name
userName
}
}`
)
expect(actual.data).toMatchObject({
data: {
addAccount: {
accountIdentifier: '7csdcd8-8a5f-49c3-ab9a-0198d42dd253',
name: 'Jake, Bob (Braine-l’Alleud) JAM',
userName: 'Bob.Marley#contoso.com',
},
},
})
})
})
Omitting the line require('ts-node/register') from both files throws this error:
T:\Test\src\it-portal\entity\Account.ts:1
import {
^^^^^^
SyntaxError: Cannot use import statement outside a module
Keeping the require line in throws:
FAIL src/resolvers/AccountResolver.test.ts × add account (31 ms) ●
Account entity › add account ConnectionNotFoundError: Connection
"default" was not found.Account entity
Version
"jest": "^26.0.1",
"ts-jest": "^26.1.0",
"ts-node-dev": "^1.0.0-pre.44",
"typescript": "^3.9.5"
Config
// jest.config.js
module.exports = {
preset: 'ts-jest',
globalSetup: './src/test-utils/config/globalSetup.ts',
globalTeardown: './src/test-utils/config/globalTeardown.ts',
setupFiles: ['./src/test-utils/config/setupFiles.ts'],
moduleDirectories: ['node_modules', 'src'],
globals: {
'ts-jest': {
tsConfig: 'tsconfig.json',
diagnostics: {
warnOnly: true,
},
},
},
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
coverageReporters: ['json', 'lcov', 'text', 'clover'],
}
Thank you for pointing out my mistakes. As I'm new I tried googling but couldn't really find an answer if this is me not understanding the tool or a bug in the too. Found a similar issue here with a PR.
It seems like the tests are running in a fully isolated environment where they can't access the connection set up within globalSetup.
Workaround
The only workaround I have found thus far is to add the following code to every test file:
beforeAll(async () => {
await createConnections()
})
afterAll(async () => {
const defaultConnection = getConnection('default')
await defaultConnection.close()
})
require('ts-node/register') shouldn't present in .ts files. They are already processed by TypeScript compiler.
This is not what globalSetup and globalTeardown are for. They run in Jest parent process and are evaluated once, while each test suite runs in child processes.
This can be achieved by providing a common setup in setupFilesAfterEnv option:
// jest.setup.ts
...
beforeAll(async () => {
await createConnections()
})
afterAll(async () => {
const defaultConnection = getConnection('default')
await defaultConnection.close()
})
Since Jest tests run in parallel, this will result in multiple database connections. If this is not desirable because of connection limit, Jest runInBand option needs to be used.
A setup for all tests not desirable because not all test suites need database connection, while they will unconditionally take time and occupy database connection pool. In this case jest.setup.ts can be imported directly in tests that use a database instead of setupFilesAfterEnv, no need to specify beforeAll and afterAll in each suite.

How to fix firebase database initialised multiple times due to React SSR initialised database and cloud function firebase initialised database?

I have updated the question as found the root cause of the issue.
As I have hosted my React SSR app which uses firebase database in the client serving by one of the cloud function named app throwing an error of Error: FIREBASE FATAL ERROR: Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.. When I comment out one by one and deploy, works perfectly. But when I deploy together doesn't work. How do I separate these two keeping both at the same repo?
ORIGINAL Question: Why firebase cloud function throwing an error of 'The default Firebase app does not exist.'?
So I am trying out firebase function for the first time. admin.messaging() throwing me the following error. Help me figure out why?
If I look at the console I get results till console.log('deviceToken', deviceToken);
so whats wrong in const messageDone = await admin.messaging().sendToDevice(deviceToken, payload);?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
exports.updateUnreadCount = functions.database.ref('/chats/{chatId}/{messageId}')
.onCreate(async(snap, context) => {
const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
appOptions.databaseAuthVariableOverride = context.auth;
const adminApp = admin.initializeApp(appOptions, 'app');
const { message, senderId, receiverUid } = snap.val();
console.log(message, senderId, receiverUid);
console.log('------------------------');
const deleteApp = () => adminApp.delete().catch(() => null);
try {
const db = adminApp.database();
const reciverUserRef = await db.ref(`users/${receiverUid}/contacts/${senderId}/`);
console.log('reciverUserRef', reciverUserRef);
const deviceTokenSnapshot = await reciverUserRef.child('deviceToken').once('value');
const deviceToken = await deviceTokenSnapshot.val();
console.log('deviceToken', deviceToken);
const payload = {
notification: {
title: 'Test Notification Title',
body: message,
sound: 'default',
badge: '1'
}
};
const messageDone = await admin.messaging().sendToDevice(deviceToken, payload);
console.log('Successfully sent message: ', JSON.stringify(messageDone));
return deleteApp().then(() => res);
} catch (err) {
console.log('error', err);
return deleteApp().then(() => Promise.reject(err));
}
});
Update1: According to this https://firebase.google.com/docs/cloud-messaging/send-message#send_to_a_topic, admin.messaging().sendToDevice(deviceToken, payload) APIs are only available in the Admin Node.js SDK?
So switched to
const payload = {
data: {
title: 'Test Notification Title',
body: message,
sound: 'default',
badge: '1'
},
token: deviceToken
};
const messageDone = await admin.messaging().send(payload);
Which is not working either. Getting an error Error: The default Firebase app does not exist. Make sure you call initializeApp() before using any of the Firebase services. Any lead will be helpful.
EDIT: Finally got the function working.
My index.js is exporting to functions, follwoing
exports.app = functions.https.onRequest(app); //React SSR
exports.updateChat = functions.database.ref('/chats/{chatId}/{messageId}').onCreate(updateChat);
exports.app is a react ssr function, which I am using to host my site. This uses database too. and throwing error of multiple database instance.
When I comment out one by one and deploy, works perfectly. But when I deploy together doesn't work. How do I separate these two keeping both at the same repo? Any suggestions, please?
You can initialise db outside export function.
const admin = require('firebase-admin');
const adminApp = admin.initializeApp(appOptions, 'app')
//continue code
Update:
const admin = require('firebase-admin');
const adminApp = admin.initializeApp(options);
async function initialize(options, apps = 'app') {
try {
const defaultApp = adminApp.name
if(defaultApp) {
const adminApp1 = admin.initializeApp(apps);
}else {
const adminApp1 = admin.initializeApp(options, apps);
}
}catch(err) {
console.error(err);
}
}
Modify this snippet as per your need and try it out
It abstracts initialize of app in another function. Just call this function at appropriate place in your code.

Categories

Resources