mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
Fix RSS health check
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class RssHealthController extends Controller
|
||||
{
|
||||
public function __invoke(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'status' => 'ok',
|
||||
'service' => 'rss',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ return new class extends Migration
|
||||
$defaults = [
|
||||
'api' => '/api/v2/capabilities,/api/v1/api',
|
||||
'http' => '/up',
|
||||
'rss' => '/rss/full-feed',
|
||||
'rss' => '/rss/health',
|
||||
];
|
||||
|
||||
foreach ($defaults as $slug => $path) {
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('service_statuses') || ! Schema::hasColumn('service_statuses', 'endpoint_url')) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table('service_statuses')
|
||||
->where('slug', 'rss')
|
||||
->where('endpoint_url', '/rss/full-feed')
|
||||
->update(['endpoint_url' => '/rss/health']);
|
||||
|
||||
$this->forgetSiteStatusCaches();
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! Schema::hasTable('service_statuses') || ! Schema::hasColumn('service_statuses', 'endpoint_url')) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table('service_statuses')
|
||||
->where('slug', 'rss')
|
||||
->where('endpoint_url', '/rss/health')
|
||||
->update(['endpoint_url' => '/rss/full-feed']);
|
||||
|
||||
$this->forgetSiteStatusCaches();
|
||||
}
|
||||
|
||||
private function forgetSiteStatusCaches(): void
|
||||
{
|
||||
Cache::forget('admin:site-status:enabled-services');
|
||||
Cache::forget('admin:site-status:active-incidents');
|
||||
Cache::forget('admin:dashboard:snapshot');
|
||||
}
|
||||
};
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! $this->hasRequiredTablesAndColumns()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table('service_incidents')
|
||||
->where('service_incidents.is_auto', true)
|
||||
->where('service_incidents.title', '[Auto] RSS — Unexpected response (HTTP 400)')
|
||||
->whereExists(static function ($query): void {
|
||||
$query->select(DB::raw(1))
|
||||
->from('service_incident_service_status')
|
||||
->join('service_statuses', 'service_statuses.id', '=', 'service_incident_service_status.service_status_id')
|
||||
->whereColumn('service_incident_service_status.service_incident_id', 'service_incidents.id')
|
||||
->where('service_statuses.slug', 'rss');
|
||||
})
|
||||
->update([
|
||||
'status' => 'resolved',
|
||||
'impact' => 'none',
|
||||
'resolved_at' => DB::raw('COALESCE(resolved_at, started_at)'),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->forgetSiteStatusCaches();
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Intentionally not reversible: these rows are auto-created false positives
|
||||
// from probing an authenticated RSS feed without an API token.
|
||||
}
|
||||
|
||||
private function hasRequiredTablesAndColumns(): bool
|
||||
{
|
||||
return Schema::hasTable('service_statuses')
|
||||
&& Schema::hasTable('service_incidents')
|
||||
&& Schema::hasTable('service_incident_service_status')
|
||||
&& Schema::hasColumn('service_incidents', 'is_auto');
|
||||
}
|
||||
|
||||
private function forgetSiteStatusCaches(): void
|
||||
{
|
||||
Cache::forget('admin:site-status:enabled-services');
|
||||
Cache::forget('admin:site-status:active-incidents');
|
||||
Cache::forget('admin:dashboard:snapshot');
|
||||
}
|
||||
};
|
||||
@@ -12,6 +12,9 @@
|
||||
*/
|
||||
|
||||
use App\Http\Controllers\RssController;
|
||||
use App\Http\Controllers\RssHealthController;
|
||||
|
||||
Route::get('health', RssHealthController::class);
|
||||
|
||||
Route::middleware('apiRateLimit')->group(function (): void {
|
||||
Route::get('mymovies', [RssController::class, 'myMoviesRss']);
|
||||
|
||||
@@ -241,7 +241,17 @@ class NzbAndRssAccessTest extends TestCase
|
||||
->assertJsonPath('error', 'Incorrect user credentials');
|
||||
}
|
||||
|
||||
public function test_rss_feed_without_api_token_returns_403_error_instead_of_login_redirect(): void
|
||||
public function test_rss_health_endpoint_is_public_for_status_monitoring(): void
|
||||
{
|
||||
$this->getJson('/rss/health')
|
||||
->assertOk()
|
||||
->assertJson([
|
||||
'status' => 'ok',
|
||||
'service' => 'rss',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_rss_feed_without_api_token_returns_api_error_instead_of_login_redirect(): void
|
||||
{
|
||||
$response = $this->get('/rss/full-feed');
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Services;
|
||||
|
||||
use App\Enums\IncidentImpactEnum;
|
||||
use App\Models\ServiceStatus;
|
||||
use App\Services\SiteStatusService;
|
||||
use App\Services\StatusProbes\ServiceProbeRegistry;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SiteStatusServiceHttpHealthCheckTest extends TestCase
|
||||
{
|
||||
public function test_authenticated_rss_feed_url_400_is_reported_as_unexpected_response(): void
|
||||
{
|
||||
config(['app.url' => 'https://example.test']);
|
||||
|
||||
Http::fake([
|
||||
'https://example.test/rss/full-feed' => Http::response(['error' => 'Missing parameter (api_token)'], 400),
|
||||
]);
|
||||
|
||||
$result = $this->statusService()->checkServiceHealth(new ServiceStatus([
|
||||
'name' => 'RSS',
|
||||
'slug' => 'rss',
|
||||
'endpoint_url' => '/rss/full-feed',
|
||||
'check_type' => 'http',
|
||||
]));
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
$this->assertSame(400, $result['status_code']);
|
||||
$this->assertSame(IncidentImpactEnum::Major, $result['impact']);
|
||||
$this->assertSame('Unexpected response (HTTP 400)', $result['reason']);
|
||||
}
|
||||
|
||||
public function test_public_rss_health_endpoint_is_reported_as_healthy(): void
|
||||
{
|
||||
config(['app.url' => 'https://example.test']);
|
||||
|
||||
Http::fake([
|
||||
'https://example.test/rss/health' => Http::response(['status' => 'ok', 'service' => 'rss'], 200),
|
||||
]);
|
||||
|
||||
$result = $this->statusService()->checkServiceHealth(new ServiceStatus([
|
||||
'name' => 'RSS',
|
||||
'slug' => 'rss',
|
||||
'endpoint_url' => '/rss/health',
|
||||
'check_type' => 'http',
|
||||
]));
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
$this->assertSame(200, $result['status_code']);
|
||||
$this->assertNull($result['impact']);
|
||||
$this->assertSame('OK', $result['reason']);
|
||||
}
|
||||
|
||||
private function statusService(): SiteStatusService
|
||||
{
|
||||
return new SiteStatusService($this->createMock(ServiceProbeRegistry::class));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user