Die Uebersicht /admin/me/press-releases war auch fuer leere Konten ~2s: der OR-Filter in scopeAccessibleBy zwang die Paginator-count(*) zum Full-Scan (EXPLAIN type=ALL, ~174k Zeilen) und den Zeilen-Fetch bei 0 Treffern zum vollen created_at-Index-Scan (~1s). Neue PressRelease::accessiblePageFor() nutzt dieselbe index-freundliche UNION wie accessibleStatusCountsFor(): schmale UNION liefert die Seiten-IDs ueber user_id-/company_id-Index, danach nur 25 Modelle hydratisiert; Total via schnellem UNION-count. Messung: leer ~2000ms -> 167ms, groesstes Konto (20.830 PMs) ~740ms -> 468ms. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79 lines
3 KiB
PHP
79 lines
3 KiB
PHP
<?php
|
||
|
||
use App\Enums\PressReleaseStatus;
|
||
use App\Models\Company;
|
||
use App\Models\PressRelease;
|
||
use App\Models\User;
|
||
|
||
test('includes authored and company-linked releases and dedupes overlap', function () {
|
||
$user = User::factory()->create();
|
||
$company = Company::factory()->create();
|
||
$user->companies()->attach($company->id, ['role' => 'member']);
|
||
|
||
$authored = PressRelease::factory()->create(['user_id' => $user->id, 'company_id' => null]);
|
||
$viaCompany = PressRelease::factory()->create(['company_id' => $company->id]);
|
||
// erfüllt beide Zweige – darf nur einmal gezählt werden
|
||
$both = PressRelease::factory()->create(['user_id' => $user->id, 'company_id' => $company->id]);
|
||
|
||
$page = PressRelease::accessiblePageFor($user);
|
||
|
||
expect($page->total())->toBe(3)
|
||
->and($page->pluck('id')->sort()->values()->all())
|
||
->toBe(collect([$authored->id, $viaCompany->id, $both->id])->sort()->values()->all());
|
||
});
|
||
|
||
test('excludes releases the user cannot access', function () {
|
||
$user = User::factory()->create();
|
||
$stranger = PressRelease::factory()->create();
|
||
|
||
$page = PressRelease::accessiblePageFor($user);
|
||
|
||
expect($page->total())->toBe(0)
|
||
->and($page->contains($stranger))->toBeFalse();
|
||
});
|
||
|
||
test('applies the constrain closure to both union branches', function () {
|
||
$user = User::factory()->create();
|
||
PressRelease::factory()->create(['user_id' => $user->id, 'status' => PressReleaseStatus::Published->value]);
|
||
PressRelease::factory()->create(['user_id' => $user->id, 'status' => PressReleaseStatus::Draft->value]);
|
||
|
||
$page = PressRelease::accessiblePageFor(
|
||
$user,
|
||
fn ($query) => $query->where('status', PressReleaseStatus::Published->value),
|
||
);
|
||
|
||
expect($page->total())->toBe(1)
|
||
->and($page->first()->status)->toBe(PressReleaseStatus::Published);
|
||
});
|
||
|
||
test('respects sort column and direction', function () {
|
||
$user = User::factory()->create();
|
||
$a = PressRelease::factory()->create(['user_id' => $user->id, 'title' => 'Alpha']);
|
||
$z = PressRelease::factory()->create(['user_id' => $user->id, 'title' => 'Zulu']);
|
||
|
||
$asc = PressRelease::accessiblePageFor($user, null, 'title', 'asc');
|
||
$desc = PressRelease::accessiblePageFor($user, null, 'title', 'desc');
|
||
|
||
expect($asc->first()->id)->toBe($a->id)
|
||
->and($desc->first()->id)->toBe($z->id);
|
||
});
|
||
|
||
test('paginates with the given page size', function () {
|
||
$user = User::factory()->create();
|
||
PressRelease::factory()->count(7)->create(['user_id' => $user->id]);
|
||
|
||
$page = PressRelease::accessiblePageFor($user, null, 'created_at', 'desc', 5);
|
||
|
||
expect($page->total())->toBe(7)
|
||
->and($page->perPage())->toBe(5)
|
||
->and($page->count())->toBe(5);
|
||
});
|
||
|
||
test('falls back to created_at for an invalid sort column', function () {
|
||
$user = User::factory()->create();
|
||
PressRelease::factory()->create(['user_id' => $user->id]);
|
||
|
||
$page = PressRelease::accessiblePageFor($user, null, 'title; drop table', 'sideways');
|
||
|
||
expect($page->total())->toBe(1);
|
||
});
|