Skip to content
This repository was archived by the owner on Jan 15, 2025. It is now read-only.

Commit 9aa4869

Browse files
Bf-LU test cleanup and other fixes (#638)
* Adding tests and API updates * test update * Moving tests to right home * fix up lubuild * Fix for PR feedback * Fixing tests Co-authored-by: Emilio Munoz <[email protected]>
1 parent ba9c702 commit 9aa4869

File tree

14 files changed

+376
-1042
lines changed

14 files changed

+376
-1042
lines changed

packages/lu/README.md

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,10 @@ To parse LU files, you can use the LUISBuilder class, which returns a LUIS class
160160
```js
161161
const Luis = require('@microsoft/bf-lu').V2.Luis
162162
const LUISBuilder = require('@microsoft/bf-lu').V2.LuisBuilder
163-
const luContent1 = `# Greeting
163+
const luContent = `# Greeting
164164
- hi`;
165165

166-
const luisObject = await LUISBuilder.buildFromLUContent(luContent1)
166+
const luisObject = await LUISBuilder.fromContentAsync(luContent)
167167

168168
// Parsed LUIS object
169169
console.log(JSON.stringify(luisObject, 2, null));
@@ -178,24 +178,11 @@ You can use the available validate() function to verify if the parsed LUIS objec
178178
const LUISBuilder = require('@microsoft/bf-lu').V2.LuisBuilder
179179
const exception = require('@microsoft/bf-lu').V2.Exception
180180
const luContent = `# Greeting
181-
- hi {userName=bob}
182-
$userName:first=
183-
-vishwac`;
184-
185-
async function parseContent() {
186-
try {
187-
const luisObject = await LUISBuilder.buildFromLUContent(luContent)
188-
luisObject.validate()
189-
} catch (error) {
190-
if (error instanceof exception) {
191-
// do something specific to this exception
192-
} else {
193-
console.log(errObj.text);
194-
}
195-
}
196-
}
181+
- hi`;
197182

198-
parseContent();
183+
const luisObject = await LUISBuilder.fromLUAsync(luContent)
184+
luisObject.intents[0].name = "testIntent123456789012345678901234567890123"
185+
luisObject.validate()
199186
```
200187

201188
## Generating lu content from LUIS JSON
@@ -214,7 +201,7 @@ const locale = 'en-us';
214201
async function parseContent() {
215202

216203
try {
217-
const luisObject = await LUISBuilder.buildFromLUContent(luContent)
204+
const luisObject = await LUISBuilder.fromContentAsync(luContent)
218205
luisObject.validate()
219206
const parsedLuisBackToLu = luisObject.parseToLuContent()
220207
} catch (error) {

packages/lu/src/parser/lubuild/builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export class Builder {
250250
}
251251

252252
async initApplicationFromLuContent(content: any, botName: string, suffix: string) {
253-
let currentApp = await LuisBuilder.fromLU([content]) // content.parseToLuis(true, content.language)
253+
let currentApp = await LuisBuilder.fromLUAsync([content]) // content.parseToLuis(true, content.language)
254254
currentApp.culture = currentApp.culture && currentApp.culture !== '' && currentApp.culture !== 'en-us' ? currentApp.culture : content.language as string
255255
currentApp.desc = currentApp.desc && currentApp.desc !== '' ? currentApp.desc : `Model for ${botName} app, targetting ${suffix}`
256256

packages/lu/src/parser/luis/luisBuilder.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
const Luis = require('./luis')
77
const parseFileContents = require('./../lufile/parseFileContents').parseFile
88
const build = require('./luisCollate').build
9-
9+
const LU = require('./../lu/lu')
1010
class LuisBuilder {
1111

1212
/**
@@ -15,7 +15,7 @@ class LuisBuilder {
1515
* @returns {Luis} new Luis instance
1616
* @throws {exception} Throws on errors. exception object includes errCode and text.
1717
*/
18-
static async fromJson(luisJson) {
18+
static fromJson(luisJson) {
1919
return new Luis(luisJson)
2020
}
2121

@@ -25,7 +25,7 @@ class LuisBuilder {
2525
* @returns {Luis} new Luis instance
2626
* @throws {exception} Throws on errors. exception object includes errCode and text.
2727
*/
28-
static async fromContent(luContent) {
28+
static async fromContentAsync(luContent) {
2929
return await parseAndValidateLuFile(luContent)
3030
}
3131

@@ -36,16 +36,16 @@ class LuisBuilder {
3636
* @returns {Luis} new Luis instance
3737
* @throws {exception} Throws on errors. exception object includes errCode and text.
3838
*/
39-
static async fromLU(luArray, luSearchFn) {
39+
static async fromLUAsync(luArray, luSearchFn) {
4040
if(!Array.isArray(luArray)){
41-
return new Luis()
42-
}
43-
44-
if(luArray.length === 1){
45-
return await parseAndValidateLuFile(luArray[0].content, false, luArray[0].language)
41+
if (luArray instanceof LU)
42+
luArray = new Array(luArray)
43+
else
44+
luArray = new Array(new LU(luArray))
4645
}
47-
48-
return build(luArray, false, '', luSearchFn)
46+
let parsedContent = await build(luArray, false, '', luSearchFn)
47+
parsedContent.validate()
48+
return parsedContent
4949
}
5050

5151
}

packages/lu/src/parser/utils/helpers.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,18 +170,26 @@ const helpers = {
170170
(finalLUISJSON.luis_schema_version === '6.0.0');
171171
if (v5DefFound) {
172172
finalLUISJSON.luis_schema_version = "6.0.0";
173-
if (finalLUISJSON.model_features) {
174-
finalLUISJSON.phraselists = finalLUISJSON.phraselists || [];
175-
finalLUISJSON.model_features.forEach(item => {
176-
if (item.enabledForAllModels === undefined) item.enabledForAllModels = true
177-
finalLUISJSON.phraselists.push(Object.assign({}, item))
178-
});
173+
if (finalLUISJSON.hasOwnProperty("model_features")) {
174+
if (finalLUISJSON.model_features !== undefined) {
175+
finalLUISJSON.phraselists = finalLUISJSON.phraselists || [];
176+
finalLUISJSON.model_features.forEach(item => {
177+
if (item.enabledForAllModels === undefined) item.enabledForAllModels = true
178+
finalLUISJSON.phraselists.push(Object.assign({}, item))
179+
});
180+
}
179181
delete finalLUISJSON.model_features;
180182
}
181183
(finalLUISJSON.composites || []).forEach(composite => {
182184
let children = composite.children;
183185
composite.children = [];
184-
children.forEach(c => composite.children.push({name : c}));
186+
children.forEach(c => {
187+
if (c.name === undefined) {
188+
composite.children.push({name : c})
189+
} else {
190+
composite.children.push(c)
191+
}
192+
});
185193
})
186194
}
187195
}
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
const fs = require('fs-extra')
2+
const path = require('path')
3+
const NEWLINE = require('os').EOL
4+
var chai = require('chai');
5+
var assert = chai.assert;
6+
const LuisBuilder = require('./../../../src/parser/luis/luisBuilder')
7+
8+
const loadLuFile = async function(filePath) {
9+
let fileContent = await fs.readFile(path.join(__dirname, filePath))
10+
return sanitize(fileContent)
11+
}
12+
13+
const sanitize = function(content) {
14+
return content.toString().replace(/\r\n/g, "\n")
15+
}
16+
17+
const loadJsonFile = async function(filePath) {
18+
let result = await fs.readJson(path.join(__dirname, filePath))
19+
result = sanitizeExampleJson(JSON.stringify(result))
20+
return JSON.parse(result)
21+
}
22+
23+
function sanitizeExampleJson(fileContent) {
24+
let escapedExampleNewLine = JSON.stringify('\r\n').replace(/"/g, '').replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
25+
let escapedNewLine = JSON.stringify(NEWLINE).replace(/"/g, '');
26+
return fileContent.replace(new RegExp(escapedExampleNewLine, 'g'), escapedNewLine);
27+
}
28+
29+
const assertToLu = async function(srcJSONFile, tgtLUFile) {
30+
let testInputFile = await loadJsonFile(srcJSONFile)
31+
let testInputJSON = LuisBuilder.fromJson(testInputFile)
32+
let testContent = testInputJSON.parseToLuContent();
33+
let verifiedContent = await loadLuFile(tgtLUFile)
34+
assert.deepEqual(sanitize(testContent), verifiedContent)
35+
}
36+
37+
const assertToJSON = async function(srcLUFile, tgtJSONFile, name = undefined) {
38+
let testInputFile = await loadLuFile(srcLUFile)
39+
let testContent = await LuisBuilder.fromLUAsync(testInputFile)
40+
if (name !== undefined) testContent.name = name
41+
let verifiedContent = await loadJsonFile(tgtJSONFile)
42+
assert.deepEqual(testContent, verifiedContent)
43+
}
44+
45+
describe('luis:convert', () => {
46+
it('luis:convert successfully reconstructs a markdown file from a LUIS input file with out of order entity references', async () => {
47+
await assertToLu('./../../fixtures/testcases/test269-d.json', './../../fixtures/verified/test269-d.lu')
48+
})
49+
50+
it('luis:convert successfully reconstructs a markdown file from a LUIS input file', async () => {
51+
await assertToLu('./../../fixtures/verified/all.json', './../../fixtures/verified/allGen.lu')
52+
})
53+
54+
it('luis:convert successfully reconstructs a markdown file from a LUIS input file (phrase list as feature)', async () => {
55+
await assertToLu('./../../fixtures/verified/plFeatures.json', './../../fixtures/verified/plFeatures.lu')
56+
})
57+
58+
it('luis:convert successfully reconstructs a markdown file from a LUIS input file (model as features)', async () => {
59+
await assertToLu('./../../fixtures/verified/modelAsFeatures.json', './../../fixtures/verified/modelAsFeatureGen.lu')
60+
})
61+
it('luis:convert successfully reconstructs a markdown file from a LUIS input file (with nDepth entity definitions in utterances)', async () => {
62+
await assertToLu('./../../fixtures/verified/nDepthEntityInUtterance.json', './../../fixtures/verified/nDepthEntityInUtterance.lu')
63+
})
64+
65+
it('luis:convert Simple intent and utterances are parsed correctly', async () => {
66+
await assertToJSON('./../../fixtures/examples/1.lu', './../../fixtures/verified/1.json', '1')
67+
})
68+
69+
it('luis:convert Multiple intent and utterance definition sections are parsed correctly', async () => {
70+
await assertToJSON('./../../fixtures/examples/3.lu', './../../fixtures/verified/3.json', '3')
71+
})
72+
73+
it('luis:convert Uttearnces with labelled values are parsed correctly', async () => {
74+
await assertToJSON('./../../fixtures/examples/4.lu', './../../fixtures/verified/4.json', '4')
75+
})
76+
77+
it('luis:convert Simple entity declaration is parsed correctly', async () => {
78+
await assertToJSON('./../../fixtures/examples/5.lu', './../../fixtures/verified/5.json', '5')
79+
})
80+
81+
it('luis:convert Prebuilt entity types are parsed correctly', async () => {
82+
await assertToJSON('./../../fixtures/examples/6.lu', './../../fixtures/verified/6.json', '6')
83+
})
84+
it('luis:convert Pattern.any entity types are parsed correctly', async () => {
85+
await assertToJSON('./../../fixtures/examples/7.lu', './../../fixtures/verified/7.json', '7')
86+
})
87+
88+
it('luis:convert List entity types are parsed correctly', async () => {
89+
await assertToJSON('./../../fixtures/examples/9.lu', './../../fixtures/verified/9.json', '9')
90+
})
91+
92+
it('luis:convert list entity definitions and intent definitions can be split up and will be parsed correctly', async () => {
93+
await assertToJSON('./../../fixtures/examples/9a.lu', './../../fixtures/verified/9a.json', '9a')
94+
})
95+
96+
it('Parse to LU instance', async () => {
97+
let luFile = `
98+
@ ml test
99+
# test
100+
- this is a {@test = one}
101+
`;
102+
103+
let result = `
104+
> LUIS application information
105+
> !# @app.versionId = 0.1
106+
> !# @app.culture = en-us
107+
> !# @app.luis_schema_version = 3.2.0
108+
109+
110+
> # Intent definitions
111+
112+
## test
113+
- this is a {@test=one}
114+
115+
116+
> # Entity definitions
117+
118+
@ ml test
119+
120+
121+
> # PREBUILT Entity definitions
122+
123+
124+
> # Phrase list definitions
125+
126+
127+
> # List entities
128+
129+
> # RegEx entities
130+
131+
132+
`
133+
const luisObject = await LuisBuilder.fromLUAsync(luFile)
134+
const newLU = luisObject.parseToLU()
135+
assert.equal(sanitize(newLU.content), result);
136+
});
137+
})
138+
describe('luis:convert version 5 upgrade test', () => {
139+
it('luis:convert successfully reconstructs a markdown file from a LUIS input file with v5 constructs', async () => {
140+
await assertToJSON('./../../fixtures/verified/v5UpgradeTest.lu', './../../fixtures/verified/v5Upgrade.json')
141+
})
142+
143+
it('luis:convert successfully converts LU with ndepth entity and features to LUIS JSON model', async () => {
144+
await assertToJSON('./../../fixtures/examples/newEntityWithFeatures.lu', './../../fixtures/verified/newEntityWithFeatures.json')
145+
})
146+
147+
it('luis:convert successfully converts LUIS JSON model with nDepth entity and features to LU', async () => {
148+
await assertToLu('./../../fixtures/verified/newEntityWithFeatures.json', './../../fixtures/verified/newEntityWithFeatures.lu')
149+
})
150+
151+
it('luis:convert successfully converts LUIS JSON model with empty intent feature descriptors', async () => {
152+
await assertToLu('./../../fixtures/testcases/emptyIntentDescriptors.json', './../../fixtures/verified/emptyIntentDescriptors.lu')
153+
})
154+
155+
it('luis:convert successfully converts LUIS JSON model with no phrase lists (output must have phraselists if any v6 concepts are present in the .lu file)', async () => {
156+
await assertToJSON('./../../fixtures/testcases/v6WithoutPhraseLists.lu', './../../fixtures/verified/v6WithoutPhraseLists.json')
157+
})
158+
159+
it('luis:convert successfully converts LUIS JSON model with no phrase lists (output must have phraselists if any v6 concepts are present in the .lu file)', async () => {
160+
await assertToJSON('./../../fixtures/testcases/plWithFlags.lu', './../../fixtures/verified/plWithFlags.json')
161+
})
162+
})
163+
164+
describe('luis:convert negative tests', () => {
165+
it('luis:convert should show ERR message when no utterances are found for an intent', (done) => {
166+
loadLuFile('./../../fixtures/testcases/bad3.lu')
167+
.then(res => {
168+
LuisBuilder.fromLUAsync(res)
169+
.then(res => done(res))
170+
.catch(err => {
171+
assert.isTrue(err.text.includes(`[ERROR] line 4:0 - line 4:16: Invalid intent body line, did you miss \'-\' at line begin`))
172+
done()
173+
})
174+
})
175+
176+
})
177+
178+
it('luis:convert should show ERR message when no labelled value is found for an entity', (done) => {
179+
loadLuFile('./../../fixtures/testcases/bad3a.lu')
180+
.then(res => {
181+
LuisBuilder.fromLUAsync(res)
182+
.then(res => done(res))
183+
.catch(err => {
184+
assert.isTrue(err.text.includes(`[ERROR] line 4:0 - line 4:19: No labelled value found for entity: "tomato" in utterance: "- i wangt {tomato=}"`))
185+
done()
186+
})
187+
})
188+
189+
})
190+
191+
it('luis:convert should show ERR message when no labelled value is found for an entity', (done) => {
192+
loadLuFile('./../../fixtures/testcases/bad2.lu')
193+
.then(res => {
194+
LuisBuilder.fromLUAsync(res)
195+
.then(res => done(res))
196+
.catch(err => {
197+
assert.isTrue(err.text.includes(`[ERROR] line 1:0 - line 1:1: syntax error: invalid input 'f' detected.`))
198+
done()
199+
})
200+
})
201+
202+
})
203+
})
204+
205+
describe('luis:convert new entity format', () => {
206+
it('luis:convert with new entity format correctly produces a LU file', async () => {
207+
assertToLu('./../../fixtures/testcases/newEntity1.json', './../../fixtures/verified/newEntity1.lu')
208+
})
209+
210+
it('luis:convert with new entity format and single roles correctly produces a LU file', async () => {
211+
assertToLu('./../../fixtures/testcases/newEntity1.json', './../../fixtures/verified/newEntity1.lu')
212+
})
213+
})
214+
215+
describe('luis:convert with pattern.any inherits information', () => {
216+
it('luis:convert with pattern.any inherits information correctly produces a LUIS app', async () => {
217+
assertToJSON('./../../fixtures/verified/LUISAppWithPAInherits.lu', './../../fixtures/verified/LUISAppWithPAInherits.lu.json')
218+
})
219+
220+
it('luis:convert with pattern.any inherits information correctly produces a LUIS app', async () => {
221+
assertToLu('./../../fixtures/testcases/LUISAppWithPAInherits.json', './../../fixtures/verified/LUISAppWithPAInherits.lu')
222+
})
223+
})
224+

0 commit comments

Comments
 (0)