mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
57404dee17
The plugin service, instance and user settings, OAuth, audit, route profile, host state and request context suites seeded plugin rows, settings fields, actions, tokens and audit entries with raw statements and read them back the same way. They now go through the ORM factories and readers over the suite's own test ORM. Two raw statements stay, marked: the PRAGMA table_info read that proves the actions scope column exists, and the audit insert that reproduces the pre-conversion appendAudit byte for byte for the hash chain parity check. The instance settings suite's stubbed isActive now follows the status the test flips through markActive instead of a synchronous read.
71 lines
4.1 KiB
TypeScript
71 lines
4.1 KiB
TypeScript
/**
|
|
* HS1 (`plugin-host-state.ts#budgetFor`'s seed read) — Plan 3j Task 3. Converted
|
|
* from a raw `better-sqlite3` connection to `PluginCapabilityAuditRepository`;
|
|
* this covers the seed's grouping (ai.complete + ai.extract vs notify.send),
|
|
* the UTC-day floor, and the `code = 'ok'` filter (a denied call never counts
|
|
* against the budget).
|
|
*/
|
|
import { describe, it, expect, beforeAll, beforeEach, afterAll, afterEach, vi } from 'vitest';
|
|
import { createSnapshotTestDb } from '../../helpers/db-mock';
|
|
import { resetTestDb } from '../../helpers/test-db';
|
|
import { createTestOrm, type TestOrm } from '../../helpers/test-orm';
|
|
import { PluginCapabilityAudit } from '../../../src/db/entities/PluginCapabilityAudit.entity';
|
|
import { updateRows } from '../../helpers/factories/rows';
|
|
import type { PluginCapabilityAuditRepository } from '../../../src/db/repositories/PluginCapabilityAudit.repository';
|
|
import { appendAudit } from '../../../src/nest/plugins/host/plugin-audit';
|
|
import { budgetFor, pluginBudgetUsage } from '../../../src/nest/plugins/host/plugin-host-state';
|
|
|
|
const testDb = createSnapshotTestDb();
|
|
let t: TestOrm;
|
|
let audit: PluginCapabilityAuditRepository;
|
|
|
|
beforeAll(async () => {
|
|
t = await createTestOrm(testDb);
|
|
audit = t.repo(PluginCapabilityAudit);
|
|
});
|
|
beforeEach(() => { resetTestDb(testDb); t.clear(); });
|
|
afterEach(() => { vi.restoreAllMocks(); });
|
|
afterAll(async () => { await t.close(); testDb.close(); });
|
|
|
|
// `budgetFor`'s `budgets` Map is deliberately module-level (see plugin-host-state.ts's
|
|
// class docstring) — it is NOT reset by `resetTestDb`/`t.clear()`, so each test below
|
|
// uses its own plugin id to avoid one test's seed being served (cached) to the next.
|
|
describe('budgetFor / pluginBudgetUsage seed read', () => {
|
|
it('HS-001: seeds ai (ai.complete + ai.extract) and notify counts separately from today\'s already-audited rows', async () => {
|
|
await appendAudit(audit, { pluginId: 'hs1', actingUserId: 1, method: 'ai.complete', resource: null, code: 'ok' });
|
|
await appendAudit(audit, { pluginId: 'hs1', actingUserId: 1, method: 'ai.extract', resource: null, code: 'ok' });
|
|
await appendAudit(audit, { pluginId: 'hs1', actingUserId: 1, method: 'notify.send', resource: null, code: 'ok' });
|
|
// A denied call must not count against the budget.
|
|
await appendAudit(audit, { pluginId: 'hs1', actingUserId: 1, method: 'ai.complete', resource: null, code: 'RESOURCE_FORBIDDEN' });
|
|
// A different plugin's calls must not bleed into this one's budget.
|
|
await appendAudit(audit, { pluginId: 'hs1-other', actingUserId: 1, method: 'ai.complete', resource: null, code: 'ok' });
|
|
|
|
const usage = await pluginBudgetUsage('hs1', audit);
|
|
expect(usage.ai).toBe(2);
|
|
expect(usage.notify).toBe(1);
|
|
});
|
|
|
|
it('HS-002: seeded only once per plugin per process lifetime — a later append is NOT re-counted on a second read', async () => {
|
|
await appendAudit(audit, { pluginId: 'hs2', actingUserId: 1, method: 'ai.complete', resource: null, code: 'ok' });
|
|
const first = await budgetFor('hs2', audit);
|
|
expect(first.used(Date.now()).ai).toBe(1);
|
|
// A direct .take() call (what a real dispatch does) advances the in-memory
|
|
// counter; the seed read itself is not re-run.
|
|
await appendAudit(audit, { pluginId: 'hs2', actingUserId: 1, method: 'ai.complete', resource: null, code: 'ok' });
|
|
const second = await budgetFor('hs2', audit);
|
|
expect(second).toBe(first); // same cached DailyBudget instance — no re-seed
|
|
expect(second.used(Date.now()).ai).toBe(1); // still 1: the second append was never taken through the budget
|
|
});
|
|
|
|
it("HS-003: ignores yesterday's rows — only today's UTC window seeds the count", async () => {
|
|
await appendAudit(audit, { pluginId: 'hs3', actingUserId: 1, method: 'ai.complete', resource: null, code: 'ok' });
|
|
// Backdate the row to yesterday, bypassing the repository (simulating a
|
|
// real prior day's audit history already on disk).
|
|
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
|
await updateRows(t, PluginCapabilityAudit, { plugin_id: 'hs3' }, { ts: yesterday });
|
|
|
|
const usage = await pluginBudgetUsage('hs3', audit);
|
|
expect(usage.ai).toBe(0);
|
|
});
|
|
});
|