[Lubuild] fix multiple bugs: case sensitive issue, default version issue and exception not throw out issue (#576)

* fix case issue of utterances and patterns

* remove lower function for entities

* adjust test cases

* use a better solution to compare utterances

* add tests to verify the fixes

* move new added tests to luis package from lu package

* add test cases for luConfig parameter in lubuild CLI

* fix error not throw out bug

* resolve comments of CLIError

* remove uncessary semicolon

* Updates and fixes

* Updates

* updating tests.

* Updates for PR feedback, fix settings out path

* updates

* fix build errors

* fix command parameter issue in test cases

* fix tslint errors

* remove lubuild.js which is not necessary

* fixing bad merge

* fix tslint and expose more in index.js and composerindex.js

* remove lubuild module in index.js

Co-authored-by: Emilio Munoz <munoz_emilio@hotmail.com>
Co-authored-by: Vishwac Sena Kannan <vishwack@hotmail.com>
This commit is contained in:
Fei Chen 2020-02-27 13:11:36 +08:00 коммит произвёл GitHub
Родитель 6dd1d873a9
Коммит 49f1871797
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 1972 добавлений и 699 удалений

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

@ -70,6 +70,7 @@ dependencies:
source-map-support: 0.5.16
testdouble: 3.12.5
ts-md5: 1.2.7
username: 4.1.0
uuid: 3.3.3
window-size: 1.1.1
xml2js: 0.4.23
@ -2192,6 +2193,20 @@ packages:
node: '>=4'
resolution:
integrity: sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==
/execa/1.0.0:
dependencies:
cross-spawn: 6.0.5
get-stream: 4.1.0
is-stream: 1.1.0
npm-run-path: 2.0.2
p-finally: 1.0.0
signal-exit: 3.0.2
strip-eof: 1.0.0
dev: false
engines:
node: '>=6'
resolution:
integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
/expand-brackets/2.1.4:
dependencies:
debug: 2.6.9
@ -2569,6 +2584,14 @@ packages:
node: '>=4'
resolution:
integrity: sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
/get-stream/4.1.0:
dependencies:
pump: 3.0.0
dev: false
engines:
node: '>=6'
resolution:
integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
/get-stream/5.1.0:
dependencies:
pump: 3.0.0
@ -3729,6 +3752,14 @@ packages:
dev: false
resolution:
integrity: sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==
/map-age-cleaner/0.1.3:
dependencies:
p-defer: 1.0.0
dev: false
engines:
node: '>=6'
resolution:
integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==
/map-cache/0.2.2:
dev: false
engines:
@ -3751,6 +3782,16 @@ packages:
dev: false
resolution:
integrity: sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=
/mem/4.3.0:
dependencies:
map-age-cleaner: 0.1.3
mimic-fn: 2.1.0
p-is-promise: 2.1.0
dev: false
engines:
node: '>=6'
resolution:
integrity: sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==
/merge-descriptors/1.0.1:
dev: false
resolution:
@ -3816,6 +3857,12 @@ packages:
node: '>=4'
resolution:
integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
/mimic-fn/2.1.0:
dev: false
engines:
node: '>=6'
resolution:
integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
/mimic-response/1.0.1:
dev: false
engines:
@ -4228,6 +4275,12 @@ packages:
node: '>=4'
resolution:
integrity: sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==
/p-defer/1.0.0:
dev: false
engines:
node: '>=4'
resolution:
integrity: sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=
/p-finally/1.0.0:
dev: false
engines:
@ -4240,6 +4293,12 @@ packages:
node: '>=4'
resolution:
integrity: sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=
/p-is-promise/2.1.0:
dev: false
engines:
node: '>=6'
resolution:
integrity: sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
/p-limit/2.2.2:
dependencies:
p-try: 2.2.0
@ -5764,6 +5823,15 @@ packages:
node: '>=0.10.0'
resolution:
integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
/username/4.1.0:
dependencies:
execa: 1.0.0
mem: 4.3.0
dev: false
engines:
node: '>=6'
resolution:
integrity: sha512-sKh1KCsMfv8jPIC9VdeQhrNAgkl842jS/M74HQv7Byr0AMAwKZt8mLWX9DmtMeD8nQA3eKa10f5LbqlSVmokMg==
/util-deprecate/1.0.2:
dev: false
resolution:
@ -6273,7 +6341,7 @@ packages:
dev: false
name: '@rush-temp/bf-lu'
resolution:
integrity: sha512-g5LXnFL9CB6a/KTzOv3maI9SP0mYK42CE+f9z3ozycf7sQzXsg+pkS9sx3UxcbX7/TspDTly+KQbNSELmx5HGA==
integrity: sha512-7SyXpwe1mc1yOJjI8p5+f/IeHGFoyAcclZKa5Ziz7yQlxb/OHPyma6c0YCV685uzcI3PXeBP27K3RtqxxTNQPQ==
tarball: 'file:projects/bf-lu.tgz'
version: 0.0.0
'file:projects/bf-luis-cli.tgz':
@ -6310,11 +6378,12 @@ packages:
tslib: 1.10.0
tslint: 5.20.1_typescript@3.7.4
typescript: 3.7.4
username: 4.1.0
uuid: 3.3.3
dev: false
name: '@rush-temp/bf-luis-cli'
resolution:
integrity: sha512-DDMDOkSq7aCvKWx8lg4izYltihcctg06xLi2eQ6qC7pIUg5i1ZiQI192RKFHAGvrIFf2ZKVhE0e82c+OYivS4w==
integrity: sha512-0E667Qi/DQ3XDAvFPkzf+jij+IQqc4ELkPMweHCr8aNg7eodwsVTD/rQuQx2V8P/nUdUuCoB9AZ7Ct12YC7jzA==
tarball: 'file:projects/bf-luis-cli.tgz'
version: 0.0.0
'file:projects/bf-qnamaker.tgz':
@ -6473,6 +6542,7 @@ specifiers:
source-map-support: ~0.5.16
testdouble: ^3.11.0
ts-md5: ^1.2.6
username: ^4.1.0
uuid: ^3.3.3
window-size: ^1.1.0
xml2js: ^0.4.19

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

@ -1,4 +1,11 @@
module.exports = {
parseFile: require('./lufile/parseFileContents').parseFile,
validateLUISBlob: require('./luis/luisValidator')
parser: {
parseFile: require('./lufile/parseFileContents').parseFile,
validateLUISBlob: require('./luis/luisValidator')
},
sectionHandler: {
luParser: require('./lufile/luParser'),
sectionOperator: require('./lufile/sectionOperator'),
luSectionTypes: require('./utils/enums/lusectiontypes')
},
}

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

@ -27,11 +27,6 @@ const modules = {
Parser: require('./lufile/classes/parserObject')
},
sectionHandler: {
luParser: require('./lufile/luParser'),
sectionOperator: require('./lufile/sectionOperator'),
luSectionTypes: require('./utils/enums/lusectiontypes')
},
V2 : {
Luis: require('./luis/luis'),
LU: require('./lu/lu'),

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

@ -1,10 +0,0 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const modules = {
luBuild: {
Builder: require('./lubuild/builder').Builder
}
};
module.exports = modules;

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

@ -12,6 +12,8 @@ const fs = require('fs-extra')
const delay = require('delay')
const fileHelper = require('./../../utils/filehelper')
const fileExtEnum = require('./../utils/helpers').FileExtTypeEnum
const retCode = require('./../utils/enums/CLI-errors')
const exception = require('./../utils/exception')
const LuisBuilderVerbose = require('./../luis/luisCollate')
const LuisBuilder = require('./../luis/luisBuilder')
const LUOptions = require('./../lu/luOptions')
@ -38,8 +40,17 @@ export class Builder {
let fileCulture: string
let fileName: string
const luFiles = await fileHelper.getLuObjects(undefined, file, true, fileExtEnum.LUFile)
const result = await LuisBuilderVerbose.build(luFiles, true, culture)
const fileContent = result.parseToLuContent()
let fileContent = ''
let result
try {
result = await LuisBuilderVerbose.build(luFiles, true, culture)
fileContent = result.parseToLuContent()
} catch (err) {
err.text = `Invalid LU file ${file}: ${err.text}`
throw(new exception(retCode.errorCode.INVALID_INPUT_FILE, err.text))
}
this.handler(`${file} loaded\n`)
let cultureFromPath = fileHelper.getCultureFromPath(file)
if (cultureFromPath) {
@ -47,7 +58,7 @@ export class Builder {
let fileNameWithCulture = path.basename(file, path.extname(file))
fileName = fileNameWithCulture.substring(0, fileNameWithCulture.length - fileCulture.length - 1)
} else {
fileCulture = culture
fileCulture = result.culture !== 'en-us' ? result.culture : culture
fileName = path.basename(file, path.extname(file))
}
@ -95,7 +106,7 @@ export class Builder {
})
if (hasDuplicates) {
throw new Error('Files with same name and locale are found.')
throw(new exception(retCode.errorCode.INVALID_INPUT_FILE, 'Files with same name and locale are found.'))
}
return {luContents, recognizers, multiRecognizers, settings}
@ -200,16 +211,23 @@ export class Builder {
return dialogContents
}
async writeDialogAssets(contents: any[], force: boolean, out: string) {
async writeDialogAssets(contents: any[], force: boolean, out: string, luconfig: string) {
let writeDone = false
if (out) {
let settingsContents = contents.filter(c => c.id.endsWith('.json'))
let writeContents = contents.filter(c => c.id.endsWith('.dialog'))
if (settingsContents && settingsContents.length > 0) {
writeContents.push(this.mergeSettingsContent(path.join(path.resolve(out), settingsContents[0].id), settingsContents))
let writeContents = contents.filter(c => c.id.endsWith('.dialog'))
let settingsContents = contents.filter(c => c.id.endsWith('.json'))
if (settingsContents && settingsContents.length > 0) {
let outPath
if (luconfig) {
outPath = path.join(path.resolve(path.dirname(luconfig)), settingsContents[0].id)
} else if (out) {
outPath = path.join(path.resolve(out), settingsContents[0].id)
}
writeContents.push(this.mergeSettingsContent(outPath, settingsContents))
}
if (out) {
for (const content of writeContents) {
const outFilePath = path.join(path.resolve(out), path.basename(content.path))
if (force || !fs.existsSync(outFilePath)) {
@ -219,7 +237,7 @@ export class Builder {
}
}
} else {
for (const content of contents) {
for (const content of writeContents) {
if (force || !fs.existsSync(content.path)) {
this.handler(`Writing to ${content.path}\n`)
await fs.writeFile(content.path, content.content, 'utf-8')
@ -269,7 +287,7 @@ export class Builder {
const versionObjs = await luBuildCore.listApplicationVersions(recognizer.getAppId())
for (const versionObj of versionObjs) {
if (versionObj.version !== newVersionId) {
this.handler(`deleting old version=${versionObj.version}`)
this.handler(`${recognizer.getLuPath()} deleting old version=${versionObj.version}`)
await luBuildCore.deleteVersion(recognizer.getAppId(), versionObj.version)
}
}

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

@ -49,13 +49,7 @@ export class LuBuildCore {
public compareApplications(currentApp: any, existingApp: any) {
currentApp.desc = currentApp.desc && currentApp.desc !== '' && currentApp.desc !== existingApp.desc ? currentApp.desc : existingApp.desc
currentApp.culture = currentApp.culture && currentApp.culture !== '' && currentApp.culture !== existingApp.culture ? currentApp.culture : existingApp.culture
currentApp.versionId = currentApp.versionId && currentApp.versionId !== '' && currentApp.versionId !== existingApp.versionId ? currentApp.versionId : existingApp.versionId;
// convert utterance text from lu file to lower case
// as utterances from luis api are all converted to lower case automatically
(currentApp.utterances || []).forEach((u: any) => {
u.text = u.text.toLowerCase()
});
currentApp.versionId = currentApp.versionId && currentApp.versionId !== '' && currentApp.versionId > existingApp.versionId ? currentApp.versionId : existingApp.versionId;
// convert list entities to remove synonyms word in list which is same with canonicalForm
(currentApp.closedLists || []).forEach((c: any) => {
@ -85,7 +79,7 @@ export class LuBuildCore {
public updateVersion(currentApp: any, existingApp: any) {
let newVersionId: string
if (currentApp.versionId && currentApp.versionId !== existingApp.versionId) {
if (currentApp.versionId > existingApp.versionId) {
newVersionId = currentApp.versionId
} else {
newVersionId = this.updateVersionValue(existingApp.versionId)
@ -178,12 +172,12 @@ export class LuBuildCore {
equal = equal && this.isArrayEqual(appA.closedLists, appB.closedLists)
equal = equal && this.isArrayEqual(appA.composites, appB.composites)
equal = equal && this.isArrayEqual(appA.entities, appB.entities)
equal = equal && this.isArrayEqual(appA.modelFeatures, appB.modelFeatures)
equal = equal && this.isArrayEqual(appA.model_features, appB.modelFeatures)
equal = equal && this.isArrayEqual(appA.patternAnyEntities, appB.patternAnyEntities)
equal = equal && this.isArrayEqual(appA.patterns, appB.patterns)
equal = equal && this.isArrayEqual(appA.prebuiltEntities, appB.prebuiltEntities)
equal = equal && this.isArrayEqual(appA.regexEntities, appB.regexEntities)
equal = equal && this.isArrayEqual(appA.regexFeatures, appB.regexFeatures)
equal = equal && this.isArrayEqual(appA.regex_entities, appB.regexEntities)
equal = equal && this.isArrayEqual(appA.regex_features, appB.regexFeatures)
equal = equal && this.isArrayEqual(appA.utterances, appB.utterances)
// handle exception for none intent which is default added in luis portal
@ -205,15 +199,14 @@ export class LuBuildCore {
let yObj = []
if (x && x.length > 0) {
xObj = JSON.parse(JSON.stringify(x))
xObj = JSON.parse(JSON.stringify(x).toLowerCase().replace(/ {2}/g, ' '))
}
if (y && y.length > 0) {
yObj = JSON.parse(JSON.stringify(y))
yObj = JSON.parse(JSON.stringify(y).toLowerCase().replace(/ {2}/g, ' '))
}
if (xObj.length !== yObj.length) return false
if (differenceWith(xObj, yObj, isEqual).length > 0) return false
return true

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

@ -1,558 +0,0 @@
import {expect, test} from '@oclif/test'
const path = require('path')
const fs = require('fs-extra')
const uuidv1 = require('uuid/v1')
const nock = require('nock')
const compareFiles = async function (file1: string, file2: string) {
let result = await fs.readFile(path.join(__dirname, file1))
let fixtureFile = await fs.readFile(path.join(__dirname, file2))
result = result.toString().replace(/\r\n/g, '\n')
fixtureFile = fixtureFile.toString().replace(/\r\n/g, '\n')
expect(fixtureFile).to.deep.equal(result)
return result === fixtureFile
}
xdescribe('luis:build cli parameters test', () => {
test
.stdout()
.command(['luis:build', '--help'])
.it('should print the help contents when --help is passed as an argument', ctx => {
expect(ctx.stdout).to.contain('Build lu files to train and publish luis applications')
})
test
.stdout()
.stderr()
.command(['luis:build', '--in', `${path.join(__dirname, './../../fixtures/testcases/lubuild')}`, '--botName', 'Contoso'])
.it('displays an error if any required input parameters are missing', ctx => {
expect(ctx.stderr).to.contain('Missing required flag:\n --authoringKey AUTHORINGKEY')
})
test
.stdout()
.stderr()
.command(['luis:build', '--authoringKey', uuidv1(), '--botName', 'Contoso'])
.it('displays an error if any required input parameters are missing', ctx => {
expect(ctx.stderr).to.contain('Missing input. Please use stdin or pass a file or folder location with --in flag')
})
test
.stdout()
.stderr()
.command(['luis:build', '--authoringKey', uuidv1(), '--in', `${path.join(__dirname, './../../fixtures/testcases/lubuild')}`])
.it('displays an error if any required input parameters are missing', ctx => {
expect(ctx.stderr).to.contain('Missing bot name. Please pass bot name with --botName flag')
})
test
.stdout()
.stderr()
.command(['luis:build', '--authoringKey', uuidv1(), '--in', `${path.join(__dirname, './../../fixtures/testcases/lubuild/file-name-duplicated')}`, '--botName', 'Contoso'])
.it('displays an error if files with same name and locale are found', ctx => {
expect(ctx.stderr).to.contain('Files with same name and locale are found')
})
})
xdescribe('luis:build create a new application successfully', () => {
before(function () {
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, [{
name: 'test.en-us.lu',
id: 'f8c64e2a-1111-3a09-8f78-39d7adc76ec5'
}])
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('import'))
.reply(201, {
appId: 'f8c64e2a-2222-3a09-8f78-39d7adc76ec5'
})
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('train'))
.reply(202, {
statusId: 2,
status: 'UpToDate'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('train'))
.reply(200, [{
modelId: '99999',
details: {
statusId: 0,
status: 'Success',
exampleCount: 0
}
}])
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('publish'))
.reply(201, {
versionId: '0.2',
isStaging: true
})
})
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich//lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log'])
.it('should create a new application successfully', ctx => {
expect(ctx.stdout).to.contain('Start to handle applications')
expect(ctx.stdout).to.contain('Creating LUIS.ai application')
expect(ctx.stdout).to.contain('training version=0.1')
expect(ctx.stdout).to.contain('waiting for training for version=0.1')
expect(ctx.stdout).to.contain('publishing version=0.1')
expect(ctx.stdout).to.contain('publishing finished')
})
})
xdescribe('luis:build update application succeed when utterances changed', () => {
const existingLuisApp = require('./../../fixtures/testcases/lubuild/sandwich/luis/test(development)-sandwich.utteranceChanged.en-us.lu.json')
before(function () {
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, [{
name: 'test(development)-sandwich.en-us.lu',
id: 'f8c64e2a-8635-3a09-8f78-39d7adc76ec5'
}])
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, {
name: 'test(development)-sandwich.en-us.lu',
id: 'f8c64e2a-8635-3a09-8f78-39d7adc76ec5',
activeVersion: '0.1'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('export'))
.reply(200, existingLuisApp)
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('import'))
.reply(201, '0.2')
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('train'))
.reply(202, {
statusId: 2,
status: 'UpToDate'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('train'))
.reply(200, [{
modelId: '99999',
details: {
statusId: 0,
status: 'Success',
exampleCount: 0
}
}])
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('publish'))
.reply(201, {
versionId: '0.2',
isStaging: true
})
})
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich/lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log'])
.it('should update a luis application when utterances changed', ctx => {
expect(ctx.stdout).to.contain('Start to handle applications')
expect(ctx.stdout).to.contain('creating version=0.2')
expect(ctx.stdout).to.contain('training version=0.2')
expect(ctx.stdout).to.contain('waiting for training for version=0.2')
expect(ctx.stdout).to.contain('publishing version=0.2')
expect(ctx.stdout).to.contain('publishing finished')
})
})
xdescribe('luis:build update application succeed when utterances added', () => {
const existingLuisApp = require('./../../fixtures/testcases/lubuild/sandwich/luis/test(development)-sandwich.utteranceAdded.en-us.lu.json')
before(function () {
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, [{
name: 'test(development)-sandwich.en-us.lu',
id: 'f8c64e2a-8635-3a09-8f78-39d7adc76ec5'
}])
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, {
name: 'test(development)-sandwich.en-us.lu',
id: 'f8c64e2a-8635-3a09-8f78-39d7adc76ec5',
activeVersion: '0.1'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('export'))
.reply(200, existingLuisApp)
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('import'))
.reply(201, '0.2')
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('train'))
.reply(202, {
statusId: 2,
status: 'UpToDate'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('train'))
.reply(200, [{
modelId: '99999',
details: {
statusId: 0,
status: 'Success',
exampleCount: 0
}
}])
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('publish'))
.reply(201, {
versionId: '0.2',
isStaging: true
})
})
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich/lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log'])
.it('should update a luis application when utterances added', ctx => {
expect(ctx.stdout).to.contain('Start to handle applications')
expect(ctx.stdout).to.contain('creating version=0.2')
expect(ctx.stdout).to.contain('training version=0.2')
expect(ctx.stdout).to.contain('waiting for training for version=0.2')
expect(ctx.stdout).to.contain('publishing version=0.2')
expect(ctx.stdout).to.contain('publishing finished')
})
})
xdescribe('luis:build not update application if no changes', () => {
const existingLuisApp = require('./../../fixtures/testcases/lubuild/sandwich/luis/test(development)-sandwich.en-us.lu.json')
before(function () {
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, [{
name: 'test(development)-sandwich.en-us.lu',
id: 'f8c64e2a-8635-3a09-8f78-39d7adc76ec5'
}])
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, {
name: 'test(development)-sandwich.en-us.lu',
id: 'f8c64e2a-8635-3a09-8f78-39d7adc76ec5',
activeVersion: '0.1'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('export'))
.reply(200, existingLuisApp)
})
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich/lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log'])
.it('should not update a luis application when there are no changes for the coming lu file', ctx => {
expect(ctx.stdout).to.contain('Start to handle applications')
expect(ctx.stdout).to.contain('no changes')
})
})
xdescribe('luis:build write dialog assets successfully if --dialog set', () => {
const existingLuisApp = require('./../../fixtures/testcases/lubuild/sandwich/luis/test(development)-sandwich.en-us.lu.json')
before(async function () {
await fs.ensureDir(path.join(__dirname, './../../../results/'))
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, [{
name: 'test(development)-sandwich.en-us.lu',
id: 'f8c64e2a-8635-3a09-8f78-39d7adc76ec5'
}])
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, {
name: 'test(development)-sandwich.en-us.lu',
id: 'f8c64e2a-8635-3a09-8f78-39d7adc76ec5',
activeVersion: '0.1'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('export'))
.reply(200, existingLuisApp)
})
after(async function () {
await fs.remove(path.join(__dirname, './../../../results/'))
})
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich/lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--dialog', '--out', './results', '--log'])
.it('should write dialog assets successfully when --dialog set', async ctx => {
expect(await compareFiles('./../../../results/luis.settings.development.westus.json', './../../fixtures/testcases/lubuild/sandwich/config/luis.settings.development.westus.json')).to.be.true
expect(await compareFiles('./../../../results/sandwich.en-us.lu.dialog', './../../fixtures/testcases/lubuild/sandwich/dialogs/sandwich.en-us.lu.dialog')).to.be.true
expect(await compareFiles('./../../../results/sandwich.lu.dialog', './../../fixtures/testcases/lubuild/sandwich/dialogs/sandwich.lu.dialog')).to.be.true
})
})
xdescribe('luis:build create multiple applications successfully when input is a folder', () => {
before(async function () {
await fs.ensureDir(path.join(__dirname, './../../../results/'))
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, [{
name: 'test.en-us.lu',
id: 'f8c64e2a-1111-3a09-8f78-39d7adc76ec5'
}])
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('import'))
.reply(201, {
appId: 'f8c64e2a-1111-3a09-8f78-39d7adc76ec5'
})
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('import'))
.reply(201, {
appId: 'f8c64e2a-2222-3a09-8f78-39d7adc76ec5'
})
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('import'))
.reply(201, {
appId: 'f8c64e2a-3333-3a09-8f78-39d7adc76ec5'
})
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('train'))
.reply(202, {
statusId: 2,
status: 'UpToDate'
})
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('train'))
.reply(202, {
statusId: 2,
status: 'UpToDate'
})
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('train'))
.reply(202, {
statusId: 2,
status: 'UpToDate'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('train'))
.reply(200, [{
modelId: '99999',
details: {
statusId: 0,
status: 'Success',
exampleCount: 0
}
}])
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('train'))
.reply(200, [{
modelId: '99999',
details: {
statusId: 0,
status: 'Success',
exampleCount: 0
}
}])
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('train'))
.reply(200, [{
modelId: '99999',
details: {
statusId: 0,
status: 'Success',
exampleCount: 0
}
}])
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('publish'))
.reply(201, {
versionId: '0.2',
isStaging: true
})
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('publish'))
.reply(201, {
versionId: '0.2',
isStaging: true
})
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('publish'))
.reply(201, {
versionId: '0.2',
isStaging: true
})
})
after(async function () {
await fs.remove(path.join(__dirname, './../../../results/'))
})
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/foo/lufiles', '--authoringKey', uuidv1(), '--botName', 'test', '--dialog', '--out', './results', '--log'])
.it('should create multiple applications and write dialog assets successfully when input is a folder', async ctx => {
expect(ctx.stdout).to.contain('foo.fr-fr.lu loaded')
expect(ctx.stdout).to.contain('foo.lu loaded')
expect(ctx.stdout).to.contain('foo.zh-cn.lu loaded')
expect(ctx.stdout).to.contain('Start to handle applications')
expect(ctx.stdout).to.contain('Creating LUIS.ai application')
expect(ctx.stdout).to.contain('training version=0.1')
expect(ctx.stdout).to.contain('waiting for training for version=0.1')
expect(ctx.stdout).to.contain('publishing version=0.1')
expect(ctx.stdout).to.contain('foo.fr-fr.lu publishing finished')
expect(ctx.stdout).to.contain('foo.lu publishing finished')
expect(ctx.stdout).to.contain('foo.zh-cn.lu publishing finished')
expect(await compareFiles('./../../../results/luis.settings.development.westus.json', './../../fixtures/testcases/lubuild/foo/config/luis.settings.development.westus.json')).to.be.true
expect(await compareFiles('./../../../results/foo.lu.dialog', './../../fixtures/testcases/lubuild/foo/dialogs/foo.lu.dialog')).to.be.true
expect(await compareFiles('./../../../results/foo.en-us.lu.dialog', './../../fixtures/testcases/lubuild/foo/dialogs/foo.en-us.lu.dialog')).to.be.true
expect(await compareFiles('./../../../results/foo.fr-fr.lu.dialog', './../../fixtures/testcases/lubuild/foo/dialogs/foo.fr-fr.lu.dialog')).to.be.true
expect(await compareFiles('./../../../results/foo.zh-cn.lu.dialog', './../../fixtures/testcases/lubuild/foo/dialogs/foo.zh-cn.lu.dialog')).to.be.true
})
})
xdescribe('luis:build update dialog assets successfully when dialog assets exist', () => {
const existingLuisApp_EN_US = require('./../../fixtures/testcases/lubuild/foo2/luis/test(development)-foo.en-us.lu.json')
const existingLuisApp_FR_FR = require('./../../fixtures/testcases/lubuild/foo2/luis/test(development)-foo.fr-fr.lu.json')
before(async function () {
await fs.ensureDir(path.join(__dirname, './../../../results/'))
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, [
{
name: 'test(development)-foo.fr-fr.lu',
id: 'f8c64e2a-1111-3a09-8f78-39d7adc76ec5'
},
{
name: 'test(development)-foo.en-us.lu',
id: 'f8c64e2a-2222-3a09-8f78-39d7adc76ec5'
}
])
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('import'))
.reply(201, {
appId: 'f8c64e2a-3333-3a09-8f78-39d7adc76ec5'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps') && uri.includes('f8c64e2a-2222-3a09-8f78-39d7adc76ec5'))
.reply(200, {
name: 'test(development)-foo.en-us.lu',
id: 'f8c64e2a-2222-3a09-8f78-39d7adc76ec5',
activeVersion: '0.1'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps') && uri.includes('f8c64e2a-1111-3a09-8f78-39d7adc76ec5'))
.reply(200, {
name: 'test(development)-foo.fr-fr.lu',
id: 'f8c64e2a-1111-3a09-8f78-39d7adc76ec5',
activeVersion: '0.1'
})
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('train'))
.reply(202, {
statusId: 2,
status: 'UpToDate'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('export') && uri.includes('f8c64e2a-2222-3a09-8f78-39d7adc76ec5'))
.reply(200, existingLuisApp_EN_US)
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('export') && uri.includes('f8c64e2a-1111-3a09-8f78-39d7adc76ec5'))
.reply(200, existingLuisApp_FR_FR)
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('train'))
.reply(200, [{
modelId: '99999',
details: {
statusId: 0,
status: 'Success',
exampleCount: 0
}
}])
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('publish'))
.reply(201, {
versionId: '0.2',
isStaging: true
})
})
after(async function () {
await fs.remove(path.join(__dirname, './../../../results/'))
})
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/foo2/lufiles-and-dialog-assets', '--authoringKey', uuidv1(), '--botName', 'test', '--dialog', '--out', './results', '--log'])
.it('should update dialog assets successfully when dialog assets exist', async ctx => {
expect(ctx.stdout).to.contain('foo.fr-fr.lu loaded')
expect(ctx.stdout).to.contain('foo.lu loaded')
expect(ctx.stdout).to.contain('foo.zh-cn.lu loaded')
expect(ctx.stdout).to.contain('luis.settings.development.westus.json loaded')
expect(ctx.stdout).to.contain('foo.lu.dialog loaded')
expect(ctx.stdout).to.contain('Start to handle applications')
expect(ctx.stdout).to.contain('foo.en-us.lu.dialog loaded')
expect(ctx.stdout).to.contain('foo.fr-fr.lu.dialog loaded')
expect(ctx.stdout).to.contain('Creating LUIS.ai application')
expect(ctx.stdout).to.contain('training version=0.1')
expect(ctx.stdout).to.contain('waiting for training for version=0.1')
expect(ctx.stdout).to.contain('publishing version=0.1')
expect(ctx.stdout).to.contain('foo.fr-fr.lu no changes')
expect(ctx.stdout).to.contain('foo.lu no changes')
expect(ctx.stdout).to.contain('foo.zh-cn.lu publishing finished')
expect(await compareFiles('./../../../results/luis.settings.development.westus.json', './../../fixtures/testcases/lubuild/foo2/config/luis.settings.development.westus.json')).to.be.true
expect(await compareFiles('./../../../results/foo.lu.dialog', './../../fixtures/testcases/lubuild/foo2/dialogs/foo.lu.dialog')).to.be.true
expect(await compareFiles('./../../../results/foo.en-us.lu.dialog', './../../fixtures/testcases/lubuild/foo2/dialogs/foo.en-us.lu.dialog')).to.be.true
expect(await compareFiles('./../../../results/foo.fr-fr.lu.dialog', './../../fixtures/testcases/lubuild/foo2/dialogs/foo.fr-fr.lu.dialog')).to.be.true
expect(await compareFiles('./../../../results/foo.zh-cn.lu.dialog', './../../fixtures/testcases/lubuild/foo2/dialogs/foo.zh-cn.lu.dialog')).to.be.true
})
})

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

