get('https://presseecho.test/de') ->assertSuccessful() ->assertHeader('X-Content-Type-Options', 'nosniff') ->assertHeader('X-Frame-Options', 'SAMEORIGIN') ->assertHeader('Referrer-Policy', 'strict-origin-when-cross-origin') ->assertHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()'); }); test('the magic link and preview consume routes are throttled', function () { $routes = Route::getRoutes(); expect($routes->getByName('magic-links.consume')->gatherMiddleware())->toContain('throttle:10,1'); expect($routes->getByName('press-releases.preview')->gatherMiddleware())->toContain('throttle:30,1'); }); test('password reset requests are rate limited per email', function () { foreach (range(1, 3) as $i) { RateLimiter::hit('password-reset|spam@example.test', 3600); } Livewire::test('auth.forgot-password') ->set('email', 'spam@example.test') ->call('sendPasswordResetLink') ->assertHasErrors(['email']); }); test('registration is rate limited per ip', function () { foreach (range(1, 10) as $i) { RateLimiter::hit('register-ip|127.0.0.1', 3600); } Livewire::test('auth.register') ->set('name', 'Bot Bot') ->set('email', 'bot@example.test') ->set('password', 'super-secret-password') ->set('password_confirmation', 'super-secret-password') ->set('terms_accepted', true) ->call('register') ->assertHasErrors(['email']); expect(User::query()->where('email', 'bot@example.test')->exists())->toBeFalse(); }); test('company create stores the logo through the image service pipeline', function () { /** @var TestCase $this */ $this->seed(RolesAndPermissionsSeeder::class); Storage::fake('public'); $admin = User::factory()->create(['is_active' => true]); $admin->assignRole('admin'); Livewire::actingAs($admin) ->test('admin.companies.create') ->set('company_name', 'Logo GmbH') ->set('email', 'logo@example.test') ->set('portal', 'presseecho') ->set('logo', UploadedFile::fake()->image('logo.png', 400, 400)) ->call('save') ->assertHasNoErrors(); $company = Company::query()->where('name', 'Logo GmbH')->firstOrFail(); // ImageService-Pfad (UUID-Name unter company-logos/{portal}/{id}) statt // roher store()-Ablage; Varianten wurden erzeugt. expect($company->logo_path)->toStartWith('company-logos/presseecho/'.$company->id.'/'); expect($company->logo_variants)->not->toBeNull(); Storage::disk('public')->assertExists($company->logo_path); }); test('company create rejects svg logos', function () { /** @var TestCase $this */ $this->seed(RolesAndPermissionsSeeder::class); Storage::fake('public'); $admin = User::factory()->create(['is_active' => true]); $admin->assignRole('admin'); Livewire::actingAs($admin) ->test('admin.companies.create') ->set('company_name', 'SVG GmbH') ->set('portal', 'presseecho') ->set('logo', UploadedFile::fake()->createWithContent('logo.svg', '')) ->call('save') ->assertHasErrors(['logo']); expect(Company::query()->where('name', 'SVG GmbH')->exists())->toBeFalse(); }); test('https responses carry hsts only when it is enabled', function () { /** @var TestCase $this */ $this->get('https://presseecho.test/de')->assertHeaderMissing('Strict-Transport-Security'); config()->set('security.hsts', ['enabled' => true, 'max_age' => 31536000]); $this->get('https://presseecho.test/de')->assertHeader('Strict-Transport-Security', 'max-age=31536000'); $this->get('http://presseecho.test/de')->assertHeaderMissing('Strict-Transport-Security'); }); test('forwarded client ips are ignored unless the proxy is trusted', function () { /** @var TestCase $this */ Route::get('/_test/client-ip', fn () => request()->ip()); $this->withServerVariables(['REMOTE_ADDR' => '203.0.113.10']) ->withHeader('X-Forwarded-For', '198.51.100.7') ->get('/_test/client-ip') ->assertContent('203.0.113.10'); config()->set('trustedproxy.proxies', '203.0.113.10'); $this->withServerVariables(['REMOTE_ADDR' => '203.0.113.10']) ->withHeader('X-Forwarded-For', '198.51.100.7') ->get('/_test/client-ip') ->assertContent('198.51.100.7'); }); test('production trusts no proxy unless one is configured', function (string $appEnv, ?string $trustedProxies, ?string $expected) { $code = 'require "vendor/autoload.php"; echo json_encode((require "config/trustedproxy.php")["proxies"]);'; $result = Process::path(base_path()) ->env(array_filter(['APP_ENV' => $appEnv, 'TRUSTED_PROXIES' => $trustedProxies], fn ($value) => $value !== null) + ['TRUSTED_PROXIES' => false]) ->run([PHP_BINARY, '-r', $code]); expect(json_decode($result->throw()->output()))->toBe($expected); })->with([ 'live without setting' => ['production', null, null], 'local behind traefik' => ['local', null, '*'], 'live behind a cdn' => ['production', '10.0.0.0/8', '10.0.0.0/8'], 'explicitly empty' => ['local', '', null], ]); test('the test suite never sends real ntfy pushes', function () { expect(config('monitoring.alerts.ntfy.url'))->toBeEmpty() ->and(config('monitoring.alerts.ntfy.topic'))->toBeEmpty() ->and(config('monitoring.alerts.ntfy.token'))->toBeEmpty(); });