fix(models): correct UserSetting import namespace and add test coverage
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class EmailNotificationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_verified_user_is_redirected(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('verification.send'))
|
||||
->assertRedirect(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
public function test_unverified_user_gets_notification(): void
|
||||
{
|
||||
$user = User::factory()->unverified()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('verification.send'))
|
||||
->assertSessionHas('status', 'verification-link-sent');
|
||||
}
|
||||
}
|
||||
@@ -55,4 +55,13 @@ class EmailVerificationTest extends TestCase
|
||||
|
||||
$this->assertFalse($user->fresh()->hasVerifiedEmail());
|
||||
}
|
||||
|
||||
public function test_verified_user_is_redirected_from_prompt(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($user)->get('/verify-email');
|
||||
|
||||
$response->assertRedirect(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LoginRequestTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_throttles_too_many_attempts(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$this->post('/login', [
|
||||
'email' => $user->email,
|
||||
'password' => 'wrong-password',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->post('/login', [
|
||||
'email' => $user->email,
|
||||
'password' => 'password',
|
||||
])->assertSessionHasErrors('email');
|
||||
}
|
||||
|
||||
public function test_successful_login_redirects(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->post('/login', [
|
||||
'email' => $user->email,
|
||||
'password' => 'password',
|
||||
])->assertRedirect(route('dashboard', absolute: false));
|
||||
|
||||
$this->assertAuthenticated();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Notifications\ResetPassword;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NewPasswordControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_password_reset_screen_can_be_rendered(): void
|
||||
{
|
||||
Notification::fake();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->post('/forgot-password', ['email' => $user->email]);
|
||||
|
||||
Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
|
||||
$response = $this->get('/reset-password/'.$notification->token);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public function test_password_can_be_reset(): void
|
||||
{
|
||||
Notification::fake();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->post('/forgot-password', ['email' => $user->email]);
|
||||
|
||||
Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
|
||||
$response = $this->post('/reset-password', [
|
||||
'token' => $notification->token,
|
||||
'email' => $user->email,
|
||||
'password' => 'new-password',
|
||||
'password_confirmation' => 'new-password',
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertSessionHasNoErrors()
|
||||
->assertRedirect(route('login'));
|
||||
|
||||
$this->assertTrue(
|
||||
\Hash::check('new-password', $user->fresh()->password)
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public function test_password_reset_fails_with_invalid_token(): void
|
||||
{
|
||||
$response = $this->post('/reset-password', [
|
||||
'token' => 'invalid-token',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password',
|
||||
'password_confirmation' => 'password',
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors('email');
|
||||
}
|
||||
|
||||
public function test_password_reset_validates_required_fields(): void
|
||||
{
|
||||
$response = $this->post('/reset-password', []);
|
||||
|
||||
$response->assertSessionHasErrors(['token', 'email', 'password']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Notifications\ResetPassword;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PasswordResetLinkControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_forgot_password_screen_can_be_rendered(): void
|
||||
{
|
||||
$response = $this->get('/forgot-password');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function test_password_reset_link_can_be_requested(): void
|
||||
{
|
||||
Notification::fake();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->post('/forgot-password', ['email' => $user->email]);
|
||||
|
||||
Notification::assertSentTo($user, ResetPassword::class);
|
||||
}
|
||||
|
||||
public function test_password_reset_link_fails_with_invalid_email(): void
|
||||
{
|
||||
$response = $this->post('/forgot-password', [
|
||||
'email' => 'nonexistent@example.com',
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors('email');
|
||||
}
|
||||
|
||||
public function test_password_reset_link_validates_email(): void
|
||||
{
|
||||
$response = $this->post('/forgot-password', [
|
||||
'email' => '',
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors('email');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Tests\TestCase;
|
||||
|
||||
class VerifyEmailControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_already_verified_user_is_redirected(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$verificationUrl = URL::temporarySignedRoute(
|
||||
'verification.verify',
|
||||
now()->addMinutes(60),
|
||||
['id' => $user->id, 'hash' => sha1($user->email)]
|
||||
);
|
||||
|
||||
$response = $this->actingAs($user)->get($verificationUrl);
|
||||
|
||||
$response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
|
||||
public function test_unverified_user_email_can_be_verified(): void
|
||||
{
|
||||
$user = User::factory()->unverified()->create();
|
||||
|
||||
Event::fake();
|
||||
|
||||
$verificationUrl = URL::temporarySignedRoute(
|
||||
'verification.verify',
|
||||
now()->addMinutes(60),
|
||||
['id' => $user->id, 'hash' => sha1($user->email)]
|
||||
);
|
||||
|
||||
$response = $this->actingAs($user)->get($verificationUrl);
|
||||
|
||||
Event::assertDispatched(Verified::class);
|
||||
$this->assertTrue($user->fresh()->hasVerifiedEmail());
|
||||
$response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
|
||||
public function test_email_not_verified_with_invalid_hash(): void
|
||||
{
|
||||
$user = User::factory()->unverified()->create();
|
||||
|
||||
$verificationUrl = URL::temporarySignedRoute(
|
||||
'verification.verify',
|
||||
now()->addMinutes(60),
|
||||
['id' => $user->id, 'hash' => sha1('wrong-email')]
|
||||
);
|
||||
|
||||
$this->actingAs($user)->get($verificationUrl);
|
||||
|
||||
$this->assertFalse($user->fresh()->hasVerifiedEmail());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Games;
|
||||
|
||||
use App\Features\Consoles\Models\Console;
|
||||
use App\Features\Games\Models\Game;
|
||||
use App\Features\Games\Models\RomFile;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Tests\TestCase;
|
||||
|
||||
class GameControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_game_edit_returns_404_for_invalid_slug(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('games.edit', 'nonexistent'))
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
public function test_game_update_returns_404_for_invalid_slug(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->put(route('games.update', 'nonexistent'), ['title' => 'Test'])
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
public function test_game_destroy_returns_404_for_invalid_slug(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('games.destroy', 'nonexistent'))
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
public function test_game_can_be_deleted_with_rom_files(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
$user = User::factory()->create();
|
||||
$console = Console::factory()->create();
|
||||
$game = Game::factory()->create(['console_id' => $console->id]);
|
||||
|
||||
RomFile::factory()->create([
|
||||
'game_id' => $game->id,
|
||||
'file_path' => 'roms/nes/game.nes',
|
||||
]);
|
||||
|
||||
Storage::disk('local')->put('roms/nes/game.nes', 'rom-data');
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('games.destroy', $game->slug))
|
||||
->assertRedirect();
|
||||
|
||||
$this->assertDatabaseMissing('games', ['id' => $game->id]);
|
||||
}
|
||||
|
||||
public function test_game_can_be_deleted_with_cover(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
$user = User::factory()->create();
|
||||
$console = Console::factory()->create();
|
||||
$game = Game::factory()->create([
|
||||
'console_id' => $console->id,
|
||||
'cover_path' => 'covers/nes/game-cover.jpg',
|
||||
]);
|
||||
|
||||
Storage::disk('local')->put('covers/nes/game-cover.jpg', 'cover-data');
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('games.destroy', $game->slug))
|
||||
->assertRedirect();
|
||||
|
||||
$this->assertDatabaseMissing('games', ['id' => $game->id]);
|
||||
}
|
||||
|
||||
public function test_game_download_returns_file(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
$user = User::factory()->create();
|
||||
$console = Console::factory()->create();
|
||||
$game = Game::factory()->create(['console_id' => $console->id]);
|
||||
|
||||
$romFile = RomFile::factory()->create([
|
||||
'game_id' => $game->id,
|
||||
'file_path' => 'roms/nes/game.nes',
|
||||
'file_name' => 'game.nes',
|
||||
]);
|
||||
|
||||
Storage::disk('local')->put('roms/nes/game.nes', 'rom-data');
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('games.download', [$game->slug, $romFile->id]))
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
public function test_game_download_returns_404_for_invalid_rom(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$console = Console::factory()->create();
|
||||
$game = Game::factory()->create(['console_id' => $console->id]);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('games.download', [$game->slug, 99999]))
|
||||
->assertNotFound();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Jobs;
|
||||
|
||||
use App\Features\Consoles\Models\Console;
|
||||
use App\Features\Games\Models\Game;
|
||||
use App\Features\Scraper\Services\ScraperService;
|
||||
use App\Jobs\ScrapeCoverJob;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ScrapeCoverJobTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private ScraperService|MockObject $scraper;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->scraper = $this->createMock(ScraperService::class);
|
||||
}
|
||||
|
||||
public function test_job_updates_cover_when_match_found(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
$console = Console::factory()->create(['slug' => 'nes']);
|
||||
$game = Game::factory()->create(['console_id' => $console->id]);
|
||||
$game->load('console');
|
||||
|
||||
$match = new \stdClass();
|
||||
$match->coverUrl = 'https://example.com/cover.jpg';
|
||||
|
||||
$this->scraper->expects($this->once())
|
||||
->method('search')
|
||||
->willReturn(collect([$match]));
|
||||
|
||||
$this->scraper->expects($this->once())
|
||||
->method('downloadCover')
|
||||
->willReturn('covers/nes/super-mario-cover.jpg');
|
||||
|
||||
$job = new ScrapeCoverJob($game);
|
||||
$job->handle($this->scraper);
|
||||
|
||||
$game->refresh();
|
||||
$this->assertEquals('covers/nes/super-mario-cover.jpg', $game->cover_path);
|
||||
}
|
||||
|
||||
public function test_job_does_nothing_when_no_results(): void
|
||||
{
|
||||
$game = Game::factory()->create();
|
||||
|
||||
$this->scraper->expects($this->once())
|
||||
->method('search')
|
||||
->willReturn(collect());
|
||||
|
||||
$this->scraper->expects($this->never())
|
||||
->method('downloadCover');
|
||||
|
||||
$job = new ScrapeCoverJob($game);
|
||||
$job->handle($this->scraper);
|
||||
|
||||
$game->refresh();
|
||||
$this->assertNull($game->cover_path);
|
||||
}
|
||||
|
||||
public function test_job_does_nothing_when_no_cover_url(): void
|
||||
{
|
||||
$game = Game::factory()->create();
|
||||
|
||||
$match = new \stdClass();
|
||||
$match->coverUrl = null;
|
||||
|
||||
$this->scraper->expects($this->once())
|
||||
->method('search')
|
||||
->willReturn(collect([$match]));
|
||||
|
||||
$this->scraper->expects($this->never())
|
||||
->method('downloadCover');
|
||||
|
||||
$job = new ScrapeCoverJob($game);
|
||||
$job->handle($this->scraper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Scraper;
|
||||
|
||||
use App\Features\Consoles\Models\Console;
|
||||
use App\Features\Games\Models\Game;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ScraperControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_search_requires_authentication(): void
|
||||
{
|
||||
$this->getJson(route('scraper.search', ['query' => 'mario']))
|
||||
->assertRedirect();
|
||||
}
|
||||
|
||||
public function test_search_validates_query(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->getJson(route('scraper.search', ['query' => '']))
|
||||
->assertSessionHasErrors('query');
|
||||
}
|
||||
|
||||
public function test_search_validates_min_query_length(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->getJson(route('scraper.search', ['query' => 'a']))
|
||||
->assertSessionHasErrors('query');
|
||||
}
|
||||
|
||||
public function test_search_returns_results(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
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' => [],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
|
||||
config([
|
||||
'services.igdb.client_id' => 'test-id',
|
||||
'services.igdb.client_secret' => 'test-secret',
|
||||
]);
|
||||
|
||||
$this->actingAs($user)
|
||||
->getJson(route('scraper.search', ['query' => 'mario']))
|
||||
->assertOk()
|
||||
->assertJsonCount(1, 'results');
|
||||
}
|
||||
|
||||
public function test_apply_requires_authentication(): void
|
||||
{
|
||||
$this->post(route('scraper.apply', 'some-game'))
|
||||
->assertRedirect(route('login'));
|
||||
}
|
||||
|
||||
public function test_apply_validates_cover_url(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$game = Game::factory()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('scraper.apply', $game->slug), [])
|
||||
->assertSessionHasErrors('cover_url');
|
||||
}
|
||||
|
||||
public function test_apply_downloads_and_updates_cover(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
$user = User::factory()->create();
|
||||
$game = Game::factory()->create();
|
||||
$game->load('console');
|
||||
|
||||
Http::fake([
|
||||
'https://example.com/cover.jpg' => Http::response('image-data'),
|
||||
]);
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('scraper.apply', $game->slug), [
|
||||
'cover_url' => 'https://example.com/cover.jpg',
|
||||
])
|
||||
->assertRedirect();
|
||||
|
||||
$game->refresh();
|
||||
$this->assertNotNull($game->cover_path);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Features\Consoles\Models\Console;
|
||||
use App\Features\Games\Models\Game;
|
||||
use App\Features\Games\Models\RomFile;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
@@ -88,4 +90,29 @@ class UploadTest extends TestCase
|
||||
|
||||
Storage::disk('local')->assertExists("roms/{$console->slug}/game.nes");
|
||||
}
|
||||
|
||||
public function test_upload_store_skips_duplicate_rom_files(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
$user = User::factory()->create();
|
||||
$console = Console::factory()->create(['rom_extensions' => ['nes']]);
|
||||
|
||||
$content = str_repeat('x', 1024);
|
||||
$hash = hash('sha256', $content);
|
||||
|
||||
$game = Game::factory()->create(['console_id' => $console->id]);
|
||||
RomFile::factory()->create([
|
||||
'game_id' => $game->id,
|
||||
'file_hash' => $hash,
|
||||
]);
|
||||
|
||||
$file = UploadedFile::fake()->createWithContent('game.nes', $content);
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('upload.store'), [
|
||||
'console_id' => $console->id,
|
||||
'rom_files' => [$file],
|
||||
])
|
||||
->assertRedirect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user