'base64:'.base64_encode(random_bytes(32)), 'captcha.enabled' => false, 'captcha.recaptcha.enabled' => false, 'captcha.turnstile.enabled' => false, ]); $this->app->instance(UncompromisedVerifier::class, new class implements UncompromisedVerifier { public function verify($data): bool { return true; } }); $this->app->bind(ValidEmailDomain::class, fn () => new class extends ValidEmailDomain { protected function validateDnsRecords(string $domain): bool { return true; } }); Schema::dropIfExists('users'); Schema::dropIfExists('roles'); Schema::dropIfExists('settings'); Schema::create('settings', function (Blueprint $table): void { $table->string('name')->primary(); $table->text('value')->nullable(); }); Schema::create('roles', function (Blueprint $table): void { $table->increments('id'); $table->string('name'); $table->string('guard_name'); $table->unsignedInteger('apirequests')->default(0); $table->integer('rate_limit')->default(60); $table->unsignedInteger('downloadrequests')->default(0); $table->unsignedInteger('defaultinvites')->default(0); $table->boolean('isdefault')->default(false); $table->integer('donation')->default(0); $table->integer('addyears')->default(0); $table->timestamps(); }); Schema::create('users', function (Blueprint $table): void { $table->increments('id'); $table->string('username')->unique(); $table->string('email')->unique(); $table->string('password'); $table->unsignedInteger('roles_id')->default(1); $table->string('host')->nullable(); $table->integer('grabs')->default(0); $table->string('api_token'); $table->integer('invites')->default(0); $table->boolean('movieview')->default(true); $table->boolean('xxxview')->default(false); $table->boolean('musicview')->default(true); $table->boolean('consoleview')->default(true); $table->boolean('bookview')->default(true); $table->boolean('gameview')->default(true); $table->integer('rate_limit')->default(60); $table->string('notes')->nullable(); $table->boolean('verified')->default(false); $table->boolean('can_post')->default(true); $table->timestamp('email_verified_at')->nullable(); $table->rememberToken(); $table->timestamps(); $table->softDeletes(); }); Schema::create('registration_periods', function (Blueprint $table): void { $table->increments('id'); $table->string('name'); $table->dateTime('starts_at'); $table->dateTime('ends_at'); $table->boolean('is_enabled')->default(true); $table->text('notes')->nullable(); $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->timestamps(); }); Schema::create('invitations', function (Blueprint $table): void { $table->increments('id'); $table->string('token')->unique(); $table->string('email')->nullable(); $table->unsignedInteger('invited_by'); $table->timestamp('expires_at'); $table->timestamp('used_at')->nullable(); $table->unsignedInteger('used_by')->nullable(); $table->boolean('is_active')->default(true); $table->json('metadata')->nullable(); $table->timestamps(); }); DB::table('settings')->insert([ 'name' => 'registerstatus', 'value' => '0', ]); DB::table('roles')->insert([ 'id' => 1, 'name' => 'User', 'guard_name' => 'web', 'isdefault' => 1, 'defaultinvites' => 1, 'created_at' => now(), 'updated_at' => now(), ]); } public function test_successful_registration_redirects_to_login_with_confirmation_message(): void { $this->mockRegisterControllerCreate(new User([ 'id' => 123, 'username' => 'newmember', 'email' => 'newmember@example.com', 'roles_id' => 1, ])); $response = $this->post(route('register.post'), [ 'action' => 'submit', 'username' => 'newmember', 'email' => 'newmember@example.com', 'password' => 'ValidPass1!', 'password_confirmation' => 'ValidPass1!', 'terms' => 'on', ]); $response->assertRedirect(route('login')); $response->assertSessionHas('info', 'Your account has been created. You will receive an account confirmation email shortly. Please verify your email address before logging in.'); } public function test_registration_failure_shows_explanation_for_deactivated_account(): void { Log::partialMock(); $registrationLogger = Mockery::mock(); Log::shouldReceive('channel') ->once() ->with('registration') ->andReturn($registrationLogger); $registrationLogger->shouldReceive('warning') ->once() ->with( 'Registration attempt failed: deactivated_account', Mockery::on(function (array $context): bool { return $context['reason'] === 'deactivated_account' && $context['email'] === 'disabled@example.com' && $context['username'] === 'freshmember' && $context['registration_status'] === 0; }) ); DB::table('users')->insert([ 'username' => 'oldmember', 'email' => 'disabled@example.com', 'password' => 'hashed-password', 'roles_id' => 1, 'api_token' => 'existing-token', 'verified' => 0, 'created_at' => now(), 'updated_at' => now(), 'deleted_at' => now(), ]); $response = $this->from(route('register'))->post(route('register.post'), [ 'action' => 'submit', 'username' => 'freshmember', 'email' => 'disabled@example.com', 'password' => 'ValidPass1!', 'password_confirmation' => 'ValidPass1!', 'terms' => 'on', ]); $response->assertRedirect(route('register')); $response->assertSessionHasErrors([ 'registration' => 'This email address belongs to a deactivated account. Please contact us if you need help reactivating it.', ]); } public function test_registration_exception_is_logged_to_registration_channel(): void { Log::partialMock(); $registrationLogger = Mockery::mock(); Log::shouldReceive('channel') ->once() ->with('registration') ->andReturn($registrationLogger); $registrationLogger->shouldReceive('error') ->once() ->with( 'Registration attempt failed: unexpected_exception', Mockery::on(function (array $context): bool { return $context['reason'] === 'unexpected_exception' && $context['email'] === 'broken@example.com' && $context['username'] === 'brokenmember' && $context['exception'] === \RuntimeException::class && $context['registration_status'] === 0; }) ); $this->mockRegisterControllerCreate( new \RuntimeException('Simulated registration failure') ); $response = $this->from(route('register'))->post(route('register.post'), [ 'action' => 'submit', 'username' => 'brokenmember', 'email' => 'broken@example.com', 'password' => 'ValidPass1!', 'password_confirmation' => 'ValidPass1!', 'terms' => 'on', ]); $response->assertRedirect(route('register')); $response->assertSessionHasErrors([ 'registration' => 'We could not complete your registration right now. Please try again in a few minutes. If the problem continues, contact support.', ]); } public function test_invite_only_registration_with_valid_invitation_succeeds(): void { DB::table('settings') ->where('name', 'registerstatus') ->update(['value' => '1']); DB::table('invitations')->insert([ 'token' => 'valid-token', 'email' => 'invitee@example.com', 'invited_by' => 1, 'expires_at' => now()->addDay(), 'is_active' => true, 'created_at' => now(), 'updated_at' => now(), ]); $this->mockRegisterControllerCreate(new User([ 'id' => 321, 'username' => 'inviteemember', 'email' => 'invitee@example.com', 'roles_id' => 1, ])); $response = $this->post(route('register.post'), [ 'action' => 'submit', 'token' => 'valid-token', 'username' => 'inviteemember', 'email' => 'invitee@example.com', 'password' => 'ValidPass1!', 'password_confirmation' => 'ValidPass1!', 'terms' => 'on', ]); $response->assertRedirect(route('login')); $this->assertNotNull( DB::table('invitations') ->where('token', 'valid-token') ->value('used_at') ); } public function test_closed_registration_rejects_even_valid_invitation(): void { DB::table('settings') ->where('name', 'registerstatus') ->update(['value' => '2']); DB::table('invitations')->insert([ 'token' => 'closed-token', 'email' => 'invitee@example.com', 'invited_by' => 1, 'expires_at' => now()->addDay(), 'is_active' => true, 'created_at' => now(), 'updated_at' => now(), ]); $response = $this->from(route('register'))->post(route('register.post'), [ 'action' => 'submit', 'token' => 'closed-token', 'username' => 'closedmember', 'email' => 'invitee@example.com', 'password' => 'ValidPass1!', 'password_confirmation' => 'ValidPass1!', 'terms' => 'on', ]); $response->assertRedirect(route('register')); $response->assertSessionHasErrors([ 'registration' => 'Registrations are currently closed.', ]); } public function test_scheduled_open_period_allows_registration_when_manual_status_is_closed(): void { DB::table('settings') ->where('name', 'registerstatus') ->update(['value' => '2']); DB::table('registration_periods')->insert([ 'name' => 'Weekend Open', 'starts_at' => now()->subHour(), 'ends_at' => now()->addHour(), 'is_enabled' => true, 'created_at' => now(), 'updated_at' => now(), ]); $this->mockRegisterControllerCreate(new User([ 'id' => 456, 'username' => 'scheduledmember', 'email' => 'scheduled@example.com', 'roles_id' => 1, ])); $response = $this->post(route('register.post'), [ 'action' => 'submit', 'username' => 'scheduledmember', 'email' => 'scheduled@example.com', 'password' => 'ValidPass1!', 'password_confirmation' => 'ValidPass1!', 'terms' => 'on', ]); $response->assertRedirect(route('login')); } private function mockRegisterControllerCreate(User|\Throwable $result): void { $mock = Mockery::mock(RegisterController::class)->makePartial(); $mock->shouldAllowMockingProtectedMethods(); $reflection = new \ReflectionProperty(RegisterController::class, 'registrationStatusService'); $reflection->setAccessible(true); $reflection->setValue($mock, app(RegistrationStatusService::class)); $expectation = $mock->shouldReceive('create')->once(); if ($result instanceof \Throwable) { $expectation->andThrow($result); } else { $expectation->andReturn($result); } $this->app->instance(RegisterController::class, $mock); } }