post('/register', [ 'email' => 'flow-user@example.com', ]); $register->assertRedirect('/register/notice'); $user = User::query()->where('email', 'flow-user@example.com')->firstOrFail(); expect($user->onboarding_step)->toBe('email'); $token = null; Queue::assertPushed(SendVerificationEmailJob::class, function (SendVerificationEmailJob $job) use (&$token) { $token = $job->token; return true; }); $this->get('/verify/' . $token)->assertRedirect('/setup/password'); $user->refresh(); expect($user->onboarding_step)->toBe('verified'); $this->actingAs($user) ->post('/setup/password', [ 'password' => 'StrongPass1!', 'password_confirmation' => 'StrongPass1!', ])->assertRedirect('/setup/username'); $this->actingAs($user) ->post('/setup/username', [ 'username' => 'flow_user_final', ])->assertRedirect('/@flow_user_final'); $user->refresh(); expect($user->onboarding_step)->toBe('complete'); expect($user->username)->toBe('flow_user_final'); }); it('rejects invalid verification token', function () { $response = $this->from('/login')->get('/verify/not-a-real-token'); $response->assertRedirect('/login'); $response->assertSessionHasErrors('email'); }); it('rejects expired verification token', function () { $user = User::factory()->create([ 'email_verified_at' => null, 'onboarding_step' => 'email', 'is_active' => false, ]); $column = \Illuminate\Support\Facades\Schema::hasColumn('user_verification_tokens', 'token_hash') ? 'token_hash' : 'token'; DB::table('user_verification_tokens')->insert([ 'user_id' => $user->id, $column => hash('sha256', 'expired-checklist-token'), 'expires_at' => now()->subHour(), 'created_at' => now(), 'updated_at' => now(), ]); $response = $this->from('/login')->get('/verify/expired-checklist-token'); $response->assertRedirect('/login'); $response->assertSessionHasErrors('email'); expect($user->fresh()->email_verified_at)->toBeNull(); }); it('rejects duplicate email at registration', function () { Queue::fake(); User::factory()->create([ 'email' => 'duplicate-check@example.com', 'email_verified_at' => now(), 'onboarding_step' => 'complete', 'is_active' => true, ]); $response = $this->post('/register', [ 'email' => 'duplicate-check@example.com', ]); $response->assertRedirect('/register/notice'); $response->assertSessionHas('status', 'If that email is valid, we sent a verification link.'); Queue::assertNothingPushed(); }); it('rejects username conflict during username setup', function () { User::factory()->create([ 'username' => 'taken_username', 'onboarding_step' => 'complete', ]); $user = User::factory()->create([ 'username' => 'candidate_username', 'onboarding_step' => 'password', ]); $this->actingAs($user) ->from('/setup/username') ->post('/setup/username', [ 'username' => 'taken_username', ]) ->assertRedirect('/setup/username') ->assertSessionHasErrors('username'); expect($user->fresh()->onboarding_step)->toBe('password'); }); it('resumes onboarding by redirecting user to current required step', function () { $user = User::factory()->create([ 'onboarding_step' => 'verified', ]); $this->actingAs($user) ->get('/profile') ->assertRedirect('/setup/password'); $user->forceFill(['onboarding_step' => 'password'])->save(); $this->actingAs($user) ->get('/upload') ->assertRedirect('/setup/username'); });