Gate testing API for staging E2E
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-03 15:00:33 +01:00
parent 030a00ba46
commit 7ea34b3b20
17 changed files with 94 additions and 22 deletions

View File

@@ -13,7 +13,7 @@ class TestCheckoutController extends Controller
{ {
public function latest(Request $request): JsonResponse public function latest(Request $request): JsonResponse
{ {
abort_unless(app()->environment(['local', 'testing']), 404); abort_unless(config('e2e.testing_enabled'), 404);
$validated = $request->validate([ $validated = $request->validate([
'email' => ['nullable', 'string', 'email'], 'email' => ['nullable', 'string', 'email'],
@@ -66,7 +66,7 @@ class TestCheckoutController extends Controller
CheckoutWebhookService $webhooks, CheckoutWebhookService $webhooks,
CheckoutSession $session CheckoutSession $session
): JsonResponse { ): JsonResponse {
abort_unless(app()->environment(['local', 'testing']), 404); abort_unless(config('e2e.testing_enabled'), 404);
$validated = $request->validate([ $validated = $request->validate([
'event_type' => ['nullable', 'string'], 'event_type' => ['nullable', 'string'],

View File

@@ -16,7 +16,7 @@ class TestCouponController extends Controller
{ {
public function store(Request $request): JsonResponse public function store(Request $request): JsonResponse
{ {
abort_unless(app()->environment(['local', 'testing']), 404); abort_unless(config('e2e.testing_enabled'), 404);
$payload = $request->input('coupons'); $payload = $request->input('coupons');
$definitions = collect(is_array($payload) ? $payload : []) $definitions = collect(is_array($payload) ? $payload : [])

View File

@@ -15,7 +15,7 @@ class TestEventController extends Controller
{ {
public function joinToken(Request $request, EventJoinTokenService $tokens): JsonResponse public function joinToken(Request $request, EventJoinTokenService $tokens): JsonResponse
{ {
abort_unless(app()->environment(['local', 'testing']), 404); abort_unless(config('e2e.testing_enabled'), 404);
$validated = $request->validate([ $validated = $request->validate([
'event_id' => ['nullable', 'integer'], 'event_id' => ['nullable', 'integer'],

View File

@@ -20,7 +20,7 @@ class TestGuestEventController extends Controller
{ {
public function store(Request $request, EventJoinTokenService $joinTokens): JsonResponse public function store(Request $request, EventJoinTokenService $joinTokens): JsonResponse
{ {
abort_unless(app()->environment(['local', 'testing']), 404); abort_unless(config('e2e.testing_enabled'), 404);
$validated = $request->validate([ $validated = $request->validate([
'slug' => ['nullable', 'string', 'max:100'], 'slug' => ['nullable', 'string', 'max:100'],

View File

@@ -10,7 +10,7 @@ class TestMailboxController extends Controller
{ {
public function index(): JsonResponse public function index(): JsonResponse
{ {
abort_unless(app()->environment(['local', 'testing']), 404); abort_unless(config('e2e.testing_enabled'), 404);
return response()->json([ return response()->json([
'data' => Mailbox::all(), 'data' => Mailbox::all(),
@@ -19,7 +19,7 @@ class TestMailboxController extends Controller
public function destroy(): JsonResponse public function destroy(): JsonResponse
{ {
abort_unless(app()->environment(['local', 'testing']), 404); abort_unless(config('e2e.testing_enabled'), 404);
Mailbox::flush(); Mailbox::flush();

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureE2ETestingAccess
{
public function handle(Request $request, Closure $next): Response
{
if (! config('e2e.testing_enabled')) {
abort(404);
}
$token = config('e2e.testing_token');
if ($token && $request->header('X-Testing-Token') !== $token) {
abort(404);
}
return $next($request);
}
}

View File

@@ -141,7 +141,7 @@ class AppServiceProvider extends ServiceProvider
[DispatchGuestNotificationPush::class, 'handle'] [DispatchGuestNotificationPush::class, 'handle']
); );
if ($this->app->environment(['local', 'testing'])) { if (config('e2e.testing_enabled')) {
EventFacade::listen( EventFacade::listen(
MessageSent::class, MessageSent::class,
[Mailbox::class, 'record'] [Mailbox::class, 'record']

View File

@@ -14,7 +14,7 @@ class Mailbox
public static function record(MessageSent $event): void public static function record(MessageSent $event): void
{ {
if (! app()->environment(['local', 'testing'])) { if (! config('e2e.testing_enabled')) {
return; return;
} }

6
config/e2e.php Normal file
View File

@@ -0,0 +1,6 @@
<?php
return [
'testing_enabled' => env('E2E_TESTING_ENABLED', in_array(env('APP_ENV'), ['local', 'testing'], true)),
'testing_token' => env('E2E_TESTING_TOKEN'),
];

View File

@@ -10,6 +10,9 @@ export default defineConfig({
reporter: 'html', reporter: 'html',
use: { use: {
baseURL: process.env.E2E_BASE_URL ?? 'https://test-y0k0.fotospiel.app', baseURL: process.env.E2E_BASE_URL ?? 'https://test-y0k0.fotospiel.app',
extraHTTPHeaders: process.env.E2E_TESTING_TOKEN
? { 'X-Testing-Token': process.env.E2E_TESTING_TOKEN }
: undefined,
trace: 'on-first-retry', trace: 'on-first-retry',
headless: true, headless: true,
screenshot: 'only-on-failure', screenshot: 'only-on-failure',

View File

@@ -353,6 +353,6 @@ Route::prefix('v1')->name('api.v1.')->group(function () {
}); });
if (app()->environment(['local', 'testing'])) { if (config('e2e.testing_enabled')) {
require __DIR__.'/testing.php'; require __DIR__.'/testing.php';
} }

View File

@@ -2,12 +2,13 @@
use App\Http\Controllers\Testing\TestCheckoutController; use App\Http\Controllers\Testing\TestCheckoutController;
use App\Http\Controllers\Testing\TestCouponController; use App\Http\Controllers\Testing\TestCouponController;
use App\Http\Controllers\Testing\TestGuestEventController;
use App\Http\Controllers\Testing\TestEventController; use App\Http\Controllers\Testing\TestEventController;
use App\Http\Controllers\Testing\TestGuestEventController;
use App\Http\Controllers\Testing\TestMailboxController; use App\Http\Controllers\Testing\TestMailboxController;
use App\Http\Middleware\EnsureE2ETestingAccess;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::prefix('_testing')->group(function () { Route::prefix('_testing')->middleware(EnsureE2ETestingAccess::class)->group(function () {
Route::get('/mailbox', [TestMailboxController::class, 'index'])->name('testing.mailbox.index'); Route::get('/mailbox', [TestMailboxController::class, 'index'])->name('testing.mailbox.index');
Route::delete('/mailbox', [TestMailboxController::class, 'destroy'])->name('testing.mailbox.destroy'); Route::delete('/mailbox', [TestMailboxController::class, 'destroy'])->name('testing.mailbox.destroy');

View File

@@ -0,0 +1,36 @@
<?php
namespace Tests\Feature;
use Tests\TestCase;
class TestingApiAccessTest extends TestCase
{
public function test_testing_routes_are_blocked_when_disabled(): void
{
config([
'e2e.testing_enabled' => false,
'e2e.testing_token' => 'secret-token',
]);
$this->withHeader('X-Testing-Token', 'secret-token')
->getJson('/api/_testing/mailbox')
->assertNotFound();
}
public function test_testing_routes_require_token_when_enabled(): void
{
config([
'e2e.testing_enabled' => true,
'e2e.testing_token' => 'secret-token',
]);
$this->getJson('/api/_testing/mailbox')
->assertNotFound();
$this->withHeader('X-Testing-Token', 'secret-token')
->getJson('/api/_testing/mailbox')
->assertOk()
->assertJsonStructure(['data']);
}
}

View File

@@ -17,8 +17,8 @@ test.describe('Marketing auth flows', () => {
await page.getByLabel(/Telefon/i).fill('+49123456789'); await page.getByLabel(/Telefon/i).fill('+49123456789');
await page.getByLabel(/Adresse/i).fill('Teststr. 1, 12345 Berlin'); await page.getByLabel(/Adresse/i).fill('Teststr. 1, 12345 Berlin');
await page.getByLabel(/Username/i).fill(username); await page.getByLabel(/Username/i).fill(username);
await page.getByLabel(/^Passwort$/i).fill(password); await page.fill('input[name="password"]', password);
await page.getByLabel(/Passwort bestätigen/i).fill(password); await page.fill('input[name="password_confirmation"]', password);
await page.locator('#privacy_consent').check(); await page.locator('#privacy_consent').check();
await page.getByRole('button', { name: /^Registrieren$/i }).click(); await page.getByRole('button', { name: /^Registrieren$/i }).click();

View File

@@ -6,8 +6,8 @@ test.describe('Marketing hero CTA smoke', () => {
await page.goto('/'); await page.goto('/');
const cta = page.getByRole('link', { const cta = page.locator('#hero').getByRole('link', {
name: /Pakete entdecken|Jetzt loslegen|Discover Packages|Get started now/i, name: /Pakete ansehen|Pakete entdecken|Discover Packages/i,
}); });
await expect(cta).toBeVisible(); await expect(cta).toBeVisible();
@@ -22,12 +22,12 @@ test.describe('Marketing hero CTA smoke', () => {
await page.goto('/packages'); await page.goto('/packages');
const cta = page.getByRole('link', { const cta = page.getByRole('link', {
name: /Pakete entdecken|Lieblingspaket sichern|Discover Packages|Explore top packages/i, name: /Pakete entdecken|Discover Packages/i,
}); });
await expect(cta).toBeVisible(); await expect(cta).toBeVisible();
await cta.click(); await cta.click();
await expect(page.locator('#endcustomer')).toBeVisible(); await expect(page.locator('#packages-showcase')).toBeVisible();
}); });
}); });

View File

@@ -18,7 +18,9 @@ test.describe('Marketing frontend smoke', () => {
} }
await expect(page.getByRole('heading', { level: 1, name: /Dein Event|Fotospiel/i })).toBeVisible(); await expect(page.getByRole('heading', { level: 1, name: /Dein Event|Fotospiel/i })).toBeVisible();
const heroCta = page.getByRole('link', { name: /paket|packages|starten|ausprobieren/i }).first(); const heroCta = page.locator('#hero').getByRole('link', {
name: /Pakete ansehen|Pakete entdecken|Discover Packages/i,
});
await expect(heroCta).toBeVisible(); await expect(heroCta).toBeVisible();
await heroCta.click(); await heroCta.click();

View File

@@ -66,9 +66,9 @@ test.describe('Standard package checkout with Paddle completion', () => {
await page.goto('/de/packages'); await page.goto('/de/packages');
const standardDetailsButton = page const standardDetailsButton = page
.getByRole('heading', { name: /^Standard$/ }) .locator('[data-slot="card"]')
.locator('..') .filter({ hasText: /Standard/i })
.getByRole('button', { name: /Details/i }) .getByRole('button', { name: /Details ansehen|Details anzeigen|View details/i })
.first(); .first();
await expect(standardDetailsButton).toBeVisible(); await expect(standardDetailsButton).toBeVisible();