- Fortifys sieben 2FA-Verwaltungsrouten antworten mit 404 (Middleware über fortify.middleware); die Sicherheitsseite verlangt weiter den zweiten Faktor (U09) - PressReleasePolicy/CompanyPolicy::before() bejahen nur aktive Super-Admins (7.2) - API-Update prüft bei Portalwechsel die bestehende Kategorie gegen das neue Portal (U10) - Aggregation: Zeitbudget 480 s unter dem Lock von 600 s, Marke nur unter Zeilensperre vom erwarteten Stand aus, sonst Fehler-Exit (U13) - Newsletter-Bereinigungsmigration behält je Paar die sent-Zeile (U08) - E-Mail-Wechsel stößt den Rechte-Sync sofort an; company-memberships:audit --classify zeigt vorher die Kandidaten und begrenzt mit --before (7.1) - Detailseite wählt bei Slug-Kollision wie der Melde-Resolver (7.5) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
78 lines
4.4 KiB
PHP
78 lines
4.4 KiB
PHP
<?php
|
||
|
||
use App\Models\Company;
|
||
use App\Models\Contact;
|
||
use App\Models\User;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Storage;
|
||
use Tests\TestCase;
|
||
|
||
/**
|
||
* Bestandsprüfung der `responsible`-Zuordnungen (Audit F01/F10, Plan 1.4):
|
||
* Welche Verantwortlichkeiten lassen sich heute noch über eine hinterlegte
|
||
* E-Mail erklären, welche nicht – und `--classify` markiert die erklärbaren
|
||
* als automatisch vergeben, damit der Login-Sync sie später zurücknehmen kann.
|
||
*/
|
||
test('the audit lists unexplained responsible links and classifies explained ones on request', function () {
|
||
/** @var TestCase $this */
|
||
Storage::fake('local');
|
||
|
||
$viaCompany = User::factory()->create(['email' => 'firma@example.test']);
|
||
$viaContact = User::factory()->create(['email' => 'kontakt@example.test']);
|
||
$orphan = User::factory()->create(['email' => 'niemand@example.test']);
|
||
|
||
$companyA = Company::factory()->presseecho()->create(['email' => 'firma@example.test', 'name' => 'Erklärbar AG']);
|
||
$companyB = Company::factory()->presseecho()->create(['email' => 'andere@example.test', 'name' => 'Kontakt GmbH']);
|
||
Contact::factory()->for($companyB)->create(['email' => 'Kontakt@Example.test', 'portal' => $companyB->portal->value]);
|
||
$companyC = Company::factory()->presseecho()->create(['email' => 'dritte@example.test', 'name' => 'Verwaist KG']);
|
||
|
||
$viaCompany->companies()->attach($companyA->id, ['role' => 'responsible']);
|
||
$viaContact->companies()->attach($companyB->id, ['role' => 'responsible']);
|
||
$orphan->companies()->attach($companyC->id, ['role' => 'responsible']);
|
||
$orphan->companies()->attach($companyA->id, ['role' => 'owner']);
|
||
|
||
$this->artisan('company-memberships:audit', ['--since' => '2000-01-01'])
|
||
->expectsOutputToContain('Verwaist KG')
|
||
->doesntExpectOutputToContain('Erklärbar AG')
|
||
->doesntExpectOutputToContain('Kontakt GmbH')
|
||
->assertSuccessful();
|
||
|
||
// Ohne --classify bleibt alles manual.
|
||
expect(DB::table('company_user')->where('granted_via', 'auto')->count())->toBe(0);
|
||
|
||
$this->artisan('company-memberships:audit', ['--classify' => true, '--since' => '2000-01-01'])->assertSuccessful();
|
||
|
||
$via = fn (int $userId, int $companyId): string => DB::table('company_user')->where('user_id', $userId)->where('company_id', $companyId)->value('granted_via');
|
||
|
||
expect($via($viaCompany->id, $companyA->id))->toBe('auto')
|
||
->and($via($viaContact->id, $companyB->id))->toBe('auto')
|
||
->and($via($orphan->id, $companyC->id))->toBe('manual')
|
||
->and($via($orphan->id, $companyA->id))->toBe('manual');
|
||
|
||
expect(collect(Storage::disk('local')->files())->filter(fn (string $f) => str_starts_with($f, 'company-memberships-unexplained-')))->toHaveCount(1);
|
||
});
|
||
|
||
test('classify previews its candidates and skips links created after the given deploy time (Audit 7.1)', function () {
|
||
/** @var TestCase $this */
|
||
Storage::fake('local');
|
||
|
||
$legacy = User::factory()->create(['email' => 'alt@example.test']);
|
||
$recent = User::factory()->create(['email' => 'neu@example.test']);
|
||
$companyOld = Company::factory()->presseecho()->create(['email' => 'alt@example.test', 'name' => 'Altbestand AG']);
|
||
$companyNew = Company::factory()->presseecho()->create(['email' => 'neu@example.test', 'name' => 'Frisch GmbH']);
|
||
|
||
$legacy->companies()->attach($companyOld->id, ['role' => 'responsible', 'created_at' => '2026-01-10 08:00:00', 'updated_at' => '2026-01-10 08:00:00']);
|
||
// Nach dem Deploy manuell vergeben: trägt ihre Herkunft bereits, darf nicht umgestuft werden.
|
||
$recent->companies()->attach($companyNew->id, ['role' => 'responsible', 'granted_via' => 'manual', 'created_at' => '2026-09-20 09:00:00', 'updated_at' => '2026-09-20 09:00:00']);
|
||
|
||
$this->artisan('company-memberships:audit', ['--classify' => true, '--since' => '2000-01-01', '--before' => '2026-09-19 00:00:00'])
|
||
->expectsOutputToContain('Umzustufen auf auto: 1 Zuordnungen vor 2026-09-19 00:00:00')
|
||
->expectsOutputToContain('Altbestand AG')
|
||
->expectsOutputToContain('1 Zuordnungen als automatisch (auto) markiert.')
|
||
->assertSuccessful();
|
||
|
||
$via = fn (int $userId, int $companyId): string => DB::table('company_user')->where('user_id', $userId)->where('company_id', $companyId)->value('granted_via');
|
||
|
||
expect($via($legacy->id, $companyOld->id))->toBe('auto')
|
||
->and($via($recent->id, $companyNew->id))->toBe('manual');
|
||
});
|