published()->create([ 'title' => 'Max Mustermann übernimmt die Leitung', 'text' => '
Max ist maximal motiviert. Herr Mustermann bleibt. Mustermanns Rolle ändert sich.
', ]); app(AnonymizationService::class)->execute( $pressRelease, ['Max Mustermann', 'Max', 'Mustermann'], '[entfernt]', detachContacts: false, ); $pressRelease->refresh(); expect($pressRelease->title)->toBe('[entfernt] übernimmt die Leitung') ->and($pressRelease->text)->toContain('maximal') // "Max" zerlegt "maximal" nicht ->and($pressRelease->text)->toContain('Mustermanns') // Genitiv nur über explizite Variante ->and($pressRelease->text)->toContain('[entfernt] ist maximal') ->and($pressRelease->text)->toContain('Herr [entfernt] bleibt') ->and($pressRelease->isSyncLocked())->toBeTrue(); }); test('preview reports hits per field without changing the press release', function () { /** @var TestCase $this */ $pressRelease = PressRelease::factory()->published()->create([ 'title' => 'Mariana Gleue informiert', 'text' => 'Kontakt: Mariana Gleue, Telefon 0123.
', ]); $preview = app(AnonymizationService::class)->preview($pressRelease, ['Mariana Gleue']); expect($preview['total_hits'])->toBe(2) ->and($preview['fields']['title']['hits'])->toBe(1) ->and($preview['fields']['text']['after'])->toContain('[entfernt]') ->and($pressRelease->fresh()->title)->toBe('Mariana Gleue informiert') ->and($pressRelease->fresh()->isSyncLocked())->toBeFalse(); }); test('execute snapshots encrypted, detaches contacts and logs without cleartext', function () { /** @var TestCase $this */ $pressRelease = PressRelease::factory()->published()->create([ 'title' => 'Matvey Teplitsky spricht für die Firma', ]); $contact = Contact::factory()->create(); $pressRelease->contacts()->attach($contact->id); $legalRequest = LegalRequest::factory() ->type(LegalRequestType::Dsgvo) ->create(['press_release_id' => $pressRelease->id]); $snapshot = app(AnonymizationService::class)->execute( $pressRelease, ['Matvey Teplitsky'], 'der Sprecher', legalRequest: $legalRequest, ); $pressRelease->refresh(); expect($pressRelease->title)->toBe('der Sprecher spricht für die Firma') ->and($pressRelease->contacts()->count())->toBe(0) ->and($snapshot->legal_request_id)->toBe($legalRequest->id) ->and($snapshot->encrypted_before['fields']['title'])->toContain('Matvey Teplitsky') ->and($snapshot->encrypted_before['contact_ids'])->toBe([$contact->id]); // Klartext weder im DB-Rohwert des Snapshots noch im Verlaufs-Eintrag. $raw = DB::table('press_release_redaction_snapshots')->where('id', $snapshot->id)->value('encrypted_before'); expect($raw)->not->toContain('Teplitsky'); $event = ActivityEvent::query()->where('type', ActivityType::Anonymization->value)->sole(); expect($event->description)->not->toContain('Teplitsky') ->and($event->meta['snapshot_id'])->toBe($snapshot->id); }); test('selected images are soft-deleted and their public files removed', function () { /** @var TestCase $this */ Storage::fake('public'); Storage::disk('public')->put('pr/foto.jpg', 'bild'); Storage::disk('public')->put('pr/foto-thumb.jpg', 'thumb'); $pressRelease = PressRelease::factory()->published()->create(); $image = PressReleaseImage::query()->create([ 'press_release_id' => $pressRelease->id, 'disk' => 'public', 'path' => 'pr/foto.jpg', 'variants' => ['thumb' => 'pr/foto-thumb.jpg'], 'title' => 'Pressefoto mit Klarname', ]); app(AnonymizationService::class)->execute( $pressRelease, [], detachContacts: false, removeImageIds: [$image->id], ); expect(PressReleaseImage::query()->find($image->id))->toBeNull() ->and(PressReleaseImage::withTrashed()->find($image->id))->not->toBeNull(); Storage::disk('public')->assertMissing('pr/foto.jpg'); Storage::disk('public')->assertMissing('pr/foto-thumb.jpg'); }); test('the purge command removes only snapshots past their retention date', function () { /** @var TestCase $this */ $due = PressReleaseRedactionSnapshot::factory()->dueForPurge()->create(); $kept = PressReleaseRedactionSnapshot::factory()->create(); $this->artisan('snapshots:purge')->assertSuccessful(); expect(PressReleaseRedactionSnapshot::query()->find($due->id))->toBeNull() ->and(PressReleaseRedactionSnapshot::query()->find($kept->id))->not->toBeNull(); });