mirror of
https://github.com/pelican-dev/panel.git
synced 2026-02-09 11:20:08 +03:00
Compare commits
1 Commits
lance/769
...
lance/1742
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2060554ab |
19
app/Events/Backup/BackupCompleted.php
Normal file
19
app/Events/Backup/BackupCompleted.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Backup;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class BackupCompleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Backup $backup, public Server $server, public User $owner) {}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Remote\Backups;
|
||||
|
||||
use App\Events\Backup\BackupCompleted;
|
||||
use App\Exceptions\DisplayException;
|
||||
use App\Exceptions\Http\HttpForbiddenException;
|
||||
use App\Extensions\Backups\BackupManager;
|
||||
@@ -79,6 +80,11 @@ class BackupStatusController extends Controller
|
||||
}
|
||||
});
|
||||
|
||||
// Fire the BackupCompleted event for successful backups.
|
||||
if ($request->boolean('successful')) {
|
||||
event(new BackupCompleted($model, $server, $server->user));
|
||||
}
|
||||
|
||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
|
||||
40
app/Listeners/Backup/BackupCompletedListener.php
Normal file
40
app/Listeners/Backup/BackupCompletedListener.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Backup;
|
||||
|
||||
use App\Events\Backup\BackupCompleted;
|
||||
use App\Filament\Server\Resources\Backups\Pages\ListBackups;
|
||||
use App\Notifications\BackupCompleted as BackupCompletedNotification;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
class BackupCompletedListener
|
||||
{
|
||||
public function handle(BackupCompleted $event): void
|
||||
{
|
||||
$event->backup->loadMissing('server');
|
||||
$event->owner->loadMissing('language');
|
||||
|
||||
$locale = $event->owner->language ?? 'en';
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('notifications.backup_completed', locale: $locale))
|
||||
->body(trans('notifications.backup_name_and_server', [
|
||||
'backup' => $event->backup->name,
|
||||
'server' => $event->server->name,
|
||||
], $locale))
|
||||
->actions([
|
||||
Action::make('view')
|
||||
->button()
|
||||
->label(trans('notifications.view_backups', locale: $locale))
|
||||
->markAsRead()
|
||||
->url(fn () => ListBackups::getUrl(panel: 'server', tenant: $event->server)),
|
||||
])
|
||||
->sendToDatabase($event->owner);
|
||||
|
||||
if (config()->get('panel.email.send_backup_notification', true)) {
|
||||
$event->owner->notify(new BackupCompletedNotification($event->backup));
|
||||
}
|
||||
}
|
||||
}
|
||||
35
app/Notifications/BackupCompleted.php
Normal file
35
app/Notifications/BackupCompleted.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Filament\Server\Resources\Backups\Pages\ListBackups;
|
||||
use App\Models\Backup;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class BackupCompleted extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(public Backup $backup) {}
|
||||
|
||||
/** @return string[] */
|
||||
public function via(): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
public function toMail(User $notifiable): MailMessage
|
||||
{
|
||||
return (new MailMessage())
|
||||
->greeting('Hello ' . $notifiable->username . '.')
|
||||
->line('Your backup has finished and is now ready.')
|
||||
->line('Backup Name: ' . $this->backup->name)
|
||||
->line('Server Name: ' . $this->backup->server->name)
|
||||
->line('Size: ' . convert_bytes_to_readable($this->backup->bytes))
|
||||
->action('View Backups', ListBackups::getUrl(panel: 'server', tenant: $this->backup->server));
|
||||
}
|
||||
}
|
||||
@@ -257,12 +257,12 @@ class PluginService
|
||||
}
|
||||
}
|
||||
|
||||
public function buildAssets(): bool
|
||||
public function buildAssets(bool $throw = false): bool
|
||||
{
|
||||
try {
|
||||
$result = Process::path(base_path())->timeout(300)->run('yarn install');
|
||||
if ($result->failed()) {
|
||||
throw new Exception('Could not install dependencies: ' . $result->errorOutput());
|
||||
throw new Exception('Could not install yarn dependencies: ' . $result->errorOutput());
|
||||
}
|
||||
|
||||
$result = Process::path(base_path())->timeout(600)->run('yarn build');
|
||||
@@ -272,7 +272,7 @@ class PluginService
|
||||
|
||||
return true;
|
||||
} catch (Exception $exception) {
|
||||
if ($this->isDevModeActive()) {
|
||||
if ($throw || $this->isDevModeActive()) {
|
||||
throw ($exception);
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ class PluginService
|
||||
}
|
||||
}
|
||||
|
||||
$this->buildAssets();
|
||||
$this->buildAssets($plugin->isTheme());
|
||||
|
||||
$this->runPluginMigrations($plugin);
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ return [
|
||||
'send_install_notification' => env('PANEL_SEND_INSTALL_NOTIFICATION', true),
|
||||
// Should an email be sent to a server owner whenever their server is reinstalled?
|
||||
'send_reinstall_notification' => env('PANEL_SEND_REINSTALL_NOTIFICATION', true),
|
||||
// Should an email be sent to a server owner whenever their server completes a backup?
|
||||
'send_backup_notification' => env('PANEL_SEND_BACKUP_NOTIFICATION', true),
|
||||
],
|
||||
|
||||
'filament' => [
|
||||
|
||||
@@ -6,6 +6,9 @@ return [
|
||||
'installation_failed' => 'Server Installation Failed',
|
||||
'reinstallation_completed' => 'Server Reinstallation Completed',
|
||||
'reinstallation_failed' => 'Server Reinstallation Failed',
|
||||
'backup_completed' => 'Backup Completed',
|
||||
'backup_name_and_server' => 'Backup :backup on server :server has completed.',
|
||||
'view_backups' => 'View Backups',
|
||||
'failed' => 'Failed',
|
||||
'user_added' => [
|
||||
'title' => 'Added to Server',
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Integration\Api\Remote\Backups;
|
||||
|
||||
use App\Events\Backup\BackupCompleted;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Node;
|
||||
use App\Notifications\BackupCompleted as BackupCompletedNotification;
|
||||
use App\Tests\Integration\IntegrationTestCase;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
class BackupCompletionNotificationTest extends IntegrationTestCase
|
||||
{
|
||||
private Node $node;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
[$user, $server] = $this->generateTestAccount();
|
||||
$this->node = $server->node;
|
||||
}
|
||||
|
||||
public function test_backup_completed_event_is_fired_on_successful_completion(): void
|
||||
{
|
||||
Event::fake([BackupCompleted::class]);
|
||||
|
||||
[$user, $server] = $this->generateTestAccount();
|
||||
|
||||
$backup = Backup::factory()->create([
|
||||
'server_id' => $server->id,
|
||||
'is_successful' => false,
|
||||
'completed_at' => null,
|
||||
]);
|
||||
|
||||
$this->withNodeAuthorization($server->node)
|
||||
->postJson("/api/remote/backups/{$backup->uuid}", [
|
||||
'successful' => true,
|
||||
'checksum' => 'abc123',
|
||||
'checksum_type' => 'sha256',
|
||||
'size' => 1024000,
|
||||
])
|
||||
->assertNoContent();
|
||||
|
||||
Event::assertDispatched(BackupCompleted::class, function ($event) use ($backup, $server) {
|
||||
return $event->backup->id === $backup->id
|
||||
&& $event->server->id === $server->id
|
||||
&& $event->owner->id === $server->user->id;
|
||||
});
|
||||
}
|
||||
|
||||
public function test_event_not_fired_on_failed_backup(): void
|
||||
{
|
||||
Event::fake([BackupCompleted::class]);
|
||||
|
||||
[$user, $server] = $this->generateTestAccount();
|
||||
|
||||
$backup = Backup::factory()->create([
|
||||
'server_id' => $server->id,
|
||||
'is_successful' => false,
|
||||
'completed_at' => null,
|
||||
]);
|
||||
|
||||
$this->withNodeAuthorization($server->node)
|
||||
->postJson("/api/remote/backups/{$backup->uuid}", [
|
||||
'successful' => false,
|
||||
])
|
||||
->assertNoContent();
|
||||
|
||||
Event::assertNotDispatched(BackupCompleted::class);
|
||||
}
|
||||
|
||||
public function test_panel_notification_created_on_backup_completion(): void
|
||||
{
|
||||
// Don't fake events for this test - we want the full flow
|
||||
Event::fake([]);
|
||||
|
||||
[$user, $server] = $this->generateTestAccount();
|
||||
|
||||
$backup = Backup::factory()->create([
|
||||
'server_id' => $server->id,
|
||||
'is_successful' => false,
|
||||
'completed_at' => null,
|
||||
]);
|
||||
|
||||
$this->withNodeAuthorization($server->node)
|
||||
->postJson("/api/remote/backups/{$backup->uuid}", [
|
||||
'successful' => true,
|
||||
'checksum' => 'abc123',
|
||||
'checksum_type' => 'sha256',
|
||||
'size' => 1024000,
|
||||
])
|
||||
->assertNoContent();
|
||||
|
||||
// Verify notification exists in database
|
||||
$this->assertDatabaseHas('notifications', [
|
||||
'notifiable_type' => 'App\Models\User',
|
||||
'notifiable_id' => $server->user->id,
|
||||
'type' => 'Filament\Notifications\DatabaseNotification',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_email_notification_sent_when_config_enabled(): void
|
||||
{
|
||||
Notification::fake();
|
||||
Event::fake([]); // Clear event fakes to allow real dispatch
|
||||
Config::set('panel.email.send_backup_notification', true);
|
||||
|
||||
[$user, $server] = $this->generateTestAccount();
|
||||
|
||||
$backup = Backup::factory()->create([
|
||||
'server_id' => $server->id,
|
||||
'is_successful' => false,
|
||||
'completed_at' => null,
|
||||
]);
|
||||
|
||||
$this->withNodeAuthorization($server->node)
|
||||
->postJson("/api/remote/backups/{$backup->uuid}", [
|
||||
'successful' => true,
|
||||
'checksum' => 'abc123',
|
||||
'checksum_type' => 'sha256',
|
||||
'size' => 1024000,
|
||||
])
|
||||
->assertNoContent();
|
||||
|
||||
Notification::assertSentTo(
|
||||
[$server->user],
|
||||
BackupCompletedNotification::class,
|
||||
function ($notification, $channels) use ($backup) {
|
||||
return $notification->backup->id === $backup->id;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function test_email_notification_not_sent_when_config_disabled(): void
|
||||
{
|
||||
Notification::fake();
|
||||
Event::fake([]); // Clear event fakes to allow real dispatch
|
||||
Config::set('panel.email.send_backup_notification', false);
|
||||
|
||||
[$user, $server] = $this->generateTestAccount();
|
||||
|
||||
$backup = Backup::factory()->create([
|
||||
'server_id' => $server->id,
|
||||
'is_successful' => false,
|
||||
'completed_at' => null,
|
||||
]);
|
||||
|
||||
$this->withNodeAuthorization($server->node)
|
||||
->postJson("/api/remote/backups/{$backup->uuid}", [
|
||||
'successful' => true,
|
||||
'checksum' => 'abc123',
|
||||
'checksum_type' => 'sha256',
|
||||
'size' => 1024000,
|
||||
])
|
||||
->assertNoContent();
|
||||
|
||||
// Email notification should not be sent when config is disabled
|
||||
Notification::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_backup_model_updated_correctly_on_successful_completion(): void
|
||||
{
|
||||
Event::fake([BackupCompleted::class]);
|
||||
|
||||
[$user, $server] = $this->generateTestAccount();
|
||||
|
||||
$backup = Backup::factory()->create([
|
||||
'server_id' => $server->id,
|
||||
'is_successful' => false,
|
||||
'completed_at' => null,
|
||||
'checksum' => null,
|
||||
'bytes' => 0,
|
||||
]);
|
||||
|
||||
$this->withNodeAuthorization($server->node)
|
||||
->postJson("/api/remote/backups/{$backup->uuid}", [
|
||||
'successful' => true,
|
||||
'checksum' => 'abc123',
|
||||
'checksum_type' => 'sha256',
|
||||
'size' => 1024000,
|
||||
])
|
||||
->assertNoContent();
|
||||
|
||||
$backup->refresh();
|
||||
|
||||
$this->assertTrue($backup->is_successful);
|
||||
$this->assertEquals('sha256:abc123', $backup->checksum);
|
||||
$this->assertEquals(1024000, $backup->bytes);
|
||||
$this->assertNotNull($backup->completed_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authorization header for node authentication.
|
||||
*/
|
||||
protected function withNodeAuthorization(Node $node): self
|
||||
{
|
||||
$token = $node->daemon_token_id . '.' . $node->daemon_token;
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer ' . $token);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
52
tests/Unit/Events/Backup/BackupCompletedTest.php
Normal file
52
tests/Unit/Events/Backup/BackupCompletedTest.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Unit\Events\Backup;
|
||||
|
||||
use App\Events\Backup\BackupCompleted;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use App\Tests\TestCase;
|
||||
|
||||
class BackupCompletedTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test that the event can be instantiated with required parameters.
|
||||
*/
|
||||
public function test_event_can_be_instantiated(): void
|
||||
{
|
||||
$backup = Backup::factory()->make();
|
||||
$server = Server::factory()->make();
|
||||
$user = User::factory()->make();
|
||||
|
||||
$event = new BackupCompleted($backup, $server, $user);
|
||||
|
||||
$this->assertInstanceOf(BackupCompleted::class, $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the event properties are accessible.
|
||||
*/
|
||||
public function test_event_properties_are_accessible(): void
|
||||
{
|
||||
$backup = Backup::factory()->make();
|
||||
$server = Server::factory()->make();
|
||||
$user = User::factory()->make();
|
||||
|
||||
$event = new BackupCompleted($backup, $server, $user);
|
||||
|
||||
$this->assertSame($backup, $event->backup);
|
||||
$this->assertSame($server, $event->server);
|
||||
$this->assertSame($user, $event->owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the event uses SerializesModels trait.
|
||||
*/
|
||||
public function test_event_uses_serializes_models_trait(): void
|
||||
{
|
||||
$traits = class_uses(BackupCompleted::class);
|
||||
|
||||
$this->assertContains('Illuminate\Queue\SerializesModels', $traits);
|
||||
}
|
||||
}
|
||||
270
tests/Unit/Listeners/Backup/BackupCompletedListenerTest.php
Normal file
270
tests/Unit/Listeners/Backup/BackupCompletedListenerTest.php
Normal file
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Unit\Listeners\Backup;
|
||||
|
||||
use App\Events\Backup\BackupCompleted;
|
||||
use App\Listeners\Backup\BackupCompletedListener;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use App\Notifications\BackupCompleted as BackupCompletedNotification;
|
||||
use App\Tests\TestCase;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Mockery as m;
|
||||
|
||||
class BackupCompletedListenerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test that the listener can be instantiated.
|
||||
*/
|
||||
public function test_listener_can_be_instantiated(): void
|
||||
{
|
||||
$listener = new BackupCompletedListener();
|
||||
|
||||
$this->assertInstanceOf(BackupCompletedListener::class, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the listener sends a panel notification to the user.
|
||||
*/
|
||||
public function test_listener_sends_panel_notification(): void
|
||||
{
|
||||
// Create test models
|
||||
$user = User::factory()->make(['id' => 1, 'language' => 'en']);
|
||||
$server = Server::factory()->make(['id' => 1, 'name' => 'Test Server']);
|
||||
$backup = Backup::factory()->make([
|
||||
'id' => 1,
|
||||
'name' => 'test-backup.tar.gz',
|
||||
'bytes' => 1024000,
|
||||
]);
|
||||
|
||||
// Set up relationships
|
||||
$backup->setRelation('server', $server);
|
||||
$user->setRelation('language', 'en');
|
||||
|
||||
// Create event
|
||||
$event = new BackupCompleted($backup, $server, $user);
|
||||
|
||||
// Mock Notification facade
|
||||
Notification::shouldReceive('make')
|
||||
->once()
|
||||
->andReturnSelf();
|
||||
Notification::shouldReceive('success')
|
||||
->once()
|
||||
->andReturnSelf();
|
||||
Notification::shouldReceive('title')
|
||||
->once()
|
||||
->with(m::type('string'))
|
||||
->andReturnSelf();
|
||||
Notification::shouldReceive('body')
|
||||
->once()
|
||||
->with(m::type('string'))
|
||||
->andReturnSelf();
|
||||
Notification::shouldReceive('actions')
|
||||
->once()
|
||||
->with(m::type('array'))
|
||||
->andReturnSelf();
|
||||
Notification::shouldReceive('sendToDatabase')
|
||||
->once()
|
||||
->with($user)
|
||||
->andReturnNull();
|
||||
|
||||
// Mock config to disable email notification for this test
|
||||
Config::shouldReceive('get')
|
||||
->with('panel.email.send_backup_notification', true)
|
||||
->once()
|
||||
->andReturn(false);
|
||||
|
||||
// Handle the event
|
||||
$listener = new BackupCompletedListener();
|
||||
$listener->handle($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the listener sends email notification when config is enabled.
|
||||
*/
|
||||
public function test_listener_sends_email_when_config_enabled(): void
|
||||
{
|
||||
// Create test models with notification spy
|
||||
$user = m::mock(User::class)->makePartial();
|
||||
$user->shouldReceive('getAttribute')->with('language')->andReturn('en');
|
||||
$user->shouldReceive('loadMissing')->with('language')->andReturnSelf();
|
||||
|
||||
$server = Server::factory()->make(['id' => 1, 'name' => 'Test Server']);
|
||||
$backup = Backup::factory()->make([
|
||||
'id' => 1,
|
||||
'name' => 'test-backup.tar.gz',
|
||||
'bytes' => 1024000,
|
||||
]);
|
||||
|
||||
// Set up relationships
|
||||
$backup->setRelation('server', $server);
|
||||
$backup->shouldReceive('loadMissing')->with('server')->andReturnSelf();
|
||||
|
||||
// Create event
|
||||
$event = new BackupCompleted($backup, $server, $user);
|
||||
|
||||
// Mock Notification facade
|
||||
Notification::shouldReceive('make')->andReturnSelf();
|
||||
Notification::shouldReceive('success')->andReturnSelf();
|
||||
Notification::shouldReceive('title')->andReturnSelf();
|
||||
Notification::shouldReceive('body')->andReturnSelf();
|
||||
Notification::shouldReceive('actions')->andReturnSelf();
|
||||
Notification::shouldReceive('sendToDatabase')->andReturnNull();
|
||||
|
||||
// Mock config to enable email notification
|
||||
Config::shouldReceive('get')
|
||||
->with('panel.email.send_backup_notification', true)
|
||||
->once()
|
||||
->andReturn(true);
|
||||
|
||||
// Expect notify to be called with BackupCompletedNotification
|
||||
$user->shouldReceive('notify')
|
||||
->once()
|
||||
->with(m::type(BackupCompletedNotification::class))
|
||||
->andReturnNull();
|
||||
|
||||
// Handle the event
|
||||
$listener = new BackupCompletedListener();
|
||||
$listener->handle($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the listener does not send email notification when config is disabled.
|
||||
*/
|
||||
public function test_listener_does_not_send_email_when_config_disabled(): void
|
||||
{
|
||||
// Create test models
|
||||
$user = m::mock(User::class)->makePartial();
|
||||
$user->shouldReceive('getAttribute')->with('language')->andReturn('en');
|
||||
$user->shouldReceive('loadMissing')->with('language')->andReturnSelf();
|
||||
|
||||
$server = Server::factory()->make(['id' => 1, 'name' => 'Test Server']);
|
||||
$backup = Backup::factory()->make([
|
||||
'id' => 1,
|
||||
'name' => 'test-backup.tar.gz',
|
||||
'bytes' => 1024000,
|
||||
]);
|
||||
|
||||
// Set up relationships
|
||||
$backup->setRelation('server', $server);
|
||||
$backup->shouldReceive('loadMissing')->with('server')->andReturnSelf();
|
||||
|
||||
// Create event
|
||||
$event = new BackupCompleted($backup, $server, $user);
|
||||
|
||||
// Mock Notification facade
|
||||
Notification::shouldReceive('make')->andReturnSelf();
|
||||
Notification::shouldReceive('success')->andReturnSelf();
|
||||
Notification::shouldReceive('title')->andReturnSelf();
|
||||
Notification::shouldReceive('body')->andReturnSelf();
|
||||
Notification::shouldReceive('actions')->andReturnSelf();
|
||||
Notification::shouldReceive('sendToDatabase')->andReturnNull();
|
||||
|
||||
// Mock config to disable email notification
|
||||
Config::shouldReceive('get')
|
||||
->with('panel.email.send_backup_notification', true)
|
||||
->once()
|
||||
->andReturn(false);
|
||||
|
||||
// Expect notify to NOT be called
|
||||
$user->shouldNotReceive('notify');
|
||||
|
||||
// Handle the event
|
||||
$listener = new BackupCompletedListener();
|
||||
$listener->handle($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the listener handles null language gracefully.
|
||||
*/
|
||||
public function test_listener_handles_null_language(): void
|
||||
{
|
||||
// Create test models with null language
|
||||
$user = User::factory()->make(['id' => 1, 'language' => null]);
|
||||
$server = Server::factory()->make(['id' => 1, 'name' => 'Test Server']);
|
||||
$backup = Backup::factory()->make([
|
||||
'id' => 1,
|
||||
'name' => 'test-backup.tar.gz',
|
||||
'bytes' => 1024000,
|
||||
]);
|
||||
|
||||
// Set up relationships
|
||||
$backup->setRelation('server', $server);
|
||||
$user->setRelation('language', null);
|
||||
|
||||
// Create event
|
||||
$event = new BackupCompleted($backup, $server, $user);
|
||||
|
||||
// Mock Notification facade - should still work with default locale
|
||||
Notification::shouldReceive('make')->once()->andReturnSelf();
|
||||
Notification::shouldReceive('success')->once()->andReturnSelf();
|
||||
Notification::shouldReceive('title')->once()->andReturnSelf();
|
||||
Notification::shouldReceive('body')->once()->andReturnSelf();
|
||||
Notification::shouldReceive('actions')->once()->andReturnSelf();
|
||||
Notification::shouldReceive('sendToDatabase')->once()->andReturnNull();
|
||||
|
||||
// Mock config
|
||||
Config::shouldReceive('get')
|
||||
->with('panel.email.send_backup_notification', true)
|
||||
->once()
|
||||
->andReturn(false);
|
||||
|
||||
// Handle the event - should not throw exception
|
||||
$listener = new BackupCompletedListener();
|
||||
$listener->handle($event);
|
||||
|
||||
// If we get here without exception, the test passes
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the listener loads missing relationships.
|
||||
*/
|
||||
public function test_listener_loads_missing_relationships(): void
|
||||
{
|
||||
// Create mocked models to verify loadMissing calls
|
||||
$user = m::mock(User::class)->makePartial();
|
||||
$server = Server::factory()->make(['id' => 1, 'name' => 'Test Server']);
|
||||
$backup = m::mock(Backup::class)->makePartial();
|
||||
|
||||
// Expect loadMissing to be called
|
||||
$backup->shouldReceive('loadMissing')
|
||||
->once()
|
||||
->with('server')
|
||||
->andReturnSelf();
|
||||
|
||||
$user->shouldReceive('loadMissing')
|
||||
->once()
|
||||
->with('language')
|
||||
->andReturnSelf();
|
||||
|
||||
$user->shouldReceive('getAttribute')->with('language')->andReturn('en');
|
||||
$backup->shouldReceive('getAttribute')->with('server')->andReturn($server);
|
||||
$backup->shouldReceive('getAttribute')->with('name')->andReturn('test-backup.tar.gz');
|
||||
|
||||
// Create event
|
||||
$event = new BackupCompleted($backup, $server, $user);
|
||||
|
||||
// Mock Notification facade
|
||||
Notification::shouldReceive('make')->andReturnSelf();
|
||||
Notification::shouldReceive('success')->andReturnSelf();
|
||||
Notification::shouldReceive('title')->andReturnSelf();
|
||||
Notification::shouldReceive('body')->andReturnSelf();
|
||||
Notification::shouldReceive('actions')->andReturnSelf();
|
||||
Notification::shouldReceive('sendToDatabase')->andReturnNull();
|
||||
|
||||
// Mock config
|
||||
Config::shouldReceive('get')
|
||||
->with('panel.email.send_backup_notification', true)
|
||||
->once()
|
||||
->andReturn(false);
|
||||
|
||||
// Handle the event
|
||||
$listener = new BackupCompletedListener();
|
||||
$listener->handle($event);
|
||||
|
||||
// Mockery will automatically verify the expectations
|
||||
}
|
||||
}
|
||||
157
tests/Unit/Notifications/BackupCompletedTest.php
Normal file
157
tests/Unit/Notifications/BackupCompletedTest.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Unit\Notifications;
|
||||
|
||||
use App\Models\Backup;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use App\Notifications\BackupCompleted;
|
||||
use App\Tests\TestCase;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class BackupCompletedTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test that the notification can be instantiated.
|
||||
*/
|
||||
public function test_notification_can_be_instantiated(): void
|
||||
{
|
||||
$backup = Backup::factory()->make();
|
||||
|
||||
$notification = new BackupCompleted($backup);
|
||||
|
||||
$this->assertInstanceOf(BackupCompleted::class, $notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the notification properties are accessible.
|
||||
*/
|
||||
public function test_notification_properties_are_accessible(): void
|
||||
{
|
||||
$backup = Backup::factory()->make();
|
||||
|
||||
$notification = new BackupCompleted($backup);
|
||||
|
||||
$this->assertSame($backup, $notification->backup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the notification implements ShouldQueue.
|
||||
*/
|
||||
public function test_notification_implements_should_queue(): void
|
||||
{
|
||||
$this->assertContains(ShouldQueue::class, class_implements(BackupCompleted::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the notification uses Queueable trait.
|
||||
*/
|
||||
public function test_notification_uses_queueable_trait(): void
|
||||
{
|
||||
$traits = class_uses(BackupCompleted::class);
|
||||
|
||||
$this->assertContains('Illuminate\Bus\Queueable', $traits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that via method returns mail channel.
|
||||
*/
|
||||
public function test_via_method_returns_mail_channel(): void
|
||||
{
|
||||
$backup = Backup::factory()->make();
|
||||
|
||||
$notification = new BackupCompleted($backup);
|
||||
|
||||
$this->assertEquals(['mail'], $notification->via());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that toMail returns a MailMessage instance.
|
||||
*/
|
||||
public function test_to_mail_returns_mail_message(): void
|
||||
{
|
||||
$server = Server::factory()->make(['name' => 'Test Server']);
|
||||
$backup = Backup::factory()->make([
|
||||
'name' => 'test-backup.tar.gz',
|
||||
'bytes' => 1024000,
|
||||
]);
|
||||
$backup->setRelation('server', $server);
|
||||
|
||||
$user = User::factory()->make(['username' => 'testuser']);
|
||||
|
||||
$notification = new BackupCompleted($backup);
|
||||
$mailMessage = $notification->toMail($user);
|
||||
|
||||
$this->assertInstanceOf(MailMessage::class, $mailMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that toMail includes correct greeting.
|
||||
*/
|
||||
public function test_to_mail_includes_greeting(): void
|
||||
{
|
||||
$server = Server::factory()->make(['name' => 'Test Server']);
|
||||
$backup = Backup::factory()->make([
|
||||
'name' => 'test-backup.tar.gz',
|
||||
'bytes' => 1024000,
|
||||
]);
|
||||
$backup->setRelation('server', $server);
|
||||
|
||||
$user = User::factory()->make(['username' => 'testuser']);
|
||||
|
||||
$notification = new BackupCompleted($backup);
|
||||
$mailMessage = $notification->toMail($user);
|
||||
|
||||
$this->assertEquals('Hello testuser.', $mailMessage->greeting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that toMail includes backup details.
|
||||
*/
|
||||
public function test_to_mail_includes_backup_details(): void
|
||||
{
|
||||
$server = Server::factory()->make(['name' => 'Test Server']);
|
||||
$backup = Backup::factory()->make([
|
||||
'name' => 'test-backup.tar.gz',
|
||||
'bytes' => 1024000,
|
||||
]);
|
||||
$backup->setRelation('server', $server);
|
||||
|
||||
$user = User::factory()->make(['username' => 'testuser']);
|
||||
|
||||
$notification = new BackupCompleted($backup);
|
||||
$mailMessage = $notification->toMail($user);
|
||||
|
||||
// Check that the intro lines contain the expected information
|
||||
$this->assertContains('Your backup has finished and is now ready.', $mailMessage->introLines);
|
||||
$this->assertContains('Backup Name: test-backup.tar.gz', $mailMessage->introLines);
|
||||
$this->assertContains('Server Name: Test Server', $mailMessage->introLines);
|
||||
$this->assertContains('Size: ' . convert_bytes_to_readable(1024000), $mailMessage->introLines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that toMail includes action button.
|
||||
*/
|
||||
public function test_to_mail_includes_action_button(): void
|
||||
{
|
||||
$server = Server::factory()->make([
|
||||
'id' => 1,
|
||||
'name' => 'Test Server',
|
||||
]);
|
||||
$backup = Backup::factory()->make([
|
||||
'name' => 'test-backup.tar.gz',
|
||||
'bytes' => 1024000,
|
||||
]);
|
||||
$backup->setRelation('server', $server);
|
||||
|
||||
$user = User::factory()->make(['username' => 'testuser']);
|
||||
|
||||
$notification = new BackupCompleted($backup);
|
||||
$mailMessage = $notification->toMail($user);
|
||||
|
||||
// Check that action button exists
|
||||
$this->assertEquals('View Backups', $mailMessage->actionText);
|
||||
$this->assertNotEmpty($mailMessage->actionUrl);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user