uploading xlsx or csv files to firebase - javascript

Hello there,
im trying to upload files (.csv, .xlsx of 5000 row and 6 col) to firebase with a cloud functions that i call from the frontend, the cloud function start and after a few seconds of run its make an error "code":"4" "details":"Deadline exceeded"
(Blaze plan)
the upload is very slow
getting an Deadline exceeded error with code 4
its crashing the server after i get the error
explication of the code below:
i do a for of loop that iterate over an array of array that looks like this
[['Dufour', 'Annie', 'annie.duf#gmail.com', 33683333005, 'f'], ['john','Doe', 'john#gmail.com', 33223424, 'm']] then i format each row in order to
structure it as a firestore doc with many fields and try to add it to firestore
then its crash
i cant figure out when its crashing its really random
and when im trying a smaller file (1000 row) its also crashing after some seconds of execution
export const createNewLeads = functions.region("europe-west3").https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called' + 'while authenticated.');
}
const { category, subcategory, allLeads, downloadUrl, file_id, fileDetails } = data;
for (const lead of allLeads) {
const leads_infos = format_lead_infos(lead , fileDetails);
const leads_meta = format_lead_metadata(downloadUrl, file_id, fileDetails);
const db_lead_ref = admin.firestore().collection('leads');
const new_lead_doc_id = db_lead_ref.doc().id;
db_lead_ref
.doc(new_lead_doc_id).set({
lead_id: new_lead_doc_id,
category: category,
subcategory: subcategory,
createdOn: Date(),
uploaded_by: context.auth.uid,
purchased_by: null,
metadata: leads_meta,
infos: leads_infos
}).then(() => {
functions.logger.info('createLeads SUCCESS', { docId: new_lead_doc_id });
return {
user_id: new_lead_doc_id,
created: true,
};
}).catch((error: any) => {
functions.logger.info('createLeads ERROR', { error });
return {
error: true,
err_mess: error,
};
});
}
});

As far as I can see you are not handling the asynchronous nature of writing to Firestore correctly, as you're not awaiting the call to set(). You're also returning a result on the first successful document write in the loop, which seems suspicious.
This should be closer to what you need:
try {
for (const lead of allLeads) {
const leads_infos = format_lead_infos(lead , fileDetails);
const leads_meta = format_lead_metadata(downloadUrl, file_id, fileDetails);
const db_lead_ref = admin.firestore().collection('leads');
const new_lead_doc_id = db_lead_ref.doc().id;
await db_lead_ref.doc(new_lead_doc_id).set({
lead_id: new_lead_doc_id,
category: category,
subcategory: subcategory,
createdOn: Date(),
uploaded_by: context.auth.uid,
purchased_by: null,
metadata: leads_meta,
infos: leads_infos
});
}
}
catch (error: any) => {
functions.logger.info('createLeads ERROR', { error });
return {
error: true,
err_mess: error,
};
}
functions.logger.info('createLeads SUCCESS', { docId: new_lead_doc_id
return {
user_id: new_lead_doc_id,
created: true,
};
In this code we await each document write operation and then return an error if any of them fails, and success only once all of them succeed.

Hello Frank thanks for helping us i copy the code you give to us bu after a few seconds of running i got this error
error of the code from the firebase console log
export const createNewLeads = functions.region("europe-west3").https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called' + 'while authentificated.');
}
const { category, subcategory, allLeads, downloadUrl, file_id, fileDetails } = data;
const db_lead_ref = admin.firestore().collection('leads');
const new_lead_doc_id = db_lead_ref.doc().id;
try {
for (const lead of allLeads) {
const leads_infos = format_lead_infos(lead , fileDetails);
const leads_meta = format_lead_metadata(downloadUrl, file_id, fileDetails);
await db_lead_ref.doc(new_lead_doc_id).set({
lead_id: new_lead_doc_id,
category: category,
subcategory: subcategory,
createdOn: Date(),
uploaded_by: context.auth.uid,
purchased_by: null,
metadata: leads_meta,
infos: leads_infos
});
}
} catch (error:any) {
functions.logger.info('createLeads ERROR', { error });
return {
error: true,
err_mess: error,
}
}
functions.logger.info('createLeads SUCCESS', { docId: new_lead_doc_id })
return {
user_id: new_lead_doc_id,
created: true,
}
});