@ -71,6 +71,7 @@
"ts-node": "^8.4.1",
"tslint": "^5.20.1",
"typescript": "^3.5.3",
"uuid": "^3.3.3"
"uuid": "^3.3.3",
"username": "^4.1.0"
}
}

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

@ -3,9 +3,11 @@
* Licensed under the MIT License.
*/
import {Command, flags} from '@microsoft/bf-cli-command'
import {CLIError, Command, flags} from '@microsoft/bf-cli-command'
const path = require('path')
const fs = require('fs-extra')
const exception = require('@microsoft/bf-lu/lib/parser/utils/exception')
const username = require('username')
const file = require('@microsoft/bf-lu/lib/utils/filehelper')
const fileExtEnum = require('@microsoft/bf-lu/lib/parser/utils/helpers').FileExtTypeEnum
const Content = require('@microsoft/bf-lu').V2.LU
@ -27,105 +29,115 @@ export default class LuisBuild extends Command {
in: flags.string({char: 'i', description: 'Lu file or folder'}),
authoringKey: flags.string({description: 'LUIS authoring key', required: true}),
botName: flags.string({description: 'Bot name'}),
out: flags.string({description: 'Output location'}),
region: flags.string({description: 'LUIS authoring region [westus|westeurope|australiaeast]', default: 'westus'}),
out: flags.string({char: 'o', description: 'Output file or folder name. If not specified, current directory will be used as output'}),
defaultCulture: flags.string({description: 'Culture code for the content. Infer from .lu if available. Defaults to en-us'}),
region: flags.string({description: 'LUIS authoring region'}),
suffix: flags.string({description: 'Environment name as a suffix identifier to include in LUIS app name'}),
force: flags.boolean({char: 'f', description: 'Force write dialog and settings files', default: false}),
dialog: flags.boolean({description: 'Write out .dialog files', default: false}),
fallbackLocale: flags.string({description: 'Locale to be used at the fallback if no locale specific recognizer is found. Only valid if --dialog is set'}),
suffix: flags.string({description: 'Environment name as a suffix identifier to include in LUIS app name. Defaults to current logged in useralias'}),
dialog: flags.boolean({description: 'Write out .dialog files', default: false}),
force: flags.boolean({char: 'f', description: 'If --dialog flag is provided, overwirtes relevant dialog file', default: false}),
luConfig: flags.string({description: 'Path to config for lu build'}),
log: flags.boolean({description: 'write out log messages to console', default: false}),
}
async run() {
const {flags} = this.parse(LuisBuild)
try {
const {flags} = this.parse(LuisBuild)
flags.stdin = await this.readStdin()
flags.stdin = await this.readStdin()
let files: string[] = []
if (flags.luConfig) {
const configFilePath = path.resolve(flags.luConfig)
if (fs.existsSync(configFilePath)) {
const configObj = JSON.parse(await file.getContentFromFile(configFilePath))
if (configObj.name && configObj.name !== '') flags.botName = configObj.name
if (configObj.defaultLanguage && configObj.defaultLanguage !== '') flags.defaultCulture = configObj.defaultLanguage
if (configObj.deleteOldVersion) flags.deleteOldVersion = true
if (configObj.out && configObj.out !== '') flags.out = configObj.out
if (configObj.writeDialogFiles) flags.dialog = true
if (configObj.models && configObj.models.length > 0) {
files = configObj.models.map((m: string) => path.resolve(m))
let files: string[] = []
if (flags.luConfig) {
const configFilePath = path.resolve(flags.luConfig)
if (fs.existsSync(configFilePath)) {
const configObj = JSON.parse(await file.getContentFromFile(configFilePath))
if (configObj.name && configObj.name !== '') flags.botName = configObj.name
if (configObj.defaultLanguage && configObj.defaultLanguage !== '') flags.defaultCulture = configObj.defaultLanguage
if (configObj.deleteOldVersion) flags.deleteOldVersion = true
if (configObj.out && configObj.out !== '') flags.out = path.isAbsolute(configObj.out) ? configObj.out : path.join(path.dirname(configFilePath), configObj.out)
if (configObj.writeDialogFiles) flags.dialog = true
if (configObj.models && configObj.models.length > 0) {
files = configObj.models.map((m: string) => path.isAbsolute(m) ? m : path.join(path.dirname(configFilePath), m))
}
}
}
}
if (!flags.stdin && !flags.in && files.length === 0) {
throw new Error('Missing input. Please use stdin or pass a file or folder location with --in flag')
}
if (!flags.botName) {
throw new Error('Missing bot name. Please pass bot name with --botName flag')
}
flags.defaultCulture = flags.defaultCulture && flags.defaultCulture !== '' ? flags.defaultCulture : 'en-us'
flags.region = flags.region && flags.region !== '' ? flags.region : 'westus'
flags.suffix = flags.suffix && flags.suffix !== '' ? flags.suffix : 'development'
flags.fallbackLocale = flags.fallbackLocale && flags.fallbackLocale !== '' ? flags.fallbackLocale : 'en-us'
// create builder class
const builder = new Builder((input: string) => {
if (flags.log) this.log(input)
})
let luContents: any[] = []
let recognizers = new Map<string, any>()
let multiRecognizers = new Map<string, any>()
let settings = new Map<string, any>()
if (flags.stdin && flags.stdin !== '') {
// load lu content from stdin and create default recognizer, multiRecognier and settings
this.log('Load lu content from stdin')
const content = new Content(flags.stdin, new LUOptions('stdin', true, flags.defaultCulture, path.join(process.cwd(), 'stdin')))
luContents.push(content)
multiRecognizers.set('stdin', new MultiLanguageRecognizer(path.join(process.cwd(), 'stdin.lu.dialog'), {}))
settings.set('stdin', new Settings(path.join(process.cwd(), `luis.settings.${flags.suffix}.${flags.region}.json`), {}))
const recognizer = Recognizer.load(content.path, content.name, path.join(process.cwd(), `${content.name}.dialog`), settings.get('stdin'), {})
recognizers.set(content.name, recognizer)
} else {
this.log('Start to load lu files')
// get lu files from flags.in
if (flags.in && flags.in !== '') {
const luFiles = await file.getLuFiles(flags.in, true, fileExtEnum.LUFile)
files.push(...luFiles)
if (!flags.stdin && !flags.in && files.length === 0) {
throw new CLIError('Missing input. Please use stdin or pass a file or folder location with --in flag')
}
// load lu contents from lu files
// load existing recognizers, multiRecogniers and settings or create default ones
const loadedResources = await builder.loadContents(files, flags.defaultCulture, flags.suffix, flags.region)
luContents = loadedResources.luContents
recognizers = loadedResources.recognizers
multiRecognizers = loadedResources.multiRecognizers
settings = loadedResources.settings
}
if (!flags.botName) {
throw new CLIError('Missing bot name. Please pass bot name with --botName flag')
}
// update or create and then train and publish luis applications based on loaded resources
this.log('Start to handle applications')
const dialogContents = await builder.build(luContents, recognizers, flags.authoringKey, flags.region, flags.botName, flags.suffix, flags.fallbackLocale, flags.deleteOldVersion, multiRecognizers, settings)
flags.defaultCulture = flags.defaultCulture && flags.defaultCulture !== '' ? flags.defaultCulture : 'en-us'
flags.region = flags.region && flags.region !== '' ? flags.region : 'westus'
flags.suffix = flags.suffix && flags.suffix !== '' ? flags.suffix : await username() || 'development'
flags.fallbackLocale = flags.fallbackLocale && flags.fallbackLocale !== '' ? flags.fallbackLocale : 'en-us'
// write dialog assets based on config
if (flags.dialog) {
const writeDone = await builder.writeDialogAssets(dialogContents, flags.force, flags.out)
const dialogFilePath = (flags.stdin || !flags.in) ? process.cwd() : flags.in.endsWith(fileExtEnum.LUFile) ? path.dirname(path.resolve(flags.in)) : path.resolve(flags.in)
const outputFolder = flags.out ? path.resolve(flags.out) : dialogFilePath
if (writeDone) {
this.log(`Successfully wrote .dialog files to ${outputFolder}`)
// create builder class
const builder = new Builder((input: string) => {
if (flags.log) this.log(input)
})
let luContents: any[] = []
let recognizers = new Map<string, any>()
let multiRecognizers = new Map<string, any>()
let settings = new Map<string, any>()
if (flags.stdin && flags.stdin !== '') {
// load lu content from stdin and create default recognizer, multiRecognier and settings
if (flags.log) this.log('Load lu content from stdin\n')
const content = new Content(flags.stdin, new LUOptions('stdin', true, flags.defaultCulture, path.join(process.cwd(), 'stdin')))
luContents.push(content)
multiRecognizers.set('stdin', new MultiLanguageRecognizer(path.join(process.cwd(), 'stdin.lu.dialog'), {}))
settings.set('stdin', new Settings(path.join(process.cwd(), `luis.settings.${flags.suffix}.${flags.region}.json`), {}))
const recognizer = Recognizer.load(content.path, content.name, path.join(process.cwd(), `${content.name}.dialog`), settings.get('stdin'), {})
recognizers.set(content.name, recognizer)
} else {
this.log(`No changes to the .dialog files in ${outputFolder}`)
if (flags.log) this.log('Loading files...\n')
// get lu files from flags.in.
if (flags.in && flags.in !== '') {
const luFiles = await file.getLuFiles(flags.in, true, fileExtEnum.LUFile)
files.push(...luFiles)
}
// de-dupe the files list
files = [...new Set(files)]
// load lu contents from lu files
// load existing recognizers, multiRecogniers and settings or create default ones
const loadedResources = await builder.loadContents(files, flags.defaultCulture, flags.suffix, flags.region)
luContents = loadedResources.luContents
recognizers = loadedResources.recognizers
multiRecognizers = loadedResources.multiRecognizers
settings = loadedResources.settings
}
} else {
this.log('The published application ids:')
this.log(JSON.parse(dialogContents[dialogContents.length - 1].content).luis)
// update or create and then train and publish luis applications based on loaded resources
if (flags.log) this.log('Handling applications...')
const dialogContents = await builder.build(luContents, recognizers, flags.authoringKey, flags.region, flags.botName, flags.suffix, flags.fallbackLocale, flags.deleteOldVersion, multiRecognizers, settings)
// write dialog assets based on config
if (flags.dialog) {
const writeDone = await builder.writeDialogAssets(dialogContents, flags.force, flags.out, flags.luConfig)
const dialogFilePath = (flags.stdin || !flags.in) ? process.cwd() : flags.in.endsWith(fileExtEnum.LUFile) ? path.dirname(path.resolve(flags.in)) : path.resolve(flags.in)
const outputFolder = flags.out ? path.resolve(flags.out) : dialogFilePath
if (writeDone) {
this.log(`Successfully wrote .dialog files to ${outputFolder}\n`)
} else {
this.log(`No changes to the .dialog files in ${outputFolder}\n`)
}
} else {
this.log('The published application ids:')
this.log(JSON.parse(dialogContents[dialogContents.length - 1].content).luis)
}
} catch (error) {
if (error instanceof exception) {
throw new CLIError(error.text)
}
throw error
}
}
}

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

@ -52,6 +52,15 @@ describe('luis:build cli parameters test', () => {
.it('displays an error if files with same name and locale are found', ctx => {
expect(ctx.stderr).to.contain('Files with same name and locale are found')
})
test
.stdout()
.stderr()
.command(['luis:build', '--authoringKey', uuidv1(), '--in', `${path.join(__dirname, './../../fixtures/testcases/bad3.lu')}`, '--botName', 'Contoso'])
.it('displays an error if error occurs in parsing lu content', ctx => {
expect(ctx.stderr).to.contain('Invalid LU file')
expect(ctx.stderr).to.contain('[ERROR] line 4:0 - line 4:16: Invalid intent body line, did you miss \'-\' at line begin')
})
})
describe('luis:build create a new application successfully', () => {
@ -97,9 +106,9 @@ describe('luis:build create a new application successfully', () => {
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich//lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log'])
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich//lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log', '--suffix', 'development'])
.it('should create a new application successfully', ctx => {
expect(ctx.stdout).to.contain('Start to handle applications')
expect(ctx.stdout).to.contain('Handling applications...')
expect(ctx.stdout).to.contain('Creating LUIS.ai application')
expect(ctx.stdout).to.contain('training version=0.1')
expect(ctx.stdout).to.contain('waiting for training for version=0.1')
@ -162,9 +171,9 @@ describe('luis:build update application succeed when utterances changed', () =>
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich/lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log'])
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich/lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log', '--suffix', 'development'])
.it('should update a luis application when utterances changed', ctx => {
expect(ctx.stdout).to.contain('Start to handle applications')
expect(ctx.stdout).to.contain('Handling applications...')
expect(ctx.stdout).to.contain('creating version=0.2')
expect(ctx.stdout).to.contain('training version=0.2')
expect(ctx.stdout).to.contain('waiting for training for version=0.2')
@ -227,9 +236,9 @@ describe('luis:build update application succeed when utterances added', () => {
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich/lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log'])
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich/lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log', '--suffix', 'development'])
.it('should update a luis application when utterances added', ctx => {
expect(ctx.stdout).to.contain('Start to handle applications')
expect(ctx.stdout).to.contain('Handling applications...')
expect(ctx.stdout).to.contain('creating version=0.2')
expect(ctx.stdout).to.contain('training version=0.2')
expect(ctx.stdout).to.contain('waiting for training for version=0.2')
@ -263,9 +272,9 @@ describe('luis:build not update application if no changes', () => {
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich/lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log'])
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich/lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log', '--suffix', 'development'])
.it('should not update a luis application when there are no changes for the coming lu file', ctx => {
expect(ctx.stdout).to.contain('Start to handle applications')
expect(ctx.stdout).to.contain('Handling applications...')
expect(ctx.stdout).to.contain('no changes')
})
})
@ -301,7 +310,7 @@ describe('luis:build write dialog assets successfully if --dialog set', () => {
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich/lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--dialog', '--out', './results', '--log'])
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/sandwich/lufiles/sandwich.en-us.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--dialog', '--out', './results', '--log', '--suffix', 'development'])
.it('should write dialog assets successfully when --dialog set', async ctx => {
expect(await compareFiles('./../../../results/luis.settings.development.westus.json', './../../fixtures/testcases/lubuild/sandwich/config/luis.settings.development.westus.json')).to.be.true
expect(await compareFiles('./../../../results/sandwich.en-us.lu.dialog', './../../fixtures/testcases/lubuild/sandwich/dialogs/sandwich.en-us.lu.dialog')).to.be.true
@ -420,13 +429,13 @@ describe('luis:build create multiple applications successfully when input is a f
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/foo/lufiles', '--authoringKey', uuidv1(), '--botName', 'test', '--dialog', '--out', './results', '--log'])
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/foo/lufiles', '--authoringKey', uuidv1(), '--botName', 'test', '--dialog', '--out', './results', '--log', '--suffix', 'development'])
.it('should create multiple applications and write dialog assets successfully when input is a folder', async ctx => {
expect(ctx.stdout).to.contain('foo.fr-fr.lu loaded')
expect(ctx.stdout).to.contain('foo.lu loaded')
expect(ctx.stdout).to.contain('foo.zh-cn.lu loaded')
expect(ctx.stdout).to.contain('Start to handle applications')
expect(ctx.stdout).to.contain('Handling applications...')
expect(ctx.stdout).to.contain('Creating LUIS.ai application')
expect(ctx.stdout).to.contain('training version=0.1')
expect(ctx.stdout).to.contain('waiting for training for version=0.1')
@ -526,7 +535,7 @@ describe('luis:build update dialog assets successfully when dialog assets exist'
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/foo2/lufiles-and-dialog-assets', '--authoringKey', uuidv1(), '--botName', 'test', '--dialog', '--out', './results', '--log'])
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/foo2/lufiles-and-dialog-assets', '--authoringKey', uuidv1(), '--botName', 'test', '--dialog', '--out', './results', '--log', '--suffix', 'development'])
.it('should update dialog assets successfully when dialog assets exist', async ctx => {
expect(ctx.stdout).to.contain('foo.fr-fr.lu loaded')
expect(ctx.stdout).to.contain('foo.lu loaded')
@ -535,7 +544,7 @@ describe('luis:build update dialog assets successfully when dialog assets exist'
expect(ctx.stdout).to.contain('luis.settings.development.westus.json loaded')
expect(ctx.stdout).to.contain('foo.lu.dialog loaded')
expect(ctx.stdout).to.contain('Start to handle applications')
expect(ctx.stdout).to.contain('Handling applications...')
expect(ctx.stdout).to.contain('foo.en-us.lu.dialog loaded')
expect(ctx.stdout).to.contain('foo.fr-fr.lu.dialog loaded')
@ -555,4 +564,128 @@ describe('luis:build update dialog assets successfully when dialog assets exist'
expect(await compareFiles('./../../../results/foo.fr-fr.lu.dialog', './../../fixtures/testcases/lubuild/foo2/dialogs/foo.fr-fr.lu.dialog')).to.be.true
expect(await compareFiles('./../../../results/foo.zh-cn.lu.dialog', './../../fixtures/testcases/lubuild/foo2/dialogs/foo.zh-cn.lu.dialog')).to.be.true
})
})
describe('luis:build not update application if only cases of utterances or patterns are changed', () => {
const existingLuisApp = require('./../../fixtures/testcases/lubuild/case-insensitive/luis/test(development)-case-insensitive.en-us.lu.json')
before(function () {
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, [{
name: 'test(development)-case-insensitive.en-us.lu',
id: 'f8c64e2a-8635-3a09-8f78-39d7adc76ec5'
}])
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, {
name: 'test(development)-case-insensitive.en-us.lu',
id: 'f8c64e2a-8635-3a09-8f78-39d7adc76ec5',
activeVersion: '0.1'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('export'))
.reply(200, existingLuisApp)
})
test
.stdout()
.command(['luis:build', '--in', './test/fixtures/testcases/lubuild/case-insensitive/lufiles/case-insensitive.lu', '--authoringKey', uuidv1(), '--botName', 'test', '--log', '--suffix', 'development'])
.it('should not update a luis application when only cases of utterances or patterns are different for the coming lu file', ctx => {
expect(ctx.stdout).to.contain('Handling applications...')
expect(ctx.stdout).to.contain('no changes')
})
})
describe('luis:build update application succeed with parameters set from luconfig', () => {
const existingLuisApp = require('./../../fixtures/testcases/lubuild/luconfig/luis/MyProject(development)-test.en-us.lu.json')
before(async function () {
await fs.ensureDir(path.join(__dirname, './../../../results/'))
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, [{
name: 'MyProject(development)-test.en-us.lu',
id: 'f8c64e2a-8635-3a09-8f78-39d7adc76ec5'
}])
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, {
name: 'MyProject(development)-test.en-us.lu',
id: 'f8c64e2a-8635-3a09-8f78-39d7adc76ec5',
activeVersion: '0.1'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('export'))
.reply(200, existingLuisApp)
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('import'))
.reply(201, '0.2')
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('apps'))
.reply(200, [
{
version: '0.1'
},
{
version: '0.2'
}
])
nock('https://westus.api.cognitive.microsoft.com')
.delete(uri => uri.includes('apps'))
.reply(200)
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('train'))
.reply(202, {
statusId: 2,
status: 'UpToDate'
})
nock('https://westus.api.cognitive.microsoft.com')
.get(uri => uri.includes('train'))
.reply(200, [{
modelId: '99999',
details: {
statusId: 0,
status: 'Success',
exampleCount: 0
}
}])
nock('https://westus.api.cognitive.microsoft.com')
.post(uri => uri.includes('publish'))
.reply(201, {
versionId: '0.2',
isStaging: true
})
})
after(async function () {
await fs.remove(path.join(__dirname, './../../../results/'))
})
test
.stdout()
.command(['luis:build', '--authoringKey', uuidv1(), '--luConfig', './test/fixtures/testcases/lubuild/luconfig/lufiles/luconfig.json', '--log', '--suffix', 'development'])
.it('should update a luis application when utterances changed', async ctx => {
console.log(ctx)
expect(ctx.stdout).to.contain('Handling applications...')
expect(ctx.stdout).to.contain('creating version=0.2')
expect(ctx.stdout).to.contain('deleting old version=0.1')
expect(ctx.stdout).to.contain('training version=0.2')
expect(ctx.stdout).to.contain('waiting for training for version=0.2')
expect(ctx.stdout).to.contain('publishing version=0.2')
expect(ctx.stdout).to.contain('publishing finished')
expect(await compareFiles('./../../../results/luis.settings.development.westus.json', './../../fixtures/testcases/lubuild/luconfig/config/luis.settings.development.westus.json')).to.be.true
expect(await compareFiles('./../../../results/test.en-us.lu.dialog', './../../fixtures/testcases/lubuild/luconfig/dialogs/test.en-us.lu.dialog')).to.be.true
expect(await compareFiles('./../../../results/test.lu.dialog', './../../fixtures/testcases/lubuild/luconfig/dialogs/test.lu.dialog')).to.be.true
})
})

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

@ -0,0 +1,34 @@
# Greeting
- Hi Morning
- Hello Evening
# Help
- Help me with {Item}
- Please Help {Item}
# GetUserProfile
- I'm 36 years old
- My age is 36
- I am 36
> Prebuilt entities
@ prebuilt age
@ prebuilt personName
> Phrase list
@ phraselist profileDefinition(interchangeable) =
- I'm
- My
- I am
> Define a list entity for user city
@ list Cities =
- seattle :
- SEA
- Seatac
- redmond :
- microsoft
- REA
> Define a regex entity for user zip code
@ regex zipCode = /[0-9]{5}/

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

@ -0,0 +1,117 @@
{
"luis_schema_version": "4.0.0",
"versionId": "0.2",
"name": "test(development)-case-insensitive.en-us.lu",
"desc": "Model for test app, targetting development",
"culture": "en-us",
"tokenizerVersion": "1.0.0",
"intents": [
{
"name": "GetUserProfile"
},
{
"name": "Greeting"
},
{
"name": "Help"
},
{
"name": "None"
}
],
"entities": [],
"composites": [],
"closedLists": [
{
"name": "Cities",
"subLists": [
{
"canonicalForm": "seattle",
"list": [
"SEA",
"Seatac"
]
},
{
"canonicalForm": "redmond",
"list": [
"microsoft",
"REA"
]
}
],
"roles": []
}
],
"patternAnyEntities": [
{
"name": "Item",
"roles": [],
"explicitList": []
}
],
"regex_entities": [
{
"name": "zipCode",
"regexPattern": "[0-9]{5}",
"roles": []
}
],
"prebuiltEntities": [
{
"name": "age",
"roles": []
},
{
"name": "personName",
"roles": []
}
],
"model_features": [
{
"name": "profileDefinition",
"mode": true,
"words": "I'm,My,I am",
"activated": true
}
],
"regex_features": [],
"patterns": [
{
"pattern": "please help {Item}",
"intent": "Help"
},
{
"pattern": "help me with {Item}",
"intent": "Help"
}
],
"utterances": [
{
"text": "hello evening",
"intent": "Greeting",
"entities": []
},
{
"text": "hi morning",
"intent": "Greeting",
"entities": []
},
{
"text": "i am 36",
"intent": "GetUserProfile",
"entities": []
},
{
"text": "i'm 36 years old",
"intent": "GetUserProfile",
"entities": []
},
{
"text": "my age is 36",
"intent": "GetUserProfile",
"entities": []
}
],
"settings": []
}

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

@ -0,0 +1,5 @@
{
"luis": {
"test_en-us_lu": "f8c64e2a-8635-3a09-8f78-39d7adc76ec5"
}
}

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

@ -0,0 +1,6 @@
{
"$type": "Microsoft.LuisRecognizer",
"applicationId": "=settings.luis.test_en-us_lu",
"endpoint": "=settings.luis.endpoint",
"endpointKey": "=settings.luis.endpointKey"
}

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

@ -0,0 +1,7 @@
{
"$type": "Microsoft.MultiLanguageRecognizer",
"recognizers": {
"en-us": "test.en-us.lu",
"": "test.en-us.lu"
}
}

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

@ -0,0 +1,10 @@
{
"name": "MyProject",
"defaultLanguage": "en-us",
"deleteOldVersion": true,
"out": "./../../../../../../results",
"writeDialogFiles": true,
"models": [
"./test.lu"
]
}

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

@ -0,0 +1,206 @@
> Entity definitions
$ itemTitle : simple
> Add a Phrase List with todo variations. Mark them as interchangeable.
$ todoItem : PhraseList interchangeable
- todo
- todos
- to dos
- todo list
- todos list
- item list
- items collection
> Add a list entity to detect list tye
$ listType : todo =
- to do
- todos
- laundry
$ listType : shopping =
- shopping
- shop
- shoppers
$ listType : grocery =
- groceries
- fruits
- vegetables
- household items
- house hold items
> Help intent and related utterances.
# Help
- What can I say?
- Who are you?
- I need help
- Not sure what I can do
- What do you want me to say?
- What can you do?
- What can you help with?
- help please
- What are my options?
- well, I do not know what my todo's title is
- I do not know
- can you please help?
> Greeting intent and related utterances
# Greeting
- hi
- hello
- hiya
- how are you?
- how do you do?
> Cancel intent and related utterances
# Cancel
- cancel
- cancel all
- stop that
- do not do it
- abort
- please stop what you are doing
- I changed my mind
- cancel add todo
- cancel that
- I do not want to add a todo
- No todo for me
- Cancel this
- cancel delete todo
- Let's just leave it as is
- I do not wish to delete my todo anymore
- Keep my todos as is
- No todo for me
> Add item
# AddItem
- Add todo
- add a to do item
- Please remind me to {itemTitle=buy milk}
- Please remember that I need to {itemTitle=buy milk}
- I need you to remember that {itemTitle=my wife's birthday is Jan 9th}
- Add a todo named {itemTitle=send report over this weekend}
- Add {itemTitle=get a new car} to the todo list
- Add {itemTitle=write a spec} to the list
- Add {itemTitle=finish this demo} to my todo list
- add a todo item {itemTitle=vacuuming by october 3rd}
- add {itemTitle=call my mother} to my todo list
- add {itemTitle=due date august to peanut butter jelly bread milk} on todos list
- add {itemTitle=go running} to my todos
- add to my todos list {itemTitle=mail the insurance forms out by saturday}
- can i add {itemTitle=shirts} on the todos list
- could i add {itemTitle=medicine} to the todos list
- would you add {itemTitle=heavy cream} to the todos list
- add {itemTitle} to my todo list
- add a to do that {itemTitle=purchase a nice sweater}
- add a to do to {itemTitle=buy shoes}
- add an task of {itemTitle=chores to do around the house}
- add {itemTitle=go to whole foods} in my to do list
- add {itemTitle=reading} to my to do list
- add this thing in to do list
- add to my to do list {itemTitle=pick up clothes}
- add to my to do list {itemTitle=print papers for 10 copies this afternoon}
- create to do
- create to do that {itemTitle=read a book tonight}
- create to do to {itemTitle=go running in the park}
- put {itemTitle=hikes} on my to do list
- remind me to {itemTitle = pick up dry cleaning}
- new to do
- add another one
- add
> Add patterns
- Please remember [to] {itemTitle}
- I need you to remember [that] {itemTitle}
- Add a todo named {itemTitle}
- Add {itemTitle} to the list
- [Please] add {itemTitle} to the todo list
- Add {itemTitle} to my todo
- add {itemTitle} to my to do list
- add {itemTitle} to my to dos
- add a to do that buy {itemTitle}
- add a to do that purchase {itemTitle}
- add a to do that shop {itemTitle}
- add a to do to {itemTitle}
- add to do that {itemTitle}
- add to do to {itemTitle}
- create a to do to {itemTitle}
- create to do to {itemTitle}
- remind me to {itemTitle}
# DeleteItem
- Remove todo
- Mark {itemTitle = buy milk} as complete
- Flag {itemTitle = first one} as done
- Remove {itemTitle = finish this demo} from the todo list
- remove another one
- remove
- clear my todo named {itemTitle = get a new car}
- I'm done with the first todo
- I finished the las todo
- Remove the first todo
- Delete todo
- Clear my todos
- Delete all my todos
- Remove all my todo
- Forget the list
- Purge the todo list
- can you delete {itemTitle=todo1}
- can you delete {itemTitle=xxx} item
- delete {itemTitle=eggs} from list
- delete off {itemTitle=pancake mix} on the shopping list
- delete {itemTitle=shampoo} from shopping list
- delete {itemTitle=shirts} from list
- delete task {itemTitle=go fishing}
- delete task {itemTitle=go to cinema tonight}
- delete the item {itemTitle=buy socks} from my todo list
- delete the second task in my shopping list
- delete the task {itemTitle=house cleanup this weekend}
- delete the task that {itemTitle=hit the gym every morning}
- delete the to do {itemTitle=meet my friends tomorrow}
- delete the to do that {itemTitle=daily practice piano}
- delete the to do that {itemTitle=meet john when he come here the next friday}
- delete to do {itemTitle=buy milk}
- delete to do {itemTitle=go shopping}
- delete to do that {itemTitle=go hiking tomorrow}
- erase {itemTitle=bananas} from shopping list
- erase {itemTitle=peanuts} on the shopping list
- remove {itemTitle=asprin} from shopping list
- remove {itemTitle=black shoes} from shopping list
- remove {itemTitle=class} from todo list
- remove {itemTitle=salad vegetables} from grocery list
- remove task {itemTitle=buy dog food}
- remove task {itemTitle=go shopping}
- remove task that {itemTitle=go hiking this weekend}
- remove task that {itemTitle=lawn mowing}
- remove the item {itemTitle=paris} from my list
- remove the task that {itemTitle=go to library after work}
- remove the to do {itemTitle=physical examination}
- remove the to do that {itemTitle=pick tom up at six p.m.}
- remove to do {itemTitle=go to the gym}
- remove to do that {itemTitle=go to the dentist tomorrow morning}
> Add patterns
- I did {itemTitle}
- I completed {itemTitle}
- Delete {itemTitle}
- Mark {itemTitle} as complete
- Remove {itemTitle} from my [todo] list
- [Please] delete {itemTitle} from the list
# ViewCollection
- show my todo
- can you please show my todos?
- please show my todo list
- todo list please
- I need to see my todo list
- can you show me the list?
- please show the list
- what do i have on my todo?
- what is on my list?
- do i have anything left on my todo list?
- I hope I do not have any todo left
- do i have any tasks left?
- hit me up with more items
- view my todos
- can you show my todo
- see todo
- I would like to see my todos list

Разница между файлами не показана из-за своего большого размера Загрузить разницу