With Chai expect, I am trying to compare API response with expected response. The expected response of API changes with time and can be one of two possible static JSONs.
Is there any way in Chai, to expect one of these two JSONs?
I know that it works perfectly fine with single value: expect(actualResponse).to.deep.equal(expectedJson);
But I want something like: expect(actualResponse).to.deep.equal(expectedJson1, expectedJson2);
You could use .satisfy(matcher[, msg]) method of chai.
E.g.
import { expect } from 'chai';
import _ from 'lodash';
describe('63811326', () => {
it('should pass', () => {
function genResponse() {
const successResponse = {
data: {},
success: true,
};
const failResponse = {
success: false,
errorMessage: 'API error',
};
return Math.random() > 0.5 ? failResponse : successResponse;
}
const expectedJson1 = { data: {}, success: true };
const expectedJson2 = { success: false, errorMessage: 'API error' };
expect(genResponse()).to.satisfy((actualResponse) => {
console.log(actualResponse);
return _.isEqual(actualResponse, expectedJson1) || _.isEqual(actualResponse, expectedJson2);
});
});
});
unit test result for the first execution:
63811326
{ success: false, errorMessage: 'API error' }
✓ should pass
1 passing (26ms)
unit test result for the second execution:
63811326
{ data: {}, success: true }
✓ should pass
1 passing (31ms)
Related
My reducer file is below
const slice = createSlice({
name: "hotels",
initialState: {
list: [],
loading: false,
lastFetch: null,
},
reducers: {
hotelsRequested: (hotels) => {
hotels.loading = true;
},
hotelsRequestFailed: (hotels) => {
hotels.loading = false;
},
hotelsReceived: (hotels, action) => {
hotels.list = action.payload;
hotels.loading = false;
hotels.lastFetch = Date.now();
},
hotelEnabled: (hotels, action) => {
const { slug } = action.payload;
const index = hotels.list.findIndex((hotel) => hotel.slug === slug);
hotels.list[index].active = true;
},
},
});
export const {
hotelsReceived,
hotelsRequestFailed,
hotelsRequested,
hotelEnabled,
} = slice.actions;
export default slice.reducer;
//Action creators
export const loadHotels = () => (dispatch, getState) => {
const { lastFetch } = getState().entities.hotels;
const diffInMinutes = moment().diff(lastFetch, "minutes");
if (diffInMinutes < 10) return;
dispatch(
hotelApiCallBegan({
url: hotelUrl,
onStart: hotelsRequested.type,
onSuccess: hotelsReceived.type,
onError: hotelsRequestFailed.type,
})
);
};
export const enableHotel = (slug) =>
hotelApiCallBegan(
{
url: `${hotelUrl}${slug}/partial-update/`,
method: "put",
data: { active: true },
onSuccess: hotelEnabled.type,
},
console.log(slug)
);
My api request middleware function is as follows
export const hotelsApi = ({ dispatch }) => (next) => async (action) => {
if (action.type !== actions.hotelApiCallBegan.type) return next(action);
const {
onStart,
onSuccess,
onError,
url,
method,
data,
redirect,
} = action.payload;
if (onStart) dispatch({ type: onStart });
next(action);
try {
const response = await axiosInstance.request({
baseURL,
url,
method,
data,
redirect,
});
//General
dispatch(actions.hotelApiCallSuccess(response.data));
//Specific
if (onSuccess) dispatch({ type: onSuccess, payload: response.data });
} catch (error) {
//general error
dispatch(actions.hotelApiCallFailed(error.message));
console.log(error.message);
//Specific error
if (onError) dispatch({ type: onError, payload: error.message });
console.log(error.message);
}
};
Could anyone point me in the right direction of how to add an optimistic update reducer to this code. Currently on hitting enable button on the UI there's a lag of maybe second before the UI is updated. Or maybe the question, is do i create another middleware function to handle optimistic updates? If yes how do i go about that? Thanks
I have been trying to implement CKEditor5 into a vuejs project and after getting all the infrastructure working, I cannot get the actual file to upload to a php server. The code calls the server and if I return a success message and file url, it all works correctly. Here is my code:
<template>
...
<ckeditor :editor="editor" v-model="details.SystemText" :config="editorConfig"></ckeditor>
...
</template>
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
class UploadAdapter {
constructor(loader) {
this.loader = loader;
}
upload() {
return new Promise((resolve, reject) => {
const data = new FormData();
data.append('upload', this.loader.file);
axios({
url: '/index/uploadimage',
method: 'post',
data,
headers: {
'Content-Type': 'multipart/form-data;'
},
withCredentials: false
}).then(response => {
if (response.data.result == 'success') {
resolve({
default: response.data.url
});
} else {
reject(response.data.message);
}
}).catch(response => {
reject ( 'Upload failed');
});
});
}
abort() {
}
}
export default {
data () {
details: {},
editor: ClassicEditor,
editorConfig: {
extraPlugins: [ this.MyCustomUploadAdapterPlugin ],
}
},
methods: {
MyCustomUploadAdapterPlugin ( editor ) {
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
return new UploadAdapter( loader );
};
},
}
Clicking the image icon in the toolbar will show the file select dialogue correctly, and upon file selection will submit a post to the server. However, the binary file is not sent, but simply:
Form Data
------WebKitFormBoundaryqPGA20WRKz9VvADd
Content-Disposition: form-data; name="upload"
[object Promise]
I have spent two days looking at all other plugins like CKFinder and others, and I seem to always get the same content being sent to the server. The line
data.append('upload', this.loader.file);
does not seem to append the actual file which is what I think it should do.
My value of this.loader is
loader.file
Promise {<pending>}
__proto__: Promise
catch: ƒ catch()
constructor: ƒ Promise()
finally: ƒ finally()
then: ƒ then()
Symbol(Symbol.toStringTag): "Promise"
__proto__: Object
[[PromiseStatus]]: "pending"
[[PromiseValue]]: undefined
Tried using their cloudservice but point to my own urls and that got the upload working.
editorConfig: {
cloudServices: {
tokenUrl: '/index/tokenendpoint',
uploadUrl: '/index/uploadimage'
}
}
I was also able to remove all the upload adapter code.
Thank you
Reason for your problem is that in version 11.0.0 of ckeditor5-upload plugin the API was changed, loader.file is now a Promise (see release notes). Unfortunately the docs were not updated accordingly.
You need to adjust your upload function a little:
upload() {
return this.loader.file
.then( uploadedFile => {
return new Promise( ( resolve, reject ) => {
const data = new FormData();
data.append( 'upload', uploadedFile );
axios( {
url: '/index/uploadimage',
method: 'post',
data,
headers: {
'Content-Type': 'multipart/form-data;'
},
withCredentials: false
} ).then( response => {
if ( response.data.result == 'success' ) {
resolve( {
default: response.data.url
} );
} else {
reject( response.data.message );
}
} ).catch( response => {
reject( 'Upload failed' );
} );
} );
} );
}
The docs that had this issue are now fixed and use promise properly. Hope this solves the problem for you!
Use jQuery ajax. I cannot find an equivalent using fetch or axios. The key is setting contentType: false and processData: false.
upload() {
return new Promise((resolve, reject) => {
const data = new FormData();
data.append('postedFile', this.loader.file);
$.ajax({
url: '/index/uploadimage',
data,
contentType: false,
processData: false,
type: 'POST',
success: response => {
resolve({
default: response.data.url
});
},
error: () => {
reject('Upload failed');
}
});
});
}
They are working on it, it is a bug.
https://github.com/ckeditor/ckeditor5/issues/1618
The issue is regarding the unit testing using JEST.
How to test the a function X which is returning a promise which is making an async HTTP request.
Function X
import httpHelper from './httpHelper';
function fetchMoxtraAccessToken(userEWAToken, successCallback, failureCallback) {
const httpObj = {
url: '/getAccessToken',
headers: { Authorization: userEWAToken },
};
return httpHelper(httpObj, successCallback, failureCallback);
}
How to unit test the function X on the basis of the response?
The easiest way is to mock httpHelper so it just returns a spy on which you can test that is was called with the correct parameter:
jest.mock('./httpHelper', () = > jest.fn()) //Path must be relative to test file
import httpHelper from './httpHelper'
describe(('fetchMoxtraAccessToken') => {
it(('makes correct http request') => {
const userEWAToken = 'someUserEWAToken'
const successCallback = jest.fn()
const failureCallback = jest.fn()
fetchMoxtraAccessToken(userEWAToken, successCallback, failureCallback)
expect(httpHelper).toHaveBeenCalledWith({
url: '/getAccessToken',
headers: {
Authorization: 'someUserEWAToken'
},
}, successCallback, failureCallback)
})
})
I am using hapijs version 17.0.1. I am trying to upload an image using ajax request on a hapijs route. Here is my AJAX code to upload profile pic:
var image_file_input = document.getElementById("user_profile_upload");
image_file_input.onchange = function () {
if(this.files != undefined)
{
if(this.files[0] != undefined)
{
var formData = tests.formdata ? new FormData() : null;
if (tests.formdata)
{
//alert(file)
formData.append('image_file', this.files[0]);
formData.append('userId', user_id);
formData.append('memberId', member_id);
}
$.ajax({
url: "/v1/User/uploadUserPic",
data: formData,
type: "POST",
dataType: "json",
contentType: false,
processData: false,
contentType: "multipart/form-data",
success: function(data){
console.log(data);
var errMsg = null;
var resData = null;
if(data.statusCode == 200)
{
resData = data.result;
}
else
{
alert(data.message)
}
},
error: function(error){
alert(error);
}
});
}
}
}
And here is my Hapijs route Code:
var uploadUserPic = {
method: 'POST',
path: '/v1/Module/uploadUserPic',
config: {
description: 'Update Image For User',
tags: ['api', 'User'],
auth: 'session',
payload: {
output: 'stream',
parse: true,
allow: 'multipart/form-data'
},
validate: {
payload: {
userId : Joi.string().regex(/^[a-f\d]{24}$/i).required(),
memberId: Joi.string().required(),
image_file: Joi.object().required(),
},
failAction: FailCallBack
}
},
handler: function (request, reply) {
var resultData = null;
var error = null;
return new Promise(function (resolve) {
var multiparty = require('multiparty');
var fs = require('fs');
var form = new multiparty.Form();
form.parse(request.payload, function (err, fields, files) {
if(err)
{
error = err;
resolve();
}
else
{
var mkdirp = require('mkdirp');
var img_dir = "./files/users/";
mkdirp(img_dir, function (err) {
if (err)
{
error = err;
console.error(err);
resolve();
}
else
{
var oldpath = files.image_file.path;
var newpath = "./files/users/"+requestPayload.userId+".png";
fs.rename(oldpath, newpath, function (err) {
if(err)
{
error = err;
}
resolve();
});
}
});
}
});
}).then(function (err, result) {
if(err) return sendError(err);
if(error) return sendError(error)
return {
"statusCode": 200,
"success": true
};
});
}
}
The above code gives me following error cannot read property 'content-length' of undefined on line form.parse(request.payload, function (err, fields, files) {});
Please let me know If I am doing something wrong. If I replace the url in ajax request with anohter url that I have written in php then it works perfectly. which means that something is wrong with my hapijs/nodejs code.
There's a good post on how to handle file uploads in Hapi.js (written in version 16) https://scotch.io/bar-talk/handling-file-uploads-with-hapi-js
Since you are using payload.parse = true, I am not seeing a particular reason why you have to use multiparty. I have the following working code that would save files (of any type) uploaded from client into uploads directory on the server (Please do not use directly on production as no sanitation is done)
{
path: '/upload',
method: 'POST',
config: {
payload: {
output: 'stream',
parse: true,
allow: 'multipart/form-data'
},
validate: {
payload: {
files: Joi.array().single()
}
}
},
handler: function(request) {
const p = request.payload, files = p.files
if(files) {
console.log(`${files.length} files`)
files.forEach(async file => {
const filename= file.hapi.filename
console.log(`Saving ${filename} to ./uploads`)
const out = fs.createWriteStream(`./uploads/${filename}`)
await file.pipe(out)
})
}
return {result: 'ok'}
}
}
You can use the following curl command to test
curl http://localhost:8080/upload -F 'files=#/path/to/a/note.txt' -F 'files=#/path/to/test.png' -vvv
There are a few issues with your code. First in your $.ajax call, you have specified contentType twice, although it's not a syntax error but it's careless to code like that. Second the function's signature inside your .then() block is incorrect. You are mixing the idea of Promise and callback. I don't think the following line will be triggered
if(err) return sendError(err);
One last trivial thing, you said you are using Hapi 17 but based on the handler function's signature
handler: function (request, reply) {
...
Seems you are not totally onboard with Hapi17 as the new signature is
handler: function (request, h) {
And it's not just the rename of reply to h.
How do you properly delete a subdoc (a task in this case) with AJAX in Mongoose?
Everything seems to be working up until the ajax in the file that's loaded into the page. Or could the problem be in the controller? I have read that you can't perform a .remove on a child element and I'm unclear on how to handle a delete.
Here is the schema:
//new user model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
// Task schema
var taskSchema = mongoose.Schema({
clientEasyTask : { type: String },
clientHardTask : { type: String },
clientStupidTask : { type: String }
});
var userSchema = new mongoose.Schema({
email: { type: String, unique: true, lowercase: true },
password: String,
task : [taskSchema]
});
module.exports = mongoose.model('Task', taskSchema);
module.exports = mongoose.model('User', userSchema);
The JS loaded into the page:
// Delete
$(document).ready(function() {
console.log('called del function');
var $alert = $('.alert');
$alert.hide();
$alert.on('error', function(event, data){
$alert.html(data)
$alert.addClass('alert-danger');
$alert.show();
});
$alert.on('success', function(event, data) {
$alert.html(data);
$alert.addClass('alert-info');
$alert.show();
})
$('.task-delete').click(function(event) {
console.log('click event occurred');
$target = $(event.target)
$.ajax({
type: 'DELETE',
url: apiDeleteTask + $target.attr('data-task-id'),
success: function(response) {
$target.parent.children.id(id).remove();
$alert.trigger('success', 'Task was removed.');
},
error: function(error) {
$alert.trigger('error', error);
}
})
});
})
Routes, which matches the working update route:
var tasks = require('./controllers/tasks-controller'),
var User = require('./models/user');
var Task = require('./models/user');
module.exports = function (app, passport) {
// Delete Task
app.delete('/api/tasks/:id', tasks.del);
};
And the tasks-controller.js
var User = require('../models/user');
var Task = require('../models/user');
exports.del = function(req, res, next) {
return User.update({ 'task._id': req.params.id }, { $set: { 'task.$.clientEasyTask': req.body.clientEasyTask }},
(function(err, user) {
if(!user) {
res.statusCode = 404;
return res.send({ error: 'Not phound' });
}
if(!err) {
console.log("Updated Existing Task with ID: " + req.params.id + " to read: " + req.body.clientEasyTask ),
res.redirect('/dashboard');
} else {
res.statusCode = 500;
console.log('Internal error(%d): %s', res.statusCode, err.message);
return res.send({ error: 'Server error' });
}
})
);
};
And last but not least I'm getting this error, that gives the task_id string & line 0:
[Error] Failed to load resource: the server responded with a status of 404 (Not Found) (54c55ac0443873db1eb8c00c, line 0)
In order to remove an entire field from the child array (tasks) the solution is to use $unset. I was wanting to use $set to update the field with a null value, but this is exactly what $unset does.
Here is the line in question that now works:
return User.update({ 'task._id': req.params.id }, { $unset: { 'task.$.clientEasyTask': req.body.clientEasyTask }},
Read more about field operators here: http://docs.mongodb.org/manual/reference/operator/update-field/
$pull would work if you want to remove the array elements without leaving behind a null value, but you must have a specific, matching query. Read about $pull and other array update options here:
http://docs.mongodb.org/manual/reference/operator/update-array/
Also, if you are struggling with a problem I can't stress how important it is to read the documentation. I can guarantee you that everyone on here that is answering problems is doing this, or has learned from someone who does.
Do the work. You'll figure it out. Don't give up.