Mock stream finish on jest - javascript

I have a function that downloads a file using got and uses fs.createWritableStream to write file on disk. The code is working smooth but the unit test are being a pain:
download(twilioPath, destFile) {
return new Promise(function(resolve, reject) {
const uri = `${TWILIO_BASE_URL}${twilioPath}`.replace('json', 'mp3')
let file = fs.createWriteStream(`/tmp/${destFile}`)
console.log(got)
let str = got.stream(uri)
console.log(uri)
str.on('error', function(err) {
console.log('Error dl', err)
reject(err)
})
str.pipe(file)
file.on('finish', function() {
console.log('banana')
let cb = function() {
console.log('Download completed')
resolve(file)
}
file.close(cb)
})
})
My resolve or reject is not being called and I don't know how to fix that:
const Recording = require('./Recording')
const Readable = require('stream')
var mockedStream = new Readable();
var mockedWrite = new Readable.Writable()
jest.mock('fs', () => ({
createWriteStream: jest.fn((file_name) => {
return mockedWrite;
})
}));
jest.mock('got', () => {
return {
stream: () => {
return mockedStream
}
}
})
describe('Recording', () => {
test('should download a file from twilio', async () => {
...
mockedStream.emit('error')
mockedWrite.emit('finish')
console.log(result)
....
})
})
I've tried to force emit events but no lucky.

Related

How to create a broken stream for a test?

I have this function and I want to test it. I want to test stream.on('error', err => reject(err)); line, but don't know how to reach it. What could I input in this function to trigger the error throw? Thank you!
function streamToString(stream) {
const chunks = [];
return new Promise((resolve, reject) => {
stream.on('data', chunk => chunks.push(Buffer.from(chunk)));
stream.on('error', err => reject(err));
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
});
}
If you are in nodejs and using jest you could Emit the error by your self like:
function streamToString(stream) {
const chunks = [];
return new Promise((resolve, reject) => {
stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
stream.on("error", (err) => reject(err));
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
});
}
test("broken stream", () => {
let fs = require("fs");
let stream = fs.createReadStream("file.txt");
let res = streamToString(stream);
stream.emit("error", "OH NO!");
return expect(res).rejects.toMatch("OH NO!");
});
//or
test("broken stream", () => {
let fs = require("fs");
let stream = fs.createReadStream("file.txt");
let res = streamToString(stream);
stream.destroy("OH NO!");
return expect(res).rejects.toMatch("OH NO!");
});

How to handle specific errors code in a geolocation promise?

I have a function that fetches the user's location. It was working this way:
const fetchGeoLocation: SearchService["fetchGeoLocation"] = async () => {
const geo = navigator.geolocation;
if (!geo) throw new Error("error.geolocation-unavailable");
const handleError = (err: any) => {
if (err.code === 1) throw new Error("error.geolocation-permission_denied");
if (err.code === 2) throw new Error("error.geolocation-unavailable");
if (err.code === 3) throw new Error("error.geolocation-timeout");
};
const handleSuccess = (position) => {
return { location: [position.coords.longitude, position.coords.latitude] };
};
geo.getCurrentPosition(handleSuccess, handleError, { maximumAge: 10000 });
};
const onUpdateLocation = async () => {
onLoad();
fetchGeoLocation()
.then((res) => onSave(res.data))
.catch(({ message }) => onError(message));
};
Because it was not a promise, the onSave() function triggered before fetchGeolocation ended. So I have to promisify it. Writing this would work:
function fetchGeolocation () {
return new Promise((resolve, reject) =>{
navigator.geolocation.getCurrentPosition(resolve, reject);
});
};
fetchGeolocation()
.then(res => onSave(res)
.catch(err => onError(err.message);
But I would need to handle all the error codes in the catch callback. I want to handle everything inside the fetchGeolocation function. How to do it?
Thanks!
If I followed your idea properly, then the next snippet might help you out:
const fetchGeoLocation: SearchService["fetchGeoLocation"] = async () => {
return new Promise((resolve, reject) => {
const { geolocation } = navigator;
if (!geolocation) reject("error.geolocation-unavailable");
const handleError = ({ code }) => {
if (code === 1) reject("error.geolocation-permission_denied");
if (code === 2) reject("error.geolocation-unavailable");
if (code === 3) reject("error.geolocation-timeout");
};
const handleSuccess = (position) => {
resolve({ location: [position.coords.longitude, position.coords.latitude] });
};
geo.getCurrentPosition(handleSuccess, handleError, { maximumAge: 10000 });
});
};
Notice instead of throw'ing, it's reject'ing the promise with the error string.

How to wait for function to finish before running another one or rest of the code?

I can't figure this one out. I have one function that connects to an SFTP server and downloads files. Then, I have a second function that reads the contents, puts the data in an array, and returns the array.
The problem is that the second function always runs first. I tried different methods but I can't get it to work. That connection to SFTP is quite slow, it can take like 10+ seconds to finish. But I need to somehow wait for it to finish before doing anything else.
const SFTPConfig = require('../config/keys').sftpconfig;
const getCSATFiles = async function(targetDate) {
try {
let Client = require('ssh2-sftp-client');
let sftp = new Client();
const date = moment(targetDate);
var dir = `../csv/${targetDate}/`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
sftp
.connect(SFTPConfig, 'once')
.then(() => {
return sftp.list('/In/Archives/');
})
.then(data => {
data.forEach(item => {
const fileName = item.name;
const remotePath = '/In/Archives/' + fileName;
const localePath = path.join(dir + fileName);
if (
moment(item.modifyTime)
.format('YYYY-MM-DD hh:mm')
.toString()
.slice(0, 10) ===
date
.format('YYYY-MM-DD hh:mm')
.toString()
.slice(0, 10)
) {
sftp
.fastGet(remotePath, localePath, {})
.then(() => {
console.log('finished getting the files!');
sftp.end();
})
.catch(err => {
sftp.end();
console.log(err, 'fastGet method error');
});
}
});
});
} catch (error) {
console.log(error);
}
};
const readCSVFiles = async function(targetDate) {
try {
const casesBO = [];
var dir = `../csv/${targetDate}/`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
const allLocalFiles = path.join(__dirname, dir);
const readDir = util.promisify(fs.readdir);
const files = await readDir(allLocalFiles);
for (let file of files) {
fs.createReadStream(allLocalFiles + file)
.pipe(csv.parse({ headers: true, delimiter: ';' }))
.on('error', error => console.error(error))
.on('data', row => {
if (row['[REGION2]'] !== 'FR') {
casesBO.push(row['[CALLERNO_EMAIL_SOCIAL]']);
console.log(
`${row['[AGENT]']} is ${row['[REGION2]']} and case = ${
row['[CALLERNO_EMAIL_SOCIAL]']
}`
);
}
})
.on('end', rowCount => {
console.log(`Parsed ${rowCount} rows`);
});
}
return casesBO;
} catch (error) {
console.log(error);
}
};
const testFunc = async () => {
const csatfiles = await getCSATFiles('2021-02-03');
const boData = await readCSVFiles('2021-02-03');
console.log(boData);
};
testFunc();
#1 as #messerbill suggested, you need to return the promise from your function.
#2 your promise has a loop inside of it that have more promises. In this case, you need to collect those promises and use Promise.all to resolve them before you second function runs. I put comments on the lines you need to change below. Try this:
const SFTPConfig = require('../config/keys').sftpconfig;
const getCSATFiles = function(targetDate) {
try {
let Client = require('ssh2-sftp-client');
let sftp = new Client();
const date = moment(targetDate);
var dir = `../csv/${targetDate}/`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
// I return the promise here
return sftp
.connect(SFTPConfig, 'once')
.then(() => {
return sftp.list('/In/Archives/');
})
.then(data => {
// I set up my promises as a blank array
const promises = [];
data.forEach(item => {
const fileName = item.name;
const remotePath = '/In/Archives/' + fileName;
const localePath = path.join(dir + fileName);
if (
moment(item.modifyTime)
.format('YYYY-MM-DD hh:mm')
.toString()
.slice(0, 10) ===
date
.format('YYYY-MM-DD hh:mm')
.toString()
.slice(0, 10)
) {
// I collect the promises here
promises.push(sftp
.fastGet(remotePath, localePath, {})
.then(() => {
console.log('finished getting the files!');
sftp.end();
})
.catch(err => {
sftp.end();
console.log(err, 'fastGet method error');
}));
}
});
// I resolve them here
return Promise.all(promises);
});
} catch (error) {
console.log(error);
}
};
const readCSVFiles = async function(targetDate) {
try {
const casesBO = [];
var dir = `../csv/${targetDate}/`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
const allLocalFiles = path.join(__dirname, dir);
const readDir = util.promisify(fs.readdir);
const files = await readDir(allLocalFiles);
for (let file of files) {
fs.createReadStream(allLocalFiles + file)
.pipe(csv.parse({ headers: true, delimiter: ';' }))
.on('error', error => console.error(error))
.on('data', row => {
if (row['[REGION2]'] !== 'FR') {
casesBO.push(row['[CALLERNO_EMAIL_SOCIAL]']);
console.log(
`${row['[AGENT]']} is ${row['[REGION2]']} and case = ${
row['[CALLERNO_EMAIL_SOCIAL]']
}`
);
}
})
.on('end', rowCount => {
console.log(`Parsed ${rowCount} rows`);
});
}
return casesBO;
} catch (error) {
console.log(error);
}
};
const testFunc = async () => {
const csatfiles = await getCSATFiles('2021-02-03');
const boData = await readCSVFiles('2021-02-03');
console.log(boData);
};
testFunc();
You need to take care, that you return the promise you want to resolve inside the function body in order to get the promise resolved at the right time.
async function promiseNotReturned() {
new Promise(resolve => setTimeout(resolve.bind(null), 5000))
}
async function promiseReturned() {
return new Promise(resolve => setTimeout(resolve.bind(null), 5000))
}
async function run() {
await promiseNotReturned()
console.log("does not wait for 5 seconds")
await promiseReturned()
console.log("waits for 5 seconds")
}
run()

cancel multiple promises inside a promise on unmount?

hi i want to cancel promise on unmount since i received warning,
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
My code:
const makeCancelable = (promise: Promise<void>) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
(val) => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)),
(error) => (hasCanceled_ ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
useEffect(() => {
const initialize = async () => {
const getImageFilesystemKey = (remoteUri: string) => {
const [_, fileName] = remoteUri.split('toolbox-talks/');
return `${cacheDirectory}${fileName}`;
};
const filesystemUri = getImageFilesystemKey(uri);
try {
// Use the cached image if it exists
const metadata = await getInfoAsync(filesystemUri);
if (metadata.exists) {
console.log('resolve 1');
setFileUri(filesystemUri);
} else {
const imageObject = await downloadAsync(uri, filesystemUri);
console.log('resolve 2');
setFileUri(imageObject.uri);
}
// otherwise download to cache
} catch (err) {
console.log('error 3');
setFileUri(uri);
}
};
const cancelable = makeCancelable(initialize());
cancelable.promise
.then(() => {
console.log('reslved');
})
.catch((e) => {
console.log('e ', e);
});
return () => {
cancelable.cancel();
};
}, []);
but i still get warning on fast press, help me please?
You're cancelling the promise, but you are not cancelling the axios call or any of the logic that happens after it inside initialize(). So while it is true that the console won't print resolved, setFileUri will be called regardless, which causes your problem.
A solution could look like this (untested):
const makeCancelable = (promise: Promise<void>) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)),
error => (hasCanceled_ ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
}
};
};
const initialize = async () => {
const getImageFilesystemKey = (remoteUri: string) => {
const [_, fileName] = remoteUri.split("toolbox-talks/");
return `${cacheDirectory}${fileName}`;
};
const filesystemUri = getImageFilesystemKey(uri);
try {
// Use the cached image if it exists
const metadata = await getInfoAsync(filesystemUri);
if (metadata.exists) {
console.log("resolve 1");
return filesystemUri;
} else {
const imageObject = await downloadAsync(uri, filesystemUri);
console.log("resolve 2");
return imageObject.uri;
}
// otherwise download to cache
} catch (err) {
console.error("error 3", err);
return uri;
}
};
useEffect(() => {
const cancelable = makeCancelable(initialize());
cancelable.promise.then(
fileURI => {
console.log("resolved");
setFileUri(fileURI);
},
() => {
// Your logic is such that it's only possible to get here if the promise is cancelled
console.log("cancelled");
}
);
return () => {
cancelable.cancel();
};
}, []);
This ensures that you will only call setFileUri if the promise is not cancelled (I did not check the logic of makeCancelable).

