mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-23 00:45:25 +08:00
265 lines
7.2 KiB
JavaScript
265 lines
7.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import test from 'node:test';
|
|
import ts from 'typescript';
|
|
|
|
function setup({
|
|
tasks = [{ runtime: { done: true } }],
|
|
engines = [{ engines: [] }],
|
|
cloud,
|
|
} = {}) {
|
|
const source = fs.readFileSync(
|
|
new URL(
|
|
'../../src/app/home/knowledge/components/kb-form/knowledge-engine-marketplace.ts',
|
|
import.meta.url,
|
|
),
|
|
'utf8',
|
|
);
|
|
const code = ts.transpileModule(source, {
|
|
compilerOptions: {
|
|
module: ts.ModuleKind.CommonJS,
|
|
target: ts.ScriptTarget.ES2022,
|
|
},
|
|
}).outputText;
|
|
const storage = new Map(),
|
|
calls = [];
|
|
let workspace = 'alpha',
|
|
now = 0,
|
|
taskIndex = 0,
|
|
engineIndex = 0;
|
|
const http = {
|
|
getPlugins: async () => ({
|
|
plugins: [
|
|
{
|
|
manifest: {
|
|
manifest: { metadata: { author: 'qa', name: 'existing' } },
|
|
},
|
|
},
|
|
],
|
|
}),
|
|
installPluginFromMarketplace: async (...args) => {
|
|
calls.push(['install', ...args]);
|
|
return { task_id: 42 };
|
|
},
|
|
getAsyncTask: async (id) => {
|
|
calls.push(['task', id]);
|
|
return tasks[Math.min(taskIndex++, tasks.length - 1)];
|
|
},
|
|
getKnowledgeEngines: async () => {
|
|
calls.push(['engines']);
|
|
return engines[Math.min(engineIndex++, engines.length - 1)];
|
|
},
|
|
};
|
|
const deps = {
|
|
'@/app/infra/http': { getCloudServiceClient: async () => cloud },
|
|
'@/app/infra/http/HttpClient': { httpClient: http },
|
|
'@/app/infra/http/workspaceContext': {
|
|
getActiveWorkspaceUuid: () => workspace,
|
|
},
|
|
};
|
|
const mod = { exports: {} };
|
|
new Function(
|
|
'require',
|
|
'module',
|
|
'exports',
|
|
'window',
|
|
'sessionStorage',
|
|
'CustomEvent',
|
|
'Date',
|
|
'setTimeout',
|
|
code,
|
|
)(
|
|
(name) => {
|
|
assert.ok(name in deps, name);
|
|
return deps[name];
|
|
},
|
|
mod,
|
|
mod.exports,
|
|
new EventTarget(),
|
|
{
|
|
getItem: (key) => storage.get(key) ?? null,
|
|
setItem: (key, value) => storage.set(key, value),
|
|
removeItem: (key) => storage.delete(key),
|
|
},
|
|
CustomEvent,
|
|
{ now: () => now },
|
|
(callback, ms) => {
|
|
now += ms;
|
|
queueMicrotask(callback);
|
|
},
|
|
);
|
|
return {
|
|
api: mod.exports,
|
|
storage,
|
|
calls,
|
|
setWorkspace: (value) => {
|
|
workspace = value;
|
|
},
|
|
};
|
|
}
|
|
const plugin = {
|
|
author: 'qa',
|
|
name: 'engine',
|
|
label: { en_US: 'QA Engine' },
|
|
latest_version: '2.0.0',
|
|
};
|
|
const engine = { plugin_id: 'qa/engine', name: 'Registered QA Engine' };
|
|
|
|
test('catalog loads every page with the engine filter and excludes unrelated components', async () => {
|
|
const searches = [];
|
|
const { api } = setup({
|
|
cloud: {
|
|
searchMarketplaceExtensions: async (args) => {
|
|
searches.push(args);
|
|
return {
|
|
total: 201,
|
|
plugins:
|
|
args.page === 1
|
|
? [
|
|
{
|
|
...plugin,
|
|
components: { KnowledgeEngine: 1 },
|
|
install_count: 5,
|
|
},
|
|
]
|
|
: args.page === 2
|
|
? [
|
|
{
|
|
name: 'runner',
|
|
components: { Runner: 1 },
|
|
install_count: 999,
|
|
},
|
|
]
|
|
: [
|
|
{
|
|
...plugin,
|
|
name: 'popular',
|
|
components: { KnowledgeEngine: 1 },
|
|
install_count: 20,
|
|
},
|
|
],
|
|
};
|
|
},
|
|
},
|
|
});
|
|
const result = await api.loadKnowledgeEngineCatalog();
|
|
assert.deepEqual(
|
|
searches.map((value) => value.page),
|
|
[1, 2, 3],
|
|
);
|
|
for (const args of searches)
|
|
assert.deepEqual(args, {
|
|
query: '',
|
|
page: args.page,
|
|
page_size: 100,
|
|
type_filter: 'plugin',
|
|
component_filter: 'KnowledgeEngine',
|
|
});
|
|
assert.deepEqual(
|
|
result.marketplaceEngines.map((value) => value.name),
|
|
['popular', 'engine'],
|
|
);
|
|
assert.deepEqual(result.installedPluginIds, ['qa/existing']);
|
|
});
|
|
|
|
test('installation persists a scoped task before polling and waits for component registration', async () => {
|
|
const { api, storage, calls } = setup({
|
|
tasks: [{ runtime: { done: false } }, { runtime: { done: true } }],
|
|
engines: [{ engines: [{ plugin_id: 'qa/other' }] }, { engines: [engine] }],
|
|
});
|
|
let created;
|
|
const installed = await api.installMarketplaceKnowledgeEngine(plugin, {
|
|
scope: 'kb:new',
|
|
onTaskCreated: (id) => {
|
|
created = id;
|
|
assert.equal(api.readPendingKnowledgeEngineInstall('kb:new').taskId, 42);
|
|
},
|
|
});
|
|
assert.equal(installed, engine);
|
|
assert.equal(created, 42);
|
|
assert.deepEqual(calls, [
|
|
['install', 'qa', 'engine', '2.0.0'],
|
|
['task', 42],
|
|
['task', 42],
|
|
['engines'],
|
|
['engines'],
|
|
]);
|
|
assert.equal(storage.size, 0);
|
|
});
|
|
|
|
test('a pending engine install resumes only in its original workspace and form scope', async () => {
|
|
const { api, storage, calls, setWorkspace } = setup({
|
|
engines: [{ engines: [engine] }],
|
|
});
|
|
storage.set(
|
|
'langbot-knowledge-engine-install:alpha:kb:new',
|
|
JSON.stringify({
|
|
taskId: 42,
|
|
pluginId: 'qa/engine',
|
|
pluginAuthor: 'qa',
|
|
pluginName: 'engine',
|
|
pluginLabel: 'QA Engine',
|
|
scope: 'kb:new',
|
|
startedAt: 0,
|
|
}),
|
|
);
|
|
setWorkspace('beta');
|
|
assert.equal(await api.resumePendingKnowledgeEngineInstall('kb:new'), null);
|
|
setWorkspace('alpha');
|
|
assert.equal(await api.resumePendingKnowledgeEngineInstall('kb:other'), null);
|
|
assert.equal(await api.resumePendingKnowledgeEngineInstall('kb:new'), engine);
|
|
assert.deepEqual(calls, [['task', 42], ['engines']]);
|
|
assert.equal(storage.size, 0);
|
|
});
|
|
|
|
test('failed tasks clear their intent and never query component registration', async () => {
|
|
const { api, calls, storage } = setup({
|
|
tasks: [{ runtime: { done: true, exception: 'Package invalid' } }],
|
|
});
|
|
await assert.rejects(
|
|
api.installMarketplaceKnowledgeEngine(plugin, { scope: 'kb:new' }),
|
|
/Package invalid/,
|
|
);
|
|
assert.deepEqual(calls, [
|
|
['install', 'qa', 'engine', '2.0.0'],
|
|
['task', 42],
|
|
]);
|
|
assert.equal(storage.size, 0);
|
|
});
|
|
|
|
test('an unfinished task times out with a recoverable intent, while missing registration clears it', async () => {
|
|
const pending = setup({ tasks: [{ runtime: { done: false } }] });
|
|
await assert.rejects(
|
|
pending.api.installMarketplaceKnowledgeEngine(plugin, { scope: 'kb:new' }),
|
|
(error) => error.code === 'install-timeout',
|
|
);
|
|
assert.equal(
|
|
pending.api.readPendingKnowledgeEngineInstall('kb:new').taskId,
|
|
42,
|
|
);
|
|
assert.equal(
|
|
pending.calls.some((value) => value[0] === 'engines'),
|
|
false,
|
|
);
|
|
const unregistered = setup();
|
|
await assert.rejects(
|
|
unregistered.api.installMarketplaceKnowledgeEngine(plugin, {
|
|
scope: 'kb:new',
|
|
}),
|
|
(error) => error.code === 'registration-timeout',
|
|
);
|
|
assert.equal(unregistered.storage.size, 0);
|
|
});
|
|
|
|
test('a plugin without a version never starts an install task', async () => {
|
|
const { api, calls } = setup();
|
|
await assert.rejects(
|
|
api.installMarketplaceKnowledgeEngine(
|
|
{ ...plugin, latest_version: '' },
|
|
{ scope: 'kb:new' },
|
|
),
|
|
(error) => error.code === 'version-unavailable',
|
|
);
|
|
assert.deepEqual(calls, []);
|
|
});
|