Unit testing code coverage for multer Node.js using Jest - node.js

I am trying to cover the lines and functions for code coverage of the simple multer file in Node.js but I am not getting where I am going wrong.
Here is the file:
fileUpload.js
const express = require("express");
const multer = require("multer");
const path = require("path");
const storage = multer.diskStorage({
destination: (req, file, callBack) => {
callBack(null, "uploads");
},
filename: (req, file, callBack) => {
callBack(null, `${file.originalname}`);
},
});
const upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (file.mimetype == "image/png" || file.mimetype == "image/jpg" || file.mimetype == "text/plain") {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('Only .png, .jpg, .txt and .jpeg format allowed!'));
}
}
})
module.exports = {
upload,
express
};
fileUpload.test.js
const multer = require('multer')
const fileStorage = require("../file-storage");
jest.mock('multer', () => {
const multer = () => ({
diskStorage: () => {
return {
destination: (req, res, callBack) => {
callBack(null, 'uploads')
},
filename: (req, res, callBack) => {
req.body = {
userName: 'testUser'
}
req.files = [{
originalname: 'sample.name',
mimetype: 'sample.type',
path: 'sample.url',
buffer: Buffer.from('whatever'), // this is required since `formData` needs access to the buffer
}, ]
return callBack()
}
}
},
})
multer.diskStorage = () => jest.fn()
return multer
})
describe('65317652', () => {
const upload = fileStorage.upload
it('should pass', () => {
const storage = upload.diskStorage();
const uploadData = multer({
storage: storage
});
expect(uploadData).toBeTruthy();
});
});
I have searched plenty of blogs but didn't find any proper solution.
How to cover those lines?

