quality / changes (push) Successful in 7s
quality / commitlint (push) Skipped
quality / openapi (push) Successful in 30s
quality / web (push) Successful in 1m3s
quality / api (push) Successful in 1m15s
quality / linux-agent-smoke (push) Failing after 27s
quality / docker-check (push) Failing after 2m28s
CD / quality (push) Failing after 5m52s
CD / publish (push) Skipped
Implement reliable agent apply, telemetry v2, analytics retention, guarded rollouts, enrollment, labels and webhooks. Improve ReUI frontend data safety and bounded list loading, and add Docker smoke, recovery and resource checks. Document local verification and pending native acceptance.
100 lines
6.9 KiB
TypeScript
100 lines
6.9 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { createMemoryDb, runMigrations, repos } from '@evofw/db'
|
|
|
|
const opened: ReturnType<typeof createMemoryDb>[] = []
|
|
afterEach(() => { vi.restoreAllMocks(); for (const mem of opened.splice(0)) mem.sqlite.close() })
|
|
const first = '2026-01-01T00:00:00.000Z'
|
|
const second = '2026-01-01T00:01:00.000Z'
|
|
function memory() {
|
|
const mem = createMemoryDb(); opened.push(mem); runMigrations(mem.sqlite)
|
|
for (const id of ['a', 'b']) repos.insertAgent(mem.db, { id, name: id, tokenPrefix: id, tokenHash: id, status: 'approved' })
|
|
return mem
|
|
}
|
|
function hits(count: number, packets: number) {
|
|
return Array.from({ length: count }, (_, index) => ({ ip: `10.${Math.floor(index / 256)}.${index % 256}.1`, port: index + 1, protocol: 'tcp', packets }))
|
|
}
|
|
|
|
describe('bounded detail counter batches', () => {
|
|
for (const count of [200, 500]) it(`preserves ${count} detail baselines and prepares bounded queries`, () => {
|
|
const { db, sqlite } = memory()
|
|
// Older rows sharing the same IP must not expand the port prefetch result.
|
|
repos.upsertPortBlockStats(db, 'a', Array.from({ length: 900 }, (_, index) => ({ ip: '10.0.0.1', port: index + 1000, protocol: 'udp', packets: 1 })), first)
|
|
repos.upsertIpBlockStats(db, 'b', hits(count, 999), first)
|
|
const prepare = vi.spyOn(sqlite, 'prepare')
|
|
const started = performance.now()
|
|
repos.upsertIpBlockStats(db, 'a', hits(count, 100), first, { counterEpoch: 'one' })
|
|
repos.upsertPortBlockStats(db, 'a', hits(count, 100), first, { counterEpoch: 'one' })
|
|
const calls = prepare.mock.calls.map(([query]) => String(query))
|
|
expect(calls.filter((query) => /^select /i.test(query))).toHaveLength(2 * Math.ceil(count / 200))
|
|
expect(calls.filter((query) => /^insert into /i.test(query))).toHaveLength(2)
|
|
expect(calls.filter((query) => /^select /i.test(query)).every((query) => query.includes(' in (') || query.includes('"port" = ?'))).toBe(true)
|
|
prepare.mockRestore()
|
|
const unknown = vi.fn()
|
|
repos.upsertIpBlockStats(db, 'a', hits(count, 105), second, { counterEpoch: 'one', onUnknownInterval: unknown })
|
|
repos.upsertPortBlockStats(db, 'a', hits(count, 105), second, { counterEpoch: 'one', onUnknownInterval: unknown })
|
|
expect(unknown).not.toHaveBeenCalled()
|
|
expect(repos.listIpBlockStats(db, 'a', count + 1)).toHaveLength(count)
|
|
expect(repos.listIpBlockStats(db, 'a', count + 1).every((row) => row.packets === 5 && row.firstSeenAt === first && row.lastSeenAt === second)).toBe(true)
|
|
expect(sqlite.prepare('SELECT count(*) AS n FROM agent_port_block_stats WHERE agent_id = ? AND protocol = ? AND packets = ?').get('a', 'tcp', 5)).toEqual({ n: count })
|
|
expect(repos.listIpBlockStats(db, 'b', count + 1).every((row) => row.packets === 999)).toBe(true)
|
|
repos.upsertIpBlockStats(db, 'a', hits(count, 1), second, { counterEpoch: 'two', onUnknownInterval: unknown })
|
|
repos.upsertPortBlockStats(db, 'a', hits(count, 1), second, { counterEpoch: 'two', onUnknownInterval: unknown })
|
|
expect(unknown).toHaveBeenCalledTimes(2 * count)
|
|
expect(repos.listIpBlockStats(db, 'a', count).every((row) => row.packets === 5)).toBe(true)
|
|
expect(sqlite.prepare('SELECT count(*) AS n FROM agent_port_block_stats WHERE agent_id = ? AND protocol = ? AND packets = ?').get('a', 'tcp', 5)).toEqual({ n: count })
|
|
console.info(`detail batch ${count}: baseline + delta + epoch reset, both kinds ${(performance.now() - started).toFixed(1)}ms`)
|
|
})
|
|
|
|
it('preserves sequential duplicates, legacy resets, timestamps and presence re-hits', () => {
|
|
const { db } = memory()
|
|
const ip = '192.0.2.1'
|
|
const duplicate = [10, 15, 2, 4].map((packets) => ({ ip, packets, port: 22, protocol: 'tcp' }))
|
|
repos.upsertIpBlockStats(db, 'a', duplicate, first)
|
|
repos.upsertPortBlockStats(db, 'a', duplicate, first)
|
|
expect(repos.listIpBlockStats(db, 'a')[0]!.packets).toBe(19)
|
|
expect(repos.listPortBlockStatsAggregate(db, 'a')[0]!.packets).toBe(19)
|
|
repos.upsertIpBlockStats(db, 'a', [{ ip, packets: 4 }], second)
|
|
expect(repos.listIpBlockStats(db, 'a')[0]!.lastSeenAt).toBe(first)
|
|
repos.resetIpBlockStatsBaselines(db, 'a')
|
|
repos.resetPortBlockStatsBaselines(db, 'a')
|
|
repos.upsertIpBlockStats(db, 'a', [{ ip, packets: 3 }], second)
|
|
repos.upsertPortBlockStats(db, 'a', [{ ip, port: 22, protocol: 'TCP', packets: 3 }], second)
|
|
expect(repos.listIpBlockStats(db, 'a')[0]!.packets).toBe(22)
|
|
expect(repos.listPortBlockStatsAggregate(db, 'a')[0]!.packets).toBe(22)
|
|
const presence = [{ ip: '192.0.2.2', packets: 50 }, { ip: '192.0.2.2', packets: 99 }]
|
|
repos.upsertIpBlockStats(db, 'a', presence, first, { mode: 'presence' })
|
|
repos.upsertIpBlockStats(db, 'a', presence, second, { mode: 'presence' })
|
|
expect(repos.listIpBlockStats(db, 'a').find((row) => row.ip === '192.0.2.2')!.packets).toBe(1)
|
|
repos.upsertIpBlockStats(db, 'a', presence, '2026-01-01T00:04:00.000Z', { mode: 'presence' })
|
|
expect(repos.listIpBlockStats(db, 'a').find((row) => row.ip === '192.0.2.2')!.packets).toBe(2)
|
|
})
|
|
|
|
it('rolls back the complete batch on overflow and preserves callback semantics', () => {
|
|
const { db, sqlite } = memory()
|
|
repos.upsertIpBlockStats(db, 'a', [{ ip: '192.0.2.2', packets: Number.MAX_SAFE_INTEGER }], first)
|
|
const batch = [{ ip: '192.0.2.1', packets: 1 }, { ip: '192.0.2.2', packets: 1 }]
|
|
expect(() => repos.upsertIpBlockStats(db, 'a', batch, second)).toThrow('safe integer')
|
|
expect(sqlite.prepare('SELECT count(*) AS n FROM agent_ip_block_stats').get()).toEqual({ n: 1 })
|
|
const overflow = vi.fn()
|
|
repos.upsertIpBlockStats(db, 'a', batch, second, { onOverflow: overflow })
|
|
expect(overflow).toHaveBeenCalledOnce()
|
|
expect(repos.listIpBlockStats(db, 'a')[0]!.packets).toBe(Number.MAX_SAFE_INTEGER)
|
|
})
|
|
|
|
it('keeps duplicate epoch resets unknown and unchanged port timestamps stable', () => {
|
|
const { db, sqlite } = memory()
|
|
const unknown = vi.fn()
|
|
const rows = [100, 105, 1, 4].map((packets) => ({ ip: '192.0.2.1', port: 22, protocol: 'tcp', packets }))
|
|
repos.upsertIpBlockStats(db, 'a', rows, first, { counterEpoch: 'one', onUnknownInterval: unknown })
|
|
repos.upsertPortBlockStats(db, 'a', rows, first, { counterEpoch: 'one', onUnknownInterval: unknown })
|
|
expect(unknown).toHaveBeenCalledTimes(4)
|
|
expect(repos.listIpBlockStats(db, 'a')[0]!.packets).toBe(8)
|
|
expect(repos.listPortBlockStatsAggregate(db, 'a')[0]!.packets).toBe(8)
|
|
repos.upsertPortBlockStats(db, 'a', [rows[3]!], second, { counterEpoch: 'one' })
|
|
expect(repos.listPortBlockStatsAggregate(db, 'a')[0]!.lastSeenAt).toBe(first)
|
|
sqlite.prepare('UPDATE agent_port_block_stats SET packets = ?').run(Number.MAX_SAFE_INTEGER)
|
|
expect(() => repos.upsertPortBlockStats(db, 'a', [{ ...rows[3]!, packets: 5 }], second, { counterEpoch: 'one' })).toThrow('safe integer')
|
|
expect(sqlite.prepare('SELECT last_reported_packets AS n FROM agent_port_block_stats').get()).toEqual({ n: 4 })
|
|
})
|
|
})
|