Migration: PM-Datum korrekt übernehmen, UUID stabil, Zwei-Phasen-Runbook
- 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>
This commit is contained in:
parent
7dbc238bdb
commit
97d69a1a3a
4 changed files with 297 additions and 40 deletions
180
tests/Feature/LegacyPressReleaseDateImportTest.php
Normal file
180
tests/Feature/LegacyPressReleaseDateImportTest.php
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
<?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');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue