You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
55 lines
2.9 KiB
TypeScript
55 lines
2.9 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { openDb } from '../src/db'
|
|
import { createClient } from '../src/repos-clients'
|
|
import { upsertAwsUsage, getAwsUsage, listAwsUsage, costRanking, awsCostForClient, previousMonth } from '../src/repos-aws'
|
|
|
|
function setup() {
|
|
const db = openDb(':memory:')
|
|
const a = createClient(db, 'u1', { name: 'Acme', code: 'ACME', stateCode: '32' })
|
|
const b = createClient(db, 'u1', { name: 'Bolt', code: 'BOLT', stateCode: '29' })
|
|
return { db, a, b }
|
|
}
|
|
|
|
describe('aws usage repo', () => {
|
|
it('upserts and merges a (client, month) row without clobbering the other source', () => {
|
|
const { db, a } = setup()
|
|
upsertAwsUsage(db, 'u1', { clientId: a.id, month: '2026-06', costPaise: 5_000_00, source: 'auto' })
|
|
upsertAwsUsage(db, 'u1', { clientId: a.id, month: '2026-06', storageGb: 42.5, source: 'manual' }) // merges
|
|
const row = getAwsUsage(db, a.id, '2026-06')!
|
|
expect(row.costPaise).toBe(5_000_00) // auto cost preserved
|
|
expect(row.storageGb).toBe(42.5) // manual storage applied
|
|
expect(db.prepare(`SELECT COUNT(*) AS n FROM aws_usage WHERE client_id=?`).get(a.id)).toMatchObject({ n: 1 })
|
|
const audits = db.prepare(`SELECT action FROM audit_log WHERE entity='aws_usage'`).all()
|
|
expect(audits.length).toBe(2) // create + update
|
|
})
|
|
it('lists newest month first, capped', () => {
|
|
const { db, a } = setup()
|
|
for (const m of ['2026-04', '2026-05', '2026-06']) upsertAwsUsage(db, 'u1', { clientId: a.id, month: m, costPaise: 100 })
|
|
expect(listAwsUsage(db, a.id, 2).map((r) => r.month)).toEqual(['2026-06', '2026-05'])
|
|
})
|
|
it('rejects a bad month', () => {
|
|
const { db, a } = setup()
|
|
expect(() => upsertAwsUsage(db, 'u1', { clientId: a.id, month: '2026/06', costPaise: 1 })).toThrow(/month/i)
|
|
})
|
|
it('ranks clients by cost with share in basis points', () => {
|
|
const { db, a, b } = setup()
|
|
upsertAwsUsage(db, 'u1', { clientId: a.id, month: '2026-06', costPaise: 7_500_00 })
|
|
upsertAwsUsage(db, 'u1', { clientId: b.id, month: '2026-06', costPaise: 2_500_00 })
|
|
const r = costRanking(db, '2026-06')
|
|
expect(r.total).toBe(10_000_00)
|
|
expect(r.rows.map((x) => x.clientId)).toEqual([a.id, b.id]) // Acme first (higher cost)
|
|
expect(r.rows[0]!.sharePctBp).toBe(7500) // 75.00%
|
|
expect(r.rows[1]!.sharePctBp).toBe(2500)
|
|
})
|
|
it('sums a client cost over a month range and derives the previous month', () => {
|
|
const { db, a } = setup()
|
|
upsertAwsUsage(db, 'u1', { clientId: a.id, month: '2026-05', costPaise: 100 })
|
|
upsertAwsUsage(db, 'u1', { clientId: a.id, month: '2026-06', costPaise: 200 })
|
|
upsertAwsUsage(db, 'u1', { clientId: a.id, month: '2026-07', costPaise: 400 })
|
|
expect(awsCostForClient(db, a.id, '2026-05-01', '2026-06-30')).toBe(300)
|
|
expect(awsCostForClient(db, a.id)).toBe(700)
|
|
expect(previousMonth('2026-07-10')).toBe('2026-06')
|
|
expect(previousMonth('2026-01-05')).toBe('2025-12') // year rollover
|
|
})
|
|
})
|