diff --git a/apps/hq/src/db.ts b/apps/hq/src/db.ts index c378199..9431583 100644 --- a/apps/hq/src/db.ts +++ b/apps/hq/src/db.ts @@ -137,6 +137,16 @@ CREATE TABLE IF NOT EXISTS reminder ( error TEXT, created_at TEXT NOT NULL, sent_at TEXT, UNIQUE (rule_kind, subject_id, due_period) -- the idempotency key: at-most-once per due-period ); +CREATE TABLE IF NOT EXISTS aws_usage ( + id TEXT PRIMARY KEY, client_id TEXT NOT NULL, + month TEXT NOT NULL, -- 'YYYY-MM' + storage_gb REAL NOT NULL DEFAULT 0, -- GB stored, month-end snapshot (manual or CloudWatch later) + transfer_gb REAL NOT NULL DEFAULT 0, -- GB transferred over the month + cost_paise INTEGER NOT NULL DEFAULT 0, -- Cost Explorer UnblendedCost for the 'client' tag, in paise (INR) + source TEXT NOT NULL DEFAULT 'auto' CHECK (source IN ('auto','manual')), + updated_at TEXT NOT NULL, + UNIQUE (client_id, month) -- one row per client per month; the pull upserts on this key +); ` export function openDb(dataDir?: string): DB { diff --git a/apps/hq/test/hq3-schema.test.ts b/apps/hq/test/hq3-schema.test.ts new file mode 100644 index 0000000..1c1a40b --- /dev/null +++ b/apps/hq/test/hq3-schema.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from 'vitest' +import { openDb } from '../src/db' + +describe('hq3 schema', () => { + it('creates the aws_usage table', () => { + const db = openDb(':memory:') + const names = (db.prepare(`SELECT name FROM sqlite_master WHERE type='table'`).all() as { name: string }[]).map((r) => r.name) + expect(names).toContain('aws_usage') + }) + it('has the expected columns', () => { + const db = openDb(':memory:') + const cols = (db.prepare(`PRAGMA table_info(aws_usage)`).all() as { name: string }[]).map((c) => c.name) + for (const c of ['id', 'client_id', 'month', 'storage_gb', 'transfer_gb', 'cost_paise', 'source', 'updated_at']) + expect(cols, `missing column ${c}`).toContain(c) + }) + it('enforces one row per (client, month)', () => { + const db = openDb(':memory:') + const ins = db.prepare( + `INSERT INTO aws_usage (id, client_id, month, storage_gb, transfer_gb, cost_paise, source, updated_at) + VALUES (?, 'c1', '2026-06', 0, 0, 0, 'auto', '2026-07-01T00:00:00Z')`, + ) + expect(ins.run('a1').changes).toBe(1) + expect(() => ins.run('a2')).toThrow() // UNIQUE (client_id, month) + }) +})