diff --git a/.cursor/commands/qa/Taskfile.yml b/.cursor/commands/qa/Taskfile.yml index 5aacd8d..e330f32 100644 --- a/.cursor/commands/qa/Taskfile.yml +++ b/.cursor/commands/qa/Taskfile.yml @@ -110,16 +110,41 @@ tasks: - cd ../../../frontend && bun run test:e2e:ui silent: false - # Complete test suite + # Validation-only QA suite (for git hooks and CI) + # Does NOT modify code - only validates all: - desc: "Complete test suite (smoke + lint + typecheck + unit + e2e)" + desc: "Validation QA suite (rules + smoke + lint + typecheck + unit + e2e)" cmds: + - echo "๐ŸŽฏ Running validation QA suite (no auto-fix)..." + - echo "" + - task: rules:check + - echo "" - task: smoke + - echo "" - task: lint + - echo "" - task: typecheck + - echo "" - task: unit + - echo "" - task: e2e - - echo "๐ŸŽ‰ All tests passed!" + - echo "" + - echo "๐ŸŽ‰ All validation checks passed!" + silent: false + + # Complete QA workflow (fix THEN validate) + # Use this manually before committing + all:fix: + desc: "Complete workflow - auto-fix then validate (manual use only)" + cmds: + - echo "Step 1 - Auto-fixing issues..." + - task: fix + - echo "" + - echo "Auto-fix complete. Review changes with git diff" + - echo "Stage changes with git add . if satisfied" + - echo "" + - echo "Step 2 - Running validation suite..." + - task: all silent: false # Appium (mobile testing) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0f5a80f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,148 @@ +# GitHub Actions CI Workflow +# +# Status: ACTIVE +# Purpose: Run comprehensive QA suite on every push/PR +# +# This workflow uses the unified `task qa:all` command. +# Mirrors Husky pre-push hook exactly (same commands, same checks). + +name: CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + # Comprehensive QA Suite (mirrors pre-push hook) + # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + # Command: cd .cursor && task qa:all + # Includes: fix, rules, smoke, lint, typecheck, unit, e2e + + qa-all: + name: QA Suite (fix + rules + smoke + lint + typecheck + unit + e2e) + runs-on: ubuntu-latest + env: + ENCORE_AUTH_KEY: ${{ secrets.ENCORE_AUTH_KEY }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install go-task + run: | + sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin + task --version + + - name: Setup bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Encore CLI + run: | + curl -L https://encore.dev/install.sh | bash + echo "$HOME/.encore/bin" >> $GITHUB_PATH + + - name: Authenticate with Encore Cloud + run: | + if [ -z "$ENCORE_AUTH_KEY" ]; then + echo "โš ๏ธ WARNING: ENCORE_AUTH_KEY not set in GitHub Secrets" + echo " Encore builds requiring secrets will fail" + echo " To fix:" + echo " 1. Go to https://app.encore.cloud/screengraph-ovzi" + echo " 2. Navigate to: App Settings โ†’ Auth Keys" + echo " 3. Create new auth key" + echo " 4. Add as GitHub Secret named 'ENCORE_AUTH_KEY'" + exit 1 + else + echo "๐Ÿ” Authenticating with Encore Cloud..." + encore auth login --auth-key "$ENCORE_AUTH_KEY" + echo "โœ… Encore authentication successful" + fi + + - name: Install Backend Dependencies + run: cd backend && bun install + + - name: Install Frontend Dependencies + run: cd frontend && bun install + + - name: Install Playwright Browser Binaries + run: cd frontend && bunx playwright install --with-deps chromium + + - name: Start Backend + run: | + cd backend + encore run & + echo "Waiting for backend to be ready..." + timeout 60 bash -c 'until curl -sf http://localhost:4000/health > /dev/null; do sleep 2; done' + + - name: Start Frontend + run: | + cd frontend + bun run dev & + echo "Waiting for frontend to be ready..." + timeout 60 bash -c 'until curl -sf http://localhost:5173 > /dev/null; do sleep 2; done' + + - name: Run Complete QA Suite + run: cd .cursor && task qa:all + +# โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” +# Implementation Notes: +# โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” +# +# SIMPLICITY: Single job runs `task qa:all` - same as pre-push hook +# MIRRORS LOCAL: Exact same command developers run locally +# DRY: No duplication - all logic in .cursor/commands/qa/Taskfile.yml +# +# What `task qa:all` runs (VALIDATION ONLY - no code modification): +# 1. qa:rules - Validate founder rules (no console.log, no any, American spelling) +# 2. qa:smoke - Health checks (backend + frontend) +# 3. qa:lint - Linting (backend + frontend) +# 4. qa:typecheck - TypeScript validation (frontend) +# 5. qa:unit - Unit tests (backend only - encore test) +# 6. qa:e2e - E2E tests (frontend Playwright) +# +# Note: Auto-fix (qa:fix) is intentionally excluded from qa:all +# - Git hooks should validate, not modify uncommitted code +# - CI should validate, not modify code (anti-pattern) +# - Manual workflow: `task qa:all:fix` (fix โ†’ validate) before committing +# +# Dependencies: +# - go-task - Taskfile runner +# - bun - Package manager +# - Node.js - Automation scripts +# - Encore CLI - Backend runtime +# +# Environment: +# - Uses standard ports from .env (4000 backend, 5173 frontend) +# - In-memory database for tests +# - ENCORE_AUTH_KEY: GitHub Secret (app-specific auth key) for Encore Cloud authentication +# +# GitHub Secrets Setup: +# 1. Go to: https://app.encore.cloud/screengraph-ovzi โ†’ App Settings โ†’ Auth Keys +# 2. Create new auth key (NOT `encore auth token` - that's different!) +# 3. Go to: GitHub repo โ†’ Settings โ†’ Secrets and variables โ†’ Actions +# 4. Create new secret: ENCORE_AUTH_KEY +# 5. Paste the auth key from step 2 +# +# Testing before activation: +# 1. Create feature branch +# 2. Rename to ci.yml +# 3. Push to trigger workflow +# 4. Verify qa:all passes +# 5. Merge to main + +# Validation checklist when modifying: +# 1. Create feature branch +# 2. Push to trigger workflow +# 3. Confirm qa:all passes in GitHub Actions +# 4. Merge to main after review + diff --git a/.github/workflows/ci.yml.scaffold b/.github/workflows/ci.yml.scaffold deleted file mode 100644 index 5a01843..0000000 --- a/.github/workflows/ci.yml.scaffold +++ /dev/null @@ -1,176 +0,0 @@ -# GitHub Actions CI Workflow - SCAFFOLD -# -# Status: NOT YET ACTIVE (rename to ci.yml to activate) -# Purpose: Run automated tests and quality checks on every push/PR -# -# This workflow will use the unified Task automation system. -# All checks mirror local development (Husky hooks + Cursor commands) -# -# To activate: -# 1. Rename this file from ci.yml.scaffold to ci.yml -# 2. Verify all task commands work in CI environment -# 3. Test in a feature branch first -# 4. Merge to main when validated - -name: CI - -on: - push: - branches: [ main, develop ] - pull_request: - branches: [ main, develop ] - -jobs: - # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” - # Job 1: Founder Rules Validation - # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” - # Mirrors: .husky/pre-commit hook -# Command: bun run task founder:rules:check - - founder-rules: - name: Validate Founder Rules - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - # TODO: Install Task - # - name: Install go-task - # run: | - # sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin - # task --version - - # TODO: Install bun - # - name: Setup bun - # uses: oven-sh/setup-bun@v1 - # with: - # bun-version: latest - - # TODO: Install Node.js (for automation scripts) - # - name: Setup Node.js - # uses: actions/setup-node@v4 - # with: - # node-version: '20' - - # TODO: Run founder rules check - # - name: Check Founder Rules - # run: bun run task founder:rules:check - - # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” - # Job 2: Backend Smoke Tests - # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” - # Mirrors: .husky/pre-push hook (backend part) -# Command: bun run qa:smoke:backend - - backend-smoke: - name: Backend Smoke Tests - runs-on: ubuntu-latest - needs: founder-rules - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - # TODO: Setup dependencies - # - Install Task, bun, Node.js (same as above) - - # TODO: Install backend dependencies - # - name: Install Backend Dependencies - # run: cd backend && bun install - - # TODO: Start backend - # - name: Start Backend - # run: cd .cursor && task backend:dev & - # # Give it time to start - # sleep 5 - - # TODO: Run smoke tests - # - name: Run Backend Smoke Tests - # run: bun run qa:smoke:backend - - # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” - # Job 3: Frontend Smoke Tests - # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” - # Mirrors: .husky/pre-push hook (frontend part) -# Command: bun run qa:smoke:frontend - - frontend-smoke: - name: Frontend Smoke Tests - runs-on: ubuntu-latest - needs: founder-rules - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - # TODO: Setup dependencies - # - Install Task, bun, Node.js (same as above) - - # TODO: Install frontend dependencies - # - name: Install Frontend Dependencies - # run: cd frontend && bun install - - # TODO: Build frontend - # - name: Build Frontend - # run: cd .cursor && task frontend:build - - # TODO: Start frontend - # - name: Start Frontend - # run: cd .cursor && task frontend:dev & - # sleep 5 - - # TODO: Run smoke tests - # - name: Run Frontend Smoke Tests - # run: bun run qa:smoke:frontend - - # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” - # Job 4: Type Checking - # โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” - # Additional quality check -# Command: bun run typecheck - - typecheck: - name: TypeScript Type Checking - runs-on: ubuntu-latest - needs: founder-rules - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - # TODO: Setup and run typecheck - # - name: Run TypeScript Type Checking - # run: bun run typecheck - -# โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” -# Implementation Notes: -# โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” -# -# 1. All Task commands are the SAME as local development -# 2. CI enforces the same rules as Husky hooks (redundant safety) -# 3. Jobs run in parallel where possible (founder-rules first, others parallel) -# 4. Uses ubuntu-latest for consistency -# 5. Each job installs its own dependencies (GitHub Actions best practice) -# -# Dependencies needed: -# - go-task (Taskfile runner) -# - bun (package manager) -# - Node.js (for automation scripts) -# - Encore CLI (for backend) -# -# Environment variables: -# - BACKEND_PORT, FRONTEND_PORT (auto-resolved from automation/scripts/env.mjs) -# -# Security: -# - No secrets needed for CI checks -# - Database: Use in-memory or test DB -# - No external API calls in smoke tests -# -# To test before activating: -# 1. Create feature branch -# 2. Rename to ci.yml -# 3. Push to trigger workflow -# 4. Verify all jobs pass -# 5. Merge to main - diff --git a/.husky/pre-commit b/.husky/pre-commit index 796f07a..369913f 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,17 +1,21 @@ #!/bin/sh # Husky pre-commit hook -# Auto-fixes and validates code before allowing commit +# Validation QA suite BEFORE commit (coding agent safety) +# +# WHY REDUNDANT: Coding agents can bypass guards and commit directly. +# Running qa:all here ensures quality checks at EVERY commit. +# +# IMPORTANT: qa:all is VALIDATION ONLY (does not modify code) +# To auto-fix before committing, run: cd .cursor && task qa:all:fix +# This will fix issues, show you the changes, and let you stage them. -# Auto-fix linting and formatting -echo "๐Ÿ”ง Auto-fixing code quality issues..." -(cd .cursor && task qa:fix) || exit 1 - -# Run the full QA suite (linting, testing, etc.) -echo "๐Ÿ› ๏ธ Running comprehensive QA checks..." +echo "๐Ÿ› ๏ธ Running validation QA suite before commit..." +echo " (Validation only - does not modify code)" +echo " (Redundant with pre-push for coding agent safety)" +echo "" (cd .cursor && task qa:all) || exit 1 -# Validate founder rules -echo "๐Ÿ“‹ Validating founder rules..." -(cd .cursor && task qa:rules:check) || exit 1 +echo "" +echo "โœ… Pre-commit validation passed!" diff --git a/.husky/pre-push b/.husky/pre-push index 889824a..7490441 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,19 +1,17 @@ #!/bin/sh # Husky pre-push hook -# 1. Cleanup root documentation (vibe_manager_vibe responsibility) -# 2. Run auto-fix and comprehensive QA suite -# 3. Check for merge commits in new changes +# Validation QA suite + merge commit check +# +# WHY REDUNDANT: Coding agents can bypass pre-commit and push directly. +# Running qa:all here ensures quality checks at EVERY push. +# +# IMPORTANT: qa:all is VALIDATION ONLY (does not modify code) +# To auto-fix before pushing, run: cd .cursor && task qa:all:fix +# This will fix issues, show you the changes, and let you stage them. -echo "๐Ÿงน Vibe Manager: Cleaning up root documentation..." -echo "" - -echo "" -echo "๐Ÿ”ง Auto-fixing code quality issues before push..." -echo "" -(cd .cursor && task qa:fix) || exit 1 - -echo "" -echo "๐Ÿ› ๏ธ Running comprehensive QA checks before push..." +echo "๐Ÿ› ๏ธ Running validation QA suite before push..." +echo " (Validation only - does not modify code)" +echo " (Redundant with pre-commit for coding agent safety)" echo "" (cd .cursor && task qa:all) || exit 1 diff --git a/backend/agent/nodes/setup/EnsureDevice/device-check.test.ts b/backend/agent/nodes/setup/EnsureDevice/device-check.test.ts index 3c08a14..63ca672 100644 --- a/backend/agent/nodes/setup/EnsureDevice/device-check.test.ts +++ b/backend/agent/nodes/setup/EnsureDevice/device-check.test.ts @@ -1,89 +1,153 @@ -import { describe, expect, test } from "vitest"; +import { beforeEach, describe, expect, test, vi } from "vitest"; import { checkDevicePrerequisites } from "./device-check"; +// Mock child_process to avoid real adb calls +vi.mock("node:child_process", () => ({ + exec: vi.fn(), +})); + +// Import after mocking to get the mocked version +const childProcess = await import("node:child_process"); +const { promisify } = await import("node:util"); + describe("checkDevicePrerequisites", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); test("should detect online device via adb", async () => { + // Mock successful adb output with one device + vi.mocked(childProcess.exec).mockImplementation((cmd, callback) => { + if (typeof callback === "function") { + callback(null, { + stdout: + "List of devices attached\nemulator-5554 device product:sdk_gphone64_arm64", + stderr: "", + } as never); + } + return {} as never; + }); + const result = await checkDevicePrerequisites({ appId: "com.jetbrains.kotlinconf", }); - // This test depends on having a device/emulator connected - // In CI, we'd mock the exec call - expect(result).toHaveProperty("isOnline"); - - if (result.isOnline) { - expect(result.deviceId).toBeTruthy(); - expect(result.details).toHaveProperty("totalDevices"); - } else { - // No device connected - this is expected in test environment - expect(result.error).toBeTruthy(); - expect(result.error).toContain("device"); - } + expect(result.isOnline).toBe(true); + expect(result.deviceId).toBe("emulator-5554"); + expect(result.details).toHaveProperty("totalDevices"); + expect(result.details?.totalDevices).toBe(1); }); test("should return error when no devices connected", async () => { - // This test would need mocking to simulate "no devices" - // For now, just verify error structure exists + // Mock adb output with no devices + vi.mocked(childProcess.exec).mockImplementation((cmd, callback) => { + if (typeof callback === "function") { + callback(null, { + stdout: "List of devices attached\n", + stderr: "", + } as never); + } + return {} as never; + }); + const result = await checkDevicePrerequisites({ appId: "com.example.test", }); - expect(result).toHaveProperty("isOnline"); - - if (!result.isOnline) { - expect(result.error).toBeTruthy(); - expect(result.error).toContain("device"); - } + expect(result.isOnline).toBe(false); + expect(result.error).toBeTruthy(); + expect(result.error).toContain("No connected devices found"); }); test("should include device details when online", async () => { + // Mock successful adb output with specific device + vi.mocked(childProcess.exec).mockImplementation((cmd, callback) => { + if (typeof callback === "function") { + callback(null, { + stdout: + "List of devices attached\nemulator-5554 device product:sdk_gphone64_arm64", + stderr: "", + } as never); + } + return {} as never; + }); + const result = await checkDevicePrerequisites({ appId: "com.jetbrains.kotlinconf", deviceId: "emulator-5554", }); - expect(result).toHaveProperty("details"); - - if (result.isOnline) { - expect(result.details).toHaveProperty("adbOutput"); - } + expect(result.isOnline).toBe(true); + expect(result.details).toHaveProperty("adbOutput"); + expect(result.details).toHaveProperty("totalDevices"); }); test("should filter for specific deviceId when provided", async () => { - // Test with a likely-available device ID + // Mock adb output with multiple devices + vi.mocked(childProcess.exec).mockImplementation((cmd, callback) => { + if (typeof callback === "function") { + callback(null, { + stdout: + "List of devices attached\nemulator-5554 device product:sdk_gphone64_arm64\nemulator-5556 device product:sdk_gphone64_x86", + stderr: "", + } as never); + } + return {} as never; + }); + const result = await checkDevicePrerequisites({ appId: "com.jetbrains.kotlinconf", deviceId: "emulator-5554", }); - if (result.isOnline) { - // If device found, should match requested ID - expect(result.deviceId).toBe("emulator-5554"); - } else { - // If not found, error should mention requested ID - expect(result.error).toContain("emulator-5554"); - expect(result.error).toContain("not found"); - expect(result.details).toHaveProperty("requestedDeviceId"); - expect(result.details?.requestedDeviceId).toBe("emulator-5554"); - } + expect(result.isOnline).toBe(true); + expect(result.deviceId).toBe("emulator-5554"); + expect(result.details?.totalDevices).toBe(2); }); test("should return error when requested deviceId not found in multi-device lab", async () => { - // Test with a non-existent device ID + // Mock adb output with devices, but not the requested one + vi.mocked(childProcess.exec).mockImplementation((cmd, callback) => { + if (typeof callback === "function") { + callback(null, { + stdout: + "List of devices attached\nemulator-5554 device product:sdk_gphone64_arm64\nemulator-5556 device product:sdk_gphone64_x86", + stderr: "", + } as never); + } + return {} as never; + }); + const result = await checkDevicePrerequisites({ appId: "com.jetbrains.kotlinconf", deviceId: "nonexistent-device-12345", }); - // Should always fail for non-existent device expect(result.isOnline).toBe(false); expect(result.error).toBeTruthy(); expect(result.error).toContain("nonexistent-device-12345"); expect(result.error).toContain("not found"); + expect(result.details).toHaveProperty("requestedDeviceId", "nonexistent-device-12345"); + expect(result.details).toHaveProperty("availableDevices"); + expect(Array.isArray(result.details?.availableDevices)).toBe(true); + expect(result.details?.availableDevices).toHaveLength(2); + }); + + test("should handle adb command failure gracefully", async () => { + // Mock adb command failure (binary not found) + vi.mocked(childProcess.exec).mockImplementation((cmd, callback) => { + if (typeof callback === "function") { + const error = new Error("Command failed: adb devices -l\n/bin/sh: 1: adb: not found"); + callback(error as never, null as never); + } + return {} as never; + }); - // Should list available devices in error - if (result.details?.availableDevices) { - expect(Array.isArray(result.details.availableDevices)).toBe(true); - } + const result = await checkDevicePrerequisites({ + appId: "com.example.test", + }); + + expect(result.isOnline).toBe(false); + expect(result.error).toBeTruthy(); + expect(result.error).toContain("Device check failed"); }); }); diff --git a/bun.lock b/bun.lock index 9d57257..dcc9baf 100644 --- a/bun.lock +++ b/bun.lock @@ -48,6 +48,7 @@ "envalid": "^8.1.1", "lucide-svelte": "^0.425.0", "tailwind-merge": "^3.3.1", + "tailwind-variants": "^3.1.1", "tw-animate-css": "^1.4.0", }, "devDependencies": { @@ -1755,6 +1756,8 @@ "tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="], + "tailwind-variants": ["tailwind-variants@3.1.1", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-ftLXe3krnqkMHsuBTEmaVUXYovXtPyTK7ckEfDRXS8PBZx0bAUas+A0jYxuKA5b8qg++wvQ3d2MQ7l/xeZxbZQ=="], + "tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="], "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], diff --git a/frontend/package.json b/frontend/package.json index 1ba20b1..1d7a77e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,7 @@ "envalid": "^8.1.1", "lucide-svelte": "^0.425.0", "tailwind-merge": "^3.3.1", + "tailwind-variants": "^3.1.1", "tw-animate-css": "^1.4.0" }, "devDependencies": {