import { DataRelationManager } from '../../core/utils/data-relations' import { UserGenerator } from '../../core/generators/users' import { AudioGenerator } from '../../core/generators/audio' import { ConversationGenerator } from '../../core/generators/conversations' import { vezaFaker } from '../../core/utils/faker-config' import type { User, Audio, Playlist, Conversation } from '../../core/schemas/database' /** * New User Onboarding Scenario * * Simulates a complete user journey from registration to first meaningful interactions */ export interface OnboardingScenarioContext { user: User initialData: { recommendedTracks: Audio[] welcomePlaylist: Playlist welcomeConversation: Conversation onboardingSteps: OnboardingStep[] } expectedBehavior: { shouldSeeWelcomeMessage: boolean shouldHaveEmptyLibrary: boolean shouldReceiveRecommendations: boolean shouldJoinWelcomeChannel: boolean shouldCompleteOnboarding: boolean } testData: { loginCredentials: { email: string; password: string } profileData: Partial firstActions: UserAction[] } } export interface OnboardingStep { id: string title: string description: string type: 'welcome' | 'profile' | 'preferences' | 'discovery' | 'social' isRequired: boolean isCompleted: boolean data?: any } export interface UserAction { type: 'play_track' | 'like_track' | 'create_playlist' | 'follow_user' | 'send_message' timestamp: Date data: any expectedResult: string } /** * New User Onboarding Scenario Generator */ export class NewUserOnboardingScenario { /** * Generate complete onboarding scenario */ static async setup(): Promise { console.log('🎯 Setting up new user onboarding scenario...') // Generate new user const user = UserGenerator.generate({ role: 'user', status: 'active', emailVerified: false, // Will be verified during onboarding withAvatar: false, // Will be set during profile completion withBio: false, withStats: false, // New user has no activity }) // Override stats for new user user.stats = { tracksUploaded: 0, playlistsCreated: 0, followersCount: 0, followingCount: 0, totalPlays: 0, } DataRelationManager.registerUser(user) // Generate initial recommendations based on popular content const allTracks = Array.from(DataRelationManager.getAll().tracks.values()) const recommendedTracks = this.generateRecommendations(allTracks, user) // Create welcome playlist const welcomePlaylist = AudioGenerator.generatePlaylist({ createdById: 'system', // System-generated playlist trackCount: 10, visibility: 'public', theme: 'random' }) welcomePlaylist.title = 'Bienvenue sur Veza !' welcomePlaylist.description = 'Une sélection de morceaux populaires pour découvrir la plateforme' welcomePlaylist.trackIds = recommendedTracks.slice(0, 10).map(t => t.id) DataRelationManager.registerPlaylist(welcomePlaylist) // Create welcome conversation (general channel) const welcomeConversation = ConversationGenerator.generateConversation({ type: 'channel', participantIds: [user.id, 'system'], // User + system/moderators messageCount: 5, // Few welcome messages timeSpan: 'recent' }) welcomeConversation.name = 'Bienvenue' welcomeConversation.description = 'Canal d\'accueil pour les nouveaux utilisateurs' welcomeConversation.isPrivate = false DataRelationManager.registerConversation(welcomeConversation) // Generate onboarding steps const onboardingSteps = this.generateOnboardingSteps(user) // Generate first user actions const firstActions = this.generateFirstUserActions(user, recommendedTracks) const context: OnboardingScenarioContext = { user, initialData: { recommendedTracks, welcomePlaylist, welcomeConversation, onboardingSteps }, expectedBehavior: { shouldSeeWelcomeMessage: true, shouldHaveEmptyLibrary: true, shouldReceiveRecommendations: true, shouldJoinWelcomeChannel: true, shouldCompleteOnboarding: true }, testData: { loginCredentials: { email: user.email, password: 'TestPassword123!' }, profileData: { firstName: user.firstName, lastName: user.lastName, bio: 'Nouveau sur Veza, passionné de musique !', location: user.location, preferences: { ...user.preferences, notifications: { email: true, push: true, desktop: false } } }, firstActions } } console.log('✅ New user onboarding scenario setup complete') return context } /** * Generate multiple onboarding scenarios for different user types */ static async setupBatch(count: number = 5): Promise { const scenarios: OnboardingScenarioContext[] = [] for (let i = 0; i < count; i++) { const scenario = await this.setup() scenarios.push(scenario) } return scenarios } /** * Simulate user completing onboarding steps */ static simulateOnboardingCompletion(context: OnboardingScenarioContext): { completedSteps: OnboardingStep[] updatedUser: User generatedContent: { firstPlaylist: Playlist profileUpdates: Partial firstInteractions: UserAction[] } } { console.log(`🎮 Simulating onboarding completion for user: ${context.user.username}`) // Mark steps as completed const completedSteps = context.initialData.onboardingSteps.map(step => ({ ...step, isCompleted: true, data: this.generateStepCompletionData(step, context.user) })) // Update user profile const updatedUser: User = { ...context.user, ...context.testData.profileData, emailVerified: true, avatar: vezaFaker.image.avatar(), isVerified: false, // New users aren't verified yet preferences: { ...context.user.preferences, ...context.testData.profileData.preferences }, updatedAt: new Date() } // Create user's first playlist const firstPlaylist = AudioGenerator.generatePlaylist({ createdById: context.user.id, trackCount: 5, visibility: 'private', theme: 'random' }) firstPlaylist.title = 'Ma première playlist' firstPlaylist.description = 'Mes premiers coups de cœur sur Veza' firstPlaylist.trackIds = context.initialData.recommendedTracks.slice(0, 5).map(t => t.id) DataRelationManager.registerPlaylist(firstPlaylist) // Update user stats updatedUser.stats.playlistsCreated = 1 // Generate first interactions const firstInteractions = this.generateFirstInteractions(context) console.log('✅ Onboarding completion simulation complete') return { completedSteps, updatedUser, generatedContent: { firstPlaylist, profileUpdates: context.testData.profileData, firstInteractions } } } /** * Validate onboarding scenario expectations */ static validateScenario(context: OnboardingScenarioContext): { isValid: boolean passedChecks: string[] failedChecks: string[] warnings: string[] } { const passedChecks: string[] = [] const failedChecks: string[] = [] const warnings: string[] = [] // Check user data if (context.user.stats.tracksUploaded === 0) { passedChecks.push('New user has no uploaded tracks') } else { failedChecks.push('New user should not have uploaded tracks') } if (context.user.stats.playlistsCreated === 0) { passedChecks.push('New user has no created playlists') } else { failedChecks.push('New user should not have created playlists initially') } if (!context.user.emailVerified) { passedChecks.push('New user email is not verified initially') } else { warnings.push('New user email should not be verified initially') } // Check recommendations if (context.initialData.recommendedTracks.length > 0) { passedChecks.push('Recommendations are provided') } else { failedChecks.push('No recommendations provided for new user') } // Check welcome content if (context.initialData.welcomePlaylist) { passedChecks.push('Welcome playlist exists') } else { failedChecks.push('Welcome playlist not created') } if (context.initialData.welcomeConversation) { passedChecks.push('Welcome conversation exists') } else { failedChecks.push('Welcome conversation not created') } // Check onboarding steps const requiredSteps = context.initialData.onboardingSteps.filter(s => s.isRequired) if (requiredSteps.length >= 3) { passedChecks.push('Sufficient required onboarding steps') } else { failedChecks.push('Not enough required onboarding steps') } // Check first actions if (context.testData.firstActions.length > 0) { passedChecks.push('First user actions defined') } else { warnings.push('No first user actions defined') } return { isValid: failedChecks.length === 0, passedChecks, failedChecks, warnings } } /** * Private helper methods */ private static generateRecommendations(allTracks: Audio[], user: User): Audio[] { // For new users, recommend popular tracks across different genres const popularTracks = allTracks .sort((a, b) => b.stats.plays - a.stats.plays) .slice(0, 50) // Select diverse genres const genreGroups = this.groupTracksByGenre(popularTracks) const recommendations: Audio[] = [] // Take 2-3 tracks from each popular genre Object.entries(genreGroups).forEach(([genre, tracks]) => { const genreTracks = vezaFaker.helpers.arrayElements(tracks, Math.min(3, tracks.length)) recommendations.push(...genreTracks) }) // Shuffle and limit to 20 recommendations return vezaFaker.helpers.shuffle(recommendations).slice(0, 20) } private static groupTracksByGenre(tracks: Audio[]): Record { return tracks.reduce((groups, track) => { if (!groups[track.genre]) { groups[track.genre] = [] } groups[track.genre]!.push(track) return groups }, {} as Record) } private static generateOnboardingSteps(user: User): OnboardingStep[] { return [ { id: 'welcome', title: 'Bienvenue sur Veza !', description: 'Découvrez votre nouvelle plateforme musicale', type: 'welcome', isRequired: true, isCompleted: false, data: { welcomeMessage: `Salut ${user.firstName} ! Bienvenue dans la communauté Veza.`, features: ['streaming', 'playlists', 'chat', 'discovery'] } }, { id: 'email_verification', title: 'Vérifiez votre email', description: 'Confirmez votre adresse email pour sécuriser votre compte', type: 'profile', isRequired: true, isCompleted: false, data: { email: user.email, verificationCode: vezaFaker.string.numeric(6) } }, { id: 'profile_setup', title: 'Complétez votre profil', description: 'Ajoutez une photo et une bio pour vous présenter', type: 'profile', isRequired: false, isCompleted: false, data: { suggestedBio: 'Passionné de musique, nouveau sur Veza !', avatarOptions: [ vezaFaker.image.avatar(), vezaFaker.image.avatar(), vezaFaker.image.avatar() ] } }, { id: 'music_preferences', title: 'Choisissez vos genres préférés', description: 'Aidez-nous à vous recommander de la musique', type: 'preferences', isRequired: false, isCompleted: false, data: { availableGenres: ['rock', 'pop', 'jazz', 'electronic', 'hip-hop', 'classical'], maxSelections: 5 } }, { id: 'first_playlist', title: 'Créez votre première playlist', description: 'Organisez vos morceaux préférés', type: 'discovery', isRequired: false, isCompleted: false, data: { suggestedName: 'Mes découvertes', suggestedTracks: 5 } }, { id: 'join_community', title: 'Rejoignez la communauté', description: 'Participez aux discussions et découvrez de nouveaux artistes', type: 'social', isRequired: false, isCompleted: false, data: { recommendedChannels: ['Bienvenue', 'Découvertes', 'Feedback'], welcomeMessage: 'Salut tout le monde ! Je suis nouveau sur Veza 👋' } } ] } private static generateFirstUserActions(user: User, tracks: Audio[]): UserAction[] { const actions: UserAction[] = [] const now = new Date() // First action: play a recommended track const firstTrack = tracks[0] if (!firstTrack) return actions actions.push({ type: 'play_track', timestamp: new Date(now.getTime() + 5 * 60 * 1000), // 5 minutes after registration data: { trackId: firstTrack.id, duration: Math.min(firstTrack.metadata.duration, 30), // Listen for 30 seconds completion: 0.1 }, expectedResult: 'Track plays successfully, user engagement recorded' }) // Second action: like the track actions.push({ type: 'like_track', timestamp: new Date(now.getTime() + 6 * 60 * 1000), data: { trackId: firstTrack.id }, expectedResult: 'Track is added to user likes, recommendation algorithm updated' }) // Third action: create first playlist actions.push({ type: 'create_playlist', timestamp: new Date(now.getTime() + 10 * 60 * 1000), data: { name: 'Ma première playlist', description: 'Mes premiers coups de cœur', trackIds: tracks.slice(0, 3).map(t => t.id), visibility: 'private' }, expectedResult: 'Playlist created, user stats updated' }) // Fourth action: send welcome message actions.push({ type: 'send_message', timestamp: new Date(now.getTime() + 15 * 60 * 1000), data: { conversationId: 'welcome-channel', content: 'Salut tout le monde ! Je découvre Veza, c\'est génial ! 🎵', type: 'text' }, expectedResult: 'Message sent to welcome channel, community engagement started' }) return actions } private static generateStepCompletionData(step: OnboardingStep, user: User): any { switch (step.type) { case 'welcome': return { completedAt: new Date(), timeSpent: vezaFaker.number.int({ min: 30, max: 120 }), // seconds interacted: true } case 'profile': if (step.id === 'email_verification') { return { verifiedAt: new Date(), verificationCode: step.data?.verificationCode, attempts: 1 } } return { avatarSelected: vezaFaker.image.avatar(), bioAdded: true, completedAt: new Date() } case 'preferences': return { selectedGenres: vezaFaker.helpers.arrayElements( step.data?.availableGenres || [], vezaFaker.number.int({ min: 2, max: 5 }) ), completedAt: new Date() } case 'discovery': return { playlistCreated: true, tracksAdded: vezaFaker.number.int({ min: 3, max: 8 }), completedAt: new Date() } case 'social': return { channelsJoined: vezaFaker.helpers.arrayElements( step.data?.recommendedChannels || [], vezaFaker.number.int({ min: 1, max: 3 }) ), messageSent: true, completedAt: new Date() } default: return { completedAt: new Date(), success: true } } } private static generateFirstInteractions(context: OnboardingScenarioContext): UserAction[] { const interactions: UserAction[] = [] const now = new Date() // Simulate realistic user behavior over first hour const timeOffsets = [300, 600, 900, 1200, 1800, 2400, 3000, 3600] // seconds timeOffsets.forEach((offset, index) => { const actionTypes = ['play_track', 'like_track', 'create_playlist', 'follow_user', 'send_message'] const actionType = vezaFaker.helpers.arrayElement(actionTypes) as any let data: any = {} let expectedResult = '' switch (actionType) { case 'play_track': const track = vezaFaker.helpers.arrayElement(context.initialData.recommendedTracks) data = { trackId: track.id, duration: vezaFaker.number.int({ min: 15, max: track.metadata.duration }), completion: vezaFaker.number.float({ min: 0.1, max: 1.0 }) } expectedResult = 'Track playback initiated, listening stats updated' break case 'like_track': data = { trackId: vezaFaker.helpers.arrayElement(context.initialData.recommendedTracks).id } expectedResult = 'Track liked, user preferences updated' break case 'send_message': data = { conversationId: context.initialData.welcomeConversation.id, content: vezaFaker.chat.messageContent(), type: 'text' } expectedResult = 'Message sent, conversation activity updated' break default: data = { action: actionType } expectedResult = `${actionType} completed successfully` } interactions.push({ type: actionType, timestamp: new Date(now.getTime() + offset * 1000), data, expectedResult }) }) return interactions } } /** * Export scenario configuration */ export const NEW_USER_ONBOARDING_SCENARIO = { name: 'New User Onboarding', description: 'Complete user journey from registration to first meaningful platform interactions', duration: '1 hour', complexity: 'medium', userTypes: ['new_user'], expectedOutcomes: [ 'User successfully completes registration', 'Email verification completed', 'Profile setup completed', 'First playlist created', 'Community engagement initiated', 'Recommendation system activated' ], testCoverage: [ 'authentication_flow', 'profile_management', 'content_discovery', 'playlist_creation', 'social_features', 'notification_system' ] }