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
+69
View File
@@ -0,0 +1,69 @@
<?php
namespace Tests\Unit\Data;
use App\Features\Games\Data\RomFileData;
use App\Features\Games\Models\RomFile;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class RomFileDataTest extends TestCase
{
#[Test]
public function it_can_be_created_from_array(): void
{
$data = RomFileData::from([
'id' => 1,
'game_id' => 1,
'file_path' => 'roms/nes/game.nes',
'file_name' => 'game.nes',
'file_size' => 102400,
'file_hash' => 'abc123',
'region' => 'USA',
'language' => 'en',
]);
$this->assertEquals(1, $data->id);
$this->assertEquals(1, $data->gameId);
$this->assertEquals('roms/nes/game.nes', $data->filePath);
$this->assertEquals('game.nes', $data->fileName);
$this->assertEquals(102400, $data->fileSize);
$this->assertEquals('abc123', $data->fileHash);
$this->assertEquals('USA', $data->region);
$this->assertEquals('en', $data->language);
}
#[Test]
public function it_can_be_created_from_model(): void
{
$romFile = RomFile::factory()->create([
'region' => 'Europe',
'language' => 'de',
]);
$data = RomFileData::fromModel($romFile);
$this->assertEquals($romFile->id, $data->id);
$this->assertEquals($romFile->game_id, $data->gameId);
$this->assertEquals($romFile->file_path, $data->filePath);
$this->assertEquals($romFile->file_name, $data->fileName);
$this->assertEquals($romFile->file_size, $data->fileSize);
$this->assertEquals($romFile->file_hash, $data->fileHash);
$this->assertEquals('Europe', $data->region);
$this->assertEquals('de', $data->language);
}
#[Test]
public function it_handles_null_optional_fields(): void
{
$romFile = RomFile::factory()->create([
'region' => null,
'language' => null,
]);
$data = RomFileData::fromModel($romFile);
$this->assertNull($data->region);
$this->assertNull($data->language);
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace Tests\Unit\Data;
use App\Features\Settings\Data\SaveFileData;
use App\Features\Settings\Models\SaveFile;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class SaveFileDataTest extends TestCase
{
#[Test]
public function it_can_be_created_from_array(): void
{
$data = SaveFileData::from([
'id' => 1,
'user_id' => 1,
'game_id' => 1,
'file_path' => 'saves/test.srm',
'file_name' => 'test.srm',
'file_size' => 1024,
'last_synced_at' => null,
]);
$this->assertEquals(1, $data->id);
$this->assertEquals(1, $data->userId);
$this->assertEquals(1, $data->gameId);
$this->assertEquals('saves/test.srm', $data->filePath);
$this->assertEquals('test.srm', $data->fileName);
$this->assertEquals(1024, $data->fileSize);
$this->assertNull($data->lastSyncedAt);
}
#[Test]
public function it_can_be_created_from_model(): void
{
$saveFile = SaveFile::factory()->create([
'last_synced_at' => '2024-01-15 10:30:00',
]);
$data = SaveFileData::fromModel($saveFile);
$this->assertEquals($saveFile->id, $data->id);
$this->assertEquals($saveFile->user_id, $data->userId);
$this->assertEquals($saveFile->game_id, $data->gameId);
$this->assertEquals($saveFile->file_path, $data->filePath);
$this->assertEquals($saveFile->file_name, $data->fileName);
$this->assertEquals($saveFile->file_size, $data->fileSize);
$this->assertNotNull($data->lastSyncedAt);
}
#[Test]
public function it_handles_null_last_synced_at(): void
{
$saveFile = SaveFile::factory()->create([
'last_synced_at' => null,
]);
$data = SaveFileData::fromModel($saveFile);
$this->assertNull($data->lastSyncedAt);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace Tests\Unit\Data;
use App\Features\Scraper\Data\ScraperResultData;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class ScraperResultDataTest extends TestCase
{
#[Test]
public function it_can_be_created_from_array(): void
{
$data = ScraperResultData::from([
'id' => 123,
'title' => 'Super Mario Bros',
'cover_url' => 'https://example.com/cover.jpg',
'year' => '1985',
'source' => 'igdb',
]);
$this->assertEquals(123, $data->id);
$this->assertEquals('Super Mario Bros', $data->title);
$this->assertEquals('https://example.com/cover.jpg', $data->coverUrl);
$this->assertEquals('1985', $data->year);
$this->assertEquals('igdb', $data->source);
}
#[Test]
public function it_handles_null_optional_fields(): void
{
$data = ScraperResultData::from([
'id' => 1,
'title' => 'Test Game',
'cover_url' => null,
'year' => null,
'source' => 'thegamesdb',
]);
$this->assertNull($data->coverUrl);
$this->assertNull($data->year);
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace Tests\Unit\Data;
use App\Features\WebDav\Data\WebDavAccountData;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class WebDavAccountDataTest extends TestCase
{
#[Test]
public function it_can_be_created_from_array(): void
{
$data = WebDavAccountData::from([
'id' => 1,
'user_id' => 1,
'username' => 'testuser',
'display_name' => 'Test User',
'enabled' => true,
]);
$this->assertEquals(1, $data->id);
$this->assertEquals(1, $data->userId);
$this->assertEquals('testuser', $data->username);
$this->assertEquals('Test User', $data->displayName);
$this->assertTrue($data->enabled);
}
#[Test]
public function it_handles_disabled_account(): void
{
$data = WebDavAccountData::from([
'id' => 2,
'user_id' => 1,
'username' => 'disableduser',
'display_name' => 'Disabled User',
'enabled' => false,
]);
$this->assertFalse($data->enabled);
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
namespace Tests\Unit\Models;
use App\Features\Consoles\Models\Console;
use App\Features\Games\Models\Game;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class ConsoleTest extends TestCase
{
use RefreshDatabase;
#[Test]
public function it_has_many_games(): void
{
$console = Console::factory()->create();
Game::factory()->count(3)->create(['console_id' => $console->id]);
$this->assertCount(3, $console->games);
}
#[Test]
public function it_uses_slug_as_route_key(): void
{
$console = new Console();
$this->assertEquals('slug', $console->getRouteKeyName());
}
#[Test]
public function it_casts_rom_extensions_to_array(): void
{
$console = Console::factory()->create(['rom_extensions' => ['nes', 'sms']]);
$this->assertIsArray($console->rom_extensions);
$this->assertEquals(['nes', 'sms'], $console->rom_extensions);
}
#[Test]
public function it_has_fillable_attributes(): void
{
$console = new Console();
$this->assertEquals([
'name',
'slug',
'icon_path',
'rom_extensions',
'igdb_platform_id',
'tgdb_platform_id',
], $console->getFillable());
}
}
+102
View File
@@ -0,0 +1,102 @@
<?php
namespace Tests\Unit\Models;
use App\Features\Consoles\Models\Console;
use App\Features\Games\Models\Game;
use App\Features\Games\Models\RomFile;
use App\Features\Settings\Models\SaveFile;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class GameTest extends TestCase
{
use RefreshDatabase;
#[Test]
public function it_belongs_to_a_console(): void
{
$game = Game::factory()->create();
$this->assertInstanceOf(Console::class, $game->console);
}
#[Test]
public function it_has_many_rom_files(): void
{
$game = Game::factory()->create();
RomFile::factory()->count(3)->create(['game_id' => $game->id]);
$this->assertCount(3, $game->romFiles);
}
#[Test]
public function it_has_many_save_files(): void
{
$game = Game::factory()->create();
SaveFile::factory()->count(2)->create(['game_id' => $game->id]);
$this->assertCount(2, $game->saveFiles);
}
#[Test]
public function it_uses_slug_as_route_key(): void
{
$game = new Game();
$this->assertEquals('slug', $game->getRouteKeyName());
}
#[Test]
public function it_auto_generates_slug_from_title_on_creating(): void
{
$game = Game::factory()->create([
'title' => 'Super Mario Bros',
'slug' => null,
]);
$this->assertEquals('super-mario-bros', $game->slug);
}
#[Test]
public function it_keeps_existing_slug(): void
{
$game = Game::factory()->create([
'title' => 'Super Mario Bros',
'slug' => 'custom-slug',
]);
$this->assertEquals('custom-slug', $game->slug);
}
#[Test]
public function it_casts_rating_to_decimal(): void
{
$game = Game::factory()->create(['rating' => 8.5]);
$this->assertEquals('8.5', (string) $game->rating);
}
#[Test]
public function it_has_fillable_attributes(): void
{
$game = new Game();
$this->assertEquals([
'console_id',
'title',
'slug',
'igdb_id',
'tgdb_id',
'description',
'release_year',
'developer',
'publisher',
'genre',
'rating',
'cover_path',
'thumbnail_path',
], $game->getFillable());
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace Tests\Unit\Models;
use App\Features\Games\Models\Game;
use App\Features\Games\Models\RomFile;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class RomFileTest extends TestCase
{
use RefreshDatabase;
#[Test]
public function it_belongs_to_a_game(): void
{
$romFile = RomFile::factory()->create();
$this->assertInstanceOf(Game::class, $romFile->game);
}
#[Test]
public function it_has_fillable_attributes(): void
{
$romFile = new RomFile();
$this->assertEquals([
'game_id',
'file_path',
'file_name',
'file_size',
'file_hash',
'region',
'language',
], $romFile->getFillable());
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace Tests\Unit\Models;
use App\Features\Games\Models\Game;
use App\Features\Settings\Models\SaveFile;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class SaveFileTest extends TestCase
{
use RefreshDatabase;
#[Test]
public function it_belongs_to_a_user(): void
{
$saveFile = SaveFile::factory()->create();
$this->assertInstanceOf(User::class, $saveFile->user);
}
#[Test]
public function it_belongs_to_a_game(): void
{
$saveFile = SaveFile::factory()->create();
$this->assertInstanceOf(Game::class, $saveFile->game);
}
#[Test]
public function it_casts_last_synced_at_to_datetime(): void
{
$saveFile = SaveFile::factory()->create([
'last_synced_at' => '2024-01-15 10:30:00',
]);
$this->assertInstanceOf(\Illuminate\Support\Carbon::class, $saveFile->last_synced_at);
}
#[Test]
public function it_has_fillable_attributes(): void
{
$saveFile = new SaveFile();
$this->assertEquals([
'user_id',
'game_id',
'file_path',
'file_name',
'file_size',
'last_synced_at',
], $saveFile->getFillable());
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace Tests\Unit\Models;
use App\Features\Settings\Models\UserSetting;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class UserSettingTest extends TestCase
{
use RefreshDatabase;
#[Test]
public function it_belongs_to_a_user(): void
{
$setting = UserSetting::factory()->create();
$this->assertInstanceOf(User::class, $setting->user);
}
#[Test]
public function it_has_fillable_attributes(): void
{
$setting = new UserSetting();
$this->assertEquals([
'user_id',
'setting_key',
'setting_value',
], $setting->getFillable());
}
}
+129
View File
@@ -0,0 +1,129 @@
<?php
namespace Tests\Unit\Models;
use App\Features\Settings\Models\SaveFile;
use App\Features\Settings\Models\UserSetting;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class UserTest extends TestCase
{
use RefreshDatabase;
#[Test]
public function it_has_many_save_files(): void
{
$user = User::factory()->create();
SaveFile::factory()->count(3)->create(['user_id' => $user->id]);
$this->assertCount(3, $user->saveFiles);
}
#[Test]
public function it_has_many_settings(): void
{
$user = User::factory()->create();
UserSetting::factory()->count(2)->create(['user_id' => $user->id]);
$this->assertCount(2, $user->settings);
}
#[Test]
public function it_gets_setting_by_key(): void
{
$user = User::factory()->create();
UserSetting::factory()->create([
'user_id' => $user->id,
'setting_key' => 'theme',
'setting_value' => 'dark',
]);
$this->assertEquals('dark', $user->getSetting('theme'));
}
#[Test]
public function it_returns_default_when_setting_not_found(): void
{
$user = User::factory()->create();
$this->assertEquals('default', $user->getSetting('nonexistent', 'default'));
}
#[Test]
public function it_returns_null_when_setting_not_found_without_default(): void
{
$user = User::factory()->create();
$this->assertNull($user->getSetting('nonexistent'));
}
#[Test]
public function it_sets_setting(): void
{
$user = User::factory()->create();
$user->setSetting('theme', 'dark');
$this->assertDatabaseHas('user_settings', [
'user_id' => $user->id,
'setting_key' => 'theme',
'setting_value' => 'dark',
]);
}
#[Test]
public function it_updates_existing_setting(): void
{
$user = User::factory()->create();
UserSetting::factory()->create([
'user_id' => $user->id,
'setting_key' => 'theme',
'setting_value' => 'light',
]);
$user->setSetting('theme', 'dark');
$this->assertDatabaseHas('user_settings', [
'user_id' => $user->id,
'setting_key' => 'theme',
'setting_value' => 'dark',
]);
$this->assertDatabaseCount('user_settings', 1);
}
#[Test]
public function it_casts_email_verified_at_to_datetime(): void
{
$user = User::factory()->create(['email_verified_at' => '2024-01-15 10:30:00']);
$this->assertInstanceOf(\Illuminate\Support\Carbon::class, $user->email_verified_at);
}
#[Test]
public function it_hashes_password(): void
{
$user = User::factory()->create(['password' => 'test-password']);
$this->assertNotEquals('test-password', $user->password);
$this->assertTrue(\Hash::check('test-password', $user->password));
}
#[Test]
public function it_has_fillable_attributes(): void
{
$user = new User();
$this->assertEquals(['name', 'email', 'password'], $user->getFillable());
}
#[Test]
public function it_has_hidden_attributes(): void
{
$user = new User();
$this->assertEquals(['password', 'remember_token'], $user->getHidden());
}
}
@@ -0,0 +1,28 @@
<?php
namespace Tests\Unit\Providers;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Gate;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class HorizonServiceProviderTest extends TestCase
{
use RefreshDatabase;
#[Test]
public function it_defines_view_horizon_gate(): void
{
$this->assertTrue(Gate::has('viewHorizon'));
}
#[Test]
public function it_denies_access_for_users_not_in_list(): void
{
$user = User::factory()->create();
$this->assertFalse(Gate::forUser($user)->allows('viewHorizon'));
}
}
@@ -0,0 +1,51 @@
<?php
namespace Tests\Unit\Providers;
use App\Providers\TypeScriptTransformerServiceProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
use Spatie\LaravelTypeScriptTransformer\TypeScriptTransformerApplicationServiceProvider as BaseTypeScriptTransformerServiceProvider;
use Spatie\TypeScriptTransformer\TypeScriptTransformerConfigFactory;
use Tests\TestCase;
class TypeScriptTransformerServiceProviderTest extends TestCase
{
#[Test]
public function it_is_a_service_provider(): void
{
$provider = new TypeScriptTransformerServiceProvider($this->app);
$this->assertInstanceOf(BaseTypeScriptTransformerServiceProvider::class, $provider);
}
#[Test]
public function it_configures_transformers(): void
{
$config = $this->createMock(TypeScriptTransformerConfigFactory::class);
$config->expects($this->exactly(3))
->method('transformer')
->willReturnSelf();
$config->expects($this->once())
->method('transformDirectories')
->willReturnSelf();
$config->expects($this->once())
->method('writer')
->willReturnSelf();
$config->expects($this->once())
->method('formatter')
->willReturnSelf();
$provider = new TypeScriptTransformerServiceProvider($this->app);
$reflection = new \ReflectionClass($provider);
$method = $reflection->getMethod('configure');
$method->setAccessible(true);
$method->invoke($provider, $config);
}
}
@@ -0,0 +1,64 @@
<?php
namespace Tests\Unit\Repositories;
use App\Features\Consoles\Models\Console;
use App\Features\Consoles\Repositories\ConsoleRepository;
use App\Features\Games\Models\Game;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class ConsoleRepositoryTest extends TestCase
{
use RefreshDatabase;
private ConsoleRepository $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = new ConsoleRepository(new Console());
}
#[Test]
public function it_gets_all_consoles_with_games_count(): void
{
Console::factory()->count(3)->create();
$consoles = $this->repository->getAll();
$this->assertCount(3, $consoles);
}
#[Test]
public function it_gets_console_by_slug(): void
{
$console = Console::factory()->create(['slug' => 'nes']);
$found = $this->repository->getBySlug('nes');
$this->assertNotNull($found);
$this->assertEquals($console->id, $found->id);
}
#[Test]
public function it_returns_null_for_nonexistent_slug(): void
{
$found = $this->repository->getBySlug('nonexistent');
$this->assertNull($found);
}
#[Test]
public function it_gets_consoles_with_games_count(): void
{
$console = Console::factory()->create();
Game::factory()->count(5)->create(['console_id' => $console->id]);
$consoles = $this->repository->getWithGamesCount();
$this->assertCount(1, $consoles);
$this->assertEquals(5, $consoles->first()->games_count);
}
}
@@ -0,0 +1,126 @@
<?php
namespace Tests\Unit\Repositories;
use App\Features\Consoles\Models\Console;
use App\Features\Games\Models\Game;
use App\Features\Games\Repositories\GameRepository;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class GameRepositoryTest extends TestCase
{
use RefreshDatabase;
private GameRepository $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = new GameRepository(new Game());
}
#[Test]
public function it_gets_games_by_console(): void
{
$console = Console::factory()->create();
Game::factory()->count(5)->create(['console_id' => $console->id]);
Game::factory()->count(3)->create();
$games = $this->repository->getByConsole($console->id);
$this->assertCount(5, $games);
}
#[Test]
public function it_gets_game_by_slug(): void
{
$game = Game::factory()->create(['slug' => 'super-mario']);
$found = $this->repository->getBySlug('super-mario');
$this->assertNotNull($found);
$this->assertEquals($game->id, $found->id);
}
#[Test]
public function it_returns_null_for_nonexistent_slug(): void
{
$found = $this->repository->getBySlug('nonexistent');
$this->assertNull($found);
}
#[Test]
public function it_gets_recent_games(): void
{
Game::factory()->count(5)->create();
$recent = $this->repository->getRecent(3);
$this->assertCount(3, $recent);
}
#[Test]
public function it_searches_games_by_title(): void
{
Game::factory()->create(['title' => 'Super Mario Bros']);
Game::factory()->create(['title' => 'Super Mario World']);
Game::factory()->create(['title' => 'Sonic the Hedgehog']);
$results = $this->repository->search('Mario');
$this->assertCount(2, $results);
}
#[Test]
public function it_gets_all_games_paginated(): void
{
Game::factory()->count(30)->create();
$all = $this->repository->getAll();
$this->assertCount(24, $all);
$this->assertEquals(30, $all->total());
}
#[Test]
public function it_creates_a_game(): void
{
$game = $this->repository->create([
'console_id' => Console::factory()->create()->id,
'title' => 'New Game',
'slug' => 'new-game',
]);
$this->assertDatabaseHas('games', ['slug' => 'new-game']);
}
#[Test]
public function it_creates_game_first_or_creates(): void
{
$console = Console::factory()->create();
$game1 = $this->repository->firstOrCreate(
['console_id' => $console->id, 'slug' => 'test-game'],
['title' => 'Test Game']
);
$game2 = $this->repository->firstOrCreate(
['console_id' => $console->id, 'slug' => 'test-game'],
['title' => 'Different Title']
);
$this->assertEquals($game1->id, $game2->id);
$this->assertEquals('Test Game', $game2->title);
}
#[Test]
public function it_counts_games(): void
{
Game::factory()->count(5)->create();
$this->assertEquals(5, $this->repository->count());
}
}
@@ -0,0 +1,90 @@
<?php
namespace Tests\Unit\Repositories;
use App\Features\Consoles\Models\Console;
use App\Features\Games\Models\Game;
use App\Features\Games\Models\RomFile;
use App\Features\Games\Repositories\RomFileRepository;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class RomFileRepositoryTest extends TestCase
{
use RefreshDatabase;
private RomFileRepository $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = new RomFileRepository(new RomFile());
}
#[Test]
public function it_counts_rom_files(): void
{
$console = Console::factory()->create();
$game = Game::factory()->create(['console_id' => $console->id]);
RomFile::factory()->count(5)->create(['game_id' => $game->id]);
$this->assertEquals(5, $this->repository->count());
}
#[Test]
public function it_finds_by_hash(): void
{
$console = Console::factory()->create();
$game = Game::factory()->create(['console_id' => $console->id]);
$romFile = RomFile::factory()->create([
'game_id' => $game->id,
'file_hash' => 'abc123',
]);
$found = $this->repository->findByHash('abc123');
$this->assertNotNull($found);
$this->assertEquals($romFile->id, $found->id);
}
#[Test]
public function it_returns_null_for_nonexistent_hash(): void
{
$found = $this->repository->findByHash('nonexistent');
$this->assertNull($found);
}
#[Test]
public function it_creates_a_rom_file(): void
{
$console = Console::factory()->create();
$game = Game::factory()->create(['console_id' => $console->id]);
$romFile = $this->repository->create([
'game_id' => $game->id,
'file_path' => 'roms/nes/game.nes',
'file_name' => 'game.nes',
'file_size' => 102400,
'file_hash' => 'abc123',
]);
$this->assertDatabaseHas('rom_files', ['file_hash' => 'abc123']);
}
#[Test]
public function it_gets_rom_files_by_game(): void
{
$console = Console::factory()->create();
$game = Game::factory()->create(['console_id' => $console->id]);
RomFile::factory()->count(3)->create(['game_id' => $game->id]);
$otherGame = Game::factory()->create(['console_id' => $console->id]);
RomFile::factory()->count(2)->create(['game_id' => $otherGame->id]);
$romFiles = $this->repository->getByGame($game->id);
$this->assertCount(3, $romFiles);
}
}
@@ -0,0 +1,120 @@
<?php
namespace Tests\Unit\Repositories;
use App\Features\Settings\Models\SaveFile;
use App\Features\Settings\Models\UserSetting;
use App\Features\Settings\Repositories\SettingsRepository;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class SettingsRepositoryTest extends TestCase
{
use RefreshDatabase;
private SettingsRepository $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = new SettingsRepository(
new SaveFile(),
new UserSetting()
);
}
#[Test]
public function it_gets_user_saves(): void
{
$user = User::factory()->create();
SaveFile::factory()->count(3)->create(['user_id' => $user->id]);
SaveFile::factory()->count(2)->create();
$saves = $this->repository->getUserSaves($user);
$this->assertCount(3, $saves);
}
#[Test]
public function it_gets_user_settings(): void
{
$user = User::factory()->create();
UserSetting::factory()->count(2)->create(['user_id' => $user->id]);
UserSetting::factory()->count(3)->create();
$settings = $this->repository->getUserSettings($user);
$this->assertCount(2, $settings);
}
#[Test]
public function it_gets_a_setting_by_key(): void
{
$user = User::factory()->create();
UserSetting::factory()->create([
'user_id' => $user->id,
'setting_key' => 'theme',
'setting_value' => 'dark',
]);
$value = $this->repository->getSetting($user, 'theme');
$this->assertEquals('dark', $value);
}
#[Test]
public function it_returns_default_when_setting_not_found(): void
{
$user = User::factory()->create();
$value = $this->repository->getSetting($user, 'nonexistent', 'default_value');
$this->assertEquals('default_value', $value);
}
#[Test]
public function it_returns_null_when_setting_not_found_without_default(): void
{
$user = User::factory()->create();
$value = $this->repository->getSetting($user, 'nonexistent');
$this->assertNull($value);
}
#[Test]
public function it_creates_a_setting(): void
{
$user = User::factory()->create();
$this->repository->setSetting($user, 'theme', 'dark');
$this->assertDatabaseHas('user_settings', [
'user_id' => $user->id,
'setting_key' => 'theme',
'setting_value' => 'dark',
]);
}
#[Test]
public function it_updates_an_existing_setting(): void
{
$user = User::factory()->create();
UserSetting::factory()->create([
'user_id' => $user->id,
'setting_key' => 'theme',
'setting_value' => 'light',
]);
$this->repository->setSetting($user, 'theme', 'dark');
$this->assertDatabaseHas('user_settings', [
'user_id' => $user->id,
'setting_key' => 'theme',
'setting_value' => 'dark',
]);
$this->assertDatabaseCount('user_settings', 1);
}
}
@@ -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);
}
}