jam-cloud/jam-ui/test/analyze-session-flow.ts

615 lines
19 KiB
TypeScript

/**
* Analyzes the captured network traffic to document the session join flow
* and create a migration test plan
*/
import * as fs from 'fs';
import * as path from 'path';
interface ApiCall {
step: string;
method?: string;
url: string;
status?: number;
timestamp: string;
}
interface WebSocketMessage {
event: string;
payload?: string;
url?: string;
timestamp: string;
}
function analyzeApiCalls(apiCalls: ApiCall[]): Map<string, ApiCall[]> {
const apiMap = new Map<string, ApiCall[]>();
for (const call of apiCalls) {
if (call.step === 'REQUEST' && call.url.includes('/api/')) {
// Extract the API endpoint path
const url = new URL(call.url);
const endpoint = `${call.method} ${url.pathname}`;
if (!apiMap.has(endpoint)) {
apiMap.set(endpoint, []);
}
apiMap.get(endpoint)!.push(call);
}
}
return apiMap;
}
function analyzeWebSocketMessages(wsMessages: WebSocketMessage[]): Map<string, number> {
const messageTypes = new Map<string, number>();
for (const msg of wsMessages) {
if (msg.event === 'sent' || msg.event === 'received') {
try {
const payload = JSON.parse(msg.payload || '{}');
// Extract message type or method name
let messageType = 'unknown';
if (payload.type) {
messageType = `TYPE:${payload.type}`;
} else if (payload.method) {
messageType = `METHOD:${payload.method}`;
} else if (payload.args && payload.args.length > 0) {
// Try to parse the args to get more info
try {
const argsData = JSON.parse(payload.args[0]);
if (argsData.method_name) {
messageType = `NATIVE_METHOD:${argsData.method_name}`;
} else if (argsData.event_id) {
messageType = `NATIVE_EVENT:${argsData.event_id}`;
}
} catch (e) {
// Ignore parse errors
}
}
messageTypes.set(messageType, (messageTypes.get(messageType) || 0) + 1);
} catch (e) {
// Ignore JSON parse errors
}
}
}
return messageTypes;
}
function extractSequentialFlow(apiCalls: ApiCall[], wsMessages: WebSocketMessage[]): any[] {
// Combine and sort by timestamp
const allEvents = [
...apiCalls.map(call => ({
type: call.step,
subtype: 'API',
method: call.method,
url: call.url,
status: call.status,
timestamp: new Date(call.timestamp).getTime(),
})),
...wsMessages.filter(msg => msg.event === 'sent' || msg.event === 'received').map(msg => ({
type: msg.event === 'sent' ? 'WS_SENT' : 'WS_RECEIVED',
subtype: 'WEBSOCKET',
payload: msg.payload?.substring(0, 200),
timestamp: new Date(msg.timestamp).getTime(),
})),
].sort((a, b) => a.timestamp - b.timestamp);
return allEvents;
}
function main() {
const testResultsDir = path.join(__dirname, '../test-results');
console.log('='.repeat(80));
console.log('SESSION JOIN FLOW ANALYSIS');
console.log('='.repeat(80));
console.log();
// Read API calls
const apiCallsPath = path.join(testResultsDir, 'api-calls.json');
const apiCalls: ApiCall[] = JSON.parse(fs.readFileSync(apiCallsPath, 'utf-8'));
console.log(`✓ Loaded ${apiCalls.length} API call records`);
// Read WebSocket messages
const wsMessagesPath = path.join(testResultsDir, 'websocket-messages.json');
const wsMessages: WebSocketMessage[] = JSON.parse(fs.readFileSync(wsMessagesPath, 'utf-8'));
console.log(`✓ Loaded ${wsMessages.length} WebSocket message records`);
console.log();
// Analyze API calls
console.log('API ENDPOINTS CALLED');
console.log('-'.repeat(80));
const apiMap = analyzeApiCalls(apiCalls);
const sortedApis = Array.from(apiMap.entries()).sort((a, b) => b[1].length - a[1].length);
for (const [endpoint, calls] of sortedApis) {
console.log(` ${endpoint.padEnd(60)} (${calls.length} calls)`);
}
console.log();
// Analyze WebSocket messages
console.log('WEBSOCKET MESSAGE TYPES');
console.log('-'.repeat(80));
const messageTypes = analyzeWebSocketMessages(wsMessages);
const sortedTypes = Array.from(messageTypes.entries()).sort((a, b) => b[1] - a[1]);
for (const [type, count] of sortedTypes.slice(0, 20)) {
console.log(` ${type.padEnd(60)} (${count} times)`);
}
console.log(` ... and ${sortedTypes.length - 20} more message types`);
console.log();
// Generate migration plan
const planPath = path.join(testResultsDir, 'SESSION_MIGRATION_PLAN.md');
const plan = `# Session Join Flow Migration Test Plan
## Overview
This document outlines the complete test plan for migrating the "Joining a Music Session" feature from the legacy JamKazam application to the new React-based jam-ui application.
**Captured on:** ${new Date().toISOString()}
**Legacy URL:** http://www.jamkazam.local:3100
**Target:** jam-ui React application
## Overview of Flow
The session join flow consists of 5 main steps:
1. User Authentication (login)
2. Dashboard Load (with WebSocket connection to native client)
3. Skip Upgrade Modal (keyboard shortcut)
4. Navigate to Create Session
5. Create Quick Start Session (joins the session)
Note: The Ctrl+Shift+0 keyboard shortcut for enabling native client features is handled during session creation (Step 5), not as a separate step.
## Session Join Flow Steps
### Step 1: User Authentication
**Description:** User logs into the application
**API Calls Required:**
- POST /api/sessions (login endpoint)
- GET /api/users/{user_id} (fetch user profile)
- GET /api/genres (load genre data)
- GET /api/countries (load country data)
**Test Requirements:**
- Verify authentication token is stored in cookies
- Verify user session is established
- Verify redirect to dashboard after successful login
---
### Step 2: Dashboard Load
**Description:** Load user dashboard with all necessary data
**API Calls Required:**
${Array.from(apiMap.entries())
.filter(([endpoint]) => !endpoint.includes('paypal') && !endpoint.includes('session_create'))
.slice(0, 20)
.map(([endpoint]) => `- ${endpoint}`)
.join('\n')}
**WebSocket Connection:**
- Establish WebSocket connection to native client (ws://localhost:3060/)
- Initialize jkfrontendchannel communication
- Exchange handshake messages
**Native Client Messages:**
${Array.from(messageTypes.entries())
.filter(([type]) => type.startsWith('NATIVE_METHOD:'))
.slice(0, 10)
.map(([type, count]) => `- ${type} (${count} times)`)
.join('\n')}
**Test Requirements:**
- Verify all dashboard data loads correctly
- Verify WebSocket connection establishes successfully
- Verify native client communication is functional
- Verify user can see friends, notifications, and session options
---
### Step 3: Skip Upgrade Modal
**Description:** Handle upgrade modal on dashboard
**User Action:** Press Cmd+Shift+0 (Mac) or Ctrl+Shift+0 (Windows)
**API Calls Made:**
- GET /api/versioncheck
**WebSocket Connection Established:**
- Opens WebSocket connection to server: `ws://www.jamkazam.local:6767/websocket?channel_id=...&client_type=browser...`
- This is a CRITICAL connection - not just a UI interaction
- Approximately 100 WebSocket messages exchanged during initialization
**Expected Behavior:**
- Upgrade modal dismisses
- Server WebSocket connection established
- User can interact with dashboard
**Test Requirements:**
- Verify keyboard shortcut works
- Verify modal closes without errors
- **Verify WebSocket connection to server is established**
- Verify dashboard remains functional
---
### Step 4: Navigate to Create Session
**Description:** User clicks "Create Session" tile
**API Calls Made:**
- GET /api/sessions/scheduled (fetch any scheduled sessions)
- GET /api/users/{user_id} (refresh user profile)
- GET /api/jamtracks/purchased (refresh purchased tracks)
**Expected Behavior:**
- Navigate to session creation page
- Load session creation UI
- Maintain existing WebSocket connection from Step 3
- Load list of scheduled sessions
**Test Requirements:**
- Verify navigation occurs
- Verify session creation options are visible
- Verify scheduled sessions are loaded
- Verify no API errors occur
- Verify WebSocket connection remains active
---
### Step 5: Create Quick Start Session
**Description:** User creates a quick start session
**User Action:** Press Ctrl+Shift+0 to enable native client features, then click "Create Quick Start" button
**API Calls Made (28 total):**
**Critical New API Call:**
- **POST /api/users/{user_id}/udp_reachable** - Reports UDP reachability for P2P connections
**Dashboard Data Reload (triggered by Ctrl+Shift+0):**
- GET /api/users/{user_id} (2 times)
- GET /api/countries
- GET /api/shopping_carts
- GET /api/genres (3 times)
- GET /api/instruments (3 times)
- GET /api/users/{user_id}/friends (2 times)
- GET /api/jamtracks/purchased (3 times)
- GET /api/languages
- GET /api/subjects
- GET /api/chat
- GET /api/sessions/scheduled
- GET /api/users/{user_id}/notifications
- GET /api/regions
- GET /api/versioncheck (3 times)
- GET /api/teacher_distributions
- GET /api/config/client
- GET /api/users/{user_id}/broadcast_notification
**Expected Session Creation API Calls:**
- POST /api/music_sessions (create session)
- GET /api/music_sessions/{session_id} (fetch session details)
- GET /api/music_sessions/{session_id}/participants (fetch participants)
**WebSocket Connections Established:**
1. **Native Client Connection:**
- URL: `ws://localhost:3060/`
- Purpose: Audio/video streaming and control
- ~100+ messages during initialization
2. **Server Connection:**
- URL: `ws://www.jamkazam.local:6767/websocket?channel_id=...&client_id=...`
- Purpose: Session coordination and real-time updates
- ~100+ messages during initialization
**Key Native Client Operations:**
- NetworkTestResult calls
- getConnectionDetail calls
- SetLatencyTestBlocked
- SetScoreWorkTimingInterval
**Important Notes:**
- Pressing Ctrl+Shift+0 essentially "reinitializes" the client
- It triggers a full dashboard data reload
- It establishes BOTH WebSocket connections simultaneously
- UDP reachability check is performed before session creation
**Test Requirements:**
- Verify Ctrl+Shift+0 triggers dashboard data reload
- Verify POST /api/users/{user_id}/udp_reachable is called
- Verify native client WebSocket connection (ws://localhost:3060/)
- Verify server WebSocket connection is established
- Verify session is created successfully
- Verify session ID is returned
- Verify WebSocket messages are exchanged correctly
- Verify user enters session interface
- Verify audio setup begins
---
## Complete API Endpoint Reference
### Authentication & User Management
${Array.from(apiMap.entries())
.filter(([endpoint]) => endpoint.includes('/users') || endpoint.includes('/session'))
.map(([endpoint, calls]) => `- ${endpoint} (called ${calls.length} times)`)
.join('\n')}
### Static Data Loading
${Array.from(apiMap.entries())
.filter(([endpoint]) =>
endpoint.includes('/genres') ||
endpoint.includes('/instruments') ||
endpoint.includes('/countries') ||
endpoint.includes('/languages') ||
endpoint.includes('/regions')
)
.map(([endpoint, calls]) => `- ${endpoint} (called ${calls.length} times)`)
.join('\n')}
### Session Management
${Array.from(apiMap.entries())
.filter(([endpoint]) => endpoint.includes('/music_session'))
.map(([endpoint, calls]) => `- ${endpoint} (called ${calls.length} times)`)
.join('\n')}
### Real-time Features
${Array.from(apiMap.entries())
.filter(([endpoint]) =>
endpoint.includes('/chat') ||
endpoint.includes('/notification') ||
endpoint.includes('/broadcast')
)
.map(([endpoint, calls]) => `- ${endpoint} (called ${calls.length} times)`)
.join('\n')}
### System Health
${Array.from(apiMap.entries())
.filter(([endpoint]) =>
endpoint.includes('/healthcheck') ||
endpoint.includes('/versioncheck') ||
endpoint.includes('/config')
)
.map(([endpoint, calls]) => `- ${endpoint} (called ${calls.length} times)`)
.join('\n')}
---
## WebSocket Message Types Reference
### Connection & Handshake
${Array.from(messageTypes.entries())
.filter(([type]) => type.includes('TYPE:'))
.map(([type, count]) => `- ${type} (${count} messages)`)
.join('\n')}
### Native Client Methods (Most Frequent)
${Array.from(messageTypes.entries())
.filter(([type]) => type.startsWith('NATIVE_METHOD:'))
.slice(0, 15)
.map(([type, count]) => `- ${type} (${count} calls)`)
.join('\n')}
### Native Client Events
${Array.from(messageTypes.entries())
.filter(([type]) => type.startsWith('NATIVE_EVENT:'))
.slice(0, 10)
.map(([type, count]) => `- ${type} (${count} events)`)
.join('\n')}
---
## Playwright Test Structure
### Test 1: Complete Session Join Flow
\`\`\`typescript
test('User can join a music session - complete flow', async ({ page }) => {
// Step 1: Login
await loginUser(page, 'user@example.com', 'password');
// Step 2: Wait for dashboard to load
await expect(page.locator('[data-testid="dashboard"]')).toBeVisible();
// Step 3: Skip upgrade modal
await page.keyboard.press('Meta+Shift+Digit0');
// Step 4: Click create session
await page.click('[data-testid="create-session"]');
// Step 5: Create quick start session (Ctrl+Shift+0 handled internally)
await page.keyboard.press('Control+Shift+Digit0');
await page.click('[data-testid="quick-start"]');
// Verify we're in session
await expect(page.locator('[data-testid="session-interface"]')).toBeVisible();
});
\`\`\`
### Test 2: API Calls Validation
\`\`\`typescript
test('Session join makes correct API calls', async ({ page }) => {
const apiCalls: string[] = [];
page.on('request', request => {
if (request.url().includes('/api/')) {
apiCalls.push(\`\${request.method()} \${new URL(request.url()).pathname}\`);
}
});
await joinSession(page);
// Verify critical API calls were made
expect(apiCalls).toContain('GET /api/users/');
expect(apiCalls).toContain('GET /api/genres');
expect(apiCalls).toContain('POST /api/music_sessions');
});
\`\`\`
### Test 3: WebSocket Communication
\`\`\`typescript
test('Session join establishes WebSocket connection', async ({ page }) => {
let wsConnected = false;
let messagesReceived = 0;
page.on('websocket', ws => {
wsConnected = true;
ws.on('framereceived', () => messagesReceived++);
});
await joinSession(page);
expect(wsConnected).toBe(true);
expect(messagesReceived).toBeGreaterThan(0);
});
\`\`\`
### Test 4: Native Client Integration
\`\`\`typescript
test('Native client methods are called correctly', async ({ page }) => {
const nativeMethods: string[] = [];
page.on('websocket', ws => {
ws.on('framesent', frame => {
try {
const data = JSON.parse(frame.payload.toString());
if (data.args) {
const argsData = JSON.parse(data.args[0]);
if (argsData.method_name) {
nativeMethods.push(argsData.method_name);
}
}
} catch (e) {}
});
});
await joinSession(page);
// Verify critical native methods were called
expect(nativeMethods).toContain('getConnectionDetail');
expect(nativeMethods).toContain('NetworkTestResult');
});
\`\`\`
---
## Implementation Checklist
### Phase 1: Authentication & Dashboard
- [ ] Implement login flow matching legacy API calls
- [ ] Load all dashboard data (users, genres, instruments, etc.)
- [ ] Establish WebSocket connection to native client
- [ ] Implement upgrade modal with keyboard shortcut (Cmd/Ctrl+Shift+0)
- [ ] Implement dashboard UI with session creation option
- [ ] Test: User can log in, dismiss modal, and see dashboard
### Phase 2: Session Creation UI
- [ ] Implement "Create Session" navigation
- [ ] Implement session creation modal/page
- [ ] Implement keyboard shortcut (Ctrl+Shift+0) for enabling native features
- [ ] Implement "Quick Start" option
- [ ] Test: User can navigate to session creation and see options
### Phase 3: Native Client Integration
- [ ] Establish WebSocket communication protocol
- [ ] Implement native client message handlers
- [ ] Implement handshake and initialization sequence
- [ ] Test: WebSocket messages match legacy exactly
### Phase 4: Session Creation & Join
- [ ] Implement session creation API calls
- [ ] Handle session initialization responses
- [ ] Implement session interface loading
- [ ] Coordinate WebSocket messages with session creation
- [ ] Test: Complete flow works end-to-end
### Phase 5: Audio/Video Setup
- [ ] Implement audio device initialization via native client
- [ ] Implement latency testing (NetworkTestResult)
- [ ] Implement connection detail fetching (getConnectionDetail)
- [ ] Handle real-time audio/video stream setup
- [ ] Test: Audio setup completes successfully
---
## Critical Success Criteria
1. **API Compatibility**: All API calls must match the sequence and format of the legacy application
2. **WebSocket Protocol**: WebSocket messages must be identical in structure and timing
3. **Native Client Communication**: Native client method calls must match exactly
4. **User Experience**: Flow must be seamless with no additional steps
5. **Error Handling**: Must handle all error cases gracefully
6. **Performance**: Page loads and transitions must be as fast or faster than legacy
---
## Testing Strategy
### Unit Tests
- Test individual API call functions
- Test WebSocket message handlers
- Test UI component rendering
### Integration Tests
- Test complete authentication flow
- Test dashboard data loading
- Test session creation flow
### End-to-End Tests (Playwright)
- Test complete user journey from login to session join
- Test with real WebSocket connections
- Test with native client simulation
### Comparison Tests
- Run legacy and new app side-by-side
- Compare API call sequences
- Compare WebSocket message sequences
- Compare timing and performance
---
## Files Generated
- \`test-results/session-join-flow.har\` - Complete HAR file with all network traffic
- \`test-results/api-calls.json\` - All API calls made during the flow
- \`test-results/websocket-messages.json\` - All WebSocket messages exchanged
- \`test-results/all-network-activity.json\` - Combined network activity log
- \`test-results/*.png\` - Screenshots at various stages
---
## Next Steps
1. Review this plan with the team
2. Prioritize which components to implement first
3. Set up Playwright test infrastructure
4. Begin implementing Phase 1 (Authentication & Dashboard)
5. Write corresponding Playwright tests for each phase
6. Run comparison tests against legacy application
7. Iterate until behavior matches exactly
---
**Generated by:** Session Flow Analyzer
**Date:** ${new Date().toLocaleString()}
**Total API Calls Captured:** ${apiCalls.length}
**Total WebSocket Messages Captured:** ${wsMessages.length}
**Unique API Endpoints:** ${apiMap.size}
**Unique WebSocket Message Types:** ${messageTypes.size}
`;
fs.writeFileSync(planPath, plan);
console.log(`✅ Migration plan written to: ${planPath}`);
console.log();
console.log('Review the plan and adjust as needed for your specific requirements.');
}
main();