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

* 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.
This commit is contained in:
nyk 2026-03-02 13:53:00 +07:00 committed by GitHub
parent d6879c66c1
commit b2703b37d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 55 additions and 12 deletions

View File

@ -5,3 +5,4 @@ AUTH_SECRET=test-legacy-secret
MC_ALLOW_ANY_HOST=1 MC_ALLOW_ANY_HOST=1
MC_COOKIE_SECURE= MC_COOKIE_SECURE=
MC_COOKIE_SAMESITE=lax MC_COOKIE_SAMESITE=lax
MC_DISABLE_RATE_LIMIT=1

View File

@ -39,12 +39,12 @@ jobs:
- name: Unit tests - name: Unit tests
run: pnpm test run: pnpm test
- name: Build
run: pnpm build
- name: Prepare E2E environment - name: Prepare E2E environment
run: cp .env.test .env run: cp .env.test .env
- name: Build
run: pnpm build
- name: Install Playwright browsers - name: Install Playwright browsers
run: pnpm exec playwright install --with-deps run: pnpm exec playwright install --with-deps

View File

@ -61,7 +61,6 @@ Initial login is seeded from `AUTH_USER` / `AUTH_PASS` on first run.
### Known Limitations ### Known Limitations
- **CSP still includes `unsafe-inline`**`unsafe-eval` has been removed, but inline styles remain for framework compatibility - **CSP still includes `unsafe-inline`**`unsafe-eval` has been removed, but inline styles remain for framework compatibility
- **Vitest stubs need real assertions** — unit test files exist but most are placeholder stubs
### Security Considerations ### Security Considerations
@ -87,6 +86,12 @@ Token usage dashboard with per-model breakdowns, trend charts, and cost analysis
### Background Automation ### Background Automation
Scheduled tasks for database backups, stale record cleanup, and agent heartbeat monitoring. Configurable via UI or API. Scheduled tasks for database backups, stale record cleanup, and agent heartbeat monitoring. Configurable via UI or API.
### Direct CLI Integration
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.
### GitHub Issues Sync
Inbound sync from GitHub repositories with label and assignee mapping. Synced issues appear on the task board alongside agent-created tasks.
### Integrations ### Integrations
Outbound webhooks with delivery history, configurable alert rules with cooldowns, and multi-gateway connection management. Optional 1Password CLI integration for secret management. Outbound webhooks with delivery history, configurable alert rules with cooldowns, and multi-gateway connection management. Optional 1Password CLI integration for secret management.
@ -108,7 +113,7 @@ mission-control/
│ ├── lib/ │ ├── lib/
│ │ ├── auth.ts # Session + API key auth, RBAC │ │ ├── auth.ts # Session + API key auth, RBAC
│ │ ├── db.ts # SQLite (better-sqlite3, WAL mode) │ │ ├── db.ts # SQLite (better-sqlite3, WAL mode)
│ │ ├── migrations.ts # 15 schema migrations │ │ ├── migrations.ts # 18 schema migrations
│ │ ├── scheduler.ts # Background task scheduler │ │ ├── scheduler.ts # Background task scheduler
│ │ ├── webhooks.ts # Outbound webhook delivery │ │ ├── webhooks.ts # Outbound webhook delivery
│ │ └── websocket.ts # Gateway WebSocket client │ │ └── websocket.ts # Gateway WebSocket client
@ -128,7 +133,7 @@ mission-control/
| Charts | Recharts 3 | | Charts | Recharts 3 |
| Real-time | WebSocket + Server-Sent Events | | Real-time | WebSocket + Server-Sent Events |
| Auth | scrypt hashing, session tokens, RBAC | | Auth | scrypt hashing, session tokens, RBAC |
| Testing | Vitest + Playwright (146 E2E tests) | | Testing | Vitest + Playwright (165 E2E tests) |
## Authentication ## Authentication
@ -233,6 +238,18 @@ All endpoints require authentication unless noted. Full reference below.
| `GET/POST/PUT/DELETE` | `/api/alerts` | admin | Alert rules | | `GET/POST/PUT/DELETE` | `/api/alerts` | admin | Alert rules |
| `GET/POST/PUT/DELETE` | `/api/gateways` | admin | Gateway connections | | `GET/POST/PUT/DELETE` | `/api/gateways` | admin | Gateway connections |
| `GET/PUT/DELETE/POST` | `/api/integrations` | admin | Integration management | | `GET/PUT/DELETE/POST` | `/api/integrations` | admin | Integration management |
| `POST` | `/api/github` | admin | Trigger GitHub Issues sync |
</details>
<details>
<summary><strong>Direct CLI</strong></summary>
| Method | Path | Role | Description |
|--------|------|------|-------------|
| `POST` | `/api/connect` | operator | Register direct CLI connection |
| `GET` | `/api/connect` | viewer | List active connections |
| `DELETE` | `/api/connect` | operator | Disconnect CLI session |
</details> </details>
@ -339,13 +356,15 @@ See [open issues](https://github.com/builderz-labs/mission-control/issues) for p
- [x] Export endpoint row limits ([#43](https://github.com/builderz-labs/mission-control/issues/43)) - [x] Export endpoint row limits ([#43](https://github.com/builderz-labs/mission-control/issues/43))
- [x] Fill in Vitest unit test stubs with real assertions - [x] Fill in Vitest unit test stubs with real assertions
- [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))
- [x] OpenAPI 3.1 documentation with Scalar UI ([#60](https://github.com/builderz-labs/mission-control/pull/60))
- [x] GitHub Issues sync — inbound sync with label/assignee mapping ([#63](https://github.com/builderz-labs/mission-control/pull/63))
**Up next:** **Up next:**
- [ ] Agent-agnostic gateway support — connect any orchestration framework (OpenClaw, ZeroClaw, OpenFang, NeoBot, IronClaw, etc.), not just OpenClaw - [ ] Agent-agnostic gateway support — connect any orchestration framework (OpenClaw, ZeroClaw, OpenFang, NeoBot, IronClaw, etc.), not just OpenClaw
- [ ] Direct CLI integration — connect tools like Codex, Claude Code, or custom CLIs directly without requiring a gateway
- [ ] Native macOS app (Electron or Tauri) - [ ] Native macOS app (Electron or Tauri)
- [ ] First-class per-agent cost breakdowns — dedicated panel with per-agent token usage and spend (currently derivable from per-session data) - [ ] First-class per-agent cost breakdowns — dedicated panel with per-agent token usage and spend (currently derivable from per-session data)
- [ ] OpenAPI / Swagger documentation
- [ ] Webhook retry with exponential backoff - [ ] Webhook retry with exponential backoff
- [ ] OAuth approval UI improvements - [ ] OAuth approval UI improvements
- [ ] API token rotation UI - [ ] API token rotation UI

View File

@ -74,6 +74,9 @@ function initializeSchema() {
interface CountRow { count: number } interface CountRow { count: number }
function seedAdminUserFromEnv(dbConn: Database.Database): void { function seedAdminUserFromEnv(dbConn: Database.Database): void {
// Skip seeding during `next build` — env vars may not be available yet
if (process.env.NEXT_PHASE === 'phase-production-build') return
const count = (dbConn.prepare('SELECT COUNT(*) as count FROM users').get() as CountRow).count const count = (dbConn.prepare('SELECT COUNT(*) as count FROM users').get() as CountRow).count
if (count > 0) return if (count > 0) return
@ -82,7 +85,7 @@ function seedAdminUserFromEnv(dbConn: Database.Database): void {
const displayName = username.charAt(0).toUpperCase() + username.slice(1) const displayName = username.charAt(0).toUpperCase() + username.slice(1)
dbConn.prepare(` dbConn.prepare(`
INSERT INTO users (username, display_name, password_hash, role) INSERT OR IGNORE INTO users (username, display_name, password_hash, role)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
`).run(username, displayName, hashPassword(password), 'admin') `).run(username, displayName, hashPassword(password), 'admin')

View File

@ -477,6 +477,24 @@ const migrations: Migration[] = [
CREATE INDEX IF NOT EXISTS idx_github_syncs_created_at ON github_syncs(created_at); CREATE INDEX IF NOT EXISTS idx_github_syncs_created_at ON github_syncs(created_at);
`) `)
} }
},
{
id: '018_token_usage',
up: (db) => {
db.exec(`
CREATE TABLE IF NOT EXISTS token_usage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
model TEXT NOT NULL,
session_id TEXT NOT NULL,
input_tokens INTEGER NOT NULL DEFAULT 0,
output_tokens INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
);
CREATE INDEX IF NOT EXISTS idx_token_usage_session_id ON token_usage(session_id);
CREATE INDEX IF NOT EXISTS idx_token_usage_created_at ON token_usage(created_at);
CREATE INDEX IF NOT EXISTS idx_token_usage_model ON token_usage(model);
`)
}
} }
] ]

View File

@ -18,7 +18,8 @@ test.describe('Login Flow', () => {
test('login API returns session cookie on success', async ({ request }) => { test('login API returns session cookie on success', async ({ request }) => {
const res = await request.post('/api/auth/login', { const res = await request.post('/api/auth/login', {
data: { username: 'testadmin', password: 'testpass123' } data: { username: 'testadmin', password: 'testpass123' },
headers: { 'x-forwarded-for': '10.88.88.1' }
}) })
expect(res.status()).toBe(200) expect(res.status()).toBe(200)
@ -38,7 +39,8 @@ test.describe('Login Flow', () => {
test('session cookie grants API access', async ({ request }) => { test('session cookie grants API access', async ({ request }) => {
// Login to get a session // Login to get a session
const loginRes = await request.post('/api/auth/login', { const loginRes = await request.post('/api/auth/login', {
data: { username: 'testadmin', password: 'testpass123' } data: { username: 'testadmin', password: 'testpass123' },
headers: { 'x-forwarded-for': '10.88.88.2' }
}) })
expect(loginRes.status()).toBe(200) expect(loginRes.status()).toBe(200)
@ -50,7 +52,7 @@ test.describe('Login Flow', () => {
// Use the session cookie to access /api/auth/me // Use the session cookie to access /api/auth/me
const meRes = await request.get('/api/auth/me', { const meRes = await request.get('/api/auth/me', {
headers: { 'cookie': `mc-session=${sessionToken}` } headers: { 'cookie': `mc-session=${sessionToken}`, 'x-forwarded-for': '10.88.88.2' }
}) })
expect(meRes.status()).toBe(200) expect(meRes.status()).toBe(200)
const body = await meRes.json() const body = await meRes.json()