Bilder (N01, N05, N07, N08): gemeinsamer Freigabezustand PressReleaseImage::isPubliclyDeliverable() für Seiten, OG und Medienroute (Bestand bleibt sichtbar, v2 nur mit Bestätigung, Admin-Upload bis zum Kundenklick nicht öffentlich); eigene Ability confirmImageRights nur für den Kontoinhaber, ohne Super-Admin-Kurzschluss, Identität und ausstehende Bestätigung auch im Service; Status-Regel unter der PM-Sperre; Cache-Flush nach dem Commit außerhalb der Dateibereinigung; Snapshot nach Bestätigung an veröffentlichter PM; Bestätigungstexte an die gezeigte Kennung gebunden. Beweisarchiv (N02, N10, N11, N12, N13, 5.1): Tombstone unter Zeilensperre mit erneuter Hold-/Fristprüfung; Dateioperationen ausgewertet, Commands mit FAILURE; record() über .tmp + move; Marker archive_pending_at mit archive:retry-pending und archive:sweep-orphans; Anonymisierung entfernt Bilder über den Remover mit Antragsbezug; Draft-Purge räumt Medien nie veröffentlichter abgelehnter Einreichungen; App-Kopien fallen mit dem Archiv-Tombstone; label_corrected nur für veröffentlichte Bilder; archive_entries.legal_request_id; FileDeletion-Helfer; Systemstatus-Checks. KI und Hintergrund (N03, N04, N14, N20, N21): Ergebnis-UPDATE und Audit in einer Transaktion; erwarteter Hash bis in reject()/holdForManualReview(); Ablaufmail per beanspruchtem Marker in einer Transaktion; strikte Schemaprüfung der Vision-Antwort; Fortschrittsmarke ungecacht. Berichte und Canonical (N15–N19, 5.2): lückenlose Alterskohorten; Feed-Identität mit Portal und Sprache; Sitemap-Rolle in der Kundenansicht; Indexverteilung auf einer Grundgesamtheit; Kopien nach Wegfall der Erstquelle neu bewertet; Kategorie-Admin leert Seiten-Caches; Dry-Run zählt alle Tabellen. Gesamtsuite: 2.122 Tests grün, einer übersprungen; Larastan und Pint sauber. Zwei Migrationen (nullable Spalten, INSTANT). N09 offen. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
78 lines
2.2 KiB
PHP
78 lines
2.2 KiB
PHP
<?php
|
|
|
|
namespace Tests\Support;
|
|
|
|
use Illuminate\Filesystem\FilesystemAdapter;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
/**
|
|
* Storage-Fake mit gezielt eingebautem Fehlverhalten für einzelne
|
|
* Methoden (put, delete, move, exists). Die Fake-Disk bleibt das Backend;
|
|
* die Hooks bekommen den Pfad und einen Callback auf das echte Verhalten.
|
|
*
|
|
* Nutzung: FailingDisk::install('archive', ['delete' => fn () => false]);
|
|
* Zurücksetzen: FailingDisk::restore('archive').
|
|
*/
|
|
class FailingDisk extends FilesystemAdapter
|
|
{
|
|
/** @var array<string, FilesystemAdapter> */
|
|
private static array $originals = [];
|
|
|
|
/**
|
|
* @param array<string, callable> $hooks
|
|
*/
|
|
public function __construct(FilesystemAdapter $inner, private readonly array $hooks)
|
|
{
|
|
parent::__construct($inner->getDriver(), $inner->getAdapter(), $inner->getConfig());
|
|
}
|
|
|
|
/**
|
|
* @param array<string, callable> $hooks
|
|
*/
|
|
public static function install(string $disk, array $hooks): FilesystemAdapter
|
|
{
|
|
/** @var FilesystemAdapter $original */
|
|
$original = self::$originals[$disk] ?? Storage::disk($disk);
|
|
self::$originals[$disk] = $original;
|
|
Storage::set($disk, new self($original, $hooks));
|
|
|
|
return $original;
|
|
}
|
|
|
|
public static function restore(string $disk): void
|
|
{
|
|
if (isset(self::$originals[$disk])) {
|
|
Storage::set($disk, self::$originals[$disk]);
|
|
unset(self::$originals[$disk]);
|
|
}
|
|
}
|
|
|
|
public function exists($path)
|
|
{
|
|
return $this->hook('exists', $path, fn () => parent::exists($path));
|
|
}
|
|
|
|
public function put($path, $contents, $options = [])
|
|
{
|
|
return $this->hook('put', $path, fn () => parent::put($path, $contents, $options));
|
|
}
|
|
|
|
public function delete($paths)
|
|
{
|
|
return $this->hook('delete', $paths, fn () => parent::delete($paths));
|
|
}
|
|
|
|
public function move($from, $to)
|
|
{
|
|
return $this->hook('move', $from, fn () => parent::move($from, $to));
|
|
}
|
|
|
|
private function hook(string $method, mixed $path, callable $real): mixed
|
|
{
|
|
if (isset($this->hooks[$method])) {
|
|
return ($this->hooks[$method])($path, $real);
|
|
}
|
|
|
|
return $real();
|
|
}
|
|
}
|