// apps/hq/test/aws-costs.test.ts import { describe, it, expect } from 'vitest' import { openDb } from '../src/db' import { seedIfEmpty } from '../src/seed' import { createClient } from '../src/repos-clients' import { getSetting } from '../src/repos-reminders' import { previousMonth } from '../src/repos-aws' import { signAwsRequest, pullMonthlyCosts, maybePullAwsCosts, type AwsCostDeps } from '../src/aws-costs' const CREDS = { accessKeyId: 'AKIDEXAMPLE', secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' } describe('signAwsRequest (SigV4)', () => { it('produces a deterministic, well-formed authorization header', () => { const now = new Date('2026-02-01T00:00:00.000Z') const a = signAwsRequest({ creds: CREDS, region: 'us-east-1', service: 'ce', target: 'AWSInsightsIndexService.GetCostAndUsage', body: '{}', now }) const b = signAwsRequest({ creds: CREDS, region: 'us-east-1', service: 'ce', target: 'AWSInsightsIndexService.GetCostAndUsage', body: '{}', now }) expect(a.headers['authorization']).toBe(b.headers['authorization']) // deterministic for a fixed clock expect(a.headers['authorization']).toMatch( /^AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE\/20260201\/us-east-1\/ce\/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-target, Signature=[0-9a-f]{64}$/, ) expect(a.headers['x-amz-date']).toBe('20260201T000000Z') expect(a.url).toBe('https://ce.us-east-1.amazonaws.com/') }) it('changes the signature when the secret changes', () => { const now = new Date('2026-02-01T00:00:00.000Z') const a = signAwsRequest({ creds: CREDS, region: 'us-east-1', service: 'ce', target: 't', body: '{}', now }) const b = signAwsRequest({ creds: { ...CREDS, secretAccessKey: 'other' }, region: 'us-east-1', service: 'ce', target: 't', body: '{}', now }) expect(a.headers['authorization']).not.toBe(b.headers['authorization']) }) }) const CE_RESPONSE = { ResultsByTime: [{ Groups: [ { Keys: ['client$ACME'], Metrics: { UnblendedCost: { Amount: '1234.56', Unit: 'INR' } } }, { Keys: ['client$GHOST'], Metrics: { UnblendedCost: { Amount: '10.00', Unit: 'INR' } } }, { Keys: ['client$'], Metrics: { UnblendedCost: { Amount: '5.00', Unit: 'INR' } } }, ], }], } function deps(response: unknown, capture?: (url: string, init?: RequestInit) => void): AwsCostDeps { const f = (async (url: string, init?: RequestInit) => { capture?.(String(url), init) return new Response(JSON.stringify(response), { status: 200 }) }) as unknown as typeof fetch return { f, creds: CREDS, now: () => '2026-07-01T00:00:00Z' } } describe('pullMonthlyCosts', () => { it('maps client tags to clients, upserts cost rows, and collects unknown tags', async () => { const db = openDb(':memory:'); await seedIfEmpty(db) const acme = await createClient(db, 'u1', { name: 'Acme', code: 'ACME', stateCode: '32' }) let sentUrl = '' const out = await pullMonthlyCosts(db, deps(CE_RESPONSE, (u) => { sentUrl = u }), '2026-06') expect(sentUrl).toBe('https://ce.us-east-1.amazonaws.com/') expect(out.upserted).toBe(1) // only ACME matched expect(out.unknownTags.map((u) => u.tag).sort()).toEqual(['', 'GHOST']) // ghost client + untagged expect(out.unknownTags.find((u) => u.tag === 'GHOST')?.costPaise).toBe(10_00) const row = await db.get(`SELECT cost_paise, source FROM aws_usage WHERE client_id=?`, acme.id) expect(row).toMatchObject({ cost_paise: 123456, source: 'auto' }) }) it('throws on a non-INR billing currency rather than mis-converting', async () => { const db = openDb(':memory:'); await seedIfEmpty(db) const usd = { ResultsByTime: [{ Groups: [{ Keys: ['client$ACME'], Metrics: { UnblendedCost: { Amount: '10.00', Unit: 'USD' } } }] }] } await expect(pullMonthlyCosts(db, deps(usd), '2026-06')).rejects.toThrow(/INR/i) }) }) describe('maybePullAwsCosts', () => { it('pulls the previous month once per calendar month', async () => { const db = openDb(':memory:'); await seedIfEmpty(db) await createClient(db, 'u1', { name: 'Acme', code: 'ACME', stateCode: '32' }) expect(previousMonth('2026-07-10')).toBe('2026-06') const first = await maybePullAwsCosts(db, deps(CE_RESPONSE), '2026-07-10') expect(first).not.toBeNull() expect(await getSetting(db, 'aws.last_pull_month')).toBe('2026-06') const second = await maybePullAwsCosts(db, deps(CE_RESPONSE), '2026-07-20') expect(second).toBeNull() // already pulled 2026-06 this run }) })