mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-03-14 12:23:33 +00:00
feat(#194): refactor cache to be device specific
This commit is contained in:
parent
c194ab5db1
commit
26b5f3ceb1
8 changed files with 278 additions and 110 deletions
|
|
@ -34,8 +34,13 @@ class GenerateScreenJob implements ShouldQueue
|
||||||
Device::find($this->deviceId)->update(['current_screen_image' => $newImageUuid]);
|
Device::find($this->deviceId)->update(['current_screen_image' => $newImageUuid]);
|
||||||
|
|
||||||
if ($this->pluginId) {
|
if ($this->pluginId) {
|
||||||
// cache current image
|
$plugin = Plugin::find($this->pluginId);
|
||||||
Plugin::find($this->pluginId)->update(['current_image' => $newImageUuid]);
|
$update = ['current_image' => $newImageUuid];
|
||||||
|
if ($plugin->plugin_type === 'recipe') {
|
||||||
|
$device = Device::with(['deviceModel', 'deviceModel.palette'])->find($this->deviceId);
|
||||||
|
$update['current_image_metadata'] = ImageGenerationService::buildImageMetadataFromDevice($device);
|
||||||
|
}
|
||||||
|
$plugin->update($update);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageGenerationService::cleanupFolder();
|
ImageGenerationService::cleanupFolder();
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ class Plugin extends Model
|
||||||
'preferred_renderer' => 'string',
|
'preferred_renderer' => 'string',
|
||||||
'plugin_type' => 'string',
|
'plugin_type' => 'string',
|
||||||
'alias' => 'boolean',
|
'alias' => 'boolean',
|
||||||
|
'current_image_metadata' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected static function boot()
|
protected static function boot()
|
||||||
|
|
@ -71,6 +72,7 @@ class Plugin extends Model
|
||||||
'render_markup_shared',
|
'render_markup_shared',
|
||||||
])) {
|
])) {
|
||||||
$model->current_image = null;
|
$model->current_image = null;
|
||||||
|
$model->current_image_metadata = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -331,36 +331,88 @@ class ImageGenerationService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function resetIfNotCacheable(?Plugin $plugin): void
|
/**
|
||||||
|
* Ensure plugin image cache is valid for the current context. No-op for image_webhook.
|
||||||
|
* When deviceOrModel is provided (recipe only), clears cache if stored metadata does not match.
|
||||||
|
*/
|
||||||
|
public static function resetIfNotCacheable(?Plugin $plugin, Device|DeviceModel|null $deviceOrModel = null): void
|
||||||
{
|
{
|
||||||
if ($plugin?->id) {
|
if (! $plugin?->id || $plugin->plugin_type === 'image_webhook') {
|
||||||
// Image webhook plugins have finalized images that shouldn't be reset
|
|
||||||
if ($plugin->plugin_type === 'image_webhook') {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Check if any devices have custom dimensions or use non-standard DeviceModels
|
if ($deviceOrModel === null || $plugin->plugin_type !== 'recipe') {
|
||||||
$hasCustomDimensions = Device::query()
|
return;
|
||||||
->where(function ($query): void {
|
}
|
||||||
$query->where('width', '!=', 800)
|
if ($plugin->current_image === null) {
|
||||||
->orWhere('height', '!=', 480)
|
return;
|
||||||
->orWhere('rotate', '!=', 0);
|
}
|
||||||
})
|
if (self::imageMetadataMatches($plugin->current_image_metadata, $deviceOrModel)) {
|
||||||
->orWhereHas('deviceModel', function ($query): void {
|
return;
|
||||||
// Only allow caching if all device models have standard dimensions (800x480, rotation=0)
|
}
|
||||||
$query->where(function ($subQuery): void {
|
$plugin->update([
|
||||||
$subQuery->where('width', '!=', 800)
|
'current_image' => null,
|
||||||
->orWhere('height', '!=', 480)
|
'current_image_metadata' => null,
|
||||||
->orWhere('rotation', '!=', 0);
|
]);
|
||||||
});
|
Log::debug("Plugin {$plugin->id}: cleared image cache due to metadata mismatch");
|
||||||
})
|
}
|
||||||
->exists();
|
|
||||||
|
|
||||||
if ($hasCustomDimensions) {
|
/**
|
||||||
// TODO cache image per device
|
* Build canonical image metadata from a Device for cache comparison.
|
||||||
$plugin->update(['current_image' => null]);
|
*
|
||||||
Log::debug('Skip cache as devices with custom dimensions or non-standard DeviceModels exist');
|
* @return array{width: int, height: int, rotation: int, palette_id: int|null, mime_type: string}
|
||||||
|
*/
|
||||||
|
public static function buildImageMetadataFromDevice(Device $device): array
|
||||||
|
{
|
||||||
|
$device->loadMissing(['deviceModel', 'deviceModel.palette']);
|
||||||
|
$settings = self::getImageSettings($device);
|
||||||
|
$paletteId = $device->palette_id ?? $device->deviceModel?->palette_id;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'width' => $settings['width'],
|
||||||
|
'height' => $settings['height'],
|
||||||
|
'rotation' => $settings['rotation'] ?? 0,
|
||||||
|
'palette_id' => $paletteId,
|
||||||
|
'mime_type' => $settings['mime_type'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build canonical image metadata from a DeviceModel for cache comparison.
|
||||||
|
*
|
||||||
|
* @return array{width: int, height: int, rotation: int, palette_id: int|null, mime_type: string}
|
||||||
|
*/
|
||||||
|
public static function buildImageMetadataFromDeviceModel(DeviceModel $model): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'width' => $model->width,
|
||||||
|
'height' => $model->height,
|
||||||
|
'rotation' => $model->rotation ?? 0,
|
||||||
|
'palette_id' => $model->palette_id,
|
||||||
|
'mime_type' => $model->mime_type,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if stored metadata matches the current device or device model.
|
||||||
|
* Returns false if stored is null or empty so cache is regenerated and metadata is stored.
|
||||||
|
*/
|
||||||
|
public static function imageMetadataMatches(?array $stored, Device|DeviceModel $deviceOrModel): bool
|
||||||
|
{
|
||||||
|
if ($stored === null || $stored === []) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = $deviceOrModel instanceof Device
|
||||||
|
? self::buildImageMetadataFromDevice($deviceOrModel)
|
||||||
|
: self::buildImageMetadataFromDeviceModel($deviceOrModel);
|
||||||
|
|
||||||
|
foreach (['width', 'height', 'rotation', 'palette_id', 'mime_type'] as $key) {
|
||||||
|
if (($stored[$key] ?? null) !== ($current[$key] ?? null)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('plugins', function (Blueprint $table) {
|
||||||
|
$table->json('current_image_metadata')->nullable()->after('current_image');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('plugins', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('current_image_metadata');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -88,8 +88,8 @@ Route::get('/display', function (Request $request) {
|
||||||
$refreshTimeOverride = $playlistItem->playlist()->first()->refresh_time;
|
$refreshTimeOverride = $playlistItem->playlist()->first()->refresh_time;
|
||||||
$plugin = $playlistItem->plugin;
|
$plugin = $playlistItem->plugin;
|
||||||
|
|
||||||
// Reset cache if Devices with different dimensions exist
|
ImageGenerationService::resetIfNotCacheable($plugin, $device);
|
||||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
$plugin->refresh();
|
||||||
|
|
||||||
// Check and update stale data if needed
|
// Check and update stale data if needed
|
||||||
if ($plugin->isDataStale() || $plugin->current_image === null) {
|
if ($plugin->isDataStale() || $plugin->current_image === null) {
|
||||||
|
|
@ -699,6 +699,9 @@ Route::get('/display/{uuid}/alias', function (Request $request, string $uuid) {
|
||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageGenerationService::resetIfNotCacheable($plugin, $deviceModel);
|
||||||
|
$plugin->refresh();
|
||||||
|
|
||||||
// Check if we can use cached image (only for og_png and if data is not stale)
|
// Check if we can use cached image (only for og_png and if data is not stale)
|
||||||
$useCache = $deviceModelName === 'og_png' && ! $plugin->isDataStale() && $plugin->current_image !== null;
|
$useCache = $deviceModelName === 'og_png' && ! $plugin->isDataStale() && $plugin->current_image !== null;
|
||||||
|
|
||||||
|
|
@ -744,9 +747,13 @@ Route::get('/display/{uuid}/alias', function (Request $request, string $uuid) {
|
||||||
palette: $deviceModel->palette
|
palette: $deviceModel->palette
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update plugin cache if using og_png
|
// Update plugin cache if using og_png (recipes only get metadata for cache comparison)
|
||||||
if ($deviceModelName === 'og_png') {
|
if ($deviceModelName === 'og_png') {
|
||||||
$plugin->update(['current_image' => $imageUuid]);
|
$update = ['current_image' => $imageUuid];
|
||||||
|
if ($plugin->plugin_type === 'recipe') {
|
||||||
|
$update['current_image_metadata'] = ImageGenerationService::buildImageMetadataFromDeviceModel($deviceModel);
|
||||||
|
}
|
||||||
|
$plugin->update($update);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the generated image
|
// Return the generated image
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
use App\Jobs\GenerateScreenJob;
|
use App\Jobs\GenerateScreenJob;
|
||||||
use App\Models\Device;
|
use App\Models\Device;
|
||||||
|
use App\Models\DeviceModel;
|
||||||
|
use App\Models\Plugin;
|
||||||
use Bnussbau\TrmnlPipeline\TrmnlPipeline;
|
use Bnussbau\TrmnlPipeline\TrmnlPipeline;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
|
@ -58,3 +60,26 @@ test('it preserves gitignore file during cleanup', function (): void {
|
||||||
|
|
||||||
Storage::disk('public')->assertExists('/images/generated/.gitignore');
|
Storage::disk('public')->assertExists('/images/generated/.gitignore');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it saves current_image_metadata for recipe plugins', function (): void {
|
||||||
|
$deviceModel = DeviceModel::factory()->create([
|
||||||
|
'width' => 800,
|
||||||
|
'height' => 480,
|
||||||
|
'rotation' => 0,
|
||||||
|
'mime_type' => 'image/png',
|
||||||
|
'palette_id' => null,
|
||||||
|
]);
|
||||||
|
$device = Device::factory()->create(['device_model_id' => $deviceModel->id]);
|
||||||
|
$plugin = Plugin::factory()->create(['plugin_type' => 'recipe']);
|
||||||
|
|
||||||
|
$job = new GenerateScreenJob($device->id, $plugin->id, '<div>Test</div>');
|
||||||
|
$job->handle();
|
||||||
|
|
||||||
|
$plugin->refresh();
|
||||||
|
expect($plugin->current_image)->not->toBeNull();
|
||||||
|
expect($plugin->current_image_metadata)->toBeArray();
|
||||||
|
expect($plugin->current_image_metadata)->toHaveKeys(['width', 'height', 'rotation', 'palette_id', 'mime_type']);
|
||||||
|
expect($plugin->current_image_metadata['width'])->toBe(800);
|
||||||
|
expect($plugin->current_image_metadata['height'])->toBe(480);
|
||||||
|
expect($plugin->current_image_metadata['mime_type'])->toBe('image/png');
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use App\Models\DeviceModel;
|
||||||
use App\Services\ImageGenerationService;
|
use App\Services\ImageGenerationService;
|
||||||
use Bnussbau\TrmnlPipeline\TrmnlPipeline;
|
use Bnussbau\TrmnlPipeline\TrmnlPipeline;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
uses(RefreshDatabase::class);
|
uses(RefreshDatabase::class);
|
||||||
|
|
@ -22,6 +23,10 @@ afterEach(function (): void {
|
||||||
TrmnlPipeline::restore();
|
TrmnlPipeline::restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('plugins table has current_image_metadata column', function (): void {
|
||||||
|
expect(Schema::hasColumn('plugins', 'current_image_metadata'))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
it('generates image for device without device model', function (): void {
|
it('generates image for device without device model', function (): void {
|
||||||
// Create a device without a DeviceModel (legacy behavior)
|
// Create a device without a DeviceModel (legacy behavior)
|
||||||
$device = Device::factory()->create([
|
$device = Device::factory()->create([
|
||||||
|
|
@ -270,39 +275,15 @@ it('cleanupFolder preserves .gitignore', function (): void {
|
||||||
Storage::disk('public')->assertExists('/images/generated/.gitignore');
|
Storage::disk('public')->assertExists('/images/generated/.gitignore');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resetIfNotCacheable resets when device models exist', function (): void {
|
it('resetIfNotCacheable does not reset recipe cache based on other devices', function (): void {
|
||||||
// Create a plugin
|
// Cache validity is now determined at use-time via current_image_metadata
|
||||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid', 'plugin_type' => 'recipe']);
|
||||||
|
|
||||||
// Create a device with DeviceModel (should trigger cache reset)
|
Device::factory()->create(['device_model_id' => DeviceModel::factory()->create()->id]);
|
||||||
Device::factory()->create([
|
|
||||||
'device_model_id' => DeviceModel::factory()->create()->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Run reset check
|
|
||||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
ImageGenerationService::resetIfNotCacheable($plugin);
|
||||||
|
|
||||||
// Assert plugin image was reset
|
|
||||||
$plugin->refresh();
|
$plugin->refresh();
|
||||||
expect($plugin->current_image)->toBeNull();
|
expect($plugin->current_image)->toBe('test-uuid');
|
||||||
});
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resetIfNotCacheable preserves image for standard devices', function (): void {
|
it('resetIfNotCacheable preserves image for standard devices', function (): void {
|
||||||
|
|
@ -325,27 +306,122 @@ it('resetIfNotCacheable preserves image for standard devices', function (): void
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cache is reset when plugin markup changes', function (): void {
|
it('cache is reset when plugin markup changes', function (): void {
|
||||||
// Create a plugin with cached image
|
// Create a plugin with cached image and metadata
|
||||||
$plugin = App\Models\Plugin::factory()->create([
|
$plugin = App\Models\Plugin::factory()->create([
|
||||||
'current_image' => 'cached-uuid',
|
'current_image' => 'cached-uuid',
|
||||||
|
'current_image_metadata' => ['width' => 800, 'height' => 480, 'rotation' => 0, 'palette_id' => null, 'mime_type' => 'image/png'],
|
||||||
'render_markup' => '<div>Original markup</div>',
|
'render_markup' => '<div>Original markup</div>',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Create devices with standard dimensions (cacheable)
|
$plugin->update(['render_markup' => '<div>Updated markup</div>']);
|
||||||
Device::factory()->count(2)->create([
|
|
||||||
'width' => 800,
|
|
||||||
'height' => 480,
|
|
||||||
'rotate' => 0,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Update the plugin markup
|
|
||||||
$plugin->update([
|
|
||||||
'render_markup' => '<div>Updated markup</div>',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Assert cache was reset when markup changed
|
|
||||||
$plugin->refresh();
|
$plugin->refresh();
|
||||||
expect($plugin->current_image)->toBeNull();
|
expect($plugin->current_image)->toBeNull();
|
||||||
|
expect($plugin->current_image_metadata)->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('buildImageMetadataFromDevice returns canonical metadata shape', function (): void {
|
||||||
|
$deviceModel = DeviceModel::factory()->create([
|
||||||
|
'width' => 800,
|
||||||
|
'height' => 480,
|
||||||
|
'rotation' => 0,
|
||||||
|
'mime_type' => 'image/png',
|
||||||
|
'palette_id' => null,
|
||||||
|
]);
|
||||||
|
$device = Device::factory()->create(['device_model_id' => $deviceModel->id]);
|
||||||
|
|
||||||
|
$meta = ImageGenerationService::buildImageMetadataFromDevice($device);
|
||||||
|
|
||||||
|
expect($meta)->toHaveKeys(['width', 'height', 'rotation', 'palette_id', 'mime_type']);
|
||||||
|
expect($meta['width'])->toBe(800);
|
||||||
|
expect($meta['height'])->toBe(480);
|
||||||
|
expect($meta['rotation'])->toBe(0);
|
||||||
|
expect($meta['mime_type'])->toBe('image/png');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('buildImageMetadataFromDeviceModel returns canonical metadata shape', function (): void {
|
||||||
|
$model = DeviceModel::factory()->create([
|
||||||
|
'width' => 1024,
|
||||||
|
'height' => 768,
|
||||||
|
'rotation' => 90,
|
||||||
|
'mime_type' => 'image/bmp',
|
||||||
|
'palette_id' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$meta = ImageGenerationService::buildImageMetadataFromDeviceModel($model);
|
||||||
|
|
||||||
|
expect($meta)->toHaveKeys(['width', 'height', 'rotation', 'palette_id', 'mime_type']);
|
||||||
|
expect($meta['width'])->toBe(1024);
|
||||||
|
expect($meta['height'])->toBe(768);
|
||||||
|
expect($meta['rotation'])->toBe(90);
|
||||||
|
expect($meta['mime_type'])->toBe('image/bmp');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('imageMetadataMatches returns false when stored is null or empty', function (): void {
|
||||||
|
$device = Device::factory()->create(['width' => 800, 'height' => 480, 'rotate' => 0]);
|
||||||
|
|
||||||
|
expect(ImageGenerationService::imageMetadataMatches(null, $device))->toBeFalse();
|
||||||
|
expect(ImageGenerationService::imageMetadataMatches([], $device))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('imageMetadataMatches returns true when metadata matches device', function (): void {
|
||||||
|
$deviceModel = DeviceModel::factory()->create([
|
||||||
|
'width' => 800,
|
||||||
|
'height' => 480,
|
||||||
|
'rotation' => 0,
|
||||||
|
'mime_type' => 'image/png',
|
||||||
|
'palette_id' => null,
|
||||||
|
]);
|
||||||
|
$device = Device::factory()->create(['device_model_id' => $deviceModel->id]);
|
||||||
|
$stored = ImageGenerationService::buildImageMetadataFromDevice($device);
|
||||||
|
|
||||||
|
expect(ImageGenerationService::imageMetadataMatches($stored, $device))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('imageMetadataMatches returns false when metadata differs', function (): void {
|
||||||
|
$device = Device::factory()->create(['width' => 800, 'height' => 480, 'rotate' => 0]);
|
||||||
|
$stored = ['width' => 800, 'height' => 480, 'rotation' => 0, 'palette_id' => null, 'mime_type' => 'image/png'];
|
||||||
|
|
||||||
|
$device->update(['width' => 1024]);
|
||||||
|
$device->refresh();
|
||||||
|
|
||||||
|
expect(ImageGenerationService::imageMetadataMatches($stored, $device))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resetIfNotCacheable clears recipe cache when metadata does not match', function (): void {
|
||||||
|
$plugin = App\Models\Plugin::factory()->create([
|
||||||
|
'plugin_type' => 'recipe',
|
||||||
|
'current_image' => 'cached-uuid',
|
||||||
|
'current_image_metadata' => ['width' => 800, 'height' => 480, 'rotation' => 0, 'palette_id' => null, 'mime_type' => 'image/png'],
|
||||||
|
]);
|
||||||
|
$device = Device::factory()->create(['width' => 1024, 'height' => 768, 'rotate' => 0]);
|
||||||
|
|
||||||
|
ImageGenerationService::resetIfNotCacheable($plugin, $device);
|
||||||
|
|
||||||
|
$plugin->refresh();
|
||||||
|
expect($plugin->current_image)->toBeNull();
|
||||||
|
expect($plugin->current_image_metadata)->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resetIfNotCacheable preserves cache when metadata matches', function (): void {
|
||||||
|
$deviceModel = DeviceModel::factory()->create([
|
||||||
|
'width' => 800,
|
||||||
|
'height' => 480,
|
||||||
|
'rotation' => 0,
|
||||||
|
'mime_type' => 'image/png',
|
||||||
|
]);
|
||||||
|
$device = Device::factory()->create(['device_model_id' => $deviceModel->id]);
|
||||||
|
$meta = ImageGenerationService::buildImageMetadataFromDevice($device);
|
||||||
|
$plugin = App\Models\Plugin::factory()->create([
|
||||||
|
'plugin_type' => 'recipe',
|
||||||
|
'current_image' => 'cached-uuid',
|
||||||
|
'current_image_metadata' => $meta,
|
||||||
|
]);
|
||||||
|
|
||||||
|
ImageGenerationService::resetIfNotCacheable($plugin, $device);
|
||||||
|
|
||||||
|
$plugin->refresh();
|
||||||
|
expect($plugin->current_image)->toBe('cached-uuid');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('determines correct image format from device model', function (): void {
|
it('determines correct image format from device model', function (): void {
|
||||||
|
|
|
||||||
|
|
@ -176,37 +176,15 @@ it('cleanup_folder identifies active images correctly', function (): void {
|
||||||
expect($activeImageUuids)->not->toContain(null);
|
expect($activeImageUuids)->not->toContain(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reset_if_not_cacheable detects device models', function (): void {
|
it('reset_if_not_cacheable does not reset recipe cache when other devices exist', function (): void {
|
||||||
// Create a plugin
|
// Cache validity is now determined at use-time via metadata
|
||||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid', 'plugin_type' => 'recipe']);
|
||||||
|
Device::factory()->create(['device_model_id' => DeviceModel::factory()->create()->id]);
|
||||||
|
|
||||||
// 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);
|
ImageGenerationService::resetIfNotCacheable($plugin);
|
||||||
|
|
||||||
$plugin->refresh();
|
$plugin->refresh();
|
||||||
expect($plugin->current_image)->toBeNull();
|
expect($plugin->current_image)->toBe('test-uuid');
|
||||||
});
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reset_if_not_cacheable preserves cache for standard devices', function (): void {
|
it('reset_if_not_cacheable preserves cache for standard devices', function (): void {
|
||||||
|
|
@ -258,26 +236,21 @@ it('reset_if_not_cacheable preserves cache for og_png and og_plus device models'
|
||||||
expect($plugin->current_image)->toBe('test-uuid');
|
expect($plugin->current_image)->toBe('test-uuid');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reset_if_not_cacheable resets cache for non-standard device models', function (): void {
|
it('reset_if_not_cacheable does not reset cache for non-standard device models', function (): void {
|
||||||
// Create a plugin
|
// Cache is now validated at use-time via metadata comparison
|
||||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid', 'plugin_type' => 'recipe']);
|
||||||
|
|
||||||
// Create a non-standard device model (e.g., kindle)
|
|
||||||
$kindleModel = DeviceModel::factory()->create([
|
$kindleModel = DeviceModel::factory()->create([
|
||||||
'name' => 'test_amazon_kindle_2024',
|
'name' => 'test_amazon_kindle_2024',
|
||||||
'width' => 1400,
|
'width' => 1400,
|
||||||
'height' => 840,
|
'height' => 840,
|
||||||
'rotation' => 90,
|
'rotation' => 90,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Create a device with the non-standard device model
|
|
||||||
Device::factory()->create(['device_model_id' => $kindleModel->id]);
|
Device::factory()->create(['device_model_id' => $kindleModel->id]);
|
||||||
|
|
||||||
// Test that the method resets cache for non-standard device models
|
|
||||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
ImageGenerationService::resetIfNotCacheable($plugin);
|
||||||
|
|
||||||
$plugin->refresh();
|
$plugin->refresh();
|
||||||
expect($plugin->current_image)->toBeNull();
|
expect($plugin->current_image)->toBe('test-uuid');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reset_if_not_cacheable handles null plugin', function (): void {
|
it('reset_if_not_cacheable handles null plugin', function (): void {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue