mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-11-02 10:51:54 +00:00
update
This commit is contained in:
1 parent
6bce64be3b
commit
86bfec6319
5 files changed
+100
-35
No files matched your search
+4
-5
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user