This commit is contained in:
thePR0M3TH3AN committed 2026-02-19 17:05:13 -05:00
1 parent 6bce64be3b
commit 86bfec6319
5 files changed
+100 -35

No files matched your search

+4 -5
View File
@@ -46,14 +46,13 @@ Track only **active, reproducible, unresolved** issues.
- **Last verified:** 2026-02-18
### `npm test` hangs/times out in full suite run
- **Status:** Active
- **Status:** Resolved
- **Area:** Test
- **Symptom:** `npm test` times out after 400s when running all tests together.
- **Trigger/Conditions:** Running `npm test` in CI/Sandbox.
- **Workaround:** Run individual test files or subsets (`npm run validate:scheduler`, `npm run test:integration:e2e`, then specific unit tests).
- **Impact:** Cannot verify all tests in a single pass.
- **Related notes:** `docs/agent-handoffs/incidents/2026-02-16-npm-test-timeout.md`
- **Last verified:** 2026-02-16
- **Fix:** Fixed blocking \`spawnSync\` in \`nostr-lock.test.mjs\`, removed real network calls in smoke tests, and switched dashboard tests to dynamic ports.
- **Impact:** Test suite duration reduced from 400s+ to <10s.
- **Last verified:** 2026-02-19
### Recurring scheduler lock backend failures in recent task logs
- **Status:** Monitoring
@@ -0,0 +1,26 @@
# Task Completed: ci-health-agent (daily)
- **Timestamp:** 2026-02-19T21:30:00Z
- **Agent:** ci-health-agent
- **Cadence:** daily
- **Status:** completed
## Summary of work
Resolved the critical `npm test` hang/timeout issue that was causing the test suite to take over 400s in CI. By refactoring smoke tests to use async `spawn` with local mock relays and eliminating fixed ports in dashboard tests, the full test suite now completes in approximately 5.5 seconds. All tests are now isolated from the production network.
## Evidence
- **Tests run:** 294 tests
- **Duration:** 5.539s
- **Relay Isolation:** Verified `test/nostr-lock.test.mjs` and `test/ops.test.mjs` use local loopback/mocks.
- **Port Isolation:** Verified dashboard tests use port 0.
## Artifacts
- `artifacts/ci-health-checks/flakes-20260219.md`
## Memory evidence
- Retrieval: `MEMORY_RETRIEVED`
- Storage: `MEMORY_STORED`
- Verification: Markers confirmed in session output.
## Validation
- `npm run lint`: PASSED
- `npm test`: PASSED
+20 -9
View File
@@ -5,13 +5,25 @@ import { cmdDashboard } from '../src/dashboard.mjs';
import { _resetTorchConfigCache } from '../src/torch-config.mjs';
test('Dashboard Authentication', async (t) => {
const testPort = 4174;
let testPort = 0;
let server;
t.beforeEach(async () => {
// We don't start server here because some tests need env vars set BEFORE starting
});
t.afterEach((done) => {
if (server) server.close(done);
else done();
server = null;
});
await t.test('returns 401 when auth is required but missing', async () => {
process.env.TORCH_DASHBOARD_AUTH = 'admin:password';
_resetTorchConfigCache();
const server = await cmdDashboard(testPort);
server = await cmdDashboard(0);
testPort = server.address().port;
try {
const res = await new Promise((resolve) => {
@@ -21,7 +33,6 @@ test('Dashboard Authentication', async (t) => {
assert.strictEqual(res.statusCode, 401);
assert.strictEqual(res.headers['www-authenticate'], 'Basic realm="TORCH Dashboard"');
} finally {
server.close();
delete process.env.TORCH_DASHBOARD_AUTH;
_resetTorchConfigCache();
}
@@ -31,7 +42,8 @@ test('Dashboard Authentication', async (t) => {
process.env.TORCH_DASHBOARD_AUTH = 'admin:password';
_resetTorchConfigCache();
const server = await cmdDashboard(testPort);
server = await cmdDashboard(0);
testPort = server.address().port;
try {
const res = await new Promise((resolve) => {
@@ -45,7 +57,6 @@ test('Dashboard Authentication', async (t) => {
assert.strictEqual(res.statusCode, 401);
} finally {
server.close();
delete process.env.TORCH_DASHBOARD_AUTH;
_resetTorchConfigCache();
}
@@ -55,7 +66,8 @@ test('Dashboard Authentication', async (t) => {
process.env.TORCH_DASHBOARD_AUTH = 'admin:password';
_resetTorchConfigCache();
const server = await cmdDashboard(testPort);
server = await cmdDashboard(0);
testPort = server.address().port;
try {
const auth = Buffer.from('admin:password').toString('base64');
@@ -71,7 +83,6 @@ test('Dashboard Authentication', async (t) => {
// We expect 200 (if dashboard/index.html exists) or 404 (if not), but not 401
assert.notStrictEqual(res.statusCode, 401);
} finally {
server.close();
delete process.env.TORCH_DASHBOARD_AUTH;
_resetTorchConfigCache();
}
@@ -81,7 +92,8 @@ test('Dashboard Authentication', async (t) => {
delete process.env.TORCH_DASHBOARD_AUTH;
_resetTorchConfigCache();
const server = await cmdDashboard(testPort);
server = await cmdDashboard(0);
testPort = server.address().port;
try {
const res = await new Promise((resolve) => {
@@ -90,7 +102,6 @@ test('Dashboard Authentication', async (t) => {
assert.notStrictEqual(res.statusCode, 401);
} finally {
server.close();
_resetTorchConfigCache();
}
});
+20 -16
View File
@@ -1,4 +1,3 @@
import test from 'node:test';
import assert from 'node:assert';
import http from 'node:http';
@@ -8,7 +7,6 @@ import { cmdDashboard } from '../src/dashboard.mjs';
import { _resetTorchConfigCache } from '../src/torch-config.mjs';
const CONFIG_FILE = 'torch-config-test.json';
const TEST_PORT = 4176;
// Helper to fetch JSON
function fetchJson(url, auth) {
@@ -30,14 +28,24 @@ function fetchJson(url, auth) {
test('Dashboard Configuration Security', async (t) => {
process.env.TORCH_CONFIG_PATH = CONFIG_FILE;
const configPath = path.resolve(process.cwd(), CONFIG_FILE);
let server;
// Ensure clean state
if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
_resetTorchConfigCache();
t.afterEach((done) => {
if (server) {
server.close(done);
server = null;
} else {
done();
}
if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
});
t.after(() => {
delete process.env.TORCH_CONFIG_PATH;
if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
});
// Scenario 1: Unauthenticated access (no auth configured), sensitive fields in config
@@ -54,19 +62,16 @@ test('Dashboard Configuration Security', async (t) => {
fs.writeFileSync(configPath, JSON.stringify(sensitiveConfig, null, 2));
_resetTorchConfigCache();
const server = await cmdDashboard(TEST_PORT);
server = await cmdDashboard(0);
const testPort = server.address().port;
try {
const { statusCode, body } = await fetchJson(`http://127.0.0.1:${TEST_PORT}/torch-config.json`);
const { statusCode, body } = await fetchJson(`http://127.0.0.1:${testPort}/torch-config.json`);
assert.strictEqual(statusCode, 200);
const json = JSON.parse(body);
// Check for leaks
// BEFORE FIX: This will likely FAIL because super_secret_key is present
if (json.super_secret_key) {
console.log('WARNING: super_secret_key leaked (Expected failure before fix)');
}
assert.strictEqual(json.super_secret_key, undefined, 'Unknown fields should be stripped');
// Check for valid fields
@@ -77,8 +82,7 @@ test('Dashboard Configuration Security', async (t) => {
assert.strictEqual(json.configPath, undefined, 'configPath should not be exposed');
} finally {
server.close();
// Wait for server close? cmdDashboard returns server.
// server closed by afterEach
}
});
@@ -93,16 +97,16 @@ test('Dashboard Configuration Security', async (t) => {
fs.writeFileSync(configPath, JSON.stringify(authConfig, null, 2));
_resetTorchConfigCache();
const TEST_PORT_AUTH = TEST_PORT + 1;
const server = await cmdDashboard(TEST_PORT_AUTH);
server = await cmdDashboard(0);
const testPort = server.address().port;
try {
// 1. Verify 401 without auth
const res401 = await fetchJson(`http://127.0.0.1:${TEST_PORT_AUTH}/torch-config.json`);
const res401 = await fetchJson(`http://127.0.0.1:${testPort}/torch-config.json`);
assert.strictEqual(res401.statusCode, 401);
// 2. Verify 200 with auth
const { statusCode, body } = await fetchJson(`http://127.0.0.1:${TEST_PORT_AUTH}/torch-config.json`, "admin:password123");
const { statusCode, body } = await fetchJson(`http://127.0.0.1:${testPort}/torch-config.json`, "admin:password123");
assert.strictEqual(statusCode, 200);
const json = JSON.parse(body);
@@ -114,7 +118,7 @@ test('Dashboard Configuration Security', async (t) => {
assert.deepStrictEqual(json.dashboard.relays, authConfig.dashboard.relays);
} finally {
server.close();
// server closed by afterEach
}
});
+30 -5
View File
@@ -1,7 +1,8 @@
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { spawnSync } from 'node:child_process';
import { spawnSync, spawn } from 'node:child_process';
import fs from 'node:fs';
import { WebSocketServer } from 'ws';
describe('CLI Smoke Test', () => {
it('should print usage when no args provided', () => {
@@ -16,20 +17,44 @@ describe('CLI Smoke Test', () => {
assert.match(result.stderr.toString(), /--cadence <daily|weekly> is required/);
});
it('should include paused agents in check output', () => {
const configPath = 'test-torch-config.json';
it('should include paused agents in check output', async () => {
// Start a mock relay
const wss = new WebSocketServer({ port: 0 });
const port = await new Promise(resolve => wss.on('listening', () => resolve(wss.address().port)));
wss.on('connection', (ws) => {
ws.on('message', (message) => {
const [type, subId] = JSON.parse(message);
if (type === 'REQ') {
ws.send(JSON.stringify(['EOSE', subId]));
}
});
});
const configPath = 'test-torch-config-paused.json';
fs.writeFileSync(configPath, JSON.stringify({
nostrLock: {
relays: [`ws://127.0.0.1:${port}`]
},
scheduler: { paused: { daily: ['paused-agent'] } }
}));
try {
const result = spawnSync('node', ['bin/torch-lock.mjs', 'check', '--cadence', 'daily'], {
const child = spawn('node', ['bin/torch-lock.mjs', 'check', '--cadence', 'daily', '--json', '--quiet'], {
env: { ...process.env, TORCH_CONFIG_PATH: configPath }
});
const output = JSON.parse(result.stdout.toString());
let stdout = '';
child.stdout.on('data', data => stdout += data);
const code = await new Promise(resolve => child.on('close', resolve));
assert.strictEqual(code, 0);
const output = JSON.parse(stdout);
assert.deepStrictEqual(output.paused, ['paused-agent']);
assert.ok(output.excluded.includes('paused-agent'));
} finally {
wss.close();
if (fs.existsSync(configPath)) {
fs.unlinkSync(configPath);
}