mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-11-06 04:42:24 +00:00
Merge pull request #276 from PR0M3TH3AN/test-roster-loading-logic-11897552742199720242
Add unit tests for roster loading logic
This commit is contained in:
3 files changed
+251
-10
No files matched your search
@@ -117,7 +117,7 @@
|
|||||||
"importance": 0.25,
|
"importance": 0.25,
|
||||||
"embedding_id": null,
|
"embedding_id": null,
|
||||||
"created_at": 1771370050385,
|
"created_at": 1771370050385,
|
||||||
"last_seen": 1771370148086,
|
"last_seen": 1771385274968,
|
||||||
"source": "ingest",
|
"source": "ingest",
|
||||||
"ttl_days": null,
|
"ttl_days": null,
|
||||||
"merged_into": null,
|
"merged_into": null,
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
"importance": 0.25,
|
"importance": 0.25,
|
||||||
"embedding_id": null,
|
"embedding_id": null,
|
||||||
"created_at": 1771370084740,
|
"created_at": 1771370084740,
|
||||||
"last_seen": 1771370148086,
|
"last_seen": 1771385274968,
|
||||||
"source": "ingest",
|
"source": "ingest",
|
||||||
"ttl_days": null,
|
"ttl_days": null,
|
||||||
"merged_into": null,
|
"merged_into": null,
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
"importance": 0.25,
|
"importance": 0.25,
|
||||||
"embedding_id": null,
|
"embedding_id": null,
|
||||||
"created_at": 1771370148079,
|
"created_at": 1771370148079,
|
||||||
"last_seen": 1771370148086,
|
"last_seen": 1771385274968,
|
||||||
"source": "ingest",
|
"source": "ingest",
|
||||||
"ttl_days": null,
|
"ttl_days": null,
|
||||||
"merged_into": null,
|
"merged_into": null,
|
||||||
@@ -244,5 +244,97 @@
|
|||||||
"merged_into": null,
|
"merged_into": null,
|
||||||
"pinned": false
|
"pinned": false
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"0c0f8d8f-b1c5-4b4f-bd15-4a53462f3a31",
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"id": "0c0f8d8f-b1c5-4b4f-bd15-4a53462f3a31",
|
||||||
|
"agent_id": "agent-telemetry",
|
||||||
|
"session_id": "s-telemetry",
|
||||||
|
"type": "batch_window",
|
||||||
|
"content": "customer token ABC123 secret",
|
||||||
|
"summary": "customer token ABC123 secret",
|
||||||
|
"tags": [
|
||||||
|
"incident"
|
||||||
|
],
|
||||||
|
"importance": 0.25,
|
||||||
|
"embedding_id": null,
|
||||||
|
"created_at": 1771385166452,
|
||||||
|
"last_seen": 1771385274968,
|
||||||
|
"source": "ingest",
|
||||||
|
"ttl_days": null,
|
||||||
|
"merged_into": null,
|
||||||
|
"pinned": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"5c3af7fa-cab8-4bcf-b1dc-fc845d611026",
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"id": "5c3af7fa-cab8-4bcf-b1dc-fc845d611026",
|
||||||
|
"agent_id": "agent-telemetry",
|
||||||
|
"session_id": "s-telemetry",
|
||||||
|
"type": "batch_window",
|
||||||
|
"content": "customer token ABC123 secret",
|
||||||
|
"summary": "customer token ABC123 secret",
|
||||||
|
"tags": [
|
||||||
|
"incident"
|
||||||
|
],
|
||||||
|
"importance": 0.25,
|
||||||
|
"embedding_id": null,
|
||||||
|
"created_at": 1771385220726,
|
||||||
|
"last_seen": 1771385274968,
|
||||||
|
"source": "ingest",
|
||||||
|
"ttl_days": null,
|
||||||
|
"merged_into": null,
|
||||||
|
"pinned": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"65e6d5f8-74fe-48cb-b1ab-b9b3d5a4ae85",
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"id": "65e6d5f8-74fe-48cb-b1ab-b9b3d5a4ae85",
|
||||||
|
"agent_id": "agent-telemetry",
|
||||||
|
"session_id": "s-telemetry",
|
||||||
|
"type": "batch_window",
|
||||||
|
"content": "customer token ABC123 secret",
|
||||||
|
"summary": "customer token ABC123 secret",
|
||||||
|
"tags": [
|
||||||
|
"incident"
|
||||||
|
],
|
||||||
|
"importance": 0.25,
|
||||||
|
"embedding_id": null,
|
||||||
|
"created_at": 1771385242873,
|
||||||
|
"last_seen": 1771385274968,
|
||||||
|
"source": "ingest",
|
||||||
|
"ttl_days": null,
|
||||||
|
"merged_into": null,
|
||||||
|
"pinned": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"f6c9edde-0629-4853-ab41-e90c9700dbc3",
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"id": "f6c9edde-0629-4853-ab41-e90c9700dbc3",
|
||||||
|
"agent_id": "agent-telemetry",
|
||||||
|
"session_id": "s-telemetry",
|
||||||
|
"type": "batch_window",
|
||||||
|
"content": "customer token ABC123 secret",
|
||||||
|
"summary": "customer token ABC123 secret",
|
||||||
|
"tags": [
|
||||||
|
"incident"
|
||||||
|
],
|
||||||
|
"importance": 0.25,
|
||||||
|
"embedding_id": null,
|
||||||
|
"created_at": 1771385274962,
|
||||||
|
"last_seen": 1771385274968,
|
||||||
|
"source": "ingest",
|
||||||
|
"ttl_days": null,
|
||||||
|
"merged_into": null,
|
||||||
|
"pinned": false
|
||||||
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
+17
-7
@@ -53,20 +53,20 @@ const FALLBACK_ROSTER = {
|
|||||||
|
|
||||||
let cachedCanonicalRoster = null;
|
let cachedCanonicalRoster = null;
|
||||||
|
|
||||||
function loadCanonicalRoster() {
|
function loadCanonicalRoster(fsModule = fs) {
|
||||||
if (cachedCanonicalRoster) return cachedCanonicalRoster;
|
if (cachedCanonicalRoster) return cachedCanonicalRoster;
|
||||||
|
|
||||||
let rosterPath = ROSTER_FILE;
|
let rosterPath = ROSTER_FILE;
|
||||||
|
|
||||||
// Prefer user-managed roster if present
|
// Prefer user-managed roster if present
|
||||||
if (fs.existsSync(USER_ROSTER_FILE)) {
|
if (fsModule.existsSync(USER_ROSTER_FILE)) {
|
||||||
rosterPath = USER_ROSTER_FILE;
|
rosterPath = USER_ROSTER_FILE;
|
||||||
} else if (fs.existsSync(CWD_ROSTER_FILE)) {
|
} else if (fsModule.existsSync(CWD_ROSTER_FILE)) {
|
||||||
rosterPath = CWD_ROSTER_FILE;
|
rosterPath = CWD_ROSTER_FILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(fs.readFileSync(rosterPath, 'utf8'));
|
const parsed = JSON.parse(fsModule.readFileSync(rosterPath, 'utf8'));
|
||||||
const daily = Array.isArray(parsed.daily) ? parsed.daily.map((item) => String(item).trim()).filter(Boolean) : [];
|
const daily = Array.isArray(parsed.daily) ? parsed.daily.map((item) => String(item).trim()).filter(Boolean) : [];
|
||||||
const weekly = Array.isArray(parsed.weekly)
|
const weekly = Array.isArray(parsed.weekly)
|
||||||
? parsed.weekly.map((item) => String(item).trim()).filter(Boolean)
|
? parsed.weekly.map((item) => String(item).trim()).filter(Boolean)
|
||||||
@@ -86,6 +86,11 @@ function loadCanonicalRoster() {
|
|||||||
return cachedCanonicalRoster;
|
return cachedCanonicalRoster;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function _resetRosterCache() {
|
||||||
|
cachedCanonicalRoster = null;
|
||||||
|
}
|
||||||
|
|
||||||
function parseEnvRoster(value) {
|
function parseEnvRoster(value) {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
return value
|
return value
|
||||||
@@ -94,11 +99,16 @@ function parseEnvRoster(value) {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRoster(cadence) {
|
export async function getRoster(cadence, deps = {}) {
|
||||||
const config = await loadTorchConfig();
|
const {
|
||||||
|
loadTorchConfig: loadTorchConfigFn = loadTorchConfig,
|
||||||
|
fs: fsModule = fs,
|
||||||
|
} = deps;
|
||||||
|
|
||||||
|
const config = await loadTorchConfigFn();
|
||||||
const dailyFromEnv = parseEnvRoster(process.env.NOSTR_LOCK_DAILY_ROSTER);
|
const dailyFromEnv = parseEnvRoster(process.env.NOSTR_LOCK_DAILY_ROSTER);
|
||||||
const weeklyFromEnv = parseEnvRoster(process.env.NOSTR_LOCK_WEEKLY_ROSTER);
|
const weeklyFromEnv = parseEnvRoster(process.env.NOSTR_LOCK_WEEKLY_ROSTER);
|
||||||
const canonical = loadCanonicalRoster();
|
const canonical = loadCanonicalRoster(fsModule);
|
||||||
const dailyFromConfig = config.nostrLock.dailyRoster;
|
const dailyFromConfig = config.nostrLock.dailyRoster;
|
||||||
const weeklyFromConfig = config.nostrLock.weeklyRoster;
|
const weeklyFromConfig = config.nostrLock.weeklyRoster;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import { describe, it, beforeEach, afterEach, mock } from 'node:test';
|
||||||
|
import assert from 'node:assert';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { getRoster, _resetRosterCache } from '../src/roster.mjs';
|
||||||
|
|
||||||
|
describe('Roster (Unit Tests with Dependency Injection)', () => {
|
||||||
|
const CWD = process.cwd();
|
||||||
|
const USER_ROSTER_FILE = path.resolve(CWD, 'torch/roster.json');
|
||||||
|
const CWD_ROSTER_FILE = path.resolve(CWD, 'roster.json');
|
||||||
|
|
||||||
|
const mockFs = {
|
||||||
|
existsSync: mock.fn(),
|
||||||
|
readFileSync: mock.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockLoadTorchConfig = mock.fn();
|
||||||
|
const originalConsoleError = console.error;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
_resetRosterCache();
|
||||||
|
mockFs.existsSync.mock.resetCalls();
|
||||||
|
mockFs.readFileSync.mock.resetCalls();
|
||||||
|
mockLoadTorchConfig.mock.resetCalls();
|
||||||
|
console.error = mock.fn();
|
||||||
|
|
||||||
|
// Default: config returns empty nostrLock
|
||||||
|
mockLoadTorchConfig.mock.mockImplementation(async () => ({ nostrLock: {} }));
|
||||||
|
|
||||||
|
// Default: fs.existsSync returns false (no files found)
|
||||||
|
mockFs.existsSync.mock.mockImplementation(() => false);
|
||||||
|
|
||||||
|
// Default: fs.readFileSync returns empty object
|
||||||
|
mockFs.readFileSync.mock.mockImplementation(() => '{}');
|
||||||
|
|
||||||
|
// Clear env vars
|
||||||
|
delete process.env.NOSTR_LOCK_DAILY_ROSTER;
|
||||||
|
delete process.env.NOSTR_LOCK_WEEKLY_ROSTER;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mock.reset();
|
||||||
|
console.error = originalConsoleError;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns fallback roster when no configuration or files exist', async () => {
|
||||||
|
const deps = { fs: mockFs, loadTorchConfig: mockLoadTorchConfig };
|
||||||
|
const roster = await getRoster('daily', deps);
|
||||||
|
assert.ok(Array.isArray(roster));
|
||||||
|
assert.ok(roster.length > 0);
|
||||||
|
assert.ok(roster.includes('audit-agent'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads roster from torch/roster.json (User Roster) if present', async () => {
|
||||||
|
mockFs.existsSync.mock.mockImplementation((filepath) => filepath === USER_ROSTER_FILE);
|
||||||
|
mockFs.readFileSync.mock.mockImplementation((filepath) => {
|
||||||
|
if (filepath === USER_ROSTER_FILE) {
|
||||||
|
return JSON.stringify({ daily: ['user-agent-daily'], weekly: ['user-agent-weekly'] });
|
||||||
|
}
|
||||||
|
throw new Error('ENOENT');
|
||||||
|
});
|
||||||
|
|
||||||
|
const deps = { fs: mockFs, loadTorchConfig: mockLoadTorchConfig };
|
||||||
|
const daily = await getRoster('daily', deps);
|
||||||
|
const weekly = await getRoster('weekly', deps);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(daily, ['user-agent-daily']);
|
||||||
|
assert.deepStrictEqual(weekly, ['user-agent-weekly']);
|
||||||
|
assert.strictEqual(mockFs.readFileSync.mock.calls.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads roster from roster.json (CWD Roster) if present and torch/roster.json is missing', async () => {
|
||||||
|
mockFs.existsSync.mock.mockImplementation((filepath) => filepath === CWD_ROSTER_FILE);
|
||||||
|
mockFs.readFileSync.mock.mockImplementation((filepath) => {
|
||||||
|
if (filepath === CWD_ROSTER_FILE) {
|
||||||
|
return JSON.stringify({ daily: ['cwd-agent-daily'], weekly: ['cwd-agent-weekly'] });
|
||||||
|
}
|
||||||
|
throw new Error('ENOENT');
|
||||||
|
});
|
||||||
|
|
||||||
|
const deps = { fs: mockFs, loadTorchConfig: mockLoadTorchConfig };
|
||||||
|
const daily = await getRoster('daily', deps);
|
||||||
|
assert.deepStrictEqual(daily, ['cwd-agent-daily']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to default if roster file is malformed', async () => {
|
||||||
|
mockFs.existsSync.mock.mockImplementation((filepath) => filepath === USER_ROSTER_FILE);
|
||||||
|
mockFs.readFileSync.mock.mockImplementation(() => '{ invalid json }');
|
||||||
|
|
||||||
|
const deps = { fs: mockFs, loadTorchConfig: mockLoadTorchConfig };
|
||||||
|
// Should catch error and return fallback
|
||||||
|
const daily = await getRoster('daily', deps);
|
||||||
|
assert.ok(daily.includes('audit-agent'));
|
||||||
|
|
||||||
|
// Should have tried to read file
|
||||||
|
assert.strictEqual(mockFs.readFileSync.mock.calls.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to default if roster file is missing daily/weekly arrays', async () => {
|
||||||
|
mockFs.existsSync.mock.mockImplementation((filepath) => filepath === USER_ROSTER_FILE);
|
||||||
|
mockFs.readFileSync.mock.mockImplementation(() => JSON.stringify({ other: [] }));
|
||||||
|
|
||||||
|
const deps = { fs: mockFs, loadTorchConfig: mockLoadTorchConfig };
|
||||||
|
const daily = await getRoster('daily', deps);
|
||||||
|
assert.ok(daily.includes('audit-agent'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prioritizes environment variables over file and config', async () => {
|
||||||
|
process.env.NOSTR_LOCK_DAILY_ROSTER = 'env-agent-daily';
|
||||||
|
|
||||||
|
// Setup file to exist
|
||||||
|
mockFs.existsSync.mock.mockImplementation((filepath) => filepath === USER_ROSTER_FILE);
|
||||||
|
mockFs.readFileSync.mock.mockImplementation(() => JSON.stringify({ daily: ['file-agent'] }));
|
||||||
|
|
||||||
|
const deps = { fs: mockFs, loadTorchConfig: mockLoadTorchConfig };
|
||||||
|
const daily = await getRoster('daily', deps);
|
||||||
|
assert.deepStrictEqual(daily, ['env-agent-daily']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prioritizes config over file', async () => {
|
||||||
|
mockLoadTorchConfig.mock.mockImplementation(async () => ({
|
||||||
|
nostrLock: { dailyRoster: ['config-agent-daily'] }
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Setup file to exist
|
||||||
|
mockFs.existsSync.mock.mockImplementation((filepath) => filepath === USER_ROSTER_FILE);
|
||||||
|
mockFs.readFileSync.mock.mockImplementation(() => JSON.stringify({ daily: ['file-agent'] }));
|
||||||
|
|
||||||
|
const deps = { fs: mockFs, loadTorchConfig: mockLoadTorchConfig };
|
||||||
|
const daily = await getRoster('daily', deps);
|
||||||
|
assert.deepStrictEqual(daily, ['config-agent-daily']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles multiple items in env var roster', async () => {
|
||||||
|
process.env.NOSTR_LOCK_DAILY_ROSTER = 'agent1, agent2,agent3 ';
|
||||||
|
const deps = { fs: mockFs, loadTorchConfig: mockLoadTorchConfig };
|
||||||
|
const daily = await getRoster('daily', deps);
|
||||||
|
assert.deepStrictEqual(daily, ['agent1', 'agent2', 'agent3']);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user