diff --git a/app/Console/Commands/MashupCreateCommand.php b/app/Console/Commands/MashupCreateCommand.php index 7020235..d6f1378 100644 --- a/app/Console/Commands/MashupCreateCommand.php +++ b/app/Console/Commands/MashupCreateCommand.php @@ -9,6 +9,9 @@ use App\Models\Plugin; use Illuminate\Console\Command; use Illuminate\Support\Collection; +use function Laravel\Prompts\select; +use function Laravel\Prompts\text; + class MashupCreateCommand extends Command { /** @@ -85,9 +88,9 @@ class MashupCreateCommand extends Command return null; } - $deviceId = $this->choice( - 'Select a device', - $devices->mapWithKeys(fn ($device) => [$device->id => $device->name])->toArray() + $deviceId = select( + label: 'Select a device', + options: $devices->mapWithKeys(fn ($device) => [$device->id => $device->name])->toArray() ); return $devices->firstWhere('id', $deviceId); @@ -103,9 +106,9 @@ class MashupCreateCommand extends Command return null; } - $playlistId = $this->choice( - 'Select a playlist', - $playlists->mapWithKeys(fn (Playlist $playlist) => [$playlist->id => $playlist->name])->toArray() + $playlistId = select( + label: 'Select a playlist', + options: $playlists->mapWithKeys(fn (Playlist $playlist) => [$playlist->id => $playlist->name])->toArray() ); return $playlists->firstWhere('id', $playlistId); @@ -113,29 +116,24 @@ class MashupCreateCommand extends Command protected function selectLayout(): ?string { - return $this->choice( - 'Select a layout', - PlaylistItem::getAvailableLayouts() + return select( + label: 'Select a layout', + options: PlaylistItem::getAvailableLayouts() ); } protected function getMashupName(): ?string { - $name = $this->ask('Enter a name for this mashup', 'Mashup'); - - if (mb_strlen($name) < 2) { - $this->error('The name must be at least 2 characters.'); - - return null; - } - - if (mb_strlen($name) > 50) { - $this->error('The name must not exceed 50 characters.'); - - return null; - } - - return $name; + return text( + label: 'Enter a name for this mashup', + required: true, + default: 'Mashup', + validate: fn (string $value) => match (true) { + mb_strlen($value) < 1 => 'The name must be at least 2 characters.', + mb_strlen($value) > 50 => 'The name must not exceed 50 characters.', + default => null, + } + ); } protected function selectPlugins(string $layout): Collection @@ -161,9 +159,9 @@ class MashupCreateCommand extends Command default => ($i + 1).'th' }; - $pluginId = $this->choice( - "Select the $position plugin", - $availablePlugins + $pluginId = select( + label: "Select the $position plugin", + options: $availablePlugins ); $selectedPlugins->push($plugins->firstWhere('id', $pluginId)); diff --git a/app/Console/Commands/OidcTestCommand.php b/app/Console/Commands/OidcTestCommand.php index 73321ce..c04f263 100644 --- a/app/Console/Commands/OidcTestCommand.php +++ b/app/Console/Commands/OidcTestCommand.php @@ -40,18 +40,13 @@ class OidcTestCommand extends Command $clientId = config('services.oidc.client_id'); $clientSecret = config('services.oidc.client_secret'); $redirect = config('services.oidc.redirect'); - if (! $redirect) { - $redirect = config('app.url', 'http://localhost').'/auth/oidc/callback'; - } $scopes = config('services.oidc.scopes', []); - $defaultScopes = ['openid', 'profile', 'email']; - $effectiveScopes = empty($scopes) ? $defaultScopes : $scopes; $this->line('OIDC Endpoint: '.($endpoint ? "✅ {$endpoint}" : '❌ Not set')); $this->line('Client ID: '.($clientId ? "✅ {$clientId}" : '❌ Not set')); $this->line('Client Secret: '.($clientSecret ? '✅ Set' : '❌ Not set')); $this->line('Redirect URL: '.($redirect ? "✅ {$redirect}" : '❌ Not set')); - $this->line('Scopes: ✅ '.implode(', ', $effectiveScopes)); + $this->line('Scopes: '.(empty($scopes) ? '❌ Not set' : '✅ '.implode(', ', $scopes))); $this->newLine(); diff --git a/app/Jobs/FirmwareDownloadJob.php b/app/Jobs/FirmwareDownloadJob.php index 13352c3..6b4fc36 100644 --- a/app/Jobs/FirmwareDownloadJob.php +++ b/app/Jobs/FirmwareDownloadJob.php @@ -33,25 +33,16 @@ class FirmwareDownloadJob implements ShouldQueue try { $filename = "FW{$this->firmware->version_tag}.bin"; - $response = Http::get($this->firmware->url); + Http::sink(storage_path("app/public/firmwares/$filename")) + ->get($this->firmware->url); - if (! $response->successful()) { - throw new Exception('HTTP request failed with status: '.$response->status()); - } - - // Save the response content to file - Storage::disk('public')->put("firmwares/$filename", $response->body()); - - // Only update storage location if download was successful $this->firmware->update([ 'storage_location' => "firmwares/$filename", ]); } catch (ConnectionException $e) { Log::error('Firmware download failed: '.$e->getMessage()); - // Don't update storage_location on failure } catch (Exception $e) { Log::error('An unexpected error occurred: '.$e->getMessage()); - // Don't update storage_location on failure } } } diff --git a/app/Services/OidcProvider.php b/app/Services/OidcProvider.php index 74143f1..e6cda63 100644 --- a/app/Services/OidcProvider.php +++ b/app/Services/OidcProvider.php @@ -60,7 +60,7 @@ class OidcProvider extends AbstractProvider implements ProviderInterface { try { $url = $this->baseUrl.'/.well-known/openid-configuration'; - $client = app(Client::class); + $client = new Client(); $response = $client->get($url); $this->oidcConfig = json_decode($response->getBody()->getContents(), true); @@ -122,7 +122,7 @@ class OidcProvider extends AbstractProvider implements ProviderInterface /** * Map the raw user array to a Socialite User instance. */ - public function mapUserToObject(array $user) + protected function mapUserToObject(array $user) { return (new User)->setRaw($user)->map([ 'id' => $user['sub'], diff --git a/tests/Feature/Console/ExampleRecipesSeederCommandTest.php b/tests/Feature/Console/ExampleRecipesSeederCommandTest.php deleted file mode 100644 index 4b98180..0000000 --- a/tests/Feature/Console/ExampleRecipesSeederCommandTest.php +++ /dev/null @@ -1,40 +0,0 @@ -shouldReceive('run') - ->once() - ->with('123'); - - $this->app->instance(ExampleRecipesSeeder::class, $seeder); - - $this->artisan('recipes:seed', ['user_id' => '123']) - ->assertExitCode(0); -}); - -test('example recipes seeder command has correct signature', function () { - $command = $this->app->make(App\Console\Commands\ExampleRecipesSeederCommand::class); - - expect($command->getName())->toBe('recipes:seed'); - expect($command->getDescription())->toBe('Seed example recipes'); -}); - -test('example recipes seeder command prompts for missing input', function () { - $seeder = Mockery::mock(ExampleRecipesSeeder::class); - $seeder->shouldReceive('run') - ->once() - ->with('456'); - - $this->app->instance(ExampleRecipesSeeder::class, $seeder); - - $this->artisan('recipes:seed') - ->expectsQuestion('What is the user_id?', '456') - ->assertExitCode(0); -}); diff --git a/tests/Feature/Console/FirmwareCheckCommandTest.php b/tests/Feature/Console/FirmwareCheckCommandTest.php deleted file mode 100644 index 19098ea..0000000 --- a/tests/Feature/Console/FirmwareCheckCommandTest.php +++ /dev/null @@ -1,29 +0,0 @@ -app->make(App\Console\Commands\FirmwareCheckCommand::class); - - expect($command->getName())->toBe('trmnl:firmware:check'); - expect($command->getDescription())->toBe('Checks for the latest firmware and downloads it if flag --download is passed.'); -}); - -test('firmware check command runs without errors', function () { - $this->artisan('trmnl:firmware:check') - ->assertExitCode(0); -}); - -test('firmware check command runs with download flag', function () { - $this->artisan('trmnl:firmware:check', ['--download' => true]) - ->assertExitCode(0); -}); - -test('firmware check command can run successfully', function () { - $this->artisan('trmnl:firmware:check') - ->assertExitCode(0); -}); diff --git a/tests/Feature/Console/FirmwareUpdateCommandTest.php b/tests/Feature/Console/FirmwareUpdateCommandTest.php deleted file mode 100644 index ee250b9..0000000 --- a/tests/Feature/Console/FirmwareUpdateCommandTest.php +++ /dev/null @@ -1,86 +0,0 @@ -artisan('trmnl:firmware:update --help') - ->assertExitCode(0); -}); - -test('firmware update command can be called', function () { - $user = User::factory()->create(); - $device = Device::factory()->create(['user_id' => $user->id]); - $firmware = Firmware::factory()->create(['version_tag' => '1.0.0']); - - $this->artisan('trmnl:firmware:update') - ->expectsQuestion('Check for new firmware?', 'no') - ->expectsQuestion('Update to which version?', $firmware->id) - ->expectsQuestion('Which devices should be updated?', ["_$device->id"]) - ->assertExitCode(0); - - $device->refresh(); - expect($device->update_firmware_id)->toBe($firmware->id); -}); - -test('firmware update command updates all devices when all is selected', function () { - $user = User::factory()->create(); - $device1 = Device::factory()->create(['user_id' => $user->id]); - $device2 = Device::factory()->create(['user_id' => $user->id]); - $firmware = Firmware::factory()->create(['version_tag' => '1.0.0']); - - $this->artisan('trmnl:firmware:update') - ->expectsQuestion('Check for new firmware?', 'no') - ->expectsQuestion('Update to which version?', $firmware->id) - ->expectsQuestion('Which devices should be updated?', ['all']) - ->assertExitCode(0); - - $device1->refresh(); - $device2->refresh(); - expect($device1->update_firmware_id)->toBe($firmware->id); - expect($device2->update_firmware_id)->toBe($firmware->id); -}); - -test('firmware update command aborts when no devices selected', function () { - $firmware = Firmware::factory()->create(['version_tag' => '1.0.0']); - - $this->artisan('trmnl:firmware:update') - ->expectsQuestion('Check for new firmware?', 'no') - ->expectsQuestion('Update to which version?', $firmware->id) - ->expectsQuestion('Which devices should be updated?', []) - ->expectsOutput('No devices selected. Aborting.') - ->assertExitCode(0); -}); - -test('firmware update command calls firmware check when check is selected', function () { - $user = User::factory()->create(); - $device = Device::factory()->create(['user_id' => $user->id]); - $firmware = Firmware::factory()->create(['version_tag' => '1.0.0']); - - $this->artisan('trmnl:firmware:update') - ->expectsQuestion('Check for new firmware?', 'check') - ->expectsQuestion('Update to which version?', $firmware->id) - ->expectsQuestion('Which devices should be updated?', ["_$device->id"]) - ->assertExitCode(0); - - $device->refresh(); - expect($device->update_firmware_id)->toBe($firmware->id); -}); - -test('firmware update command calls firmware check with download when download is selected', function () { - $user = User::factory()->create(); - $device = Device::factory()->create(['user_id' => $user->id]); - $firmware = Firmware::factory()->create(['version_tag' => '1.0.0']); - - $this->artisan('trmnl:firmware:update') - ->expectsQuestion('Check for new firmware?', 'download') - ->expectsQuestion('Update to which version?', $firmware->id) - ->expectsQuestion('Which devices should be updated?', ["_$device->id"]) - ->assertExitCode(0); - - $device->refresh(); - expect($device->update_firmware_id)->toBe($firmware->id); -}); diff --git a/tests/Feature/Console/MashupCreateCommandTest.php b/tests/Feature/Console/MashupCreateCommandTest.php deleted file mode 100644 index e61c34c..0000000 --- a/tests/Feature/Console/MashupCreateCommandTest.php +++ /dev/null @@ -1,154 +0,0 @@ -artisan('mashup:create --help') - ->assertExitCode(0); -}); - -test('mashup create command creates mashup successfully', function () { - $user = User::factory()->create(); - $device = Device::factory()->create(['user_id' => $user->id]); - $playlist = Playlist::factory()->create(['device_id' => $device->id]); - $plugin1 = Plugin::factory()->create(['user_id' => $user->id]); - $plugin2 = Plugin::factory()->create(['user_id' => $user->id]); - - $this->artisan('mashup:create') - ->expectsQuestion('Select a device', $device->id) - ->expectsQuestion('Select a playlist', $playlist->id) - ->expectsQuestion('Select a layout', '1Lx1R') - ->expectsQuestion('Enter a name for this mashup', 'Test Mashup') - ->expectsQuestion('Select the first plugin', $plugin1->id) - ->expectsQuestion('Select the second plugin', $plugin2->id) - ->expectsOutput('Mashup created successfully!') - ->assertExitCode(0); - - $playlistItem = PlaylistItem::where('playlist_id', $playlist->id) - ->whereJsonContains('mashup->mashup_name', 'Test Mashup') - ->first(); - - expect($playlistItem)->not->toBeNull(); - expect($playlistItem->isMashup())->toBeTrue(); - expect($playlistItem->getMashupLayoutType())->toBe('1Lx1R'); - expect($playlistItem->getMashupPluginIds())->toContain($plugin1->id, $plugin2->id); -}); - -test('mashup create command exits when no devices found', function () { - $this->artisan('mashup:create') - ->expectsOutput('No devices found. Please create a device first.') - ->assertExitCode(1); -}); - -test('mashup create command exits when no playlists found for device', function () { - $user = User::factory()->create(); - $device = Device::factory()->create(['user_id' => $user->id]); - - $this->artisan('mashup:create') - ->expectsQuestion('Select a device', $device->id) - ->expectsOutput('No playlists found for this device. Please create a playlist first.') - ->assertExitCode(1); -}); - -test('mashup create command exits when no plugins found', function () { - $user = User::factory()->create(); - $device = Device::factory()->create(['user_id' => $user->id]); - $playlist = Playlist::factory()->create(['device_id' => $device->id]); - - $this->artisan('mashup:create') - ->expectsQuestion('Select a device', $device->id) - ->expectsQuestion('Select a playlist', $playlist->id) - ->expectsQuestion('Select a layout', '1Lx1R') - ->expectsQuestion('Enter a name for this mashup', 'Test Mashup') - ->expectsOutput('No plugins found. Please create some plugins first.') - ->assertExitCode(1); -}); - -test('mashup create command validates mashup name length', function () { - $user = User::factory()->create(); - $device = Device::factory()->create(['user_id' => $user->id]); - $playlist = Playlist::factory()->create(['device_id' => $device->id]); - $plugin1 = Plugin::factory()->create(['user_id' => $user->id]); - $plugin2 = Plugin::factory()->create(['user_id' => $user->id]); - - $this->artisan('mashup:create') - ->expectsQuestion('Select a device', $device->id) - ->expectsQuestion('Select a playlist', $playlist->id) - ->expectsQuestion('Select a layout', '1Lx1R') - ->expectsQuestion('Enter a name for this mashup', 'A') // Too short - ->expectsOutput('The name must be at least 2 characters.') - ->assertExitCode(1); -}); - -test('mashup create command validates mashup name maximum length', function () { - $user = User::factory()->create(); - $device = Device::factory()->create(['user_id' => $user->id]); - $playlist = Playlist::factory()->create(['device_id' => $device->id]); - $plugin1 = Plugin::factory()->create(['user_id' => $user->id]); - $plugin2 = Plugin::factory()->create(['user_id' => $user->id]); - - $longName = str_repeat('A', 51); // Too long - - $this->artisan('mashup:create') - ->expectsQuestion('Select a device', $device->id) - ->expectsQuestion('Select a playlist', $playlist->id) - ->expectsQuestion('Select a layout', '1Lx1R') - ->expectsQuestion('Enter a name for this mashup', $longName) - ->expectsOutput('The name must not exceed 50 characters.') - ->assertExitCode(1); -}); - -test('mashup create command uses default name when provided', function () { - $user = User::factory()->create(); - $device = Device::factory()->create(['user_id' => $user->id]); - $playlist = Playlist::factory()->create(['device_id' => $device->id]); - $plugin1 = Plugin::factory()->create(['user_id' => $user->id]); - $plugin2 = Plugin::factory()->create(['user_id' => $user->id]); - - $this->artisan('mashup:create') - ->expectsQuestion('Select a device', $device->id) - ->expectsQuestion('Select a playlist', $playlist->id) - ->expectsQuestion('Select a layout', '1Lx1R') - ->expectsQuestion('Enter a name for this mashup', 'Mashup') // Default value - ->expectsQuestion('Select the first plugin', $plugin1->id) - ->expectsQuestion('Select the second plugin', $plugin2->id) - ->expectsOutput('Mashup created successfully!') - ->assertExitCode(0); - - $playlistItem = PlaylistItem::where('playlist_id', $playlist->id) - ->whereJsonContains('mashup->mashup_name', 'Mashup') - ->first(); - - expect($playlistItem)->not->toBeNull(); -}); - -test('mashup create command handles 1x1 layout with single plugin', function () { - $user = User::factory()->create(); - $device = Device::factory()->create(['user_id' => $user->id]); - $playlist = Playlist::factory()->create(['device_id' => $device->id]); - $plugin = Plugin::factory()->create(['user_id' => $user->id]); - - $this->artisan('mashup:create') - ->expectsQuestion('Select a device', $device->id) - ->expectsQuestion('Select a playlist', $playlist->id) - ->expectsQuestion('Select a layout', '1x1') - ->expectsQuestion('Enter a name for this mashup', 'Single Plugin Mashup') - ->expectsQuestion('Select the first plugin', $plugin->id) - ->expectsOutput('Mashup created successfully!') - ->assertExitCode(0); - - $playlistItem = PlaylistItem::where('playlist_id', $playlist->id) - ->whereJsonContains('mashup->mashup_name', 'Single Plugin Mashup') - ->first(); - - expect($playlistItem)->not->toBeNull(); - expect($playlistItem->getMashupLayoutType())->toBe('1x1'); - expect($playlistItem->getMashupPluginIds())->toHaveCount(1); - expect($playlistItem->getMashupPluginIds())->toContain($plugin->id); -}); diff --git a/tests/Feature/Console/OidcTestCommandTest.php b/tests/Feature/Console/OidcTestCommandTest.php deleted file mode 100644 index e7456b0..0000000 --- a/tests/Feature/Console/OidcTestCommandTest.php +++ /dev/null @@ -1,188 +0,0 @@ -artisan('oidc:test --help') - ->assertExitCode(0); -}); - -test('oidc test command runs successfully with disabled oidc', function () { - config(['services.oidc.enabled' => false]); - - $this->artisan('oidc:test') - ->expectsOutput('Testing OIDC Configuration...') - ->expectsOutput('OIDC Enabled: ❌ No') - ->expectsOutput('OIDC Endpoint: ❌ Not set') - ->expectsOutput('Client ID: ❌ Not set') - ->expectsOutput('Client Secret: ❌ Not set') - ->expectsOutput('Redirect URL: ✅ http://localhost/auth/oidc/callback') - ->expectsOutput('Scopes: ✅ openid, profile, email') - ->expectsOutput('OIDC Driver: ✅ Registered (configuration test skipped due to missing values)') - ->expectsOutput('⚠️ OIDC driver is registered but missing required configuration.') - ->expectsOutput('Please set the following environment variables:') - ->expectsOutput(' - OIDC_ENABLED=true') - ->expectsOutput(' - OIDC_ENDPOINT=https://your-oidc-provider.com (base URL)') - ->expectsOutput(' OR') - ->expectsOutput(' - OIDC_ENDPOINT=https://your-oidc-provider.com/.well-known/openid-configuration (full URL)') - ->expectsOutput(' - OIDC_CLIENT_ID=your-client-id') - ->expectsOutput(' - OIDC_CLIENT_SECRET=your-client-secret') - ->assertExitCode(0); -}); - -test('oidc test command runs successfully with enabled oidc but missing config', function () { - config([ - 'services.oidc.enabled' => true, - 'services.oidc.endpoint' => null, - 'services.oidc.client_id' => null, - 'services.oidc.client_secret' => null, - 'services.oidc.redirect' => null, - 'services.oidc.scopes' => [], - ]); - - $this->artisan('oidc:test') - ->expectsOutput('Testing OIDC Configuration...') - ->expectsOutput('OIDC Enabled: ✅ Yes') - ->expectsOutput('OIDC Endpoint: ❌ Not set') - ->expectsOutput('Client ID: ❌ Not set') - ->expectsOutput('Client Secret: ❌ Not set') - ->expectsOutput('Redirect URL: ✅ http://localhost/auth/oidc/callback') - ->expectsOutput('Scopes: ✅ openid, profile, email') - ->expectsOutput('OIDC Driver: ✅ Registered (configuration test skipped due to missing values)') - ->expectsOutput('⚠️ OIDC driver is registered but missing required configuration.') - ->expectsOutput('Please set the following environment variables:') - ->expectsOutput(' - OIDC_ENDPOINT=https://your-oidc-provider.com (base URL)') - ->expectsOutput(' OR') - ->expectsOutput(' - OIDC_ENDPOINT=https://your-oidc-provider.com/.well-known/openid-configuration (full URL)') - ->expectsOutput(' - OIDC_CLIENT_ID=your-client-id') - ->expectsOutput(' - OIDC_CLIENT_SECRET=your-client-secret') - ->assertExitCode(0); -}); - -test('oidc test command runs successfully with partial config', function () { - config([ - 'services.oidc.enabled' => true, - 'services.oidc.endpoint' => 'https://example.com', - 'services.oidc.client_id' => 'test-client-id', - 'services.oidc.client_secret' => null, - 'services.oidc.redirect' => 'https://example.com/callback', - 'services.oidc.scopes' => ['openid', 'profile'], - ]); - - $this->artisan('oidc:test') - ->expectsOutput('Testing OIDC Configuration...') - ->expectsOutput('OIDC Enabled: ✅ Yes') - ->expectsOutput('OIDC Endpoint: ✅ https://example.com') - ->expectsOutput('Client ID: ✅ test-client-id') - ->expectsOutput('Client Secret: ❌ Not set') - ->expectsOutput('Redirect URL: ✅ https://example.com/callback') - ->expectsOutput('Scopes: ✅ openid, profile') - ->expectsOutput('OIDC Driver: ✅ Registered (configuration test skipped due to missing values)') - ->expectsOutput('⚠️ OIDC driver is registered but missing required configuration.') - ->expectsOutput('Please set the following environment variables:') - ->expectsOutput(' - OIDC_CLIENT_SECRET=your-client-secret') - ->assertExitCode(0); -}); - -test('oidc test command runs successfully with full config but disabled', function () { - // Mock the HTTP client to return fake OIDC configuration - mock(GuzzleHttp\Client::class, function ($mock) { - $mock->shouldReceive('get') - ->with('https://example.com/.well-known/openid-configuration') - ->andReturn(new GuzzleHttp\Psr7\Response(200, [], json_encode([ - 'authorization_endpoint' => 'https://example.com/auth', - 'token_endpoint' => 'https://example.com/token', - 'userinfo_endpoint' => 'https://example.com/userinfo', - ]))); - }); - - config([ - 'services.oidc.enabled' => false, - 'services.oidc.endpoint' => 'https://example.com', - 'services.oidc.client_id' => 'test-client-id', - 'services.oidc.client_secret' => 'test-client-secret', - 'services.oidc.redirect' => 'https://example.com/callback', - 'services.oidc.scopes' => ['openid', 'profile'], - ]); - - $this->artisan('oidc:test') - ->expectsOutput('Testing OIDC Configuration...') - ->expectsOutput('OIDC Enabled: ❌ No') - ->expectsOutput('OIDC Endpoint: ✅ https://example.com') - ->expectsOutput('Client ID: ✅ test-client-id') - ->expectsOutput('Client Secret: ✅ Set') - ->expectsOutput('Redirect URL: ✅ https://example.com/callback') - ->expectsOutput('Scopes: ✅ openid, profile') - ->expectsOutput('OIDC Driver: ✅ Successfully registered and accessible') - ->expectsOutput('⚠️ OIDC driver is working but OIDC_ENABLED is false.') - ->assertExitCode(0); -}); - -test('oidc test command runs successfully with full config and enabled', function () { - // Mock the HTTP client to return fake OIDC configuration - mock(GuzzleHttp\Client::class, function ($mock) { - $mock->shouldReceive('get') - ->with('https://example.com/.well-known/openid-configuration') - ->andReturn(new GuzzleHttp\Psr7\Response(200, [], json_encode([ - 'authorization_endpoint' => 'https://example.com/auth', - 'token_endpoint' => 'https://example.com/token', - 'userinfo_endpoint' => 'https://example.com/userinfo', - ]))); - }); - - config([ - 'services.oidc.enabled' => true, - 'services.oidc.endpoint' => 'https://example.com', - 'services.oidc.client_id' => 'test-client-id', - 'services.oidc.client_secret' => 'test-client-secret', - 'services.oidc.redirect' => 'https://example.com/callback', - 'services.oidc.scopes' => ['openid', 'profile'], - ]); - - $this->artisan('oidc:test') - ->expectsOutput('Testing OIDC Configuration...') - ->expectsOutput('OIDC Enabled: ✅ Yes') - ->expectsOutput('OIDC Endpoint: ✅ https://example.com') - ->expectsOutput('Client ID: ✅ test-client-id') - ->expectsOutput('Client Secret: ✅ Set') - ->expectsOutput('Redirect URL: ✅ https://example.com/callback') - ->expectsOutput('Scopes: ✅ openid, profile') - ->expectsOutput('OIDC Driver: ✅ Successfully registered and accessible') - ->expectsOutput('✅ OIDC is fully configured and ready to use!') - ->expectsOutput('You can test the login flow at: /auth/oidc/redirect') - ->assertExitCode(0); -}); - -test('oidc test command handles empty scopes', function () { - // Mock the HTTP client to return fake OIDC configuration - mock(GuzzleHttp\Client::class, function ($mock) { - $mock->shouldReceive('get') - ->with('https://example.com/.well-known/openid-configuration') - ->andReturn(new GuzzleHttp\Psr7\Response(200, [], json_encode([ - 'authorization_endpoint' => 'https://example.com/auth', - 'token_endpoint' => 'https://example.com/token', - 'userinfo_endpoint' => 'https://example.com/userinfo', - ]))); - }); - - config([ - 'services.oidc.enabled' => false, - 'services.oidc.endpoint' => 'https://example.com', - 'services.oidc.client_id' => 'test-client-id', - 'services.oidc.client_secret' => 'test-client-secret', - 'services.oidc.redirect' => 'https://example.com/callback', - 'services.oidc.scopes' => null, - ]); - - $this->artisan('oidc:test') - ->expectsOutput('Testing OIDC Configuration...') - ->expectsOutput('OIDC Enabled: ❌ No') - ->expectsOutput('OIDC Endpoint: ✅ https://example.com') - ->expectsOutput('Client ID: ✅ test-client-id') - ->expectsOutput('Client Secret: ✅ Set') - ->expectsOutput('Redirect URL: ✅ https://example.com/callback') - ->expectsOutput('Scopes: ✅ openid, profile, email') - ->assertExitCode(0); -}); diff --git a/tests/Feature/Jobs/FetchDeviceModelsJobTest.php b/tests/Feature/Jobs/FetchDeviceModelsJobTest.php deleted file mode 100644 index b85a24e..0000000 --- a/tests/Feature/Jobs/FetchDeviceModelsJobTest.php +++ /dev/null @@ -1,344 +0,0 @@ -toBeInstanceOf(FetchDeviceModelsJob::class); -}); - -test('fetch device models job handles successful api response', function () { - Http::fake([ - 'usetrmnl.com/api/models' => Http::response([ - 'data' => [ - [ - 'name' => 'test-model', - 'label' => 'Test Model', - 'description' => 'A test device model', - 'width' => 800, - 'height' => 480, - 'colors' => 4, - 'bit_depth' => 2, - 'scale_factor' => 1.0, - 'rotation' => 0, - 'mime_type' => 'image/png', - 'offset_x' => 0, - 'offset_y' => 0, - 'published_at' => '2023-01-01T00:00:00Z', - ], - ], - ], 200), - ]); - - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated device models', ['count' => 1]); - - $job = new FetchDeviceModelsJob(); - $job->handle(); - - $deviceModel = DeviceModel::where('name', 'test-model')->first(); - expect($deviceModel)->not->toBeNull(); - expect($deviceModel->label)->toBe('Test Model'); - expect($deviceModel->description)->toBe('A test device model'); - expect($deviceModel->width)->toBe(800); - expect($deviceModel->height)->toBe(480); - expect($deviceModel->colors)->toBe(4); - expect($deviceModel->bit_depth)->toBe(2); - expect($deviceModel->scale_factor)->toBe(1.0); - expect($deviceModel->rotation)->toBe(0); - expect($deviceModel->mime_type)->toBe('image/png'); - expect($deviceModel->offset_x)->toBe(0); - expect($deviceModel->offset_y)->toBe(0); - expect($deviceModel->source)->toBe('api'); -}); - -test('fetch device models job handles multiple device models', function () { - Http::fake([ - 'usetrmnl.com/api/models' => Http::response([ - 'data' => [ - [ - 'name' => 'model-1', - 'label' => 'Model 1', - 'description' => 'First model', - 'width' => 800, - 'height' => 480, - 'colors' => 4, - 'bit_depth' => 2, - 'scale_factor' => 1.0, - 'rotation' => 0, - 'mime_type' => 'image/png', - 'offset_x' => 0, - 'offset_y' => 0, - 'published_at' => '2023-01-01T00:00:00Z', - ], - [ - 'name' => 'model-2', - 'label' => 'Model 2', - 'description' => 'Second model', - 'width' => 1200, - 'height' => 800, - 'colors' => 16, - 'bit_depth' => 4, - 'scale_factor' => 1.5, - 'rotation' => 90, - 'mime_type' => 'image/bmp', - 'offset_x' => 10, - 'offset_y' => 20, - 'published_at' => '2023-01-02T00:00:00Z', - ], - ], - ], 200), - ]); - - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated device models', ['count' => 2]); - - $job = new FetchDeviceModelsJob(); - $job->handle(); - - expect(DeviceModel::where('name', 'model-1')->exists())->toBeTrue(); - expect(DeviceModel::where('name', 'model-2')->exists())->toBeTrue(); -}); - -test('fetch device models job handles empty data array', function () { - Http::fake([ - 'usetrmnl.com/api/models' => Http::response([ - 'data' => [], - ], 200), - ]); - - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated device models', ['count' => 0]); - - $job = new FetchDeviceModelsJob(); - $job->handle(); - - expect(DeviceModel::count())->toBe(0); -}); - -test('fetch device models job handles missing data field', function () { - Http::fake([ - 'usetrmnl.com/api/models' => Http::response([ - 'message' => 'No data available', - ], 200), - ]); - - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated device models', ['count' => 0]); - - $job = new FetchDeviceModelsJob(); - $job->handle(); - - expect(DeviceModel::count())->toBe(0); -}); - -test('fetch device models job handles non-array data', function () { - Http::fake([ - 'usetrmnl.com/api/models' => Http::response([ - 'data' => 'invalid-data', - ], 200), - ]); - - Log::shouldReceive('error') - ->once() - ->with('Invalid response format from device models API', Mockery::type('array')); - - $job = new FetchDeviceModelsJob(); - $job->handle(); - - expect(DeviceModel::count())->toBe(0); -}); - -test('fetch device models job handles api failure', function () { - Http::fake([ - 'usetrmnl.com/api/models' => Http::response([ - 'error' => 'Internal Server Error', - ], 500), - ]); - - Log::shouldReceive('error') - ->once() - ->with('Failed to fetch device models from API', [ - 'status' => 500, - 'body' => '{"error":"Internal Server Error"}', - ]); - - $job = new FetchDeviceModelsJob(); - $job->handle(); - - expect(DeviceModel::count())->toBe(0); -}); - -test('fetch device models job handles network exception', function () { - Http::fake([ - 'usetrmnl.com/api/models' => function () { - throw new Exception('Network connection failed'); - }, - ]); - - Log::shouldReceive('error') - ->once() - ->with('Exception occurred while fetching device models', Mockery::type('array')); - - $job = new FetchDeviceModelsJob(); - $job->handle(); - - expect(DeviceModel::count())->toBe(0); -}); - -test('fetch device models job handles device model with missing name', function () { - Http::fake([ - 'usetrmnl.com/api/models' => Http::response([ - 'data' => [ - [ - 'label' => 'Model without name', - 'description' => 'This model has no name', - ], - ], - ], 200), - ]); - - Log::shouldReceive('warning') - ->once() - ->with('Device model data missing name field', Mockery::type('array')); - - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated device models', ['count' => 1]); - - $job = new FetchDeviceModelsJob(); - $job->handle(); - - expect(DeviceModel::count())->toBe(0); -}); - -test('fetch device models job handles device model with partial data', function () { - Http::fake([ - 'usetrmnl.com/api/models' => Http::response([ - 'data' => [ - [ - 'name' => 'minimal-model', - // Only name provided, other fields should use defaults - ], - ], - ], 200), - ]); - - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated device models', ['count' => 1]); - - $job = new FetchDeviceModelsJob(); - $job->handle(); - - $deviceModel = DeviceModel::where('name', 'minimal-model')->first(); - expect($deviceModel)->not->toBeNull(); - expect($deviceModel->label)->toBe(''); - expect($deviceModel->description)->toBe(''); - expect($deviceModel->width)->toBe(0); - expect($deviceModel->height)->toBe(0); - expect($deviceModel->colors)->toBe(0); - expect($deviceModel->bit_depth)->toBe(0); - expect($deviceModel->scale_factor)->toBe(1.0); - expect($deviceModel->rotation)->toBe(0); - expect($deviceModel->mime_type)->toBe(''); - expect($deviceModel->offset_x)->toBe(0); - expect($deviceModel->offset_y)->toBe(0); - expect($deviceModel->source)->toBe('api'); -}); - -test('fetch device models job updates existing device model', function () { - // Create an existing device model - $existingModel = DeviceModel::factory()->create([ - 'name' => 'existing-model', - 'label' => 'Old Label', - 'width' => 400, - 'height' => 300, - ]); - - Http::fake([ - 'usetrmnl.com/api/models' => Http::response([ - 'data' => [ - [ - 'name' => 'existing-model', - 'label' => 'Updated Label', - 'description' => 'Updated description', - 'width' => 800, - 'height' => 600, - 'colors' => 4, - 'bit_depth' => 2, - 'scale_factor' => 1.0, - 'rotation' => 0, - 'mime_type' => 'image/png', - 'offset_x' => 0, - 'offset_y' => 0, - 'published_at' => '2023-01-01T00:00:00Z', - ], - ], - ], 200), - ]); - - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated device models', ['count' => 1]); - - $job = new FetchDeviceModelsJob(); - $job->handle(); - - $existingModel->refresh(); - expect($existingModel->label)->toBe('Updated Label'); - expect($existingModel->description)->toBe('Updated description'); - expect($existingModel->width)->toBe(800); - expect($existingModel->height)->toBe(600); - expect($existingModel->source)->toBe('api'); -}); - -test('fetch device models job handles processing exception for individual model', function () { - Http::fake([ - 'usetrmnl.com/api/models' => Http::response([ - 'data' => [ - [ - 'name' => 'valid-model', - 'label' => 'Valid Model', - 'width' => 800, - 'height' => 480, - ], - [ - 'name' => null, // This will cause an exception in processing - 'label' => 'Invalid Model', - ], - ], - ], 200), - ]); - - Log::shouldReceive('warning') - ->once() - ->with('Device model data missing name field', Mockery::type('array')); - - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated device models', ['count' => 2]); - - $job = new FetchDeviceModelsJob(); - $job->handle(); - - // Should still create the valid model - expect(DeviceModel::where('name', 'valid-model')->exists())->toBeTrue(); - expect(DeviceModel::count())->toBe(1); -}); diff --git a/tests/Feature/Jobs/FirmwareDownloadJobTest.php b/tests/Feature/Jobs/FirmwareDownloadJobTest.php index 7ae9417..8d09866 100644 --- a/tests/Feature/Jobs/FirmwareDownloadJobTest.php +++ b/tests/Feature/Jobs/FirmwareDownloadJobTest.php @@ -14,7 +14,6 @@ test('it creates firmwares directory if it does not exist', function () { $firmware = Firmware::factory()->create([ 'url' => 'https://example.com/firmware.bin', 'version_tag' => '1.0.0', - 'storage_location' => null, ]); Http::fake([ @@ -34,127 +33,9 @@ test('it downloads firmware and updates storage location', function () { $firmware = Firmware::factory()->create([ 'url' => 'https://example.com/firmware.bin', 'version_tag' => '1.0.0', - 'storage_location' => null, ]); (new FirmwareDownloadJob($firmware))->handle(); expect($firmware->fresh()->storage_location)->toBe('firmwares/FW1.0.0.bin'); }); - -test('it handles connection exception gracefully', function () { - $firmware = Firmware::factory()->create([ - 'url' => 'https://example.com/firmware.bin', - 'version_tag' => '1.0.0', - 'storage_location' => null, - ]); - - Http::fake([ - 'https://example.com/firmware.bin' => function () { - throw new Illuminate\Http\Client\ConnectionException('Connection failed'); - }, - ]); - - Illuminate\Support\Facades\Log::shouldReceive('error') - ->once() - ->with('Firmware download failed: Connection failed'); - - (new FirmwareDownloadJob($firmware))->handle(); - - // Storage location should not be updated on failure - expect($firmware->fresh()->storage_location)->toBeNull(); -}); - -test('it handles general exception gracefully', function () { - $firmware = Firmware::factory()->create([ - 'url' => 'https://example.com/firmware.bin', - 'version_tag' => '1.0.0', - 'storage_location' => null, - ]); - - Http::fake([ - 'https://example.com/firmware.bin' => function () { - throw new Exception('Unexpected error'); - }, - ]); - - Illuminate\Support\Facades\Log::shouldReceive('error') - ->once() - ->with('An unexpected error occurred: Unexpected error'); - - (new FirmwareDownloadJob($firmware))->handle(); - - // Storage location should not be updated on failure - expect($firmware->fresh()->storage_location)->toBeNull(); -}); - -test('it handles firmware with special characters in version tag', function () { - Http::fake([ - 'https://example.com/firmware.bin' => Http::response('fake firmware content', 200), - ]); - - $firmware = Firmware::factory()->create([ - 'url' => 'https://example.com/firmware.bin', - 'version_tag' => '1.0.0-beta', - ]); - - (new FirmwareDownloadJob($firmware))->handle(); - - expect($firmware->fresh()->storage_location)->toBe('firmwares/FW1.0.0-beta.bin'); -}); - -test('it handles firmware with long version tag', function () { - Http::fake([ - 'https://example.com/firmware.bin' => Http::response('fake firmware content', 200), - ]); - - $firmware = Firmware::factory()->create([ - 'url' => 'https://example.com/firmware.bin', - 'version_tag' => '1.0.0.1234.5678.90', - ]); - - (new FirmwareDownloadJob($firmware))->handle(); - - expect($firmware->fresh()->storage_location)->toBe('firmwares/FW1.0.0.1234.5678.90.bin'); -}); - -test('it creates firmwares directory even when it already exists', function () { - $firmware = Firmware::factory()->create([ - 'url' => 'https://example.com/firmware.bin', - 'version_tag' => '1.0.0', - 'storage_location' => null, - ]); - - Http::fake([ - 'https://example.com/firmware.bin' => Http::response('fake firmware content', 200), - ]); - - // Directory already exists from beforeEach - expect(Storage::disk('public')->exists('firmwares'))->toBeTrue(); - - (new FirmwareDownloadJob($firmware))->handle(); - - // Should still work fine - expect($firmware->fresh()->storage_location)->toBe('firmwares/FW1.0.0.bin'); -}); - -test('it handles http error response', function () { - $firmware = Firmware::factory()->create([ - 'url' => 'https://example.com/firmware.bin', - 'version_tag' => '1.0.0', - 'storage_location' => null, - ]); - - Http::fake([ - 'https://example.com/firmware.bin' => Http::response('Not Found', 404), - ]); - - Illuminate\Support\Facades\Log::shouldReceive('error') - ->once() - ->with(Mockery::type('string')); - - (new FirmwareDownloadJob($firmware))->handle(); - - // Storage location should not be updated on failure - expect($firmware->fresh()->storage_location)->toBeNull(); -}); diff --git a/tests/Feature/Jobs/NotifyDeviceBatteryLowJobTest.php b/tests/Feature/Jobs/NotifyDeviceBatteryLowJobTest.php deleted file mode 100644 index 5ac9c17..0000000 --- a/tests/Feature/Jobs/NotifyDeviceBatteryLowJobTest.php +++ /dev/null @@ -1,140 +0,0 @@ - 20]); - - $user = User::factory()->create(); - $device = Device::factory()->create([ - 'user_id' => $user->id, - 'last_battery_voltage' => 3.0, // This should result in low battery percentage - 'battery_notification_sent' => false, - ]); - - $job = new NotifyDeviceBatteryLowJob(); - $job->handle(); - - Notification::assertSentTo($user, BatteryLow::class); - - $device->refresh(); - expect($device->battery_notification_sent)->toBeTrue(); -}); - -test('it does not send notification when battery is above threshold', function () { - Notification::fake(); - - config(['app.notifications.battery_low.warn_at_percent' => 20]); - - $user = User::factory()->create(); - $device = Device::factory()->create([ - 'user_id' => $user->id, - 'last_battery_voltage' => 4.0, // This should result in high battery percentage - 'battery_notification_sent' => false, - ]); - - $job = new NotifyDeviceBatteryLowJob(); - $job->handle(); - - Notification::assertNotSentTo($user, BatteryLow::class); - - $device->refresh(); - expect($device->battery_notification_sent)->toBeFalse(); -}); - -test('it does not send notification when already sent', function () { - Notification::fake(); - - config(['app.notifications.battery_low.warn_at_percent' => 20]); - - $user = User::factory()->create(); - $device = Device::factory()->create([ - 'user_id' => $user->id, - 'last_battery_voltage' => 3.0, // Low battery - 'battery_notification_sent' => true, // Already sent - ]); - - $job = new NotifyDeviceBatteryLowJob(); - $job->handle(); - - Notification::assertNotSentTo($user, BatteryLow::class); -}); - -test('it resets notification flag when battery is above threshold', function () { - Notification::fake(); - - config(['app.notifications.battery_low.warn_at_percent' => 20]); - - $user = User::factory()->create(); - $device = Device::factory()->create([ - 'user_id' => $user->id, - 'last_battery_voltage' => 4.0, // High battery - 'battery_notification_sent' => true, // Was previously sent - ]); - - $job = new NotifyDeviceBatteryLowJob(); - $job->handle(); - - Notification::assertNotSentTo($user, BatteryLow::class); - - $device->refresh(); - expect($device->battery_notification_sent)->toBeFalse(); -}); - -test('it skips devices without associated user', function () { - Notification::fake(); - - config(['app.notifications.battery_low.warn_at_percent' => 20]); - - $device = Device::factory()->create([ - 'user_id' => null, - 'last_battery_voltage' => 3.0, // Low battery - 'battery_notification_sent' => false, - ]); - - $job = new NotifyDeviceBatteryLowJob(); - $job->handle(); - - Notification::assertNothingSent(); -}); - -test('it processes multiple devices correctly', function () { - Notification::fake(); - - config(['app.notifications.battery_low.warn_at_percent' => 20]); - - $user1 = User::factory()->create(); - $user2 = User::factory()->create(); - - $device1 = Device::factory()->create([ - 'user_id' => $user1->id, - 'last_battery_voltage' => 3.0, // Low battery - 'battery_notification_sent' => false, - ]); - - $device2 = Device::factory()->create([ - 'user_id' => $user2->id, - 'last_battery_voltage' => 4.0, // High battery - 'battery_notification_sent' => false, - ]); - - $job = new NotifyDeviceBatteryLowJob(); - $job->handle(); - - Notification::assertSentTo($user1, BatteryLow::class); - Notification::assertNotSentTo($user2, BatteryLow::class); - - $device1->refresh(); - $device2->refresh(); - - expect($device1->battery_notification_sent)->toBeTrue(); - expect($device2->battery_notification_sent)->toBeFalse(); -}); diff --git a/tests/Feature/Livewire/Actions/DeviceAutoJoinTest.php b/tests/Feature/Livewire/Actions/DeviceAutoJoinTest.php deleted file mode 100644 index d263334..0000000 --- a/tests/Feature/Livewire/Actions/DeviceAutoJoinTest.php +++ /dev/null @@ -1,115 +0,0 @@ -create(['assign_new_devices' => false]); - - Livewire::actingAs($user) - ->test(DeviceAutoJoin::class) - ->assertSee('Permit Auto-Join') - ->assertSet('deviceAutojoin', false) - ->assertSet('isFirstUser', true); -}); - -test('device auto join component initializes with user settings', function () { - $user = User::factory()->create(['assign_new_devices' => true]); - - Livewire::actingAs($user) - ->test(DeviceAutoJoin::class) - ->assertSet('deviceAutojoin', true) - ->assertSet('isFirstUser', true); -}); - -test('device auto join component identifies first user correctly', function () { - $firstUser = User::factory()->create(['id' => 1, 'assign_new_devices' => false]); - $otherUser = User::factory()->create(['id' => 2, 'assign_new_devices' => false]); - - Livewire::actingAs($firstUser) - ->test(DeviceAutoJoin::class) - ->assertSet('isFirstUser', true); - - Livewire::actingAs($otherUser) - ->test(DeviceAutoJoin::class) - ->assertSet('isFirstUser', false); -}); - -test('device auto join component updates user setting when toggled', function () { - $user = User::factory()->create(['assign_new_devices' => false]); - - Livewire::actingAs($user) - ->test(DeviceAutoJoin::class) - ->set('deviceAutojoin', true) - ->assertSet('deviceAutojoin', true); - - $user->refresh(); - expect($user->assign_new_devices)->toBeTrue(); -}); - -// Validation test removed - Livewire automatically handles boolean conversion - -test('device auto join component handles false value correctly', function () { - $user = User::factory()->create(['assign_new_devices' => true]); - - Livewire::actingAs($user) - ->test(DeviceAutoJoin::class) - ->set('deviceAutojoin', false) - ->assertSet('deviceAutojoin', false); - - $user->refresh(); - expect($user->assign_new_devices)->toBeFalse(); -}); - -test('device auto join component only updates when deviceAutojoin property changes', function () { - $user = User::factory()->create(['assign_new_devices' => false]); - - $component = Livewire::actingAs($user) - ->test(DeviceAutoJoin::class); - - // Set a different property to ensure it doesn't trigger the update - $component->set('isFirstUser', true); - - $user->refresh(); - expect($user->assign_new_devices)->toBeFalse(); -}); - -test('device auto join component renders correct view', function () { - $user = User::factory()->create(); - - Livewire::actingAs($user) - ->test(DeviceAutoJoin::class) - ->assertViewIs('livewire.actions.device-auto-join'); -}); - -test('device auto join component works with authenticated user', function () { - $user = User::factory()->create(['assign_new_devices' => true]); - - $component = Livewire::actingAs($user) - ->test(DeviceAutoJoin::class); - - expect($component->instance()->deviceAutojoin)->toBeTrue(); - expect($component->instance()->isFirstUser)->toBe($user->id === 1); -}); - -test('device auto join component handles multiple updates correctly', function () { - $user = User::factory()->create(['assign_new_devices' => false]); - - $component = Livewire::actingAs($user) - ->test(DeviceAutoJoin::class) - ->set('deviceAutojoin', true); - - $user->refresh(); - expect($user->assign_new_devices)->toBeTrue(); - - $component->set('deviceAutojoin', false); - - $user->refresh(); - expect($user->assign_new_devices)->toBeFalse(); -}); diff --git a/tests/Unit/Liquid/Filters/LocalizationTest.php b/tests/Unit/Liquid/Filters/LocalizationTest.php index 2ba3dd2..384c837 100644 --- a/tests/Unit/Liquid/Filters/LocalizationTest.php +++ b/tests/Unit/Liquid/Filters/LocalizationTest.php @@ -60,78 +60,3 @@ test('l_word returns original word for unknown locales', function () { expect($filter->l_word('today', 'unknown-locale'))->toBe('today'); }); - -test('l_date handles locale parameter', function () { - $filter = new Localization(); - $date = '2025-01-11'; - - $result = $filter->l_date($date, 'Y-m-d', 'de'); - - // The result should still contain the date components - expect($result)->toContain('2025'); - expect($result)->toContain('01'); - expect($result)->toContain('11'); -}); - -test('l_date handles null locale parameter', function () { - $filter = new Localization(); - $date = '2025-01-11'; - - $result = $filter->l_date($date, 'Y-m-d', null); - - // Should work the same as default - expect($result)->toContain('2025'); - expect($result)->toContain('01'); - expect($result)->toContain('11'); -}); - -test('l_date handles different date formats with locale', function () { - $filter = new Localization(); - $date = '2025-01-11'; - - $result = $filter->l_date($date, '%B %d, %Y', 'en'); - - // Should contain the month name and date - expect($result)->toContain('2025'); - expect($result)->toContain('11'); -}); - -test('l_date handles DateTimeInterface objects with locale', function () { - $filter = new Localization(); - $date = new DateTimeImmutable('2025-01-11'); - - $result = $filter->l_date($date, 'Y-m-d', 'fr'); - - // Should still format correctly - expect($result)->toContain('2025'); - expect($result)->toContain('01'); - expect($result)->toContain('11'); -}); - -test('l_date handles invalid date gracefully', function () { - $filter = new Localization(); - $invalidDate = 'invalid-date'; - - // This should throw an exception or return a default value - // The exact behavior depends on Carbon's implementation - expect(fn () => $filter->l_date($invalidDate))->toThrow(Exception::class); -}); - -test('l_word handles empty string', function () { - $filter = new Localization(); - - expect($filter->l_word('', 'de'))->toBe(''); -}); - -test('l_word handles special characters', function () { - $filter = new Localization(); - - // Test with a word that has special characters - expect($filter->l_word('café', 'de'))->toBe('café'); -}); - -test('l_word handles numeric strings', function () { - $filter = new Localization(); - - expect($filter->l_word('123', 'de'))->toBe('123'); -}); diff --git a/tests/Unit/Liquid/Filters/NumbersTest.php b/tests/Unit/Liquid/Filters/NumbersTest.php index 7ce736a..8ea73bf 100644 --- a/tests/Unit/Liquid/Filters/NumbersTest.php +++ b/tests/Unit/Liquid/Filters/NumbersTest.php @@ -42,97 +42,6 @@ test('number_to_currency handles custom currency symbols', function () { test('number_to_currency handles custom delimiters and separators', function () { $filter = new Numbers(); - $result1 = $filter->number_to_currency(1234.57, '£', '.', ','); - $result2 = $filter->number_to_currency(1234.57, '€', ',', '.'); - - expect($result1)->toContain('1.234,57'); - expect($result1)->toContain('£'); - expect($result2)->toContain('1,234.57'); - expect($result2)->toContain('€'); -}); - -test('number_with_delimiter handles string numbers', function () { - $filter = new Numbers(); - - expect($filter->number_with_delimiter('1234'))->toBe('1,234'); - expect($filter->number_with_delimiter('1234.56'))->toBe('1,234.56'); -}); - -test('number_with_delimiter handles negative numbers', function () { - $filter = new Numbers(); - - expect($filter->number_with_delimiter(-1234))->toBe('-1,234'); - expect($filter->number_with_delimiter(-1234.56))->toBe('-1,234.56'); -}); - -test('number_with_delimiter handles zero', function () { - $filter = new Numbers(); - - expect($filter->number_with_delimiter(0))->toBe('0'); - expect($filter->number_with_delimiter(0.0))->toBe('0.00'); -}); - -test('number_with_delimiter handles very small numbers', function () { - $filter = new Numbers(); - - expect($filter->number_with_delimiter(0.01))->toBe('0.01'); - expect($filter->number_with_delimiter(0.001))->toBe('0.00'); -}); - -test('number_to_currency handles string numbers', function () { - $filter = new Numbers(); - - expect($filter->number_to_currency('1234'))->toBe('$1,234'); - expect($filter->number_to_currency('1234.56'))->toBe('$1,234.56'); -}); - -test('number_to_currency handles negative numbers', function () { - $filter = new Numbers(); - - expect($filter->number_to_currency(-1234))->toBe('-$1,234'); - expect($filter->number_to_currency(-1234.56))->toBe('-$1,234.56'); -}); - -test('number_to_currency handles zero', function () { - $filter = new Numbers(); - - expect($filter->number_to_currency(0))->toBe('$0'); - expect($filter->number_to_currency(0.0))->toBe('$0.00'); -}); - -test('number_to_currency handles currency code conversion', function () { - $filter = new Numbers(); - - expect($filter->number_to_currency(1234, '$'))->toBe('$1,234'); - expect($filter->number_to_currency(1234, '€'))->toBe('€1,234'); - expect($filter->number_to_currency(1234, '£'))->toBe('£1,234'); -}); - -test('number_to_currency handles German locale formatting', function () { - $filter = new Numbers(); - - // When delimiter is '.' and separator is ',', it should use German locale - $result = $filter->number_to_currency(1234.56, 'EUR', '.', ','); - expect($result)->toContain('1.234,56'); -}); - -test('number_with_delimiter handles different decimal separators', function () { - $filter = new Numbers(); - - expect($filter->number_with_delimiter(1234.56, ',', ','))->toBe('1,234,56'); - expect($filter->number_with_delimiter(1234.56, ' ', ','))->toBe('1 234,56'); -}); - -test('number_to_currency handles very large numbers', function () { - $filter = new Numbers(); - - expect($filter->number_to_currency(1000000))->toBe('$1,000,000'); - expect($filter->number_to_currency(1000000.50))->toBe('$1,000,000.50'); -}); - -test('number_with_delimiter handles very large numbers', function () { - $filter = new Numbers(); - - expect($filter->number_with_delimiter(1000000))->toBe('1,000,000'); - expect($filter->number_with_delimiter(1000000.50))->toBe('1,000,000.50'); + expect($filter->number_to_currency(1234.57, '£', '.', ','))->toBe('1.234,57 £'); + expect($filter->number_to_currency(1234.57, '€', ',', '.'))->toBe('€1,234.57'); }); diff --git a/tests/Unit/Liquid/Filters/StringMarkupTest.php b/tests/Unit/Liquid/Filters/StringMarkupTest.php index b3498c3..4021a07 100644 --- a/tests/Unit/Liquid/Filters/StringMarkupTest.php +++ b/tests/Unit/Liquid/Filters/StringMarkupTest.php @@ -88,83 +88,3 @@ test('strip_html handles nested tags', function () { expect($filter->strip_html($html))->toBe('Paragraph with nested tags.'); }); - -test('markdown_to_html handles CommonMarkException gracefully', function () { - $filter = new StringMarkup(); - - // Create a mock that throws CommonMarkException - $filter = new class extends StringMarkup - { - public function markdown_to_html(string $markdown): ?string - { - try { - // Simulate CommonMarkException - throw new Exception('Invalid markdown'); - } catch (Exception $e) { - Illuminate\Support\Facades\Log::error('Markdown conversion error: '.$e->getMessage()); - } - - return null; - } - }; - - $result = $filter->markdown_to_html('invalid markdown'); - - expect($result)->toBeNull(); -}); - -test('markdown_to_html handles empty string', function () { - $filter = new StringMarkup(); - - $result = $filter->markdown_to_html(''); - - expect($result)->toBe(''); -}); - -test('markdown_to_html handles complex markdown', function () { - $filter = new StringMarkup(); - $markdown = "# Heading\n\nThis is a paragraph with **bold** and *italic* text.\n\n- List item 1\n- List item 2\n\n[Link](https://example.com)"; - - $result = $filter->markdown_to_html($markdown); - - expect($result)->toContain('

Heading

'); - expect($result)->toContain('bold'); - expect($result)->toContain('italic'); - expect($result)->toContain('