118 lines
5.7 KiB
JavaScript
118 lines
5.7 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import LibDashStore from '../src/index.js';
|
|
import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js';
|
|
|
|
async function run() {
|
|
console.log('--- Testing store.modelconversation / store.modelmessage ---');
|
|
|
|
const tempDir = mkdtempSync(join(tmpdir(), 'test-modelconv-'));
|
|
const provider = new BetterSqlite3Provider({
|
|
filename: join(tempDir, 'test.db'),
|
|
storageDir: join(tempDir, 'storage'),
|
|
});
|
|
await provider.initialize();
|
|
const store = await new LibDashStore(provider);
|
|
|
|
try {
|
|
const userA = await store.google_auth.add('mc-a@example.com');
|
|
const company = await store.company.create({
|
|
name: 'MC Corp', iso639: 'de', taxrate: 19, reducedtaxrate: 7, googleAuthId: userA,
|
|
});
|
|
await store.google_auth.setSelectedCompany(userA, company.id);
|
|
|
|
const conv = await store.modelconversation.create(userA, {
|
|
provider: 'openrouter', model: 'test/model',
|
|
});
|
|
assert.equal(conv.state, 'idle');
|
|
assert.equal(conv.provider, 'openrouter');
|
|
await assert.rejects(store.modelconversation.create(userA, { model: 'x' }), /Provider required/);
|
|
await assert.rejects(store.modelconversation.create(userA, { provider: 'x' }), /Model required/);
|
|
|
|
const list = await store.modelconversation.list(userA);
|
|
assert.equal(list.length, 1);
|
|
assert.equal(list[0].first_user_message, null);
|
|
|
|
assert.deepEqual(await store.modelmessage.list(userA, conv.id), []);
|
|
|
|
const m1 = await store.modelmessage.add(userA, conv.id, { message: 'hello' });
|
|
assert.equal(m1.role, 'user');
|
|
assert.equal(m1.conversation_id, conv.id);
|
|
await assert.rejects(store.modelmessage.add(userA, conv.id, { message: ' ' }), /Message required/);
|
|
await assert.rejects(store.modelmessage.add(userA, 9999, { message: 'x' }), /Conversation not found/);
|
|
|
|
// Non-idle conversation refuses user messages but allows assistant messages
|
|
const busyConv = await store.modelconversation.setState(userA, conv.id, 'busy');
|
|
assert.equal(busyConv.state, 'busy');
|
|
const fetchedBusy = await store.modelconversation.get(userA, conv.id);
|
|
assert.equal(fetchedBusy.state, 'busy');
|
|
|
|
await assert.rejects(
|
|
store.modelmessage.add(userA, conv.id, { message: 'again' }),
|
|
/Conversation is not idle/,
|
|
);
|
|
|
|
// Assistant message can be added while busy
|
|
const mAssistant = await store.modelmessage.add(userA, conv.id, { message: 'echo: hello', role: 'assistant' });
|
|
assert.equal(mAssistant.role, 'assistant');
|
|
assert.equal(mAssistant.message, 'echo: hello');
|
|
|
|
// Back to idle
|
|
await store.modelconversation.setState(userA, conv.id, 'idle');
|
|
await store.modelmessage.add(userA, conv.id, { message: 'second' });
|
|
|
|
const msgs = await store.modelmessage.list(userA, conv.id);
|
|
assert.deepEqual(msgs.map((m) => m.message), ['hello', 'echo: hello', 'second']);
|
|
assert.deepEqual(msgs.map((m) => m.role), ['user', 'assistant', 'user']);
|
|
assert.equal((await store.modelconversation.list(userA)).find((c) => c.id === conv.id).first_user_message, 'hello');
|
|
assert.equal((await store.modelconversation.get(userA, conv.id)).first_user_message, 'hello');
|
|
|
|
// Other user cannot see or write
|
|
const userB = await store.google_auth.add('mc-b@example.com');
|
|
await store.companyuser.add(company.id, userB);
|
|
await store.google_auth.setSelectedCompany(userB, company.id);
|
|
assert.equal((await store.modelconversation.list(userB)).length, 0);
|
|
await assert.rejects(store.modelmessage.list(userB, conv.id), /Conversation not found/);
|
|
await assert.rejects(store.modelmessage.add(userB, conv.id, { message: 'x' }), /Conversation not found/);
|
|
|
|
// started_by: top-level is null, subagents link to their originator, list exposes it
|
|
assert.equal(conv.started_by, null);
|
|
const sub1 = await store.modelconversation.create(userA, { provider: 'openrouter', model: 'm', started_by: conv.id });
|
|
assert.equal(sub1.started_by, conv.id);
|
|
const sub2 = await store.modelconversation.create(userA, { provider: 'openrouter', model: 'm', started_by: sub1.id });
|
|
const sibling = await store.modelconversation.create(userA, { provider: 'openrouter', model: 'm' });
|
|
await assert.rejects(
|
|
store.modelconversation.create(userA, { provider: 'openrouter', model: 'm', started_by: 0 }),
|
|
/Invalid started_by/,
|
|
);
|
|
const listed = await store.modelconversation.list(userA);
|
|
assert.equal(listed.find((c) => c.id === sub2.id).started_by, sub1.id);
|
|
assert.equal((await store.modelconversation.get(userA, sub1.id)).started_by, conv.id);
|
|
await store.modelmessage.add(userA, sub2.id, { message: 'deep' });
|
|
|
|
// Deleting a child removes only its own chain; deleting the root removes the whole chain
|
|
await store.modelconversation.delete(userA, sub2.id);
|
|
await assert.rejects(store.modelconversation.get(userA, sub2.id), /Conversation not found/);
|
|
await store.modelconversation.get(userA, sub1.id);
|
|
const sub3 = await store.modelconversation.create(userA, { provider: 'openrouter', model: 'm', started_by: sub1.id });
|
|
await store.modelconversation.delete(userA, conv.id);
|
|
for (const gone of [conv.id, sub1.id, sub3.id]) {
|
|
await assert.rejects(store.modelconversation.get(userA, gone), /Conversation not found/);
|
|
}
|
|
await store.modelconversation.get(userA, sibling.id);
|
|
await assert.rejects(store.modelconversation.delete(userA, conv.id), /Conversation not found/);
|
|
|
|
console.log('PASS');
|
|
} finally {
|
|
await provider.close?.();
|
|
rmSync(tempDir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
run().catch((err) => {
|
|
console.error('FAIL', err);
|
|
process.exit(1);
|
|
});
|