Trying to initialize test environment with v9 firestore sdk using jest - firebase

Im trying to set up my testing environment to test my security fules with firestore. I've copied this code from https://firebase.google.com/docs/rules/unit-tests#before_you_run_the_emulator
let testEnv : RulesTestEnvironment;
beforeAll(async () => {
testEnv = await initializeTestEnvironment({
projectId: "demo-project-1234",
firestore: {
rules: fs.readFileSync('firestore.rules', 'utf8'),
},
});
});
However, I'm getting this error.
The host and port of the firestore emulator must be specified. (You may wrap the test script with firebase emulators:exec './your-test-script' to enable automatic discovery, or specify manually via initializeTestEnvironment({firestore: {host, port}}).
Anyone know how to solve this?
EDIT
I tried adding host and port to my running emulator like so
let testEnv : RulesTestEnvironment;
beforeAll(async () => {
testEnv = await initializeTestEnvironment({
projectId: "comment-section-e9c09",
firestore: {
rules: fs.readFileSync('firestore.rules', 'utf8'),
host:'localhost',
port:8080
},
});
});
Now it seems to be able to connect to my emulator, but when I try to fx clear the database like
test("sefse", () => {
testEnv.clearDatabase()
})
I get the following error
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Error: The host and port of the database emulator must be specified. (You may wrap the test script with 'firebase emulators:exec './your-test-script'' to enable automatic discovery, or specify manually via initializeTestEnvironment({database: {host, port}}).".] {

I give u a "mocha-based" starting point:
security.test.js:
import { readFileSync } from 'fs';
import { assertFails, assertSucceeds, initializeTestEnvironment } from "#firebase/rules-unit-testing";
import { doc, getDoc, setDoc } from "firebase/firestore";
let testEnv;
let unauthedDb;
describe("general database behaviour", () => {
before(async () => {
testEnv = await initializeTestEnvironment({
projectId: "demo-project-1234",
firestore: {
rules: readFileSync("firestore.rules", "utf8"),
host: "127.0.0.1",
port: "8080"
},
});
unauthedDb = testEnv.unauthenticatedContext().firestore();
});
after(async () => {
await testEnv.cleanup();
});
it("should let read anyone the database", async () => {
await testEnv.withSecurityRulesDisabled(async (context) => {
await setDoc(doc(context.firestore(), 'data/foobar'), { foo: 'bar' });
});
await assertSucceeds(getDoc(doc(unauthedDb, 'data/foobar')))
})
it("should not allow writing the database", async () => {
await assertFails(setDoc(doc(unauthedDb, '/data/foobar'), { something: "something" }))
})
})

Did you specify the firestore port and rules path in your firebase.json file like so?
"emulators": {
"firestore": {
"port": 8080
}
},
"firestore": {
"rules": "./rules/firestore.rules"
}

Related

Why am I getting a "Not Implemented" error when unit testing firebase security rules?

The bounty expires in 4 days. Answers to this question are eligible for a +50 reputation bounty.
woop de schwoop is looking for a canonical answer.
I am writing firebase storage security rules, and unit testing them with "#firebase/rules-unit-testing". When running the tests, I get the following error:
Not Implemented
at node_modules/#firebase/rules-unit-testing/src/impl/rules.ts:64:11
at step (node_modules/#firebase/rules-unit-testing/dist/index.cjs.js:74:23)
at Object.next (node_modules/#firebase/rules-unit-testing/dist/index.cjs.js:55:53)
at fulfilled (node_modules/#firebase/rules-unit-testing/dist/index.cjs.js:45:58)
at tryCallOne (node_modules/promise/lib/core.js:37:12)
at node_modules/promise/lib/core.js:123:15
at flush (node_modules/asap/raw.js:50:29)
The only unit test I have currently is supposed to confirm that users cannot delete files.
Here is my code:
import {
assertFails,
assertSucceeds,
initializeTestEnvironment,
RulesTestEnvironment,
} from "#firebase/rules-unit-testing";
import {
deleteObject,
ref,
uploadBytes,
} from "firebase/storage";
import * as fs from "fs";
const PROJECT_ID = "iurn-973d0";
let testEnv: RulesTestEnvironment;
describe("Firebase storage security rules", () => {
beforeAll(async () => {
testEnv = await initializeTestEnvironment({
projectId: PROJECT_ID,
firestore: {
rules: fs.readFileSync(__dirname + "/../storage.rules", "utf-8"),
host: "127.0.0.1",
port: 9199,
},
});
});
beforeEach(async () => {
if(!testEnv) return
testEnv.clearStorage();
});
it("DOES NOT allow deletion", async () => {
if(!testEnv) return
const storage = testEnv.authenticatedContext("user123").storage();
await testEnv.withSecurityRulesDisabled(async (context) => {
await uploadBytes(ref(context.storage(), "hi.jpg"), require("../assets/images/placeholder.png"))
})
expect(assertFails(deleteObject(ref(storage, "hi.jpg")))).resolves.toBeTruthy()
})
afterAll(async () => {
if(!testEnv) return
testEnv.cleanup();
});
});

How can i test 'auth().signInWithCredential' in firebase/app?

I am writing test code using firebase/auth and google login with react-native
But, there are always bug in my test code
my UI component is below.
import { GoogleSignin } from "#react-native-google-signin/google-signin";
import auth from "#react-native-firebase/auth";
let userInfo;
try {
userInfo = await GoogleSignin.signIn();
} catch (error) {
console.error(error);
}
const googleCredential = auth.GoogleAuthProvider.credential(userInfo.idToken);
let testPromise;
try {
testPromise = await auth().signInWithCredential(googleCredential);
} catch (error) {
// In real code environment, there are no problem.
// But, In my test environment, the engine of the code always occur error in here
console.error(error);
}
The error message is below.
console.error
TypeError: (0 , _auth.default) is not a function
my mocking part of the test code is below
jest.mock("#react-native-firebase/auth", () => {
return {
GoogleAuthProvider: {
credential: jest.fn().mockReturnValue({ providerId: "fakeProviderId", secret: "fakeSecret", token: "fakeToken" }),
},
signInWithCredential: jest.fn(),
};
});
some tricks are in there.
i think the way i suggests is not best practice.
but, you can do this problem like this.
In react component...
import auth from "#react-native-firebase/auth";
import { firebase } from "#react-native-firebase/auth"; << KEY POINT
test component...
const mockedFbAuth = jest.fn();
jest.mock("#react-native-firebase/auth", () => {
return {
firebase: {
auth: jest.fn().mockImplementation(() => ({
signInWithCredential: mockedFbAuth.mockReturnValue({
additionalUserInfo: {
profile: {
email: "fakeCredentialEmail",
name: "fakeCredentialName",
},
},
}),
})),
},
};
});
above this way, i can solve my problem!

Unimplemented API error when running admin.storage().bucket(..).file(..).move() in an emulated Firebase function

"firebase": "9.0.1" - local
"firebase-tools": 9.17.0 - global
"node": v14.17.4
I'm calling .move() using the Firebase admin SDK in an emulated function. Firebase returns the following error:
ApiError: file#copy failed with an error - Not Implemented
at new ApiError (.../functions/node_modules/#google-cloud/common/build/src/util.js:73:15)
statusCode: 501
request: {
agent: [Agent],
headers: [Object],
href: 'http://localhost:9199/b/public-8s9ch/o/91ff38b1-b521-4a23-8844-b12cffa0ee98%2Fscreenshot.png/rewriteTo/b/private-8s9ch/o/91ff38b1-b521-4a23-8844-b12cffa0ee98%2Fcd665e29-edc8-4832-9bc7-110c21f1e560?'
},
body: 'Not Implemented',
The exact same code, with the same tests and installed packages, work perfectly when deployed to Firebase instead of being used in the emulator.
The same issue occurs with any call to the storage API regardless of using the Admin SDK or using the client SDK e.g. .copy, .move, .delete, etc.
Here's some code to help with reproducing the error:
export const firebaseFunction = functions
.runWith({ memory: "128MB" })
.region("us-central1")
.firestore.document("documents/{docId}")
.onCreate(async (snap, context) => {
try {
const { srcId } = snap.data();
const [files] = await admin
.storage()
.bucket('public')
.getFiles({
prefix: `${srcId}/`,
delimiter: "/",
autoPaginate: false,
});
const promises = files.map((file) => {
return new Promise<void>(async (resolve, reject) => {
try {
const dst = admin
.storage()
.bucket('private')
.file(`${srcId}/${uuidv4()}`);
await file.move(dst);
resolve();
} catch (err) {
reject(err);
}
});
});
await Promise.all(promises);
} catch (error) {
return console.log(error);
}
});

Nodemailer in vercel not sending email in production

I'm using Nodemailer to send emails in my serverless Next.js project, deployed in Vercel, which works perfectly in development mode. But I'm having problems in production. No error returned, everything works the same way as is development mode, except I don't receive any email.
I have another project built with React and deployed in Heroku where I send emails the same way and it works fine, development and production, so I understand the problem is with Vercel.
Yes, I enabled "Allow Less Secured Apps" in Google account and yes, I enabled Captcha.
I also read this https://vercel.com/docs/solutions/email but it doesn't really make me understand what I should do in my case. I can see it's a matter of SMTP but I don't know what exactly.
Anybody experienced this kind of problem? How can I fix this?
const transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
auth: {
user: myEmail#gmail.com,
pass: myEmailPass
}
});
const mailOptions = {
from: `${req.body.name} ${req.body.email}`,
to: myEmail#gmail.com,
subject: `${req.body.subject}`,
text: `Text: ${req.body.text}`
}
transporter.sendMail(mailOptions, (err, res) => {
if(err) {
console.log(err);
} else {
console.log("success");
}
});
UPDATE
I changed to SendGrid: made an account, created an API Key, and changed the code like so(instead the one above):
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: `myEmail#gmail.com`,
from: `myEmail#gmail.com`,
subject: `${req.body.subject}`,
text: `${req.body.text}`
};
sgMail
.send(msg)
.then(() => {
console.log('email sent')
})
.catch((error) => {
console.error("error", error)
});
It logs out "email sent" but I don't receive any email.
It's the same problem like with Nodemailer.
I'm confused now...
I ran into this issue and managed to fix it and keep using nodemailer by adding in promises with async/await.
const nodemailer = require("nodemailer");
export default async (req, res) => {
const { firstName, lastName, email, message } = JSON.parse(req.body);
const transporter = nodemailer.createTransport({
port: 465,
host: "smtp.gmail.com",
auth: {
user: "myEmail#gmail.com",
pass: "password",
},
secure: true,
});
await new Promise((resolve, reject) => {
// verify connection configuration
transporter.verify(function (error, success) {
if (error) {
console.log(error);
reject(error);
} else {
console.log("Server is ready to take our messages");
resolve(success);
}
});
});
const mailData = {
from: {
name: `${firstName} ${lastName}`,
address: "myEmail#gmail.com",
},
replyTo: email,
to: "recipient#gmail.com",
subject: `form message`,
text: message,
html: `${message}`,
};
await new Promise((resolve, reject) => {
// send mail
transporter.sendMail(mailData, (err, info) => {
if (err) {
console.error(err);
reject(err);
} else {
console.log(info);
resolve(info);
}
});
});
res.status(200).json({ status: "OK" });
};
This problem is really confusing indeed. I've managed to fix this by simply adding async/await. This is because streaming responses (fire-and-forget functions) are not supported by Vercel.
Source: https://vercel.com/docs/platform/limits#streaming-responses
I have already encountered the same problem, nodemailer was not working on vercel but on heroku everything worked perfectly. it is specified in the doc that vercel does not block stmp connections but according to what I have experienced, in practice stmp connections are blocked. what you can do is use an alternative to nodemailer. use sendgrid and it works fine
An article on how integrating Sendgrid with Next.js
I had a similar issue with Nodemailer but I fixed it by first adding the environment variables in Vercel then commit to the github(It will automatically be uploaded on vercel). So add the variables to vercel first for it to take effect
In my own case, wrapping my email function with async solved it for me.
eg:
const sendMessage = async(message)=>{
await transporter.sendMail({...options here})
}
Then in my API I called my function using:
await sendMessage('your message')
I tried all the async/await responses and didn't work at the beginning. Digging through the real time functions logs of the app, I noticed that there was an Error: Missing credentials for "PLAIN", so all I had to do was add the respective .env variables to vercel environment variables and it worked. Here's the complete code though:
import type { NextApiRequest, NextApiResponse } from 'next'
type Data = any
const nodemailer = require('nodemailer')
const auth = {
user: process.env.WEB_MAILER,
pass: process.env.WEB_MAILER_PASSWORD,
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const { name, email, subject, message } = req.body
const mailData = {
to: process.env.EMAIL_TO,
from: process.env.WEB_MAILER,
name: name,
subject: subject,
text: `Email: ${email}.\n\nMessage: ${message}`,
html: `<div>Email: ${email}.\n\nMessage: ${message}</div>`,
}
const transporter = nodemailer.createTransport({
host: 'smtp.titan.email',
secure: true,
port: 465,
auth: auth,
})
const server = await new Promise((resolve, reject) => {
// verify connection configuration
transporter.verify(function (error: any, success: any) {
if (success) {
resolve(success)
}
reject(error)
})
})
if (!server) {
res.status(500).json({ error: 'Error failed' })
}
const success = await new Promise((resolve, reject) => {
// send mail
transporter.sendMail(mailData).then((info: any, err: any) => {
if (info.response.includes('250')) {
resolve(true)
}
reject(err)
})
})
if (!success) {
res.status(500).json({ error: 'Error sending email' })
}
res.status(200).json({ success: success })
}

How to Add Firebase Firestore Data into SSR Nuxt Apps Vuex Store

I'm trying to set locations into a Vuex store in my Nuxt app. I've looked into using vuexfire, however, I'm unsure if this would be optimal in a SSR app or generally what is the most simple best practice.
How do you request from firebase firestore and set the state (of the 'locations' in this example)?
Would it be best to use nuxtServerInit in a SSR app?
store/index.js
import Vuex from 'vuex'
import firebase, {auth, db} from '#/services/firebaseinit.js'
const createStore = () => {
return new Vuex.Store({
state: {
user: null,
locations: [],
},
getters: {
// User
activeUser: (state) => {
return state.user
},
// Locations
loadedLocations(state) {
return state.loadedLocations
}
},
mutations: {
// User
setUser (state, payload) {
state.user = payload
},
// Locations
setLocations (state, locations) {
state.locations = locations
}
},
actions: {
// Locations
setLocations(vuexContext, locations) {
vuexContext.commit('setLocations', locations)
},
// Users
autoSignIn ({commit}, payload) {
commit('setUser', payload)
},
signInWithFacebook ({commit}) {
return new Promise((resolve, reject) => {
auth.signInWithPopup(new firebase.auth.FacebookAuthProvider())
resolve()
})
},
signOut ({commit}) {
auth.signOut().then(() => {
commit('setUser', null)
}).catch(error => console.log(error))
},
}
})
}
I haven't used vuexfire but have used firebase with nuxt and it works pretty well. this is what I did.
npm install --save firebase
create a file called firebase.js and put this sort of code in it:
import * as firebase from 'firebase'
if (!firebase.apps.length) {
firebase.initializeApp({
apiKey: '<your-api-key>',
authDomain: '<your-domain>',
databaseURL: '<your-url>',
projectId: '<your-id>',
storageBucket: '<your-bucket>'
})
}
export { firebase }
then you register that file as a plugin in nuxt.config.js
plugins: [
'#plugins/firebase.js'
],
You need to import firebase at the top of your index.js (or other file you're using it in) in the store.
import * as firebase from 'firebase'
then you can use firebase in your nuxtServerInit as you want. Eg.
actions: {
nuxtServerInit({dispatch}, context) {
return Promise.all([
dispatch('get_posts', context),
dispatch('any_other_actions', context)
]);
},
get_posts (vuexContext, context) {
return firebase.database().ref(YOUR DB).once('value')
.then(res => {
//...What you want it to do here
})
},
Firebase is pretty powerful and you'll want to read the docs for specifics about the functions you want to perform but yeah, goes good in nuxt.

Resources