wulf-pulse/lib/services/analyzer/asset-audit/runner.test.ts
lorentz 1112a06afe feat: RMM Overshell, IT Glue audit/write-back, LogLift, link-aware bundles, dashboard overhaul
- RMM Overshell (migration 077): admin page, dispatch UI, executor/worker, target
  resolver, script registry (AD/DHCP/DNS/event-log/services/software/network/loglift)
- LogLift evidence pipeline (migration 078): upload webhook, B2 storage client,
  receiver/matcher, EventLogCollector PowerShell script
- IT Glue audit + write-back (migrations 075, 076): asset-audit runner, ticket
  xrefs, applications/configurations browse pages + apply/revert/audit endpoints
- Link-aware analyzer bundles (migration 073) + provider toggle (migration 074):
  link-discovery service, OpenRouter LLM provider, related-tickets/itglue-suggestion
  panels, analyze-bundle endpoint
- Endpoint data model + device-link reconciliation (migrations 079, 080): conflicts
  admin page, reconciler service, resolve endpoints
- Dashboard overhaul: integration-health service + alerts, overview/health endpoints
- Permissions: add itglue + rmm scopes; middleware: public /api/rmm/loglift route

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 07:13:18 -04:00

204 lines
6.3 KiB
TypeScript

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<string, unknown> = {}) {
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);
});
});