'sqlite', 'database' => ':memory:', 'prefix' => '', 'foreign_key_constraints' => false, ]); DB::purge('mysql_presseecho'); Schema::connection('mysql_presseecho')->create('press_release', function (Blueprint $table): void { $table->integer('id')->primary(); $table->string('title')->nullable(); $table->string('language')->nullable(); $table->text('text')->nullable(); $table->string('backlink_url')->nullable(); $table->integer('category_id')->nullable(); $table->string('keywords')->nullable(); $table->integer('user_id')->nullable(); $table->integer('company_id')->nullable(); $table->string('status')->nullable(); $table->integer('hits')->nullable(); $table->integer('teaser_begin')->nullable(); $table->integer('teaser_end')->nullable(); $table->boolean('no_export')->nullable(); $table->dateTime('created_at')->nullable(); $table->dateTime('updated_at')->nullable(); $table->string('slug')->nullable(); }); Schema::connection('mysql_presseecho')->create('press_release_image', function (Blueprint $table): void { $table->integer('id')->primary(); $table->integer('press_release_id'); }); Schema::connection('mysql_presseecho')->create('press_release_contact', function (Blueprint $table): void { $table->integer('press_release_id'); $table->integer('contact_id'); }); $category = Category::factory()->withTranslations()->create(); LegacyImportMap::query()->create([ 'legacy_portal' => 'presseecho', 'legacy_table' => 'category', 'legacy_id' => 500, 'target_table' => 'categories', 'target_id' => $category->id, ]); $author = User::factory()->create(); LegacyImportMap::query()->create([ 'legacy_portal' => 'presseecho', 'legacy_table' => 'sf_guard_user', 'legacy_id' => 700, 'target_table' => 'users', 'target_id' => $author->id, ]); } test('lockSync stamps the press release, logs the event and is idempotent', function () { /** @var TestCase $this */ $pressRelease = PressRelease::factory()->published()->create(); $pressRelease->lockSync('DSGVO-Anonymisierung M-1'); expect($pressRelease->isSyncLocked())->toBeTrue() ->and($pressRelease->sync_locked_at)->not->toBeNull() ->and($pressRelease->sync_lock_reason)->toBe('DSGVO-Anonymisierung M-1'); $firstLockedAt = $pressRelease->sync_locked_at; $this->travel(1)->hours(); $pressRelease->lockSync('Zweiter Versuch'); $this->travelBack(); $pressRelease->refresh(); expect($pressRelease->sync_locked_at->equalTo($firstLockedAt))->toBeTrue() ->and($pressRelease->sync_lock_reason)->toBe('DSGVO-Anonymisierung M-1'); $event = ActivityEvent::query()->where('type', ActivityType::SyncLock->value)->sole(); expect($event->subject_id)->toBe($pressRelease->id) ->and($event->description)->toContain('DSGVO-Anonymisierung M-1'); }); test('the importer skips a sync-locked press release even with force', function () { /** @var TestCase $this */ seedSyncLockLegacySchema(); DB::connection('mysql_presseecho')->table('press_release')->insert([ 'id' => 42, 'title' => 'Originaltitel mit Klarname', 'language' => 'de', 'text' => 'Text mit Klarname', 'category_id' => 500, 'user_id' => 700, 'status' => 'published', 'no_export' => false, 'created_at' => '2019-05-01 08:00:00', 'updated_at' => '2019-05-01 08:00:00', 'slug' => 'original', ]); // Erstimport, dann Mutation über das Meldeverfahren (Schwärzung) + Sperre. app(PressReleaseImporter::class)->run(new ImportContext('presseecho', false, true)); $pressRelease = PressRelease::withoutGlobalScopes() ->where('legacy_portal', 'presseecho')->where('legacy_id', 42)->firstOrFail(); $pressRelease->forceFill(['title' => 'Geschwärzter Titel'])->save(); $pressRelease->lockSync('DSGVO-Anonymisierung'); // Force-Re-Import darf die Schwärzung nicht überschreiben. $result = app(PressReleaseImporter::class)->run(new ImportContext('presseecho', false, true)); $pressRelease->refresh(); expect($pressRelease->title)->toBe('Geschwärzter Titel') ->and($result->skipped())->toBe(1) ->and($result->updated())->toBe(0); }); test('an unlocked press release is still updated by a force re-import', function () { /** @var TestCase $this */ seedSyncLockLegacySchema(); DB::connection('mysql_presseecho')->table('press_release')->insert([ 'id' => 43, 'title' => 'Unveränderte Meldung', 'language' => 'de', 'text' => 'Inhalt', 'category_id' => 500, 'user_id' => 700, 'status' => 'published', 'no_export' => false, 'created_at' => '2019-05-01 08:00:00', 'updated_at' => '2019-05-01 08:00:00', 'slug' => 'unveraendert', ]); app(PressReleaseImporter::class)->run(new ImportContext('presseecho', false, true)); $result = app(PressReleaseImporter::class)->run(new ImportContext('presseecho', false, true)); expect($result->updated())->toBe(1) ->and($result->skipped())->toBe(0); }); test('a redaction snapshot encrypts the previous values and gets a purge date from config', function () { /** @var TestCase $this */ config()->set('legal.snapshot_retention_days', 30); $pressRelease = PressRelease::factory()->published()->create(); $snapshot = PressReleaseRedactionSnapshot::capture($pressRelease, [ 'title' => 'Klarname Max Mustermann', ]); // Klartext darf nie unverschlüsselt in der Datenbank liegen. $raw = DB::table('press_release_redaction_snapshots') ->where('id', $snapshot->id) ->value('encrypted_before'); expect($raw)->not->toContain('Max Mustermann') ->and($snapshot->encrypted_before['title'])->toBe('Klarname Max Mustermann') ->and($snapshot->purge_after->toDateString())->toBe(now()->addDays(30)->toDateString()); }); test('the due-for-purge scope only matches snapshots past their purge date', function () { /** @var TestCase $this */ $due = PressReleaseRedactionSnapshot::factory()->dueForPurge()->create(); PressReleaseRedactionSnapshot::factory()->create(); expect(PressReleaseRedactionSnapshot::query()->dueForPurge()->pluck('id')->all()) ->toBe([$due->id]); });