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 = [
|
$defaults = [
|
||||||
'api' => '/api/v2/capabilities,/api/v1/api',
|
'api' => '/api/v2/capabilities,/api/v1/api',
|
||||||
'http' => '/up',
|
'http' => '/up',
|
||||||
'rss' => '/rss/full-feed',
|
'rss' => '/rss/health',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($defaults as $slug => $path) {
|
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\RssController;
|
||||||
|
use App\Http\Controllers\RssHealthController;
|
||||||
|
|
||||||
|
Route::get('health', RssHealthController::class);
|
||||||
|
|
||||||
Route::middleware('apiRateLimit')->group(function (): void {
|
Route::middleware('apiRateLimit')->group(function (): void {
|
||||||
Route::get('mymovies', [RssController::class, 'myMoviesRss']);
|
Route::get('mymovies', [RssController::class, 'myMoviesRss']);
|
||||||
|
|||||||
@@ -241,7 +241,17 @@ class NzbAndRssAccessTest extends TestCase
|
|||||||
->assertJsonPath('error', 'Incorrect user credentials');
|
->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');
|
$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