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
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.
./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;
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});
});
};
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
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;
});
})