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_COOKIE_SECURE=
MC_COOKIE_SAMESITE=lax
MC_DISABLE_RATE_LIMIT=1

View File

@ -39,12 +39,12 @@ jobs:
- name: Unit tests
run: pnpm test
- name: Build
run: pnpm build
- name: Prepare E2E environment
run: cp .env.test .env
- name: Build
run: pnpm build
- name: Install Playwright browsers
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
- **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
@ -87,6 +86,12 @@ Token usage dashboard with per-model breakdowns, trend charts, and cost analysis
### Background Automation
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
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/
│ │ ├── auth.ts # Session + API key auth, RBAC
│ │ ├── db.ts # SQLite (better-sqlite3, WAL mode)
│ │ ├── migrations.ts # 15 schema migrations
│ │ ├── migrations.ts # 18 schema migrations
│ │ ├── scheduler.ts # Background task scheduler
│ │ ├── webhooks.ts # Outbound webhook delivery
│ │ └── websocket.ts # Gateway WebSocket client
@ -128,7 +133,7 @@ mission-control/
| Charts | Recharts 3 |
| Real-time | WebSocket + Server-Sent Events |
| Auth | scrypt hashing, session tokens, RBAC |
| Testing | Vitest + Playwright (146 E2E tests) |
| Testing | Vitest + Playwright (165 E2E tests) |
## 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/gateways` | admin | Gateway connections |
| `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>
@ -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] 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:**
- [ ] 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)
- [ ] 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
- [ ] OAuth approval UI improvements
- [ ] API token rotation UI

View File

@ -74,6 +74,9 @@ function initializeSchema() {
interface CountRow { count: number }
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
if (count > 0) return
@ -82,7 +85,7 @@ function seedAdminUserFromEnv(dbConn: Database.Database): void {
const displayName = username.charAt(0).toUpperCase() + username.slice(1)
dbConn.prepare(`
INSERT INTO users (username, display_name, password_hash, role)
INSERT OR IGNORE INTO users (username, display_name, password_hash, role)
VALUES (?, ?, ?, ?)
`).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);
`)
}
},
{
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 }) => {
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)
@ -38,7 +39,8 @@ test.describe('Login Flow', () => {
test('session cookie grants API access', async ({ request }) => {
// Login to get a session
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)
@ -50,7 +52,7 @@ test.describe('Login Flow', () => {
// Use the session cookie to access /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)
const body = await meRes.json()