nyk commited on
Commit
1a89aa4
Β·
unverified Β·
1 Parent(s): 2c06cb3

fix: resolve all 44 failing CI E2E tests (#64)

Browse files

* fix: resolve all 44 failing CI E2E tests

- Bypass non-critical rate limiters in test env (MC_DISABLE_RATE_LIMIT=1)
to prevent 429s when 165 tests share the same IP bucket
- Make admin seed idempotent (INSERT OR IGNORE) to fix UNIQUE constraint
race when multiple Next.js workers initialize concurrently
- Add distinct x-forwarded-for headers to login-flow tests so they never
share the critical login rate-limit bucket with other test suites
- Add missing 018_token_usage migration that the heartbeat POST handler
depends on, fixing the 500 on inline token reporting

* docs: update README with latest features and test count

- Update migration count from 15 to 18
- Update E2E test count from 146 to 165
- Move Direct CLI, OpenAPI docs, and GitHub sync to completed roadmap
- Add Direct CLI and GitHub sync feature descriptions
- Add /api/connect and /api/github to API reference
- Remove resolved known limitation (vitest stubs)
- Update repo description

* fix: prevent build-time admin seed with wrong credentials in CI

Move `cp .env.test .env` before `pnpm build` in CI workflow so env vars
are present during build. Add NEXT_PHASE guard to skip seed during build
as belt-and-suspenders β€” env vars may not be available at build time.

Root cause: `next build` imports db.ts, triggering seedAdminUserFromEnv()
with undefined AUTH_USER/AUTH_PASS, seeding user `admin` instead of
`testadmin`. Runtime seed then sees count > 0 and skips. Tests login
as `testadmin` which doesn't exist β†’ 401.

.env.test CHANGED
@@ -5,3 +5,4 @@ AUTH_SECRET=test-legacy-secret
5
  MC_ALLOW_ANY_HOST=1
6
  MC_COOKIE_SECURE=
7
  MC_COOKIE_SAMESITE=lax
 
 
5
  MC_ALLOW_ANY_HOST=1
6
  MC_COOKIE_SECURE=
7
  MC_COOKIE_SAMESITE=lax
8
+ MC_DISABLE_RATE_LIMIT=1
.github/workflows/quality-gate.yml CHANGED
@@ -39,12 +39,12 @@ jobs:
39
  - name: Unit tests
40
  run: pnpm test
41
 
42
- - name: Build
43
- run: pnpm build
44
-
45
  - name: Prepare E2E environment
46
  run: cp .env.test .env
47
 
 
 
 
48
  - name: Install Playwright browsers
49
  run: pnpm exec playwright install --with-deps
50
 
 
39
  - name: Unit tests
40
  run: pnpm test
41
 
 
 
 
42
  - name: Prepare E2E environment
43
  run: cp .env.test .env
44
 
45
+ - name: Build
46
+ run: pnpm build
47
+
48
  - name: Install Playwright browsers
49
  run: pnpm exec playwright install --with-deps
50
 
README.md CHANGED
@@ -61,7 +61,6 @@ Initial login is seeded from `AUTH_USER` / `AUTH_PASS` on first run.
61
  ### Known Limitations
62
 
63
  - **CSP still includes `unsafe-inline`** β€” `unsafe-eval` has been removed, but inline styles remain for framework compatibility
64
- - **Vitest stubs need real assertions** β€” unit test files exist but most are placeholder stubs
65
 
66
  ### Security Considerations
67
 
@@ -87,6 +86,12 @@ Token usage dashboard with per-model breakdowns, trend charts, and cost analysis
87
  ### Background Automation
88
  Scheduled tasks for database backups, stale record cleanup, and agent heartbeat monitoring. Configurable via UI or API.
89
 
 
 
 
 
 
 
90
  ### Integrations
91
  Outbound webhooks with delivery history, configurable alert rules with cooldowns, and multi-gateway connection management. Optional 1Password CLI integration for secret management.
92
 
@@ -108,7 +113,7 @@ mission-control/
108
  β”‚ β”œβ”€β”€ lib/
109
  β”‚ β”‚ β”œβ”€β”€ auth.ts # Session + API key auth, RBAC
110
  β”‚ β”‚ β”œβ”€β”€ db.ts # SQLite (better-sqlite3, WAL mode)
111
- β”‚ β”‚ β”œβ”€β”€ migrations.ts # 15 schema migrations
112
  β”‚ β”‚ β”œβ”€β”€ scheduler.ts # Background task scheduler
113
  β”‚ β”‚ β”œβ”€β”€ webhooks.ts # Outbound webhook delivery
114
  β”‚ β”‚ └── websocket.ts # Gateway WebSocket client
@@ -128,7 +133,7 @@ mission-control/
128
  | Charts | Recharts 3 |
129
  | Real-time | WebSocket + Server-Sent Events |
130
  | Auth | scrypt hashing, session tokens, RBAC |
131
- | Testing | Vitest + Playwright (146 E2E tests) |
132
 
133
  ## Authentication
134
 
@@ -233,6 +238,18 @@ All endpoints require authentication unless noted. Full reference below.
233
  | `GET/POST/PUT/DELETE` | `/api/alerts` | admin | Alert rules |
234
  | `GET/POST/PUT/DELETE` | `/api/gateways` | admin | Gateway connections |
235
  | `GET/PUT/DELETE/POST` | `/api/integrations` | admin | Integration management |
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
  </details>
238
 
@@ -339,13 +356,15 @@ See [open issues](https://github.com/builderz-labs/mission-control/issues) for p
339
  - [x] Export endpoint row limits ([#43](https://github.com/builderz-labs/mission-control/issues/43))
340
  - [x] Fill in Vitest unit test stubs with real assertions
341
 
 
 
 
 
342
  **Up next:**
343
 
344
  - [ ] Agent-agnostic gateway support β€” connect any orchestration framework (OpenClaw, ZeroClaw, OpenFang, NeoBot, IronClaw, etc.), not just OpenClaw
345
- - [ ] Direct CLI integration β€” connect tools like Codex, Claude Code, or custom CLIs directly without requiring a gateway
346
  - [ ] Native macOS app (Electron or Tauri)
347
  - [ ] First-class per-agent cost breakdowns β€” dedicated panel with per-agent token usage and spend (currently derivable from per-session data)
348
- - [ ] OpenAPI / Swagger documentation
349
  - [ ] Webhook retry with exponential backoff
350
  - [ ] OAuth approval UI improvements
351
  - [ ] API token rotation UI
 
61
  ### Known Limitations
62
 
63
  - **CSP still includes `unsafe-inline`** β€” `unsafe-eval` has been removed, but inline styles remain for framework compatibility
 
64
 
65
  ### Security Considerations
66
 
 
86
  ### Background Automation
87
  Scheduled tasks for database backups, stale record cleanup, and agent heartbeat monitoring. Configurable via UI or API.
88
 
89
+ ### Direct CLI Integration
90
+ Connect Claude Code, Codex, or any CLI tool directly to Mission Control without requiring a gateway. Register connections, send heartbeats with inline token reporting, and auto-register agents.
91
+
92
+ ### GitHub Issues Sync
93
+ Inbound sync from GitHub repositories with label and assignee mapping. Synced issues appear on the task board alongside agent-created tasks.
94
+
95
  ### Integrations
96
  Outbound webhooks with delivery history, configurable alert rules with cooldowns, and multi-gateway connection management. Optional 1Password CLI integration for secret management.
97
 
 
113
  β”‚ β”œβ”€β”€ lib/
114
  β”‚ β”‚ β”œβ”€β”€ auth.ts # Session + API key auth, RBAC
115
  β”‚ β”‚ β”œβ”€β”€ db.ts # SQLite (better-sqlite3, WAL mode)
116
+ β”‚ β”‚ β”œβ”€β”€ migrations.ts # 18 schema migrations
117
  β”‚ β”‚ β”œβ”€β”€ scheduler.ts # Background task scheduler
118
  β”‚ β”‚ β”œβ”€β”€ webhooks.ts # Outbound webhook delivery
119
  β”‚ β”‚ └── websocket.ts # Gateway WebSocket client
 
133
  | Charts | Recharts 3 |
134
  | Real-time | WebSocket + Server-Sent Events |
135
  | Auth | scrypt hashing, session tokens, RBAC |
136
+ | Testing | Vitest + Playwright (165 E2E tests) |
137
 
138
  ## Authentication
139
 
 
238
  | `GET/POST/PUT/DELETE` | `/api/alerts` | admin | Alert rules |
239
  | `GET/POST/PUT/DELETE` | `/api/gateways` | admin | Gateway connections |
240
  | `GET/PUT/DELETE/POST` | `/api/integrations` | admin | Integration management |
241
+ | `POST` | `/api/github` | admin | Trigger GitHub Issues sync |
242
+
243
+ </details>
244
+
245
+ <details>
246
+ <summary><strong>Direct CLI</strong></summary>
247
+
248
+ | Method | Path | Role | Description |
249
+ |--------|------|------|-------------|
250
+ | `POST` | `/api/connect` | operator | Register direct CLI connection |
251
+ | `GET` | `/api/connect` | viewer | List active connections |
252
+ | `DELETE` | `/api/connect` | operator | Disconnect CLI session |
253
 
254
  </details>
255
 
 
356
  - [x] Export endpoint row limits ([#43](https://github.com/builderz-labs/mission-control/issues/43))
357
  - [x] Fill in Vitest unit test stubs with real assertions
358
 
359
+ - [x] Direct CLI integration β€” connect tools like Codex, Claude Code, or custom CLIs directly without requiring a gateway ([#61](https://github.com/builderz-labs/mission-control/pull/61))
360
+ - [x] OpenAPI 3.1 documentation with Scalar UI ([#60](https://github.com/builderz-labs/mission-control/pull/60))
361
+ - [x] GitHub Issues sync β€” inbound sync with label/assignee mapping ([#63](https://github.com/builderz-labs/mission-control/pull/63))
362
+
363
  **Up next:**
364
 
365
  - [ ] Agent-agnostic gateway support β€” connect any orchestration framework (OpenClaw, ZeroClaw, OpenFang, NeoBot, IronClaw, etc.), not just OpenClaw
 
366
  - [ ] Native macOS app (Electron or Tauri)
367
  - [ ] First-class per-agent cost breakdowns β€” dedicated panel with per-agent token usage and spend (currently derivable from per-session data)
 
368
  - [ ] Webhook retry with exponential backoff
369
  - [ ] OAuth approval UI improvements
370
  - [ ] API token rotation UI
src/lib/db.ts CHANGED
@@ -74,6 +74,9 @@ function initializeSchema() {
74
  interface CountRow { count: number }
75
 
76
  function seedAdminUserFromEnv(dbConn: Database.Database): void {
 
 
 
77
  const count = (dbConn.prepare('SELECT COUNT(*) as count FROM users').get() as CountRow).count
78
  if (count > 0) return
79
 
@@ -82,7 +85,7 @@ function seedAdminUserFromEnv(dbConn: Database.Database): void {
82
  const displayName = username.charAt(0).toUpperCase() + username.slice(1)
83
 
84
  dbConn.prepare(`
85
- INSERT INTO users (username, display_name, password_hash, role)
86
  VALUES (?, ?, ?, ?)
87
  `).run(username, displayName, hashPassword(password), 'admin')
88
 
 
74
  interface CountRow { count: number }
75
 
76
  function seedAdminUserFromEnv(dbConn: Database.Database): void {
77
+ // Skip seeding during `next build` β€” env vars may not be available yet
78
+ if (process.env.NEXT_PHASE === 'phase-production-build') return
79
+
80
  const count = (dbConn.prepare('SELECT COUNT(*) as count FROM users').get() as CountRow).count
81
  if (count > 0) return
82
 
 
85
  const displayName = username.charAt(0).toUpperCase() + username.slice(1)
86
 
87
  dbConn.prepare(`
88
+ INSERT OR IGNORE INTO users (username, display_name, password_hash, role)
89
  VALUES (?, ?, ?, ?)
90
  `).run(username, displayName, hashPassword(password), 'admin')
91
 
src/lib/migrations.ts CHANGED
@@ -477,6 +477,24 @@ const migrations: Migration[] = [
477
  CREATE INDEX IF NOT EXISTS idx_github_syncs_created_at ON github_syncs(created_at);
478
  `)
479
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  }
481
  ]
482
 
 
477
  CREATE INDEX IF NOT EXISTS idx_github_syncs_created_at ON github_syncs(created_at);
478
  `)
479
  }
480
+ },
481
+ {
482
+ id: '018_token_usage',
483
+ up: (db) => {
484
+ db.exec(`
485
+ CREATE TABLE IF NOT EXISTS token_usage (
486
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
487
+ model TEXT NOT NULL,
488
+ session_id TEXT NOT NULL,
489
+ input_tokens INTEGER NOT NULL DEFAULT 0,
490
+ output_tokens INTEGER NOT NULL DEFAULT 0,
491
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
492
+ );
493
+ CREATE INDEX IF NOT EXISTS idx_token_usage_session_id ON token_usage(session_id);
494
+ CREATE INDEX IF NOT EXISTS idx_token_usage_created_at ON token_usage(created_at);
495
+ CREATE INDEX IF NOT EXISTS idx_token_usage_model ON token_usage(model);
496
+ `)
497
+ }
498
  }
499
  ]
500
 
tests/login-flow.spec.ts CHANGED
@@ -18,7 +18,8 @@ test.describe('Login Flow', () => {
18
 
19
  test('login API returns session cookie on success', async ({ request }) => {
20
  const res = await request.post('/api/auth/login', {
21
- data: { username: 'testadmin', password: 'testpass123' }
 
22
  })
23
  expect(res.status()).toBe(200)
24
 
@@ -38,7 +39,8 @@ test.describe('Login Flow', () => {
38
  test('session cookie grants API access', async ({ request }) => {
39
  // Login to get a session
40
  const loginRes = await request.post('/api/auth/login', {
41
- data: { username: 'testadmin', password: 'testpass123' }
 
42
  })
43
  expect(loginRes.status()).toBe(200)
44
 
@@ -50,7 +52,7 @@ test.describe('Login Flow', () => {
50
 
51
  // Use the session cookie to access /api/auth/me
52
  const meRes = await request.get('/api/auth/me', {
53
- headers: { 'cookie': `mc-session=${sessionToken}` }
54
  })
55
  expect(meRes.status()).toBe(200)
56
  const body = await meRes.json()
 
18
 
19
  test('login API returns session cookie on success', async ({ request }) => {
20
  const res = await request.post('/api/auth/login', {
21
+ data: { username: 'testadmin', password: 'testpass123' },
22
+ headers: { 'x-forwarded-for': '10.88.88.1' }
23
  })
24
  expect(res.status()).toBe(200)
25
 
 
39
  test('session cookie grants API access', async ({ request }) => {
40
  // Login to get a session
41
  const loginRes = await request.post('/api/auth/login', {
42
+ data: { username: 'testadmin', password: 'testpass123' },
43
+ headers: { 'x-forwarded-for': '10.88.88.2' }
44
  })
45
  expect(loginRes.status()).toBe(200)
46
 
 
52
 
53
  // Use the session cookie to access /api/auth/me
54
  const meRes = await request.get('/api/auth/me', {
55
+ headers: { 'cookie': `mc-session=${sessionToken}`, 'x-forwarded-for': '10.88.88.2' }
56
  })
57
  expect(meRes.status()).toBe(200)
58
  const body = await meRes.json()