The inbound-ingest matcher read a stored card_uid as both decimal and hex, which mis-attributed all-digit uids (a stored "16" is hex 0x16 = 22, but it wrongly matched a decimal poll of card 16) and was conceptually wrong. HeatSync stores the uid as hex (the grant encoder requires it) and the ?z log / webhooks report the decimal card number, so cardMatchesEvent now matches a reported decimal when hexInt(storedUid) equals it, and matches a webhook card_uid by exact string. This removes the false positive while keeping every legit match. Attribution is audit-only (never an access decision) and inert until a space enables ingest; no migration. Also records the pass-63/64 door-epic deploy state and the pass-65 audit in the handoff log.
164 lines
6.2 KiB
TypeScript
164 lines
6.2 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
parseHeatSyncLog,
|
|
normalizeWebhookEvents,
|
|
cardMatchesEvent,
|
|
type DoorIngestEvent,
|
|
} from '@/lib/door-log-logic'
|
|
|
|
function ev(p: Partial<DoorIngestEvent>): DoorIngestEvent {
|
|
return { result: 'granted', cardNumber: null, cardUid: null, occurredAt: null, detail: '', dedupeKey: '', ...p }
|
|
}
|
|
|
|
describe('parseHeatSyncLog', () => {
|
|
it('parses a granted event and reconstructs a small card number', () => {
|
|
const log = '<pre>\nG: 12345\ng: 0\n</pre>'
|
|
const ev = parseHeatSyncLog(log)
|
|
expect(ev).toHaveLength(1)
|
|
expect(ev[0].result).toBe('granted')
|
|
expect(ev[0].cardNumber).toBe('12345')
|
|
expect(ev[0].cardUid).toBeNull()
|
|
})
|
|
|
|
it('reconstructs a card number split across the divisor (high*32767 + low)', () => {
|
|
// 40000 = 1*32767 + 7233
|
|
const log = 'G: 7233\ng: 1'
|
|
const ev = parseHeatSyncLog(log)
|
|
expect(ev[0].cardNumber).toBe('40000')
|
|
})
|
|
|
|
it('parses a denied event from D/d', () => {
|
|
const log = 'D: 100\nd: 0'
|
|
const ev = parseHeatSyncLog(log)
|
|
expect(ev[0].result).toBe('denied')
|
|
expect(ev[0].cardNumber).toBe('100')
|
|
})
|
|
|
|
it('stamps best-effort H:M:E time-of-day when present', () => {
|
|
const log = 'H: 9\nM: 5\nE: 3\nG: 50\ng: 0'
|
|
const ev = parseHeatSyncLog(log)
|
|
expect(ev[0].detail).toContain('09:05:03')
|
|
expect(ev[0].dedupeKey).toContain('09:05:03')
|
|
})
|
|
|
|
it('ignores empty ring slots (NUL keys) and unrelated keys', () => {
|
|
const log = ' |