mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-13 15:07:49 +00:00
feat(#17): add commands and jobs to poll, download and update firmware
feat(#17): add commands and jobs to poll, download and update firmware feat(#17): update firmware modal feat(#17): add tests
This commit is contained in:
parent
93aac51182
commit
87b9b57c3d
13 changed files with 567 additions and 3 deletions
37
app/Console/Commands/FirmwareCheckCommand.php
Normal file
37
app/Console/Commands/FirmwareCheckCommand.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\FirmwarePollJob;
|
||||
use App\Models\Firmware;
|
||||
use Illuminate\Console\Command;
|
||||
use function Laravel\Prompts\spin;
|
||||
use function Laravel\Prompts\table;
|
||||
|
||||
class FirmwareCheckCommand extends Command
|
||||
{
|
||||
protected $signature = 'trmnl:firmware:check {--download : Download the latest firmware if available}';
|
||||
|
||||
protected $description = 'Checks for the latest firmware and downloads it if flag --download is passed.';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
spin(
|
||||
callback: fn () => FirmwarePollJob::dispatchSync(download: $this->option('download')),
|
||||
message: 'Checking for latest firmware...'
|
||||
);
|
||||
|
||||
$latestFirmware = Firmware::getLatest();
|
||||
if ($latestFirmware) {
|
||||
table(
|
||||
rows: [
|
||||
['Latest Version', $latestFirmware->version_tag],
|
||||
['Download URL', $latestFirmware->url],
|
||||
['Storage Location', $latestFirmware->storage_location],
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$this->error('No firmware found.');
|
||||
}
|
||||
}
|
||||
}
|
||||
75
app/Console/Commands/FirmwareUpdateCommand.php
Normal file
75
app/Console/Commands/FirmwareUpdateCommand.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\Firmware;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use function Laravel\Prompts\multiselect;
|
||||
use function Laravel\Prompts\select;
|
||||
|
||||
class FirmwareUpdateCommand extends Command
|
||||
{
|
||||
protected $signature = 'trmnl:firmware:update';
|
||||
|
||||
protected $description = 'Command description';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
|
||||
$checkFirmware = select(
|
||||
label: 'Check for new firmware?',
|
||||
options: [
|
||||
'check' => 'Check. Devices will download binary from the original source.',
|
||||
'download' => 'Check & Download. Devices will download binary from BYOS.',
|
||||
'no' => 'Do not check.',
|
||||
],
|
||||
);
|
||||
|
||||
if ($checkFirmware !== 'no') {
|
||||
$this->call('trmnl:firmware:check', [
|
||||
'--download' => $checkFirmware === 'download',
|
||||
]);
|
||||
}
|
||||
|
||||
$firmwareVersion = select(
|
||||
label: 'Update to which version?',
|
||||
options: Firmware::pluck('version_tag', 'id')
|
||||
);
|
||||
|
||||
$devices = multiselect(
|
||||
label: 'Which devices should be updated?',
|
||||
options: [
|
||||
'all' => 'ALL Devices',
|
||||
...Device::all()->mapWithKeys(function ($device) {
|
||||
// without _ returns index
|
||||
return ["_$device->id" => "$device->name (Current version: $device->last_firmware_version)"];
|
||||
})->toArray()
|
||||
],
|
||||
scroll: 10
|
||||
);
|
||||
|
||||
|
||||
|
||||
if (empty($devices)) {
|
||||
$this->error('No devices selected. Aborting.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array('all', $devices)) {
|
||||
$devices = Device::pluck('id')->toArray();
|
||||
} else {
|
||||
$devices = array_map(function($selected) {
|
||||
return (int) str_replace('_', '', $selected);
|
||||
}, $devices);
|
||||
}
|
||||
|
||||
|
||||
foreach ($devices as $deviceId) {
|
||||
Device::find($deviceId)->update(['update_firmware_id' => $firmwareVersion]);
|
||||
|
||||
$this->info("Device with id [$deviceId] will update firmware on next request.");
|
||||
}
|
||||
}
|
||||
}
|
||||
47
app/Jobs/FirmwareDownloadJob.php
Normal file
47
app/Jobs/FirmwareDownloadJob.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Firmware;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class FirmwareDownloadJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
private Firmware $firmware;
|
||||
|
||||
public function __construct(Firmware $firmware)
|
||||
{
|
||||
$this->firmware = $firmware;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (! Storage::disk('public')->exists('firmwares')) {
|
||||
Storage::disk('public')->makeDirectory('firmwares');
|
||||
}
|
||||
|
||||
try {
|
||||
$filename = "FW{$this->firmware->version_tag}.bin";
|
||||
Http::sink(storage_path("app/public/firmwares/$filename"))
|
||||
->get($this->firmware->url);
|
||||
|
||||
$this->firmware->update([
|
||||
'storage_location' => "firmwares/$filename",
|
||||
]);
|
||||
} catch (ConnectionException $e) {
|
||||
Log::error('Firmware download failed: '.$e->getMessage());
|
||||
} catch (\Exception $e) {
|
||||
Log::error('An unexpected error occurred: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
55
app/Jobs/FirmwarePollJob.php
Normal file
55
app/Jobs/FirmwarePollJob.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Firmware;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class FirmwarePollJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
private bool $download;
|
||||
|
||||
public function __construct(bool $download = false)
|
||||
{
|
||||
$this->download = $download;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$response = Http::get('https://usetrmnl.com/api/firmware/latest')->json();
|
||||
|
||||
if (!is_array($response) || !isset($response['version']) || !isset($response['url'])) {
|
||||
\Log::error('Invalid firmware response format received');
|
||||
return;
|
||||
}
|
||||
|
||||
$latestFirmware = Firmware::updateOrCreate(
|
||||
['version_tag' => $response['version']],
|
||||
[
|
||||
'url' => $response['url'],
|
||||
'latest' => true,
|
||||
]
|
||||
);
|
||||
|
||||
Firmware::where('id', '!=', $latestFirmware->id)->update(['latest' => false]);
|
||||
|
||||
if ($this->download && $latestFirmware->url && $latestFirmware->storage_location === null) {
|
||||
FirmwareDownloadJob::dispatchSync($latestFirmware);
|
||||
}
|
||||
|
||||
} catch (ConnectionException $e) {
|
||||
\Log::error('Firmware download failed: '.$e->getMessage());
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Unexpected error in firmware polling: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class Device extends Model
|
||||
{
|
||||
|
|
@ -63,6 +64,10 @@ class Device extends Model
|
|||
return true;
|
||||
}
|
||||
|
||||
if ($this->update_firmware_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +77,17 @@ class Device extends Model
|
|||
return $this->proxy_cloud_response['firmware_url'];
|
||||
}
|
||||
|
||||
if ($this->update_firmware_id) {
|
||||
$firmware = Firmware::find($this->update_firmware_id);
|
||||
if ($firmware) {
|
||||
if ($firmware->storage_location) {
|
||||
return Storage::disk('public')->url($firmware->storage_location);
|
||||
}
|
||||
|
||||
return $firmware->url;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -81,6 +97,10 @@ class Device extends Model
|
|||
$this->proxy_cloud_response = array_merge($this->proxy_cloud_response, ['update_firmware' => false]);
|
||||
$this->save();
|
||||
}
|
||||
if ($this->update_firmware_id) {
|
||||
$this->update_firmware_id = null;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function playlists(): HasMany
|
||||
|
|
@ -117,4 +137,9 @@ class Device extends Model
|
|||
{
|
||||
return $this->belongsTo(Device::class, 'mirror_device_id');
|
||||
}
|
||||
|
||||
public function updateFirmware(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Firmware::class, 'update_firmware_id');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
app/Models/Firmware.php
Normal file
25
app/Models/Firmware.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Firmware extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'latest' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public static function getLatest(): ?self
|
||||
{
|
||||
return self::where('latest', true)->first();
|
||||
}
|
||||
}
|
||||
24
database/factories/FirmwareFactory.php
Normal file
24
database/factories/FirmwareFactory.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Firmware;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class FirmwareFactory extends Factory
|
||||
{
|
||||
protected $model = Firmware::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'version_tag' => $this->faker->word(),
|
||||
'url' => $this->faker->url(),
|
||||
'latest' => $this->faker->boolean(),
|
||||
'storage_location' => $this->faker->word(),
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('firmware', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('version_tag');
|
||||
$table->string('url')->nullable();
|
||||
$table->boolean('latest')->default(false);
|
||||
$table->string('storage_location')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('firmware');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('devices', function (Blueprint $table) {
|
||||
$table->foreignId('update_firmware_id')->nullable()->constrained('firmware')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('devices', function (Blueprint $table) {
|
||||
$table->dropForeign(['update_firmware_id']);
|
||||
$table->dropColumn('update_firmware_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\FirmwareDownloadJob;
|
||||
use App\Models\Firmware;
|
||||
use App\Models\Playlist;
|
||||
use App\Models\PlaylistItem;
|
||||
use Livewire\Volt\Component;
|
||||
|
|
@ -26,6 +28,11 @@ new class extends Component {
|
|||
public $active_until;
|
||||
public $refresh_time = null;
|
||||
|
||||
// Firmware properties
|
||||
public $firmwares;
|
||||
public $selected_firmware_id;
|
||||
public $download_firmware;
|
||||
|
||||
public function mount(\App\Models\Device $device)
|
||||
{
|
||||
abort_unless(auth()->user()->devices->contains($device), 403);
|
||||
|
|
@ -44,6 +51,8 @@ new class extends Component {
|
|||
$this->rotate = $device->rotate;
|
||||
$this->image_format = $device->image_format;
|
||||
$this->playlists = $device->playlists()->with('items.plugin')->orderBy('created_at')->get();
|
||||
$this->firmwares = \App\Models\Firmware::orderBy('latest', 'desc')->orderBy('created_at', 'desc')->get();
|
||||
$this->selected_firmware_id = $this->firmwares->where('latest', true)->first()?->id;
|
||||
|
||||
return view('livewire.devices.configure', [
|
||||
'image' => ($current_image_uuid) ? url($current_image_path) : null,
|
||||
|
|
@ -216,6 +225,26 @@ new class extends Component {
|
|||
$this->active_until = optional($playlist->active_until)->format('H:i');
|
||||
$this->refresh_time = $playlist->refresh_time;
|
||||
}
|
||||
|
||||
public function updateFirmware()
|
||||
{
|
||||
abort_unless(auth()->user()->devices->contains($this->device), 403);
|
||||
|
||||
$this->validate([
|
||||
'selected_firmware_id' => 'required|exists:firmware,id',
|
||||
]);
|
||||
|
||||
|
||||
if ($this->download_firmware) {
|
||||
FirmwareDownloadJob::dispatchSync(Firmware::find($this->selected_firmware_id));
|
||||
}
|
||||
|
||||
$this->device->update([
|
||||
'update_firmware_id' => $this->selected_firmware_id,
|
||||
]);
|
||||
|
||||
Flux::modal('update-firmware')->close();
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
|
|
@ -266,9 +295,18 @@ new class extends Component {
|
|||
<flux:modal.trigger name="edit-device">
|
||||
<flux:button icon="pencil-square" />
|
||||
</flux:modal.trigger>
|
||||
<flux:modal.trigger name="delete-device">
|
||||
<flux:button icon="trash" variant="subtle"/>
|
||||
</flux:modal.trigger>
|
||||
|
||||
<flux:dropdown>
|
||||
<flux:button icon="ellipsis-horizontal" variant="subtle"></flux:button>
|
||||
<flux:menu>
|
||||
<flux:modal.trigger name="update-firmware">
|
||||
<flux:menu.item icon="arrow-up-circle">Update Firmware</flux:menu.item>
|
||||
</flux:modal.trigger>
|
||||
<flux:modal.trigger name="delete-device">
|
||||
<flux:menu.item icon="trash" variant="danger">Delete Device</flux:menu.item>
|
||||
</flux:modal.trigger>
|
||||
</flux:menu>
|
||||
</flux:dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -309,6 +347,39 @@ new class extends Component {
|
|||
</div>
|
||||
</flux:modal>
|
||||
|
||||
<flux:modal name="update-firmware" class="md:w-96">
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<flux:heading size="lg">Update Firmware</flux:heading>
|
||||
<flux:subheading>Select a firmware version to update to</flux:subheading>
|
||||
</div>
|
||||
|
||||
<form wire:submit="updateFirmware">
|
||||
<div class="mb-4">
|
||||
<flux:select label="Firmware Version" wire:model="selected_firmware_id" required>
|
||||
@foreach($firmwares as $firmware)
|
||||
<flux:select.option value="{{ $firmware->id }}">
|
||||
{{ $firmware->version_tag }} {{ $firmware->latest ? '(Latest)' : '' }}
|
||||
</flux:select.option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<flux:checkbox wire:model="download_firmware" label="Cache Firmware on BYOS">
|
||||
</flux:checkbox>
|
||||
<flux:text class="text-xs mt-2">Check if the Device has no internet connection.
|
||||
</flux:text>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<flux:spacer/>
|
||||
<flux:button type="submit" variant="primary">Update Firmware</flux:button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</flux:modal>
|
||||
|
||||
<flux:modal name="delete-device" class="min-w-[22rem] space-y-6">
|
||||
<div>
|
||||
<flux:heading size="lg">Delete {{$device->name}}?</flux:heading>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\FetchProxyCloudResponses;
|
||||
use App\Jobs\FirmwarePollJob;
|
||||
|
||||
Schedule::job(FetchProxyCloudResponses::class, [])->cron(
|
||||
config('services.trmnl.proxy_refresh_cron') ? config('services.trmnl.proxy_refresh_cron') :
|
||||
sprintf('*/%s * * * *', intval(config('services.trmnl.proxy_refresh_minutes', 15)))
|
||||
);
|
||||
|
||||
Schedule::job(FirmwarePollJob::class)->daily();
|
||||
|
|
|
|||
38
tests/Feature/Jobs/FirmwareDownloadJobTest.php
Normal file
38
tests/Feature/Jobs/FirmwareDownloadJobTest.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\FirmwareDownloadJob;
|
||||
use App\Models\Firmware;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
beforeEach(function () {
|
||||
Storage::fake('public');
|
||||
Storage::disk('public')->makeDirectory('/firmwares');
|
||||
});
|
||||
|
||||
test('it creates firmwares directory if it does not exist', function () {
|
||||
$firmware = Firmware::factory()->create([
|
||||
'url' => 'https://example.com/firmware.bin',
|
||||
'version_tag' => '1.0.0',
|
||||
]);
|
||||
|
||||
(new FirmwareDownloadJob($firmware))->handle();
|
||||
|
||||
expect(Storage::disk('public')->exists('firmwares'))->toBeTrue();
|
||||
});
|
||||
|
||||
test('it downloads firmware and updates storage location', function () {
|
||||
Http::fake([
|
||||
'https://example.com/firmware.bin' => Http::response('fake firmware content', 200),
|
||||
]);
|
||||
|
||||
$firmware = Firmware::factory()->create([
|
||||
'url' => 'https://example.com/firmware.bin',
|
||||
'version_tag' => '1.0.0',
|
||||
]);
|
||||
|
||||
(new FirmwareDownloadJob($firmware))->handle();
|
||||
|
||||
expect($firmware->fresh()->storage_location)->toBe('firmwares/FW1.0.0.bin');
|
||||
});
|
||||
116
tests/Feature/Jobs/FirmwarePollJobTest.php
Normal file
116
tests/Feature/Jobs/FirmwarePollJobTest.php
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\FirmwarePollJob;
|
||||
use App\Models\Firmware;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
beforeEach(function () {
|
||||
Http::preventStrayRequests();
|
||||
});
|
||||
|
||||
test('it creates new firmware record when polling', function () {
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'version' => '1.0.0',
|
||||
'url' => 'https://example.com/firmware.bin'
|
||||
], 200)
|
||||
]);
|
||||
|
||||
(new FirmwarePollJob())->handle();
|
||||
|
||||
expect(Firmware::where('version_tag', '1.0.0')->exists())->toBeTrue()
|
||||
->and(Firmware::where('version_tag', '1.0.0')->first())
|
||||
->url->toBe('https://example.com/firmware.bin')
|
||||
->latest->toBeTrue();
|
||||
});
|
||||
|
||||
test('it updates existing firmware record when polling', function () {
|
||||
$existingFirmware = Firmware::factory()->create([
|
||||
'version_tag' => '1.0.0',
|
||||
'url' => 'https://old-url.com/firmware.bin',
|
||||
'latest' => true
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'version' => '1.0.0',
|
||||
'url' => 'https://new-url.com/firmware.bin'
|
||||
], 200)
|
||||
]);
|
||||
|
||||
(new FirmwarePollJob())->handle();
|
||||
|
||||
expect($existingFirmware->fresh())
|
||||
->url->toBe('https://new-url.com/firmware.bin')
|
||||
->latest->toBeTrue();
|
||||
});
|
||||
|
||||
test('it marks previous firmware as not latest when new version is found', function () {
|
||||
$oldFirmware = Firmware::factory()->create([
|
||||
'version_tag' => '1.0.0',
|
||||
'latest' => true
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'version' => '1.1.0',
|
||||
'url' => 'https://example.com/firmware.bin'
|
||||
], 200)
|
||||
]);
|
||||
|
||||
(new FirmwarePollJob())->handle();
|
||||
|
||||
expect($oldFirmware->fresh()->latest)->toBeFalse()
|
||||
->and(Firmware::where('version_tag', '1.1.0')->first()->latest)->toBeTrue();
|
||||
});
|
||||
|
||||
test('it handles connection exception gracefully', function () {
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => function () {
|
||||
throw new ConnectionException('Connection failed');
|
||||
}
|
||||
]);
|
||||
|
||||
(new FirmwarePollJob())->handle();
|
||||
|
||||
// Verify no firmware records were created
|
||||
expect(Firmware::count())->toBe(0);
|
||||
});
|
||||
|
||||
test('it handles invalid response gracefully', function () {
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => Http::response(null, 200)
|
||||
]);
|
||||
|
||||
(new FirmwarePollJob())->handle();
|
||||
|
||||
// Verify no firmware records were created
|
||||
expect(Firmware::count())->toBe(0);
|
||||
});
|
||||
|
||||
test('it handles missing version in response gracefully', function () {
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'url' => 'https://example.com/firmware.bin'
|
||||
], 200)
|
||||
]);
|
||||
|
||||
(new FirmwarePollJob())->handle();
|
||||
|
||||
// Verify no firmware records were created
|
||||
expect(Firmware::count())->toBe(0);
|
||||
});
|
||||
|
||||
test('it handles missing url in response gracefully', function () {
|
||||
Http::fake([
|
||||
'usetrmnl.com/api/firmware/latest' => Http::response([
|
||||
'version' => '1.0.0'
|
||||
], 200)
|
||||
]);
|
||||
|
||||
(new FirmwarePollJob())->handle();
|
||||
|
||||
// Verify no firmware records were created
|
||||
expect(Firmware::count())->toBe(0);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue