Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ var SuiteData = require("./suiteData.js");
var Suite = require("./suite");
var ResultsManager = require("./resultsManager");
var config = require("./config.json");

var restify = require("restify");

const applicationinsights = require("applicationinsights");
Expand Down
75 changes: 57 additions & 18 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ async function test(context, testData) {
}
}
else {
reason = getTestTitle(testData) + ": " + err.message;
reason = getTestTitle(testData) + ": " + err.message;
}
return new Result(false, reason, 500);
}
Expand Down Expand Up @@ -79,7 +79,7 @@ function createConversationSteps(testData) {
}

function isUserMessage(testData, message) {
return (testData && testData.userId) ? (message.from.id == testData.userId) : (message.recipient ? (message.recipient.role == "bot") : (message.from.role != "bot"));
return (testData && testData.userId) ? (message.from.id == testData.userId) : (message.recipient ? (message.recipient.role == "bot") : (message.from.role != "bot"));
}

function conversationStep(message) {
Expand All @@ -93,7 +93,7 @@ function testConversation(context, testUserId, conversationSteps, conversationId
context.log("conversationSteps: " + utils.stringify(conversationSteps));
context.log("conversationId: " + conversationId);
context.log("defaultTimeout: " + defaultTimeout);
return new Promise(function(resolve, reject) {
return new Promise(function (resolve, reject) {
var index = 0;
function nextStep() {
if (index < conversationSteps.length) {
Expand All @@ -105,7 +105,7 @@ function testConversation(context, testUserId, conversationSteps, conversationId
}
else {
context.log("testConversation end");
resolve({count: index});
resolve({ count: index });
}
}
return nextStep();
Expand All @@ -128,16 +128,16 @@ function testStep(context, conversationId, userMessage, expectedReplies, timeout
context.log("expectedReplies: " + utils.stringify(expectedReplies));
context.log("timeoutMilliseconds: " + timeoutMilliseconds);
return directline.sendMessage(conversationId, userMessage)
.then(function(response) {
.then(function (response) {
var nMessages = expectedReplies.hasOwnProperty("length") ? expectedReplies.length : 1;
var bUserMessageIncluded = response != null;
return directline.pollMessages(conversationId, nMessages, bUserMessageIncluded, timeoutMilliseconds);
})
.then(function(messages) {
.then(function (messages) {
return compareMessages(context, userMessage, expectedReplies, messages);
})
.catch(function(err) {
var message = `User message '${userMessage.text}' response failed - ${err.message}`;
.catch(function (err) {
var message = `User message '${userMessage.text}' response failed - ${err.message}`;
if (err.hasOwnProperty("details")) {
err.details.message = message;
}
Expand All @@ -148,14 +148,47 @@ function testStep(context, conversationId, userMessage, expectedReplies, timeout
});
}

// Replacement method for chai's deep.equal
function deepEqual(expected, actual) {
// Test using regex for attributes starting with regex keyword
if ((typeof expected === "string" && expected.startsWith("regex:"))) {
const regex = new RegExp(expected.replace("regex:", "").trim());
if (!regex.test(actual))
throw "Actual value doesn't match provided regex, Regex: " + regex.toString() + ", Actual: " + JSON.stringify(actual);
else return true;
}
// Regular test for other values
else if (expected === actual)
return true;
else if ((typeof expected === "object" && expected !== null) && (typeof actual === "object" && actual !== null)) {
for (var prop in expected) {
Copy link
Contributor

@guy-microsoft guy-microsoft Sep 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also check that number of attributes to ensure deep equality.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will conflict with what we did in the transformation script, kindly consult with Amir on how/when we should use this deepEqual method and get back to me.

if (actual.hasOwnProperty(prop)) {
try {
deepEqual(expected[prop], actual[prop]);
} catch (error) {
if (error === "Attributes mismatch")
throw "Cards are not equal, Error in attribute '" + prop + "', expectedValue: " + JSON.stringify(expected[prop]) + ", actualValue: " + actual[prop];
throw error;
}
} else {
throw "Actual card is missing '" + prop + "' property";
}
}
return true;
}
else {
throw "Attributes mismatch";
}
}

function compareMessages(context, userMessage, expectedReplies, actualMessages) {
context.log("compareMessages started");
context.log("actualMessages: " + utils.stringify(actualMessages));
// Filter out messages from the (test) user, leaving only bot replies
var botReplies = _.reject(actualMessages,
function(message) {
return message.from.id == userMessage.from.id;
});
var botReplies = _.reject(actualMessages,
function (message) {
return message.from.id == userMessage.from.id;
});

expect(botReplies, `reply to user message '${userMessage.text}'`).to.have.lengthOf(expectedReplies.length);

Expand All @@ -165,16 +198,22 @@ function compareMessages(context, userMessage, expectedReplies, actualMessages)
var botReply = botReplies[i];

if (botReply.hasOwnProperty("text")) {
var expr = 'expect(botReply.text, "user message number ' + (i+1) + ' ").' + assert + '(expectedReply.text)';
eval(expr);
// Test using regex for text starting with regex keyword
if (expectedReply.text.startsWith("regex:")) {
const regex = new RegExp(expectedReply.text.replace("regex:", "").trim());
expect(regex.test(botReply.text), "Regex: " + regex.toString() + " doesn't match: " + botReply.text).to.be.true;
} else {
var expr = 'expect(botReply.text, "user message number ' + (i + 1) + ' ").' + assert + '(expectedReply.text)';
eval(expr);
}
}
if (botReply.hasOwnProperty("attachments")) {
try {
expect(botReply.attachments,`attachments of reply number ${i+1} to user message '${userMessage.text}'`).to.deep.equal(expectedReply.attachments);
expect(deepEqual(expectedReply.attachments, botReply.attachments)).to.be.true;
}
catch (err) {
var exception = new Error(err.message);
exception.details = {message: err.message, expected: err.expected, actual: err.actual, diff: diff(err.expected, err.actual)};
var exception = new Error(err);
exception.details = { message: err, expected: expectedReply.attachments, actual: botReply.attachments, diff: diff(expectedReply.attachments, botReply.attachments) };
throw exception;
}
}
Expand All @@ -183,7 +222,7 @@ function compareMessages(context, userMessage, expectedReplies, actualMessages)
}

function getTestTitle(testData) {
return `Test ${testData.name? `'${testData.name}'` : `#${testData.index || 0}`}`;
return `Test ${testData.name ? `'${testData.name}'` : `#${testData.index || 0}`}`;
}

module.exports = Test;
66 changes: 25 additions & 41 deletions transcriptTransformationScript.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@
Bot Framework Emulator, and creates a new transformed transcript file named "'"transfromed.transcript"
in the script directory.
Transformation is done by applying all defined handlers on each entry of the transcript object.
**/
**/

const fs = require('fs');

/** Here we can add all the handlers we need **/
// Handler 1
function removeSchemaAttribute(currEntry) {
if (currEntry['type'] === 'message') {
if (currEntry.hasOwnProperty("attachments") && currEntry["attachments"][0].hasOwnProperty("content") && currEntry["attachments"][0]["content"].hasOwnProperty("$schema")) {
delete currEntry["attachments"][0]["content"]["$schema"];
function removeUnnecessaryAttributes(attachment, attributesToRemove) {
for (attr in attachment) {
if (typeof attachment[attr] === 'object') {
removeUnnecessaryAttributes(attachment[attr], attributesToRemove);
} else if (attributesToRemove.includes(attr)) {
delete attachment[attr];
}
}
}
Expand All @@ -42,6 +44,7 @@ function convertColumnsWidthToString(items) {
}
}
}

// Handler 3
function convertNumbersToString(currEntry) {
if (currEntry['type'] === 'message') {
Expand All @@ -65,70 +68,51 @@ function convertNumbersToString(currEntry) {
}
}

function convertAttachmentAttributesToCamelCase(attachment, unchangedAttributes) {
for (attr in attachment) {
if (typeof attachment[attr] === 'object') {
convertAttachmentAttributesToCamelCase(attachment[attr], unchangedAttributes);
} else if (typeof attachment[attr] === 'string' && !unchangedAttributes.includes(attr)) {
attachment[attr] = attachment[attr].charAt(0).toLowerCase() + attachment[attr].slice(1, attachment[attr].length);
}
}
}

// Handler 4
function convertColumnAttributesToCamelCase(currEntry) {
function editAttachmentsAttributes(currEntry) {
const attributesToRemove = ['horizontalAlignment', 'style', 'version', '$schema'];
const unchangedAttributes = ['placeholder', 'text', 'type', 'title', 'value', 'id', 'label', 'FoodChoice'];
if (currEntry['type'] === 'message') {
if (currEntry.hasOwnProperty("attachments")) {
const attachments = currEntry["attachments"];
attachments.forEach(attachment => {
if (attachment.hasOwnProperty("content")) {
const content = attachment["content"];
if (content.hasOwnProperty("body")) {
const contentBody = content["body"][0];
if (contentBody.hasOwnProperty("items")) {
const bodyItems = contentBody["items"];
bodyItems.forEach(bodyItem => {
if (bodyItem.hasOwnProperty("columns")) {
const columns = bodyItem["columns"];
columns.forEach(column => {
if (column.hasOwnProperty("items")) {
const colItems = column["items"];
const attributesToEdit = ["size", "weight", "color", "horizontalAlignment", "spacing"];
colItems.forEach(colItem => {
attributesToEdit.forEach(attr => {
if (colItem.hasOwnProperty(attr)) {
colItem[attr] = colItem[attr].charAt(0).toLowerCase() + colItem[attr].slice(1, colItem[attr].length);
}
});
});
}
});
}
});
}
}
}
removeUnnecessaryAttributes(attachment, attributesToRemove);
convertAttachmentAttributesToCamelCase(attachment, unchangedAttributes);
});
}
}
}


/** This is the main function - It iterates over all entries of the transcript, and applies all handlers on each entry **/
function main(path) {
console.log("Started");
let contentBuffer;
try {
contentBuffer = fs.readFileSync(path);
}
catch (e) {
} catch (e) {
console.log("Cannot open file", e.path);
return;
}
let jsonTranscript = JSON.parse(contentBuffer);
for (let i = 0; i < jsonTranscript.length; i++) {
let currEntry = jsonTranscript[i];
// Here we call to all the handlers we defined
removeSchemaAttribute(currEntry);
addSeparationAttribute(currEntry);
convertNumbersToString(currEntry);
convertColumnAttributesToCamelCase(currEntry);

editAttachmentsAttributes(currEntry);
}
try {
const filename = path.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, ''); // Extracts filename without extension from full path.
fs.writeFileSync(filename + '_transformed.transcript', JSON.stringify(jsonTranscript));
console.log("Done");
} catch (e) {
console.log("Cannot write file ", e);
}
Expand Down