mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-13 15:07:49 +00:00
This commit is contained in:
parent
a88e72b75e
commit
ba3bf31bb7
29 changed files with 2379 additions and 215 deletions
35
tests/Feature/Api/DeviceModelsEndpointTest.php
Normal file
35
tests/Feature/Api/DeviceModelsEndpointTest.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\User;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
it('allows an authenticated user to fetch device models', function (): void {
|
||||
$user = User::factory()->create();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->getJson('/api/device-models');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'*' => [
|
||||
'id',
|
||||
'name',
|
||||
'label',
|
||||
'description',
|
||||
'width',
|
||||
'height',
|
||||
'bit_depth',
|
||||
],
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('blocks unauthenticated users from accessing device models', function (): void {
|
||||
$response = $this->getJson('/api/device-models');
|
||||
|
||||
$response->assertUnauthorized();
|
||||
});
|
||||
|
|
@ -1,158 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Laravel\Socialite\Two\User as SocialiteUser;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class OidcAuthenticationTest extends TestCase
|
||||
beforeEach(function (): void {
|
||||
// Enable OIDC for testing
|
||||
Config::set('services.oidc.enabled', true);
|
||||
Config::set('services.oidc.endpoint', 'https://example.com/oidc');
|
||||
Config::set('services.oidc.client_id', 'test-client-id');
|
||||
Config::set('services.oidc.client_secret', 'test-client-secret');
|
||||
|
||||
// Mock Socialite OIDC driver to avoid any external HTTP calls
|
||||
$provider = Mockery::mock();
|
||||
$provider->shouldReceive('redirect')->andReturn(redirect('/fake-oidc-redirect'));
|
||||
|
||||
// Default Socialite user returned by callback
|
||||
$socialiteUser = mockSocialiteUser();
|
||||
$provider->shouldReceive('user')->andReturn($socialiteUser);
|
||||
|
||||
Socialite::shouldReceive('driver')
|
||||
->with('oidc')
|
||||
->andReturn($provider);
|
||||
});
|
||||
|
||||
afterEach(function (): void {
|
||||
Mockery::close();
|
||||
});
|
||||
|
||||
it('oidc redirect works when enabled', function (): void {
|
||||
$response = $this->get(route('auth.oidc.redirect'));
|
||||
|
||||
// Since we're using a mock OIDC provider, this will likely fail
|
||||
// but we can check that the route exists and is accessible
|
||||
expect($response->getStatusCode())->not->toBe(404);
|
||||
});
|
||||
|
||||
it('oidc redirect fails when disabled', function (): void {
|
||||
Config::set('services.oidc.enabled', false);
|
||||
|
||||
$response = $this->get(route('auth.oidc.redirect'));
|
||||
|
||||
$response->assertRedirect(route('login'));
|
||||
$response->assertSessionHasErrors(['oidc' => 'OIDC authentication is not enabled.']);
|
||||
});
|
||||
|
||||
it('oidc callback creates new user (placeholder)', function (): void {
|
||||
mockSocialiteUser();
|
||||
|
||||
$this->get(route('auth.oidc.callback'));
|
||||
|
||||
// We expect to be redirected to dashboard after successful authentication
|
||||
// In a real test, this would be mocked properly
|
||||
expect(true)->toBeTrue(); // Placeholder assertion
|
||||
});
|
||||
|
||||
it('oidc callback updates existing user by oidc_sub (placeholder)', function (): void {
|
||||
// Create a user with OIDC sub
|
||||
User::factory()->create([
|
||||
'oidc_sub' => 'test-sub-123',
|
||||
'name' => 'Old Name',
|
||||
'email' => 'old@example.com',
|
||||
]);
|
||||
|
||||
mockSocialiteUser([
|
||||
'id' => 'test-sub-123',
|
||||
'name' => 'Updated Name',
|
||||
'email' => 'updated@example.com',
|
||||
]);
|
||||
|
||||
// This would need proper mocking of Socialite in a real test
|
||||
expect(true)->toBeTrue(); // Placeholder assertion
|
||||
});
|
||||
|
||||
it('oidc callback links existing user by email (placeholder)', function (): void {
|
||||
// Create a user without OIDC sub but with matching email
|
||||
User::factory()->create([
|
||||
'oidc_sub' => null,
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
|
||||
mockSocialiteUser([
|
||||
'id' => 'test-sub-456',
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
|
||||
// This would need proper mocking of Socialite in a real test
|
||||
expect(true)->toBeTrue(); // Placeholder assertion
|
||||
});
|
||||
|
||||
it('oidc callback fails when disabled', function (): void {
|
||||
Config::set('services.oidc.enabled', false);
|
||||
|
||||
$response = $this->get(route('auth.oidc.callback'));
|
||||
|
||||
$response->assertRedirect(route('login'));
|
||||
$response->assertSessionHasErrors(['oidc' => 'OIDC authentication is not enabled.']);
|
||||
});
|
||||
|
||||
it('login view shows oidc button when enabled', function (): void {
|
||||
$response = $this->get(route('login'));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee('Continue with OIDC');
|
||||
$response->assertSee('Or');
|
||||
});
|
||||
|
||||
it('login view hides oidc button when disabled', function (): void {
|
||||
Config::set('services.oidc.enabled', false);
|
||||
|
||||
$response = $this->get(route('login'));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertDontSee('Continue with OIDC');
|
||||
});
|
||||
|
||||
it('user model has oidc_sub fillable', function (): void {
|
||||
$user = new User();
|
||||
|
||||
expect($user->getFillable())->toContain('oidc_sub');
|
||||
});
|
||||
|
||||
/**
|
||||
* Mock a Socialite user for testing.
|
||||
*
|
||||
* @param array<string, mixed> $userData
|
||||
*/
|
||||
function mockSocialiteUser(array $userData = []): SocialiteUser
|
||||
{
|
||||
use RefreshDatabase;
|
||||
$defaultData = [
|
||||
'id' => 'test-sub-123',
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
'avatar' => null,
|
||||
];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Enable OIDC for testing
|
||||
Config::set('services.oidc.enabled', true);
|
||||
Config::set('services.oidc.endpoint', 'https://example.com/oidc');
|
||||
Config::set('services.oidc.client_id', 'test-client-id');
|
||||
Config::set('services.oidc.client_secret', 'test-client-secret');
|
||||
}
|
||||
$userData = array_merge($defaultData, $userData);
|
||||
|
||||
public function test_oidc_redirect_works_when_enabled()
|
||||
{
|
||||
$response = $this->get(route('auth.oidc.redirect'));
|
||||
/** @var SocialiteUser $socialiteUser */
|
||||
$socialiteUser = Mockery::mock(SocialiteUser::class);
|
||||
$socialiteUser->shouldReceive('getId')->andReturn($userData['id']);
|
||||
$socialiteUser->shouldReceive('getName')->andReturn($userData['name']);
|
||||
$socialiteUser->shouldReceive('getEmail')->andReturn($userData['email']);
|
||||
$socialiteUser->shouldReceive('getAvatar')->andReturn($userData['avatar']);
|
||||
|
||||
// Since we're using a mock OIDC provider, this will likely fail
|
||||
// but we can check that the route exists and is accessible
|
||||
$this->assertNotEquals(404, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function test_oidc_redirect_fails_when_disabled()
|
||||
{
|
||||
Config::set('services.oidc.enabled', false);
|
||||
|
||||
$response = $this->get(route('auth.oidc.redirect'));
|
||||
|
||||
$response->assertRedirect(route('login'));
|
||||
$response->assertSessionHasErrors(['oidc' => 'OIDC authentication is not enabled.']);
|
||||
}
|
||||
|
||||
public function test_oidc_callback_creates_new_user()
|
||||
{
|
||||
$mockUser = $this->mockSocialiteUser();
|
||||
|
||||
$response = $this->get(route('auth.oidc.callback'));
|
||||
|
||||
// We expect to be redirected to dashboard after successful authentication
|
||||
// In a real test, this would be mocked properly
|
||||
$this->assertTrue(true); // Placeholder assertion
|
||||
}
|
||||
|
||||
public function test_oidc_callback_updates_existing_user_by_oidc_sub()
|
||||
{
|
||||
// Create a user with OIDC sub
|
||||
$user = User::factory()->create([
|
||||
'oidc_sub' => 'test-sub-123',
|
||||
'name' => 'Old Name',
|
||||
'email' => 'old@example.com',
|
||||
]);
|
||||
|
||||
$mockUser = $this->mockSocialiteUser([
|
||||
'id' => 'test-sub-123',
|
||||
'name' => 'Updated Name',
|
||||
'email' => 'updated@example.com',
|
||||
]);
|
||||
|
||||
// This would need proper mocking of Socialite in a real test
|
||||
$this->assertTrue(true); // Placeholder assertion
|
||||
}
|
||||
|
||||
public function test_oidc_callback_links_existing_user_by_email()
|
||||
{
|
||||
// Create a user without OIDC sub but with matching email
|
||||
$user = User::factory()->create([
|
||||
'oidc_sub' => null,
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
|
||||
$mockUser = $this->mockSocialiteUser([
|
||||
'id' => 'test-sub-456',
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
|
||||
// This would need proper mocking of Socialite in a real test
|
||||
$this->assertTrue(true); // Placeholder assertion
|
||||
}
|
||||
|
||||
public function test_oidc_callback_fails_when_disabled()
|
||||
{
|
||||
Config::set('services.oidc.enabled', false);
|
||||
|
||||
$response = $this->get(route('auth.oidc.callback'));
|
||||
|
||||
$response->assertRedirect(route('login'));
|
||||
$response->assertSessionHasErrors(['oidc' => 'OIDC authentication is not enabled.']);
|
||||
}
|
||||
|
||||
public function test_login_view_shows_oidc_button_when_enabled()
|
||||
{
|
||||
$response = $this->get(route('login'));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee('Continue with OIDC');
|
||||
$response->assertSee('Or');
|
||||
}
|
||||
|
||||
public function test_login_view_hides_oidc_button_when_disabled()
|
||||
{
|
||||
Config::set('services.oidc.enabled', false);
|
||||
|
||||
$response = $this->get(route('login'));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertDontSee('Continue with OIDC');
|
||||
}
|
||||
|
||||
public function test_user_model_has_oidc_sub_fillable()
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
$this->assertContains('oidc_sub', $user->getFillable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock a Socialite user for testing.
|
||||
*/
|
||||
protected function mockSocialiteUser(array $userData = [])
|
||||
{
|
||||
$defaultData = [
|
||||
'id' => 'test-sub-123',
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
'avatar' => null,
|
||||
];
|
||||
|
||||
$userData = array_merge($defaultData, $userData);
|
||||
|
||||
$socialiteUser = Mockery::mock(SocialiteUser::class);
|
||||
$socialiteUser->shouldReceive('getId')->andReturn($userData['id']);
|
||||
$socialiteUser->shouldReceive('getName')->andReturn($userData['name']);
|
||||
$socialiteUser->shouldReceive('getEmail')->andReturn($userData['email']);
|
||||
$socialiteUser->shouldReceive('getAvatar')->andReturn($userData['avatar']);
|
||||
|
||||
return $socialiteUser;
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Mockery::close();
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
||||
return $socialiteUser;
|
||||
}
|
||||
|
|
|
|||
89
tests/Feature/DeviceModelsTest.php
Normal file
89
tests/Feature/DeviceModelsTest.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\DeviceModel;
|
||||
use App\Models\User;
|
||||
|
||||
it('allows a user to view the device models page', function (): void {
|
||||
$user = User::factory()->create();
|
||||
$deviceModels = DeviceModel::factory()->count(3)->create();
|
||||
|
||||
$response = $this->actingAs($user)->get('/device-models');
|
||||
|
||||
$response->assertSuccessful();
|
||||
$response->assertSee('Device Models');
|
||||
$response->assertSee('Add Device Model');
|
||||
|
||||
foreach ($deviceModels as $deviceModel) {
|
||||
$response->assertSee($deviceModel->label);
|
||||
$response->assertSee((string) $deviceModel->width);
|
||||
$response->assertSee((string) $deviceModel->height);
|
||||
$response->assertSee((string) $deviceModel->bit_depth);
|
||||
}
|
||||
});
|
||||
|
||||
it('allows creating a device model', function (): void {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$deviceModelData = [
|
||||
'name' => 'test-model',
|
||||
'label' => 'Test Model',
|
||||
'description' => 'A test device model',
|
||||
'width' => 800,
|
||||
'height' => 600,
|
||||
'colors' => 256,
|
||||
'bit_depth' => 8,
|
||||
'scale_factor' => 1.0,
|
||||
'rotation' => 0,
|
||||
'mime_type' => 'image/png',
|
||||
'offset_x' => 0,
|
||||
'offset_y' => 0,
|
||||
];
|
||||
|
||||
$deviceModel = DeviceModel::create($deviceModelData);
|
||||
|
||||
$this->assertDatabaseHas('device_models', $deviceModelData);
|
||||
expect($deviceModel->name)->toBe($deviceModelData['name']);
|
||||
});
|
||||
|
||||
it('allows updating a device model', function (): void {
|
||||
$user = User::factory()->create();
|
||||
$deviceModel = DeviceModel::factory()->create();
|
||||
|
||||
$updatedData = [
|
||||
'name' => 'updated-model',
|
||||
'label' => 'Updated Model',
|
||||
'description' => 'An updated device model',
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
'colors' => 65536,
|
||||
'bit_depth' => 16,
|
||||
'scale_factor' => 1.5,
|
||||
'rotation' => 90,
|
||||
'mime_type' => 'image/jpeg',
|
||||
'offset_x' => 10,
|
||||
'offset_y' => 20,
|
||||
];
|
||||
|
||||
$deviceModel->update($updatedData);
|
||||
|
||||
$this->assertDatabaseHas('device_models', $updatedData);
|
||||
expect($deviceModel->fresh()->name)->toBe($updatedData['name']);
|
||||
});
|
||||
|
||||
it('allows deleting a device model', function (): void {
|
||||
$user = User::factory()->create();
|
||||
$deviceModel = DeviceModel::factory()->create();
|
||||
|
||||
$deviceModelId = $deviceModel->id;
|
||||
$deviceModel->delete();
|
||||
|
||||
$this->assertDatabaseMissing('device_models', ['id' => $deviceModelId]);
|
||||
});
|
||||
|
||||
it('redirects unauthenticated users from the device models page', function (): void {
|
||||
$response = $this->get('/device-models');
|
||||
|
||||
$response->assertRedirect('/login');
|
||||
});
|
||||
20
tests/Feature/FetchDeviceModelsCommandTest.php
Normal file
20
tests/Feature/FetchDeviceModelsCommandTest.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Jobs\FetchDeviceModelsJob;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
test('command dispatches fetch device models job', function () {
|
||||
Queue::fake();
|
||||
|
||||
$this->artisan('device-models:fetch')
|
||||
->expectsOutput('Dispatching FetchDeviceModelsJob...')
|
||||
->expectsOutput('FetchDeviceModelsJob has been dispatched successfully.')
|
||||
->assertExitCode(0);
|
||||
|
||||
Queue::assertPushed(FetchDeviceModelsJob::class);
|
||||
});
|
||||
|
|
@ -10,6 +10,12 @@ uses(Illuminate\Foundation\Testing\RefreshDatabase::class);
|
|||
beforeEach(function () {
|
||||
Storage::fake('public');
|
||||
Storage::disk('public')->makeDirectory('/images/generated');
|
||||
Http::preventStrayRequests();
|
||||
Http::fake([
|
||||
'https://example.com/test-image.bmp*' => Http::response([], 200),
|
||||
'https://trmnl.app/api/log' => Http::response([], 200),
|
||||
'https://example.com/api/log' => Http::response([], 200),
|
||||
]);
|
||||
});
|
||||
|
||||
test('it fetches and processes proxy cloud responses for devices', function () {
|
||||
|
|
|
|||
425
tests/Feature/ImageGenerationServiceTest.php
Normal file
425
tests/Feature/ImageGenerationServiceTest.php
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\ImageFormat;
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceModel;
|
||||
use App\Services\ImageGenerationService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function (): void {
|
||||
Storage::fake('public');
|
||||
Storage::disk('public')->makeDirectory('/images/generated');
|
||||
});
|
||||
|
||||
it('generates image for device without device model', function (): void {
|
||||
// Create a device without a DeviceModel (legacy behavior)
|
||||
$device = Device::factory()->create([
|
||||
'width' => 800,
|
||||
'height' => 480,
|
||||
'rotate' => 0,
|
||||
'image_format' => ImageFormat::PNG_8BIT_GRAYSCALE->value,
|
||||
]);
|
||||
|
||||
$markup = '<div style="background: white; color: black; padding: 20px;">Test Content</div>';
|
||||
$uuid = ImageGenerationService::generateImage($markup, $device->id);
|
||||
|
||||
// Assert the device was updated with a new image UUID
|
||||
$device->refresh();
|
||||
expect($device->current_screen_image)->toBe($uuid);
|
||||
|
||||
// Assert PNG file was created
|
||||
Storage::disk('public')->assertExists("/images/generated/{$uuid}.png");
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('generates image for device with device model', function (): void {
|
||||
// Create a DeviceModel
|
||||
$deviceModel = DeviceModel::factory()->create([
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
'colors' => 256,
|
||||
'bit_depth' => 8,
|
||||
'scale_factor' => 1.0,
|
||||
'rotation' => 0,
|
||||
'mime_type' => 'image/png',
|
||||
'offset_x' => 0,
|
||||
'offset_y' => 0,
|
||||
]);
|
||||
|
||||
// Create a device with the DeviceModel
|
||||
$device = Device::factory()->create([
|
||||
'device_model_id' => $deviceModel->id,
|
||||
]);
|
||||
|
||||
$markup = '<div style="background: white; color: black; padding: 20px;">Test Content</div>';
|
||||
$uuid = ImageGenerationService::generateImage($markup, $device->id);
|
||||
|
||||
// Assert the device was updated with a new image UUID
|
||||
$device->refresh();
|
||||
expect($device->current_screen_image)->toBe($uuid);
|
||||
|
||||
// Assert PNG file was created
|
||||
Storage::disk('public')->assertExists("/images/generated/{$uuid}.png");
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('generates 4-color 2-bit PNG with device model', function (): void {
|
||||
// Create a DeviceModel for 4-color, 2-bit PNG
|
||||
$deviceModel = DeviceModel::factory()->create([
|
||||
'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,
|
||||
]);
|
||||
|
||||
// Create a device with the DeviceModel
|
||||
$device = Device::factory()->create([
|
||||
'device_model_id' => $deviceModel->id,
|
||||
]);
|
||||
|
||||
$markup = '<div style="background: white; color: black; padding: 20px;">Test Content</div>';
|
||||
$uuid = ImageGenerationService::generateImage($markup, $device->id);
|
||||
|
||||
// Assert the device was updated with a new image UUID
|
||||
$device->refresh();
|
||||
expect($device->current_screen_image)->toBe($uuid);
|
||||
|
||||
// Assert PNG file was created
|
||||
Storage::disk('public')->assertExists("/images/generated/{$uuid}.png");
|
||||
|
||||
// Verify the image file has content and isn't blank
|
||||
$imagePath = Storage::disk('public')->path("/images/generated/{$uuid}.png");
|
||||
$imageSize = filesize($imagePath);
|
||||
expect($imageSize)->toBeGreaterThan(200); // Should be at least 200 bytes for a 2-bit PNG
|
||||
|
||||
// Verify it's a valid PNG file
|
||||
$imageInfo = getimagesize($imagePath);
|
||||
expect($imageInfo[0])->toBe(800); // Width
|
||||
expect($imageInfo[1])->toBe(480); // Height
|
||||
expect($imageInfo[2])->toBe(IMAGETYPE_PNG); // PNG type
|
||||
|
||||
// Debug: Check if the image has any non-transparent pixels
|
||||
$image = imagecreatefrompng($imagePath);
|
||||
$width = imagesx($image);
|
||||
$height = imagesy($image);
|
||||
$hasContent = false;
|
||||
|
||||
// Check a few sample pixels to see if there's content
|
||||
for ($x = 0; $x < min(10, $width); $x += 2) {
|
||||
for ($y = 0; $y < min(10, $height); $y += 2) {
|
||||
$color = imagecolorat($image, $x, $y);
|
||||
if ($color !== 0) { // Not black/transparent
|
||||
$hasContent = true;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
imagedestroy($image);
|
||||
expect($hasContent)->toBe(true, 'Image should contain visible content');
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('generates BMP with device model', function (): void {
|
||||
// Create a DeviceModel for BMP format
|
||||
$deviceModel = DeviceModel::factory()->create([
|
||||
'width' => 800,
|
||||
'height' => 480,
|
||||
'colors' => 2,
|
||||
'bit_depth' => 1,
|
||||
'scale_factor' => 1.0,
|
||||
'rotation' => 0,
|
||||
'mime_type' => 'image/bmp',
|
||||
'offset_x' => 0,
|
||||
'offset_y' => 0,
|
||||
]);
|
||||
|
||||
// Create a device with the DeviceModel
|
||||
$device = Device::factory()->create([
|
||||
'device_model_id' => $deviceModel->id,
|
||||
]);
|
||||
|
||||
$markup = '<div style="background: white; color: black; padding: 20px;">Test Content</div>';
|
||||
$uuid = ImageGenerationService::generateImage($markup, $device->id);
|
||||
|
||||
// Assert the device was updated with a new image UUID
|
||||
$device->refresh();
|
||||
expect($device->current_screen_image)->toBe($uuid);
|
||||
|
||||
// Assert BMP file was created
|
||||
Storage::disk('public')->assertExists("/images/generated/{$uuid}.bmp");
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('applies scale factor from device model', function (): void {
|
||||
// Create a DeviceModel with scale factor
|
||||
$deviceModel = DeviceModel::factory()->create([
|
||||
'width' => 800,
|
||||
'height' => 480,
|
||||
'colors' => 256,
|
||||
'bit_depth' => 8,
|
||||
'scale_factor' => 2.0, // Scale up by 2x
|
||||
'rotation' => 0,
|
||||
'mime_type' => 'image/png',
|
||||
'offset_x' => 0,
|
||||
'offset_y' => 0,
|
||||
]);
|
||||
|
||||
// Create a device with the DeviceModel
|
||||
$device = Device::factory()->create([
|
||||
'device_model_id' => $deviceModel->id,
|
||||
]);
|
||||
|
||||
$markup = '<div style="background: white; color: black; padding: 20px;">Test Content</div>';
|
||||
$uuid = ImageGenerationService::generateImage($markup, $device->id);
|
||||
|
||||
// Assert the device was updated with a new image UUID
|
||||
$device->refresh();
|
||||
expect($device->current_screen_image)->toBe($uuid);
|
||||
|
||||
// Assert PNG file was created
|
||||
Storage::disk('public')->assertExists("/images/generated/{$uuid}.png");
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('applies rotation from device model', function (): void {
|
||||
// Create a DeviceModel with rotation
|
||||
$deviceModel = DeviceModel::factory()->create([
|
||||
'width' => 800,
|
||||
'height' => 480,
|
||||
'colors' => 256,
|
||||
'bit_depth' => 8,
|
||||
'scale_factor' => 1.0,
|
||||
'rotation' => 90, // Rotate 90 degrees
|
||||
'mime_type' => 'image/png',
|
||||
'offset_x' => 0,
|
||||
'offset_y' => 0,
|
||||
]);
|
||||
|
||||
// Create a device with the DeviceModel
|
||||
$device = Device::factory()->create([
|
||||
'device_model_id' => $deviceModel->id,
|
||||
]);
|
||||
|
||||
$markup = '<div style="background: white; color: black; padding: 20px;">Test Content</div>';
|
||||
$uuid = ImageGenerationService::generateImage($markup, $device->id);
|
||||
|
||||
// Assert the device was updated with a new image UUID
|
||||
$device->refresh();
|
||||
expect($device->current_screen_image)->toBe($uuid);
|
||||
|
||||
// Assert PNG file was created
|
||||
Storage::disk('public')->assertExists("/images/generated/{$uuid}.png");
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('applies offset from device model', function (): void {
|
||||
// Create a DeviceModel with offset
|
||||
$deviceModel = DeviceModel::factory()->create([
|
||||
'width' => 800,
|
||||
'height' => 480,
|
||||
'colors' => 256,
|
||||
'bit_depth' => 8,
|
||||
'scale_factor' => 1.0,
|
||||
'rotation' => 0,
|
||||
'mime_type' => 'image/png',
|
||||
'offset_x' => 10, // Offset by 10 pixels
|
||||
'offset_y' => 20, // Offset by 20 pixels
|
||||
]);
|
||||
|
||||
// Create a device with the DeviceModel
|
||||
$device = Device::factory()->create([
|
||||
'device_model_id' => $deviceModel->id,
|
||||
]);
|
||||
|
||||
$markup = '<div style="background: white; color: black; padding: 20px;">Test Content</div>';
|
||||
$uuid = ImageGenerationService::generateImage($markup, $device->id);
|
||||
|
||||
// Assert the device was updated with a new image UUID
|
||||
$device->refresh();
|
||||
expect($device->current_screen_image)->toBe($uuid);
|
||||
|
||||
// Assert PNG file was created
|
||||
Storage::disk('public')->assertExists("/images/generated/{$uuid}.png");
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('falls back to device settings when no device model', function (): void {
|
||||
// Create a device with custom settings but no DeviceModel
|
||||
$device = Device::factory()->create([
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
'rotate' => 180,
|
||||
'image_format' => ImageFormat::PNG_8BIT_256C->value,
|
||||
]);
|
||||
|
||||
$markup = '<div style="background: white; color: black; padding: 20px;">Test Content</div>';
|
||||
$uuid = ImageGenerationService::generateImage($markup, $device->id);
|
||||
|
||||
// Assert the device was updated with a new image UUID
|
||||
$device->refresh();
|
||||
expect($device->current_screen_image)->toBe($uuid);
|
||||
|
||||
// Assert PNG file was created
|
||||
Storage::disk('public')->assertExists("/images/generated/{$uuid}.png");
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('handles auto image format for legacy devices', function (): void {
|
||||
// Create a device with AUTO format (legacy behavior)
|
||||
$device = Device::factory()->create([
|
||||
'width' => 800,
|
||||
'height' => 480,
|
||||
'rotate' => 0,
|
||||
'image_format' => ImageFormat::AUTO->value,
|
||||
'last_firmware_version' => '1.6.0', // Modern firmware
|
||||
]);
|
||||
|
||||
$markup = '<div style="background: white; color: black; padding: 20px;">Test Content</div>';
|
||||
$uuid = ImageGenerationService::generateImage($markup, $device->id);
|
||||
|
||||
// Assert the device was updated with a new image UUID
|
||||
$device->refresh();
|
||||
expect($device->current_screen_image)->toBe($uuid);
|
||||
|
||||
// Assert PNG file was created (modern firmware defaults to PNG)
|
||||
Storage::disk('public')->assertExists("/images/generated/{$uuid}.png");
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('cleanupFolder removes unused images', function (): void {
|
||||
// Create active devices with images
|
||||
Device::factory()->create(['current_screen_image' => 'active-uuid-1']);
|
||||
Device::factory()->create(['current_screen_image' => 'active-uuid-2']);
|
||||
|
||||
// Create some test files
|
||||
Storage::disk('public')->put('/images/generated/active-uuid-1.png', 'test');
|
||||
Storage::disk('public')->put('/images/generated/active-uuid-2.png', 'test');
|
||||
Storage::disk('public')->put('/images/generated/inactive-uuid.png', 'test');
|
||||
Storage::disk('public')->put('/images/generated/another-inactive.png', 'test');
|
||||
|
||||
// Run cleanup
|
||||
ImageGenerationService::cleanupFolder();
|
||||
|
||||
// Assert active files are preserved
|
||||
Storage::disk('public')->assertExists('/images/generated/active-uuid-1.png');
|
||||
Storage::disk('public')->assertExists('/images/generated/active-uuid-2.png');
|
||||
|
||||
// Assert inactive files are removed
|
||||
Storage::disk('public')->assertMissing('/images/generated/inactive-uuid.png');
|
||||
Storage::disk('public')->assertMissing('/images/generated/another-inactive.png');
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('cleanupFolder preserves .gitignore', function (): void {
|
||||
// Create gitignore file
|
||||
Storage::disk('public')->put('/images/generated/.gitignore', '*');
|
||||
|
||||
// Create some test files
|
||||
Storage::disk('public')->put('/images/generated/test.png', 'test');
|
||||
|
||||
// Run cleanup
|
||||
ImageGenerationService::cleanupFolder();
|
||||
|
||||
// Assert gitignore is preserved
|
||||
Storage::disk('public')->assertExists('/images/generated/.gitignore');
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('resetIfNotCacheable resets when device models exist', function (): void {
|
||||
// Create a plugin
|
||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
||||
|
||||
// Create a device with DeviceModel (should trigger cache reset)
|
||||
Device::factory()->create([
|
||||
'device_model_id' => DeviceModel::factory()->create()->id,
|
||||
]);
|
||||
|
||||
// Run reset check
|
||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
||||
|
||||
// Assert plugin image was reset
|
||||
$plugin->refresh();
|
||||
expect($plugin->current_image)->toBeNull();
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('resetIfNotCacheable resets when custom dimensions exist', function (): void {
|
||||
// Create a plugin
|
||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
||||
|
||||
// Create a device with custom dimensions (should trigger cache reset)
|
||||
Device::factory()->create([
|
||||
'width' => 1024, // Different from default 800
|
||||
'height' => 768, // Different from default 480
|
||||
]);
|
||||
|
||||
// Run reset check
|
||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
||||
|
||||
// Assert plugin image was reset
|
||||
$plugin->refresh();
|
||||
expect($plugin->current_image)->toBeNull();
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('resetIfNotCacheable preserves image for standard devices', function (): void {
|
||||
// Create a plugin
|
||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
||||
|
||||
// Create devices with standard dimensions (should not trigger cache reset)
|
||||
Device::factory()->count(3)->create([
|
||||
'width' => 800,
|
||||
'height' => 480,
|
||||
'rotate' => 0,
|
||||
]);
|
||||
|
||||
// Run reset check
|
||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
||||
|
||||
// Assert plugin image was preserved
|
||||
$plugin->refresh();
|
||||
expect($plugin->current_image)->toBe('test-uuid');
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('determines correct image format from device model', function (): void {
|
||||
// Test BMP format detection
|
||||
$bmpModel = DeviceModel::factory()->create([
|
||||
'mime_type' => 'image/bmp',
|
||||
'bit_depth' => 1,
|
||||
'colors' => 2,
|
||||
]);
|
||||
|
||||
$device = Device::factory()->create(['device_model_id' => $bmpModel->id]);
|
||||
$markup = '<div>Test</div>';
|
||||
$uuid = ImageGenerationService::generateImage($markup, $device->id);
|
||||
|
||||
$device->refresh();
|
||||
expect($device->current_screen_image)->toBe($uuid);
|
||||
Storage::disk('public')->assertExists("/images/generated/{$uuid}.bmp");
|
||||
|
||||
// Test PNG 8-bit grayscale format detection
|
||||
$pngGrayscaleModel = DeviceModel::factory()->create([
|
||||
'mime_type' => 'image/png',
|
||||
'bit_depth' => 8,
|
||||
'colors' => 2,
|
||||
]);
|
||||
|
||||
$device2 = Device::factory()->create(['device_model_id' => $pngGrayscaleModel->id]);
|
||||
$uuid2 = ImageGenerationService::generateImage($markup, $device2->id);
|
||||
|
||||
$device2->refresh();
|
||||
expect($device2->current_screen_image)->toBe($uuid2);
|
||||
Storage::disk('public')->assertExists("/images/generated/{$uuid2}.png");
|
||||
|
||||
// Test PNG 8-bit 256 color format detection
|
||||
$png256Model = DeviceModel::factory()->create([
|
||||
'mime_type' => 'image/png',
|
||||
'bit_depth' => 8,
|
||||
'colors' => 256,
|
||||
]);
|
||||
|
||||
$device3 = Device::factory()->create(['device_model_id' => $png256Model->id]);
|
||||
$uuid3 = ImageGenerationService::generateImage($markup, $device3->id);
|
||||
|
||||
$device3->refresh();
|
||||
expect($device3->current_screen_image)->toBe($uuid3);
|
||||
Storage::disk('public')->assertExists("/images/generated/{$uuid3}.png");
|
||||
})->skipOnGitHubActions();
|
||||
|
|
@ -16,6 +16,10 @@ test('it creates firmwares directory if it does not exist', function () {
|
|||
'version_tag' => '1.0.0',
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'https://example.com/firmware.bin' => Http::response('fake firmware content', 200),
|
||||
]);
|
||||
|
||||
(new FirmwareDownloadJob($firmware))->handle();
|
||||
|
||||
expect(Storage::disk('public')->exists('firmwares'))->toBeTrue();
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ beforeEach(function () {
|
|||
|
||||
test('it creates new firmware record when polling', function () {
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'https://usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'version' => '1.0.0',
|
||||
'url' => 'https://example.com/firmware.bin',
|
||||
], 200),
|
||||
|
|
@ -33,7 +33,7 @@ test('it updates existing firmware record when polling', function () {
|
|||
]);
|
||||
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'https://usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'version' => '1.0.0',
|
||||
'url' => 'https://new-url.com/firmware.bin',
|
||||
], 200),
|
||||
|
|
@ -53,7 +53,7 @@ test('it marks previous firmware as not latest when new version is found', funct
|
|||
]);
|
||||
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'https://usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'version' => '1.1.0',
|
||||
'url' => 'https://example.com/firmware.bin',
|
||||
], 200),
|
||||
|
|
@ -67,7 +67,7 @@ test('it marks previous firmware as not latest when new version is found', funct
|
|||
|
||||
test('it handles connection exception gracefully', function () {
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => function () {
|
||||
'https://usetrmnl.com/api/firmware/latest' => function () {
|
||||
throw new ConnectionException('Connection failed');
|
||||
},
|
||||
]);
|
||||
|
|
@ -80,7 +80,7 @@ test('it handles connection exception gracefully', function () {
|
|||
|
||||
test('it handles invalid response gracefully', function () {
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => Http::response(null, 200),
|
||||
'https://usetrmnl.com/api/firmware/latest' => Http::response(null, 200),
|
||||
]);
|
||||
|
||||
(new FirmwarePollJob)->handle();
|
||||
|
|
@ -91,7 +91,7 @@ test('it handles invalid response gracefully', function () {
|
|||
|
||||
test('it handles missing version in response gracefully', function () {
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'https://usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'url' => 'https://example.com/firmware.bin',
|
||||
], 200),
|
||||
]);
|
||||
|
|
@ -104,7 +104,7 @@ test('it handles missing version in response gracefully', function () {
|
|||
|
||||
test('it handles missing url in response gracefully', function () {
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'https://usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'version' => '1.0.0',
|
||||
], 200),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -20,11 +20,15 @@ registerSpatiePestHelpers();
|
|||
arch()
|
||||
->preset()
|
||||
->laravel()
|
||||
->ignoring(App\Http\Controllers\Auth\OidcController::class);
|
||||
->ignoring([
|
||||
App\Http\Controllers\Auth\OidcController::class,
|
||||
App\Models\DeviceModel::class,
|
||||
]);
|
||||
|
||||
arch()
|
||||
->expect('App')
|
||||
->not->toUse(['die', 'dd', 'dump']);
|
||||
->not->toUse(['die', 'dd', 'dump', 'ray']);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Expectations
|
||||
|
|
|
|||
262
tests/Unit/Services/ImageGenerationServiceTest.php
Normal file
262
tests/Unit/Services/ImageGenerationServiceTest.php
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\ImageFormat;
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceModel;
|
||||
use App\Services\ImageGenerationService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('get_image_settings returns device model settings when available', function (): void {
|
||||
// Create a DeviceModel
|
||||
$deviceModel = DeviceModel::factory()->create([
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
'colors' => 256,
|
||||
'bit_depth' => 8,
|
||||
'scale_factor' => 1.5,
|
||||
'rotation' => 90,
|
||||
'mime_type' => 'image/png',
|
||||
'offset_x' => 10,
|
||||
'offset_y' => 20,
|
||||
]);
|
||||
|
||||
// Create a device with the DeviceModel
|
||||
$device = Device::factory()->create([
|
||||
'device_model_id' => $deviceModel->id,
|
||||
]);
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass(ImageGenerationService::class);
|
||||
$method = $reflection->getMethod('getImageSettings');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$settings = $method->invoke(null, $device);
|
||||
|
||||
// Assert DeviceModel settings are used
|
||||
expect($settings['width'])->toBe(1024);
|
||||
expect($settings['height'])->toBe(768);
|
||||
expect($settings['colors'])->toBe(256);
|
||||
expect($settings['bit_depth'])->toBe(8);
|
||||
expect($settings['scale_factor'])->toBe(1.5);
|
||||
expect($settings['rotation'])->toBe(90);
|
||||
expect($settings['mime_type'])->toBe('image/png');
|
||||
expect($settings['offset_x'])->toBe(10);
|
||||
expect($settings['offset_y'])->toBe(20);
|
||||
expect($settings['use_model_settings'])->toBe(true);
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('get_image_settings falls back to device settings when no device model', function (): void {
|
||||
// Create a device without DeviceModel
|
||||
$device = Device::factory()->create([
|
||||
'width' => 800,
|
||||
'height' => 480,
|
||||
'rotate' => 180,
|
||||
'image_format' => ImageFormat::PNG_8BIT_GRAYSCALE->value,
|
||||
]);
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass(ImageGenerationService::class);
|
||||
$method = $reflection->getMethod('getImageSettings');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$settings = $method->invoke(null, $device);
|
||||
|
||||
// Assert device settings are used
|
||||
expect($settings['width'])->toBe(800);
|
||||
expect($settings['height'])->toBe(480);
|
||||
expect($settings['rotation'])->toBe(180);
|
||||
expect($settings['image_format'])->toBe(ImageFormat::PNG_8BIT_GRAYSCALE->value);
|
||||
expect($settings['use_model_settings'])->toBe(false);
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('get_image_settings uses defaults for missing device properties', function (): void {
|
||||
// Create a device without DeviceModel and missing properties
|
||||
$device = Device::factory()->create([
|
||||
'width' => null,
|
||||
'height' => null,
|
||||
'rotate' => null,
|
||||
// image_format has a default value of 'auto', so we can't set it to null
|
||||
]);
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass(ImageGenerationService::class);
|
||||
$method = $reflection->getMethod('getImageSettings');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$settings = $method->invoke(null, $device);
|
||||
|
||||
// Assert default values are used
|
||||
expect($settings['width'])->toBe(800);
|
||||
expect($settings['height'])->toBe(480);
|
||||
expect($settings['rotation'])->toBe(0);
|
||||
expect($settings['colors'])->toBe(2);
|
||||
expect($settings['bit_depth'])->toBe(1);
|
||||
expect($settings['scale_factor'])->toBe(1.0);
|
||||
expect($settings['mime_type'])->toBe('image/png');
|
||||
expect($settings['offset_x'])->toBe(0);
|
||||
expect($settings['offset_y'])->toBe(0);
|
||||
// image_format will be null if the device doesn't have it set, which is the expected behavior
|
||||
expect($settings['image_format'])->toBeNull();
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('determine_image_format_from_model returns correct formats', function (): void {
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass(ImageGenerationService::class);
|
||||
$method = $reflection->getMethod('determineImageFormatFromModel');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Test BMP format
|
||||
$bmpModel = DeviceModel::factory()->create([
|
||||
'mime_type' => 'image/bmp',
|
||||
'bit_depth' => 1,
|
||||
'colors' => 2,
|
||||
]);
|
||||
$format = $method->invoke(null, $bmpModel);
|
||||
expect($format)->toBe(ImageFormat::BMP3_1BIT_SRGB->value);
|
||||
|
||||
// Test PNG 8-bit grayscale format
|
||||
$pngGrayscaleModel = DeviceModel::factory()->create([
|
||||
'mime_type' => 'image/png',
|
||||
'bit_depth' => 8,
|
||||
'colors' => 2,
|
||||
]);
|
||||
$format = $method->invoke(null, $pngGrayscaleModel);
|
||||
expect($format)->toBe(ImageFormat::PNG_8BIT_GRAYSCALE->value);
|
||||
|
||||
// Test PNG 8-bit 256 color format
|
||||
$png256Model = DeviceModel::factory()->create([
|
||||
'mime_type' => 'image/png',
|
||||
'bit_depth' => 8,
|
||||
'colors' => 256,
|
||||
]);
|
||||
$format = $method->invoke(null, $png256Model);
|
||||
expect($format)->toBe(ImageFormat::PNG_8BIT_256C->value);
|
||||
|
||||
// Test PNG 2-bit 4 color format
|
||||
$png4ColorModel = DeviceModel::factory()->create([
|
||||
'mime_type' => 'image/png',
|
||||
'bit_depth' => 2,
|
||||
'colors' => 4,
|
||||
]);
|
||||
$format = $method->invoke(null, $png4ColorModel);
|
||||
expect($format)->toBe(ImageFormat::PNG_2BIT_4C->value);
|
||||
|
||||
// Test unknown format returns AUTO
|
||||
$unknownModel = DeviceModel::factory()->create([
|
||||
'mime_type' => 'image/jpeg',
|
||||
'bit_depth' => 16,
|
||||
'colors' => 65536,
|
||||
]);
|
||||
$format = $method->invoke(null, $unknownModel);
|
||||
expect($format)->toBe(ImageFormat::AUTO->value);
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('cleanup_folder identifies active images correctly', function (): void {
|
||||
// Create devices with images
|
||||
$device1 = Device::factory()->create(['current_screen_image' => 'active-uuid-1']);
|
||||
$device2 = Device::factory()->create(['current_screen_image' => 'active-uuid-2']);
|
||||
$device3 = Device::factory()->create(['current_screen_image' => null]);
|
||||
|
||||
// Create a plugin with image
|
||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'plugin-uuid']);
|
||||
|
||||
// For unit testing, we could test the logic that determines active UUIDs
|
||||
$activeDeviceImageUuids = Device::pluck('current_screen_image')->filter()->toArray();
|
||||
$activePluginImageUuids = App\Models\Plugin::pluck('current_image')->filter()->toArray();
|
||||
$activeImageUuids = array_merge($activeDeviceImageUuids, $activePluginImageUuids);
|
||||
|
||||
expect($activeImageUuids)->toContain('active-uuid-1');
|
||||
expect($activeImageUuids)->toContain('active-uuid-2');
|
||||
expect($activeImageUuids)->toContain('plugin-uuid');
|
||||
expect($activeImageUuids)->not->toContain(null);
|
||||
});
|
||||
|
||||
it('reset_if_not_cacheable detects device models', function (): void {
|
||||
// Create a plugin
|
||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
||||
|
||||
// Create a device with DeviceModel
|
||||
Device::factory()->create([
|
||||
'device_model_id' => DeviceModel::factory()->create()->id,
|
||||
]);
|
||||
|
||||
// Test that the method detects DeviceModels and resets cache
|
||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
||||
|
||||
$plugin->refresh();
|
||||
expect($plugin->current_image)->toBeNull();
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('reset_if_not_cacheable detects custom dimensions', function (): void {
|
||||
// Create a plugin
|
||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
||||
|
||||
// Create a device with custom dimensions
|
||||
Device::factory()->create([
|
||||
'width' => 1024, // Different from default 800
|
||||
'height' => 768, // Different from default 480
|
||||
]);
|
||||
|
||||
// Test that the method detects custom dimensions and resets cache
|
||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
||||
|
||||
$plugin->refresh();
|
||||
expect($plugin->current_image)->toBeNull();
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('reset_if_not_cacheable preserves cache for standard devices', function (): void {
|
||||
// Create a plugin
|
||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
||||
|
||||
// Create devices with standard dimensions
|
||||
Device::factory()->count(3)->create([
|
||||
'width' => 800,
|
||||
'height' => 480,
|
||||
'rotate' => 0,
|
||||
]);
|
||||
|
||||
// Test that the method preserves cache for standard devices
|
||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
||||
|
||||
$plugin->refresh();
|
||||
expect($plugin->current_image)->toBe('test-uuid');
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('reset_if_not_cacheable handles null plugin', function (): void {
|
||||
// Test that the method handles null plugin gracefully
|
||||
expect(fn () => ImageGenerationService::resetIfNotCacheable(null))->not->toThrow(Exception::class);
|
||||
})->skipOnGitHubActions();
|
||||
|
||||
it('image_format enum includes new 2bit 4c format', function (): void {
|
||||
// Test that the new format is properly defined in the enum
|
||||
expect(ImageFormat::PNG_2BIT_4C->value)->toBe('png_2bit_4c');
|
||||
expect(ImageFormat::PNG_2BIT_4C->label())->toBe('PNG 2-bit Grayscale 4c');
|
||||
});
|
||||
|
||||
it('device model relationship works correctly', function (): void {
|
||||
// Create a DeviceModel
|
||||
$deviceModel = DeviceModel::factory()->create();
|
||||
|
||||
// Create a device with the DeviceModel
|
||||
$device = Device::factory()->create([
|
||||
'device_model_id' => $deviceModel->id,
|
||||
]);
|
||||
|
||||
// Test the relationship
|
||||
expect($device->deviceModel)->toBeInstanceOf(DeviceModel::class);
|
||||
expect($device->deviceModel->id)->toBe($deviceModel->id);
|
||||
});
|
||||
|
||||
it('device without device model returns null relationship', function (): void {
|
||||
// Create a device without DeviceModel
|
||||
$device = Device::factory()->create([
|
||||
'device_model_id' => null,
|
||||
]);
|
||||
|
||||
// Test the relationship returns null
|
||||
expect($device->deviceModel)->toBeNull();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue