seed(LegalDocumentSeeder::class); $this->seed(CityWorldSeeder::class); } /** * Create an admin that already accepted all current legal documents. */ private function makeAdmin(): User { $admin = User::factory()->admin()->create(); foreach (LegalDocument::where('is_current', true)->pluck('id') as $docId) { UserConsent::create([ 'user_id' => $admin->id, 'legal_document_id' => $docId, 'accepted_at' => now(), 'ip_address' => '127.0.0.1', 'user_agent' => 'phpunit', 'source' => 'web_form', ]); } return $admin; } /** * New registrations must start as pending, not active. */ public function test_register_creates_pending_user(): void { $response = $this->post('/register', [ 'name' => 'Nova Pessoa', 'email' => 'nova@example.com', 'password' => 'Secret123!', 'password_confirmation' => 'Secret123!', 'locale' => 'pt_BR', 'captcha' => $this->captchaForRegister(), ]); $user = User::where('email', 'nova@example.com')->first(); $this->assertNotNull($user); $this->assertSame(User::STATUS_PENDING, $user->status); $this->assertFalse($user->is_active); $response->assertRedirect(route('terms.accept')); } /** * Pending users are sent to the approval wait screen from the dashboard. */ public function test_pending_user_sees_wait_screen(): void { $user = User::factory()->pending()->create(); $this->actingAs($user) ->get('/dashboard') ->assertRedirect(route('account.pending')); $this->actingAs($user) ->get(route('account.pending')) ->assertOk() ->assertSee(__('auth.pending_title'), false); } /** * Login attempt by a pending account shows the approval message. */ public function test_login_rejects_pending_with_message(): void { $user = User::factory()->pending()->create(['password' => 'Secret123!']); $response = $this->post('/login', [ 'email' => $user->email, 'password' => 'Secret123!', 'captcha' => $this->captchaForLogin(), ]); $response->assertSessionHasErrors('email'); $this->assertSame(__('auth.pending'), session('errors')->first('email')); } /** * Admin approval activates the account and creates the character. */ public function test_admin_approval_creates_character(): void { $admin = $this->makeAdmin(); $pending = User::factory()->pending()->create(['name' => 'Ana Silva']); $this->actingAs($admin) ->post(route('admin.citizens.approve', $pending)) ->assertRedirect(route('admin.citizens.index')); $pending->refresh(); $this->assertSame(User::STATUS_ACTIVE, $pending->status); $this->assertTrue($pending->is_active); $character = Character::query()->where('user_id', $pending->id)->first(); $this->assertNotNull($character); $this->assertSame('Ana', $character->name); $this->assertNotNull(City::active()); $this->assertSame(City::active()->id, $character->city_id); } /** * Banned accounts cannot authenticate. */ public function test_banned_user_cannot_login(): void { $user = User::factory()->inactive()->create(['password' => 'Secret123!']); $response = $this->post('/login', [ 'email' => $user->email, 'password' => 'Secret123!', 'captcha' => $this->captchaForLogin(), ]); $response->assertSessionHasErrors('email'); } /** * Solve the session math CAPTCHA for a register request. */ private function captchaForRegister(): int { // Prime the session by loading the form first in real usage; in tests // we set the session keys directly to avoid an extra round-trip. $a = random_int(1, 9); $b = random_int(1, 9); session(['captcha_result' => $a + $b, 'captcha_expires_at' => now()->addMinutes(10)->getTimestamp()]); return $a + $b; } /** * Solve the session math CAPTCHA for a login request. */ private function captchaForLogin(): int { return $this->captchaForRegister(); } }