Writing to file in a promise

The function here creates a bunch of files but does not write to them. How ever, if I remove the Promise.all in the end and don't resolve the function at all it DOES write the data to the files. It doesn't matter what I try to write, I can comment out everything and just write 'hello world' and it still won't write anything. The question is simple, why?
const writeSmilFiles = (smilInfo) => {
return new Promise ((resolve, reject) => {
const p1 = smilPartOne();
const p2 = smilPartTwo();
let promises = dataTypes.new.camera_set.map(instance => {
return new Promise((resolve, reject) => {
const smilEntries = smilInfo.filter(smil => smil.BroadcastAndKlippTupleId == instance.BroadcastAndKlippTupleId && smil.CameraId == instance.CameraId);
try {
const fileName = `${__dirname}/newSmilFiles/${smilEntries[0].Smil}`;
const file = fs.createWriteStream(fileName);
file.write(p1);
smilEntries.forEach(entry => {
const smilEntry = smilSwitch(entry.Filename, entry.Bitrate, entry.Width, entry.Height);
file.write(smilEntry);
console.log(smilEntry);
file.write('\n');
});
file.write(p2);
file.end();
resolve(`Smil written.`);
} catch (ex) {
reject(ex);
}
});
});
Promise.all(promises).then(msg => {
resolve(msg);
});
});
};
Resolve when the stream has actually finished:
file.on("finish", () => resolve(`Smil written.`));

Categories

Resources