How to run Jest test on a asyn function - asynchronous

I have a function that will find a zip file in the directory and unzip the file and it work fine. however, I'm wondering how i can run a test on this function using Jest.
var fs = require('fs'),
PNG = require('pngjs').PNG
const unzipper = require('unzipper')
PNG = require('pngjs').PNG
const dir = __dirname + "/";
function unzipFile(fileName, outputPath) {
return new Promise((resolve, reject) => {
if (fs.existsSync(fileName) !== true) {
reject("there is no zip file");
}
const createdFile = dir + fileName;
const stream = fs
.createReadStream(createdFile)
.pipe(unzipper.Extract({ path: outputPath }));
stream.on("finish", () => {
console.log("file unzipped");
resolve(outputPath);
});
});
}
I have read through the doc and try to use .then but I'm not sure how my expected output should be like when using Jest.

Here is the unit test solution:
index.js:
const fs = require("fs");
const unzipper = require("./unzipper");
const dir = __dirname + "/";
function unzipFile(fileName, outputPath) {
return new Promise((resolve, reject) => {
if (fs.existsSync(fileName) !== true) {
reject("there is no zip file");
}
const createdFile = dir + fileName;
const stream = fs
.createReadStream(createdFile)
.pipe(unzipper.Extract({ path: outputPath }));
stream.on("finish", () => {
console.log("file unzipped");
resolve(outputPath);
});
});
}
module.exports = unzipFile;
index.spec.js:
const unzipFile = require("./");
const fs = require("fs");
const unzipper = require("./unzipper");
jest.mock("fs", () => {
return {
existsSync: jest.fn(),
createReadStream: jest.fn().mockReturnThis(),
pipe: jest.fn()
};
});
describe("unzipFile", () => {
afterEach(() => {
jest.restoreAllMocks();
jest.resetAllMocks();
});
it("should unzip file correctly", async () => {
const filename = "go.pdf";
const outputPath = "workspace";
const eventHandlerMap = {};
const mStream = {
on: jest.fn().mockImplementation((event, handler) => {
eventHandlerMap[event] = handler;
})
};
const logSpy = jest.spyOn(console, "log");
fs.existsSync.mockReturnValueOnce(true);
fs.createReadStream().pipe.mockReturnValueOnce(mStream);
jest.spyOn(unzipper, "Extract").mockReturnValueOnce({});
const pending = unzipFile(filename, outputPath);
eventHandlerMap["finish"]();
const actual = await pending;
expect(actual).toEqual(outputPath);
expect(fs.existsSync).toBeCalledWith(filename);
expect(fs.createReadStream).toBeCalled();
expect(fs.createReadStream().pipe).toBeCalledWith({});
expect(logSpy).toBeCalledWith("file unzipped");
expect(mStream.on).toBeCalledWith("finish", eventHandlerMap["finish"]);
});
});
Unit test result with coverage report:
PASS src/stackoverflow/59027031/index.spec.js
unzipFile
✓ should unzip file correctly (10ms)
console.log node_modules/jest-mock/build/index.js:860
file unzipped
-------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------|----------|----------|----------|----------|-------------------|
All files | 92.31 | 50 | 75 | 92.31 | |
index.js | 91.67 | 50 | 100 | 91.67 | 8 |
unzipper.js | 100 | 100 | 0 | 100 | |
-------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.618s, estimated 9s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59027031

Related

"contract" can be obtained as a result of "signAndSend", but the compilation error is not resolved

