Skip to content

Commit 72efb99

Browse files
committed
ci: GitHub Actions workflow + smoke tests
Add .github/workflows/ci.yml with two jobs: - unit-tests: Runs on every push/PR. Validates JS syntax, ecosystem config, compressor unit tests (28 assertions), and smoke tests (11 assertions) - integration-tests: Manual trigger only (workflow_dispatch). Requires OPENAI_API_KEY secret. Runs uptime, recovery, and chaos tests with PM2. Add test/smoke-test.js — fast (< 1s) sanity check that verifies: - All 8 utilities export expected functions - All 5 agent files exist and are non-empty - Ecosystem config defines 6 apps with valid scripts Update package.json: 'npm test' now runs both unit + smoke tests. Integration tests remain available via 'npm run test:{uptime,chaos,recovery,full}'
1 parent a6219f3 commit 72efb99

3 files changed

Lines changed: 226 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
unit-tests:
11+
name: Unit Tests + Smoke Checks
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Node.js
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: '22'
21+
cache: 'npm'
22+
23+
- name: Install dependencies
24+
run: npm ci
25+
26+
- name: Validate JS syntax
27+
run: |
28+
echo "Checking all JavaScript files parse correctly..."
29+
find src test -name '*.js' -exec node --check {} \;
30+
echo "✅ All files parse correctly"
31+
32+
- name: Validate ecosystem config
33+
run: |
34+
node -e "
35+
const config = require('./ecosystem.config.js');
36+
assert(config.apps && config.apps.length > 0, 'No apps defined');
37+
assert(config.apps.every(a => a.name && a.script), 'Each app needs name and script');
38+
console.log('✅ Valid PM2 config with', config.apps.length, 'apps');
39+
"
40+
41+
- name: Run unit tests
42+
run: node test/compressor-test.js
43+
44+
- name: Run smoke tests
45+
env:
46+
OPENAI_API_KEY: sk-test-dummy-key-for-ci
47+
SCAN_ROOT: /tmp
48+
run: node test/smoke-test.js
49+
50+
integration-tests:
51+
name: Integration Tests (optional)
52+
runs-on: ubuntu-latest
53+
if: github.event_name == 'workflow_dispatch'
54+
steps:
55+
- name: Checkout
56+
uses: actions/checkout@v4
57+
58+
- name: Setup Node.js
59+
uses: actions/setup-node@v4
60+
with:
61+
node-version: '22'
62+
cache: 'npm'
63+
64+
- name: Install dependencies
65+
run: npm ci
66+
67+
- name: Start agents
68+
env:
69+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
70+
SCAN_ROOT: /tmp
71+
run: |
72+
mkdir -p logs .checkpoints .control
73+
npx pm2 start ecosystem.config.js
74+
sleep 10
75+
npx pm2 status
76+
77+
- name: Run uptime test
78+
run: node test/uptime-test.js
79+
80+
- name: Run recovery test
81+
run: node test/recovery-test.js
82+
83+
- name: Run chaos test
84+
run: node test/chaos-test.js
85+
86+
- name: Upload test reports
87+
if: always()
88+
uses: actions/upload-artifact@v4
89+
with:
90+
name: test-reports
91+
path: test-report-*.json
92+
93+
- name: Cleanup
94+
if: always()
95+
run: npx pm2 delete ecosystem.config.js || true

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
"restart": "pm2 restart ecosystem.config.js",
1010
"logs": "pm2 logs",
1111
"monit": "pm2 monit",
12+
"test": "node test/compressor-test.js && node test/smoke-test.js",
13+
"test:unit": "node test/compressor-test.js",
14+
"test:smoke": "node test/smoke-test.js",
1215
"test:agents": "node test/uptime-test.js",
1316
"test:agents:long": "TEST_DURATION_MS=300000 node test/uptime-test.js",
1417
"test:uptime": "node test/uptime-test.js",

