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.',