test: improve coverage
Some checks are pending
tests / ci (push) Waiting to run

This commit is contained in:
Benjamin Nussbaum 2025-09-23 23:56:11 +02:00
parent 4f251bf37e
commit 42b515e322
21 changed files with 2212 additions and 32 deletions

View file

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
use Database\Seeders\ExampleRecipesSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('example recipes seeder command calls seeder with correct user id', function () {
$seeder = Mockery::mock(ExampleRecipesSeeder::class);
$seeder->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);
});

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('firmware check command has correct signature', function () {
$command = $this->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);
});

View file

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
use App\Models\Device;
use App\Models\Firmware;
use App\Models\User;
test('firmware update command has correct signature', function () {
$this->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);
});

View file

@ -0,0 +1,154 @@
<?php
declare(strict_types=1);
use App\Models\Device;
use App\Models\Playlist;
use App\Models\PlaylistItem;
use App\Models\Plugin;
use App\Models\User;
test('mashup create command has correct signature', function () {
$this->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);
});

View file

@ -0,0 +1,188 @@
<?php
declare(strict_types=1);
use function Pest\Laravel\mock;
test('oidc test command has correct signature', function () {
$this->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);
});