'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); });