Files
one-pane/test/one-pane.test.ts
2026-09-28 14:49:01 -04:00

134 lines
5.4 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { overall, validateReport } from '@extant2000/evidence-record'
import { ambiguousAttribution, formatPane, unify, type Signal } from '../src/index.js'
const NOW = '2026-08-01T12:00:00Z'
const fresh = '2026-08-01T11:58:00Z'
const old = '2026-08-01T09:00:00Z'
const sig = (p: Partial<Signal> = {}): Signal => ({
source: 'gatus',
subject: 'crm',
metric: 'availability',
value: 100,
health: 'up',
observedAt: fresh,
where: 'https://status.example.com',
...p,
})
describe('silence is not health', () => {
it('a stale panel is NOT ASSESSED, not green', () => {
// An error board can show zero events because ingestion stopped, not
// because nothing went wrong.
const r = unify([sig({ observedAt: old })], { now: NOW })
expect(r.findings[0]!.determination).toBe('not-assessed')
expect(overall(r)).toBe('not-assessed')
const text = formatPane([sig({ observedAt: old })], { now: NOW })
expect(text).toContain('Silence is not health')
expect(text).toContain('showing its last reading')
expect(text).not.toContain('✓')
})
it('passes a reading inside the freshness window', () => {
expect(overall(unify([sig()], { now: NOW }))).toBe('pass')
})
it('honours a custom freshness window', () => {
// 2 minutes old, with a 60-second window.
expect(overall(unify([sig()], { now: NOW, maxAgeSeconds: 60 }))).toBe('not-assessed')
})
it('treats an unstamped reading as indistinguishable from a stale one', () => {
const r = unify([sig({ observedAt: undefined })], { now: NOW })
expect(r.findings[0]!.determination).toBe('not-assessed')
expect(r.findings[0]!.detail).toContain('may be current; nothing here establishes that')
})
it('reports an expected subject with no signal at all', () => {
const r = unify([sig({ subject: 'crm' })], { now: NOW, expectedSubjects: ['crm', 'hr', 'desk'] })
const missing = r.findings.filter(f => f.id.endsWith('/missing'))
expect(missing).toHaveLength(2)
expect(missing[0]!.detail).toContain('green because nothing was ever wired to it')
})
})
describe('shared identity is not per-subject evidence', () => {
// Several products reporting through ONE error-tracking project. A
// per-product search returning nothing means "not separable", not "no errors".
const shared = [
sig({ source: 'sentry', subject: 'crm', metric: 'errors', value: 0, attributedTo: 'shared-project' }),
sig({ source: 'sentry', subject: 'hr', metric: 'errors', value: 0, attributedTo: 'shared-project' }),
sig({ source: 'sentry', subject: 'desk', metric: 'errors', value: 0, attributedTo: 'shared-project' }),
]
it('names the entangled subjects rather than saying "ambiguous"', () => {
const groups = ambiguousAttribution(shared)
expect(groups).toHaveLength(1)
expect(groups[0]!.subjects).toEqual(['crm', 'desk', 'hr'])
})
it('refuses to read a shared-identity zero as a per-subject pass', () => {
const r = unify(shared, { now: NOW })
expect(overall(r)).toBe('not-assessed')
for (const f of r.findings.filter(f => f.id.startsWith('crm/'))) {
expect(f.determination).toBe('not-assessed')
}
})
it('says what a search returning nothing actually means', () => {
const text = formatPane(shared, { now: NOW })
expect(text).toContain('absence of attributed events is not absence of events')
expect(text).toContain('crm, desk, hr')
})
it('leaves a uniquely-attributed signal alone', () => {
const distinct = [
sig({ source: 'sentry', subject: 'crm', attributedTo: 'crm-prod' }),
sig({ source: 'sentry', subject: 'hr', attributedTo: 'hr-prod' }),
]
expect(ambiguousAttribution(distinct)).toEqual([])
expect(overall(unify(distinct, { now: NOW }))).toBe('pass')
})
it('does not entangle the same identity across different sources', () => {
// gatus/"prod" and sentry/"prod" are unrelated namespaces.
const r = ambiguousAttribution([
sig({ source: 'gatus', subject: 'crm', attributedTo: 'prod' }),
sig({ source: 'sentry', subject: 'hr', attributedTo: 'prod' }),
])
expect(r).toEqual([])
})
})
describe('health states', () => {
it('reports down as critical and degraded as high', () => {
expect(unify([sig({ health: 'down' })], { now: NOW }).findings[0]!.severity).toBe('critical')
expect(unify([sig({ health: 'degraded' })], { now: NOW }).findings[0]!.severity).toBe('high')
expect(overall(unify([sig({ health: 'down' })], { now: NOW }))).toBe('fail')
})
it('reports an uncomputable value as not-assessed, not zero', () => {
const r = unify([sig({ value: null, health: undefined })], { now: NOW })
expect(r.findings[0]!.determination).toBe('not-assessed')
})
it('carries the sample count into the citation for a null value', () => {
const text = formatPane([sig({ value: null, health: undefined })], { now: NOW, evidence: 'inline' })
expect(text).toContain('not computable')
expect(text).toContain('0 samples')
})
})
describe('conformance with the evidence-record standard', () => {
it('an empty board is not-assessed, never healthy', () => {
const r = unify([], { now: NOW })
expect(overall(r)).toBe('not-assessed')
expect(formatPane([], { now: NOW })).toContain('An empty board is not a healthy one')
})
it('every conclusion carries a citation', () => {
expect(validateReport(unify([sig(), sig({ subject: 'hr', health: 'down' })], { now: NOW }))).toEqual([])
})
})