Not translating key on model define unique custom error - node.js

Problem
Using 18next.t function to translate key, is getting me the generic sequelize unique constraint error message instead of defined custom message
Environment
sequelize#5.22.4
i18next#21.3.3
Model definition candidate.js
...
module.exports = (sequelize, DataTypes) => {
const Candidate = sequelize.define('Candidate', {
status: {
type: DataTypes.ENUM,
values: [
...
],
},
docTin : {
...
unique: {
args: 'candidates_unique_doctin_company_unity',
get msg() { return i18next.t('invalid-candidate-unique-doc-tin') }
},
...
Result:
docTin must be unique
Expected:
{Custom error message located on lang.json}

The solution that i founded was prototyping the main create function on model definition, to allow use functions on unique msg properties
...
const orgCreate = Candidate.create;
Candidate.create = function(){
return orgCreate
.apply(this, arguments)
.catch(err => {
const uniqueErrorName = 'SequelizeUniqueConstraintError'
if (err.name === uniqueErrorName) {
err.errors = err.errors.map(e => ({
...e,
message: typeof e.message === 'function' ? e.message() : e.message
}))
}
throw err;
});
...
replacing
get msg() { return i18next.t('invalid-candidate-unique-doc-tin') }
for
msg: () => i18next.t('invalid-candidate-unique-doc-tin')

Related

How to test mongoose methods using sinon fakes?

I have the following arrangement of tests using sinon, mocha and chai:
type ModelObject = {
name: string;
model: typeof Categoria | typeof Articulo | typeof Usuario;
fakeMultiple: () => object[];
fakeOne: (id?: string) => object;
}
const models: ModelObject[] = [
{
name: 'categorias',
model: Categoria,
fakeMultiple: () => fakeMultiple({ creator: oneCategoria }),
fakeOne: oneCategoria
},
{
name: 'articulos',
model: Articulo,
fakeMultiple: () => fakeMultiple({ creator: oneArticulo }),
fakeOne: oneArticulo
},
{
name: 'usuarios',
model: Usuario,
fakeMultiple: () => fakeMultiple({ creator: oneUsuario }),
fakeOne: oneUsuario
}
];
const randomModel = models[Math.floor(Math.random() * models.length)];
describe(`v1/${randomModel.name}`, function () {
this.afterEach(function () {
sinon.restore();
});
context.only("When requesting information from an endpoint, this should take the Model of the requested endpoint and query the database for all the elements of that model", function () {
it.only(`Should return a list of elements of ${randomModel.name} model`, function (done) {
const fakes = randomModel.fakeMultiple();
const findFake = sinon.fake.resolves({ [randomModel.name]: fakes });
sinon.replace(randomModel.model, 'find', findFake);
chai.request(app)
.get(`/api/v1/${randomModel.name}`)
.end(
(err, res) => {
expect(res).to.have.status(200);
expect(res.body.data).to.be.an('object');
expect(res.body.data).to.have.property(randomModel.name);
expect(res.body.data[randomModel.name]).to.have.lengthOf(fakes.length);
expect(findFake.calledOnce).to.be.true;
done();
}
)
});
}}
I use this to test an endpoint that arbitrary returns information about a given model. In my controllers, I'm using a dynamic middleware to determine which model is going to be queried, for example, if the route consumed is "api/v1/categorias", it will query for Categorias model. If the route consumed is "api/v1/articulos", it will query for Articulos model, and so on.
To make the query, i use the following service:
import { Articulo } from '../models/articulo';
import { Usuario } from '../models/usuario';
import { Categoria } from '../models/categoria';
import logger from '../config/logging';
import { Model } from 'mongoose';
const determineModel = (model: string): Model<any> => {
switch (model) {
case 'articulos':
return Articulo;
case 'usuarios':
return Usuario;
case 'categorias':
return Categoria;
default:
throw new Error(`Model ${model} not found`);
}
};
export const getInformation = async (schema: string, page: number, limit: number) => {
try {
const model = determineModel(schema);
const data = await model.find().skip((page - 1) * limit).limit(limit);
const dataLength = await model.find().countDocuments();
return {
data,
total: dataLength,
};
} catch (err) {
logger.error(err);
console.log(err);
throw err;
}
};
The problem here lies when running my tests, it seems that is unable to run the .skip() and .limit() methods for my model.find()
error: model.find(...).skip is not a function
TypeError: model.find(...).skip is not a function
I think that I need to fake those methods, because when running the same test without skip and limit, it works as a charm. My problem lies in the fact that I don't know how to fake those, or to see if my guess is correct.
As a note, I have default params for the variables page and limit (1 and 15 respectively) so I'm not passing empty values to the methods.

How to make kuzzle-device-manager plugin API actions works?

I successfully installed and loaded kuzzle-device-manager in the backend file:
import { Backend } from 'kuzzle';
import { DeviceManagerPlugin } from 'kuzzle-device-manager';
const app = new Backend('playground');
console.log(app.config);
const deviceManager = new DeviceManagerPlugin();
const mappings = {
updatedAt: { type: 'date' },
payloadUuid: { type: 'keyword' },
value: { type: 'float' }
}
deviceManager.devices.registerMeasure('humidity', mappings)
app.plugin.use(deviceManager)
app.start()
.then(async () => {
// Interact with Kuzzle API to create a new index if it does not already exist
console.log(' started!');
})
.catch(console.error);
But when i try to use controllers from that plugin for example device-manager/device with create action i get an error output.
Here is my "client" code in js:
const { Kuzzle, WebSocket } = require("kuzzle-sdk")
const kuzzle = new Kuzzle(
new WebSocket('KUZZLE_IP')
)
kuzzle.on('networkError', error => {
console.error('Network Error: ', error);
})
const run = async () => {
try {
// Connects to the Kuzzle server
await kuzzle.connect();
// Creates an index
const result = await kuzzle.query({
index: "nyc-open-data",
controller: "device-manager/device",
action: "create",
body: {
model: "model-1234",
reference: "reference-1234"
}
}, {
queuable: false
})
console.log(result)
} catch (error) {
console.error(error.message);
} finally {
kuzzle.disconnect();
}
};
run();
And the result log:
API action "device-manager/device":"create" not found
Note: The nyc-open-data index exists and is empty.
We apologize for this mistake in the documentation, the device-manager/device:create method is not available because the plugin is using auto-provisioning until the v2.
You should send a payload to your decoder, the plugin will automatically provision the device if it does not exists https://docs.kuzzle.io/official-plugins/device-manager/1/guides/decoders/#receive-payloads

Why is the `message` for my Jest custom matcher not being displayed?

I've created a Jest custom matcher. It works (meaning, it passes/fails when it should), but I don't see the message anywhere in Jest's output.
What am I doing wrong? Do I have to do something to "enable" messages? Am I totally misunderstanding where the message is supposed to show up?
Environment: NestJS, Prisma
Execution command: jest --watch
Simplified code:
declare global {
namespace jest {
interface Matchers<R> {
toMatchHash(received: string, expected: string): R;
}
}
}
expect.extend({
toMatchJsonHash(received, expected) {
return {
pass: false,
message: () => `Why doesn't this work?!`,
};
},
});
expect(prisma.name.findMany).toHaveBeenCalledWith(expect.toMatchJsonHash('db0110285c148c77943f996a17cbaf27'));
Output:
● MyService › should pass a test using a custom matcher
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: toMatchJsonHash<db0110285c148c77943f996a17cbaf27>
Received: {<Big ol' object redacted for conciseness>}
Number of calls: 1
178 |
179 | // #ts-ignore
> 180 | expect(prisma.name.findMany).toHaveBeenCalledWith(expect.toMatchJsonHash('db0110285c148c77943f996a17cbaf27'));
| ^
181 | // expect(prisma.name.findMany).toHaveBeenCalledWith({
182 | // select: { type: true, name: true },
183 | // where: {
at Object.<anonymous> (my/my.service.spec.ts:180:32)
I'm expecting to see "Why doesn't this work?!" somewhere in the output, but I don't. What am I missing?
As suggested by #jonsharpe, the reason was that Jest was showing the message from the "outer" matcher, .toHaveBeenCalledWith().
To fix this, I found the source that defines the .toHaveBeenCalledWith() matcher and "merged" its code into my custom matcher.
This enabled my custom matcher to effectively "extend" the functionality of the .toHaveBeenCalledWith() matcher, including my own custom code and messages.
In case it helps someone, the code I ended up with for my specific use case was:
declare global {
namespace jest {
interface Matchers<R> {
toHaveBeenCalledWithObjectMatchingHash(expected: string): CustomMatcherResult;
}
}
}
expect.extend({toHaveBeenCalledWithObjectMatchingHash(received, expected) {
const isSpy = (received: any) =>
received != null &&
received.calls != null &&
typeof received.calls.all === 'function' &&
typeof received.calls.count === 'function';
const receivedIsSpy = isSpy(received);
const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
const calls = receivedIsSpy
? received.calls.all().map((x: any) => x.args)
: received.mock.calls;
if(calls.length === 0) {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the function was not called.`,
};
}
if(calls[0].length === 0) {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the function was called, but not with any arguments.`,
};
}
const md5Hash = crypto.createHash('md5');
const receivedHash = md5Hash.update(JSON.stringify(calls[0][0])).digest('hex');
const pass = receivedHash === expected;
if(pass) {
return {
pass: true,
message: () => `expected the function to not be called with an object that hashes to '${expected}'. Instead, the passed object hashes to the same value.`,
};
} else {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the passed object hashes to '${receivedHash}'.`,
};
}
}});

How can I convert from a firebase document to a custom class in Node JS

In node.js I'm getting the error below. Any ideas why?
Conversion of type 'Promise' to type 'Member[]' may be a mistake
because neither type sufficiently overlaps with the other. If this was
intentional, convert the expression to 'unknown' first. Type
'Promise' is missing the following properties from type
'Member[]': length, pop, push, concat, and 26 more.
export async function getFamilyMembers(tenantId: string, familyCode: string): Promise<Member[]> {
return db.collection(`tenants/${tenantId}/members`)
.where('familyCode', '==', familyCode)
.get()
.then(snaps => {
snaps.docs.forEach(doc => {
return { id: doc.id, ...doc.data()}
});
}) as Member[];
}
EDIT:
If I remove the types and change it to
export async function getFamilyMembers(tenantId: string, familyCode: string) {
return db.collection(`tenants/${tenantId}/members`)
.where('familyCode', '==', familyCode)
.get()
.then(snaps => {
snaps.docs.forEach(doc => {
return { id: doc.id, ...doc.data()}
});
});
}
I just have to deal with the problem later.
I get the error
Property 'length' does not exist on type 'void'.
const familyMembers: Member[] | void = await getFamilyMembers(tenantId, familyCode);
if (familyMembers === null) {
isVerified = false;
verificationFailMessage = `Sorry we can't find this code. Please check it is correct.`;
} else if (familyMembers.length === 0) {
I needed to add Promise in front of Member.
export async function getFamilyMembers(tenantId: string, familyCode: string): Promise<Member[]> {
return db.collection(`tenants/${tenantId}/members`)
.where('familyCode', '==', familyCode)
.get()
.then(snaps => {
snaps.docs.forEach(doc => {
return { id: doc.id, ...doc.data()}
});
}) as Promise<Member[]>;
}

Best way to access data in react

PROBLEM:
I have a MERN application that is has a model with a couple of other models in it. The problem that I figured out later is that it saves the _id of the object and not the actual object in the model when you do this
const checkoutHistory = new Schema({
book: { type: mongoose.Schema.Types.ObjectId, ref: 'books',required: true },
checkoutCopiesNum: {type: Number, required: true},
profChosen: { type: mongoose.Schema.Types.ObjectId, ref: 'prof', required: true },
dueDate: {type: String, required: true}
})
The book: part of the object when retreived will be an id some string like "DKKLDFJhdkghhe839kdd" whatever. This is fine because then I guess I can make an API call in the react app later to search for this book. Is this the correct way to do it though?
The other way that I thought of was in the actual endpoint that retrieves the data was to call the findByID functions and set that data. It didn't work though here is the code for that:
const checkoutHistoryMiddle = async (req, res, next) => {
try {
//get the body of the request
const body = req.body
//check for data
if(!body){
return res.status(400).json({
success: false,
error: 'no body given'
})
}
const history = new CheckoutHist(body)
console.log(history)
// await Book.findById({_id: history.book}, (err, book) => {
// history.book = book
// })
// await Prof.findById({_id: history.profChosen}, (err, prof) => history.profChosen = prof)
console.log(history)
history.save().then(() => next()).catch(error => {
return res.status(400).json({
success: false,
message: error,
msg: "checkout save failed"
})
})
} catch (error) {
res.status(400).json({
success: false,
message: error,
msg: "checkoutHist failed"
})
}
}
I commented out the part I was talking about because well, it didn't work. It still saved the id instead of the object. Which like I said is fine. I gave my other idea a go and decided to do the calls inside the react app.
So I first got the array of objects from the schema provided above like this:
const [bookHist, setBookHist] = useState()
useEffect( () => {
const getHistory = async () => {
api.getCheckoutHist().then(hist => {
setBookHist(hist.data.data.filter((data) => data.book === props.book_id))
})
}
getHistory()
}, [])
This will create an array of objects in bookHist that looks like this
[{_id: "DKJFDKJDKLFJSL", book: "LDKhgajgahgelkji8440skg", checkoutCopiesNum: 3, profChosen: "gjellkdh39gh39kal930alkdfj", dueDate: "11/11/11"}, {...}]
so the next step would be to take each item in the array and get the id to search the database with so api.findProfByID(bookHist[0].profChosen)
then I would need to update the state of bookHist somehow only that item without effect the other items in the array.
The questions I have are what is the best way to update one item in the array state?
How do I make so many api calls? how do I make sure that they are waited on so that the state actually changes once the calls complete?
Here are things I have tried so far:
useEffect(() => {
bookHist.map(async bHist => {
await Axios.get("http://localhost:8174/user/professor/" + bHist.profChosen).then(async prof => {
// console.log(prof)
// console.log(prof)
bHist.profChosen = prof.data.data
// setBookHist(prevStat => ({}))
// setBookHist(...bookHist, [bookHist.])
})
setBookHist(bHist)
})
}, [])
this didn't work I assume because it would not update the state because it is not waiting on the map to finish before it sets the state of bookHist
So then I searched on the internet and found a promisAll method in react like this:
useEffect(() => {
const change = async () => {
if(bookHist){
console.log("prof")
//get the prof data
// const galleries = []
await Promise.all(bookHist.map( (bHist, index) => {
return await Axios.get("http://localhost:8174/user/professor/" + bHist.profChosen);
})).then(someData => {
console.log(someData)
});
}
change()
}, [])
This also does not work for unknown reasons. It only works if it hot reloads and does not refresh. The logging actually logs something when it hot refreshes.
here is the entirety of the funcitional component:
import React, {useState, useEffect} from 'react'
import api from '../../api/index'
import Axios from 'axios'
export default function CheckoutBookHistroy(props){
const [bookHist, setBookHist] = useState()
const [histData, setHistData] = useState([{
book: {},
prof: {}
}])
useEffect( () => {
const getHistory = async () => {
api.getCheckoutHist().then(hist => {
setBookHist(hist.data.data.filter((data) => data.book === props.book_id))
})
}
getHistory()
}, [])
//i also tried this way but this resulted in an infinite loop
const [profChosen, setProfChosen] = useState()
const handleProfFind = async (id) => {
await Axios.get("http://localhost:8174/user/professor/" + id).then(prof => {
setProfChosen(prof.data.data)
})
}
return (
<div>
{
bookHist ?
bookHist.map(data => {
//need to present the prof data here for each data obj
return (
<div>Checked out {data.checkoutCopiesNum}</div>
)}) : <div>no data</div>
}
</div>
)
}
I really hope I can gain some insight into the correct way to do all of this. I must be either really close or awfully wrong. Thank you in advance!
just by looking at your code, i don't see too much issue, although your code is a bit convoluted.
some functions has no caller, ex. handleProfFind. One suggestion, if you want to do something, just do it, no need that many functions, ex.
// assume you only want to do it once after mounting
useEffect( () => {
if (!data) {
api.getCheckoutHist().then(hist => {
// you can set your data state here
// or you can get the id inside each item, and then call more APIs
// whatever you want to do, please finish it here
}
}
}, [])

Resources