fix: Registrierung — Platzhalter serverseitig übersetzt statt als Alpine-Ausdruck (GlitchTip browser-pressekonto 15), Wächtertest; Promise-Ablehnungen mit Typ und Inhalt (10); vertippte artisan-Befehle nicht melden (server-pressekonto 9)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a90f24a43f
commit
16af3740bc
6 changed files with 88 additions and 3 deletions
|
|
@ -20,6 +20,7 @@ use Illuminate\Support\Facades\Route;
|
||||||
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
|
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
|
||||||
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
|
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
|
||||||
use Sentry\Laravel\Integration;
|
use Sentry\Laravel\Integration;
|
||||||
|
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||||
|
|
||||||
return Application::configure(basePath: dirname(__DIR__))
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
->withRouting(
|
->withRouting(
|
||||||
|
|
@ -162,4 +163,8 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||||
// Validierung, Auth) gilt weiter — diese Fälle meldet
|
// Validierung, Auth) gilt weiter — diese Fälle meldet
|
||||||
// ReportErrorSignals gezielt als Warnung.
|
// ReportErrorSignals gezielt als Warnung.
|
||||||
Integration::handles($exceptions);
|
Integration::handles($exceptions);
|
||||||
|
|
||||||
|
// Vertippte artisan-Befehle sind kein Anwendungsfehler (GlitchTip
|
||||||
|
// server-pressekonto, Issue 9: „config:cachels“ auf Live).
|
||||||
|
$exceptions->dontReport(CommandNotFoundException::class);
|
||||||
})->create();
|
})->create();
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,27 @@ function loadSdk() {
|
||||||
return sdk;
|
return sdk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promise-Ablehnungen mit Objekt statt Error kamen als „Error: [object Object]“
|
||||||
|
* an (GlitchTip browser-pressekonto, Issue 10) — Typ und Inhalt mitschicken,
|
||||||
|
* damit die Quelle erkennbar ist.
|
||||||
|
*/
|
||||||
|
function rejectionToError(reason) {
|
||||||
|
if (reason instanceof Error) {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
const kind = reason?.constructor?.name ?? typeof reason;
|
||||||
|
let text;
|
||||||
|
try {
|
||||||
|
text = typeof reason === 'string' ? reason : JSON.stringify(reason);
|
||||||
|
} catch {
|
||||||
|
text = String(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Error(`Unbehandelte Ablehnung (${kind}): ${String(text ?? '').slice(0, 300)}`);
|
||||||
|
}
|
||||||
|
|
||||||
function capture(error) {
|
function capture(error) {
|
||||||
loadSdk()
|
loadSdk()
|
||||||
.then((Sentry) => Sentry.captureException(error))
|
.then((Sentry) => Sentry.captureException(error))
|
||||||
|
|
@ -102,6 +123,6 @@ if (config?.dsn) {
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('unhandledrejection', (event) => {
|
window.addEventListener('unhandledrejection', (event) => {
|
||||||
capture(event.reason instanceof Error ? event.reason : new Error(String(event.reason)));
|
capture(rejectionToError(event.reason));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Konto erstell
|
||||||
autofocus
|
autofocus
|
||||||
autocomplete="name"
|
autocomplete="name"
|
||||||
class="field-input"
|
class="field-input"
|
||||||
:placeholder="__('Vor- und Nachname')"
|
placeholder="{{ __('Vor- und Nachname') }}"
|
||||||
@error('name') aria-invalid="true" @enderror
|
@error('name') aria-invalid="true" @enderror
|
||||||
/>
|
/>
|
||||||
@error('name')
|
@error('name')
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,11 @@ test('registration screen can be rendered', function () {
|
||||||
/** @var TestCase $this */
|
/** @var TestCase $this */
|
||||||
$response = $this->get('/register');
|
$response = $this->get('/register');
|
||||||
|
|
||||||
$response->assertStatus(200);
|
// Platzhalter serverseitig übersetzt, nicht als Alpine-Ausdruck
|
||||||
|
// (ReferenceError „__ is not defined“, GlitchTip 06.10.2026).
|
||||||
|
$response->assertStatus(200)
|
||||||
|
->assertSee('placeholder="Vor- und Nachname"', false)
|
||||||
|
->assertDontSee(':placeholder="__(', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('new users register inactive and are sent to the verification notice', function () {
|
test('new users register inactive and are sent to the verification notice', function () {
|
||||||
|
|
|
||||||
46
tests/Feature/BladeAlpineTranslationGuardTest.php
Normal file
46
tests/Feature/BladeAlpineTranslationGuardTest.php
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `:attr="__('…')"` ist nur an Blade-Komponenten (`<x-…>`, `<flux:…>`)
|
||||||
|
* ein PHP-Ausdruck. An einem normalen HTML-Element reicht Blade das
|
||||||
|
* Attribut unverändert an Alpine durch, und der Browser wirft
|
||||||
|
* „ReferenceError: __ is not defined“ — so geschehen auf /register
|
||||||
|
* (GlitchTip browser-pressekonto, Issue 15, 06.10.2026). Für HTML-Elemente
|
||||||
|
* gilt: `attr="{{ __('…') }}"`.
|
||||||
|
*/
|
||||||
|
function alpineTranslationBindingPattern(): string
|
||||||
|
{
|
||||||
|
// HTML-Tag (keine Komponente), beliebige Attribute auch über mehrere
|
||||||
|
// Zeilen, dann ein Alpine-Binding, dessen Ausdruck mit __( beginnt.
|
||||||
|
return '/<(?!x-|flux:|livewire:|\/)([a-z][a-z0-9-]*)\b((?:[^>"]|"[^"]*")*?)\s(:[a-zA-Z.-]+|x-bind:[a-zA-Z.-]+)="\s*__\(/s';
|
||||||
|
}
|
||||||
|
|
||||||
|
test('no html element binds an alpine attribute to the php translation helper', function () {
|
||||||
|
$pattern = alpineTranslationBindingPattern();
|
||||||
|
$offenders = [];
|
||||||
|
|
||||||
|
foreach (File::allFiles(resource_path('views')) as $view) {
|
||||||
|
if (! str_ends_with($view->getFilename(), '.blade.php')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = File::get($view->getRealPath());
|
||||||
|
|
||||||
|
preg_match_all($pattern, $contents, $matches, PREG_OFFSET_CAPTURE);
|
||||||
|
|
||||||
|
foreach ($matches[3] as [$attribute, $offset]) {
|
||||||
|
$offenders[] = sprintf('%s:%d %s', $view->getRelativePathname(), substr_count($contents, "\n", 0, $offset) + 1, $attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect($offenders)->toBe([], "Alpine-Binding mit __() an einem HTML-Element — auf attr=\"{{ __('…') }}\" umstellen:\n".implode("\n", $offenders));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the guard catches the pattern that broke the registration', function () {
|
||||||
|
$pattern = alpineTranslationBindingPattern();
|
||||||
|
|
||||||
|
expect(preg_match($pattern, "<input\n wire:model=\"name\"\n :placeholder=\"__('Vor- und Nachname')\"\n/>"))->toBe(1)
|
||||||
|
->and(preg_match($pattern, "<x-web.page-header\n :title=\"__('static.faq.title')\"\n/>"))->toBe(0);
|
||||||
|
});
|
||||||
|
|
@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Route;
|
||||||
use Sentry\Event;
|
use Sentry\Event;
|
||||||
use Sentry\ExceptionDataBag;
|
use Sentry\ExceptionDataBag;
|
||||||
use Sentry\SentrySdk;
|
use Sentry\SentrySdk;
|
||||||
|
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||||
use Tests\Support\FakeGlitchTip;
|
use Tests\Support\FakeGlitchTip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -35,6 +36,14 @@ test('exceptions laravel does not report stay out of glitchtip', function () {
|
||||||
expect($glitchTip->events)->toBe([]);
|
expect($glitchTip->events)->toBe([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a mistyped artisan command is not reported', function () {
|
||||||
|
$glitchTip = FakeGlitchTip::install();
|
||||||
|
|
||||||
|
report(new CommandNotFoundException('Command "config:cachels" is not defined.'));
|
||||||
|
|
||||||
|
expect($glitchTip->events)->toBe([]);
|
||||||
|
});
|
||||||
|
|
||||||
test('request data loses tokens, signatures and bodies', function () {
|
test('request data loses tokens, signatures and bodies', function () {
|
||||||
$scrubbed = ErrorReportScrubber::scrubRequest([
|
$scrubbed = ErrorReportScrubber::scrubRequest([
|
||||||
'url' => 'https://pressekonto.test/newsroom/claim/5?signature=abc123&expires=1700000000&src=mail_publish',
|
'url' => 'https://pressekonto.test/newsroom/claim/5?signature=abc123&expires=1700000000&src=mail_publish',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue