Merge remote-tracking branch 'origin/develop'

This commit is contained in:
snipe
2025-05-23 10:17:10 +01:00
9 changed files with 462 additions and 167 deletions

View File

@@ -27,6 +27,7 @@ use App\Notifications\CheckoutAssetNotification;
use App\Notifications\CheckoutConsumableNotification;
use App\Notifications\CheckoutLicenseSeatNotification;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Notification;
use Exception;
@@ -40,6 +41,24 @@ class CheckoutableListener
Component::class,
];
/**
* Register the listeners for the subscriber.
*
* @param Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$events->listen(
\App\Events\CheckoutableCheckedIn::class,
'App\Listeners\CheckoutableListener@onCheckedIn'
);
$events->listen(
\App\Events\CheckoutableCheckedOut::class,
'App\Listeners\CheckoutableListener@onCheckedOut'
);
}
/**
* Notify the user and post to webhook about the checked out checkoutable
* and add a record to the checkout_requests table.
@@ -50,94 +69,70 @@ class CheckoutableListener
return;
}
/**
* Make a checkout acceptance and attach it in the notification
*/
$settings = Setting::getSettings();
$shouldSendEmailToUser = $this->shouldSendCheckoutEmailToUser($event->checkoutable);
$shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress();
$shouldSendWebhookNotification = $this->shouldSendWebhookNotification();
if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) {
return;
}
$acceptance = $this->getCheckoutAcceptance($event);
$adminCcEmailsArray = [];
if ($settings->admin_cc_email !== '') {
$adminCcEmail = $settings->admin_cc_email;
$adminCcEmailsArray = array_map('trim', explode(',', $adminCcEmail));
}
$ccEmails = array_filter($adminCcEmailsArray);
$mailable = $this->getCheckoutMailType($event, $acceptance);
$notifiable = $this->getNotifiableUsers($event);
if ($shouldSendEmailToUser || $shouldSendEmailToAlertAddress) {
$mailable = $this->getCheckoutMailType($event, $acceptance);
$notifiable = $this->getNotifiableUser($event);
$notifiableHasEmail = $notifiable instanceof User && $notifiable->email;
// Send email notifications
try {
/**
* Send an email if any of the following conditions are met:
* 1. The asset requires acceptance
* 2. The item has a EULA
* 3. The item should send an email at check-in/check-out
* 4. If the admin CC email is set, even if the item being checked out doesn't have an email address (location, etc)
*/
$shouldSendEmailToUser = $shouldSendEmailToUser && $notifiableHasEmail;
if ($event->checkoutable->requireAcceptance() /* does category require acceptance? */ ||
$event->checkoutable->getEula() /* is there *some* kind of EULA? */ ||
$this->checkoutableShouldSendEmail($event) /* does the category have 'checkin_email' [sic] set? */) {
[$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable);
// Send a checkout email to the admin CC addresses, even if the target has no email
if (!empty($ccEmails)) {
Mail::to($ccEmails)->send($mailable);
Log::info('Checkout Mail sent to CC addresses');
}
// Send a checkout email to the target if it has an email
if (!empty($notifiable->email)) {
Mail::to($notifiable)->send($mailable);
if (!empty($to)) {
try {
Mail::to(array_flatten($to))->cc(array_flatten($cc))->send($mailable);
Log::info('Checkout Mail sent to checkout target');
} catch (ClientException $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
} catch (Exception $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
}
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
} catch (Exception $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
}
// Send notification
try {
if ($this->shouldSendWebhookNotification()) {
if ($shouldSendWebhookNotification) {
try {
if ($this->newMicrosoftTeamsWebhookEnabled()) {
$message = $this->getCheckoutNotification($event)->toMicrosoftTeams();
$notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint);
$notification->success()->sendMessage($message[0], $message[1]); // Send the message to Microsoft Teams
} else {
Notification::route($this->webhookSelected(), Setting::getSettings()->webhook_endpoint)
->notify($this->getCheckoutNotification($event, $acceptance));
}
} catch (ClientException $e) {
if (strpos($e->getMessage(), 'channel_not_found') !== false) {
Log::warning(Setting::getSettings()->webhook_selected . " notification failed: " . $e->getMessage());
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_channel_not_found'));
} else {
Log::error("ClientException caught during checkin notification: " . $e->getMessage());
}
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail'));
} catch (Exception $e) {
Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
'error' => $e->getMessage(),
'webhook_endpoint' => Setting::getSettings()->webhook_endpoint,
'event' => $event,
]);
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail'));
}
} catch (ClientException $e) {
if (strpos($e->getMessage(), 'channel_not_found') !== false) {
Log::warning(Setting::getSettings()->webhook_selected." notification failed: " . $e->getMessage());
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_channel_not_found') );
}
else {
Log::error("ClientException caught during checkin notification: " . $e->getMessage());
}
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_fail') );
} catch (Exception $e) {
Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
'error' => $e->getMessage(),
'webhook_endpoint' => Setting::getSettings()->webhook_endpoint,
'event' => $event,
]);
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail'));
}
}
/**
* Notify the user and post to webhook about the checked in checkoutable
*/
*/
public function onCheckedIn($event)
{
Log::debug('onCheckedIn in the Checkoutable listener fired');
@@ -146,49 +141,44 @@ class CheckoutableListener
return;
}
/**
* Send the appropriate notification
*/
if ($event->checkedOutTo && $event->checkoutable){
$acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id)
->where('assigned_to_id', $event->checkedOutTo->id)
->get();
$shouldSendEmailToUser = $this->checkoutableCategoryShouldSendEmail($event->checkoutable);
$shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress();
$shouldSendWebhookNotification = $this->shouldSendWebhookNotification();
foreach($acceptances as $acceptance){
if($acceptance->isPending()){
$acceptance->delete();
if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) {
return;
}
if ($shouldSendEmailToUser || $shouldSendEmailToAlertAddress) {
/**
* Send the appropriate notification
*/
if ($event->checkedOutTo && $event->checkoutable) {
$acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id)
->where('assigned_to_id', $event->checkedOutTo->id)
->get();
foreach ($acceptances as $acceptance) {
if ($acceptance->isPending()) {
$acceptance->delete();
}
}
}
}
$settings = Setting::getSettings();
$adminCcEmailsArray = [];
if($settings->admin_cc_email !== '') {
$adminCcEmail = $settings->admin_cc_email;
$adminCcEmailsArray = array_map('trim', explode(',', $adminCcEmail));
}
$ccEmails = array_filter($adminCcEmailsArray);
$mailable = $this->getCheckinMailType($event);
$notifiable = $this->getNotifiableUsers($event);
$mailable = $this->getCheckinMailType($event);
$notifiable = $this->getNotifiableUser($event);
$notifiableHasEmail = $notifiable instanceof User && $notifiable->email;
$shouldSendEmailToUser = $shouldSendEmailToUser && $notifiableHasEmail;
[$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable);
// Send email notifications
if ($this->checkoutableShouldSendEmail($event)) {
try {
/**
* Send a check-in n email *only* if the item should send an email at check-in/check-out
*/
// Send a checkout email to the admin's CC addresses, even if the target has no email
if (!empty($ccEmails)) {
Mail::to($ccEmails)->send($mailable);
if (!empty($to)) {
Mail::to(array_flatten($to))->cc(array_flatten($cc))->send($mailable);
Log::info('Checkin Mail sent to CC addresses');
}
// Send a checkout email to the target if it has an email
if (!empty($notifiable->email)) {
Mail::to($notifiable)->send($mailable);
Log::info('Checkin Mail sent to checkout target');
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkin email: " . $e->getMessage());
} catch (Exception $e) {
@@ -196,9 +186,9 @@ class CheckoutableListener
}
}
// Send Webhook notification
try {
if ($this->shouldSendWebhookNotification()) {
if ($shouldSendWebhookNotification) {
// Send Webhook notification
try {
if ($this->newMicrosoftTeamsWebhookEnabled()) {
$message = $this->getCheckinNotification($event)->toMicrosoftTeams();
$notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint);
@@ -207,25 +197,24 @@ class CheckoutableListener
Notification::route($this->webhookSelected(), Setting::getSettings()->webhook_endpoint)
->notify($this->getCheckinNotification($event));
}
}
} catch (ClientException $e) {
if (strpos($e->getMessage(), 'channel_not_found') !== false) {
Log::warning(Setting::getSettings()->webhook_selected." notification failed: " . $e->getMessage());
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_channel_not_found') );
}
else {
Log::error("ClientException caught during checkin notification: " . $e->getMessage());
} catch (ClientException $e) {
if (strpos($e->getMessage(), 'channel_not_found') !== false) {
Log::warning(Setting::getSettings()->webhook_selected . " notification failed: " . $e->getMessage());
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_channel_not_found'));
} else {
Log::error("ClientException caught during checkin notification: " . $e->getMessage());
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail'));
}
} catch (Exception $e) {
Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
'error' => $e->getMessage(),
'webhook_endpoint' => Setting::getSettings()->webhook_endpoint,
'event' => $event,
]);
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail'));
}
} catch (Exception $e) {
Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
'error' => $e->getMessage(),
'webhook_endpoint' => Setting::getSettings()->webhook_endpoint,
'event' => $event,
]);
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_fail'));
}
}
}
/**
* Generates a checkout acceptance
@@ -247,13 +236,13 @@ class CheckoutableListener
$acceptance->assignedTo()->associate($event->checkedOutTo);
$acceptance->save();
return $acceptance;
return $acceptance;
}
/**
* Get the appropriate notification for the event
*
* @param CheckoutableCheckedIn $event
*
* @param CheckoutableCheckedIn $event
* @return Notification
*/
private function getCheckinNotification($event)
@@ -267,7 +256,7 @@ class CheckoutableListener
break;
case Asset::class:
$notificationClass = CheckinAssetNotification::class;
break;
break;
case LicenseSeat::class:
$notificationClass = CheckinLicenseSeatNotification::class;
break;
@@ -275,9 +264,8 @@ class CheckoutableListener
Log::debug('Notification class: '.$notificationClass);
return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note);
return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note);
}
/**
* Get the appropriate notification for the event
*
@@ -319,6 +307,7 @@ class CheckoutableListener
return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note);
}
private function getCheckinMailType($event){
$lookup = [
Accessory::class => CheckinAccessoryMail::class,
@@ -340,7 +329,8 @@ class CheckoutableListener
* @param $event
* @return mixed
*/
private function getNotifiableUsers($event){
private function getNotifiableUser($event)
{
// If it's assigned to an asset, get that asset's assignedTo object
if ($event->checkedOutTo instanceof Asset){
@@ -356,6 +346,7 @@ class CheckoutableListener
return $event->checkedOutTo;
}
}
private function webhookSelected(){
if(Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general'){
return 'slack';
@@ -364,32 +355,13 @@ class CheckoutableListener
return Setting::getSettings()->webhook_selected;
}
/**
* Register the listeners for the subscriber.
*
* @param Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$events->listen(
\App\Events\CheckoutableCheckedIn::class,
'App\Listeners\CheckoutableListener@onCheckedIn'
);
$events->listen(
\App\Events\CheckoutableCheckedOut::class,
'App\Listeners\CheckoutableListener@onCheckedOut'
);
}
private function shouldNotSendAnyNotifications($checkoutable): bool
{
if(in_array(get_class($checkoutable), $this->skipNotificationsFor)) {
return true;
}
if ($this->shouldSendWebhookNotification()) {
return false;
}
return in_array(get_class($checkoutable), $this->skipNotificationsFor);
}
private function shouldSendEmailNotifications(Model $checkoutable): bool
{
//runs a check if the category wants to send checkin/checkout emails to users
$category = match (true) {
$checkoutable instanceof Asset => $checkoutable->model->category,
@@ -400,27 +372,93 @@ class CheckoutableListener
};
if (!$category?->checkin_email) {
return true;
return false;
}
return false;
return true;
}
private function shouldSendWebhookNotification(): bool
{
return Setting::getSettings() && Setting::getSettings()->webhook_endpoint;
}
private function checkoutableShouldSendEmail($event): bool
private function checkoutableCategoryShouldSendEmail(Model $checkoutable): bool
{
if($event->checkoutable instanceof LicenseSeat){
return $event->checkoutable->license->checkin_email();
if ($checkoutable instanceof LicenseSeat) {
return $checkoutable->license->checkin_email();
}
return (method_exists($event->checkoutable, 'checkin_email') && $event->checkoutable->checkin_email());
return (method_exists($checkoutable, 'checkin_email') && $checkoutable->checkin_email());
}
private function newMicrosoftTeamsWebhookEnabled(): bool
{
return Setting::getSettings()->webhook_selected === 'microsoft' && Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows');
}
private function shouldSendCheckoutEmailToUser(Model $checkoutable): bool
{
/**
* Send an email if any of the following conditions are met:
* 1. The asset requires acceptance
* 2. The item has a EULA
* 3. The item should send an email at check-in/check-out
*/
if ($checkoutable->requireAcceptance()) {
return true;
}
if ($checkoutable->getEula()) {
return true;
}
if ($this->checkoutableCategoryShouldSendEmail($checkoutable)) {
return true;
}
return false;
}
private function shouldSendEmailToAlertAddress(): bool
{
return Setting::getSettings() && Setting::getSettings()->admin_cc_email;
}
private function getFormattedAlertAddresses(): array
{
$alertAddresses = Setting::getSettings()->admin_cc_email;
if ($alertAddresses !== '') {
return array_filter(array_map('trim', explode(',', $alertAddresses)));
}
return [];
}
private function generateEmailRecipients(
bool $shouldSendEmailToUser,
bool $shouldSendEmailToAlertAddress,
mixed $notifiable
): array {
$to = [];
$cc = [];
// if user && cc: to user, cc admin
if ($shouldSendEmailToUser && $shouldSendEmailToAlertAddress) {
$to[] = $notifiable;
$cc[] = $this->getFormattedAlertAddresses();
}
// if user && no cc: to user
if ($shouldSendEmailToUser && !$shouldSendEmailToAlertAddress) {
$to[] = $notifiable;
}
// if no user && cc: to admin
if (!$shouldSendEmailToUser && $shouldSendEmailToAlertAddress) {
$to[] = $this->getFormattedAlertAddresses();
}
return array($to, $cc);
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Notifications;
use AllowDynamicProperties;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Channels\SlackWebhookChannel;
@@ -12,7 +13,7 @@ use Illuminate\Support\Str;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
class AuditNotification extends Notification
#[AllowDynamicProperties] class AuditNotification extends Notification
{
use Queueable;
/**

View File

@@ -28,7 +28,7 @@ class CategoryFactory extends Factory
'checkin_email' => true,
'eula_text' => $this->faker->paragraph(),
'require_acceptance' => false,
'use_default_eula' => $this->faker->boolean(),
'use_default_eula' => false,
'created_by' => User::factory()->superuser(),
'notes' => 'Created by DB seeder',
];

View File

@@ -22,7 +22,7 @@
<div class="box-body">
<div class="row">
<div class="col-md-12">
<div class="table table-responsive">
<table
data-columns="{{ \App\Presenters\UserPresenter::dataTableLayout() }}"
@@ -41,7 +41,7 @@
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'>
</table>
</div>
</div>
</div>
</div>

View File

@@ -4,6 +4,8 @@ namespace Tests\Feature\Notifications\Email;
use App\Mail\CheckinAssetMail;
use App\Models\Accessory;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Consumable;
use App\Models\LicenseSeat;
use Illuminate\Support\Facades\Mail;
@@ -11,8 +13,6 @@ use PHPUnit\Framework\Attributes\Group;
use App\Events\CheckoutableCheckedIn;
use App\Models\Asset;
use App\Models\User;
use App\Notifications\CheckinAssetNotification;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
#[Group('notifications')]
@@ -25,10 +25,8 @@ class EmailNotificationsUponCheckinTest extends TestCase
Mail::fake();
}
public function testCheckInEmailSentToUserIfSettingEnabled()
public function test_check_in_email_sent_to_user_if_setting_enabled()
{
Mail::fake();
$user = User::factory()->create();
$asset = Asset::factory()->assignedToUser($user)->create();
@@ -39,13 +37,11 @@ class EmailNotificationsUponCheckinTest extends TestCase
Mail::assertSent(CheckinAssetMail::class, function($mail) use ($user) {
return $mail->hasTo($user->email);
});
}
public function testCheckInEmailNotSentToUserIfSettingDisabled()
public function test_check_in_email_not_sent_to_user_if_setting_disabled()
{
$this->settings->disableAdminCC();
Mail::fake();
$user = User::factory()->create();
$checkoutables = collect([
@@ -93,6 +89,69 @@ class EmailNotificationsUponCheckinTest extends TestCase
});
}
public function test_handles_user_not_having_email_address_set()
{
$user = User::factory()->create(['email' => null]);
$asset = Asset::factory()->assignedToUser($user)->create();
$asset->model->category->update(['checkin_email' => true]);
$this->fireCheckInEvent($asset, $user);
Mail::assertNothingSent();
}
public function test_admin_alert_email_sends()
{
$this->settings->enableAdminCC('cc@example.com');
$user = User::factory()->create();
$asset = Asset::factory()->assignedToUser($user)->create();
$asset->model->category->update(['checkin_email' => true]);
$this->fireCheckInEvent($asset, $user);
Mail::assertSent(CheckinAssetMail::class, function ($mail) use ($user) {
return $mail->hasTo($user->email) && $mail->hasCc('cc@example.com');
});
}
public function test_admin_alert_email_still_sent_when_category_email_is_not_set_to_send_email_to_user()
{
$this->settings->enableAdminCC('cc@example.com');
$category = Category::factory()->create([
'checkin_email' => false,
'eula_text' => null,
'use_default_eula' => false,
]);
$assetModel = AssetModel::factory()->create(['category_id' => $category->id]);
$asset = Asset::factory()->create(['model_id' => $assetModel->id]);
$this->fireCheckInEvent($asset, User::factory()->create());
Mail::assertSent(CheckinAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com');
});
}
public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
{
$this->settings->enableAdminCC('cc@example.com');
$user = User::factory()->create(['email' => null]);
$asset = Asset::factory()->assignedToUser($user)->create();
$asset->model->category->update(['checkin_email' => true]);
$this->fireCheckInEvent($asset, $user);
Mail::assertSent(CheckinAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com');
});
}
private function fireCheckInEvent($asset, $user): void
{
event(new CheckoutableCheckedIn(

View File

@@ -0,0 +1,144 @@
<?php
namespace Tests\Feature\Notifications\Email;
use App\Events\CheckoutableCheckedOut;
use App\Mail\CheckoutAssetMail;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use PHPUnit\Framework\Attributes\Group;
use Tests\TestCase;
#[Group('notifications')]
class EmailNotificationsUponCheckoutTest extends TestCase
{
private Asset $asset;
private AssetModel $assetModel;
private Category $category;
private User $user;
protected function setUp(): void
{
parent::setUp();
Mail::fake();
$this->category = Category::factory()->create([
'checkin_email' => false,
'eula_text' => null,
'require_acceptance' => false,
'use_default_eula' => false,
]);
$this->assetModel = AssetModel::factory()->for($this->category)->create();
$this->asset = Asset::factory()->for($this->assetModel, 'model')->create();
$this->user = User::factory()->create();
}
public function test_email_sent_to_user_when_category_requires_acceptance()
{
$this->category->update(['require_acceptance' => true]);
$this->fireCheckoutEvent();
$this->assertUserSentEmail();
}
public function test_email_sent_to_user_when_category_using_default_eula()
{
$this->settings->setEula();
$this->category->update(['use_default_eula' => true]);
$this->fireCheckoutEvent();
$this->assertUserSentEmail();
}
public function test_email_sent_to_user_when_category_using_local_eula()
{
$this->category->update(['eula_text' => 'Some EULA text']);
$this->fireCheckoutEvent();
$this->assertUserSentEmail();
}
public function test_email_sent_to_user_when_category_set_to_explicitly_send_email()
{
$this->category->update(['checkin_email' => true]);
$this->fireCheckoutEvent();
$this->assertUserSentEmail();
}
public function test_handles_user_not_having_email_address_set()
{
$this->category->update(['checkin_email' => true]);
$this->user->update(['email' => null]);
$this->fireCheckoutEvent();
Mail::assertNothingSent();
}
public function test_admin_alert_email_sends()
{
$this->settings->enableAdminCC('cc@example.com');
$this->category->update(['checkin_email' => true]);
$this->fireCheckoutEvent();
Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
return $mail->hasCc('cc@example.com');
});
}
public function test_admin_alert_email_still_sent_when_category_is_not_set_to_send_email_to_user()
{
$this->settings->enableAdminCC('cc@example.com');
$this->fireCheckoutEvent();
Mail::assertSent(CheckoutAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com');
});
}
public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
{
$this->settings->enableAdminCC('cc@example.com');
$this->category->update(['checkin_email' => true]);
$this->user->update(['email' => null]);
$this->fireCheckoutEvent();
Mail::assertSent(CheckoutAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com');
});
}
private function fireCheckoutEvent(): void
{
event(new CheckoutableCheckedOut(
$this->asset,
$this->user,
User::factory()->superuser()->create(),
'',
));
}
private function assertUserSentEmail(): void
{
Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
return $mail->hasTo($this->user->email);
});
}
}

View File

@@ -2,6 +2,8 @@
namespace Tests\Feature\Notifications\Webhooks;
use App\Models\AssetModel;
use App\Models\Category;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use App\Events\CheckoutableCheckedIn;
@@ -95,6 +97,27 @@ class SlackNotificationsUponCheckinTest extends TestCase
$this->assertNoSlackNotificationSent(CheckinAssetNotification::class);
}
public function testSlackNotificationIsStillSentWhenCategoryEmailIsNotSetToSendEmails()
{
$this->settings->enableSlackWebhook();
$category = Category::factory()->create([
'checkin_email' => false,
'eula_text' => null,
'require_acceptance' => false,
'use_default_eula' => false,
]);
$assetModel = AssetModel::factory()->for($category)->create();
$asset = Asset::factory()->for($assetModel, 'model')->assignedToUser()->create();
$this->fireCheckInEvent(
$asset,
User::factory()->create(),
);
$this->assertSlackNotificationSent(CheckinAssetNotification::class);
}
public function testComponentCheckinDoesNotSendSlackNotification()
{
$this->settings->enableSlackWebhook();

View File

@@ -2,6 +2,8 @@
namespace Tests\Feature\Notifications\Webhooks;
use App\Models\AssetModel;
use App\Models\Category;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use App\Events\CheckoutableCheckedOut;
@@ -97,6 +99,27 @@ class SlackNotificationsUponCheckoutTest extends TestCase
$this->assertNoSlackNotificationSent(CheckoutAssetNotification::class);
}
public function testSlackNotificationIsStillSentWhenCategoryEmailIsNotSetToSendEmails()
{
$this->settings->enableSlackWebhook();
$category = Category::factory()->create([
'checkin_email' => false,
'eula_text' => null,
'require_acceptance' => false,
'use_default_eula' => false,
]);
$assetModel = AssetModel::factory()->for($category)->create();
$asset = Asset::factory()->for($assetModel, 'model')->create();
$this->fireCheckOutEvent(
$asset,
User::factory()->create(),
);
$this->assertSlackNotificationSent(CheckoutAssetNotification::class);
}
public function testComponentCheckoutDoesNotSendSlackNotification()
{
$this->settings->enableSlackWebhook();

View File

@@ -46,6 +46,13 @@ class Settings
]);
}
public function enableAdminCC(string $email = 'cc@example.co'): Settings
{
return $this->update([
'admin_cc_email' => $email,
]);
}
public function disableAdminCC(): Settings
{
return $this->update([