convoreader / tests /chat-git.test.js
luguog's picture
Serialize live chat commits and harden branch controls (part 2)
6a03cc1 verified
Raw
History Blame Contribute Delete
3.73 kB
'use strict';
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const test = require('node:test');
const assert = require('node:assert/strict');
const { webcrypto } = require('node:crypto');
function storage() {
const values = new Map();
return { getItem: key => values.get(key) || null, setItem: (key, value) => values.set(key, value), removeItem: key => values.delete(key) };
}
function runtime(fetchImpl = async () => { throw new Error('unexpected fetch'); }) {
const window = { dispatchEvent() {} };
const context = { window, localStorage: storage(), sessionStorage: storage(), crypto: webcrypto,
TextEncoder, btoa: value => Buffer.from(value, 'binary').toString('base64'), CustomEvent: class {}, fetch: fetchImpl };
vm.runInNewContext(fs.readFileSync(path.join(__dirname, '..', 'chat-git.js'), 'utf8'), context);
return window.ChatGit;
}
test('chat commits form a content-addressed branch', async () => {
const git = runtime();
const first = await git.commit({ streamId: 'chat-1', message: 'user: hello', snapshot: [{ role: 'user', content: 'hello' }] });
const second = await git.commit({ streamId: 'chat-1', message: 'assistant: hi', snapshot: [{ role: 'assistant', content: 'hi' }] });
assert.equal(first.id.length, 64);
assert.deepEqual(Array.from(second.parents), [first.id]);
assert.equal(git.read().branches.main, second.id);
});
test('simultaneous user and assistant commits serialize without loss', async () => {
const git = runtime();
const [user, assistant] = await Promise.all([
git.commit({ streamId: 'chat-1', message: 'user turn', snapshot: ['u'] }),
git.commit({ streamId: 'chat-1', message: 'assistant turn', snapshot: ['u', 'a'] })
]);
assert.equal(git.read().commits.length, 2);
assert.deepEqual(Array.from(assistant.parents), [user.id]);
});
test('parallel branches retain independent heads and merge parents', async () => {
const git = runtime();
const root = await git.commit({ message: 'root', snapshot: {} });
git.createBranch('experiment');
const experiment = await git.commit({ message: 'parallel', snapshot: { version: 2 } });
git.switchBranch('main');
const merged = await git.merge('experiment', 'main');
assert.deepEqual(Array.from(merged.parents), [root.id, experiment.id]);
assert.equal(git.read().branches.main, merged.id);
});
test('token is session-only and excluded from durable state', async () => {
const git = runtime();
const state = git.read();
assert.equal(JSON.stringify(state).includes('token'), false);
assert.equal(git.hasSessionToken(), false);
});
test('github mirror writes one stream file through the contents API', async () => {
const calls = [];
const git = runtime(async (url, options = {}) => {
calls.push({ url, options });
if (url.endsWith('/repos/acme/demo')) return { ok: true, status: 200, json: async () => ({ full_name: 'acme/demo', default_branch: 'main', private: true }) };
if ((options.method || 'GET') === 'GET') return { ok: false, status: 404, json: async () => ({ message: 'Not Found' }) };
return { ok: true, status: 200, json: async () => ({ commit: { sha: 'abc123' } }) };
});
await git.connect({ owner: 'acme', repo: 'demo', accessToken: 'session-token', autoSync: false });
await git.commit({ streamId: 'chat-1', message: 'user: hello', snapshot: [{ role: 'user', content: 'hello' }] });
await git.syncStream('chat-1');
const put = calls.find(call => call.options.method === 'PUT');
assert.match(put.url, /\/contents\/\.convoreader\/streams\/chat-1\.json$/);
assert.equal(git.read().sync.lastCommit, 'abc123');
assert.equal(JSON.stringify(git.read()).includes('session-token'), false);
});