I implemented the sample code according to the polkadot.js official site in the link below.
Running and testing with "yarn dev" command works as expected.
But when I run "yarn build" I get compilation errors.
Anyone know how to solve this?
Environment
next.js
polkadot.js 9.5.1, 8.13.1, 8.4.1 #tried multiple versions
Sample Code:
const deployContract = async () => {
const { web3FromSource } = await import("#polkadot/extension-dapp");
const keyring = new Keyring({ type: 'sr25519' });
const alicePair = keyring.addFromUri('//Alice', { name: 'Alice default' });
const caller = keyring.addFromAddress("actingAddress");
const wsProvider = new WsProvider(blockchainUrl);
const api = await ApiPromise.create({ provider: wsProvider });
setApi(api);
const contractWasm = contract_file.source.wasm;
const code = new CodePromise(api, abi, contractWasm);
const initValue = false;
console.log("contract is :", code);
const performingAccount = accounts[0];
const injector = await web3FromSource(performingAccount.meta.source);
const tx = code.tx.new({ gasLimit, storageDepositLimit }, initValue)
let address;
const unsub = await tx.signAndSend(alicePair, ({ contract, status }) => {
if (status.isInBlock || status.isFinalized) {
address = contract.address.toString();
unsub();
}
});
};
Compile Error:
./pages/index.tsx:64:10
Type error: Property 'contract' does not exist on type 'ISubmittableResult'.
62 | actingAddress,
63 | { signer: injector.signer },
> 64 | ({ contract, status }) => {
| ^
65 | if (status.isInBlock) {
66 | setResult("in a block");
67 | } else if (status.isFinalized) {
> Build error occurred
Thank you.

Critical dependency: the request of a dependency is an expression in next.js

./models/index.js
Critical dependency: the request of a dependency is an expression
Import trace for requested module:
./pages/api/submitAllegationApi.js
Below is my submitAllegationApi.js:
import db from '../../models';
const { DataTypes } = require("sequelize");
const Allegation = require('../../models/allegations')(db.sequelize, DataTypes)
export default async function handler(req, res){
var data= req.body;
await Allegation.create({
***
}).then(ret => ret.AllegationID).catch(err => console.log(err));
console.log("received");
res.status(200).send(data);
};
models/index.js
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
fs.readdirSync(__dirname).filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;

Do I need to run firebase command in my project directory in order to install firebase cloud function?

Lately I have been facing problem about install firebase cloud function for my Ionic project, I want to ask that should we run firebase init and firebase deploy only in the project directory in order to install firebase cloud function?
For example,my project directory is C:/users/name/project
but I am unable to run any firebase command in the directory because an error message will appear 'firebase' is not recognized as an internal or external command,operable program or batch file. and I am only able to run firebase command in C:/program files/git/usr/local directory, is this a reason why I am unable to install the firebase cloud function successfully?
-
-
To be more clear to my question, this is the dependencies inside the file tools/functions/package.json
"name": "functions",
"description": "Cloud Functions for Firebase",
"dependencies": {
"#google-cloud/storage": "^0.4.0",
"child-process-promise": "^2.2.0",
"firebase": "^7.5.0",
"firebase-admin": "^5.11.0",
"firebase-functions": "^3.3.0",
"mkdirp": "^0.5.1",
"mkdirp-promise": "^4.0.0"
},
"private": true
}
This is my ionic info
Ionic CLI : 5.4.6 (c:\Users\CJ\AppData\Roaming\npm\node_modules\ionic)
Ionic Framework : ionic-angular 3.9.5
#ionic/app-scripts : 3.2.2
Cordova:
Cordova CLI : not installed
Cordova Platforms : none
Cordova Plugins : no whitelisted plugins (0 plugins total)
Utility:
cordova-res : not installed
native-run : not installed
System:
Android SDK Tools : 26.1.1 (C:\Users\CJ\AppData\Local\Android\Sdk)
NodeJS : v12.13.0 (C:\Program Files\nodejs\node.exe)
npm : 6.13.4
OS : Windows 10
This is the code of index.js
'use strict';
const functions = require('firebase-functions');
const mkdirp = require('mkdirp-promise');
// Include a Service Account Key to use a Signed URL
const gcs = require('#google-cloud/storage')({keyFilename: 'service-account-credentials.json'});
const admin = require('firebase-admin');
admin.initializeApp();
const spawn = require('child-process-promise').spawn;
const path = require('path');
const os = require('os');
const fs = require('fs');
// Thumbnail size
const THUMB_MAX_HEIGHT = 256; // Max height and width of the thumbnail in pixels.
const THUMB_MAX_WIDTH = 512;
const THUMB_PREFIX = 'thumb_'; // Thumbnail prefix added to file names.
// Order status
const STATUS_PENDING = 'pending';
const STATUS_COMPLETE = 'complete';
/**
* When an image is uploaded in the Storage bucket We generate a thumbnail automatically using
* ImageMagick.
* After the thumbnail has been generated and uploaded to Cloud Storage,
* we write the public URL to the Firebase Realtime Database.
*/
exports.generateThumbnail = functions.storage.object().onFinalize((object, context) => {
// File and directory paths.
const filePath = object.name;
const contentType = object.contentType; // This is the image Mimme type
const fileDir = path.dirname(filePath);
const fileName = path.basename(filePath);
const thumbFilePath = path.normalize(path.join(fileDir, `${THUMB_PREFIX}${fileName}`));
const tempLocalFile = path.join(os.tmpdir(), filePath);
const tempLocalDir = path.dirname(tempLocalFile);
const tempLocalThumbFile = path.join(os.tmpdir(), thumbFilePath);
// Exit if this is triggered on a file that is not an image.
if (!contentType.startsWith('image/')) {
console.log('This is not an image.');
return null;
}
// Exit if the image is already a thumbnail.
if (fileName.startsWith(THUMB_PREFIX)) {
console.log('Already a Thumbnail.');
return null;
}
// Cloud Storage files.
const bucket = gcs.bucket(object.bucket);
const file = bucket.file(filePath);
const thumbFile = bucket.file(thumbFilePath);
const metadata = {contentType: contentType};
// Create the temp directory where the storage file will be downloaded.
return mkdirp(tempLocalDir).then(() => {
// Download file from bucket.
return file.download({destination: tempLocalFile});
}).then(() => {
console.log('The file has been downloaded to', tempLocalFile);
// Generate a thumbnail using ImageMagick.
return spawn('convert', [
tempLocalFile,
'-thumbnail',
`${THUMB_MAX_WIDTH}x${THUMB_MAX_HEIGHT}^`,
'-gravity',
'center',
'-crop',
`${THUMB_MAX_WIDTH}x${THUMB_MAX_HEIGHT}+0+0`,
tempLocalThumbFile
], {capture: ['stdout', 'stderr']}
);
}).then(() => {
console.log('Thumbnail created at', tempLocalThumbFile);
// Uploading the Thumbnail.
return bucket.upload(tempLocalThumbFile, {destination: thumbFilePath, metadata: metadata});
}).then(() => {
console.log('Thumbnail uploaded to Storage at', thumbFilePath);
// Once the image has been uploaded delete the local files to free up disk space.
fs.unlinkSync(tempLocalFile);
fs.unlinkSync(tempLocalThumbFile);
// Get the Signed URLs for the thumbnail and original image.
const config = {
action: 'read',
expires: '03-01-2500'
};
return Promise.all([
thumbFile.getSignedUrl(config),
file.getSignedUrl(config)
]);
}).then(results => {
console.log('Got Signed URLs.');
const thumbResult = results[0];
const originalResult = results[1];
const thumbFileUrl = thumbResult[0];
const fileUrl = originalResult[0];
// Add the URLs to the Database
return admin.database().ref('images').push({path: fileUrl, thumbnail: thumbFileUrl});
}).then(() => console.log('Thumbnail URLs saved to database.'));
});
// listen for new order created then calculate order value
exports.calOrder = functions.firestore.document('orders/{orderId}').onCreate(function (snap, context) {
// Grab the current value of what was written to the Realtime Database
const original = snap.data();
// log record detail
console.log('Received order', context.params.orderId);
console.log('Order data', original);
// set status to pending
snap.ref.set({status: STATUS_PENDING}, {merge: true});
// set order number
var orderSettingDocRef = admin.firestore().collection('settings').doc('order');
orderSettingDocRef.get().then(doc => {
let val = 2018000001;
if (doc.exists) {
val = doc.data().orderNumber + 1;
orderSettingDocRef.update({orderNumber: val});
} else {
orderSettingDocRef.set({orderNumber: val});
}
snap.ref.set({orderNumber: val}, {merge: true});
});
// set createdAt & updatedAt
snap.ref.set({
createdAt: Date.now(),
updatedAt: Date.now(),
}, {merge: true});
// calculate order data
let total = 0;
let itemDocRef;
let soldCount = 0;
original.items.map(item => {
total += item.subTotal;
// update item report
itemDocRef = admin.firestore().collection('items').doc(item.id);
itemDocRef.get().then(function (snapshot) {
if (snapshot.exists) {
soldCount = snapshot.data().soldCount ? snapshot.data().soldCount + item.quantity : item.quantity;
itemDocRef.update({soldCount: soldCount});
}
});
});
// send notifications to store
admin.firestore().collection('notifications').doc(original.store.id).collection('orders').add({
orderId: context.params.orderId
});
// write total to firebase
// should convert total to float
return snap.ref.set({total: total.toFixed(2) / 1}, {merge: true});
});
// listen to order update
exports.calReport = functions.firestore.document('orders/{orderId}').onUpdate(function (change, context) {
// Grab the current value of what was written to the Realtime Database
const newValue = change.after.data();
// Grab previous value for comparison
const previousValue = change.before.data();
// order total value
const orderTotal = parseFloat(newValue.total);
const date = new Date();
console.log('status', newValue.status, previousValue.status);
// send notification if order's status has changed
if (newValue.status !== previousValue.status) {
// notify to user if order has changed status
if (previousValue.status) {
admin.firestore().collection('notifications').doc(newValue.userId).collection('orders').add({
orderId: context.params.orderId
});
}
// build report data
const reportDocRef = admin.firestore().collection('reports').doc(newValue.store.id);
var reportData;
reportDocRef.get().then(function (doc) {
// set initial data
if (doc.exists) {
reportData = doc.data();
if (reportData.order) {
if (reportData.order[newValue.status]) {
reportData.order[newValue.status]++;
} else {
reportData.order[newValue.status] = 1;
}
if (reportData.order[previousValue.status] && (reportData.order[previousValue.status] > 1)) {
reportData.order[previousValue.status]--;
} else {
reportData.order[previousValue.status] = 0;
}
} else {
reportData.order = {};
reportData.order[newValue.status] = 1;
}
} else {
reportData = {order: {}, sale: {}};
reportData.order[newValue.status] = 1;
}
if (newValue.status == STATUS_COMPLETE) {
if (reportData.sale.total) {
reportData.sale.total += orderTotal;
} else {
reportData.sale.total = orderTotal;
}
const year = date.getFullYear();
if (reportData.sale[year]) {
reportData.sale[year].total += orderTotal;
} else {
reportData.sale[year] = {
total: orderTotal
};
}
const month = date.getMonth() + 1;
if (reportData.sale[year][month]) {
reportData.sale[year][month].total += orderTotal;
} else {
reportData.sale[year][month] = {
total: orderTotal
};
}
const today = date.getDate();
if (reportData.sale[year][month][today]) {
reportData.sale[year][month][today].total += orderTotal;
} else {
reportData.sale[year][month][today] = {
total: orderTotal
};
}
}
console.log('report', reportData);
return reportDocRef.set(reportData);
});
}
});
// listen to item created
exports.itemOnCreate = functions.firestore.document('items/{itemId}').onCreate(function (snap, context) {
// Grab the current value of what was written to the Realtime Database
const original = snap.data();
console.log('item created: ', original);
updateCatItemCount(original.categoryId);
return updateItemCount(original.storeId);
});
// listen to item deleted
exports.itemOnDelete = functions.firestore.document('items/{itemId}').onDelete(function (snap, context) {
// Grab the previous value of what was written to the Realtime Database
const original = snap.data();
updateCatItemCount(original.categoryId);
return updateItemCount(original.storeId);
});
// listen for item review then update item & store rating
exports.calRate = functions.firestore.document('items/{itemId}/reviews/{reviewId}').onCreate(function (snap, context) {
// Grab the current value of what was written to the Realtime Database
const original = snap.data();
var itemDocRef = admin.firestore().collection('items').doc(context.params.itemId);
// calculate avg rating for item
itemDocRef.get().then(function (doc) {
var item = doc.data();
var storeDocRef = admin.firestore().collection('stores').doc(item.storeId);
if (item.rateCount) {
item.rating = (item.rating * item.rateCount + original.rating) / (item.rateCount + 1);
item.rateCount++;
} else { // on first time
item.rating = original.rating;
item.rateCount = 1;
}
itemDocRef.update({
rating: item.rating.toFixed(2),
rateCount: item.rateCount
});
storeDocRef.get().then(function (storeDoc) {
var store = storeDoc.data();
if (store.rateCount) {
store.rating = (store.rating * store.rateCount + original.rating) / (store.rateCount + 1);
store.rateCount++;
} else { // on first time
store.rating = original.rating;
store.rateCount = 1;
}
storeDocRef.update({
rating: store.rating.toFixed(2),
rateCount: store.rateCount
});
});
});
return true;
});
// update store's itemCount
var updateItemCount = function (storeId) {
var itemCount = 0;
var storeDocRef = admin.firestore().collection('stores').doc(storeId);
return admin.firestore().collection('items').where('storeId', '==', storeId).get().then(function (docs) {
itemCount = docs.size;
return storeDocRef.update({itemCount: itemCount});
});
};
// update category item count
var updateCatItemCount = function (catId) {
var itemCount = 0;
var storeDocRef = admin.firestore().collection('categories').doc(catId);
return admin.firestore().collection('items').where('categoryId', '==', catId).get().then(function (docs) {
itemCount = docs.size;
return storeDocRef.update({itemCount: itemCount});
});
};

How to stub chained functions using sinon in TypeORM

I'm currently using TypeORM and Sinonjs in my project. But I'm not sure how to write the unit test in the right way. Especially how to stub a chained function call, like this
async find(id: number): Promise<User> {
const user = await this.connection
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: id })
.getOne();
return user;
}
My test file
it('should return a data from db', async () => {
let user = {
id: 1,
name: 'my name'
};
const getOne = Sinon.stub().resolves(user);
const where = Sinon.stub().callsArg(0);
const createQueryBuilder = Sinon.stub().callsArg(0);
const connection = {
getRepository: Sinon.stub()
};
connection.getRepository.withArgs(User).returns(createQueryBuilder);
createQueryBuilder.withArgs('user').returns(where);
where.withArgs('user.id = :id', { id: user.id }).returns(getOne);
});
I always got this error
TypeError: this.connection.getRepository(...).createQueryBuilder is not a function
Any advice is welcome!
Thank you very much!
You should use sinon.stub(obj, 'method').returnsThis() to
Causes the stub to return its this value.
E.g.
index.ts:
type User = any;
export const model = {
connection: {
getRepository(model) {
return this;
},
createQueryBuilder(model) {
return this;
},
where(query, bindings) {
return this;
},
getOne() {
console.log('get one');
}
},
async find(id: number): Promise<User> {
const user = await this.connection
.getRepository('User')
.createQueryBuilder('user')
.where('user.id = :id', { id: id })
.getOne();
return user;
}
};
index.spec.ts:
import { model } from './';
import sinon from 'sinon';
import { expect } from 'chai';
describe('model', () => {
describe('#find', () => {
afterEach(() => {
sinon.restore();
});
it('should find user', async () => {
let mUser = {
id: 1,
name: 'my name'
};
const getRepository = sinon.stub(model.connection, 'getRepository').returnsThis();
const createQueryBuilder = sinon.stub(model.connection, 'createQueryBuilder').returnsThis();
const where = sinon.stub(model.connection, 'where').returnsThis();
const getOne = sinon.stub(model.connection, 'getOne').resolves(mUser);
const user = await model.find(1);
expect(user).to.be.eql(mUser);
expect(getRepository.calledWith('User')).to.be.true;
expect(createQueryBuilder.calledWith('user')).to.be.true;
expect(where.calledWith('user.id = :id', { id: 1 })).to.be.true;
expect(getOne.calledOnce).to.be.true;
});
});
});
Unit test result with coverage report:
model
#find
✓ should find user
1 passing (13ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 86.21 | 100 | 55.56 | 84.62 | |
index.spec.ts | 100 | 100 | 100 | 100 | |
index.ts | 50 | 100 | 20 | 42.86 | 6,9,12,15 |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/57843415

Firebase Cloud Function connection error when bucket.upload()

When I try to upload the generated PDF file to storage bucket, the firebase logs gives me that response after log "Init upload of file...":
Function execution took 3721 ms, finished with status: 'connection
error'
Maybe the problem can be the order of Promises. But I'm beginner with Cloud Functions and Node.js to reorder that.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const pdf = require('html-pdf');
const gcs = require('#google-cloud/storage')({keyFilename: './service_account.json'});
const handlebars = require('handlebars');
const path = require('path');
const os = require('os');
const fs = require('fs');
const bucket = gcs.bucket(bucketURL);
admin.initializeApp(functions.config().firebase);
var html = null;
exports.generatePdf = functions.https.onRequest((request, response) => {
// data to apply to template file
const user = {
"date": new Date().toISOString(),
"title": "Voucher",
"body": "Voucher body"
};
const options = {
"format": 'A4',
"orientation": "portrait"
};
const localPDFFile = path.join(os.tmpdir(), 'localPDFFile.pdf');
try {
const source = fs.readFileSync(__dirname + '/voucher.html', 'utf8');
html = handlebars.compile(source)(user);
} catch (error) {
console.error(error);
}
const phantomJsCloud = require("phantomjscloud");
const browser = new phantomJsCloud.BrowserApi(phantomApiKey);
var pageRequest = { content: html, renderType: "pdf" };
// // Send our HTML to PhantomJS to convert to PDF
return browser.requestSingle(pageRequest)
.then(function (userResponse) {
if (userResponse.statusCode !== 200) {
console.log("invalid status code" + userResponse.statusCode);
} else {
console.log('Successfully generated PDF');
// Save the PDF locally
fs.writeFile(localPDFFile, userResponse.content.data, {
encoding: userResponse.content.encoding,
}, function (err) {
console.log('Init upload of file...' + localPDFFile);
// Upload the file to our cloud bucket
return bucket.upload(localPDFFile, {
destination: '/pdfs/voucher.pdf',
metadata: {
contentType: 'application/pdf'
}
}).then(() => {
console.log('bucket upload complete: ' + localPDFFile);
response.status(200).send({
message: 'PDF Gerado com sucesso!',
address: localPDFFile
});
return true;
}).catch(error => {
response.status(400).send({
message: 'Error on bucket upload!',
error: error
});
return false;
});
});
return true;
}
return true;
});
})

Resources