Files
Catalyst/tests/db.test.js
josh 505315c8bd
Some checks failed
Build / test (push) Successful in 11m12s
Build / build (push) Failing after 2m33s
adds tests
2026-03-27 23:51:03 -04:00

251 lines
8.6 KiB
JavaScript

import { describe, it, expect, beforeEach } from 'vitest'
import initSqlJs from 'sql.js'
// ── Schema (mirrors db.js) ────────────────────────────────────────────────────
const SCHEMA = `
CREATE TABLE instances (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
state TEXT DEFAULT 'deployed',
stack TEXT DEFAULT '',
vmid INTEGER UNIQUE NOT NULL,
atlas INTEGER DEFAULT 0,
argus INTEGER DEFAULT 0,
semaphore INTEGER DEFAULT 0,
patchmon INTEGER DEFAULT 0,
tailscale INTEGER DEFAULT 0,
andromeda INTEGER DEFAULT 0,
tailscale_ip TEXT DEFAULT '',
hardware_acceleration INTEGER DEFAULT 0,
createdAt TEXT DEFAULT (datetime('now')),
updatedAt TEXT DEFAULT (datetime('now'))
)
`
// ── Helpers ───────────────────────────────────────────────────────────────────
let db
beforeEach(async () => {
const SQL = await initSqlJs()
db = new SQL.Database()
db.run(SCHEMA)
})
function rows(res) {
if (!res.length) return []
const cols = res[0].columns
return res[0].values.map(row => Object.fromEntries(cols.map((c, i) => [c, row[i]])))
}
function insert(overrides = {}) {
const defaults = {
name: 'test-instance', state: 'deployed', stack: 'production', vmid: 100,
atlas: 0, argus: 0, semaphore: 0, patchmon: 0,
tailscale: 0, andromeda: 0, tailscale_ip: '', hardware_acceleration: 0,
}
const d = { ...defaults, ...overrides }
db.run(
`INSERT INTO instances
(name, state, stack, vmid, atlas, argus, semaphore, patchmon, tailscale, andromeda, tailscale_ip, hardware_acceleration)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[d.name, d.state, d.stack, d.vmid, d.atlas, d.argus, d.semaphore,
d.patchmon, d.tailscale, d.andromeda, d.tailscale_ip, d.hardware_acceleration]
)
return d
}
function getInstances(filters = {}) {
let sql = 'SELECT * FROM instances WHERE 1=1'
const params = []
if (filters.search) {
sql += ' AND (name LIKE ? OR CAST(vmid AS TEXT) LIKE ? OR stack LIKE ?)'
const s = `%${filters.search}%`
params.push(s, s, s)
}
if (filters.state) { sql += ' AND state = ?'; params.push(filters.state) }
if (filters.stack) { sql += ' AND stack = ?'; params.push(filters.stack) }
sql += ' ORDER BY name ASC'
return rows(db.exec(sql, params))
}
function getInstance(vmid) {
const res = rows(db.exec('SELECT * FROM instances WHERE vmid = ?', [vmid]))
return res[0] ?? null
}
function getDistinctStacks() {
const res = db.exec(`SELECT DISTINCT stack FROM instances WHERE stack != '' ORDER BY stack`)
if (!res.length) return []
return res[0].values.map(r => r[0])
}
// ── Tests ─────────────────────────────────────────────────────────────────────
describe('getInstances', () => {
it('returns empty array when no instances exist', () => {
expect(getInstances()).toEqual([])
})
it('returns all instances sorted by name', () => {
insert({ name: 'zebra', vmid: 1 })
insert({ name: 'alpha', vmid: 2 })
const result = getInstances()
expect(result).toHaveLength(2)
expect(result[0].name).toBe('alpha')
expect(result[1].name).toBe('zebra')
})
it('filters by state', () => {
insert({ name: 'a', vmid: 1, state: 'deployed' })
insert({ name: 'b', vmid: 2, state: 'degraded' })
insert({ name: 'c', vmid: 3, state: 'testing' })
expect(getInstances({ state: 'deployed' })).toHaveLength(1)
expect(getInstances({ state: 'degraded' })).toHaveLength(1)
expect(getInstances({ state: 'testing' })).toHaveLength(1)
})
it('filters by stack', () => {
insert({ name: 'a', vmid: 1, stack: 'production' })
insert({ name: 'b', vmid: 2, stack: 'development' })
expect(getInstances({ stack: 'production' })).toHaveLength(1)
expect(getInstances({ stack: 'development' })).toHaveLength(1)
})
it('searches by name', () => {
insert({ name: 'plex', vmid: 1 })
insert({ name: 'gitea', vmid: 2 })
expect(getInstances({ search: 'ple' })).toHaveLength(1)
expect(getInstances({ search: 'ple' })[0].name).toBe('plex')
})
it('searches by vmid', () => {
insert({ name: 'a', vmid: 137 })
insert({ name: 'b', vmid: 200 })
expect(getInstances({ search: '137' })).toHaveLength(1)
})
it('searches by stack', () => {
insert({ name: 'a', vmid: 1, stack: 'production' })
insert({ name: 'b', vmid: 2, stack: 'development' })
expect(getInstances({ search: 'prod' })).toHaveLength(1)
})
it('combines search and state filters', () => {
insert({ name: 'plex', vmid: 1, state: 'deployed' })
insert({ name: 'plex2', vmid: 2, state: 'degraded' })
expect(getInstances({ search: 'plex', state: 'deployed' })).toHaveLength(1)
})
it('returns empty array when no results match', () => {
insert({ name: 'plex', vmid: 1 })
expect(getInstances({ search: 'zzz' })).toEqual([])
})
})
describe('getInstance', () => {
it('returns the instance with the given vmid', () => {
insert({ name: 'plex', vmid: 117 })
const inst = getInstance(117)
expect(inst).not.toBeNull()
expect(inst.name).toBe('plex')
expect(inst.vmid).toBe(117)
})
it('returns null for an unknown vmid', () => {
expect(getInstance(999)).toBeNull()
})
})
describe('getDistinctStacks', () => {
it('returns empty array when no instances exist', () => {
expect(getDistinctStacks()).toEqual([])
})
it('returns unique stacks sorted alphabetically', () => {
insert({ vmid: 1, stack: 'production' })
insert({ vmid: 2, stack: 'development' })
insert({ vmid: 3, stack: 'production' })
expect(getDistinctStacks()).toEqual(['development', 'production'])
})
it('excludes blank stack values', () => {
insert({ vmid: 1, stack: '' })
insert({ vmid: 2, stack: 'production' })
expect(getDistinctStacks()).toEqual(['production'])
})
})
describe('createInstance', () => {
it('inserts a new instance', () => {
insert({ name: 'traefik', vmid: 100, stack: 'production', state: 'deployed' })
const inst = getInstance(100)
expect(inst.name).toBe('traefik')
expect(inst.stack).toBe('production')
expect(inst.state).toBe('deployed')
})
it('stores service flags correctly', () => {
insert({ vmid: 1, atlas: 1, argus: 0, tailscale: 1, hardware_acceleration: 1 })
const inst = getInstance(1)
expect(inst.atlas).toBe(1)
expect(inst.argus).toBe(0)
expect(inst.tailscale).toBe(1)
expect(inst.hardware_acceleration).toBe(1)
})
it('rejects duplicate vmid', () => {
insert({ vmid: 100 })
expect(() => insert({ name: 'other', vmid: 100 })).toThrow()
})
it('sets createdAt and updatedAt on insert', () => {
insert({ vmid: 1 })
const inst = getInstance(1)
expect(inst.createdAt).not.toBeNull()
expect(inst.updatedAt).not.toBeNull()
})
})
describe('updateInstance', () => {
it('updates fields on an existing instance', () => {
insert({ name: 'old-name', vmid: 100, state: 'testing', stack: 'development' })
const before = getInstance(100)
db.run(
`UPDATE instances SET name=?, state=?, stack=?, updatedAt=datetime('now') WHERE id=?`,
['new-name', 'deployed', 'production', before.id]
)
const after = getInstance(100)
expect(after.name).toBe('new-name')
expect(after.state).toBe('deployed')
expect(after.stack).toBe('production')
})
it('updates updatedAt on write', () => {
insert({ vmid: 1 })
const before = getInstance(1)
db.run(`UPDATE instances SET name=?, updatedAt=datetime('now') WHERE id=?`, ['updated', before.id])
const after = getInstance(1)
expect(after.updatedAt).not.toBeNull()
})
})
describe('deleteInstance', () => {
it('removes the instance', () => {
insert({ vmid: 1 })
const inst = getInstance(1)
db.run('DELETE FROM instances WHERE id = ?', [inst.id])
expect(getInstance(1)).toBeNull()
})
it('only removes the targeted instance', () => {
insert({ name: 'a', vmid: 1 })
insert({ name: 'b', vmid: 2 })
const inst = getInstance(1)
db.run('DELETE FROM instances WHERE id = ?', [inst.id])
expect(getInstance(1)).toBeNull()
expect(getInstance(2)).not.toBeNull()
})
})