I'm trying to unit test the query function. When I debug with a breakpoint at the unit test below, I see that this.datasets = Object with the dataset info in my unit test. However, when I step into the query function, data is undefined. Why is it undefined and how do I set it to the dataset in my unit test?
QueryController.ts:
public query(query: QueryRequest): QueryResponse {
Log.trace('QueryController::query( ' + JSON.stringify(query) + ' )');
let data: any = this.datasets["courses"];
// do stuff
}
Unit test:
it("Valid query", function () {
let query: QueryRequest = {
// valid query code here
};
let dataset: Datasets = {
"234": {
"courses_dept": "math",
"courses_id": "327",
"courses_avg": 86.17,
"courses_instructor": "spears, britney",
"courses_title": "opera appreciation",
"courses_pass": 23,
"courses_fail": 10,
"courses_audit": 10
},
};
let controller = new QueryController(dataset);
let ret = controller.query(query);
//check stuff
});
Edit: I fixed the problem. query() was calling this.datasets["courses"]. I needed to add a "courses" key to my dataset in the unit test.
Related
I am trying to stub the following code
async function logUpdate(client) {
const results = await client.query(query.toParam());
const { count, timestamp } = results.rows[0];
await db.updateDatasourceLogs(destdb, DB.src, TABLES.src, timestamp, count);
}
This is the following code i am using to stub the above code
fakeClient = {
query: sinon.stub().resolves(fakeRows),
};
const rowData = {
count: 1,
timestamp: ''
};
fakeRows = {
rows: sinon.stub().returns([rowData]),
};
fakeSequel = {
useFlavour: sinon.stub().returns(toParam: () => false,),
};
I am getting an error for destructuring
TypeError: Cannot destructure property count of 'undefined' or 'null'.
at line
const { count, timestamp } = results.rows[0];
how to stub the above line?
If we look at the body of your logUpdate function, we see it begins with the following two lines:
const results = await client.query(query.toParam());
const { count, timestamp } = results.rows[0];
This code says:
Await the Promise returned by the call to client.query and assign
it to a variable called results.
results is an Object with a property called rows which is an
Array whose 0th element should be an Object with count and
timestamp properties - which we destructure into local variables.
This implies that the value of results looks like:
{
"rows": [
{
"count": 1
"timestamp": "0"
}
]
}
However, in our stubbing code, we have the following:
fakeRows = {
rows: sinon.stub().returns([rowData]),
};
Which says that fakeRows is an Object with a rows property whose value is a function that returns [rowData].
If we were to implement this Object without sinon, it would look like:
{
"rows": function () {
return [
{
"count": 1
"timestamp": "0"
}
];
}
}
Notice the difference between the structure that logUpdate is expecting and what fakeRows actually provides. Specifically, logUpdate expects results to have a rows Array, but, with our stubbed code, it has a rows Function!
We can fix this simply by having fakeRows reference our rowData directly:
const fakeRows = {
rows: [rowData]
};
I'm trying to make an AWS Lambda interact with a DynamoDB table but I can't seem to generate correctly the scan() query:
In my scenario we have 2 tables, one contains the teachers and one contains their lectures. Each teacher has 1+ subject to teach and each lesson is referred to a single subject of the teacher.
I'd like to obtain, from the lectures table, every lecture of a specific teacher (given its identificator).
So far I've tried this:
exports.handler = async(event, context, callback) => {
var courses_ids = "";
event.courses.forEach(function (course) {
courses_ids+=''+course.id+ ', ';
});
courses_ids = courses_ids.substring(0, courses_ids.length -2);
console.log("Courses IDs: " + courses_ids); // -> 1, 2, 3
await retrieveLecturesOfTeacher(courses_ids).then(data => {
console.log("Resulting data: "+JSON.stringify(data.Item))
// TODO
});
};
function retrieveLecturesOfTeacher(courses_ids) {
var filter_expression = 'course_id IN ( '+courses_ids+' ) ';
console.log("Filter Expression: "+filter_expression);
const params = {
TableName: 'lectures',
FilterExpression: 'course_id IN ( :courses_ids )' ,
ExpressionAttributeValues: {
':courses_ids' : courses_ids
}
}
console.log("Params: "+JSON.stringify(params))
return ddb.scan(params).promise();
}
But no lecture is found for courses 1,2 and 3 (despite their existence in the database).
Function Logs:
INFO Params: {"TableName":"lezioni","FilterExpression":"id_corso IN ( :courses_ids )","ExpressionAttributeValues":{":courses_ids":"undefined, undefined"}}
INFO Resulting data: null
However replacing the parameters as follows solves the problem:
const params = {
TableName: 'lectures',
FilterExpression: 'course_id IN ( :course_1, :course_2, :course_3) ',
ExpressionAttributeValues: {
':course_1' : 1,
':course_2' : 2,
':course_3' : 3
}
}
I already checked this and this questions but my problem seems to be related to the data type as the course_id in the lecture table is a number, while my courses_ids variable is a string. Is there a solution (also considering that a teacher can have an arbitrary number of exams)?
As it's possible to see from the question, the main problem seems to be the type difference, in particular changing the test as follows will result into a failure (as it will produce Resulting data: undefined):
const params = {
TableName: 'lezioni',
FilterExpression: 'id_corso IN ( :course_1, :course_2, :course_3) ',
ExpressionAttributeValues: {
':course_1' : "1",
':course_2' : "2",
':course_3' : "3"
}
}
Thus, as it is not possible to generate a string that correctly retrieves the lectures I had to find a workarournd to preserve the data type: in particular dynamically creating the FilterExpression string and the ExpressionAttributeValues maps. I hereby report the corrected code.
exports.handler = async(event, context, callback) => {
var lectures_map = {};
var i = 0;
var query_string = "";
event.corsi.forEach(function (course) {
lectures_map[":value_"+i] = course.course_id;
query_string += ":value_"+i+", ";
i+=1;
});
query_string = query_string.substring(0, query_string.length -2);
await retrieveLecturesOfTeacher(query_string, lectures_map).then(data => {
event.lectures = data;
callback(null, event);
});
};
function retrieveLecturesOfTeacher(query_string, attribute_values) {
var filterExpression = 'course_id IN ( '+query_string+' ) '
const params = {
TableName: 'lectures',
FilterExpression: filterExpression,
ExpressionAttributeValues: attribute_values
}
console.log("Params: "+JSON.stringify(params))
return ddb.scan(params).promise();
}
Here is the scan parameters
Function Logs:
INFO Params: {"TableName":"lezioni","FilterExpression":"id_corso IN ( :value_0, :value_1 ) ","ExpressionAttributeValues":{":value_0":1,":value_1":2}}
My JSON is like:
{
"boundaries": [
{
"boundary": {
"boundaryId": "45083021141",
"boundaryType": "USA_POSTCODE",
"boundaryRef": "B1"
}
}
],
"themes": [
{
"TheftCrimeTheme": {
"boundaryRef": "B1",
"individualValueVariable": [
{
"name": "2013 Theft Crime",
"description": "Theft Crime for 2013",
"count": 2080
}
]
}
},
{
"BurglaryCrimeTheme": {
"boundaryRef": "B1",
"individualValueVariable": [
{
"name": "2013 Burglary Crime",
"description": "Burglary Crime for 2013",
"count": 302
}
]
}
}
]
}
I want to get value of count to display in graph. As you can see in the above json, inside themes there are two keys i.e TheftCrimeTheme and BurglaryCrimeTheme. I want to get value of count in everycrimetheme. For this I did the below code:
$http.get("http://152.144.218.70:8080/USACrime/api/crimeAPI?city="+$scope.strCity+"&crimeType="+$scope.type1+"&years="+$scope.type+"&month="+$scope.type2).success(function (result) {
for(var i=0;i<result.themes.length;i++){
var crime={};
console.log("did",result.themes[i]);
var test2 = result.themes[i];
console.log("test2",test2);
var test = test2[Object.keys(test2)];
console.log("test",test);
crime.name = Object.keys(result.themes[i]);
console.log("CrimeName",crime.name);
crime.data = [];
var test1 = test.individualValueVariable[0].count;
console.log("test1",test1);
crime.data.push(test1);
crime_data.push(crime);
}
});
My agenda is to plot graph showing count of every year.To achieve this firstly I have to get multiple keys like TheftCrimeTheme, BurglaryCrimeTheme etc. Then after that i can access value of count in individualValueVariable.
when I used Object.keys() method , I got an error "undefined" when i console values of nameR. Please suggest me how should i do this?
This function recieves info (as the whole json), and theme as the theme which you want to get the count (ie.: "BurglaryCrimeTheme").
getThemeCrimesCount = (info, theme) => {
const record = info.themes.find(obj => Object.keys(obj)[0] == theme)[theme];
return record.individualValueVariable.reduce((a, b) => a += b.count, 0);
}
getThemeCrimesCount(info, "BurglaryCrimeTheme"); // 302
getThemeCrimesCount(info, "TheftCrimeTheme"); // 2080
Formatting this to separate the elements for clarity.
// Builds and returns URL with query string attached.
const buildURL = (uri, params) => {
let queryParams = Object.keys(params).map(function(k) {
return encodeURIComponent(k) + '=' + encodeURIComponent(params[k])
}).join('&');
return uri + '?' + queryParams;
};
// Parses returned JSON object.
const parseStatistics = (result) => {
// My assumption here is that you are receiving a JSON string, which
// would need to be parsed into an object before being used.
let result = JSON.parse(result);
// Set base object
let namR = {};
// Iterate through themes array.
result.themes.forEach(function(element) {
// Find the object name for current theme.
let type = Object.keys(element)[0];
// Find count for current theme.
let count = element[type].individualValueVariable.count;
// Set count.
namR[type] = count;
});
// Log object
console.log(namR);
};
// Set up url info.
let params = {
city: $scope.strCity,
crimeType: $scope.type1,
years: $scope.type,
month: $scope.type2
};
let baseURL = "http://152.144.218.70:8080/USACrime/api/crimeAPI";
// Execute request.
$http.get(buildURL(baseURL, params)).success(parseStatistics(response));
I have a MongoDB aggregate framework that uses a startDate, endDate and intervalValue to query my database. However these variables are hardcoded. I would like to use a datepicker to send startDate, endDate and intervalValuefrom a webpage.
I initially thought I should split the JS file. One handling the request from the webpage and the other handling the GET function. Unfortunately I have reached a dead-end. I'm unable to use variables declared in sandbox.js in test.js.
Also I'm a bit confuse on how to structure my JS files to input the date and display the output.
sandbox.js
function myTime() {
var startValue = document.getElementById("startTime").value;
var endValue = document.getElementById("endTime").value;
var intervalValue = document.getElementById("interval").value;
var startDate = new Date("1/1/2015 " + startValue);
var endDate = new Date("1/1/2015 " + endValue);
var offset = intervalValue * 1000 * 60;
}
test.js
{ $match : { datetime : { $gt : startDate, $lt : endDate } } },
{
$group: {
_id:{
"$add": [
{ "$subtract": [
{ "$subtract": [ "$datetime", new Date(0) ] },
{ "$mod": [
{ "$subtract": ["$datetime" , new Date(0) ] },
offset]}] },
new Date(0)
]},
Humidity: {$avg: "$humidity"},
Temperature: {$avg: "$temperature"}
},
},
{ $project : { _id : 1 , Humidity : 1, Temperature: 1 } },
// { $limit : 10 },
{ $sort : {"_id":1, "Humidity":1, "Temperature": 1}}
I will be glad if I can get some help. Thanks
Firstly, you need to make sure that both files are included in the HTML page that you are loading (i.e. your form).
Next, I see that your test.js file is simply a JSON object.
I would recommend changing this so that you return a JSON object via a function call rather. This will allow you to pass through your gathered inputs into the function to return your required JSON object to send to Mongo.
I would recommend giving the following changes a try:
sandbox.js changes
function myTime() {
var result = {}
result.startValue = document.getElementById("startTime").value;
result.endValue = document.getElementById("endTime").value;
result.intervalValue = document.getElementById("interval").value;
result.startDate = new Date("1/1/2015 " + startValue);
result.endDate = new Date("1/1/2015 " + endValue);
result.offset = intervalValue * 1000 * 60;
return result;
}
This will allow you get get all the variables into a result to pass through to the test.js file
test.js changes
function getMongoObject(values) {
return { "$match" : { "datetime" : { "$gt" : values.startDate, "$lt" : values.endDate } } },
{
"$group": {
"_id":{
"$add": [
{ "$subtract": [
{ "$subtract": [ "$datetime", new Date(0) ] },
{ "$mod": [
{ "$subtract": ["$datetime" , new Date(0) ] },
values.offset]}] },
new Date(0)
]},
"Humidity": {$avg: "$humidity"},
"Temperature": {$avg: "$temperature"}
},
},
{ "$project" : { "_id" : 1 , "Humidity" : 1, "Temperature": 1 } },
// { "$limit" : 10 },
{ "$sort" : {"_id":1, "Humidity":1, "Temperature": 1}}
}
Once you've made those changes to the test.js file, you can execute the following where you want to get the JSON object to pass to Mongo.
var mongoValues = getTime();
var mongoObject = getMongoObject(mongoValues);
Now you can use mongoObject to send to the DB.
UPDATE:
Approach 2:
If you do not want to send the variables through to the test.js file you will need to make your variables in the sandbox.js file global. Currently they are "private" (scoped only to the function)
Try these changes to sandbox.js
var startValue, endValue, intervalValue, startDate, endDate, offset;
function myTime() {
startValue = document.getElementById("startTime").value;
endValue = document.getElementById("endTime").value;
intervalValue = document.getElementById("interval").value;
startDate = new Date("1/1/2015 " + startValue);
endDate = new Date("1/1/2015 " + endValue);
offset = intervalValue * 1000 * 60;
}
In my opinion, option 1 is still better because it allows you to control what the variables are that you are binding to your JSON object that you will be passing to Mongo.
With this method, you could accidentally get different results in your JSON object due to the variables not being set at the time test.js is invoked or due to the variables being accidentally overridden.
UPDATE 3
In order to access the startDate and other variables in the test.js file you will need to make the JSON object a variable.
I have made a github repo for a test to show you want I mean, check it out here:
https://github.com/NewteqDeveloper/so-q-41462690
From this example you will note that the alert displays 2. (startdate + 1).
For more information check this stackoverflow page: Can I access variables from another file?.
An important note to make is that you need to include the sandbox.js file before the test.js file in the html page.
I'm were had troble to fetching plain objects from database using restangular and i solved it by using restangular plain() method on fetched data. but now i'm having a trouble on assigning that data to outer scope variable. demo code-
i have load all function -
function loadAll() {
var feelings = [];
Restangular.all('feelings').getList().then(function(data) {
angular.forEach(data.plain(), function(feeling) {
feelings.push(feeling);
})
})
console.log(feelings) // shows empty array :( , how to sole this issue
//----------------------------------------
// i wants my data like this to be returned
var feelings = [{
feeling_name: 'best',
view_count: 50
}, {
feeling_name: 'funny',
view_count: 150
}, {
feeling_name: 'angry',
view_count: 40
}, {
feeling_name: 'loved',
view_count: 42
}]
//-----------------------------------------------
return feelings.map(function(feeling) {
feeling.value = feeling.feeling_name.toLowerCase();
return feeling;
});
}