From ba8513c256b734a5ce29479465a08d91f5dff8a7 Mon Sep 17 00:00:00 2001 From: myrmidex Date: Sun, 16 Aug 2026 22:53:52 +0200 Subject: [PATCH] 158 - Fix Belga feed offset=1 skipping newest article --- config/feed.php | 3 +- ...024_01_01_000024_fix_belga_feed_offset.php | 65 +++++++ .../FixBelgaFeedOffsetMigrationTest.php | 176 ++++++++++++++++++ .../Parsers/BelgaHomepageParserTest.php | 3 +- 4 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 database/migrations/2024_01_01_000024_fix_belga_feed_offset.php create mode 100644 tests/Feature/FixBelgaFeedOffsetMigrationTest.php diff --git a/config/feed.php b/config/feed.php index 78d23199..12a0b33c 100644 --- a/config/feed.php +++ b/config/feed.php @@ -45,7 +45,8 @@ 'type' => 'website', 'is_active' => true, 'languages' => [ - 'en' => ['url' => 'https://capi.belga.press/belgapress/api/public/pressreleases?offset=1&count=50&search=&start=&end=&newsroomId=70&language=EN'], + // offset is a 0-based item index; offset=1 skips the newest release (#158). + 'en' => ['url' => 'https://capi.belga.press/belgapress/api/public/pressreleases?offset=0&count=50&search=&start=&end=&newsroomId=70&language=EN'], ], 'parsers' => [ 'homepage' => BelgaHomepageParserAdapter::class, diff --git a/database/migrations/2024_01_01_000024_fix_belga_feed_offset.php b/database/migrations/2024_01_01_000024_fix_belga_feed_offset.php new file mode 100644 index 00000000..30e88483 --- /dev/null +++ b/database/migrations/2024_01_01_000024_fix_belga_feed_offset.php @@ -0,0 +1,65 @@ +where('provider', 'belga')->where('url', $url)->min('id') + ?? DB::table('feeds')->where('provider', 'belga')->min('id'); + + if ($keepId === null) { + return; + } + + DB::table('feeds') + ->where('provider', 'belga') + ->where('id', '!=', $keepId) + ->update([ + 'is_active' => false, + 'updated_at' => now(), + ]); + + DB::table('feeds') + ->where('id', $keepId) + ->update([ + 'url' => $url, + 'updated_at' => now(), + ]); + } + + public function down(): void + { + // No rollback: restoring offset=1 would reinstate a feed that silently + // drops the newest press release. + } +}; diff --git a/tests/Feature/FixBelgaFeedOffsetMigrationTest.php b/tests/Feature/FixBelgaFeedOffsetMigrationTest.php new file mode 100644 index 00000000..4e5c0921 --- /dev/null +++ b/tests/Feature/FixBelgaFeedOffsetMigrationTest.php @@ -0,0 +1,176 @@ +up(); + } + + private function findFeed(int $id): stdClass + { + $feed = DB::table('feeds')->find($id); + + $this->assertInstanceOf(stdClass::class, $feed); + + return $feed; + } + + private function targetUrl(): string + { + $url = config('feed.providers.belga.languages.en.url'); + + $this->assertIsString($url); + + return $url; + } + + private function seedFeed(string $url, bool $isActive = true, string $provider = 'belga', string $type = 'website'): int + { + return DB::table('feeds')->insertGetId([ + 'name' => 'Belga', + 'provider' => $provider, + 'type' => $type, + 'url' => $url, + 'is_active' => $isActive, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + public function test_moves_a_feed_still_on_the_offset_one_url(): void + { + DB::table('feeds')->delete(); + $id = $this->seedFeed(self::OFFSET_ONE_URL); + + $this->runMigration(); + + $feed = $this->findFeed($id); + + $this->assertSame($this->targetUrl(), $feed->url); + $this->assertSame('website', $feed->type); + $this->assertTrue((bool) $feed->is_active); + } + + public function test_is_idempotent_when_the_feed_is_already_on_the_target_url(): void + { + DB::table('feeds')->delete(); + $id = $this->seedFeed($this->targetUrl()); + + $this->runMigration(); + $this->runMigration(); + + $this->assertSame(1, DB::table('feeds')->where('provider', 'belga')->count()); + + $feed = $this->findFeed($id); + $this->assertSame($this->targetUrl(), $feed->url); + $this->assertTrue((bool) $feed->is_active); + } + + public function test_deactivates_superseded_rows_instead_of_colliding_on_the_unique_url(): void + { + DB::table('feeds')->delete(); + $staleId = $this->seedFeed(self::OFFSET_ONE_URL); + $currentId = $this->seedFeed($this->targetUrl()); + + $this->runMigration(); + + // Both rows survive: routes.feed_id cascades on delete, so a superseded + // feed is deactivated rather than removed. + $this->assertSame(2, DB::table('feeds')->where('provider', 'belga')->count()); + + $current = $this->findFeed($currentId); + $this->assertTrue((bool) $current->is_active); + $this->assertSame($this->targetUrl(), $current->url); + + $stale = $this->findFeed($staleId); + $this->assertFalse((bool) $stale->is_active); + } + + public function test_adopts_the_oldest_row_when_none_is_on_the_target_url(): void + { + DB::table('feeds')->delete(); + $oldestId = $this->seedFeed(self::OFFSET_ONE_URL); + $newerId = $this->seedFeed('https://capi.belga.press/belgapress/api/public/pressreleases?offset=1&count=6'); + + $this->runMigration(); + + // Neither row matches config, so the migration falls back to the lowest + // id — the oldest row, which is the one routes are most likely tied to. + $oldest = $this->findFeed($oldestId); + $this->assertSame($this->targetUrl(), $oldest->url); + $this->assertTrue((bool) $oldest->is_active); + + $this->assertFalse((bool) $this->findFeed($newerId)->is_active); + } + + public function test_leaves_other_providers_untouched(): void + { + DB::table('feeds')->delete(); + $vrtId = $this->seedFeed('https://www.vrt.be/vrtnws/nl/', true, 'vrt'); + // Seed a belga row too, so the migration actually runs its updates + // rather than bailing out at the "no belga feed" guard. + $this->seedFeed(self::OFFSET_ONE_URL); + + $this->runMigration(); + + $vrt = $this->findFeed($vrtId); + + $this->assertSame('https://www.vrt.be/vrtnws/nl/', $vrt->url); + $this->assertTrue((bool) $vrt->is_active); + } + + public function test_down_is_a_deliberate_no_op(): void + { + DB::table('feeds')->delete(); + $id = $this->seedFeed($this->targetUrl()); + + $migration = require database_path('migrations/2024_01_01_000024_fix_belga_feed_offset.php'); + $migration->down(); + + // Rolling back must not restore the offset=1 url that drops the newest + // press release. + $feed = $this->findFeed($id); + $this->assertSame($this->targetUrl(), $feed->url); + $this->assertTrue((bool) $feed->is_active); + } + + public function test_does_nothing_when_no_belga_feed_exists(): void + { + DB::table('feeds')->delete(); + + $this->runMigration(); + + $this->assertSame(0, DB::table('feeds')->count()); + } + + public function test_does_nothing_when_the_configured_url_is_missing(): void + { + DB::table('feeds')->delete(); + config(['feed.providers.belga.languages.en.url' => '']); + $id = $this->seedFeed(self::OFFSET_ONE_URL); + + $this->runMigration(); + + $feed = $this->findFeed($id); + + $this->assertSame(self::OFFSET_ONE_URL, $feed->url); + } +} diff --git a/tests/Unit/Services/Parsers/BelgaHomepageParserTest.php b/tests/Unit/Services/Parsers/BelgaHomepageParserTest.php index 15b74342..12eae183 100644 --- a/tests/Unit/Services/Parsers/BelgaHomepageParserTest.php +++ b/tests/Unit/Services/Parsers/BelgaHomepageParserTest.php @@ -114,7 +114,8 @@ public function test_adapter_api_url_pins_load_bearing_query_params(): void $this->assertStringStartsWith('https://capi.belga.press/belgapress/api/public/pressreleases?', $url); $this->assertStringContainsString('newsroomId=70', $url); - $this->assertStringContainsString('offset=1', $url); + // 0-based item index: offset=1 skipped the newest release entirely (#158). + $this->assertStringContainsString('offset=0&', $url); $this->assertStringContainsString('count=50', $url); $this->assertStringContainsString('language=EN', $url); }