141 - Add a cascading foreign key to article publications
This commit is contained in:
parent
dc64dd83b8
commit
6270c97c29
2 changed files with 157 additions and 0 deletions
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// The column never had a constraint, so pre-existing rows may point at deleted channels.
|
||||||
|
$orphans = DB::table('article_publications')
|
||||||
|
->whereNotIn('platform_channel_id', DB::table('platform_channels')->select('id'));
|
||||||
|
|
||||||
|
if (($count = $orphans->count()) > 0) {
|
||||||
|
Log::warning("Deleting {$count} orphaned article_publications rows before adding the channel foreign key.");
|
||||||
|
$orphans->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::table('article_publications', function (Blueprint $table) {
|
||||||
|
$table->foreign('platform_channel_id')
|
||||||
|
->references('id')
|
||||||
|
->on('platform_channels')
|
||||||
|
->onDelete('cascade');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
// Orphan rows deleted in up() are not restored.
|
||||||
|
Schema::table('article_publications', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['platform_channel_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
120
tests/Feature/ChannelDeletionCascadeTest.php
Normal file
120
tests/Feature/ChannelDeletionCascadeTest.php
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\Article;
|
||||||
|
use App\Models\ArticlePublication;
|
||||||
|
use App\Models\Feed;
|
||||||
|
use App\Models\Keyword;
|
||||||
|
use App\Models\PlatformAccount;
|
||||||
|
use App\Models\PlatformChannel;
|
||||||
|
use App\Models\Route;
|
||||||
|
use App\Models\RouteArticle;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class ChannelDeletionCascadeTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{PlatformChannel, Feed}
|
||||||
|
*/
|
||||||
|
private function channelWithEverything(): array
|
||||||
|
{
|
||||||
|
$feed = Feed::factory()->create();
|
||||||
|
/** @var PlatformChannel $channel */
|
||||||
|
$channel = PlatformChannel::factory()->create();
|
||||||
|
|
||||||
|
Route::create([
|
||||||
|
'feed_id' => $feed->id,
|
||||||
|
'platform_channel_id' => $channel->id,
|
||||||
|
'priority' => 50,
|
||||||
|
'is_active' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Keyword::create([
|
||||||
|
'feed_id' => $feed->id,
|
||||||
|
'platform_channel_id' => $channel->id,
|
||||||
|
'keyword' => 'brussels',
|
||||||
|
'is_active' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$article = Article::factory()->create(['feed_id' => $feed->id]);
|
||||||
|
|
||||||
|
RouteArticle::factory()->create([
|
||||||
|
'feed_id' => $feed->id,
|
||||||
|
'platform_channel_id' => $channel->id,
|
||||||
|
'article_id' => $article->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
ArticlePublication::factory()->create([
|
||||||
|
'article_id' => $article->id,
|
||||||
|
'platform_channel_id' => $channel->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$account = PlatformAccount::factory()->create();
|
||||||
|
$channel->platformAccounts()->attach($account->id, [
|
||||||
|
'is_active' => true,
|
||||||
|
'priority' => 1,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [$channel, $feed];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_deleting_a_channel_cascades_to_every_dependent_table(): void
|
||||||
|
{
|
||||||
|
[$channel] = $this->channelWithEverything();
|
||||||
|
|
||||||
|
$this->assertSame(1, Route::count());
|
||||||
|
$this->assertSame(1, Keyword::count());
|
||||||
|
$this->assertSame(1, RouteArticle::count());
|
||||||
|
$this->assertSame(1, ArticlePublication::count());
|
||||||
|
|
||||||
|
$channel->delete();
|
||||||
|
|
||||||
|
$this->assertSame(0, Route::count());
|
||||||
|
$this->assertSame(0, Keyword::count());
|
||||||
|
$this->assertSame(0, RouteArticle::count());
|
||||||
|
$this->assertSame(0, ArticlePublication::count());
|
||||||
|
$this->assertDatabaseCount('platform_account_channels', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_deleting_a_channel_leaves_the_feed_and_article_intact(): void
|
||||||
|
{
|
||||||
|
[$channel, $feed] = $this->channelWithEverything();
|
||||||
|
|
||||||
|
$channel->delete();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('feeds', ['id' => $feed->id]);
|
||||||
|
$this->assertSame(1, Article::count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_deleting_a_channel_leaves_other_channels_untouched(): void
|
||||||
|
{
|
||||||
|
[$channel] = $this->channelWithEverything();
|
||||||
|
$other = PlatformChannel::factory()->create();
|
||||||
|
|
||||||
|
$channel->delete();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('platform_channels', ['id' => $other->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_publications_for_another_channel_survive(): void
|
||||||
|
{
|
||||||
|
[$channel] = $this->channelWithEverything();
|
||||||
|
|
||||||
|
$other = PlatformChannel::factory()->create();
|
||||||
|
$article = Article::factory()->create();
|
||||||
|
ArticlePublication::factory()->create([
|
||||||
|
'article_id' => $article->id,
|
||||||
|
'platform_channel_id' => $other->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$channel->delete();
|
||||||
|
|
||||||
|
$this->assertSame(1, ArticlePublication::count());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue