F10: company_user.granted_via unterscheidet automatisch vergebene Rechte (E-Mail-Sync) von manuellen und aus dem Altbestand. Beim Login senkt der Sync automatische responsible-Rechte ohne aktuellen E-Mail- Treffer auf member (E2) — Mitarbeiterwechsel nehmen den Schreibzugriff mit, Lesezugriff bleibt. Das Admin-Formular markiert nur neue oder geänderte Zeilen als manual, unveränderte behalten ihre Herkunft. Bestandsprüfung: php artisan company-memberships:audit listet responsible-Zuordnungen ohne E-Mail-Grundlage (Tabelle ab Go-Live, CSV vollständig) und markiert mit --classify die erklärbaren als auto. Auf Live nach der Migration einmal mit --classify laufen lassen; nicht erklärbare Zeilen bleiben manual und damit geschützt. F03 (E3): Zwei-Faktor deaktivieren, Wiederherstellungs-Codes erneuern und die E-Mail-Änderung verlangen einen aktuellen App-Code oder einen Wiederherstellungs-Code (der dabei verbraucht wird); ohne 2FA das Passwort. Reine Magic-Link-Konten ohne Passwort (der Altbestand) können die Adresse weiter ändern — die neue Adresse muss ihren Bestätigungslink ohnehin einlösen. Fünf Fehlversuche pro Minute. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
53 lines
2.7 KiB
PHP
53 lines
2.7 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);
|
||
});
|