Files
hermes-ui/tests/server/hermes-schemas.test.ts
T
toor 1e9ea89d86
Build / build (push) Canceled after 0s
NPM Lockfile Check / npm ci --ignore-scripts (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s
chore: bootstrap hermes-ui skeleton from 0.6.29 snapshot
Import the client, server, skills, tests, and build scripts as the starting
point for the rebuild, then trim the surfaces this project does not ship:
Electron desktop packaging, the marketing website, and the ESP32 firmware
tree. CI keeps the web build, e2e, lockfile, release, and Docker workflows.

Rename the package and CLI entry points to hermes-ui, drop the prepare hook so
npm ci no longer triggers a build, and drop the removed tsconfig and nodemon
references. Rewrite AGENTS.md, ARCHITECTURE.md, DEVELOPMENT.md, and the READMEs
so the documented package map and hard rules match this repository.

reference_code/ is ignored: it is a read-only snapshot, not an input to the build.
2026-10-16 10:34:35 +08:00

128 lines
6.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
describe('Hermes schema initialization', () => {
let db: any = null
beforeEach(async () => {
vi.resetModules()
const { DatabaseSync } = await import('node:sqlite')
db = new DatabaseSync(':memory:')
vi.doMock('../../packages/server/src/db/index', () => ({
getDb: () => db,
getStoragePath: () => ':memory:',
}))
})
afterEach(() => {
db?.close()
db = null
vi.doUnmock('../../packages/server/src/db/index')
vi.resetModules()
})
it('initializes all tables with correct schemas', async () => {
const { initAllHermesTables, USAGE_TABLE, SESSIONS_TABLE, MESSAGES_TABLE, GC_ROOMS_TABLE, USERS_TABLE, USER_PROFILES_TABLE, DEVICES_TABLE, MCU_DEVICES_TABLE } =
await import('../../packages/server/src/db/hermes/schemas')
expect(() => initAllHermesTables()).not.toThrow()
// Verify core tables exist
const tables = db.prepare(`SELECT name FROM sqlite_master WHERE type='table'`).all() as Array<{ name: string }>
expect(tables.map(t => t.name)).toContain(USAGE_TABLE)
expect(tables.map(t => t.name)).toContain(SESSIONS_TABLE)
expect(tables.map(t => t.name)).toContain(MESSAGES_TABLE)
expect(tables.map(t => t.name)).toContain(GC_ROOMS_TABLE)
expect(tables.map(t => t.name)).toContain(USERS_TABLE)
expect(tables.map(t => t.name)).toContain(USER_PROFILES_TABLE)
expect(tables.map(t => t.name)).toContain(DEVICES_TABLE)
expect(tables.map(t => t.name)).toContain(MCU_DEVICES_TABLE)
// Verify USAGE_TABLE structure
const usageCols = db.prepare(`PRAGMA table_info("${USAGE_TABLE}")`).all() as Array<{ name: string }>
expect(usageCols.some(c => c.name === 'id')).toBe(true)
expect(usageCols.some(c => c.name === 'session_id')).toBe(true)
expect(usageCols.some(c => c.name === 'input_tokens')).toBe(true)
expect(usageCols.some(c => c.name === 'output_tokens')).toBe(true)
const sessionCols = db.prepare(`PRAGMA table_info("${SESSIONS_TABLE}")`).all() as Array<{ name: string }>
expect(sessionCols.some(c => c.name === 'source')).toBe(true)
expect(sessionCols.some(c => c.name === 'agent')).toBe(true)
const userCols = db.prepare(`PRAGMA table_info("${USERS_TABLE}")`).all() as Array<{ name: string }>
expect(userCols.some(c => c.name === 'id')).toBe(true)
expect(userCols.some(c => c.name === 'username')).toBe(true)
expect(userCols.some(c => c.name === 'password_hash')).toBe(true)
expect(userCols.some(c => c.name === 'role')).toBe(true)
const profileCols = db.prepare(`PRAGMA table_info("${USER_PROFILES_TABLE}")`).all() as Array<{ name: string }>
expect(profileCols.some(c => c.name === 'user_id')).toBe(true)
expect(profileCols.some(c => c.name === 'profile_name')).toBe(true)
expect(profileCols.some(c => c.name === 'is_default')).toBe(true)
const deviceCols = db.prepare(`PRAGMA table_info("${DEVICES_TABLE}")`).all() as Array<{ name: string }>
expect(deviceCols.some(c => c.name === 'id')).toBe(true)
expect(deviceCols.some(c => c.name === 'status')).toBe(true)
expect(deviceCols.some(c => c.name === 'device_public_key')).toBe(true)
const mcuDeviceCols = db.prepare(`PRAGMA table_info("${MCU_DEVICES_TABLE}")`).all() as Array<{ name: string }>
expect(mcuDeviceCols.some(c => c.name === 'id')).toBe(true)
expect(mcuDeviceCols.some(c => c.name === 'name')).toBe(true)
expect(mcuDeviceCols.some(c => c.name === 'device_code')).toBe(true)
expect(mcuDeviceCols.some(c => c.name === 'is_official')).toBe(true)
expect(mcuDeviceCols.some(c => c.name === 'created_at')).toBe(true)
})
it('preserves existing data when adding safe schema columns', async () => {
const { initAllHermesTables, USAGE_TABLE, USAGE_SCHEMA } =
await import('../../packages/server/src/db/hermes/schemas')
// Create table with minimal schema
db.exec(`CREATE TABLE "${USAGE_TABLE}" (id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, created_at INTEGER NOT NULL)`)
// Insert test data
db.prepare(`INSERT INTO "${USAGE_TABLE}" (session_id, created_at) VALUES (?, ?)`).run('test-session', Date.now())
// Run initialization (should add safe missing columns)
expect(() => initAllHermesTables()).not.toThrow()
// Verify data is preserved
const row = db.prepare(`SELECT * FROM "${USAGE_TABLE}" WHERE session_id = ?`).get('test-session')
expect(row).toBeTruthy()
expect(row.session_id).toBe('test-session')
// Verify safe new columns were added
const cols = db.prepare(`PRAGMA table_info("${USAGE_TABLE}")`).all() as Array<{ name: string }>
expect(cols.some(c => c.name === 'input_tokens')).toBe(true)
expect(cols.some(c => c.name === 'output_tokens')).toBe(true)
})
it('handles single-column primary key tables correctly', async () => {
const { initAllHermesTables, GC_ROOM_AGENTS_TABLE } =
await import('../../packages/server/src/db/hermes/schemas')
expect(() => initAllHermesTables()).not.toThrow()
// Verify table has primary key and required columns
const tableInfo = db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name=?`).get(GC_ROOM_AGENTS_TABLE) as { sql: string }
expect(tableInfo.sql).toContain('PRIMARY KEY')
expect(tableInfo.sql).toContain('id')
expect(tableInfo.sql).toContain('roomId')
expect(tableInfo.sql).toContain('agentId')
// Verify we can insert multiple entries with unique id
db.prepare(`INSERT INTO "${GC_ROOM_AGENTS_TABLE}" (id, roomId, agentId, profile, name, description, invited) VALUES (?, ?, ?, ?, ?, ?, ?)`)
.run('agent-1', 'room-1', 'agent-1', 'default', 'Agent 1', '', 0)
db.prepare(`INSERT INTO "${GC_ROOM_AGENTS_TABLE}" (id, roomId, agentId, profile, name, description, invited) VALUES (?, ?, ?, ?, ?, ?, ?)`)
.run('agent-2', 'room-1', 'agent-2', 'default', 'Agent 2', '', 0)
const count = db.prepare(`SELECT COUNT(*) as count FROM "${GC_ROOM_AGENTS_TABLE}"`).get() as { count: number }
expect(count.count).toBe(2)
// Verify duplicate primary key is rejected
expect(() => {
db.prepare(`INSERT INTO "${GC_ROOM_AGENTS_TABLE}" (id, roomId, agentId, profile, name, description, invited) VALUES (?, ?, ?, ?, ?, ?, ?)`)
.run('agent-1', 'room-1', 'agent-1', 'default', 'Agent 1 Duplicate', '', 0)
}).toThrow()
})
})