import { describe, it, expect } from 'vitest'; import { _ASSET_AUDIT_INTERNALS } from './data-builder'; import { fieldNameToTraitKey } from './persistence'; import { buildAssetAuditUserPayload, _PROMPT_INTERNALS } from './prompt'; import { AssetAuditResponse } from '@/lib/types/analyzer'; describe('fillCount', () => { const { fillCount } = _ASSET_AUDIT_INTERNALS; it('counts populated trait keys', () => { expect(fillCount({ a: 'x', b: 'y' })).toBe(2); }); it('drops empty strings, empty arrays, null, undefined', () => { expect( fillCount({ a: '', b: ' ', c: null, d: undefined, e: [], f: 'real', }) ).toBe(1); }); it('counts non-empty objects/arrays', () => { expect(fillCount({ a: { values: [1] }, b: [1, 2] })).toBe(2); }); it('returns 0 for null/undefined input', () => { expect(fillCount(null)).toBe(0); expect(fillCount(undefined)).toBe(0); }); }); describe('fieldNameToTraitKey', () => { it('matches IT Glue trait-key convention', () => { expect(fieldNameToTraitKey('Name')).toBe('name'); expect(fieldNameToTraitKey('Wulf Application Champion(s)')).toBe( 'wulf-application-champion-s' ); expect(fieldNameToTraitKey('Application on Device(s)')).toBe( 'application-on-device-s' ); expect(fieldNameToTraitKey('Client/Server Software Installation Media Location')) .toBe('client-server-software-installation-media-location'); }); it('strips leading/trailing/repeated hyphens', () => { expect(fieldNameToTraitKey(' --Foo-- ')).toBe('foo'); expect(fieldNameToTraitKey('A & B')).toBe('a-b'); }); }); describe('AssetAuditResponse Zod schema', () => { it('accepts a well-formed response', () => { const ok = AssetAuditResponse.safeParse({ field_gaps: [ { field_name: 'Wulf Application Champion(s)', why_missing_matters: 'Jake Hammel is the SME but not recorded.', suggested_value: 'Jake Hammel', evidence_ticket_numbers: ['T20260502.0033'], confidence: 'high', }, ], notes_promotions: [ { quoted_note_text: 'Per Jake if down send to Steve Cianflone', target_field: 'Vendor Maintenance/Support', suggested_value: 'Steve Cianflone (Kastech)', confidence: 'medium', }, ], contradictions: [ { description: 'Notes say 2-3 VMs, Application-on-Device-s lists 1', evidence: 'Notes field; application-on-device-s.values', }, ], overall_score: 0.55, }); expect(ok.success).toBe(true); }); it('allows null suggested_value', () => { const ok = AssetAuditResponse.safeParse({ field_gaps: [ { field_name: 'URL', why_missing_matters: 'Vendor admin console URL not recorded.', suggested_value: null, evidence_ticket_numbers: [], confidence: 'low', }, ], notes_promotions: [], contradictions: [], overall_score: 0.7, }); expect(ok.success).toBe(true); }); it('rejects out-of-range overall_score', () => { const bad = AssetAuditResponse.safeParse({ field_gaps: [], notes_promotions: [], contradictions: [], overall_score: 1.5, }); expect(bad.success).toBe(false); }); it('rejects bogus confidence values', () => { const bad = AssetAuditResponse.safeParse({ field_gaps: [ { field_name: 'X', why_missing_matters: 'y', suggested_value: null, evidence_ticket_numbers: [], confidence: 'sky-high', }, ], notes_promotions: [], contradictions: [], overall_score: 0.5, }); expect(bad.success).toBe(false); }); }); describe('buildAssetAuditUserPayload', () => { function makeCtx(overrides: Record = {}) { return { asset_type: 'flexible_asset', asset: { id: '1', organization_id: '100', organization_name: 'Acme', type_id: '3790', type_name: 'Applications', name: 'MISYS', traits: { name: 'MISYS', version: '6.3' }, }, type_id: 3790, type_name: 'Applications', fields: [ { id: '1', name: 'Name', kind: 'Text', hint: null, required: true }, ], peer_same_client: [], peer_global: [], fill_rate_client: [{ field_name: 'Name', fill_rate: 1.0 }], fill_rate_global: [{ field_name: 'Name', fill_rate: 0.95 }], ticket_evidence: [], ticket_scope: null, ...overrides, }; } it('renders all sections (flexible asset)', () => { const ctx = makeCtx(); const { payload, trimmed } = buildAssetAuditUserPayload(ctx as never); expect(payload).toContain('=== FLEXIBLE ASSET UNDER AUDIT ==='); expect(payload).toContain('=== FIELD SCHEMA'); expect(payload).toContain('=== FILL-RATE STATS ==='); expect(payload).toContain('=== PEER EXEMPLARS — SAME CLIENT ==='); expect(payload).toContain('=== PEER EXEMPLARS — BEST-IN-CLASS'); expect(payload).toContain('=== TICKET EVIDENCE'); expect(trimmed.peer_global_dropped).toBe(0); expect(trimmed.tickets_dropped).toBe(0); }); it('renders configuration header when assetType is configuration', () => { const ctx = makeCtx({ asset_type: 'configuration' }); const { payload } = buildAssetAuditUserPayload(ctx as never); expect(payload).toContain('=== CONFIGURATION UNDER AUDIT ==='); }); it('renders ticket-scoped header when ticket_scope is set', () => { const ctx = makeCtx({ ticket_scope: { analysis_id: 'a', ticket_number: 'T20260502.0033' }, }); const { payload } = buildAssetAuditUserPayload(ctx as never); expect(payload).toContain('single ticket'); expect(payload).toContain('T20260502.0033'); }); it('trims peer_global before tickets when over the cap', () => { const bigPeer = { id: 'x', organization_id: '99', organization_name: 'Other', type_id: '3790', type_name: 'Applications', name: 'BigPeer', traits: { huge: 'x'.repeat(_PROMPT_INTERNALS.PAYLOAD_CHAR_CAP) }, }; const ctx = makeCtx({ peer_global: [bigPeer, bigPeer] }); const { trimmed } = buildAssetAuditUserPayload(ctx as never); expect(trimmed.peer_global_dropped).toBeGreaterThan(0); }); });