Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dynamic capability using regex #17

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
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 === "1")
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 "1";
memassamara marked this conversation as resolved.
Show resolved Hide resolved
}
}

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);
// Can
memassamara marked this conversation as resolved.
Show resolved Hide resolved
if (expectedReply.text.startsWith("regex:")) {
const regex = new RegExp(expectedReply.text.replace("regex:", "").trim());
expect(regex.test(botReply.text)).to.equals(true);
memassamara marked this conversation as resolved.
Show resolved Hide resolved
} 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