-
diff --git a/resources/views/layouts/_partials/_seo.blade.php b/resources/views/layouts/_partials/_seo.blade.php
index a72bcd6..b827883 100644
--- a/resources/views/layouts/_partials/_seo.blade.php
+++ b/resources/views/layouts/_partials/_seo.blade.php
@@ -8,6 +8,9 @@
{{--
--}}
@if(!empty($page))
+ @php
+ $seoImage = $page->image ?: url(asset(config('seo.default_image')));
+ @endphp
{{ $page->title }}
@@ -15,22 +18,19 @@
-
+
-
+
+
+
-
-
-
+
-
+
@endif
-
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php
index 737aa28..556538a 100644
--- a/resources/views/layouts/app.blade.php
+++ b/resources/views/layouts/app.blade.php
@@ -9,7 +9,9 @@ class="scroll-smooth"
-
+ @if (! empty($preconnectCloudinary))
+
+ @endif
diff --git a/tests/Feature/Http/SecurityHeadersTest.php b/tests/Feature/Http/SecurityHeadersTest.php
new file mode 100644
index 0000000..da6ed31
--- /dev/null
+++ b/tests/Feature/Http/SecurityHeadersTest.php
@@ -0,0 +1,37 @@
+ true]);
+
+ $response = get(route(Str::slug(LocaleEnum::DE->value).'.start.index'));
+
+ $response->assertOk();
+ $response->assertHeader('Cross-Origin-Opener-Policy', 'same-origin');
+ $response->assertHeader('X-Content-Type-Options', 'nosniff');
+ $response->assertHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
+ $response->assertHeader('X-Frame-Options', 'DENY');
+})->group('security');
+
+it('adds content security policy when enabled', function () {
+ config(['csp.enabled' => true]);
+
+ $response = get(route(Str::slug(LocaleEnum::DE->value).'.start.index'));
+
+ $response->assertOk();
+ expect($response->headers->get('Content-Security-Policy'))->toContain("frame-ancestors 'self'");
+})->group('security');
+
+it('adds strict transport security on secure requests', function () {
+ $response = get(route(Str::slug(LocaleEnum::DE->value).'.start.index'), [
+ 'HTTPS' => 'on',
+ 'SERVER_PORT' => 443,
+ ]);
+
+ $response->assertOk();
+ $response->assertHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
+})->group('security');
diff --git a/tests/Unit/Support/CloudinaryUrlTest.php b/tests/Unit/Support/CloudinaryUrlTest.php
new file mode 100644
index 0000000..5498224
--- /dev/null
+++ b/tests/Unit/Support/CloudinaryUrlTest.php
@@ -0,0 +1,34 @@
+toBe(
+ 'https://res.cloudinary.com/codebar/image/upload/w_256,h_256,c_fill,f_auto,q_auto/team/Alex_v3.webp'
+ );
+});
+
+it('replaces existing cloudinary transforms', function () {
+ $url = 'https://res.cloudinary.com/codebar/image/upload/w_400,h_400,f_auto,q_auto/www-paperflakes-ch/people/alexander_boll.webp';
+
+ expect(CloudinaryUrl::src($url, 256))->toBe(
+ 'https://res.cloudinary.com/codebar/image/upload/w_256,h_256,c_fill,f_auto,q_auto/www-paperflakes-ch/people/alexander_boll.webp'
+ );
+});
+
+it('returns non-cloudinary urls unchanged', function () {
+ $url = 'https://www.codebar.ch/images/team/alex.webp';
+
+ expect(CloudinaryUrl::src($url, 256))->toBe($url);
+});
+
+it('builds a responsive srcset', function () {
+ $url = 'https://res.cloudinary.com/codebar/image/upload/team/Alex_v3.webp';
+
+ expect(CloudinaryUrl::srcset($url, 256))->toBe(
+ 'https://res.cloudinary.com/codebar/image/upload/w_256,h_256,c_fill,f_auto,q_auto/team/Alex_v3.webp 256w, '.
+ 'https://res.cloudinary.com/codebar/image/upload/w_512,h_512,c_fill,f_auto,q_auto/team/Alex_v3.webp 512w'
+ );
+});