feat: [#1514] Port JS CLU Recognizer unit tests (#1521)

* Port unit tests for JS CLU Recognizer

* Change return of mocked functions
This commit is contained in:
Cecilia Avila 2023-08-07 11:58:47 -03:00 коммит произвёл GitHub
Родитель 7eef04cc83
Коммит 9894bffcc1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 615 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,26 @@
{
"name": "@microsoft/bot-components-clu-recognizer-tests",
"private": true,
"scripts": {
"build:package": "yarn workspace @microsoft/bot-components-clu-recognizer build",
"test": "yarn run build:package && mocha --require ts-node/register tests/*.test.ts",
"lint": "eslint . --ext .js,.ts --config ../../../../../packages/Recognizers/ConversationLanguageUnderstanding/js/.eslintrc.json"
},
"dependencies": {
"@microsoft/bot-components-clu-recognizer": "workspace:packages/Recognizers/ConversationLanguageUnderstanding/js",
"@types/mocha": "^8.2.2",
"@types/sinon": "^10.0.16",
"@types/uuid": "^9.0.2",
"botbuilder": "4.19.3",
"botbuilder-dialogs-adaptive": "4.19.3-preview",
"botbuilder-dialogs-adaptive-testing": "4.19.3-preview",
"botframework-connector": "4.19.3",
"mocha": "^9.0.2",
"nock": "^13.1.1",
"ts-node": "^10.0.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"eslint": "^7.30.0"
}
}

Просмотреть файл

@ -0,0 +1,165 @@
// Licensed under the MIT License.
// Copyright (c) Microsoft Corporation. All rights reserved.
import 'mocha';
import assert from 'assert';
import { CluApplication } from '@microsoft/bot-components-clu-recognizer';
const ProjectName = 'MockProjectName';
const EndpointKey = '4da536f842114fa68c657115d7312026';
const Endpoint = 'https://mockcluservice.cognitiveservices.azure.com';
const DeploymentName = 'MockDeploymentName';
const badArgumentCases = [
{
it: 'empty arguments',
projectName: '',
endpointKey: '',
endpoint: '',
deploymentName: '',
},
{
it: 'only ProjectName',
projectName: ProjectName,
endpointKey: '',
endpoint: '',
deploymentName: '',
},
{
it: 'only EndpointKey',
projectName: '',
endpointKey: EndpointKey,
endpoint: '',
deploymentName: '',
},
{
it: 'only Endpoint',
projectName: '',
endpointKey: '',
endpoint: Endpoint,
deploymentName: '',
},
{
it: 'only DeploymentName',
projectName: '',
endpointKey: '',
endpoint: '',
deploymentName: DeploymentName,
},
{
it: 'no Endpoint or DeploymentName',
projectName: ProjectName,
endpointKey: EndpointKey,
endpoint: '',
deploymentName: '',
},
{
it: 'no EndpointKey or DeploymentName',
projectName: ProjectName,
endpointKey: '',
endpoint: Endpoint,
deploymentName: '',
},
{
it: 'no EndpointKey or Endpoint',
projectName: ProjectName,
endpointKey: '',
endpoint: '',
deploymentName: DeploymentName,
},
{
it: 'no ProjectName or DeploymentName',
projectName: '',
endpointKey: EndpointKey,
endpoint: Endpoint,
deploymentName: '',
},
{
it: 'no ProjectName or Endpoint',
projectName: '',
endpointKey: EndpointKey,
endpoint: '',
deploymentName: DeploymentName,
},
{
it: 'no ProjectName or EndpointKey',
projectName: '',
endpointKey: '',
endpoint: Endpoint,
deploymentName: DeploymentName,
},
{
it: 'no DeploymentName',
projectName: ProjectName,
endpointKey: EndpointKey,
endpoint: Endpoint,
deploymentName: '',
},
{
it: 'no Endpoint',
projectName: ProjectName,
endpointKey: EndpointKey,
endpoint: '',
deploymentName: DeploymentName,
},
{
it: 'no EndpointKey',
projectName: ProjectName,
endpointKey: '',
endpoint: Endpoint,
deploymentName: DeploymentName,
},
{
it: 'no ProjectName',
projectName: '',
endpointKey: EndpointKey,
endpoint: Endpoint,
deploymentName: DeploymentName,
},
{
it: 'no valid EndpointKey',
projectName: ProjectName,
endpointKey: 'NotValidGuid',
endpoint: Endpoint,
deploymentName: DeploymentName,
},
{
it: 'no valid Endpoint',
projectName: ProjectName,
endpointKey: EndpointKey,
endpoint: 'NotValidEndpoint',
deploymentName: DeploymentName,
},
];
describe('CluApplication Tests', function () {
describe('Constructor should throw with bad arguments', function () {
badArgumentCases.forEach((testCase) => {
it(`${testCase.it}`, () => {
assert.throws(
() =>
new CluApplication(
testCase.projectName,
testCase.endpointKey,
testCase.endpoint,
testCase.deploymentName
)
);
});
});
});
it('Constructor should work when using valid arguments', () => {
const cluApplication = new CluApplication(
ProjectName,
EndpointKey,
Endpoint,
DeploymentName
);
assert.deepStrictEqual(cluApplication.projectName, ProjectName);
assert.deepStrictEqual(cluApplication.endpointKey, EndpointKey);
assert.deepStrictEqual(cluApplication.endpoint, Endpoint);
assert.deepStrictEqual(cluApplication.deploymentName, DeploymentName);
});
});

Просмотреть файл

@ -0,0 +1,124 @@
// Licensed under the MIT License.
// Copyright (c) Microsoft Corporation. All rights reserved.
import 'mocha';
import assert from 'assert';
import { CluExtensions } from '@microsoft/bot-components-clu-recognizer/lib/cluExtensions';
const expectedMappedIntents: Record<string, number> = {
OrderPizza: 0.79148775,
Help: 0.51214343,
CancelOrder: 0.44985053,
None: 0,
};
const sut = {
topIntent: 'OrderPizza',
projectKind: 'Conversation',
intents: [
{
category: 'OrderPizza',
confidenceScore: 0.79148775,
},
{
category: 'Help',
confidenceScore: 0.51214343,
},
{
category: 'CancelOrder',
confidenceScore: 0.44985053,
},
{
category: 'None',
confidenceScore: 0,
},
],
entities: [
{
category: 'DateTimeOfOrder',
text: 'tomorrow',
offset: 29,
length: 8,
confidenceScore: 1,
resolutions: [
{
resolutionKind: 'DateTimeResolution',
dateTimeSubKind: 'Date',
timex: '2023-02-03',
value: '2023-02-03',
},
],
extraInformation: [
{
extraInformationKind: 'EntitySubtype',
value: 'datetime.date',
},
],
},
{
category: 'Ingredients',
text: 'ham',
offset: 43,
length: 3,
confidenceScore: 1,
extraInformation: [
{
extraInformationKind: 'ListKey',
key: 'Ham',
},
],
},
{
category: 'Ingredients',
text: 'cheese and onions',
offset: 48,
length: 17,
confidenceScore: 1,
},
{
category: 'DateTimeOfOrder',
text: 'next week',
offset: 89,
length: 9,
confidenceScore: 1,
resolutions: [
{
resolutionKind: 'TemporalSpanResolution',
begin: '2023-02-06',
end: '2023-02-13',
},
],
extraInformation: [
{
extraInformationKind: 'EntitySubtype',
value: 'datetime.daterange',
},
],
},
],
};
describe('CluExtensions Tests', function () {
it('ExtractIntents should extract intents from clu result', () => {
const result = CluExtensions.extractIntents(sut);
for (const key in result) {
const expectedKeys = Object.keys(expectedMappedIntents);
if (expectedKeys.includes(key)) {
const expectedIntentKey = expectedKeys.find((expKey) => expKey === key);
assert.strictEqual(key, expectedIntentKey);
assert.strictEqual(result[key].score, expectedMappedIntents[key]);
}
}
});
it('ExtractEntities should extract entities from clu result', () => {
const result = CluExtensions.extractEntities(sut);
const resultArray = Object.entries(result);
assert.strictEqual(resultArray.length, 2);
assert.strictEqual(resultArray[0][0], 'DateTimeOfOrder');
assert.strictEqual(resultArray[1][0], 'Ingredients');
});
});

Просмотреть файл

@ -0,0 +1,124 @@
// Licensed under the MIT License.
// Copyright (c) Microsoft Corporation. All rights reserved.
import 'mocha';
import assert from 'assert';
import {
HttpClient,
HttpHeaders,
HttpOperationResponse,
WebResourceLike,
} from '@azure/ms-rest-js';
import {
CluApplication,
CluRecognizerOptions,
} from '@microsoft/bot-components-clu-recognizer';
import { v4 } from 'uuid';
const responseContentStr = {
kind: 'ConversationResult',
result: {
query: 'I want to order 3 pizzas with ham tomorrow',
prediction: {
topIntent: 'OrderPizza',
projectKind: 'Conversation',
intents: [
{
category: 'OrderPizza',
confidenceScore: 0.9043113,
},
{
category: 'None',
confidenceScore: 0,
},
],
entities: [
{
category: 'Ingredients',
text: 'ham',
offset: 30,
length: 3,
confidenceScore: 1,
extraInformation: [
{
extraInformationKind: 'ListKey',
key: 'Ham',
},
],
},
{
category: 'DateTimeOfOrder',
text: 'tomorrow',
offset: 34,
length: 8,
confidenceScore: 1,
resolutions: [
{
resolutionKind: 'DateTimeResolution',
dateTimeSubKind: 'Date',
timex: '2023-02-04',
value: '2023-02-04',
},
],
extraInformation: [
{
extraInformationKind: 'EntitySubtype',
value: 'datetime.date',
},
],
},
],
},
},
};
describe('CluRecognizerOptions Tests', function () {
const options = new CluRecognizerOptions(
new CluApplication(
'MockProjectName',
v4(),
'https://mockendpoint.com',
'MockDeploymentName'
)
);
it('Recognize should return recognizeResult when called with utterance', async () => {
const httpClient = new HttpClientMock();
const result = await options.recognize('test', httpClient);
assert(result);
assert.strictEqual(result.text, 'test');
assert.strictEqual(result.alteredText, 'test');
assert(result.intents);
const intents = Object.keys(result.intents);
assert.strictEqual(intents.length, 2);
intents.forEach((intent) => {
if (intent == 'OrderPizza') {
assert.strictEqual(result.intents[intent].score, 0.9043113);
} else if (intent == 'None') {
assert.strictEqual(result.intents[intent].score, 0);
}
});
assert(result.entities);
const expectedKeys = Object.keys(result.entities);
assert.strictEqual(expectedKeys.length, 2);
assert.strictEqual(expectedKeys.includes('DateTimeOfOrder'), true);
assert.strictEqual(expectedKeys.includes('Ingredients'), true);
});
});
class HttpClientMock implements HttpClient {
sendRequest(httpRequest: WebResourceLike): Promise<HttpOperationResponse> {
return Promise.resolve({
parsedBody: responseContentStr,
headers: new HttpHeaders(),
request: httpRequest,
status: 200,
});
}
}

Просмотреть файл

@ -0,0 +1,87 @@
// Licensed under the MIT License.
// Copyright (c) Microsoft Corporation. All rights reserved.
import 'mocha';
import assert from 'assert';
import {
CluApplication,
CluConstants,
CluRecognizerOptionsBase,
} from '@microsoft/bot-components-clu-recognizer';
import { HttpClient } from '@azure/ms-rest-js';
import {
TurnContext,
RecognizerResult,
Activity,
NullTelemetryClient,
} from 'botbuilder';
import { DialogContext } from 'botbuilder-dialogs';
import sinon from 'sinon';
describe('CluRecognizerOptionsBase Tests', function () {
it('Should throw when application is undefined', () => {
let application: CluApplication;
assert.throws(() => new CluRecognizerOptionsBaseMock(application));
});
it('Should set application when application is defined', () => {
const application: CluApplication = sinon.createStubInstance(
CluApplication
);
const options = new CluRecognizerOptionsBaseMock(application);
assert.deepStrictEqual(options.application, application);
});
it('Should default properties with correct values', () => {
const application: CluApplication = sinon.createStubInstance(
CluApplication
);
const options = new CluRecognizerOptionsBaseMock(application);
assert.deepStrictEqual(
options.timeout,
CluConstants.HttpClientOptions.Timeout
);
assert.deepStrictEqual(options.telemetryClient, new NullTelemetryClient());
assert.deepStrictEqual(
options.cluRequestBodyStringIndexType,
CluConstants.RequestOptions.StringIndexType
);
assert.deepStrictEqual(
options.cluApiVersion,
CluConstants.RequestOptions.ApiVersion
);
});
});
class CluRecognizerOptionsBaseMock extends CluRecognizerOptionsBase {
constructor(application: CluApplication) {
super(application);
}
recognize(
turnContext: TurnContext,
httpClient: HttpClient
): Promise<RecognizerResult>;
recognize(
dialogContext: DialogContext,
activity: Activity,
httpClient: HttpClient
): Promise<RecognizerResult>;
recognize(
utterance: string,
httpClient: HttpClient
): Promise<RecognizerResult>;
recognize(
_dialogContext: unknown,
_activity: unknown,
_httpClient?: unknown
): Promise<RecognizerResult> {
return Promise.resolve({ text: 'text', intents: {}, entities: {} });
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Licensed under the MIT License.
// Copyright (c) Microsoft Corporation. All rights reserved.
import 'mocha';
import assert from 'assert';
import { DefaultHttpClientFactory } from '@microsoft/bot-components-clu-recognizer/lib/defaultHttpClientFactory';
import { ActivityTypes, TestAdapter, TurnContext } from 'botbuilder';
import {
ConnectorClient,
MicrosoftAppCredentials,
} from 'botframework-connector';
describe('DefaultHttpClientFactory Tests', function () {
const connectorClient = new ConnectorClient(
new MicrosoftAppCredentials('abc', '123'),
{
baseUri: 'https://smba.trafficmanager.net/amer/',
}
);
const adapter = new TestAdapter();
const activity = {
type: ActivityTypes.Message,
text: 'test',
};
const context = new TurnContext(adapter, activity);
context.turnState.set(context.adapter.ConnectorClientKey, connectorClient);
it('Create should return same http client instance', async () => {
const factory = new DefaultHttpClientFactory(context);
const firstClient = factory.create();
const secondClient = factory.create();
assert.strictEqual(firstClient === secondClient, true);
});
});

Просмотреть файл

@ -0,0 +1,9 @@
{
"extends": "@tsconfig/recommended",
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"composite": true,
"sourceMap": true,
"strict": true
}
}

Просмотреть файл

@ -918,6 +918,26 @@ __metadata:
languageName: node
linkType: hard
"@microsoft/bot-components-clu-recognizer-tests@workspace:tests/unit/packages/Recognizers/js":
version: 0.0.0-use.local
resolution: "@microsoft/bot-components-clu-recognizer-tests@workspace:tests/unit/packages/Recognizers/js"
dependencies:
"@microsoft/bot-components-clu-recognizer": "workspace:packages/Recognizers/ConversationLanguageUnderstanding/js"
"@types/mocha": ^8.2.2
"@types/sinon": ^10.0.16
"@types/uuid": ^9.0.2
botbuilder: 4.19.3
botbuilder-dialogs-adaptive: 4.19.3-preview
botbuilder-dialogs-adaptive-testing: 4.19.3-preview
botframework-connector: 4.19.3
eslint: ^7.30.0
mocha: ^9.0.2
nock: ^13.1.1
ts-node: ^10.0.0
uuid: ^8.3.2
languageName: unknown
linkType: soft
"@microsoft/bot-components-clu-recognizer@workspace:packages/Recognizers/ConversationLanguageUnderstanding/js":
version: 0.0.0-use.local
resolution: "@microsoft/bot-components-clu-recognizer@workspace:packages/Recognizers/ConversationLanguageUnderstanding/js"
@ -1660,6 +1680,22 @@ __metadata:
languageName: node
linkType: hard
"@types/sinon@npm:^10.0.16":
version: 10.0.16
resolution: "@types/sinon@npm:10.0.16"
dependencies:
"@types/sinonjs__fake-timers": "*"
checksum: fbce0708cf11f4962fa17a57e0cc27c93c381a2220764a3f059d23263875063047c9a8cdf3d5a7b69bb19a0d909c9c04710cddf6ce89243499712a85469170eb
languageName: node
linkType: hard
"@types/sinonjs__fake-timers@npm:*":
version: 8.1.2
resolution: "@types/sinonjs__fake-timers@npm:8.1.2"
checksum: 27e88de175c4ee8e95338be9f8bfef9ba29bfa505f67addd227242aa0d57deb0cfd237e1596105da4b71fd389a8eb1c6030f0322cda350fd7aa6f2bb2736ecbf
languageName: node
linkType: hard
"@types/stack-utils@npm:^1.0.1":
version: 1.0.1
resolution: "@types/stack-utils@npm:1.0.1"
@ -1676,6 +1712,13 @@ __metadata:
languageName: node
linkType: hard
"@types/uuid@npm:^9.0.2":
version: 9.0.2
resolution: "@types/uuid@npm:9.0.2"
checksum: c4f5a8e284a3daca4638384445824246f862ed021526598481fa85f8ee0208aa5bed44db32258e39c0981d2b00666d025359956087f1972b6d8ec3c14290995f
languageName: node
linkType: hard
"@types/ws@npm:^6.0.3":
version: 6.0.4
resolution: "@types/ws@npm:6.0.4"