Related

Two requests inside a service in express

Currently I'm working in a project where I'm trying to build a service in express which calls another two external EP. I have a trouble here, because express shows me an error that I can't understand. But I suppose, the way I'm working it should be wrong.
So
app.get("/items/:id", (req, res) => {
return request.get({
url: `https://myapi.com/${req.params.id}`,
json: true
},
(error, response) => {
if(error) {
return res.send("Error ocurred");
}
const itemDesc = request.get({ // Here I'm trying to do the second call and use it later
url: `https://myapi.com/${req.params.id}/description`,
json: true
},
(error, responseDesc) => {
return responseDesc
});
const itemDetails = response.body;
const strPrice = itemDetails.price.toString().split('.');
const numberPrice = parseInt(strPrice[0]);
const floatPrice = strPrice[1] ? parseInt(strPrice[1]) : 00;
return res.send({
id: itemDetails.id,
title: itemDetails.title,
price: {
currency: itemDetails.currency_id,
amount: numberPrice,
decimals: floatPrice,
},
picture: itemDetails.pictures[0].url,
condition: itemDetails.condition,
free_shipping: itemDetails.shipping.free_shipping,
sold_quantity: itemDetails.sold_quantity,
description: itemDesc // Here I'm using the variable of the previous request
});
});
});
Basically, the error I get is that I can't do two calls. I know that because if I remove the nested request, it works.
The error I get is the following:
My question is: Is there any way to do two external request inside the same method?
Thanks in advance
it's cleaner if you do it with async await in your case.
modify your code like this
app.get("/items/:id", async(req, res) => {
try {
const promise1 = fetch(`https://myapi.com/${req.params.id}`).then(data => data.json())
const promise2 = fetch(`https://myapi.com/${req.params.id}/description`)
const [itemDetails, itemDesc] = await Promise.all([promise1, promise2])
const strPrice = itemDetails.price.toString().split('.');
const numberPrice = parseInt(strPrice[0]);
const floatPrice = strPrice[1] ? parseInt(strPrice[1]) : 00;
res.send({
id: itemDetails.id,
title: itemDetails.title,
price: {
currency: itemDetails.currency_id,
amount: numberPrice,
decimals: floatPrice,
},
picture: itemDetails.pictures[0].url,
condition: itemDetails.condition,
free_shipping: itemDetails.shipping.free_shipping,
sold_quantity: itemDetails.sold_quantity,
description: itemDesc // Here I'm using the variable of the previous request
});
} catch (
res.send("Error ocurred")
)
});

Why does my async function always return undefined?

