Spaces:
Running
Running
| ; | |
| 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); | |
| }); | |