@tiledesk/tiledesk-server 2.29.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -1
- package/app.js +1 -1
- package/config/native_mcp/native_mcp_servers.json +7 -0
- package/controllers/chatbot.controller.js +63 -3
- package/models/faq_kb.js +35 -0
- package/package.json +1 -1
- package/pubmodules/aiGenerations/README.md +154 -0
- package/pubmodules/aiGenerations/index.js +7 -0
- package/pubmodules/aiGenerations/listeners.js +96 -0
- package/pubmodules/aiGenerations/models/faqKbRevision.js +51 -0
- package/pubmodules/aiGenerations/routes/aiGenerations.js +426 -0
- package/pubmodules/aiGenerations/services/agentWriter.js +286 -0
- package/pubmodules/aiGenerations/services/atomic.js +70 -0
- package/pubmodules/aiGenerations/services/references.js +78 -0
- package/pubmodules/aiGenerations/services/revisionService.js +169 -0
- package/pubmodules/aiGenerations/services/snapshot.js +109 -0
- package/pubmodules/aiGenerations/test/aiGenerationsRoute.js +503 -0
- package/pubmodules/pubModulesManager.js +17 -0
- package/routes/faq.js +10 -3
- package/routes/faq_kb.js +86 -1
- package/services/faqService.js +36 -8
- package/services/v3FlowService.js +416 -0
- package/test/faqRoute.js +83 -0
- package/test/faqkbV3Route.js +436 -0
- package/test/nativemcp.test.js +6 -1
- package/test/v3FlowService.js +187 -0
package/test/faqRoute.js
CHANGED
|
@@ -356,6 +356,89 @@ describe('FaqKBRoute', () => {
|
|
|
356
356
|
}).timeout(20000);
|
|
357
357
|
|
|
358
358
|
|
|
359
|
+
it('Get faqs without the bot in every row (populate=false)', (done) => {
|
|
360
|
+
|
|
361
|
+
var email = "test-signup-" + Date.now() + "@email.com";
|
|
362
|
+
var pwd = "pwd";
|
|
363
|
+
|
|
364
|
+
userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
|
|
365
|
+
projectService.create("test-faqkb-create", savedUser._id).then(function (savedProject) {
|
|
366
|
+
chai.request(server)
|
|
367
|
+
.post('/' + savedProject._id + '/faq_kb')
|
|
368
|
+
.auth(email, pwd)
|
|
369
|
+
.send({ "name": "testbot", type: "tilebot", language: 'en', template: 'blank' })
|
|
370
|
+
.end((err, res) => {
|
|
371
|
+
|
|
372
|
+
if (err) { console.error("err: ", err); }
|
|
373
|
+
if (log) { console.log("res.body", res.body); }
|
|
374
|
+
|
|
375
|
+
res.should.have.status(200);
|
|
376
|
+
let id_faq_kb = res.body._id;
|
|
377
|
+
|
|
378
|
+
// as always: every block carries the bot
|
|
379
|
+
chai.request(server)
|
|
380
|
+
.get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)
|
|
381
|
+
.auth(email, pwd)
|
|
382
|
+
.end((err, res) => {
|
|
383
|
+
|
|
384
|
+
if (err) { console.error("err: ", err); }
|
|
385
|
+
if (log) { console.log("res.body", res.body); }
|
|
386
|
+
|
|
387
|
+
res.should.have.status(200);
|
|
388
|
+
res.body.should.be.a('array');
|
|
389
|
+
expect(res.body.length).to.be.above(0);
|
|
390
|
+
const blocks = res.body.length;
|
|
391
|
+
res.body.forEach((faq) => {
|
|
392
|
+
expect(faq.faq_kb).to.be.a('array');
|
|
393
|
+
expect(faq.faq_kb[0]._id).to.equal(id_faq_kb);
|
|
394
|
+
expect(faq.faq_kb[0].name).to.equal("testbot");
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// populate=false: the same blocks, without the bot
|
|
398
|
+
chai.request(server)
|
|
399
|
+
.get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb + '&populate=false')
|
|
400
|
+
.auth(email, pwd)
|
|
401
|
+
.end((err, res) => {
|
|
402
|
+
|
|
403
|
+
if (err) { console.error("err: ", err); }
|
|
404
|
+
if (log) { console.log("res.body", res.body); }
|
|
405
|
+
|
|
406
|
+
res.should.have.status(200);
|
|
407
|
+
res.body.should.be.a('array');
|
|
408
|
+
expect(res.body.length).to.equal(blocks);
|
|
409
|
+
res.body.forEach((faq) => {
|
|
410
|
+
expect(faq.faq_kb).to.equal(undefined);
|
|
411
|
+
expect(faq.id_faq_kb).to.equal(id_faq_kb);
|
|
412
|
+
expect(faq.intent_display_name).to.be.a('string');
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// any other value: as always
|
|
416
|
+
chai.request(server)
|
|
417
|
+
.get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb + '&populate=true')
|
|
418
|
+
.auth(email, pwd)
|
|
419
|
+
.end((err, res) => {
|
|
420
|
+
|
|
421
|
+
if (err) { console.error("err: ", err); }
|
|
422
|
+
if (log) { console.log("res.body", res.body); }
|
|
423
|
+
|
|
424
|
+
res.should.have.status(200);
|
|
425
|
+
expect(res.body.length).to.equal(blocks);
|
|
426
|
+
res.body.forEach((faq) => {
|
|
427
|
+
expect(faq.faq_kb).to.be.a('array');
|
|
428
|
+
expect(faq.faq_kb[0]._id).to.equal(id_faq_kb);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
done();
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
}).timeout(20000);
|
|
440
|
+
|
|
441
|
+
|
|
359
442
|
it('searchFaqs', (done) => {
|
|
360
443
|
|
|
361
444
|
var email = "test-signup-" + Date.now() + "@email.com";
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
//During the test the env variable is set to test
|
|
2
|
+
process.env.NODE_ENV = 'test';
|
|
3
|
+
process.env.LOG_LEVEL = 'error';
|
|
4
|
+
|
|
5
|
+
const Faq_kb = require('../models/faq_kb');
|
|
6
|
+
const projectService = require('../services/projectService');
|
|
7
|
+
const userService = require('../services/userService');
|
|
8
|
+
|
|
9
|
+
let chai = require('chai');
|
|
10
|
+
let chaiHttp = require('chai-http');
|
|
11
|
+
let server = require('../app');
|
|
12
|
+
let should = chai.should();
|
|
13
|
+
const expect = chai.expect;
|
|
14
|
+
|
|
15
|
+
chai.use(chaiHttp);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* V3 agents on the faq_kb routes (15/10/2026): the version label `ds_version` and the connectors
|
|
19
|
+
* list `connectors` are fields of the bot. A V3 agent is born in V3 form from its template, keeps
|
|
20
|
+
* both fields through export, import, fork and restore, and saves its list on a dedicated endpoint.
|
|
21
|
+
* Every test also checks that an agent created without the label (a V2 agent) sees no difference.
|
|
22
|
+
*/
|
|
23
|
+
describe('FaqKBRoute V3 fields', () => {
|
|
24
|
+
|
|
25
|
+
async function signupAndProject(prefix) {
|
|
26
|
+
const email = "test-signup-" + prefix + "-" + Date.now() + "@email.com";
|
|
27
|
+
const pwd = "pwd";
|
|
28
|
+
const savedUser = await userService.signup(email, pwd, "Test Firstname", "Test lastname");
|
|
29
|
+
const savedProject = await projectService.create("test-faqkb-v3", savedUser._id);
|
|
30
|
+
return { email, pwd, project: savedProject };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createBot(ctx, body) {
|
|
34
|
+
return chai.request(server)
|
|
35
|
+
.post('/' + ctx.project._id + '/faq_kb')
|
|
36
|
+
.auth(ctx.email, ctx.pwd)
|
|
37
|
+
.send(body);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getBot(ctx, id) {
|
|
41
|
+
return chai.request(server).get('/' + ctx.project._id + '/faq_kb/' + id).auth(ctx.email, ctx.pwd);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getConnectors(ctx, id) {
|
|
45
|
+
return chai.request(server).get('/' + ctx.project._id + '/faq_kb/' + id + '/connectors').auth(ctx.email, ctx.pwd);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function putConnectors(ctx, id, collection, ifMatch) {
|
|
49
|
+
let req = chai.request(server).put('/' + ctx.project._id + '/faq_kb/' + id + '/connectors').auth(ctx.email, ctx.pwd);
|
|
50
|
+
if (ifMatch) { req = req.set('If-Match', ifMatch); }
|
|
51
|
+
return req.send(collection);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The template's blocks are saved right after the bot, without being awaited: wait for them. */
|
|
55
|
+
async function getIntents(ctx, id, expectedAtLeast) {
|
|
56
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
57
|
+
const res = await chai.request(server).get('/' + ctx.project._id + '/faq?id_faq_kb=' + id).auth(ctx.email, ctx.pwd);
|
|
58
|
+
res.should.have.status(200);
|
|
59
|
+
if (res.body.length >= (expectedAtLeast || 1)) { return res.body; }
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
61
|
+
}
|
|
62
|
+
throw new Error("intents of " + id + " not saved in time");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function destinationKeysOf(intent) {
|
|
66
|
+
const keys = [];
|
|
67
|
+
const next = intent.attributes && intent.attributes.nextBlockAction;
|
|
68
|
+
if (next && Object.prototype.hasOwnProperty.call(next, 'intentName')) { keys.push('attributes.nextBlockAction.intentName'); }
|
|
69
|
+
(intent.actions || []).forEach((action, i) => {
|
|
70
|
+
['intentName', 'trueIntent', 'falseIntent', 'goToIntent', 'elseIntent', 'fallbackIntent', 'errorIntent'].forEach((field) => {
|
|
71
|
+
if (Object.prototype.hasOwnProperty.call(action, field)) { keys.push('actions[' + i + '].' + field); }
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
return keys;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
describe('Create', () => {
|
|
78
|
+
|
|
79
|
+
it('create-v3-agent-from-blank-template-in-v3-form', async () => {
|
|
80
|
+
const ctx = await signupAndProject("v3-blank");
|
|
81
|
+
|
|
82
|
+
const created = await createBot(ctx, { name: "v3bot", type: "tilebot", language: "en", template: "blank", ds_version: "v3" });
|
|
83
|
+
created.should.have.status(200);
|
|
84
|
+
expect(created.body.ds_version).to.equal("v3");
|
|
85
|
+
const id = created.body._id;
|
|
86
|
+
|
|
87
|
+
const bot = await getBot(ctx, id);
|
|
88
|
+
bot.should.have.status(200);
|
|
89
|
+
expect(bot.body.ds_version).to.equal("v3");
|
|
90
|
+
expect(bot.body.connectors).to.be.an('object');
|
|
91
|
+
expect(bot.body.connectors.version).to.equal(1);
|
|
92
|
+
expect(bot.body.connectors.items).to.be.an('array').that.is.not.empty;
|
|
93
|
+
|
|
94
|
+
const intents = await getIntents(ctx, id, 2);
|
|
95
|
+
const ids = new Set(intents.map((intent) => intent.intent_id));
|
|
96
|
+
intents.forEach((intent) => {
|
|
97
|
+
expect(destinationKeysOf(intent)).to.deep.equal([], intent.intent_display_name + " still carries V2 destinations");
|
|
98
|
+
});
|
|
99
|
+
bot.body.connectors.items.forEach((record) => {
|
|
100
|
+
expect(ids.has(record.from.intentId)).to.equal(true, "record from an unknown block: " + record.id);
|
|
101
|
+
expect(ids.has(record.to.intentId)).to.equal(true, "record to an unknown block: " + record.id);
|
|
102
|
+
expect(record.id).to.equal(record.from.intentId + '/' + record.from.actionId + (record.from.slot === 'next' || record.from.slot === 'default' ? '' : '/' + record.from.slot) + '/' + record.to.intentId);
|
|
103
|
+
});
|
|
104
|
+
const start = intents.find((intent) => intent.intent_display_name === 'start');
|
|
105
|
+
expect(start).to.exist;
|
|
106
|
+
expect(bot.body.connectors.items.some((record) => record.from.intentId === start.intent_id && record.kind === 'action')).to.equal(true, "start has no connector in the list");
|
|
107
|
+
const fallback = intents.find((intent) => intent.intent_display_name === 'defaultFallback');
|
|
108
|
+
expect(bot.body.connectors.items.some((record) => record.from.intentId === fallback.intent_id && record.kind === 'block')).to.equal(true, "defaultFallback's dot has no connector in the list");
|
|
109
|
+
|
|
110
|
+
// The list of bots never carries the connectors list.
|
|
111
|
+
const list = await chai.request(server).get('/' + ctx.project._id + '/faq_kb').auth(ctx.email, ctx.pwd);
|
|
112
|
+
list.should.have.status(200);
|
|
113
|
+
const row = list.body.find((b) => b._id === id);
|
|
114
|
+
expect(row).to.exist;
|
|
115
|
+
expect(row.ds_version).to.equal("v3");
|
|
116
|
+
expect(row).to.not.have.property('connectors');
|
|
117
|
+
}).timeout(20000);
|
|
118
|
+
|
|
119
|
+
it('create-agent-without-label-is-unchanged', async () => {
|
|
120
|
+
const ctx = await signupAndProject("v2-blank");
|
|
121
|
+
|
|
122
|
+
const created = await createBot(ctx, { name: "v2bot", type: "tilebot", language: "en", template: "blank" });
|
|
123
|
+
created.should.have.status(200);
|
|
124
|
+
expect(created.body).to.not.have.property('ds_version');
|
|
125
|
+
expect(created.body).to.not.have.property('connectors');
|
|
126
|
+
const id = created.body._id;
|
|
127
|
+
|
|
128
|
+
const bot = await getBot(ctx, id);
|
|
129
|
+
bot.should.have.status(200);
|
|
130
|
+
expect(bot.body).to.not.have.property('ds_version');
|
|
131
|
+
expect(bot.body).to.not.have.property('connectors');
|
|
132
|
+
|
|
133
|
+
const intents = await getIntents(ctx, id, 2);
|
|
134
|
+
const start = intents.find((intent) => intent.intent_display_name === 'start');
|
|
135
|
+
expect(start.actions[0].intentName).to.be.a('string').that.matches(/^#/);
|
|
136
|
+
const fallback = intents.find((intent) => intent.intent_display_name === 'defaultFallback');
|
|
137
|
+
expect(fallback.attributes.nextBlockAction.intentName).to.be.a('string').that.matches(/^#/);
|
|
138
|
+
|
|
139
|
+
const connectors = await getConnectors(ctx, id);
|
|
140
|
+
connectors.should.have.status(200);
|
|
141
|
+
expect(connectors.body).to.equal(null);
|
|
142
|
+
}).timeout(20000);
|
|
143
|
+
|
|
144
|
+
it('create-promotes-the-label-written-inside-attributes', async () => {
|
|
145
|
+
const ctx = await signupAndProject("legacy-create");
|
|
146
|
+
const created = await createBot(ctx, { name: "old client", type: "tilebot", language: "en", template: "blank", attributes: { dsVersion: "v3", notes: [] } });
|
|
147
|
+
created.should.have.status(200);
|
|
148
|
+
expect(created.body.ds_version).to.equal("v3");
|
|
149
|
+
expect(created.body.attributes).to.not.have.property('dsVersion');
|
|
150
|
+
expect(created.body.attributes.notes).to.deep.equal([]);
|
|
151
|
+
const bot = await getBot(ctx, created.body._id);
|
|
152
|
+
expect(bot.body.connectors.items).to.be.an('array').that.is.not.empty;
|
|
153
|
+
}).timeout(20000);
|
|
154
|
+
|
|
155
|
+
it('create-ignores-an-unknown-version-label', async () => {
|
|
156
|
+
const ctx = await signupAndProject("v9");
|
|
157
|
+
const created = await createBot(ctx, { name: "v9bot", type: "tilebot", language: "en", template: "blank", ds_version: "v9" });
|
|
158
|
+
created.should.have.status(200);
|
|
159
|
+
expect(created.body).to.not.have.property('ds_version');
|
|
160
|
+
expect(created.body).to.not.have.property('connectors');
|
|
161
|
+
}).timeout(20000);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('Connectors endpoint', () => {
|
|
165
|
+
|
|
166
|
+
it('put-connectors-replaces-the-list-marks-modified-and-detects-conflicts', async () => {
|
|
167
|
+
const ctx = await signupAndProject("put");
|
|
168
|
+
const created = await createBot(ctx, { name: "v3bot", type: "tilebot", language: "en", template: "blank", ds_version: "v3" });
|
|
169
|
+
created.should.have.status(200);
|
|
170
|
+
const id = created.body._id;
|
|
171
|
+
|
|
172
|
+
const first = await getConnectors(ctx, id);
|
|
173
|
+
first.should.have.status(200);
|
|
174
|
+
expect(first.body.version).to.equal(1);
|
|
175
|
+
const firstUpdatedAt = first.body.updatedAt;
|
|
176
|
+
const items = first.body.items;
|
|
177
|
+
expect(items.length).to.be.greaterThan(1);
|
|
178
|
+
|
|
179
|
+
// a shorter list, saved on top of the one the client last saw
|
|
180
|
+
const saved = await putConnectors(ctx, id, { version: 1, items: [items[0]] }, firstUpdatedAt);
|
|
181
|
+
saved.should.have.status(200);
|
|
182
|
+
expect(saved.body.version).to.equal(1);
|
|
183
|
+
expect(saved.body.items).to.have.lengthOf(1);
|
|
184
|
+
expect(saved.body.items[0].id).to.equal(items[0].id);
|
|
185
|
+
expect(saved.body.updatedAt).to.not.equal(firstUpdatedAt);
|
|
186
|
+
|
|
187
|
+
const bot = await getBot(ctx, id);
|
|
188
|
+
expect(bot.body.modified).to.equal(true);
|
|
189
|
+
expect(bot.body.connectors.items).to.have.lengthOf(1);
|
|
190
|
+
|
|
191
|
+
// the same client saves again with the old updatedAt: conflict, current list returned
|
|
192
|
+
const conflict = await putConnectors(ctx, id, { version: 1, items: items }, firstUpdatedAt);
|
|
193
|
+
conflict.should.have.status(409);
|
|
194
|
+
expect(conflict.body.connectors.items).to.have.lengthOf(1);
|
|
195
|
+
|
|
196
|
+
// the same precondition as a query parameter, for clients that cannot send the header
|
|
197
|
+
const conflictByQuery = await chai.request(server)
|
|
198
|
+
.put('/' + ctx.project._id + '/faq_kb/' + id + '/connectors?if_match=' + encodeURIComponent(firstUpdatedAt))
|
|
199
|
+
.auth(ctx.email, ctx.pwd)
|
|
200
|
+
.send({ version: 1, items: items });
|
|
201
|
+
conflictByQuery.should.have.status(409);
|
|
202
|
+
|
|
203
|
+
// without If-Match the save goes through (last writer wins, as the attributes patch did)
|
|
204
|
+
const forced = await putConnectors(ctx, id, { version: 1, items: items });
|
|
205
|
+
forced.should.have.status(200);
|
|
206
|
+
expect(forced.body.items).to.have.lengthOf(items.length);
|
|
207
|
+
|
|
208
|
+
// an empty list is a valid list
|
|
209
|
+
const emptied = await putConnectors(ctx, id, { version: 1, items: [] }, forced.body.updatedAt);
|
|
210
|
+
emptied.should.have.status(200);
|
|
211
|
+
expect(emptied.body.items).to.have.lengthOf(0);
|
|
212
|
+
}).timeout(20000);
|
|
213
|
+
|
|
214
|
+
it('put-connectors-refuses-a-malformed-list-and-a-non-v3-agent', async () => {
|
|
215
|
+
const ctx = await signupAndProject("put-bad");
|
|
216
|
+
const v3 = await createBot(ctx, { name: "v3bot", type: "tilebot", language: "en", template: "blank", ds_version: "v3" });
|
|
217
|
+
const v2 = await createBot(ctx, { name: "v2bot", type: "tilebot", language: "en", template: "blank" });
|
|
218
|
+
|
|
219
|
+
const wrongVersion = await putConnectors(ctx, v3.body._id, { version: 2, items: [] });
|
|
220
|
+
wrongVersion.should.have.status(400);
|
|
221
|
+
expect(wrongVersion.body.errors).to.be.an('array').that.is.not.empty;
|
|
222
|
+
|
|
223
|
+
const wrongRecord = await putConnectors(ctx, v3.body._id, { version: 1, items: [{ id: 'a/b/c', from: { intentId: 'x', actionId: 'b', slot: 'next' }, to: { intentId: 'c' }, kind: 'block' }] });
|
|
224
|
+
wrongRecord.should.have.status(400);
|
|
225
|
+
|
|
226
|
+
const duplicated = await putConnectors(ctx, v3.body._id, { version: 1, items: [
|
|
227
|
+
{ id: 'a/b/c', from: { intentId: 'a', actionId: 'b', slot: 'next' }, to: { intentId: 'c' }, kind: 'block' },
|
|
228
|
+
{ id: 'a/b/c', from: { intentId: 'a', actionId: 'b', slot: 'next' }, to: { intentId: 'c' }, kind: 'block' }
|
|
229
|
+
] });
|
|
230
|
+
duplicated.should.have.status(400);
|
|
231
|
+
|
|
232
|
+
const onV2 = await putConnectors(ctx, v2.body._id, { version: 1, items: [] });
|
|
233
|
+
onV2.should.have.status(400);
|
|
234
|
+
const v2bot = await getBot(ctx, v2.body._id);
|
|
235
|
+
expect(v2bot.body).to.not.have.property('connectors');
|
|
236
|
+
expect(v2bot.body.modified).to.not.equal(true);
|
|
237
|
+
}).timeout(20000);
|
|
238
|
+
|
|
239
|
+
it('patch-attributes-promotes-the-legacy-keys-and-still-merges-the-others', async () => {
|
|
240
|
+
const ctx = await signupAndProject("patch");
|
|
241
|
+
const created = await createBot(ctx, { name: "v3bot", type: "tilebot", language: "en", template: "blank", ds_version: "v3" });
|
|
242
|
+
const id = created.body._id;
|
|
243
|
+
const url = '/' + ctx.project._id + '/faq_kb/' + id + '/attributes';
|
|
244
|
+
|
|
245
|
+
// the list sent the old way lands in the field, not in the attributes
|
|
246
|
+
const withConnectors = await chai.request(server).patch(url).auth(ctx.email, ctx.pwd).send({ connectors: { version: 1, items: [] }, notes: [{ text: "hello" }] });
|
|
247
|
+
withConnectors.should.have.status(200);
|
|
248
|
+
expect(withConnectors.body.attributes).to.not.have.property('connectors');
|
|
249
|
+
expect(withConnectors.body.attributes.notes).to.have.lengthOf(1);
|
|
250
|
+
const bot = await getBot(ctx, id);
|
|
251
|
+
expect(bot.body.connectors.items).to.have.lengthOf(0);
|
|
252
|
+
expect(bot.body.modified).to.equal(true);
|
|
253
|
+
expect(bot.body.attributes).to.not.have.property('connectors');
|
|
254
|
+
|
|
255
|
+
// a malformed list is refused and nothing changes
|
|
256
|
+
const bad = await chai.request(server).patch(url).auth(ctx.email, ctx.pwd).send({ connectors: { version: 2, items: [] } });
|
|
257
|
+
bad.should.have.status(400);
|
|
258
|
+
|
|
259
|
+
// the label sent the old way makes an unlabelled agent V3, and never changes a labelled one
|
|
260
|
+
const v2 = await createBot(ctx, { name: "old", type: "tilebot", language: "en", template: "blank" });
|
|
261
|
+
const labelled = await chai.request(server).patch('/' + ctx.project._id + '/faq_kb/' + v2.body._id + '/attributes').auth(ctx.email, ctx.pwd).send({ dsVersion: " V3 " });
|
|
262
|
+
labelled.should.have.status(200);
|
|
263
|
+
expect(labelled.body.ds_version).to.equal("v3");
|
|
264
|
+
expect(labelled.body.attributes || {}).to.not.have.property('dsVersion');
|
|
265
|
+
const relabel = await chai.request(server).patch(url).auth(ctx.email, ctx.pwd).send({ dsVersion: "v4" });
|
|
266
|
+
relabel.should.have.status(200);
|
|
267
|
+
expect(relabel.body.ds_version).to.equal("v3");
|
|
268
|
+
expect(relabel.body.attributes).to.not.have.property('dsVersion');
|
|
269
|
+
|
|
270
|
+
// a patch without the legacy keys is the merge it always was
|
|
271
|
+
const other = await chai.request(server).patch(url).auth(ctx.email, ctx.pwd).send({ globals: [{ key: "x" }] });
|
|
272
|
+
other.should.have.status(200);
|
|
273
|
+
expect(other.body.attributes.globals).to.have.lengthOf(1);
|
|
274
|
+
expect(other.body.attributes.notes).to.have.lengthOf(1);
|
|
275
|
+
}).timeout(30000);
|
|
276
|
+
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe('Export, import, fork and restore', () => {
|
|
280
|
+
|
|
281
|
+
it('export-and-import-keep-the-v3-fields-and-leave-a-v2-export-untouched', async () => {
|
|
282
|
+
const ctx = await signupAndProject("export");
|
|
283
|
+
const v3 = await createBot(ctx, { name: "v3bot", type: "tilebot", language: "en", template: "blank", ds_version: "v3" });
|
|
284
|
+
const v2 = await createBot(ctx, { name: "v2bot", type: "tilebot", language: "en", template: "blank" });
|
|
285
|
+
await getIntents(ctx, v3.body._id, 2);
|
|
286
|
+
await getIntents(ctx, v2.body._id, 2);
|
|
287
|
+
|
|
288
|
+
const exportedV3 = await chai.request(server).get('/' + ctx.project._id + '/faq_kb/exportjson/' + v3.body._id).auth(ctx.email, ctx.pwd);
|
|
289
|
+
exportedV3.should.have.status(200);
|
|
290
|
+
const jsonV3 = JSON.parse(exportedV3.text);
|
|
291
|
+
expect(jsonV3.ds_version).to.equal("v3");
|
|
292
|
+
expect(jsonV3.connectors.version).to.equal(1);
|
|
293
|
+
expect(jsonV3.connectors.items).to.be.an('array').that.is.not.empty;
|
|
294
|
+
expect(jsonV3.attributes || {}).to.not.have.property('connectors');
|
|
295
|
+
|
|
296
|
+
const exportedV2 = await chai.request(server).get('/' + ctx.project._id + '/faq_kb/exportjson/' + v2.body._id).auth(ctx.email, ctx.pwd);
|
|
297
|
+
exportedV2.should.have.status(200);
|
|
298
|
+
const jsonV2 = JSON.parse(exportedV2.text);
|
|
299
|
+
expect(jsonV2).to.not.have.property('ds_version');
|
|
300
|
+
expect(jsonV2).to.not.have.property('connectors');
|
|
301
|
+
|
|
302
|
+
const imported = await chai.request(server)
|
|
303
|
+
.post('/' + ctx.project._id + '/faq_kb/importjson/null?create=true')
|
|
304
|
+
.auth(ctx.email, ctx.pwd)
|
|
305
|
+
.send(jsonV3);
|
|
306
|
+
imported.should.have.status(200);
|
|
307
|
+
expect(imported.body.ds_version).to.equal("v3");
|
|
308
|
+
const copy = await getBot(ctx, imported.body._id);
|
|
309
|
+
expect(copy.body.connectors.items).to.have.lengthOf(jsonV3.connectors.items.length);
|
|
310
|
+
expect(copy.body.connectors.items.map((r) => r.id)).to.deep.equal(jsonV3.connectors.items.map((r) => r.id));
|
|
311
|
+
const copiedIntents = await getIntents(ctx, imported.body._id, 2);
|
|
312
|
+
copiedIntents.forEach((intent) => expect(destinationKeysOf(intent)).to.deep.equal([]));
|
|
313
|
+
|
|
314
|
+
const importedV2 = await chai.request(server)
|
|
315
|
+
.post('/' + ctx.project._id + '/faq_kb/importjson/null?create=true')
|
|
316
|
+
.auth(ctx.email, ctx.pwd)
|
|
317
|
+
.send(jsonV2);
|
|
318
|
+
importedV2.should.have.status(200);
|
|
319
|
+
expect(importedV2.body).to.not.have.property('ds_version');
|
|
320
|
+
expect(importedV2.body).to.not.have.property('connectors');
|
|
321
|
+
}).timeout(30000);
|
|
322
|
+
|
|
323
|
+
it('import-promotes-a-json-exported-with-the-fields-inside-attributes', async () => {
|
|
324
|
+
const ctx = await signupAndProject("legacy");
|
|
325
|
+
const legacy = {
|
|
326
|
+
name: "legacy v3", type: "tilebot", language: "en",
|
|
327
|
+
attributes: { dsVersion: "V3", connectors: { version: 1, items: [] }, globals: [] },
|
|
328
|
+
intents: [{ intent_id: "s1", intent_display_name: "start", question: "\\start", actions: [], attributes: { position: { x: 0, y: 0 } } }]
|
|
329
|
+
};
|
|
330
|
+
const imported = await chai.request(server)
|
|
331
|
+
.post('/' + ctx.project._id + '/faq_kb/importjson/null?create=true')
|
|
332
|
+
.auth(ctx.email, ctx.pwd)
|
|
333
|
+
.send(legacy);
|
|
334
|
+
imported.should.have.status(200);
|
|
335
|
+
expect(imported.body.ds_version).to.equal("v3");
|
|
336
|
+
const bot = await getBot(ctx, imported.body._id);
|
|
337
|
+
expect(bot.body.connectors.version).to.equal(1);
|
|
338
|
+
expect(bot.body.attributes).to.not.have.property('dsVersion');
|
|
339
|
+
expect(bot.body.attributes).to.not.have.property('connectors');
|
|
340
|
+
expect(bot.body.attributes.globals).to.deep.equal([]);
|
|
341
|
+
}).timeout(20000);
|
|
342
|
+
|
|
343
|
+
it('import-converts-a-v3-json-that-has-no-list', async () => {
|
|
344
|
+
const ctx = await signupAndProject("convert-import");
|
|
345
|
+
const json = {
|
|
346
|
+
name: "v3 without list", type: "tilebot", language: "en", ds_version: "v3",
|
|
347
|
+
intents: [
|
|
348
|
+
{ intent_id: "s1", intent_display_name: "start", question: "\\start", actions: [{ _tdActionId: "a1", _tdActionType: "intent", intentName: "#w1" }], attributes: { position: { x: 0, y: 0 } } },
|
|
349
|
+
{ intent_id: "w1", intent_display_name: "welcome", actions: [{ _tdActionId: "a2", _tdActionType: "reply", text: "hi", attributes: { commands: [] } }], attributes: { position: { x: 0, y: 0 }, nextBlockAction: { _tdActionId: "d1", _tdActionType: "intent", intentName: "#s1" } } }
|
|
350
|
+
]
|
|
351
|
+
};
|
|
352
|
+
const imported = await chai.request(server)
|
|
353
|
+
.post('/' + ctx.project._id + '/faq_kb/importjson/null?create=true')
|
|
354
|
+
.auth(ctx.email, ctx.pwd)
|
|
355
|
+
.send(json);
|
|
356
|
+
imported.should.have.status(200);
|
|
357
|
+
const bot = await getBot(ctx, imported.body._id);
|
|
358
|
+
expect(bot.body.connectors.items.map((r) => r.id)).to.deep.equal(["s1/a1/w1", "w1/d1/s1"]);
|
|
359
|
+
const intents = await getIntents(ctx, imported.body._id, 2);
|
|
360
|
+
intents.forEach((intent) => expect(destinationKeysOf(intent)).to.deep.equal([]));
|
|
361
|
+
}).timeout(20000);
|
|
362
|
+
|
|
363
|
+
it('import-refuses-a-malformed-connectors-list', async () => {
|
|
364
|
+
const ctx = await signupAndProject("bad-import");
|
|
365
|
+
const bad = { name: "bad", type: "tilebot", language: "en", ds_version: "v3", connectors: { version: 1, items: [{ id: "x" }] }, intents: [] };
|
|
366
|
+
const imported = await chai.request(server)
|
|
367
|
+
.post('/' + ctx.project._id + '/faq_kb/importjson/null?create=true')
|
|
368
|
+
.auth(ctx.email, ctx.pwd)
|
|
369
|
+
.send(bad);
|
|
370
|
+
imported.should.have.status(400);
|
|
371
|
+
}).timeout(20000);
|
|
372
|
+
|
|
373
|
+
it('fork-and-restore-carry-the-v3-fields', async () => {
|
|
374
|
+
const ctx = await signupAndProject("fork");
|
|
375
|
+
const created = await createBot(ctx, { name: "v3bot", type: "tilebot", language: "en", template: "blank", ds_version: "v3" });
|
|
376
|
+
const draft_id = created.body._id;
|
|
377
|
+
await getIntents(ctx, draft_id, 2);
|
|
378
|
+
|
|
379
|
+
const exported = await chai.request(server).get('/' + ctx.project._id + '/faq_kb/exportjson/' + draft_id).auth(ctx.email, ctx.pwd);
|
|
380
|
+
exported.should.have.status(200);
|
|
381
|
+
const json = JSON.parse(exported.text);
|
|
382
|
+
const originalIds = json.connectors.items.map((r) => r.id);
|
|
383
|
+
|
|
384
|
+
const previousService = server.get('chatbot_service');
|
|
385
|
+
server.set('chatbot_service', { async getBotById() { return JSON.parse(JSON.stringify(json)); } });
|
|
386
|
+
let forked;
|
|
387
|
+
try {
|
|
388
|
+
forked = await chai.request(server)
|
|
389
|
+
.post('/' + ctx.project._id + '/faq_kb/fork/' + draft_id + '?public=false&globals=true&projectid=' + ctx.project._id + '&skipSubagents=true')
|
|
390
|
+
.auth(ctx.email, ctx.pwd)
|
|
391
|
+
.set('Content-Type', 'application/json');
|
|
392
|
+
} finally {
|
|
393
|
+
server.set('chatbot_service', previousService);
|
|
394
|
+
}
|
|
395
|
+
forked.should.have.status(200);
|
|
396
|
+
const copy_id = forked.body.bot_id;
|
|
397
|
+
const copy = await getBot(ctx, copy_id);
|
|
398
|
+
expect(copy.body.ds_version).to.equal("v3");
|
|
399
|
+
expect(copy.body.connectors.items.map((r) => r.id)).to.deep.equal(originalIds);
|
|
400
|
+
|
|
401
|
+
// The copy stands as a published version of the draft; the draft loses its list, the restore brings it back.
|
|
402
|
+
await Faq_kb.findByIdAndUpdate(copy_id, { trashed: true, root_id: draft_id }).exec();
|
|
403
|
+
const emptied = await putConnectors(ctx, draft_id, { version: 1, items: [] });
|
|
404
|
+
emptied.should.have.status(200);
|
|
405
|
+
|
|
406
|
+
const restored = await chai.request(server)
|
|
407
|
+
.put('/' + ctx.project._id + '/faq_kb/' + draft_id + '/restore/' + copy_id)
|
|
408
|
+
.auth(ctx.email, ctx.pwd)
|
|
409
|
+
.send({});
|
|
410
|
+
restored.should.have.status(200);
|
|
411
|
+
const draft = await getBot(ctx, draft_id);
|
|
412
|
+
expect(draft.body.ds_version).to.equal("v3");
|
|
413
|
+
expect(draft.body.connectors.items.map((r) => r.id)).to.deep.equal(originalIds);
|
|
414
|
+
expect(draft.body.modified).to.equal(true);
|
|
415
|
+
}).timeout(30000);
|
|
416
|
+
|
|
417
|
+
it('subagent-of-a-v3-agent-is-created-and-exported-with-its-fields', async () => {
|
|
418
|
+
const ctx = await signupAndProject("subagent");
|
|
419
|
+
const parent = await createBot(ctx, { name: "parent", type: "tilebot", language: "en", template: "blank", ds_version: "v3" });
|
|
420
|
+
const sub = await createBot(ctx, { name: "sub", type: "tilebot", subtype: "subagent", template: "blank_subagent", language: "en", parent_id: parent.body._id, ds_version: "v3" });
|
|
421
|
+
sub.should.have.status(200);
|
|
422
|
+
expect(sub.body.ds_version).to.equal("v3");
|
|
423
|
+
const subBot = await getBot(ctx, sub.body._id);
|
|
424
|
+
expect(subBot.body.connectors.items).to.be.an('array').that.is.not.empty;
|
|
425
|
+
await getIntents(ctx, sub.body._id, 2);
|
|
426
|
+
await getIntents(ctx, parent.body._id, 2);
|
|
427
|
+
|
|
428
|
+
const exported = await chai.request(server).get('/' + ctx.project._id + '/faq_kb/exportjson/' + parent.body._id).auth(ctx.email, ctx.pwd);
|
|
429
|
+
exported.should.have.status(200);
|
|
430
|
+
const json = JSON.parse(exported.text);
|
|
431
|
+
expect(json.subagents).to.have.lengthOf(1);
|
|
432
|
+
expect(json.subagents[0].ds_version).to.equal("v3");
|
|
433
|
+
expect(json.subagents[0].connectors.items.map((r) => r.id)).to.deep.equal(subBot.body.connectors.items.map((r) => r.id));
|
|
434
|
+
}).timeout(30000);
|
|
435
|
+
});
|
|
436
|
+
});
|
package/test/nativemcp.test.js
CHANGED
|
@@ -40,7 +40,7 @@ describe('NativeMCP', () => {
|
|
|
40
40
|
|
|
41
41
|
res.should.have.status(200);
|
|
42
42
|
res.body.should.be.an('array');
|
|
43
|
-
res.body.should.have.length(
|
|
43
|
+
res.body.should.have.length(3);
|
|
44
44
|
|
|
45
45
|
expect(res.body[0].id).to.equal("tiledesk-communicator");
|
|
46
46
|
expect(res.body[0].name).to.equal("Tiledesk Communicator");
|
|
@@ -52,6 +52,11 @@ describe('NativeMCP', () => {
|
|
|
52
52
|
expect(res.body[1].native).to.equal(true);
|
|
53
53
|
expect(res.body[1].transport).to.equal("streamable_http");
|
|
54
54
|
expect(res.body[1].url).to.not.exist;
|
|
55
|
+
expect(res.body[2].id).to.equal("tiledesk-docsparse");
|
|
56
|
+
expect(res.body[2].name).to.equal("Tiledesk Docsparse");
|
|
57
|
+
expect(res.body[2].native).to.equal(true);
|
|
58
|
+
expect(res.body[2].transport).to.equal("streamable_http");
|
|
59
|
+
expect(res.body[2].url).to.not.exist;
|
|
55
60
|
|
|
56
61
|
done();
|
|
57
62
|
});
|