From 3dbc3fa6735d40c08d11f1cd6b383f20564e006f Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Fri, 2 Oct 2026 13:09:00 +0000 Subject: [PATCH] tests: gemeinsamer Testhelfer, DB_URL-Guard vor RefreshDatabase, Larastan voller Scope ohne Meldung (Umsetzungsaudit 29.09.: Q-01, Q-02; 02.10.: O02) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - makeCustomerForShowPhase8a() nach tests/Pest.php — die Suite läuft damit auch parallel (--parallel) und dateiweise grün - phpunit.xml leert DB_URL, der Guard in TestCase::setUpTraits() bricht bei gesetzter url ab, bevor RefreshDatabase eine Verbindung öffnet - HasUniqueSlug ohne widersprüchliche @var-Annotation Co-Authored-By: Claude Opus 5.5 --- app/Models/Concerns/HasUniqueSlug.php | 3 +- phpunit.xml | 3 ++ tests/Feature/PressReleaseShowPhase8aTest.php | 32 --------------- tests/Feature/TestDatabaseGuardTest.php | 21 ++++++++++ tests/Pest.php | 39 +++++++++++++++++++ tests/TestCase.php | 10 +++++ 6 files changed, 74 insertions(+), 34 deletions(-) diff --git a/app/Models/Concerns/HasUniqueSlug.php b/app/Models/Concerns/HasUniqueSlug.php index c813021c..066d7462 100644 --- a/app/Models/Concerns/HasUniqueSlug.php +++ b/app/Models/Concerns/HasUniqueSlug.php @@ -86,10 +86,9 @@ trait HasUniqueSlug */ protected function slugExists(string $slug, array $scope): bool { - /** @var Model $self */ $self = $this; - $query = $self::query() + $query = static::query() ->withoutGlobalScopes() ->where($this->slugColumn(), $slug); diff --git a/phpunit.xml b/phpunit.xml index 648c4405..1ca5f122 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -31,6 +31,9 @@ + + + diff --git a/tests/Feature/PressReleaseShowPhase8aTest.php b/tests/Feature/PressReleaseShowPhase8aTest.php index f602dfb3..07c610fd 100644 --- a/tests/Feature/PressReleaseShowPhase8aTest.php +++ b/tests/Feature/PressReleaseShowPhase8aTest.php @@ -4,9 +4,6 @@ use App\Enums\Portal; use App\Enums\PressReleaseClassification; use App\Enums\PressReleaseContentTier; use App\Enums\PressReleaseStatus; -use App\Models\Category; -use App\Models\Company; -use App\Models\Contact; use App\Models\PressRelease; use App\Models\User; use Database\Seeders\RolesAndPermissionsSeeder; @@ -18,35 +15,6 @@ beforeEach(function (): void { $this->seed(RolesAndPermissionsSeeder::class); }); -/** - * @return array{customer: User, company: Company, contact: Contact, category: Category, pr: PressRelease} - */ -function makeCustomerForShowPhase8a(array $prAttributes = []): array -{ - $customer = User::factory()->create(['is_active' => true]); - $customer->assignRole('customer'); - - $company = Company::factory()->presseecho()->create(); - $customer->companies()->attach($company->id, ['role' => 'owner']); - - $contact = Contact::factory()->for($company)->create([ - 'portal' => $company->portal->value, - ]); - $category = Category::factory()->create(); - - $pr = PressRelease::factory()->create(array_merge([ - 'user_id' => $customer->id, - 'company_id' => $company->id, - 'category_id' => $category->id, - 'portal' => $company->portal->value, - 'status' => 'draft', - ], $prAttributes)); - - $pr->contacts()->sync([$contact->id]); - - return compact('customer', 'company', 'contact', 'category', 'pr'); -} - function makeAdminForShowPhase8a(): User { $admin = User::factory()->create(['is_active' => true]); diff --git a/tests/Feature/TestDatabaseGuardTest.php b/tests/Feature/TestDatabaseGuardTest.php index e2403171..c4d0a1d9 100644 --- a/tests/Feature/TestDatabaseGuardTest.php +++ b/tests/Feature/TestDatabaseGuardTest.php @@ -33,3 +33,24 @@ test('the database guard rejects anything but sqlite in memory', function () { config()->set('database.default', 'sqlite'); } }); + +test('the database guard rejects a url override on the sqlite connection', function () { + /** @var TestCase $this */ + $guard = fn () => $this->assertTestDatabaseIsInMemorySqlite(); + + // Laravel löst `url` erst beim Verbindungsaufbau auf und überschreibt + // damit Treiber und Datenbank — die rohen sqlite/:memory:-Werte allein + // beweisen nichts (Umsetzungsaudit 02.10.2026, O02). Nur Config, keine + // Verbindung: der Guard muss vorher abbrechen. + try { + config()->set('database.connections.sqlite.url', 'mysql://root:secret@127.0.0.1:3306/pressekonto'); + + expect($guard)->toThrow(RuntimeException::class, 'DB_URL'); + } finally { + config()->set('database.connections.sqlite.url', null); + } +}); + +test('phpunit neutralises DB_URL for the whole run', function () { + expect(config('database.connections.sqlite.url'))->toBeEmpty(); +}); diff --git a/tests/Pest.php b/tests/Pest.php index 14ffefae..e0a4569a 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,6 +1,10 @@ $prAttributes + * @return array{customer: User, company: Company, contact: Contact, category: Category, pr: PressRelease} + */ +function makeCustomerForShowPhase8a(array $prAttributes = []): array +{ + $customer = User::factory()->create(['is_active' => true]); + $customer->assignRole('customer'); + + $company = Company::factory()->presseecho()->create(); + $customer->companies()->attach($company->id, ['role' => 'owner']); + + $contact = Contact::factory()->for($company)->create([ + 'portal' => $company->portal->value, + ]); + $category = Category::factory()->create(); + + $pr = PressRelease::factory()->create(array_merge([ + 'user_id' => $customer->id, + 'company_id' => $company->id, + 'category_id' => $category->id, + 'portal' => $company->portal->value, + 'status' => 'draft', + ], $prAttributes)); + + $pr->contacts()->sync([$contact->id]); + + return compact('customer', 'company', 'contact', 'category', 'pr'); +} diff --git a/tests/TestCase.php b/tests/TestCase.php index dca76039..42bd3dd0 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -44,6 +44,16 @@ abstract class TestCase extends BaseTestCase $default = (string) DB::getDefaultConnection(); $connection = (array) config('database.connections.'.$default, []); + // Eine gesetzte `url` überschreibt Treiber und Datenbank erst beim + // Verbindungsaufbau — dann wären die geprüften Rohwerte wertlos + // (Umsetzungsaudit 02.10.2026, O02). + if (! empty($connection['url'])) { + throw new RuntimeException(sprintf( + 'Tests laufen mit DB_URL-Override auf „%s“ — die Verbindung würde nicht SQLite :memory: sein. DB_URL für Tests leeren.', + $default, + )); + } + if ($default !== 'sqlite' || ($connection['driver'] ?? null) !== 'sqlite' || ($connection['database'] ?? null) !== ':memory:') { throw new RuntimeException(sprintf( 'Tests laufen gegen „%s“ (%s) statt SQLite :memory: — vermutlich liegt ein Config-Cache. `php artisan config:clear` und erneut starten.',