mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-04 11:05:53 +00:00
387 lines
No EOL
7.8 KiB
Markdown
387 lines
No EOL
7.8 KiB
Markdown
# Testing Guide
|
|
|
|
## Quick Commands
|
|
|
|
```bash
|
|
# Run all tests
|
|
./scripts/test-all.sh
|
|
|
|
# Platform-specific
|
|
cd mac && xcodebuild test
|
|
cd ios && ./scripts/test-with-coverage.sh
|
|
cd web && pnpm test
|
|
|
|
# With coverage
|
|
cd web && pnpm test:coverage
|
|
```
|
|
|
|
## Test Structure
|
|
|
|
```
|
|
tests/
|
|
├── unit/ # Unit tests
|
|
├── integration/ # Integration tests
|
|
├── e2e/ # End-to-end tests
|
|
└── fixtures/ # Test data
|
|
```
|
|
|
|
## Unit Testing
|
|
|
|
### Swift (XCTest)
|
|
|
|
```swift
|
|
// mac/VibeTunnelTests/ServerManagerTests.swift
|
|
import XCTest
|
|
@testable import VibeTunnel
|
|
|
|
class ServerManagerTests: XCTestCase {
|
|
func testServerStart() async throws {
|
|
let manager = ServerManager()
|
|
|
|
try await manager.start()
|
|
|
|
XCTAssertTrue(manager.isRunning)
|
|
XCTAssertEqual(manager.port, "4020")
|
|
}
|
|
|
|
func testPortValidation() {
|
|
XCTAssertThrowsError(try validatePort("abc"))
|
|
XCTAssertNoThrow(try validatePort("8080"))
|
|
}
|
|
}
|
|
```
|
|
|
|
### TypeScript (Vitest)
|
|
|
|
```typescript
|
|
// web/tests/session-manager.test.ts
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { SessionManager } from '../src/server/services/session-manager';
|
|
|
|
describe('SessionManager', () => {
|
|
let manager: SessionManager;
|
|
|
|
beforeEach(() => {
|
|
manager = new SessionManager();
|
|
});
|
|
|
|
it('creates session', async () => {
|
|
const session = await manager.create({
|
|
shell: '/bin/bash',
|
|
cols: 80,
|
|
rows: 24
|
|
});
|
|
|
|
expect(session.id).toBeDefined();
|
|
expect(session.status).toBe('running');
|
|
});
|
|
});
|
|
```
|
|
|
|
## Integration Testing
|
|
|
|
### API Testing
|
|
|
|
```typescript
|
|
// web/tests/integration/api.test.ts
|
|
import request from 'supertest';
|
|
import { app } from '../../src/server/app';
|
|
|
|
describe('API Integration', () => {
|
|
it('creates session via API', async () => {
|
|
const response = await request(app)
|
|
.post('/api/sessions')
|
|
.send({ shell: '/bin/bash' })
|
|
.expect(201);
|
|
|
|
expect(response.body).toHaveProperty('id');
|
|
expect(response.body.status).toBe('running');
|
|
});
|
|
});
|
|
```
|
|
|
|
### WebSocket Testing
|
|
|
|
```typescript
|
|
// web/tests/integration/websocket.test.ts
|
|
import { WebSocket } from 'ws';
|
|
|
|
describe('WebSocket Integration', () => {
|
|
it('connects to session', async () => {
|
|
const ws = new WebSocket('ws://localhost:4020/api/sessions/test/ws');
|
|
|
|
await new Promise((resolve) => {
|
|
ws.on('open', resolve);
|
|
});
|
|
|
|
ws.send(JSON.stringify({ type: 'input', data: 'echo test\n' }));
|
|
|
|
const message = await new Promise((resolve) => {
|
|
ws.on('message', resolve);
|
|
});
|
|
|
|
expect(message.toString()).toContain('test');
|
|
});
|
|
});
|
|
```
|
|
|
|
## E2E Testing
|
|
|
|
### Playwright Setup
|
|
|
|
```typescript
|
|
// web/playwright.config.ts
|
|
import { defineConfig } from '@playwright/test';
|
|
|
|
export default defineConfig({
|
|
testDir: './tests/e2e',
|
|
use: {
|
|
baseURL: 'http://localhost:4020',
|
|
trace: 'on-first-retry',
|
|
},
|
|
webServer: {
|
|
command: 'pnpm dev',
|
|
port: 4020,
|
|
reuseExistingServer: !process.env.CI,
|
|
},
|
|
});
|
|
```
|
|
|
|
### E2E Tests
|
|
|
|
```typescript
|
|
// web/tests/e2e/terminal.test.ts
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test('complete terminal workflow', async ({ page }) => {
|
|
// Navigate to app
|
|
await page.goto('/');
|
|
|
|
// Create new terminal
|
|
await page.click('button:has-text("New Terminal")');
|
|
|
|
// Wait for terminal to load
|
|
const terminal = page.locator('.terminal');
|
|
await expect(terminal).toBeVisible();
|
|
|
|
// Type command
|
|
await page.keyboard.type('echo "Hello, VibeTunnel"');
|
|
await page.keyboard.press('Enter');
|
|
|
|
// Verify output
|
|
await expect(terminal).toContainText('Hello, VibeTunnel');
|
|
|
|
// Close session
|
|
await page.click('button[aria-label="Close terminal"]');
|
|
await expect(terminal).not.toBeVisible();
|
|
});
|
|
```
|
|
|
|
## Performance Testing
|
|
|
|
### Load Testing
|
|
|
|
```javascript
|
|
// tests/performance/load.js
|
|
import { check } from 'k6';
|
|
import ws from 'k6/ws';
|
|
|
|
export default function() {
|
|
const url = 'ws://localhost:4020/api/sessions/test/ws';
|
|
|
|
ws.connect(url, {}, function(socket) {
|
|
socket.on('open', () => {
|
|
socket.send(JSON.stringify({ type: 'input', data: 'ls\n' }));
|
|
});
|
|
|
|
socket.on('message', (data) => {
|
|
check(data, {
|
|
'received response': (d) => d.length > 0,
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
export const options = {
|
|
vus: 100, // 100 virtual users
|
|
duration: '30s', // 30 second test
|
|
};
|
|
```
|
|
|
|
### Benchmark Suite
|
|
|
|
```typescript
|
|
// tests/performance/benchmark.ts
|
|
import { bench, describe } from 'vitest';
|
|
|
|
describe('Buffer encoding performance', () => {
|
|
bench('encode 1KB', () => {
|
|
encodeBuffer('x'.repeat(1024));
|
|
});
|
|
|
|
bench('encode 10KB', () => {
|
|
encodeBuffer('x'.repeat(10240));
|
|
});
|
|
});
|
|
```
|
|
|
|
## Test Coverage
|
|
|
|
### Coverage Requirements
|
|
|
|
| Component | Target | Current |
|
|
|-----------|--------|---------|
|
|
| Server | 80% | 85% |
|
|
| Client | 70% | 72% |
|
|
| Mac App | 60% | 65% |
|
|
| iOS App | 75% | 78% |
|
|
|
|
### Generate Reports
|
|
|
|
```bash
|
|
# Web coverage
|
|
cd web && pnpm test:coverage
|
|
|
|
# iOS coverage
|
|
cd ios && ./scripts/test-with-coverage.sh
|
|
|
|
# View HTML report
|
|
open coverage/index.html
|
|
```
|
|
|
|
## Testing External Devices
|
|
|
|
### iPad/iPhone Testing
|
|
|
|
```bash
|
|
# 1. Start dev server on all interfaces
|
|
cd web && pnpm dev --host 0.0.0.0
|
|
|
|
# 2. Get Mac IP
|
|
ifconfig | grep inet
|
|
|
|
# 3. Access from device
|
|
# http://192.168.1.100:4021
|
|
```
|
|
|
|
### Cross-Browser Testing
|
|
|
|
```typescript
|
|
// playwright.config.ts
|
|
projects: [
|
|
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
|
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
|
|
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
|
|
{ name: 'Mobile Safari', use: { ...devices['iPhone 13'] } },
|
|
]
|
|
```
|
|
|
|
## Mocking & Fixtures
|
|
|
|
### Mock PTY
|
|
|
|
```typescript
|
|
// tests/mocks/pty.ts
|
|
export class MockPTY {
|
|
write(data: string) {
|
|
this.emit('data', `mock: ${data}`);
|
|
}
|
|
|
|
resize(cols: number, rows: number) {
|
|
this.cols = cols;
|
|
this.rows = rows;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Test Fixtures
|
|
|
|
```typescript
|
|
// tests/fixtures/sessions.ts
|
|
export const mockSession = {
|
|
id: 'test-session-123',
|
|
name: 'Test Session',
|
|
status: 'running',
|
|
created: new Date(),
|
|
pid: 12345,
|
|
};
|
|
```
|
|
|
|
## CI/CD Testing
|
|
|
|
### GitHub Actions
|
|
|
|
```yaml
|
|
# .github/workflows/test.yml
|
|
name: Tests
|
|
on: [push, pull_request]
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: macos-14
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
cd web && pnpm install
|
|
|
|
- name: Run tests
|
|
run: ./scripts/test-all.sh
|
|
|
|
- name: Upload coverage
|
|
uses: codecov/codecov-action@v3
|
|
```
|
|
|
|
## Debugging Tests
|
|
|
|
### Debug Swift Tests
|
|
|
|
```bash
|
|
# Run with verbose output
|
|
xcodebuild test -verbose
|
|
|
|
# Debug specific test
|
|
xcodebuild test -only-testing:VibeTunnelTests/ServerManagerTests/testServerStart
|
|
```
|
|
|
|
### Debug TypeScript Tests
|
|
|
|
```bash
|
|
# Run with inspector
|
|
node --inspect-brk ./node_modules/.bin/vitest
|
|
|
|
# Run single test file
|
|
pnpm test session-manager.test.ts
|
|
|
|
# Watch mode
|
|
pnpm test --watch
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Test naming**: Use descriptive names like `shouldCreateSessionWithCustomShell`
|
|
2. **Isolation**: Each test should be independent
|
|
3. **Cleanup**: Always cleanup resources (sessions, files, connections)
|
|
4. **Assertions**: Test both success and error cases
|
|
5. **Speed**: Keep unit tests under 100ms each
|
|
6. **Flakiness**: Retry flaky tests, investigate root cause
|
|
|
|
## Common Issues
|
|
|
|
| Issue | Solution |
|
|
|-------|----------|
|
|
| Tests timeout | Increase timeout, check async |
|
|
| Port conflicts | Use random ports in tests |
|
|
| Flaky WebSocket | Add connection retry logic |
|
|
| Coverage gaps | Add tests for error paths |
|
|
|
|
## See Also
|
|
- [Development Guide](development.md)
|
|
- [CI/CD Setup](../reference/release-process.md#cicd-pipeline)
|
|
- [Troubleshooting](../reference/troubleshooting.md) |