It seems im using async wrong, can anybody spot what I am doing wrong?
This is the function I am waiting on:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
export async function firebaseAcceptTradeOffer(tradeOfferID, userData) {
var tradeInstanceID;
var senderID;
var receiverID;
var senderItemsTemp;
var receiverItemsTemp;
var response;
var tradeOffer = db.collection("tradeOffers").doc(tradeOfferID);
return tradeOffer
.get()
.then((doc) => {
senderItemsTemp = doc.data().sendersItems;
receiverItemsTemp = doc.data().receiversItems;
senderID = doc.data().senderID;
receiverID = doc.data().receiverID;
})
.then(() => {
var itemInTrade = false;
senderItemsTemp.forEach((item) => {
db.collection("listings")
.doc(item.itemID)
.get()
.then((doc) => {
if (doc.data().status !== "listed") {
itemInTrade = true;
}
})
.then(() => {
receiverItemsTemp.forEach((item) => {
db.collection("listings")
.doc(item.itemID)
.get()
.then((doc) => {
if (doc.data().status !== "listed") {
itemInTrade = true;
}
})
.then(() => {
if (itemInTrade) {
tradeOffer.update({
status: "declined",
});
return false;
} else {
db.collection("trades")
.add({
tradeOfferID: tradeOfferID,
senderTradeStatus: {
created: true,
sentToSeekio: "current",
inspection: false,
sentToPartner: false,
},
receiverTradeStatus: {
created: true,
sentToSeekio: "current",
inspection: false,
sentToPartner: false,
},
postagePhotos: [],
inspectionPhotos: [],
senderPaid: false,
receiverPaid: false,
senderUploadedProof: false,
receiverUploadedProof: false,
senderID: senderID,
receiverID: receiverID,
messages: [
{
message: `Trade created. A representative, will message this chat shortly with instructions and postage address. If you would like more information about the trading process, head to seekio.io/help. Thank you for using Seekio!`,
sender: "System",
timestamp: firebase.firestore.Timestamp.fromDate(
new Date()
),
},
],
})
.then((docRef) => {
tradeInstanceID = docRef.id;
tradeOffer
.set(
{
status: "accepted",
tradeInstanceID: docRef.id,
},
{ merge: true }
)
.then(() => {
var receiver = db.collection("users").doc(senderID);
var notification = {
from: auth.currentUser.uid,
fromUsername: userData.username,
type: "tradeOfferAccepted",
time: firebase.firestore.Timestamp.fromDate(
new Date()
),
seen: false,
};
receiver
.update({
notifications: firebase.firestore.FieldValue.arrayUnion(
notification
),
})
.then(() => {
response = {
sendersItems: senderItemsTemp,
receiversItems: receiverItemsTemp,
};
return response;
});
});
})
.catch((err) => console.log(err));
}
});
});
});
});
});
}
And here is where I am calling it:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
async function acceptTradeOffer() {
var tradeOfferID = currentTradeFocus;
var senderID = "";
setLoading("loading");
if (userData !== null && tradeOfferID !== "") {
const response = await firebaseAcceptTradeOffer(
currentTradeFocus,
userData
);
console.log(
"RESPONSE FROM FIREBASE SERVICE>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>: ",
response
);
if (!response) {
setErrorMsg("One of the selected items is no longer available.");
} else if (
response.sendersItems !== null &&
response.receiversItems !== null
) {
setSenderItems(response.sendersItems);
setReceiverItems(response.receiversItems);
toggleConfirmScreen("cancel");
setLoading("idle");
setItemsSet(true);
}
fetch(
"https://europe-west2-seekio-86408.cloudfunctions.net/sendMail?type=tradeUpdate&userID=" +
senderID
).catch((err) => {
console.log(err);
setLoading("idle");
});
}
}
So basically I want to go and check if any of the items in this 'trade' are not equal to 'listed' (which means they are not available, I want to return false, if not, then I return the array of items so the trade can continue.
EDIT: I've tried to rejig it all and it's half working. A top level look at what I am trying to do:
User wants to accept a trade offer for some items >
Check through all items to make sure they are available and not sold >
If so, accept the trade >
Then once its accepted, go and cancel all remaining trade offers that include items from this accepted trade, cause they are not available anymore.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
export async function firebaseAcceptTradeOffer(tradeOfferID, userData) {
console.log(
"----- starting firebaseAcceptTradeOffer--------- ",
unavailableItem
);
//==============
var tradeInstanceID;
var senderID;
var receiverID;
var senderItemsTemp;
var receiverItemsTemp;
var unavailableItem = false;
var response;
var itemsArray;
var notListed = false;
//==============
var tradeOffer = db.collection("tradeOffers").doc(tradeOfferID);
unavailableItem = tradeOffer
.get()
.then((doc) => {
senderID = doc.data().senderID;
receiverID = doc.data().receiverID;
itemsArray = doc.data().sendersItems.concat(doc.data().receiversItems);
})
.then(() => {
itemsArray.forEach((item) => {
db.collection("listings")
.doc(item.itemID)
.get()
.then((doc) => {
if (doc.data().status !== "listed") {
notListed = true;
}
});
});
})
.then(() => {
return notListed;
});
console.log(
"-----unavailableItem at the end of method --------- ",
unavailableItem
);
//^^^^^^^^^^ here i am getting a promise result of false (which is correct) but HOW CAN I ACCESS IT
if (unavailableItem) {
tradeOffer.update({
status: "declined",
});
return false;
} else {
response = await createTrade(
tradeOffer,
tradeOfferID,
senderID,
receiverID,
userData.username
);
console.log("response from createTrade", response);
return response;
}
}
I am getting a promise object back with the value false above. False is correct value I am expecting, but how can I access it? its in the form of a promise object?
I have some time on my hands, so let's break this down.
Notes on Variables
If you aren't using TypeScript (and even if you are), I highly recommend inserting the type into the name of your variables.
db # ✔ by convention, either firebase.database() or firebase.firestore()
tradeOffer # ❓ type unclear, could be a number, an object, a string, etc
tradeOfferDocRef # ✔ a DocumentReference
trades # ❓ type unclear, plural implies a collection of some sort
tradesColRef # ✔ a CollectionReference
You may also encounter these:
doc # ❓ by convention, a DocumentSnapshot, but with unknown data
tradeDoc # ✔ implies a DocumentSnapshot<TradeData> (DocumentSnapshot containing trade data)
When using just doc, you need to look around where its used for context on what this DocumentSnapshot contains.
db.collection('trades').doc(tradeOfferID).get()
.then((doc) => { // contents implied to be TradeData
const data = doc.data();
});
// or
tradeDocRef.get()
.then((doc) => { // contents implied to be TradeData
const data = doc.data();
});
You should rename doc as appropriate, especially when using async/await syntax, so you don't end up in situations like:
const doc = await db.collection('trades').doc(tradeOfferID).get();
/* ... many lines ... */
const senderID = doc.get("senderID"); // what was doc again?
As you've tagged reactjs in your question, this implies you are using modern JavaScript.
Ditch any use of var and replace it with the block-scoped versions: const (prevents reassigning the variable) or let (similar to var, but not quite). These are safer and prevents the chances of accidentally overwriting something you shouldn't.
You can also make use of Object destructuring to assign your variables.
const senderID = doc.data().senderID;
const receiverID = doc.data().receiverID;
const itemsArray = doc.data().sendersItems.concat(doc.data().receiversItems);
can become:
const { senderID, receiverID, sendersItems, receiversItems } = doc.data();
const itemsArray = sendersItems.concat(receiversItems);
If you ever need only a single property out of a document, you should use DocumentSnapshot#get() instead of DocumentSnapshot#data() so it will parse only the field you want instead of the whole document's data.
function getUserAddress(uid) {
return firebase.firestore()
.collection('users')
.doc(uid)
.get()
.then(userDoc => userDoc.get("address")); // skips username, email, phone, etc
}
Notes on Promises
var senderID;
var receiverID;
var itemsArray;
tradeOfferDocRef
.get()
.then((doc) => {
senderID = doc.data().senderID;
receiverID = doc.data().receiverID;
itemsArray = doc.data().sendersItems.concat(doc.data().receiversItems);
})
.then(() => {
/* use results from above */
});
While the above code block functions as intended, when you have many of these variables like this as you do, it becomes unclear when and where they are set.
It also leads to problems like this where you think the variable has a value:
var senderID;
var receiverID;
var itemsArray;
tradeOfferDocRef
.get()
.then((doc) => {
// this line runs after the line below
senderID = doc.data().senderID;
receiverID = doc.data().receiverID;
itemsArray = doc.data().sendersItems.concat(doc.data().receiversItems);
});
// this line before the line above
console.log(senderID); // will always log "undefined"
This can be avoided in one of three ways:
Returning data to pass through to the next handler (you wouldn't use this for this example, only if the next then() handler is elsewhere):
tradeOfferDocRef
.get()
.then((doc) => {
const { senderID, receiverID, sendersItems, receiversItems } = doc.data();
const itemsArray = sendersItems.concat(receiversItems);
return { senderID, receiverID, itemsArray }; // pass to next step
})
.then((neededData) =>
/* use neededData.senderID, neededData.receiverID, etc */
});
Using the data within the same handler:
tradeOfferDocRef
.get()
.then((doc) => {
const { senderID, receiverID, sendersItems, receiversItems } = doc.data();
const itemsArray = sendersItems.concat(receiversItems);
/* use results from above */
});
Using async-await syntax:
const tradeDoc = await tradeOfferDocRef.get();
const { senderID, receiverID, sendersItems, receiversItems } = tradeDoc.data();
const itemsArray = sendersItems.concat(receiversItems);
/* use results from above */
Writing to Firestore
Your current code consists of the following steps:
1. Get the trade offer document</li>
2. If successful, pull out the sender and receiver's IDs, along with any items in the trade
3. If successful, do the following for each item in the sender items array:
a) Check if any of the sender's items are unavailable</li>
b) If successful, do the following for each item in the receiver items array:
- If **any item** was unavailable prior to this, decline the trade & return `false`.
- If all items **so far** are available, do the following:
a) Create a document containing information about the trade with the needed data
b) If successful, edit the trade offer document to accept it
c) If successful, create a notification for the receiver
d) If successful, return the traded items
e) If any of a) to d) fail, log the error and return `undefined` instead
4. Return `undefined`
In the above steps, you can see some problems with your promise chaining. But aside from that, you can also see that you create and edit documents one-by-one instead of all-at-once ("atomically"). If any of these writes were to fail, your database ends up in an unknown state. As an example, you could have created and accepted a trade, but failed to create the notification.
To atomically write to your database, you need to use a batched write where you bundle a bunch of changes together and then send them off to Firestore. If any of them were to fail, no data is changed in the database.
Next, you store a user's notifications inside of their user document. For a small number of notifications this is fine, but do you need to download all of those notifications if you wanted to pull just an address or phone number like in the example in the above section? I recommend splitting them out into their own document (such as /users/{someUserId}/metadata/notifications), but ideally their own collection (such as /users/{someUserId}/notifications/{someNotificationID}). By placing them in their own collection, you can query them and use QuerySnapshot#docChanges to synchronize changes and use Cloud Firestore triggers to send push notifications.
Refactored Function
1. Get the trade offer document</li>
2. Once the retrieved, do the following depending on the result:
- If failed or empty, return an error
- If successful, do the following:
a) Pull out the sender and receiver's IDs, along with any items in the trade.
b) For each item in the trade, check if any are unavailable and once the check has completed, do the following depending on the result:
- If any item is unavailable, do the following:
a) Decline the trade
b) Return the list of unavailable items
- If all items are available, do the following:
a) Create a new write batch containing:
- Create a document about the trade
- Edit the trade offer document to accept it
- Create a notification for the receiver
b) Commit the write batch to Firestore
c) Once the commit has completed, do the following depending on the result:
- If failed, return an error
- If successful, return the traded items and the trade's ID
Because the steps here depend on each other, this is a good candidate to use async/await syntax.
To see this in action, closely study this:
import * as firebase from "firebase-admin";
// insert here: https://gist.github.com/samthecodingman/aea3bc9481bbab0a7fbc72069940e527
async function firebaseAcceptTradeOffer(tradeOfferID, userData) {
const tradeOfferDocRef = db.collection("tradeOffers").doc(tradeOfferID);
const tradeDoc = await tradeOfferDocRef.get();
const { senderID, receiverID, sendersItems, receiversItems } =
tradeDoc.data();
const itemsArray = sendersItems.concat(receiversItems);
// TODO: Check if this is an accurate assumption
if (sendersItems.length == 0 || receiversItems.length == 0) {
success: false,
message: "One-sided trades are not permitted",
detail: {
sendersItemsIDs: sendersItems.map(({ itemID }) => itemID),
receiversItemsIDs: receiversItems.map(({ itemID }) => itemID),
},
};
const listingsColQuery = db
.collection("listings")
.where("status", "==", "listed");
const uniqueItemIds = Array.from(
itemsArray.reduce(
(set, { itemID }) => set.add(itemID),
new Set()
)
);
const foundIds = {};
await fetchDocumentsWithId(
listingsColQuery,
uniqueItemIds,
(listingDoc) => {
// if here, listingDoc must exist because we used .where("status") above
foundIds[listingDoc.id] = true;
}
);
const unavailableItemIDs = uniqueItemIds
.filter(id => !foundIds[id]);
if (unavailableItems.length > 0) {
// one or more items are unavailable!
await tradeOfferDocRef.update({
status: "declined",
});
return {
success: false,
message: "Some items were unavailable",
detail: {
unavailableItemIDs,
},
};
}
const tradeDocRef = db.collection("trades").doc();
const tradeInstanceID = tradeDocRef.id;
const batch = db.batch();
batch.set(tradeDocRef, {
tradeOfferID,
senderTradeStatus: {
created: true,
sentToSeekio: "current",
inspection: false,
sentToPartner: false,
},
receiverTradeStatus: {
created: true,
sentToSeekio: "current",
inspection: false,
sentToPartner: false,
},
postagePhotos: [],
inspectionPhotos: [],
senderPaid: false,
receiverPaid: false,
senderUploadedProof: false,
receiverUploadedProof: false,
senderID,
receiverID,
messages: [
{
message: `Trade created. A representative, will message this chat shortly with instructions and postage address. If you would like more information about the trading process, head to seekio.io/help. Thank you for using Seekio!`,
sender: "System",
timestamp: firebase.firestore.Timestamp.fromDate(new Date()),
},
],
});
batch.set(
tradeOfferDocRef,
{
status: "accepted",
tradeInstanceID,
},
{ merge: true }
);
const receiverNotificationRef = db
.collection("users")
.doc(senderID)
.collection("notifications")
.doc();
batch.set(receiverNotificationRef, {
from: auth.currentUser.uid,
fromUsername: userData.username,
type: "tradeOfferAccepted",
time: firebase.firestore.Timestamp.fromDate(new Date()),
seen: false,
});
await batch.commit();
return {
success: true,
message: "Trade accepted",
detail: {
tradeID: tradeInstanceID,
senderItems,
receiversItems,
},
};
}
Usage:
try {
const tradeResult = await firebaseAcceptTradeOffer(someTradeId);
} catch (err) {
// if here, one of the following things happened:
// - syntax error
// - database read/write error
// - database rejected batch write
}
In general, when you are returning a promise where it can't be resolved you must await its result. Additionally, you must be returning a value from within a promise then chain, at minimal the last .then() needs to be returning a value, this can also be done within a .finally() method.
Using Get from any firebase resource, realtime, firestore, and storage are all Async processes and must be awaited. in your case, you are missing an await for the return:
var tradeOffer = db.collection("tradeOffers").doc(tradeOfferID);
return tradeOffer
and you don't appear to be returning anything inside your .then() statements, I would suggest a complete rewrite of what you are trying to so you are returning values as they are needed.

