Generic function to test response schema in Postman - javascript

I have had a major headache in recent days creating test scripts to validate more than 300 endpoints of an application we are working on. In the end I came up with a very practical solution that boils down to: a generic JSON validation function and copying and pasting the expected result into an object for testing. This script perform each field validation for a maximum of 3 levels deep inside the JSON.
pm.globals.set("validationHelper", function validationHelper(example) {
for (var field in example) {
if (typeof example[field] === "object") {
pm.test(`Field '${field}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(field);
});
for (var nested in example[field]) {
if (!Array.isArray(example[field][nested])) {
pm.test(`Nested field '${nested}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(nested);
});
} else {
pm.test(`Nested field '${nested}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(nested);
});
for (var index in example[field][nested]) {
if (typeof example[field][nested][index] === "object") {
if (!Array.isArray(example[field][nested][index])) {
for (var child in example[field][nested][index]) {
pm.test(`Child field '${child}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(child);
});
}
}
}
}
}
}
} else {
pm.test(`Field '${field}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(field);
});
}
}
return true
} + ';');
Using Postman, create a Pre-request Script in the collection that you will use to run the tests
Inside the request that you are going to test, paste the following code:
// Save the example response used to validate
// the body using a validation function
example = {
"detail": "Successfully logged out."
}
// This function loads the global helper function
// and starts using the example schema
eval(pm.globals.get("validationHelper"));
validationHelper(example);
The example object you have the save with the response that you are expecting.
Try to send the request and get all green
Postman documentation doesn't recommend to loop tests because of performance, but depending how much time you wanna save, this could be a good solution. :-)

After some research I found a more elegant solution for this same problem. ;-)
pm.globals.set("validationHelper", function validationHelper(example, keys = {}) {
for (var k in example) {
if (typeof example[k] == "object" && example[k] !== null) {
if (k.constructor === String) {
if (!k.match(/^-{0,1}\d+$/)) {
existProperty = false
for (var key in keys) {
(key === k) && (existProperty = true)
}
if (!existProperty) {
keys[k] = true
pm.test(`Child field '${k}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(k);
});
}
}
}
validationHelper(example[k], keys);
} else {
if (k.constructor === String) {
if (!k.match(/^-{0,1}\d+$/)) {
existProperty = false
for (var key in keys) {
(key === k) && (existProperty = true)
}
if (!existProperty) {
keys[k] = true
pm.test(`Child field '${k}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(k);
});
}
}
}
}
}
return true
} + ';');

Related

Simplify forEach in forEach React

I have a function where I have to return for each "subcontractor" its response for each selection criteria.
Subcontractor object contains a selectionCriteria object. selectionCriteria object contains an array of data for each selectionCriteria a user has responded to.
Each array item is an object, that contains files, id, request (object that contains info about selection criteria user is responding to), response (contains value of the response).
Here is an example of how a subcontractor looks:
This is the function I come up with, but it's quite complex:
const { subcontractors } = useLoaderData<typeof loader>();
const { t } = useTranslation();
const submittedSubcontractors = subcontractors.filter(
(s) => s.status === 'submitted'
);
const subcontractorsResponsesToSelectionCriteria: Array<ISubcontractor> = [];
let providedAnswersResponded: boolean | null = null;
let providedAnswersFiles: Array<IFile> | [] = [];
let providedAnswersRequiresFiles: boolean | null = null;
submittedSubcontractors.forEach((u) => {
u.selectionCriteria.forEach((c) => {
if (c.request.id === criteriaId) {
if (c.response && 'answer' in c.response) {
if (typeof c.response.answer === 'boolean') {
providedAnswersResponded = c.response.answer;
} else {
providedAnswersResponded = null;
}
} else {
providedAnswersResponded = null;
}
providedAnswersFiles = c.files;
providedAnswersRequiresFiles = c.request.are_files_required;
subcontractorsResponsesToSelectionCriteria.push(u as ISubcontractor);
}
});
});
How could I simplify this code by using .reduce() method, or maybe even better ideas?
You should start working on reducing the level of nesting in your if/else like so:
function getProvidedAnswersResponded(response: any) {
if (response && ('answer' in response) && (typeof response.answer === 'boolean')) {
return response.answer;
}
return null;
}
submittedSubcontractors.forEach(u => {
u.selectionCriteria.forEach(c => {
if (c.request.id !== criteriaId) {
return;
}
providedAnswersResponded = getProvidedAnswersResponded(c.response);
providedAnswersFiles = c.files;
providedAnswersRequiresFiles = c.request.are_files_required;
subcontractorsResponsesToSelectionCriteria.push(u);
});
});
The strategy followed was basically to invert the special cases (such as c.requet.id === criteriaId) and exit the function immediately.
Also, extracting the "provided answer responded" function seems atomic enough to move it to a separate block, giving it more verbosity about what that specific code block is doing.

replace lines between two lines in a template file in nodejs

I have a file with the following content:
function(doc) {
//pr reqs
var facet = true;
var store = true;
// Template start
var fields = {
}
// template end
noiseList = ["type", "objects", "value"]
const isNumeric = (num) => {
return !isNaN(num)
}
const emitIndex = () => {
if (doc.created_by_ref) {
Object.keys(fields).forEach(function(key) {
if (typeof fields[key] == 'object' && fields[key].length !== undefined) {
if (fields[key].length === 0) {
index(key, 'UNDEFINED', {
'store': store,
'facet': facet
});
} else {
fields[key].forEach(function(ele) {
index(key, ele.toString(), {
'store': store,
'facet': facet
});
})
}
} else {
index(key, fields[key].toString(), {
'store': store,
'facet': facet
});
}
})
}
}
Object.keys(doc).map(obj => {
if (typeof doc[obj] === 'object' && doc[obj] !== null) {
traverseObjectsInDoc(doc[obj], noiseCancelation(obj) ? "" : obj, doc.objects, false);
} else if (doc[obj] !== null && isValueType(obj) && !noiseCancelation(obj)) {
AddToFields(`${obj}`, doc[obj])
}
});
emitIndex();
}
As you I have two special sign there: template start and template end
what I am trying to achieve is to replace sth similar to
var fields = {
"test1": "test",
"test2": "test2"
}
instead of
var fields = {
}
in that file. And I should mention that this fields are generated in runtime so the content needs to be dynamic as well that is why I need this approach. All I can think of is to read the file
const searchAll1 = () => {
const contents = fs
.readFileSync("./lib/design_documents/searchAll", "utf8");
// find the template end and start replace the new fields some and return
};
and find the template end and start replace the new fields somehow and return. However I am not really sure if this is the best way?
What is the best way to do so?
Adjust the design of your function by adding a second parameter you can add the fields object dynamically whenever you call the function.
// your-script.js
module.exports = function(doc, fields) {
...
}
Then when you import and use the function, create a new object and pass it to your function and call it.
const yourFunction = require('./your-script.js');
let doc = someValue;
let fields = {
"test1": "test",
"test2": "test2"
}
yourFunction(doc, fields);

Angular Implementing multi search filter

I am using the angular advanced search box and I want to implement a custom filter in my angular page but I am having trouble figuring out how to handle a string of requirements in a query. Let's say I have several objects that follow the following format:
{
"displayName":"John",
"gender":"male",
"type":"customer",
"items": 3,
}
I would want to be able to search in plain english `Anyone who's name is John and is of type Customer". Here is my angular search code so far:
app.filter('infoFilter', function() {
return function(data, query) {
var output = [];
var index;
//loop over the original array
angular.forEach(data, function(row, index) {
angular.forEach(query, function(input, value) {
if(input) {
if(angular.isNumber(row[value]) && row[value] == input) {
output.push(row);
} else if(!angular.isNumber(row[value]) && row[value].toLowerCase().indexOf(input.toLowerCase()) > -1) {
output.push(row);
}
}
});
});
if(query) {
return data;
} else {
return output;
}
}
});
The query comes in as an object that looks like this:
{
"displayName":"John"
}
This works perfectly fine for 1 search parameter. So if I searched for John my table would update to show all entries with the name of john. However, this wouldn't really work for multi search parameters. So if the query looked like this:
{
"displayName":"John",
"gender":"Female"
}
I need to apply all the parameters at once before i do output.push(row). How exactly would I go about doing this?
If I understand you correctly you want to filter the rows where all query parameters apply (AND). I modified your code slightly to achieve this behavior.
app.filter('infoFilter', function() {
return function(data, query) {
var output = [];
var index;
//loop over the original array
angular.forEach(data, function(row, index) {
var pushRow = true;
angular.forEach(query, function(input, value) {
if(input) {
if(angular.isNumber(row[value]) && row[value] == input) {
return;
} else if(!angular.isNumber(row[value]) && row[value].toLowerCase().indexOf(input.toLowerCase()) > -1) {
return;
}
}
pushRow = false;
});
if (pushRow) {
output.push(row);
}
});
// This bit also seems to be the wrong way around in your code.
if(!query) {
return data;
} else {
return output;
}
}
});
Edit:
Here is also an optimized version of the same filter using javascripts built in array functions.
app.filter('infoFilter', function() {
return function(data, query) {
if(!query || !data) {
return data;
}
return data.filter(function(row) {
return Object.keys(query).every(function(key) {
var rowValue = row[key];
var queryValue = query[key];
return (angular.isNumber(rowValue) && rowValue == input) ||
(angular.isString(rowValue) && rowValue.toLowerCase().indexOf(queryValue.toLowerCase()) > -1);
});
});
};
});

make async.waterfall start with argument from another function

I have bumped into a problem that I can't seem to solve. This is for a steam trading bot and it works well except for when two people trades with it at the same time because class_id and other_id are global variables and they will change in the middle of a trade if more than one is using it.
I tried defining the variables inside the last if statement but then get_class_id did not find the variables. Is there any way the async function can take item.classid and convert_id_64(offer.accountid_other) directly without defining them as variables? I appreciate any help.
var class_id
var other_id
function accept_all_trades(offers_recieved) {
offers_recieved.forEach( function(offer) {
if (offer.trade_offer_state == 1) {
if (typeof offer.items_to_give === "accept") {
offers.acceptOffer({tradeOfferId: offer.tradeofferid}, function(error, response) {
console.log('accepterat offer');
offer.items_to_receive.forEach(function(item) {
if (item.appid === '420') {
class_id = item.classid;
other_id = convert_id_64(offer.accountid_other);
console.log(class_id);
async.waterfall([get_class_id, get_stack, get_name, get_save], add_names);
}
});
});
}
}
});
}
function get_class_id(callback) {
var test = class_id
callback(null, test)
}
Update
I've changed the code to what ben suggested but still when I call get_class_id and try to print the id it is just a blank row in the console, any Ideas?
function get_class_id(callback) {
console.log(class_id);
var test = class_id;
callback(null, test)
}
The problem here is not aysnc.waterfall(). It's async calls (offers.acceptOffer(), get_class_id, get_stack, get_name, get_save, add_names) inside regular javascript forEach(). You need control-flow loops that can control the flow of those async calls. Here is the revised code using async.each():
function accept_all_trades(offers_recieved) {
async.each(offers_recieved, function(offer, eachCb) {
if (offer.trade_offer_state !== 1 || typeof offer.items_to_give !== "accept") {
return eachCb(null);
}
offers.acceptOffer({tradeOfferId: offer.tradeofferid}, function(error, response) {
console.log('accepterat offer');
async.each(offer.items_to_receive, function(item, eachCb) {
var class_id;
var other_id;
if (item.appid !== '420') {
return eachCb(null);
}
class_id = item.classid;
other_id = convert_id_64(offer.accountid_other);
console.log(class_id);
async.waterfall([
function(waterfallCb) {
var test = class_id;
console.log(class_id);
waterfallCb(null, test);
},
get_stack,
get_name,
get_save,
add_names
], eachCb);
}, function(err) {
console.log(err);
eachCb(null);
});
});
});
}

AngularJs localStorage delete element in loop

I do not know how can i delete element in localStorage loop
In save method i add element and check for it duplicate
explain please how can i delete element using for example only id or all values
My Factory
.factory('SaveDocuments', function() {
var documents = [];
save: function (id, name, link) {
if(documents.filter(function(a){return a.id==id}).length)
{ alert('conflict!'); }
else {
// add to it,
documents.push({id: id, name: name, link: link});
// then put it back.
localStorage.setItem('document', JSON.stringify(documents));
}
},
del: function(id, name, link) {
if(documents.filter(function(a){return a.id==id}).length) {
for (i = 0; i < localStorage.length; i++){
key = localStorage.key(i);
value = localStorage.getItem(key);
localStorage.removeItem(value);
console.log(value);
break;
}
}
else {
alert('conflict!');
}
}
}
MyController
.controller('PageSearchCtrl', function($scope, ConstSearch, SaveDocuments) {
$scope.saveDocument = function() {
//Create new project
$scope.document = [{"id": 1, "name": "new1", "link": "#/const"}];
SaveDocuments.save($scope.document[0].id,$scope.document[0].name,$scope.document[0].link);
};
$scope.deleteDocument = function () {
$scope.document = [{"id": 1, "name": "new1", "link": "#/const"}];
//Create new project
SaveDocuments.del($scope.document[0].id,$scope.document[0].name,$scope.document[0].link);
}
I recommend changing your service to something like the following:
.factory('SaveDocuments', function () {
var lsKey = 'document', // the key to store the docs in local storage under
documents = JSON.parse(localStorage.getItem(lsKey) || '[]'); // initialise from localStorage
function saveToLocalStorage() {
localStorage.setItem(lsKey, JSON.stringify(documents));
}
return {
save: function (id, name, link) {
if (documents.filter(function (a) {
return a.id == id;
}).length) {
alert('conflict!');
} else {
// add to it,
documents.push({
id: id,
name: name,
link: link
});
saveToLocalStorage();
}
},
del: function (id, name, link) {
// clear all if del() is called with no arguments or null for all args
if (!id && !name && !link) {
documents = [];
saveToLocalStorage();
return;
}
var initialLength = documents.length;
documents = documents.filter(function (doc) {
return (!id || doc.id !== id) && (!name || doc.name !== name) && (!link || doc.link !== link);
});
// if nothing was removed, show error
if (documents.length === initialLength) {
alert('conflict!');
} else {
saveToLocalStorage();
}
}
};
});
Note that I correctly initialised it from the local storage state when the application starts (so when you reload the page the data is there correctly), used a variable to hold the only key you use to store the data in local storage (to keep the code DRY), and fixed your del() method so it keeps ones which don't match the deletion criteria or deletes everything if no arguments passed in, then just overwrites the value in local storage with the updated state.
NB: You should test this, I did not do any testing to see if this works.

Categories