That might not be the best way to test your module which uses multer (I don't personally know this library). However you could manage to reach your code in the following way.
Get rid of your mock since you want to cover the code from your file-storage.js not the code from your mock.
Perform all calls in order to reach the code you want to cover.
Here is how you could reach the 'destination' code from you file-storage.js:
const multer = require('multer')
const fileStorage = require("../file-storage");
// 1 - Get rid of mock since this is not what you want to cover
//
// jest.mock('multer', () => {
// const multer = () => ({
// diskStorage: () => {
// return {
// destination: (req, res, callBack) => {
// callBack(null, 'uploads')
// },
// filename: (req, res, callBack) => {
// req.body = {
// userName: 'testUser'
// }
// req.files = [{
// originalname: 'sample.name',
// mimetype: 'sample.type',
// path: 'sample.url',
// buffer: Buffer.from('whatever'), // this is required since `formData` needs access to the buffer
// },]
// return callBack()
// }
// }
// },
// })
// multer.diskStorage = () => jest.fn()
// return multer
// })
describe('65317652', () => {
const upload = fileStorage.upload
it('should pass', () => {
const storage = upload;
const multerRes = multer({
storage: storage
});
// 2 - Perform all calls in order to reach the code you want to cover.
//
expect(multerRes).toBeTruthy();
const diskStorageRes = multerRes.storage.storage
expect(diskStorageRes).toBeTruthy();
const getDestinationRes = diskStorageRes.getDestination()
expect(getDestinationRes).toBeTruthy();
});
});
There seem to be other more appropriate ways to test multer. See https://stackoverflow.com/a/58474936/6210975 for example.

Related

Can't get the flutter posted image to the nodjes server

I want to get the image that have been posted to the nodejs flutter. I am using the bellow code in the flutter:
_openCameraPicture(BuildContext context) async {
var image = await ImagePicker().pickImage(source: ImageSource.camera);
setState(() async {
selectedImage = File(image!.path);
if (Network.isConnected) {
var request = http.MultipartRequest(
'POST', Uri.parse("${Network.serverUrl}/upload_image"));
request.fields['title'] = "testImage";
request.files.add(http.MultipartFile.fromBytes(
'picture', selectedImage!.readAsBytesSync(),
filename: selectedImage!.path.split("/").last));
var response =
await request.send().then((value) => {Navigator.pop(context)});
// if (response.statusCode == 200) {
// Do something ...
// }
}
});
}
In my nodejs server api I use:
router.post("/upload_image", PhoneInfoCtrl.apiUploadImage);
But I can not get the image and upload it to the server. My apiUploadImage is like:
static async apiUploadImage(req, res, next) {
try {
console.log(req);
res.sendStatus(200);
} catch (error) {
//res.status(500);
}
}
How may I extract the image from req? I tested some ways but was not possible. Is something wrong with my flutter code?
Solved by using multer in my router in nodejs.
const path = require('path');
const multer = require("multer");
const storage = multer.diskStorage({
destination : (req, file, cb) =>{
cb(null , "./public/images")
},
filename: (req, file, cb) =>{
console.log(file);
cb(null , Date.now() + path.extname(file.originalname));
},
});
then:
router.post("/upload_image", upload.single("picture"),myController.apiUploadImage);

Upload workds, but I can't access req.file in Node Express using Multer

The uploaded file is saved in the destination, but I can't access it in the express route.
Why might this be happening? It makes no sense to me...
EDIT: What I've noticed is that the console.log sometimes works, and sometimes doesn't... This is even weirder. If I upload a tiny file, it always logs. For larger ones, it randomly decides when to log. I wonder what the problem might be.
Client
async function uploadFile(file) {
console.log("file", file);
let formData = new FormData();
formData.append("file", file);
formData.append("recordUid", recordUid);
formData.append("fieldUid", fieldUid);
await fetchPostFile("/api/files", formData);
}
export async function fetchPostFile(url, formData) {
try {
let result = await (
await fetch(url, {
method: "POST",
withCredentials: true,
credentials: "include",
headers: {
Authorization: localStorage.getItem("token"),
},
body: formData,
})
).json();
return result;
} catch (err) {
return err;
}
}
Server
const express = require("express");
const router = express.Router();
const path = require("path");
const fs = require("fs");
const multer = require("multer");
const { getUidForTable } = require(`../utils.js`);
let filesFolderPath = path.join(__dirname, "../../../files/");
const storage = multer.diskStorage({
destination: filesFolderPath,
filename: async (req, file, cb) => {
cb(null, await getUidForTable("recordDataFile", 20));
},
});
const upload = multer({
storage: storage,
limits: { fileSize: 1024 * 1024 * 10 }, // 10 mb
});
router.post("/api/files", upload.single("file"), async (req, res, next) => {
try {
console.log(req.file, req.body); // <------------ NOTHING IS LOGGED
res.status(200).send({ success: true });
} catch (err) {
next(err);
} finally {
req.connection.release();
}
});

Express Router - uploading to S3 and MongoDb

I'm trying to do the following in Node.js using express router, Multer-S3, Multer, AWS and Mongodb.
I want to:
1: Check if filetype is image, price is number etc (some kind of quality check)
2: If above true, upload image to S3 to get Image url
3: If Image Url was generated, upload to Mongodb, including the generated image url..
Trying with below code but can only get one of these to work at same time..
const express = require("express");
const router = express.Router();
const shopController = require("../controllers/shop");
router.post(
"/shop/create/:shopId",
shopController.creatingShop,
shopController.createShopItem
);
const ShopItem = require("../models/shopitem"); //Mongoose Schema
const multer = require("multer");
const fileview = multer().single("file1"); //Trying to use this to view file before uploading to S3
const uploader = require("../services/file-upload");
const singleUpload = uploader.single("file1"); //Using this to upload to S3
exports.createShopItem = (req, res, next) => {
fileview(req, res, function (err) {
const file = req.file;
const title = req.body.title;
const price = req.body.price;
const description = req.body.description;
const location = req.body.location;
const user = "OrreSnorre";
if (
file.mimetype != "image/jpeg" &&
file.mimetype != "image/jpg" &&
file.mimetype != "image/png"
) {
return next(new Error("invalid file type"));
}
if (file.size > 2500000) {
return next(new Error("Your image is to big. Maximum 2.5mb"));
}
next();
console.log(
"Here I want to add upload text to mongoDb... including URL from S3 after it is generated"
);
});
exports.creatingShop = (req, res, next) => {
singleUpload(req, res, function (err) {
console.log(req.file);
// res.json({ "image-url": req.file.location });
});
next();
};
Anyone got ideas? Or examples that work?
Best regards,
Oscar
There are 2 ways to do this, either you can use only multer or multer-s3.
For simplicity, I will show you the way using only multer.
Flow of processing as follow:
Multer process and save to local
You read from local, and upload to s3 using s3 SDK (You should explore how to remove the file after upload as well, but I wont clutter you with this logic here)
If upload is successful, you retrieve the URL and pass it to your MongoDB.
// Make "temp" directory as multer.diskStorage wont create folder
fs.mkdir('./temp', { recursive: true }, (err) => {
if (err) throw err;
});
const PORT = parseInt(process.argv[2]) || parseInt(process.env.PORT) || 3000;
// Multer
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './temp');
},
filename: function (req, file, cb) {
let extArray = file.mimetype.split('/');
let extension = extArray[extArray.length - 1];
cb(null, new Date().getTime() + '.' + extension);
},
});
const upload = multer({ storage: storage });
const endpoint = new AWS.Endpoint(AWS_S3_HOSTNAME);
const s3 = new AWS.S3({
endpoint,
accessKeyId: AWS_S3_ACCESSKEY_ID,
secretAccessKey: AWS_S3_SECRET_ACCESSKEY,
});
// Get the uploaded file in local here
const readFile = (path) =>
new Promise((resolve, reject) =>
fs.readFile(path, (err, buff) => {
if (null != err) reject(err);
else resolve(buff);
})
// Upload to AWS S3 here
const putObject = (file, buff, s3) =>
new Promise((resolve, reject) => {
const params = {
Bucket: AWS_S3_BUCKET_NAME,
Key: file.filename,
Body: buff,
ACL: 'public-read',
ContentType: file.mimetype,
ContentLength: file.size,
};
s3.putObject(params, (err, result) => {
if (null != err) reject(err);
else resolve(file.filename);
});
});
);
const mongoClient = new MongoClient(MONGO_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
app.post('/api/post', upload.single('imageFile'), async (req, res) => {
readFile(req.file.path)
.then((buff) =>
// Insert Image to S3 upon succesful read
putObject(req.file, buff, s3)
)
.then((results) => {
// build url of the resource upon successful insertion
const resourceURL = `https://${AWS_S3_BUCKET_NAME}.${AWS_S3_HOSTNAME}/${results}`;
const doc = {
comments,
title,
ts: new Date(),
image: resourceURL, // Your URL reference to image here
};
// Insert to your mongoDB
mongoClient
.db(MONGO_DB)
.collection(MONGO_COLLECTION)
.insertOne(doc)
.then((results) => {
// delete the temp file when no error from MONGO & AWS S3
fs.unlink(req.file.path, () => {});
// return the inserted object
res.status(200).json(results.ops[0]);
})
.catch((error) => {
console.error('Mongo insert error: ', error);
res.status(500);
res.json({ error });
});
})
.catch((error) => {
console.error('insert error: ', error);
res.status(500);
res.json({ error });
});
}

How to create a filename from frontend?

I am creating a React and Node application, and at the moment I need to upload a PDF file, the code works well, but I was wondering how to use front variables to create the file name by multer.
The code...
Front:
const handleSubmit = async (e) => {
const dado = new FormData()
const year = data.split("/")
const dd = year[0];
const mm = year[1];
const aaaa = year[2];
dado.append('file', file)
dado.append('uni', uni)
dado.append('dd', dd)
dado.append('mm', mm)
dado.append('aaaa', aaaa)
console.log(dado)
axios.post("http://localhost:8080/api/comprebem/file", dado, {
})
.then(res => {
console.log(res.statusText)
})
}
Back:
const storage = multer.diskStorage({
destination:"../login/public/comprebem/ibama/",
filename: function (req, file, cb) {
console.log(file);
cb(null, `unidade${"req['uni']"}_data${file.originalname}` )
}
})
const upload = multer({ storage: storage }).single('file')
app.post('/api/comprebem/file',function(req, res) {
upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
return res.status(500).json(err)
} else if (err) {
return res.status(500).json(err)
}
return res.status(200).send(req.file)
})
});

