From a1a57014b6402da28cbf1b94cc723e26e5cf679f Mon Sep 17 00:00:00 2001 From: Benjamin Nussbaum Date: Wed, 24 Sep 2025 19:24:55 +0200 Subject: [PATCH] test: use TrmnlPipeline::fake() to speed up test suite --- composer.json | 2 +- composer.lock | 14 +-- .../livewire/devices/configure.blade.php | 1 - tests/Feature/Api/DeviceEndpointsTest.php | 8 +- tests/Feature/Console/OidcTestCommandTest.php | 11 ++- tests/Feature/GenerateScreenJobTest.php | 8 +- tests/Feature/ImageGenerationServiceTest.php | 97 ++++--------------- .../ImageGenerationWithoutFakeTest.php | 55 +++++++++++ .../Services/ImageGenerationServiceTest.php | 25 +++-- 9 files changed, 119 insertions(+), 102 deletions(-) create mode 100644 tests/Feature/ImageGenerationWithoutFakeTest.php diff --git a/composer.json b/composer.json index e3cbb13..eee7896 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "ext-imagick": "*", "ext-zip": "*", "bnussbau/laravel-trmnl-blade": "2.0.*", - "bnussbau/trmnl-pipeline-php": "^0.2.0", + "bnussbau/trmnl-pipeline-php": "^0.3.0", "keepsuit/laravel-liquid": "^0.5.2", "laravel/framework": "^12.1", "laravel/sanctum": "^4.0", diff --git a/composer.lock b/composer.lock index 5a3c004..cf2ce06 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f8f7d3fd0eba117ddeb5463047ac5493", + "content-hash": "7d12a2e6d66b2e82c6d96d6a0c5366f0", "packages": [ { "name": "aws/aws-crt-php", @@ -243,16 +243,16 @@ }, { "name": "bnussbau/trmnl-pipeline-php", - "version": "0.2.0", + "version": "0.3.0", "source": { "type": "git", "url": "https://github.com/bnussbau/trmnl-pipeline-php.git", - "reference": "0a85e4c935a7009c469c014c6b7f2d9783d82523" + "reference": "89ceac9e0f35bdee591dfddd7b048aff1218bb6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bnussbau/trmnl-pipeline-php/zipball/0a85e4c935a7009c469c014c6b7f2d9783d82523", - "reference": "0a85e4c935a7009c469c014c6b7f2d9783d82523", + "url": "https://api.github.com/repos/bnussbau/trmnl-pipeline-php/zipball/89ceac9e0f35bdee591dfddd7b048aff1218bb6e", + "reference": "89ceac9e0f35bdee591dfddd7b048aff1218bb6e", "shasum": "" }, "require": { @@ -294,7 +294,7 @@ ], "support": { "issues": "https://github.com/bnussbau/trmnl-pipeline-php/issues", - "source": "https://github.com/bnussbau/trmnl-pipeline-php/tree/0.2.0" + "source": "https://github.com/bnussbau/trmnl-pipeline-php/tree/0.3.0" }, "funding": [ { @@ -310,7 +310,7 @@ "type": "github" } ], - "time": "2025-09-18T16:40:28+00:00" + "time": "2025-09-24T16:29:38+00:00" }, { "name": "brick/math", diff --git a/resources/views/livewire/devices/configure.blade.php b/resources/views/livewire/devices/configure.blade.php index 44e424c..30b4481 100644 --- a/resources/views/livewire/devices/configure.blade.php +++ b/resources/views/livewire/devices/configure.blade.php @@ -383,7 +383,6 @@ new class extends Component { Edit TRMNL - makeDirectory('/images/generated'); }); @@ -573,7 +575,7 @@ test('plugin caches image until data is stale', function () { expect($thirdResponse['filename']) ->not->toBe($firstResponse['filename']); -})->skipOnCi(); +}); test('plugins in playlist are rendered in order', function () { // Create source device with a playlist @@ -677,7 +679,7 @@ test('plugins in playlist are rendered in order', function () { $thirdResponse->assertOk(); expect($thirdResponse['filename']) ->not->toBe($secondResponse['filename']); -})->skipOnCi(); +}); test('display endpoint updates last_refreshed_at timestamp', function () { $device = Device::factory()->create([ @@ -787,7 +789,7 @@ test('display endpoint handles mashup playlist items correctly', function () { // Verify the playlist item was marked as displayed $playlistItem->refresh(); expect($playlistItem->last_displayed_at)->not->toBeNull(); -})->skipOnCi(); +}); test('device in sleep mode returns sleep image and correct refresh rate', function () { $device = Device::factory()->create([ diff --git a/tests/Feature/Console/OidcTestCommandTest.php b/tests/Feature/Console/OidcTestCommandTest.php index e7456b0..b523574 100644 --- a/tests/Feature/Console/OidcTestCommandTest.php +++ b/tests/Feature/Console/OidcTestCommandTest.php @@ -10,7 +10,15 @@ test('oidc test command has correct signature', function () { }); test('oidc test command runs successfully with disabled oidc', function () { - config(['services.oidc.enabled' => false]); + config([ + 'app.url' => 'http://localhost', + 'services.oidc.enabled' => false, + '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...') @@ -34,6 +42,7 @@ test('oidc test command runs successfully with disabled oidc', function () { test('oidc test command runs successfully with enabled oidc but missing config', function () { config([ + 'app.url' => 'http://localhost', 'services.oidc.enabled' => true, 'services.oidc.endpoint' => null, 'services.oidc.client_id' => null, diff --git a/tests/Feature/GenerateScreenJobTest.php b/tests/Feature/GenerateScreenJobTest.php index 35b4377..78ba932 100644 --- a/tests/Feature/GenerateScreenJobTest.php +++ b/tests/Feature/GenerateScreenJobTest.php @@ -2,11 +2,13 @@ use App\Jobs\GenerateScreenJob; use App\Models\Device; +use Bnussbau\TrmnlPipeline\TrmnlPipeline; use Illuminate\Support\Facades\Storage; uses(Illuminate\Foundation\Testing\RefreshDatabase::class); beforeEach(function () { + TrmnlPipeline::fake(); Storage::fake('public'); Storage::disk('public')->makeDirectory('/images/generated'); }); @@ -23,7 +25,7 @@ test('it generates screen images and updates device', function () { // Assert both PNG and BMP files were created $uuid = $device->current_screen_image; Storage::disk('public')->assertExists("/images/generated/{$uuid}.png"); -})->skipOnCi(); +}); test('it cleans up unused images', function () { // Create some test devices with images @@ -45,7 +47,7 @@ test('it cleans up unused images', function () { Storage::disk('public')->assertMissing('/images/generated/uuid-to-be-replaced.bmp'); Storage::disk('public')->assertMissing('/images/generated/inactive-uuid.png'); Storage::disk('public')->assertMissing('/images/generated/inactive-uuid.bmp'); -})->skipOnCi(); +}); test('it preserves gitignore file during cleanup', function () { Storage::disk('public')->put('/images/generated/.gitignore', '*'); @@ -55,4 +57,4 @@ test('it preserves gitignore file during cleanup', function () { $job->handle(); Storage::disk('public')->assertExists('/images/generated/.gitignore'); -})->skipOnCi(); +}); diff --git a/tests/Feature/ImageGenerationServiceTest.php b/tests/Feature/ImageGenerationServiceTest.php index f2af102..603205e 100644 --- a/tests/Feature/ImageGenerationServiceTest.php +++ b/tests/Feature/ImageGenerationServiceTest.php @@ -6,6 +6,7 @@ use App\Enums\ImageFormat; use App\Models\Device; use App\Models\DeviceModel; use App\Services\ImageGenerationService; +use Bnussbau\TrmnlPipeline\TrmnlPipeline; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Storage; @@ -14,6 +15,11 @@ uses(RefreshDatabase::class); beforeEach(function (): void { Storage::fake('public'); Storage::disk('public')->makeDirectory('/images/generated'); + TrmnlPipeline::fake(); +}); + +afterEach(function (): void { + TrmnlPipeline::restore(); }); it('generates image for device without device model', function (): void { @@ -34,7 +40,7 @@ it('generates image for device without device model', function (): void { // Assert PNG file was created Storage::disk('public')->assertExists("/images/generated/{$uuid}.png"); -})->skipOnCi(); +}); it('generates image for device with device model', function (): void { // Create a DeviceModel @@ -64,68 +70,7 @@ it('generates image for device with device model', function (): void { // Assert PNG file was created Storage::disk('public')->assertExists("/images/generated/{$uuid}.png"); -})->skipOnCi(); - -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 = '
Test Content
'; - $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'); -})->skipOnCi(); +}); it('generates BMP with device model', function (): void { // Create a DeviceModel for BMP format @@ -155,7 +100,7 @@ it('generates BMP with device model', function (): void { // Assert BMP file was created Storage::disk('public')->assertExists("/images/generated/{$uuid}.bmp"); -})->skipOnCi(); +}); it('applies scale factor from device model', function (): void { // Create a DeviceModel with scale factor @@ -185,7 +130,7 @@ it('applies scale factor from device model', function (): void { // Assert PNG file was created Storage::disk('public')->assertExists("/images/generated/{$uuid}.png"); -})->skipOnCi(); +}); it('applies rotation from device model', function (): void { // Create a DeviceModel with rotation @@ -215,7 +160,7 @@ it('applies rotation from device model', function (): void { // Assert PNG file was created Storage::disk('public')->assertExists("/images/generated/{$uuid}.png"); -})->skipOnCi(); +}); it('applies offset from device model', function (): void { // Create a DeviceModel with offset @@ -245,7 +190,7 @@ it('applies offset from device model', function (): void { // Assert PNG file was created Storage::disk('public')->assertExists("/images/generated/{$uuid}.png"); -})->skipOnCi(); +}); it('falls back to device settings when no device model', function (): void { // Create a device with custom settings but no DeviceModel @@ -265,7 +210,7 @@ it('falls back to device settings when no device model', function (): void { // Assert PNG file was created Storage::disk('public')->assertExists("/images/generated/{$uuid}.png"); -})->skipOnCi(); +}); it('handles auto image format for legacy devices', function (): void { // Create a device with AUTO format (legacy behavior) @@ -286,7 +231,7 @@ it('handles auto image format for legacy devices', function (): void { // Assert PNG file was created (modern firmware defaults to PNG) Storage::disk('public')->assertExists("/images/generated/{$uuid}.png"); -})->skipOnCi(); +}); it('cleanupFolder removes unused images', function (): void { // Create active devices with images @@ -309,7 +254,7 @@ it('cleanupFolder removes unused images', function (): void { // Assert inactive files are removed Storage::disk('public')->assertMissing('/images/generated/inactive-uuid.png'); Storage::disk('public')->assertMissing('/images/generated/another-inactive.png'); -})->skipOnCi(); +}); it('cleanupFolder preserves .gitignore', function (): void { // Create gitignore file @@ -323,7 +268,7 @@ it('cleanupFolder preserves .gitignore', function (): void { // Assert gitignore is preserved Storage::disk('public')->assertExists('/images/generated/.gitignore'); -})->skipOnCi(); +}); it('resetIfNotCacheable resets when device models exist', function (): void { // Create a plugin @@ -340,7 +285,7 @@ it('resetIfNotCacheable resets when device models exist', function (): void { // Assert plugin image was reset $plugin->refresh(); expect($plugin->current_image)->toBeNull(); -})->skipOnCi(); +}); it('resetIfNotCacheable resets when custom dimensions exist', function (): void { // Create a plugin @@ -358,7 +303,7 @@ it('resetIfNotCacheable resets when custom dimensions exist', function (): void // Assert plugin image was reset $plugin->refresh(); expect($plugin->current_image)->toBeNull(); -})->skipOnCi(); +}); it('resetIfNotCacheable preserves image for standard devices', function (): void { // Create a plugin @@ -377,7 +322,7 @@ it('resetIfNotCacheable preserves image for standard devices', function (): void // Assert plugin image was preserved $plugin->refresh(); expect($plugin->current_image)->toBe('test-uuid'); -})->skipOnCi(); +}); it('determines correct image format from device model', function (): void { // Test BMP format detection @@ -422,7 +367,7 @@ it('determines correct image format from device model', function (): void { $device3->refresh(); expect($device3->current_screen_image)->toBe($uuid3); Storage::disk('public')->assertExists("/images/generated/{$uuid3}.png"); -})->skipOnCi(); +}); it('generates BMP for legacy device with bmp3_1bit_srgb format', function (): void { // Create a device with BMP format but no DeviceModel (legacy behavior) @@ -454,4 +399,4 @@ it('generates BMP for legacy device with bmp3_1bit_srgb format', function (): vo expect($imageInfo[0])->toBe(800); // Width expect($imageInfo[1])->toBe(480); // Height expect($imageInfo[2])->toBe(IMAGETYPE_BMP); // BMP type -})->skipOnCi(); +}); diff --git a/tests/Feature/ImageGenerationWithoutFakeTest.php b/tests/Feature/ImageGenerationWithoutFakeTest.php new file mode 100644 index 0000000..ff70174 --- /dev/null +++ b/tests/Feature/ImageGenerationWithoutFakeTest.php @@ -0,0 +1,55 @@ +makeDirectory('/images/generated'); +}); + +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 = '
Test Content
'; + $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 + +})->skipOnCI(); diff --git a/tests/Unit/Services/ImageGenerationServiceTest.php b/tests/Unit/Services/ImageGenerationServiceTest.php index 37ed4e2..660e984 100644 --- a/tests/Unit/Services/ImageGenerationServiceTest.php +++ b/tests/Unit/Services/ImageGenerationServiceTest.php @@ -6,10 +6,15 @@ use App\Enums\ImageFormat; use App\Models\Device; use App\Models\DeviceModel; use App\Services\ImageGenerationService; +use Bnussbau\TrmnlPipeline\TrmnlPipeline; use Illuminate\Foundation\Testing\RefreshDatabase; uses(RefreshDatabase::class); +beforeEach(function () { + TrmnlPipeline::fake(); +}); + it('get_image_settings returns device model settings when available', function (): void { // Create a DeviceModel $deviceModel = DeviceModel::factory()->create([ @@ -47,7 +52,7 @@ it('get_image_settings returns device model settings when available', function ( expect($settings['offset_x'])->toBe(10); expect($settings['offset_y'])->toBe(20); expect($settings['use_model_settings'])->toBe(true); -})->skipOnCi(); +}); it('get_image_settings falls back to device settings when no device model', function (): void { // Create a device without DeviceModel @@ -71,7 +76,7 @@ it('get_image_settings falls back to device settings when no device model', func expect($settings['rotation'])->toBe(180); expect($settings['image_format'])->toBe(ImageFormat::PNG_8BIT_GRAYSCALE->value); expect($settings['use_model_settings'])->toBe(false); -})->skipOnCi(); +}); it('get_image_settings uses defaults for missing device properties', function (): void { // Create a device without DeviceModel and missing properties @@ -101,7 +106,7 @@ it('get_image_settings uses defaults for missing device properties', function () expect($settings['offset_y'])->toBe(0); // image_format defaults to 'auto' when not set expect($settings['image_format'])->toBe('auto'); -})->skipOnCi(); +}); it('determine_image_format_from_model returns correct formats', function (): void { // Use reflection to access private method @@ -153,7 +158,7 @@ it('determine_image_format_from_model returns correct formats', function (): voi ]); $format = $method->invoke(null, $unknownModel); expect($format)->toBe(ImageFormat::AUTO->value); -})->skipOnCi(); +}); it('cleanup_folder identifies active images correctly', function (): void { // Create devices with images @@ -189,7 +194,7 @@ it('reset_if_not_cacheable detects device models', function (): void { $plugin->refresh(); expect($plugin->current_image)->toBeNull(); -})->skipOnCi(); +}); it('reset_if_not_cacheable detects custom dimensions', function (): void { // Create a plugin @@ -206,7 +211,7 @@ it('reset_if_not_cacheable detects custom dimensions', function (): void { $plugin->refresh(); expect($plugin->current_image)->toBeNull(); -})->skipOnCi(); +}); it('reset_if_not_cacheable preserves cache for standard devices', function (): void { // Create a plugin @@ -224,7 +229,7 @@ it('reset_if_not_cacheable preserves cache for standard devices', function (): v $plugin->refresh(); expect($plugin->current_image)->toBe('test-uuid'); -})->skipOnCi(); +}); it('reset_if_not_cacheable preserves cache for og_png and og_plus device models', function (): void { // Create a plugin @@ -255,7 +260,7 @@ it('reset_if_not_cacheable preserves cache for og_png and og_plus device models' $plugin->refresh(); expect($plugin->current_image)->toBe('test-uuid'); -})->skipOnCi(); +}); it('reset_if_not_cacheable resets cache for non-standard device models', function (): void { // Create a plugin @@ -277,12 +282,12 @@ it('reset_if_not_cacheable resets cache for non-standard device models', functio $plugin->refresh(); expect($plugin->current_image)->toBeNull(); -})->skipOnCi(); +}); 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); -})->skipOnCi(); +}); it('image_format enum includes new 2bit 4c format', function (): void { // Test that the new format is properly defined in the enum