subHour(); $pair = []; foreach ([0, 1] as $index) { $company = Company::factory()->businessportal24()->create([ 'owner_user_id' => $user->id, 'name' => 'Firma '.$title, 'slug' => $index === 0 ? 'firma-'.$slugBase : 'firma-'.$slugBase.'-2', ]); $pressRelease = PressRelease::factory() ->forPortal(Portal::Businessportal24) ->published() ->create([ 'user_id' => $user->id, 'company_id' => $company->id, 'title' => $title, 'slug' => $index === 0 ? $slugBase : $slugBase.'-2', 'created_at' => $createdAt->copy()->addSeconds($index * 20), ]); $pressRelease->statusLogs()->create([ 'from_status' => 'draft', 'to_status' => 'review', 'source' => 'api', 'created_at' => $pressRelease->created_at, ]); $pair[] = $pressRelease; } return $pair; } test('moves the second api duplicate with its company to the other portal', function () { /** @var TestCase $this */ $user = User::factory()->create(['portal' => Portal::Businessportal24]); [$original, $duplicate] = apiDuplicatePair($user, 'Doppelt gesendete Meldung', 'doppelt-gesendete-meldung'); $this->artisan('press-releases:fix-api-portal-duplicates') ->assertSuccessful(); $original->refresh(); $duplicate->refresh(); expect($original->portal)->toBe(Portal::Businessportal24) ->and($original->slug)->toBe('doppelt-gesendete-meldung') ->and($duplicate->portal)->toBe(Portal::Presseecho) ->and($duplicate->slug)->toBe('doppelt-gesendete-meldung'); expect($original->company()->withoutGlobalScopes()->first()->portal)->toBe(Portal::Businessportal24) ->and($duplicate->company()->withoutGlobalScopes()->first()->portal)->toBe(Portal::Presseecho) ->and($duplicate->company()->withoutGlobalScopes()->first()->slug)->toBe('firma-doppelt-gesendete-meldung'); }); test('dry run reports but changes nothing', function () { /** @var TestCase $this */ $user = User::factory()->create(['portal' => Portal::Businessportal24]); [, $duplicate] = apiDuplicatePair($user, 'Trockenlauf Meldung', 'trockenlauf-meldung'); $this->artisan('press-releases:fix-api-portal-duplicates', ['--dry-run' => true]) ->assertSuccessful(); expect($duplicate->refresh()->portal)->toBe(Portal::Businessportal24) ->and($duplicate->slug)->toBe('trockenlauf-meldung-2'); }); test('ignores web double submits without api status log', function () { /** @var TestCase $this */ $user = User::factory()->create(['portal' => Portal::Presseecho]); $company = Company::factory()->presseecho()->create(['owner_user_id' => $user->id]); $pressReleases = collect([0, 1])->map(fn (int $index) => PressRelease::factory() ->forPortal(Portal::Presseecho) ->create([ 'user_id' => $user->id, 'company_id' => $company->id, 'title' => 'Doppelklick im Formular', 'slug' => $index === 0 ? 'doppelklick-im-formular' : 'doppelklick-im-formular-2', 'created_at' => now()->subMinutes(10)->addSeconds($index * 3), ])); $this->artisan('press-releases:fix-api-portal-duplicates') ->assertSuccessful(); expect($pressReleases[1]->refresh()->portal)->toBe(Portal::Presseecho) ->and($pressReleases[1]->slug)->toBe('doppelklick-im-formular-2'); }); test('ignores legacy synced press releases and pairs outside the time window', function () { /** @var TestCase $this */ $user = User::factory()->create(['portal' => Portal::Businessportal24]); [, $duplicate] = apiDuplicatePair($user, 'Legacy Sync Meldung', 'legacy-sync-meldung'); $duplicate->forceFill(['legacy_id' => 987654, 'legacy_portal' => 'businessportal24'])->save(); [, $lateDuplicate] = apiDuplicatePair($user, 'Späte zweite Meldung', 'spate-zweite-meldung'); $lateDuplicate->forceFill(['created_at' => now()->subHour()->addHours(3)])->save(); $this->artisan('press-releases:fix-api-portal-duplicates') ->assertSuccessful(); expect($duplicate->refresh()->portal)->toBe(Portal::Businessportal24) ->and($lateDuplicate->refresh()->portal)->toBe(Portal::Businessportal24); }); test('shared companies stay untouched when the duplicate moves', function () { /** @var TestCase $this */ $user = User::factory()->create(['portal' => Portal::Businessportal24]); $sharedCompany = Company::factory()->businessportal24()->create(['owner_user_id' => $user->id]); $pressReleases = collect([0, 1])->map(function (int $index) use ($user, $sharedCompany) { $pressRelease = PressRelease::factory() ->forPortal(Portal::Businessportal24) ->create([ 'user_id' => $user->id, 'company_id' => $sharedCompany->id, 'title' => 'Meldung mit Stammfirma', 'slug' => $index === 0 ? 'meldung-mit-stammfirma' : 'meldung-mit-stammfirma-2', 'created_at' => now()->subHour()->addSeconds($index * 20), ]); $pressRelease->statusLogs()->create([ 'from_status' => 'draft', 'to_status' => 'review', 'source' => 'api', 'created_at' => $pressRelease->created_at, ]); return $pressRelease; }); $this->artisan('press-releases:fix-api-portal-duplicates') ->assertSuccessful(); expect($pressReleases[1]->refresh()->portal)->toBe(Portal::Presseecho) ->and($sharedCompany->refresh()->portal)->toBe(Portal::Businessportal24); });