Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions app/Http/Controllers/ItemController.php
Original file line number Diff line number Diff line change
Expand Up @@ -587,18 +587,46 @@ public function websitelookup($url): StreamInterface
}

/**
* @param $id
* @return void
* Return live stats for an enhanced application tile.
*
* Always responds with HTTP 200 and valid JSON so the frontend refresh
* loop (liveStatRefresh.js) keeps re-queueing the tile. On any failure we
* degrade gracefully to an inactive, empty tile instead of a 500.
*
* @param int|string $id
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response
*/
public function getStats($id)
{
$graceful = response()->json(['status' => 'inactive', 'html' => '']);

$item = Item::find($id);
if ($item === null) {
return $graceful;
}

// Non-enhanced items (or stale records) have no live-stats class.
if (empty($item->class)) {
return $graceful;
}

try {
$config = $item->getconfig();

// Guard against a stale/renamed class string from the remote apps repo.
if (! class_exists($item->class)) {
return $graceful;
}

$config = $item->getconfig();
if (isset($item->class)) {
$application = new $item->class;
$application->config = $config;
echo $application->livestats();

// livestats() returns a JSON string; return it verbatim (no re-encoding).
return response($application->livestats());
} catch (\Throwable $e) {
Log::error('getStats failed for item '.$id.' ('.$item->class.'): '.$e->getMessage());

return $graceful;
}
}

Expand Down
87 changes: 87 additions & 0 deletions tests/Feature/GetStatsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace Tests\Feature;

use App\Item;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

/**
* Fixture app whose live stats rendering throws, mirroring the Komga
* failure from issue #1558 (broken remote blade / upstream API error).
*/
class ThrowingStatApp
{
public $config;

public function livestats()
{
throw new \Exception('boom');
}
}

/**
* Fixture app whose live stats rendering succeeds and returns the JSON
* string the frontend expects.
*/
class HappyStatApp
{
public $config;

public function livestats()
{
return json_encode(['status' => 'active', 'html' => '<b>ok</b>']);
}
}

class GetStatsTest extends TestCase
{
use RefreshDatabase;

public function test_missing_item_id_does_not_500(): void
{
$response = $this->get('get_stats/999999');

$response->assertStatus(200);
$response->assertJson(['status' => 'inactive', 'html' => '']);
}

public function test_throwing_app_degrades_gracefully(): void
{
$item = Item::factory()->create([
'class' => ThrowingStatApp::class,
]);

$response = $this->get('get_stats/'.$item->id);

$response->assertStatus(200);
$response->assertJson(['status' => 'inactive', 'html' => '']);
}

public function test_item_with_no_class_degrades_gracefully(): void
{
$item = Item::factory()->create([
'class' => null,
]);

$response = $this->get('get_stats/'.$item->id);

$response->assertStatus(200);
$response->assertJson(['status' => 'inactive', 'html' => '']);
}

public function test_happy_path_returns_livestats_output_verbatim(): void
{
$item = Item::factory()->create([
'class' => HappyStatApp::class,
]);

$expected = json_encode(['status' => 'active', 'html' => '<b>ok</b>']);

$response = $this->get('get_stats/'.$item->id);

$response->assertStatus(200);
$this->assertSame($expected, $response->getContent());
$response->assertJson(['status' => 'active', 'html' => '<b>ok</b>']);
}
}
Loading