src/services/demo-dataset.test.ts
Rama main · réplica de lectura del repositorio original.
import assert from 'node:assert/strict';
import test from 'node:test';
import {
DEMO_CHAT_ENTRY,
DemoDatasetConflictError,
DemoDatasetService,
demoToolSpec,
parseDemoDatasetManifest,
} from './demo-dataset';
const hash = 'a'.repeat(64);
class FakeDemoDatabase {
readonly events: string[] = [];
readonly targets = new Map<string, Record<string, unknown>>([
['llm', { name: 'llm', kind: 'inhub', stateless: 1, needs_project: 0, timeout_ms: 120000, enabled: 1, description: 'built-in' }],
]);
readonly providers = new Map<string, Record<string, unknown>>();
readonly routes = new Map<string, Record<string, unknown>>();
readonly clients = new Map<string, Record<string, unknown>>();
readonly chatEntries = new Map<string, Record<string, unknown>>();
readonly sessions: Array<Record<string, unknown>> = [];
readonly threads: Array<Record<string, unknown>> = [];
readonly jobs: Array<Record<string, unknown>> = [];
readonly toolApprovals: Array<Record<string, unknown>> = [];
readonly jobRatings: Array<Record<string, unknown>> = [];
readonly toolEmbeddings: Array<Record<string, unknown>> = [];
readonly pageContexts: Array<Record<string, unknown>> = [];
readonly channels: Array<Record<string, unknown>> = [];
readonly executorTokens: Array<Record<string, unknown>> = [];
auditRows = 0;
marker: Record<string, unknown> | null = null;
readonly connection = {
query: async (sql: string, params: unknown[] = []) => this.query(sql, params),
beginTransaction: async () => { this.events.push('BEGIN'); },
commit: async () => { this.events.push('COMMIT'); },
rollback: async () => { this.events.push('ROLLBACK'); },
release: () => { this.events.push('RELEASE'); },
};
readonly store = { db: { getConnection: async () => this.connection, query: async (sql: string, params: unknown[] = []) => this.query(sql, params) } };
private rows<T extends Record<string, unknown>>(values: Iterable<T>): T[] {
return [...values].map((value) => ({ ...value }));
}
private duplicate(): never {
throw Object.assign(new Error('duplicate'), { code: 'ER_DUP_ENTRY', errno: 1062 });
}
private put(
map: Map<string, Record<string, unknown>>,
key: string,
row: Record<string, unknown>,
sql: string,
): void {
if (map.has(key) && !sql.includes('ON DUPLICATE KEY UPDATE')) this.duplicate();
map.set(key, row);
}
private async query(sql: string, params: unknown[]): Promise<[any[], Record<string, never>]> {
const normalized = sql.replace(/\s+/g, ' ').trim();
this.events.push(normalized);
const result = (rows: any[] = []): [any[], Record<string, never>] => [rows, {}];
if (normalized.startsWith('SELECT GET_LOCK')) return result([{ acquired: 1 }]);
if (normalized.startsWith('SELECT RELEASE_LOCK')) return result([{ released: 1 }]);
if (normalized === 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE') return result();
if (normalized.includes('(SELECT COUNT(*) FROM bz_routes) AS routes')) {
return result([{
routes: this.routes.size,
clients: this.clients.size,
tool_providers: this.providers.size,
targets: this.targets.size,
channels: this.channels.length,
chat_entries: this.chatEntries.size,
jobs: this.jobs.length,
approvals: 0,
auxiliary: this.executorTokens.length,
custom_targets: [...this.targets.keys()].filter((name) => name !== 'llm').length,
}]);
}
if (normalized.startsWith('SELECT version,manifest_json,imported_at,updated_at FROM bz_demo_datasets')) {
return result(this.marker ? [{ ...this.marker }] : []);
}
if (normalized === 'SELECT name FROM bz_targets WHERE name=? FOR UPDATE') return result(this.targets.has(String(params[0])) ? [{ name: params[0] }] : []);
if (normalized === 'SELECT name FROM bz_tool_providers WHERE name=? FOR UPDATE') return result(this.providers.has(String(params[0])) ? [{ name: params[0] }] : []);
if (normalized === 'SELECT route_key FROM bz_routes WHERE route_key=? FOR UPDATE') return result(this.routes.has(String(params[0])) ? [{ route_key: params[0] }] : []);
if (normalized === 'SELECT app_id FROM bz_clients WHERE app_id=? FOR UPDATE') return result(this.clients.has(String(params[0])) ? [{ app_id: params[0] }] : []);
if (normalized === 'SELECT entry_key FROM bz_chat_entries WHERE entry_key=? FOR UPDATE') return result(this.chatEntries.has(String(params[0])) ? [{ entry_key: params[0] }] : []);
if (normalized.startsWith('SELECT id FROM bz_sessions WHERE route_key=')) return result(this.sessions.filter((row) => row['route_key'] === params[0]).slice(0, 1));
if (normalized.startsWith('SELECT thread_id FROM bz_threads WHERE route_key=')) return result(this.threads.filter((row) => row['route_key'] === params[0]).slice(0, 1));
if (normalized.startsWith('SELECT job_id FROM bz_jobs WHERE target=? OR client_app_id=?')) {
return result(this.jobs.filter((row) => row['target'] === params[0]
|| row['client_app_id'] === params[1]
|| (row['dispatch'] as Record<string, unknown> | undefined)?.['route_key'] === params[2]).slice(0, 1));
}
if (normalized.startsWith('SELECT id FROM bz_tool_approvals WHERE provider=')) {
return result(this.toolApprovals.filter((row) => row['provider'] === params[0]).slice(0, 1));
}
if (normalized.startsWith('SELECT job_id FROM bz_job_ratings WHERE entry_key=')) {
return result(this.jobRatings.filter((row) => row['entry_key'] === params[0]).slice(0, 1));
}
if (normalized.startsWith('SELECT id FROM bz_tool_embeddings WHERE provider=')) {
return result(this.toolEmbeddings.filter((row) => row['provider'] === params[0]).slice(0, 1));
}
if (normalized === 'SELECT * FROM bz_targets ORDER BY name') return result(this.rows(this.targets.values()));
if (normalized.startsWith('SELECT * FROM bz_tool_providers WHERE name=')) return result(this.providers.has(String(params[0])) ? [{ ...this.providers.get(String(params[0]))! }] : []);
if (normalized.startsWith('SELECT * FROM bz_routes WHERE route_key=')) return result(this.routes.has(String(params[0])) ? [{ ...this.routes.get(String(params[0]))! }] : []);
if (normalized.startsWith('SELECT * FROM bz_clients WHERE app_id=')) return result(this.clients.has(String(params[0])) ? [{ ...this.clients.get(String(params[0]))! }] : []);
if (normalized.startsWith('SELECT * FROM bz_chat_entries WHERE entry_key=')) return result(this.chatEntries.has(String(params[0])) ? [{ ...this.chatEntries.get(String(params[0]))! }] : []);
if (normalized.startsWith('INSERT INTO bz_targets ')) {
this.put(this.targets, String(params[0]), {
name: params[0], kind: params[1], stateless: params[2], needs_project: params[3], timeout_ms: params[4],
enabled: params[5], description: params[6], created_at: params[7], updated_at: params[8],
}, normalized);
return result();
}
if (normalized.startsWith('INSERT INTO bz_tool_providers ')) {
this.put(this.providers, String(params[0]), {
name: params[0], base_url: params[1], spec_source: params[2], spec_access_policy: params[3], spec_url: params[4],
spec_json: params[5], spec_refreshed_at: params[6], spec_access_probe_json: params[7], authz_probe_json: params[8],
secret: params[9], log_payload: params[10], timeout_ms: params[11], rate_limit_per_min: params[12],
auto_refresh_min: params[13], enabled: params[14], description: params[15], embed_credential: params[16],
embed_model: params[17], embed_dim: params[18], created_at: params[19], updated_at: params[20],
}, normalized);
return result();
}
if (normalized.startsWith('INSERT INTO bz_routes ')) {
const columns = ['route_key', 'name', 'enabled', 'target', 'target_config', 'project', 'profile', 'permission', 'session_policy',
'session_fixed_id', 'session_key_field', 'default_callback_url', 'delivery', 'knowledge', 'retry', 'tools', 'audience', 'memory',
'budget', 'description', 'created_at', 'updated_at'];
this.put(this.routes, String(params[0]), Object.fromEntries(columns.map((column, index) => [column, params[index]])), normalized);
return result();
}
if (normalized.startsWith('INSERT INTO bz_clients ')) {
const columns = ['app_id', 'name', 'token', 'allowed_routes', 'allowed_channels', 'rate_limit_per_min', 'budget', 'enabled', 'description', 'created_at', 'updated_at'];
this.put(this.clients, String(params[0]), Object.fromEntries(columns.map((column, index) => [column, params[index]])), normalized);
return result();
}
if (normalized.startsWith('INSERT INTO bz_chat_entries ')) {
const columns = ['entry_key', 'name', 'route_key', 'enabled', 'allowed_origins', 'rate_limit_per_min', 'ticket_client', 'bucket',
'title', 'greeting', 'color', 'appearance', 'description', 'created_at', 'updated_at'];
this.put(this.chatEntries, String(params[0]), Object.fromEntries(columns.map((column, index) => [column, params[index]])), normalized);
return result();
}
if (normalized.startsWith('UPDATE bz_clients SET token=')) {
const client = this.clients.get(String(params[2]));
if (client) { client['token'] = params[0]; client['updated_at'] = params[1]; }
return result();
}
if (normalized.startsWith('INSERT INTO bz_demo_datasets ')) {
if (this.marker) this.duplicate();
this.marker = { dataset_key: params[0], version: params[1], manifest_json: params[2], imported_at: params[3], updated_at: params[4] };
return result();
}
if (normalized.startsWith('UPDATE bz_demo_datasets SET version=')) {
assert.ok(this.marker);
this.marker = { ...this.marker!, version: params[0], manifest_json: params[1], updated_at: params[2] };
return result();
}
if (normalized === 'SELECT route_key,target,tools,audience FROM bz_routes FOR UPDATE') return result(this.rows(this.routes.values()));
if (normalized === 'SELECT entry_key,route_key,ticket_client FROM bz_chat_entries FOR UPDATE') return result(this.rows(this.chatEntries.values()));
if (normalized === 'SELECT id,entry_key FROM bz_page_contexts FOR UPDATE') return result(this.rows(this.pageContexts));
if (normalized === 'SELECT name,route_key FROM bz_channels FOR UPDATE') return result(this.rows(this.channels));
if (normalized === 'SELECT app_id,allowed_routes FROM bz_clients FOR UPDATE') return result(this.rows(this.clients.values()));
if (normalized === 'SELECT name,allowed_targets FROM bz_executor_tokens FOR UPDATE') return result(this.rows(this.executorTokens));
if (normalized.startsWith('DELETE FROM bz_chat_entries WHERE entry_key=')) { this.chatEntries.delete(String(params[0])); return result(); }
if (normalized.startsWith('DELETE FROM bz_clients WHERE app_id=')) { this.clients.delete(String(params[0])); return result(); }
if (normalized.startsWith('DELETE FROM bz_routes WHERE route_key=')) { this.routes.delete(String(params[0])); return result(); }
if (normalized.startsWith('DELETE FROM bz_tool_providers WHERE name=')) { this.providers.delete(String(params[0])); return result(); }
if (normalized.startsWith('DELETE FROM bz_targets WHERE name=')) { this.targets.delete(String(params[0])); return result(); }
if (normalized.startsWith('DELETE FROM bz_demo_datasets WHERE dataset_key=')) { this.marker = null; return result(); }
throw new Error(`FakeDemoDatabase 未覆盖 SQL: ${normalized}`);
}
}
test('demo dataset manifest: MySQL JSON 重排键后仍按结构验证', () => {
const reordered = JSON.stringify({
resources: {
chat_entries: [],
clients: [{ fingerprint: hash, key: 'demo-app' }],
routes: [{ fingerprint: hash, key: 'demo_support' }],
tool_providers: [{ fingerprint: hash, key: 'demo-business' }],
targets: [{ fingerprint: hash, key: 'demo-agent' }],
},
profile: 'stateless-readonly',
schema: 'bailinghub.demo-dataset.v1',
});
assert.equal(parseDemoDatasetManifest(reordered).profile, 'stateless-readonly');
});
test('demo dataset contract: hosted profile 不声明写工具或公开聊天入口', () => {
const spec = demoToolSpec('stateless-readonly') as { paths: Record<string, unknown> };
assert.deepEqual(Object.keys(spec.paths).sort(), ['/failure-demo', '/orders']);
assert.match(DEMO_CHAT_ENTRY, /^pub_[A-Za-z0-9_-]{8,64}$/);
assert.throws(() => parseDemoDatasetManifest({
schema: 'bailinghub.demo-dataset.v1',
profile: 'stateless-readonly',
resources: {
targets: [{ key: 'demo-agent', fingerprint: hash }],
tool_providers: [{ key: 'demo-business', fingerprint: hash }],
routes: [{ key: 'demo_support', fingerprint: hash }],
clients: [{ key: 'demo-app', fingerprint: hash }],
chat_entries: [{ key: DEMO_CHAT_ENTRY, fingerprint: hash }],
},
}), /chat_entries/);
});
test('demo dataset import: SERIALIZABLE 在交易前设置且首导 duplicate fail-closed', async () => {
const events: string[] = [];
const connection = {
async query(sql: string): Promise<[any[], any]> {
events.push(sql);
if (sql.startsWith('SELECT GET_LOCK')) return [[{ acquired: 1 }], []];
if (sql.startsWith('SELECT RELEASE_LOCK')) return [[{ released: 1 }], []];
if (sql.startsWith('INSERT INTO bz_targets')) {
assert.doesNotMatch(sql, /ON DUPLICATE KEY UPDATE/);
throw Object.assign(new Error('duplicate'), { code: 'ER_DUP_ENTRY', errno: 1062 });
}
return [[], []];
},
async beginTransaction(): Promise<void> { events.push('BEGIN'); },
async commit(): Promise<void> { events.push('COMMIT'); },
async rollback(): Promise<void> { events.push('ROLLBACK'); },
release(): void { events.push('RELEASE'); },
};
const service = new DemoDatasetService({
db: { getConnection: async () => connection },
} as any, {
businessBaseUrl: 'http://127.0.0.1:19080',
toolSecret: 'strong-independent-demo-secret',
profile: 'stateless-readonly',
});
await assert.rejects(
() => service.import(),
(error: unknown) => error instanceof DemoDatasetConflictError && /Core 已回滚/.test(error.message),
);
assert.ok(events.indexOf('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE') < events.indexOf('BEGIN'));
assert.ok(events.includes('ROLLBACK'));
assert.ok(events.includes('RELEASE'));
});
test('demo dataset lifecycle: stateless import/status/refresh/clear 不伪造或清理运行账本', async () => {
const db = new FakeDemoDatabase();
let tick = 0;
const service = new DemoDatasetService(db.store as any, {
businessBaseUrl: 'http://127.0.0.1:19080',
toolSecret: '4f7b2c91a8d03e65b49c27e018f3d6aa',
profile: 'stateless-readonly',
}, () => `2026-08-08T10:00:0${tick++}.000Z`);
assert.deepEqual(await service.status(), {
available: true,
imported: false,
empty: true,
counts: { routes: 0, clients: 0, tool_providers: 0, targets: 1, channels: 0, chat_entries: 0, jobs: 0, approvals: 0 },
});
const imported = await service.import();
assert.equal(imported.imported, true);
assert.equal(db.chatEntries.size, 0);
assert.deepEqual(imported.created.sort(), [
'client:demo-app', 'route:demo_support', 'target:demo-agent', 'tool-provider:demo-business',
]);
const importedAt = imported.imported_at;
const refreshed = await service.import();
assert.equal(refreshed.imported_at, importedAt);
assert.equal(db.marker?.['version'], 1);
db.jobs.push({ job_id: 'job-1', dispatch: { route_key: 'demo_support' } });
db.auditRows = 3;
const cleared = await service.clear();
assert.equal(cleared.imported, false);
assert.equal(cleared.counts.jobs, 1);
assert.equal(db.jobs.length, 1);
assert.equal(db.auditRows, 3);
assert.equal(db.targets.has('demo-agent'), false);
assert.equal(db.targets.has('llm'), true);
assert.equal(db.events.some((sql) => /^DELETE FROM bz_(jobs|audit|messages)/.test(sql)), false);
});
test('demo dataset ownership: 指纹漂移后拒绝覆盖与删除', async () => {
const db = new FakeDemoDatabase();
const service = new DemoDatasetService(db.store as any, {
businessBaseUrl: 'http://127.0.0.1:19080',
toolSecret: '4f7b2c91a8d03e65b49c27e018f3d6aa',
profile: 'stateless-readonly',
});
await service.import();
db.routes.get('demo_support')!['name'] = '用户重建的同名路由';
await assert.rejects(() => service.import(), (error: unknown) =>
error instanceof DemoDatasetConflictError && error.conflicts.includes('route:demo_support'));
assert.equal(db.routes.get('demo_support')?.['name'], '用户重建的同名路由');
await assert.rejects(() => service.clear(), (error: unknown) =>
error instanceof DemoDatasetConflictError && error.conflicts.includes('route:demo_support'));
assert.equal(db.routes.has('demo_support'), true);
assert.ok(db.marker);
});
test('demo dataset references: 首导不意外接通 dangling chat,清理不破坏外部引用', async () => {
const dangling = new FakeDemoDatabase();
dangling.chatEntries.set('pub_existing_entry', {
entry_key: 'pub_existing_entry', name: 'existing', route_key: 'demo_support', enabled: 1,
allowed_origins: '[]', rate_limit_per_min: 20, ticket_client: null,
});
const firstImport = new DemoDatasetService(dangling.store as any, {
businessBaseUrl: 'http://127.0.0.1:19080',
toolSecret: '4f7b2c91a8d03e65b49c27e018f3d6aa',
profile: 'stateless-readonly',
});
await assert.rejects(() => firstImport.import(), (error: unknown) =>
error instanceof DemoDatasetConflictError && error.conflicts.includes('chat-entry:pub_existing_entry->route:demo_support'));
assert.equal(dangling.routes.has('demo_support'), false);
const db = new FakeDemoDatabase();
const service = new DemoDatasetService(db.store as any, {
businessBaseUrl: 'http://127.0.0.1:19080',
toolSecret: '4f7b2c91a8d03e65b49c27e018f3d6aa',
profile: 'stateless-readonly',
});
await service.import();
db.routes.set('user-route', {
route_key: 'user-route', name: 'user', enabled: 1, target: 'demo-agent', target_config: '{}', profile: 'readonly',
session_policy: 'new', tools: null, audience: null,
});
await assert.rejects(() => service.import(), (error: unknown) =>
error instanceof DemoDatasetConflictError && error.conflicts.includes('route:user-route->target:demo-agent'));
await assert.rejects(() => service.clear(), (error: unknown) =>
error instanceof DemoDatasetConflictError && error.conflicts.includes('route:user-route->target:demo-agent'));
assert.equal(db.routes.has('demo_support'), true);
});
test('演示数据首导:保留的运行账本不能重新认领固定 demo 身份', async () => {
const db = new FakeDemoDatabase();
db.jobs.push({ job_id: 'legacy-job', target: 'demo-agent', client_app_id: null, dispatch: {} });
const service = new DemoDatasetService(db.store as any, {
businessBaseUrl: 'http://127.0.0.1:19080',
toolSecret: '4f7b2c91a8d03e65b49c27e018f3d6aa',
profile: 'stateless-readonly',
});
await assert.rejects(() => service.import(), (error: unknown) =>
error instanceof DemoDatasetConflictError && error.conflicts.includes('job-demo-identity'));
assert.equal(db.routes.has('demo_support'), false);
});
test('demo dataset profile: full-local 仅导入默认停用的合法 chat entry', async () => {
const db = new FakeDemoDatabase();
const service = new DemoDatasetService(db.store as any, {
businessBaseUrl: 'http://127.0.0.1:19080',
toolSecret: 'demo-tool-secret-change-me',
profile: 'full-local',
clientToken: 'bailing-demo-client-token',
});
const imported = await service.import();
assert.ok(imported.created.includes(`chat-entry:${DEMO_CHAT_ENTRY}`));
assert.equal(db.chatEntries.get(DEMO_CHAT_ENTRY)?.['enabled'], 0);
assert.equal(db.chatEntries.get(DEMO_CHAT_ENTRY)?.['route_key'], 'demo_support');
});