--since=<Zeitpunkt> schränkt den Quell-Scan auf Legacy-Zeilen ein, deren created_at ODER updated_at im Fenster liegt (users/companies/contacts/ press-releases; Kategorien und link-associations bleiben ungefiltert). Kombiniert mit --force werden geänderte Bestandszeilen aktualisiert — damit dauert ein Tages-Delta Sekunden statt eines Volldurchlaufs über 150k+ Zeilen. Dazu Bugfix, der den Delta erst rund macht: User-/Company-/Contact- Importer übergaben created_at/updated_at innerhalb von updateOrCreate — die Felder sind nicht fillable und wurden stillschweigend verworfen (Live-Befund: NULL-Timestamps, bisher nur durch legacy:fix-timestamps geheilt). Jetzt explizit per forceFill wie im PressReleaseImporter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
242 lines
8.3 KiB
PHP
242 lines
8.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\Carbon;
|
||
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');
|
||
});
|
||
|
||
test('a since delta only scans legacy rows created or updated in the window', function () {
|
||
/** @var TestCase $this */
|
||
seedLegacyPressReleaseSchema();
|
||
|
||
DB::connection('mysql_presseecho')->table('press_release')->insert([
|
||
[
|
||
'id' => 20,
|
||
'title' => 'Alte 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' => 'alt-unveraendert',
|
||
],
|
||
[
|
||
'id' => 21,
|
||
'title' => 'Heute neu angelegt',
|
||
'language' => 'de',
|
||
'text' => 'Inhalt',
|
||
'category_id' => 500,
|
||
'user_id' => 700,
|
||
'status' => 'published',
|
||
'no_export' => false,
|
||
'created_at' => '2026-07-20 09:00:00',
|
||
'updated_at' => '2026-07-20 09:00:00',
|
||
'slug' => 'heute-neu',
|
||
],
|
||
[
|
||
'id' => 22,
|
||
'title' => 'Alt, aber heute geändert',
|
||
'language' => 'de',
|
||
'text' => 'Inhalt',
|
||
'category_id' => 500,
|
||
'user_id' => 700,
|
||
'status' => 'published',
|
||
'no_export' => false,
|
||
'created_at' => '2012-01-15 10:00:00',
|
||
'updated_at' => '2026-07-20 10:30:00',
|
||
'slug' => 'alt-heute-geaendert',
|
||
],
|
||
]);
|
||
|
||
app(PressReleaseImporter::class)->run(new ImportContext(
|
||
'presseecho',
|
||
false,
|
||
true,
|
||
Carbon::parse('2026-07-20 00:00:00'),
|
||
));
|
||
|
||
$imported = PressRelease::withoutGlobalScopes()
|
||
->where('legacy_portal', 'presseecho')
|
||
->whereIn('legacy_id', [20, 21, 22])
|
||
->pluck('legacy_id')->sort()->values()->all();
|
||
|
||
expect($imported)->toBe([21, 22]);
|
||
});
|