test/smoke-test.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* Smoke Test — Verifies all modules load without crashing
3+
*
4+
* This is a fast sanity check (runs in < 1s) that ensures:
5+
* 1. All utility modules export valid functions
6+
* 2. All agent modules load without immediate errors
7+
* 3. The ecosystem config is valid
8+
*
9+
* Note: This does NOT start agents or make API calls. It only checks
10+
* that require() succeeds and exports are as expected.
11+
*/
12+
13+
const assert = require('assert');
14+
15+
let passed = 0;
16+
let failed = 0;
17+
18+
function test(name, fn) {
19+
try {
20+
fn();
21+
passed++;
22+
console.log(` ✅ ${name}`);
23+
} catch (err) {
24+
failed++;
25+
console.error(` ❌ ${name}: ${err.message}`);
26+
}
27+
}
28+
29+
// ── Utilities ──
30+
console.log('\n🔧 Utilities');
31+
32+
test('logger exports Logger class', () => {
33+
const { Logger } = require('../src/utils/logger');
34+
assert(typeof Logger === 'function');
35+
const l = new Logger('Test');
36+
assert(typeof l.info === 'function');
37+
assert(typeof l.error === 'function');
38+
});
39+
40+
test('checkpoint exports saveCheckpoint and loadCheckpoint', () => {
41+
const { saveCheckpoint, loadCheckpoint } = require('../src/utils/checkpoint');
42+
assert(typeof saveCheckpoint === 'function');
43+
assert(typeof loadCheckpoint === 'function');
44+
});
45+
46+
test('control exports isPaused', () => {
47+
const { isPaused } = require('../src/utils/control');
48+
assert(typeof isPaused === 'function');
49+
});
50+
51+
test('openai-client exports embed, chat, chatStream, buildRagPrompt', () => {
52+
const { embed, chat, chatStream, buildRagPrompt } = require('../src/utils/openai-client');
53+
assert(typeof embed === 'function');
54+
assert(typeof chat === 'function');
55+
assert(typeof chatStream === 'function');
56+
assert(typeof buildRagPrompt === 'function');
57+
});
58+
59+
test('content-compressor exports compress and detectContentType', () => {
60+
const { compress, detectContentType, ContentType } = require('../src/utils/content-compressor');
61+
assert(typeof compress === 'function');
62+
assert(typeof detectContentType === 'function');
63+
assert(typeof ContentType === 'object');
64+
});
65+
66+
test('knowledge-db exports getStats, hybridSearch, addConversation', () => {
67+
const db = require('../src/utils/knowledge-db');
68+
assert(typeof db.getStats === 'function');
69+
assert(typeof db.hybridSearch === 'function');
70+
assert(typeof db.addConversation === 'function');
71+
});
72+
73+
test('file-scanner exports walk and extractText', () => {
74+
const { walk, walkSync, extractText } = require('../src/utils/file-scanner');
75+
assert(typeof walk === 'function');
76+
assert(typeof walkSync === 'function');
77+
assert(typeof extractText === 'function');
78+
});
79+
80+
test('search exports search functions', () => {
81+
const search = require('../src/utils/search');
82+
assert(typeof search === 'object');
83+
});
84+
85+
// ── Agents (syntax only — verified in CI via node --check) ──
86+
console.log('\n🤖 Agents');
87+
88+
test('all agent files exist and are non-empty', () => {
89+
const fs = require('fs');
90+
const agents = [
91+
'src/agents/search-agent.js',
92+
'src/agents/extractor-agent.js',
93+
'src/agents/research-coordinator.js',
94+
'src/agents/knowledge-agent.js',
95+
'src/agents/doc-synthesizer.js',
96+
];
97+
for (const f of agents) {
98+
const stat = fs.statSync(f);
99+
assert(stat.size > 100, `${f} is suspiciously small (${stat.size} bytes)`);
100+
}
101+
});
102+
103+
// ── Ecosystem Config ──
104+
console.log('\n⚙️ Ecosystem Config');
105+
106+
test('ecosystem.config.js exports apps array', () => {
107+
const config = require('../ecosystem.config.js');
108+
assert(Array.isArray(config.apps));
109+
assert(config.apps.length === 6, `Expected 6 apps, got ${config.apps.length}`);
110+
assert(config.apps.every(a => a.name && a.script), 'Each app needs name and script');
111+
});
112+
113+
test('all agent scripts exist', () => {
114+
const fs = require('fs');
115+
const path = require('path');
116+
const config = require('../ecosystem.config.js');
117+
for (const app of config.apps) {
118+
const scriptPath = path.join(process.cwd(), app.script);
119+
assert(fs.existsSync(scriptPath), `Missing script: ${app.script}`);
120+
}
121+
});
122+
123+
// ── Summary ──
124+
console.log('\n═══════════════════════════════════════════════════════════════');
125+
console.log(` Results: ${passed} passed, ${failed} failed`);
126+
console.log('═══════════════════════════════════════════════════════════════\n');
127+
128+
process.exit(failed > 0 ? 1 : 0);

0 commit comments

Comments
 (0)