- published_at = Legacy-created_at (Publikationsdatum, Frontend-Sortier- schlüssel) statt updated_at, das bei Alt-Daten oft den Massen-/Migrations- stempel trägt und ein falsches "frisches" Datum erzeugte. - created_at/updated_at per forceFill direkt im Import gesetzt (waren nicht fillable, wurden still verworfen) – Import allein ist jetzt datumssauber. - legacy:fix-timestamps korrigiert zusätzlich published_at (status=published). - PM-UUID bleibt beim Re-Import/Delta-Lauf erhalten (kein Neu-Würfeln). - MIGRATION-STEPS auf Zwei-Phasen-Strategie umgestellt: migrate, Phase 1 mit --force, Phase 2 (Delta) ohne --force, Grandfather-Re-Run, Idempotenz-Tabelle. - Tests: LegacyPressReleaseDateImportTest (published_at-Quelle, Entwurf, Force-Re-Import erhält UUID + Datum). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
180 lines
6.3 KiB
PHP
180 lines
6.3 KiB
PHP
<?php
|
||
|
||
use App\Enums\PressReleaseStatus;
|
||
use App\Models\Category;
|
||
use App\Models\LegacyImportMap;
|
||
use App\Models\PressRelease;
|
||
use App\Models\User;
|
||
use App\Services\Import\ImportContext;
|
||
use App\Services\Import\PressReleaseImporter;
|
||
use Illuminate\Database\Schema\Blueprint;
|
||
use Illuminate\Support\Facades\Config;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Schema;
|
||
use Tests\TestCase;
|
||
|
||
/**
|
||
* Baut das minimale Legacy-Schema (press_release + leere Bild-/Kontakt-Pivots)
|
||
* und legt Kategorie + User samt Import-Map-Einträgen an, damit der Importer
|
||
* Kategorie und Autor auflösen kann (ohne Autor wird eine PM übersprungen).
|
||
*/
|
||
function seedLegacyPressReleaseSchema(): Category
|
||
{
|
||
Config::set('database.connections.mysql_presseecho', [
|
||
'driver' => '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,
|
||
]);
|
||
|
||
return $category;
|
||
}
|
||
|
||
test('a published release takes published_at from the legacy created_at, not updated_at', function () {
|
||
/** @var TestCase $this */
|
||
seedLegacyPressReleaseSchema();
|
||
|
||
// updated_at trägt im Altsystem den Massen-/Migrationsstempel (2026) – das
|
||
// darf NICHT als Veröffentlichungsdatum landen.
|
||
DB::connection('mysql_presseecho')->table('press_release')->insert([
|
||
'id' => 10,
|
||
'title' => 'Alte veröffentlichte Meldung',
|
||
'language' => 'de',
|
||
'text' => 'Inhalt',
|
||
'category_id' => 500,
|
||
'user_id' => 700,
|
||
'status' => 'published',
|
||
'hits' => 5,
|
||
'no_export' => false,
|
||
'created_at' => '2008-11-18 12:05:42',
|
||
'updated_at' => '2026-04-23 01:08:26',
|
||
'slug' => 'alte-meldung',
|
||
]);
|
||
|
||
app(PressReleaseImporter::class)->run(new ImportContext('presseecho', false, true));
|
||
|
||
$pr = PressRelease::withoutGlobalScopes()
|
||
->where('legacy_portal', 'presseecho')
|
||
->where('legacy_id', 10)
|
||
->firstOrFail();
|
||
|
||
expect($pr->status)->toBe(PressReleaseStatus::Published);
|
||
expect($pr->published_at?->format('Y-m-d H:i:s'))->toBe('2008-11-18 12:05:42');
|
||
expect($pr->created_at?->format('Y-m-d H:i:s'))->toBe('2008-11-18 12:05:42');
|
||
});
|
||
|
||
test('a non-published release has no published_at and keeps its legacy created_at', function () {
|
||
/** @var TestCase $this */
|
||
seedLegacyPressReleaseSchema();
|
||
|
||
DB::connection('mysql_presseecho')->table('press_release')->insert([
|
||
'id' => 11,
|
||
'title' => 'Entwurf',
|
||
'language' => 'de',
|
||
'text' => 'Inhalt',
|
||
'category_id' => 500,
|
||
'user_id' => 700,
|
||
'status' => 'new',
|
||
'no_export' => false,
|
||
'created_at' => '2010-03-01 09:00:00',
|
||
'updated_at' => '2026-04-23 01:08:26',
|
||
'slug' => 'entwurf',
|
||
]);
|
||
|
||
app(PressReleaseImporter::class)->run(new ImportContext('presseecho', false, true));
|
||
|
||
$pr = PressRelease::withoutGlobalScopes()
|
||
->where('legacy_portal', 'presseecho')
|
||
->where('legacy_id', 11)
|
||
->firstOrFail();
|
||
|
||
expect($pr->status)->toBe(PressReleaseStatus::Draft);
|
||
expect($pr->published_at)->toBeNull();
|
||
expect($pr->created_at?->format('Y-m-d H:i:s'))->toBe('2010-03-01 09:00:00');
|
||
});
|
||
|
||
test('a force re-import preserves the uuid and the publication date', function () {
|
||
/** @var TestCase $this */
|
||
seedLegacyPressReleaseSchema();
|
||
|
||
DB::connection('mysql_presseecho')->table('press_release')->insert([
|
||
'id' => 12,
|
||
'title' => 'Stabile Referenz',
|
||
'language' => 'de',
|
||
'text' => 'Inhalt',
|
||
'category_id' => 500,
|
||
'user_id' => 700,
|
||
'status' => 'published',
|
||
'no_export' => false,
|
||
'created_at' => '2009-04-08 15:43:42',
|
||
'updated_at' => '2026-04-21 22:07:40',
|
||
'slug' => 'stabile-referenz',
|
||
]);
|
||
|
||
app(PressReleaseImporter::class)->run(new ImportContext('presseecho', false, true));
|
||
|
||
$first = PressRelease::withoutGlobalScopes()
|
||
->where('legacy_portal', 'presseecho')->where('legacy_id', 12)->firstOrFail();
|
||
$originalUuid = $first->uuid;
|
||
|
||
// Zweiter --force-Lauf (Delta-Szenario): darf uuid nicht neu würfeln.
|
||
app(PressReleaseImporter::class)->run(new ImportContext('presseecho', false, true));
|
||
|
||
$second = PressRelease::withoutGlobalScopes()
|
||
->where('legacy_portal', 'presseecho')->where('legacy_id', 12)->firstOrFail();
|
||
|
||
expect($second->uuid)->toBe($originalUuid);
|
||
expect($second->published_at?->format('Y-m-d H:i:s'))->toBe('2009-04-08 15:43:42');
|
||
});
|