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
df557e419b
commit
3fd3ac002f
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\CheckForAnyAbility;
|
||||
use Sentry\Laravel\Integration;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
|
|
@ -162,4 +163,8 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||
// Validierung, Auth) gilt weiter — diese Fälle meldet
|
||||
// ReportErrorSignals gezielt als Warnung.
|
||||
Integration::handles($exceptions);
|
||||
|
||||
// Vertippte artisan-Befehle sind kein Anwendungsfehler (GlitchTip
|
||||
// server-pressekonto, Issue 9: „config:cachels“ auf Live).
|
||||
$exceptions->dontReport(CommandNotFoundException::class);
|
||||
})->create();
|
||||
|
|
|
|||
|
|
@ -87,6 +87,27 @@ function loadSdk() {
|
|||
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) {
|
||||
loadSdk()
|
||||
.then((Sentry) => Sentry.captureException(error))
|
||||
|
|
@ -102,6 +123,6 @@ if (config?.dsn) {
|
|||
});
|
||||
|
||||
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
|
||||
autocomplete="name"
|
||||
class="field-input"
|
||||
:placeholder="__('Vor- und Nachname')"
|
||||
placeholder="{{ __('Vor- und Nachname') }}"
|
||||
@error('name') aria-invalid="true" @enderror
|
||||
/>
|
||||
@error('name')
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ test('registration screen can be rendered', function () {
|
|||
/** @var TestCase $this */
|
||||
$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 () {
|
||||
|
|
|
|||
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\ExceptionDataBag;
|
||||
use Sentry\SentrySdk;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
use Tests\Support\FakeGlitchTip;
|
||||
|
||||
/**
|
||||
|
|
@ -35,6 +36,14 @@ test('exceptions laravel does not report stay out of glitchtip', function () {
|
|||
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 () {
|
||||
$scrubbed = ErrorReportScrubber::scrubRequest([
|
||||
'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