How can i change the size of images with multer with sharp?

I built a web app and want to resize my images to be smaller for better quality to my profile pictures. I am using "multer" for upload picture and sharp package for resizing.
For some reason i get this error:
"[0] [Error: D:\DevConnectors\public\resized\5f4f4e0bb295ba36042536bf.jpg: unable to open for write
[0] windows error: The storage control block address is invalid."
My code:
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, DIR)
},
filename: (req, file, cb) => {
var typeFile = file.originalname.split(".").pop()
const fileName = req.user.id + "." + typeFile
cb(null, fileName)
},
})
var upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (
file.mimetype == "image/png" ||
file.mimetype == "image/jpg" ||
file.mimetype == "image/jpeg"
) {
cb(null, true)
} else {
return cb(new Error("Only .png, .jpg and .jpeg format allowed!"), false)
}
},
})
const Profile = require("../../moduls/Profile")
const User = require("../../moduls/User")
//#route GET/api/profile/me
//#desc Get current users profile
//#access Private
router.post(
"/upload",
[auth, upload.single("imageProfile")],
async (req, res) => {
try {
console.log(req.file)
const { filename: image } = req.file
await sharp(req.file.path)
.resize(150)
.jpeg({ quality: 50 })
.toFile(path.resolve(req.file.destination, "resized", image))
fs.unlinkSync(req.file.path)
const url = req.protocol + "://" + req.get("host")
let user = await User.findById(req.user.id)
const profile = await Profile.findOne({ user: req.user.id })
//Update
if (user) {
user.avatar = url + "/public/" + req.file.filename
await user.save()
return res.json(profile)
}
} catch (err) {
console.log(err)
}
}
)
This happens at this line :
path.resolve(req.file.destination,'resized',image))
What am i doing wrong? i am using sharp docs.
Try this
Configuration of multer ( change it according to your needs)
import multer from "multer";
import sharpe from "sharp";
const upload = multer({ //multer configuration
//dest: "avatars", //so that buffer is available in route handler
limits: {
fileSize: 1000000,
},
fileFilter(req, file, cb) { // object method shorthand syntax
if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) { //.match for using regex b/w (//)
return cb(new Error("Please upload a IMAGE"));
}
cb(undefined, true);
},
});
handle sharp in your route handle like this.
router.post(
"path",
upload.single("avatar"),
async (req, res) => {
const buffer = await sharpe(req.file.buffer)
.png()
.resize({
width: 300,
height: 300
})
.toBuffer();
req.user.avatar = buffer;
await req.user.save();
res.send();
},
(error, req, res, next) => {
//to tell express this how mutler/s error should be handled
res.status(400).send({
error: error.message,
});
}
);
I had the same problem, and in my case the issue was that the directory (in your case "resized") didn't exist yet. So either create it manually or programmatically like this:
const targetPath = path.resolve(req.file.destination, 'resized')
if (!existsSync(targetPath)) {
mkdirSync(targetPath);
}
await sharp(...)

Resources