import { describe, expect, it } from 'vitest' import { AccountRecordSchema, HostRecordSchema, KillSignalSchema, RELAY_REVOCATIONS_CHANNEL, REVOCATION_PUSH_BUDGET_MS, RevocationScopeSchema, RouteEntrySchema, SessionRecordSchema, INV8_VERSION_TABLE_DDL, HOST_VERSIONS_DDL, HOSTS_CURRENT_DDL, } from '../src/index.js' const UUID = '11111111-1111-4111-8111-111111111111' const TS = '2026-07-01T00:00:00.000Z' describe('§4.2 account/host/session Zod schemas', () => { it('accepts a valid HostRecord', () => { const rec = { hostId: UUID, accountId: UUID, subdomain: 'alice', agentPubkey: new Uint8Array([1, 2, 3]), enrollFpr: 'fpr:abc', status: 'online', lastSeen: TS, createdAt: TS, revokedAt: null, } expect(HostRecordSchema.parse(rec)).toEqual(rec) }) it('rejects a HostRecord with an invalid status', () => { expect(() => HostRecordSchema.parse({ hostId: UUID, accountId: UUID, subdomain: 'a', agentPubkey: new Uint8Array(), enrollFpr: 'f', status: 'zombie', lastSeen: TS, createdAt: TS, revokedAt: null, }), ).toThrow() }) it('rejects a HostRecord with a non-UUID hostId', () => { const bad = { hostId: 'not-a-uuid', accountId: UUID, subdomain: 'a', agentPubkey: new Uint8Array(), enrollFpr: 'f', status: 'online', lastSeen: TS, createdAt: TS, revokedAt: null, } expect(HostRecordSchema.safeParse(bad).success).toBe(false) }) it('rejects unknown extra keys (strict)', () => { expect( AccountRecordSchema.safeParse({ accountId: UUID, plan: 'pro', createdAt: TS, status: 'active', extra: 1, }).success, ).toBe(false) }) it('accepts a valid AccountRecord across all plan tiers', () => { for (const plan of ['free', 'personal', 'pro', 'team'] as const) { expect(AccountRecordSchema.parse({ accountId: UUID, plan, createdAt: TS, status: 'active' }).plan).toBe( plan, ) } }) it('validates SessionRecord and RouteEntry', () => { expect( SessionRecordSchema.parse({ sessionId: UUID, hostId: UUID, accountId: UUID, createdAt: TS, lastAttachAt: TS, }).sessionId, ).toBe(UUID) expect(RouteEntrySchema.parse({ relayNodeId: 'node-1', updatedAt: TS }).relayNodeId).toBe('node-1') }) }) describe('§4.2 FIX 4 revocation teardown shapes', () => { it('exposes the frozen channel + budget constants', () => { expect(RELAY_REVOCATIONS_CHANNEL).toBe('relay:revocations') expect(REVOCATION_PUSH_BUDGET_MS).toBe(2000) }) it('accepts each RevocationScope variant', () => { expect(RevocationScopeSchema.parse({ kind: 'host', hostId: UUID }).kind).toBe('host') expect(RevocationScopeSchema.parse({ kind: 'account', accountId: UUID }).kind).toBe('account') expect(RevocationScopeSchema.parse({ kind: 'global' }).kind).toBe('global') }) it('rejects a host scope missing hostId', () => { expect(RevocationScopeSchema.safeParse({ kind: 'host' }).success).toBe(false) }) it('validates a full KillSignal', () => { const signal = { scope: { kind: 'global' }, at: 1719800000, reason: 'operator drain' } expect(KillSignalSchema.parse(signal)).toEqual(signal) }) }) describe('§4.2 INV8 version-table DDL contract', () => { it('exposes host_versions + hosts_current DDL in order', () => { expect(INV8_VERSION_TABLE_DDL).toEqual([HOST_VERSIONS_DDL, HOSTS_CURRENT_DDL]) expect(HOST_VERSIONS_DDL).toContain('host_versions') expect(HOST_VERSIONS_DDL).toContain('supersedes') expect(HOSTS_CURRENT_DDL).toContain('hosts_current') expect(HOSTS_CURRENT_DDL).toContain('version_id') }) })