How to iterate over objects stored in a JavaScript array

I'm coding a simple JavaScript currency converter. Pulled the currency data from an API and stored it to a state object. Next, I wanted to add currency name, country and symbol to the app so I'm pulling that data from a Rest Countries API and storing it to "currencies" array.
Here's the code:
export const state = {
date: [],
time: {},
currency: [],
rates: {},
result: [],
};
export const getSymbolsCountry = async function (symbol) {
try {
const data = await fetch(`https://restcountries.eu/rest/v2/currency/${symbol}`);
const json = await data.json();
addInfoToSymbol(json[0]);
} catch (err) {
console.log(err);
}
};
function addInfoToSymbol(data) {
let country = {
code: data.currencies[0].code,
symbol: data.currencies[0].symbol,
flag: data.flag,
country: data.name,
};
state.currency.push(country);
}
Console logging the currency from state shows that it's an array but using map or forEach on it does nothing.
console log state.currency
Could someone please help me understand what I'm doing wrong cause I can't wrap my head around it.
What do you mean by using map of forEach on it does nothing ?
Here is an example where I use forEach on state.currency and its working perfectly.
const state = {
currency: []
};
const getSymbolsCountry = async function (symbol) {
try {
const data = await fetch(`https://restcountries.eu/rest/v2/currency/${symbol}`);
const json = await data.json();
addInfoToSymbol(json[0]);
} catch (err) {
console.log(err);
}
};
function addInfoToSymbol(data) {
let country = {
code: data.currencies[0].code,
symbol: data.currencies[0].symbol,
flag: data.flag,
country: data.name,
};
state.currency.push(country);
}
Promise.all([
getSymbolsCountry("EUR"),
getSymbolsCountry("USD"),
getSymbolsCountry("JPY")
])
.then(() =>
state.currency.forEach(({code}) =>
$("#result").append("<li>" + code + "</li>")
)
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul id="result"></ul>

How can i test my resolvers properly with jest?

I'm trying to test my resolvers but i'd like to test each field of the response, here's the code to call the response:
interface Options {
source: string;
variableValues?: Maybe<{ [key: string]: unknown | null }>;
}
let schema: GraphQLSchema;
const gCall = async ({
source,
variableValues,
}: Options): Promise<ExecutionResult> => {
if (!schema) {
schema = await createSchema();
}
return graphql({
schema,
source,
variableValues,
});
};
export default gCall;
And that's the code to test the resolver:
let connection: Connection;
const challengeMutation = `
mutation CreateChallenge($data: CreateChallengeInput!) {
createChallenge(data: $data) {
id
name
category
startDate
endDate
goal
description
}
}
`;
describe('Create Challenge', () => {
beforeAll(async () => {
connection = await databaseTestConnection();
await connection.createQueryBuilder().delete().from(Challenge).execute();
});
afterAll(async () => {
await connection.createQueryBuilder().delete().from(Challenge).execute();
await connection.close();
});
it('should create challenge', async () => {
const challenge = {
name: 'some awesome name',
category: 'distância',
startDate: new Date(2020, 7, 4).toISOString(),
endDate: new Date(2020, 7, 5).toISOString(),
goal: 5000,
description: 'some excelent challenge description',
};
const response = await gCall({
source: challengeMutation,
variableValues: {
data: challenge,
},
});
expect(response).toMatchObject({
data: {
createChallenge: {
name: challenge.name,
category: challenge.category,
startDate: challenge.startDate,
endDate: challenge.endDate,
goal: challenge.goal,
description: challenge.description,
},
},
});
});
});
What I'd like to do is test the fields separately, like this:
expect(response.data.createChallenge.name).toEqual(challenge.name);
But I'm getting the following error when I try to execute the above code:
Object is possibly 'null' or 'undefined'.
What can I do to solve this error and to make this test better?
Object is possibly 'null' or 'undefined'.
TypeScript warns you that the response data might not exist as the graphql "server" might return error instead. So you should use ! operator to assert it's not null.
You should also do that after checking it's not undefined with expect().

While loop is not generating data randomly

I'm creating API tests with async-await using Supertest and Mocha.
In the accountsData.js file I created a function to generate random test accounts.
In the accountsHelper.js file I created a function to create unlimited accounts using a while loop
When I run tests on the post_accounts.js file, the first account is created successfully, but from the second account, the data generated in the accountsData.js file is already repeated.
Why isn't data randomly generated when I create more than one account using data from the accountsData.js file?
accountsData.js
const casual = require('casual');
function randomAccount() {
return {
'email': casual.email,
'password': '123456',
};
}
module.exports = {
randomAccount,
};
accountsHelper.js
const request = require('supertest');
const commonData = require('../data/commonData');
/* eslint-disable no-console */
const accountList = [];
let counterAccounts;
module.exports = {
async createAccount(account, accountsToCreate = 2, validateResponse = true) {
counterAccounts = 0;
while (counterAccounts < accountsToCreate) {
try {
const res = await request(commonData.environment.staging)
.post(commonData.endpoint.accounts)
.send(account);
if (validateResponse === true) {
if (res.status === commonData.statusCode.ok) {
accountList.push(res.body);
} else {
throw new Error('Email already exists\n\n' + JSON.stringify(res.body, null, ' '));
}
} else {
return res.body;
}
} catch (err) {
console.log(err);
}
counterAccounts++;
}
return accountList;
},
};
post_accounts.js
const accountsData = require('../../data/accountsData');
const accountsHelper = require('../../helpers/accountsHelper');
const account = accountsData.randomAccount();
describe('Create accounts with email and password', () => {
context('valid accounts', () => {
it('should create an account successfully', async() => {
const res = await accountsHelper.createAccount(account);
// eslint-disable-next-line no-console
console.log(res);
});
});
});
API response:
Create accounts with email and password
valid accounts
Error: Email already exists
{
"error": {
"statusCode": 422,
"name": "ValidationError",
"message": "The `account` instance is not valid. Details: `email` Email already exists (value: \"Lemuel.Lynch#Susan.net\").",
"details": {
"context": "account",
"codes": {
"email": [
"uniqueness"
]
},
"messages": {
"email": [
"Email already exists"
]
}
}
}
}
at Object.createAccount (/Users/rafael/Desktop/projects/services/test/helpers/accountsHelper.js:24:19)
at process._tickCallback (internal/process/next_tick.js:68:7)
[ { 'privacy-terms': false,
'created-date': '2019-08-24T10:00:34.094Z',
admin: false,
isQueued: false,
lastReleaseAttempt: '1970-01-01T00:00:00.000Z',
'agreed-to-rules': { agreed: false },
email: 'Lemuel.Lynch#Susan.net',
id: '5d610ac213c07d752ae53d91' } ]
✓ should create an account successfully (2243ms)
1 passing (2s)
The code that you posted doesn't correspond to the code that you're describing in prose.
However, I tested your accountsData.js file, in the way that your words (but not your code) say that you're using it, and it works fine.
// main.js
const { createPerson } = require(__dirname + '/accountsData')
console.log(createPerson())
console.log(createPerson())
console.log(createPerson())
console.log(createPerson())
console.log(createPerson())
Output from running it once:
$ node main.js
{ email: 'Anne_Ebert#Macie.com', password: '123456' }
{ email: 'Manley.Lindgren#Kshlerin.info', password: '123456' }
{ email: 'McClure_Thurman#Zboncak.net', password: '123456' }
{ email: 'Breitenberg.Alexander#Savannah.com', password: '123456' }
{ email: 'Keely.Mann#Stark.io', password: '123456' }
And again:
$ node main.js
{ email: 'Destany_Herman#Penelope.net', password: '123456' }
{ email: 'Narciso_Roob#gmail.com', password: '123456' }
{ email: 'Burnice_Rice#yahoo.com', password: '123456' }
{ email: 'Roma_Nolan#yahoo.com', password: '123456' }
{ email: 'Lilla_Beier#yahoo.com', password: '123456' }
Nothing in the code that you posted is actually requiring or using accountsData.js. If you change your code to use it, I think you'll see, like I do, that it works.
Problem is, you are generating the random account and storing it in a variable 'post_accounts.js(line 3)'. So, when you create an account, you are using the same payload to create multiple accounts, which obviously throws an error.
I just modified the accountHelper to properly handle your scenario. Hope this helps.
Note: The code is not tested, I just wrote it from my mind. Please test and let me know if it works.
// accountsHelper.js
const request = require('supertest');
const commonData = require('../data/commonData');
const accountsData = require('../../data/accountsData');
/* eslint-disable no-console */
const accountList = [];
module.exports = {
async createAccount(account, accountsToCreate = 1, validateResponse = true) {
// creates an array of length passed in accountsToCreate param
return (await Promise.all(Array(accountsToCreate)
.fill()
.map(async () => {
try {
const res = await request(commonData.environment.staging)
.post(commonData.endpoint.accounts)
// takes account if passed or generates a random account
.send(account || accountsData.randomAccount());
// validates and throw error if validateResponse is true
if (validateResponse === true && (res.status !== commonData.statusCode.ok)) {
throw new Error(
'Email already exists\n\n' +
JSON.stringify(res.body, null, ' ')
);
}
// return response body by default
return res.body;
} catch (e) {
console.error(e);
// return null if the create account service errors out, just to make sure the all other create account call doesnt fail
return null;
}
})))
// filter out the null(error) responses
.filter(acc => acc);
}
};
//post_accounts.js
const accountsHelper = require('../../helpers/accountsHelper');
const accountsData = require('../../data/accountsData');
const GENERATE_RANDOM_ACCOUNT = null;
describe('Create accounts with email and password', () => {
context('valid accounts', () => {
it('should create an account successfully', async () => {
const result = await accountsHelper.createAccount();
expect(result.length).toEquals(1);
});
it('should create 2 accounts successfully', async () => {
const result = await accountsHelper.createAccount(GENERATE_RANDOM_ACCOUNT, 2);
expect(result.length).toEquals(2);
});
it('should not create duplicate accounts', async () => {
const account = accountsData.randomAccount();
// here we are trying to create same account twice
const result = await accountsHelper.createAccount(account, 2);
// expected result should be one as the second attempt will fail with duplicate account
expect(result.length).toEquals(1);
});
});
});

Categories