fix(models): correct UserSetting import namespace and add test coverage

This commit is contained in:
2026-09-07 18:34:51 +02:00
parent 208efbb85d
commit 3502c3e85b
34 changed files with 2497 additions and 2 deletions
@@ -0,0 +1,256 @@
<?php
namespace Tests\Unit\Services;
use App\Features\Scraper\Services\IGDBCoverScraper;
use Illuminate\Support\Facades\Http;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class IGDBCoverScraperTest extends TestCase
{
#[Test]
public function it_returns_empty_when_no_config(): void
{
config(['services.igdb.client_id' => '', 'services.igdb.client_secret' => '']);
$scraper = new IGDBCoverScraper();
$results = $scraper->search('mario');
$this->assertTrue($results->isEmpty());
}
#[Test]
public function it_returns_null_cover_when_no_config(): void
{
config(['services.igdb.client_id' => '', 'services.igdb.client_secret' => '']);
$scraper = new IGDBCoverScraper();
$this->assertNull($scraper->getCoverUrl(123));
}
#[Test]
public function it_returns_null_screenshot_when_no_config(): void
{
config(['services.igdb.client_id' => '', 'services.igdb.client_secret' => '']);
$scraper = new IGDBCoverScraper();
$this->assertNull($scraper->getScreenshotUrl(123));
}
#[Test]
public function it_returns_empty_images_when_no_config(): void
{
config(['services.igdb.client_id' => '', 'services.igdb.client_secret' => '']);
$scraper = new IGDBCoverScraper();
$images = $scraper->getImages(123);
$this->assertEmpty($images);
}
#[Test]
public function it_searches_games_with_valid_config(): void
{
config([
'services.igdb.client_id' => 'test-id',
'services.igdb.client_secret' => 'test-secret',
]);
Http::fake([
'https://id.twitch.tv/oauth2/token' => Http::response(['access_token' => 'fake-token']),
'https://api.igdb.com/v4/games' => Http::response([
[
'id' => 1,
'name' => 'Super Mario Bros',
'cover' => ['url' => 'https://images.igdb.com/igdb/image/upload/thumb/test.jpg'],
'release_dates' => [['year' => 1985]],
],
]),
]);
$scraper = new IGDBCoverScraper();
$results = $scraper->search('mario');
$this->assertCount(1, $results);
$this->assertEquals('Super Mario Bros', $results->first()['title']);
$this->assertEquals('igdb', $results->first()['source']);
}
#[Test]
public function it_searches_games_with_console_id(): void
{
config([
'services.igdb.client_id' => 'test-id',
'services.igdb.client_secret' => 'test-secret',
]);
Http::fake([
'https://id.twitch.tv/oauth2/token' => Http::response(['access_token' => 'fake-token']),
'https://api.igdb.com/v4/games' => Http::response([
[
'id' => 1,
'name' => 'Super Mario Bros',
'cover' => null,
'release_dates' => [],
],
]),
]);
$scraper = new IGDBCoverScraper();
$results = $scraper->search('mario', 21);
$this->assertCount(1, $results);
$this->assertNull($results->first()['cover_url']);
$this->assertNull($results->first()['year']);
}
#[Test]
public function it_returns_empty_on_api_failure(): void
{
config([
'services.igdb.client_id' => 'test-id',
'services.igdb.client_secret' => 'test-secret',
]);
Http::fake([
'https://id.twitch.tv/oauth2/token' => Http::response(['access_token' => 'fake-token']),
'https://api.igdb.com/v4/games' => Http::response([], 500),
]);
$scraper = new IGDBCoverScraper();
$results = $scraper->search('mario');
$this->assertTrue($results->isEmpty());
}
#[Test]
public function it_returns_empty_on_exception(): void
{
config([
'services.igdb.client_id' => 'test-id',
'services.igdb.client_secret' => 'test-secret',
]);
Http::fake([
'https://id.twitch.tv/oauth2/token' => Http::response(['access_token' => 'fake-token']),
'https://api.igdb.com/v4/games' => fn () => throw new \Exception('Network error'),
]);
$scraper = new IGDBCoverScraper();
$results = $scraper->search('mario');
$this->assertTrue($results->isEmpty());
}
#[Test]
public function it_gets_cover_url(): void
{
config([
'services.igdb.client_id' => 'test-id',
'services.igdb.client_secret' => 'test-secret',
]);
Http::fake([
'https://id.twitch.tv/oauth2/token' => Http::response(['access_token' => 'fake-token']),
'https://api.igdb.com/v4/covers' => Http::response([
['url' => 'https://images.igdb.com/igdb/image/upload/thumb/test.jpg'],
]),
]);
$scraper = new IGDBCoverScraper();
$url = $scraper->getCoverUrl(123);
$this->assertNotNull($url);
$this->assertStringContainsString('cover_big', $url);
}
#[Test]
public function it_returns_null_cover_when_empty_response(): void
{
config([
'services.igdb.client_id' => 'test-id',
'services.igdb.client_secret' => 'test-secret',
]);
Http::fake([
'https://id.twitch.tv/oauth2/token' => Http::response(['access_token' => 'fake-token']),
'https://api.igdb.com/v4/covers' => Http::response([]),
]);
$scraper = new IGDBCoverScraper();
$url = $scraper->getCoverUrl(123);
$this->assertNull($url);
}
#[Test]
public function it_gets_screenshot_url(): void
{
config([
'services.igdb.client_id' => 'test-id',
'services.igdb.client_secret' => 'test-secret',
]);
Http::fake([
'https://id.twitch.tv/oauth2/token' => Http::response(['access_token' => 'fake-token']),
'https://api.igdb.com/v4/screenshots' => Http::response([
['url' => 'https://images.igdb.com/igdb/image/upload/thumb/screenshot.jpg'],
]),
]);
$scraper = new IGDBCoverScraper();
$url = $scraper->getScreenshotUrl(123);
$this->assertNotNull($url);
}
#[Test]
public function it_returns_null_screenshot_when_empty(): void
{
config([
'services.igdb.client_id' => 'test-id',
'services.igdb.client_secret' => 'test-secret',
]);
Http::fake([
'https://id.twitch.tv/oauth2/token' => Http::response(['access_token' => 'fake-token']),
'https://api.igdb.com/v4/screenshots' => Http::response([]),
]);
$scraper = new IGDBCoverScraper();
$url = $scraper->getScreenshotUrl(123);
$this->assertNull($url);
}
#[Test]
public function it_gets_images(): void
{
config([
'services.igdb.client_id' => 'test-id',
'services.igdb.client_secret' => 'test-secret',
]);
Http::fake([
'https://id.twitch.tv/oauth2/token' => Http::response(['access_token' => 'fake-token']),
'https://api.igdb.com/v4/covers' => Http::response([
['url' => 'https://images.igdb.com/igdb/image/upload/thumb/test.jpg'],
]),
'https://api.igdb.com/v4/screenshots' => Http::response([
['url' => 'https://images.igdb.com/igdb/image/upload/thumb/screenshot.jpg'],
]),
]);
$scraper = new IGDBCoverScraper();
$images = $scraper->getImages(123);
$this->assertCount(2, $images);
$this->assertArrayHasKey('cover', $images);
$this->assertArrayHasKey('screenshot', $images);
}
}
+201
View File
@@ -0,0 +1,201 @@
<?php
namespace Tests\Unit\Services;
use App\Features\Consoles\Models\Console;
use App\Features\Games\Models\Game;
use App\Features\Scraper\Services\IGDBCoverScraper;
use App\Features\Scraper\Services\ScraperService;
use App\Features\Scraper\Services\TGDBCoverScraper;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class ScraperServiceTest extends TestCase
{
private IGDBCoverScraper|MockObject $igdb;
private TGDBCoverScraper|MockObject $tgdb;
private ScraperService $service;
protected function setUp(): void
{
parent::setUp();
$this->igdb = $this->createMock(IGDBCoverScraper::class);
$this->tgdb = $this->createMock(TGDBCoverScraper::class);
$this->service = new ScraperService($this->igdb, $this->tgdb);
}
#[Test]
public function it_searches_with_igdb_first(): void
{
$console = Console::factory()->create(['igdb_platform_id' => 21]);
$expected = collect([['title' => 'Game']]);
$this->igdb->expects($this->once())
->method('search')
->with('mario', 21)
->willReturn($expected);
$results = $this->service->search('mario', $console->slug);
$this->assertEquals($expected, $results);
}
#[Test]
public function it_falls_back_to_tgdb_when_igdb_returns_empty(): void
{
$console = Console::factory()->create([
'igdb_platform_id' => 21,
'tgdb_platform_id' => 42,
]);
$expected = collect([['title' => 'Game']]);
$this->igdb->expects($this->once())
->method('search')
->willReturn(collect());
$this->tgdb->expects($this->once())
->method('search')
->with('mario', 42)
->willReturn($expected);
$results = $this->service->search('mario', $console->slug);
$this->assertEquals($expected, $results);
}
#[Test]
public function it_searches_without_console(): void
{
$expected = collect([['title' => 'Game']]);
$this->igdb->expects($this->once())
->method('search')
->with('mario', null)
->willReturn($expected);
$results = $this->service->search('mario');
$this->assertEquals($expected, $results);
}
#[Test]
public function it_scrapes_cover_for_game_with_igdb_id(): void
{
$game = Game::factory()->create(['igdb_id' => 123, 'tgdb_id' => null]);
$game->load('console');
$this->igdb->expects($this->once())
->method('getCoverUrl')
->with(123)
->willReturn('https://example.com/cover.jpg');
$this->tgdb->expects($this->never())
->method('getCoverUrl');
Http::fake([
'https://example.com/cover.jpg' => Http::response('image-data'),
]);
Storage::fake('local');
$result = $this->service->scrapeForGame($game);
$this->assertNotNull($result);
}
#[Test]
public function it_falls_back_to_tgdb_for_cover(): void
{
$game = Game::factory()->create(['igdb_id' => 123, 'tgdb_id' => 456]);
$game->load('console');
$this->igdb->expects($this->once())
->method('getCoverUrl')
->with(123)
->willReturn(null);
$this->tgdb->expects($this->once())
->method('getCoverUrl')
->with(456)
->willReturn('https://example.com/tgdb-cover.jpg');
Http::fake([
'https://example.com/tgdb-cover.jpg' => Http::response('image-data'),
]);
Storage::fake('local');
$result = $this->service->scrapeForGame($game);
$this->assertNotNull($result);
}
#[Test]
public function it_returns_null_when_no_cover_found(): void
{
$game = Game::factory()->create(['igdb_id' => 123, 'tgdb_id' => null]);
$game->load('console');
$this->igdb->expects($this->once())
->method('getCoverUrl')
->willReturn(null);
$this->tgdb->expects($this->never())
->method('getCoverUrl');
$result = $this->service->scrapeForGame($game);
$this->assertNull($result);
}
#[Test]
public function it_downloads_cover(): void
{
$game = Game::factory()->create(['slug' => 'super-mario']);
$game->setRelation('console', Console::factory()->create(['slug' => 'nes']));
Http::fake([
'https://example.com/cover.jpg' => Http::response('image-data'),
]);
Storage::fake('local');
$path = $this->service->downloadCover('https://example.com/cover.jpg', $game);
$this->assertEquals('covers/nes/super-mario-cover.jpg', $path);
Storage::disk('local')->assertExists('covers/nes/super-mario-cover.jpg');
}
#[Test]
public function it_returns_empty_string_on_download_failure(): void
{
$game = Game::factory()->create(['slug' => 'super-mario']);
$game->setRelation('console', Console::factory()->create(['slug' => 'nes']));
Http::fake([
'https://example.com/cover.jpg' => fn () => throw new \Exception('Network error'),
]);
Storage::fake('local');
$path = $this->service->downloadCover('https://example.com/cover.jpg', $game);
$this->assertEquals('', $path);
}
#[Test]
public function it_downloads_cover_with_unknown_console(): void
{
$game = Game::factory()->create(['slug' => 'test-game']);
$game->setRelation('console', null);
Http::fake([
'https://example.com/cover.jpg' => Http::response('image-data'),
]);
Storage::fake('local');
$path = $this->service->downloadCover('https://example.com/cover.jpg', $game);
$this->assertEquals('covers/unknown/test-game-cover.jpg', $path);
}
}
@@ -0,0 +1,319 @@
<?php
namespace Tests\Unit\Services;
use App\Features\Scraper\Services\TGDBCoverScraper;
use Illuminate\Support\Facades\Http;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class TGDBCoverScraperTest extends TestCase
{
#[Test]
public function it_returns_empty_when_no_config(): void
{
config(['services.thegamesdb.api_key' => '']);
$scraper = new TGDBCoverScraper();
$results = $scraper->search('mario');
$this->assertTrue($results->isEmpty());
}
#[Test]
public function it_returns_null_cover_when_no_config(): void
{
config(['services.thegamesdb.api_key' => '']);
$scraper = new TGDBCoverScraper();
$this->assertNull($scraper->getCoverUrl(123));
}
#[Test]
public function it_returns_null_screenshot_when_no_config(): void
{
config(['services.thegamesdb.api_key' => '']);
$scraper = new TGDBCoverScraper();
$this->assertNull($scraper->getScreenshotUrl(123));
}
#[Test]
public function it_returns_empty_images_when_no_config(): void
{
config(['services.thegamesdb.api_key' => '']);
$scraper = new TGDBCoverScraper();
$images = $scraper->getImages(123);
$this->assertEmpty($images);
}
#[Test]
public function it_searches_games(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/ByGameName*' => Http::response([
'data' => [
'games' => [
[
'id' => 1,
'game_title' => 'Super Mario Bros',
'cover' => 'https://example.com/cover.jpg',
'release_date' => '1985-09-13',
],
],
],
]),
]);
$scraper = new TGDBCoverScraper();
$results = $scraper->search('mario');
$this->assertCount(1, $results);
$this->assertEquals('Super Mario Bros', $results->first()['title']);
$this->assertEquals('thegamesdb', $results->first()['source']);
$this->assertEquals('1985', $results->first()['year']);
}
#[Test]
public function it_searches_games_with_console_id(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/ByGameName*' => Http::response([
'data' => ['games' => []],
]),
]);
$scraper = new TGDBCoverScraper();
$results = $scraper->search('mario', 21);
$this->assertTrue($results->isEmpty());
}
#[Test]
public function it_searches_games_without_release_date(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/ByGameName*' => Http::response([
'data' => [
'games' => [
[
'id' => 1,
'game_title' => 'Test Game',
'cover' => null,
],
],
],
]),
]);
$scraper = new TGDBCoverScraper();
$results = $scraper->search('test');
$this->assertCount(1, $results);
$this->assertNull($results->first()['year']);
$this->assertNull($results->first()['cover_url']);
}
#[Test]
public function it_returns_empty_on_api_failure(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/ByGameName*' => Http::response([], 500),
]);
$scraper = new TGDBCoverScraper();
$results = $scraper->search('mario');
$this->assertTrue($results->isEmpty());
}
#[Test]
public function it_returns_empty_on_exception(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/ByGameName*' => fn () => throw new \Exception('Network error'),
]);
$scraper = new TGDBCoverScraper();
$results = $scraper->search('mario');
$this->assertTrue($results->isEmpty());
}
#[Test]
public function it_gets_cover_url(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/Images*' => Http::response([
'data' => [
'images' => [
['filename' => 'cover.jpg'],
],
],
]),
]);
$scraper = new TGDBCoverScraper();
$url = $scraper->getCoverUrl(123);
$this->assertNotNull($url);
$this->assertStringContainsString('cover.jpg', $url);
}
#[Test]
public function it_returns_null_cover_when_empty(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/Images*' => Http::response([
'data' => ['images' => []],
]),
]);
$scraper = new TGDBCoverScraper();
$url = $scraper->getCoverUrl(123);
$this->assertNull($url);
}
#[Test]
public function it_returns_null_cover_on_api_failure(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/Images*' => Http::response([], 500),
]);
$scraper = new TGDBCoverScraper();
$url = $scraper->getCoverUrl(123);
$this->assertNull($url);
}
#[Test]
public function it_returns_null_cover_on_exception(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/Images*' => fn () => throw new \Exception('Network error'),
]);
$scraper = new TGDBCoverScraper();
$url = $scraper->getCoverUrl(123);
$this->assertNull($url);
}
#[Test]
public function it_gets_screenshot_url(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/Images*' => Http::response([
'data' => [
'images' => [
['filename' => 'screenshot.jpg'],
],
],
]),
]);
$scraper = new TGDBCoverScraper();
$url = $scraper->getScreenshotUrl(123);
$this->assertNotNull($url);
$this->assertStringContainsString('screenshot.jpg', $url);
}
#[Test]
public function it_returns_null_screenshot_when_empty(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/Images*' => Http::response([
'data' => ['images' => []],
]),
]);
$scraper = new TGDBCoverScraper();
$url = $scraper->getScreenshotUrl(123);
$this->assertNull($url);
}
#[Test]
public function it_returns_null_screenshot_on_api_failure(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/Images*' => Http::response([], 500),
]);
$scraper = new TGDBCoverScraper();
$url = $scraper->getScreenshotUrl(123);
$this->assertNull($url);
}
#[Test]
public function it_returns_null_screenshot_on_exception(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/Images*' => fn () => throw new \Exception('Network error'),
]);
$scraper = new TGDBCoverScraper();
$url = $scraper->getScreenshotUrl(123);
$this->assertNull($url);
}
#[Test]
public function it_gets_images(): void
{
config(['services.thegamesdb.api_key' => 'test-key']);
Http::fake([
'*/Games/Images*' => Http::response([
'data' => [
'images' => [
['filename' => 'boxart.jpg'],
],
],
]),
]);
$scraper = new TGDBCoverScraper();
$images = $scraper->getImages(123);
$this->assertCount(2, $images);
$this->assertArrayHasKey('cover', $images);
$this->assertArrayHasKey('screenshot', $images);
}
}