diff --git a/app/Listeners/CheckoutableListener.php b/app/Listeners/CheckoutableListener.php index 9766c88da1..7e289b19e6 100644 --- a/app/Listeners/CheckoutableListener.php +++ b/app/Listeners/CheckoutableListener.php @@ -4,10 +4,12 @@ namespace App\Listeners; use App\Events\CheckoutableCheckedOut; use App\Mail\CheckinAccessoryMail; +use App\Mail\CheckinComponentMail; use App\Mail\CheckinLicenseMail; use App\Mail\CheckoutAccessoryMail; use App\Mail\CheckoutAssetMail; use App\Mail\CheckinAssetMail; +use App\Mail\CheckoutComponentMail; use App\Mail\CheckoutConsumableMail; use App\Mail\CheckoutLicenseMail; use App\Models\Accessory; @@ -22,9 +24,11 @@ use App\Models\Setting; use App\Models\User; use App\Notifications\CheckinAccessoryNotification; use App\Notifications\CheckinAssetNotification; +use App\Notifications\CheckinComponentNotification; use App\Notifications\CheckinLicenseSeatNotification; use App\Notifications\CheckoutAccessoryNotification; use App\Notifications\CheckoutAssetNotification; +use App\Notifications\CheckoutComponentNotification; use App\Notifications\CheckoutConsumableNotification; use App\Notifications\CheckoutLicenseSeatNotification; use GuzzleHttp\Exception\ClientException; @@ -39,7 +43,7 @@ use Osama\LaravelTeamsNotification\TeamsNotification; class CheckoutableListener { private array $skipNotificationsFor = [ - Component::class, +// Component::class, ]; /** @@ -145,7 +149,6 @@ class CheckoutableListener $shouldSendEmailToUser = $this->checkoutableCategoryShouldSendEmail($event->checkoutable); $shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress(); $shouldSendWebhookNotification = $this->shouldSendWebhookNotification(); - if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) { return; } @@ -269,6 +272,9 @@ class CheckoutableListener case LicenseSeat::class: $notificationClass = CheckinLicenseSeatNotification::class; break; + case Component::class: + $notificationClass = CheckinComponentNotification::class; + break; } Log::debug('Notification class: '.$notificationClass); @@ -299,6 +305,9 @@ class CheckoutableListener case LicenseSeat::class: $notificationClass = CheckoutLicenseSeatNotification::class; break; + case Component::class: + $notificationClass = CheckoutComponentNotification::class; + break; } @@ -310,6 +319,7 @@ class CheckoutableListener Asset::class => CheckoutAssetMail::class, LicenseSeat::class => CheckoutLicenseMail::class, Consumable::class => CheckoutConsumableMail::class, + Component::class => CheckoutComponentMail::class, ]; $mailable= $lookup[get_class($event->checkoutable)]; @@ -322,8 +332,8 @@ class CheckoutableListener Accessory::class => CheckinAccessoryMail::class, Asset::class => CheckinAssetMail::class, LicenseSeat::class => CheckinLicenseMail::class, + Component::class => CheckinComponentMail::class, ]; - $mailable= $lookup[get_class($event->checkoutable)]; return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note); @@ -469,7 +479,8 @@ class CheckoutableListener return match (true) { $checkoutable instanceof Asset => $checkoutable->model->category, $checkoutable instanceof Accessory, - $checkoutable instanceof Consumable => $checkoutable->category, + $checkoutable instanceof Consumable, + $checkoutable instanceof Component => $checkoutable->category, $checkoutable instanceof LicenseSeat => $checkoutable->license->category, }; } diff --git a/app/Mail/CheckinComponentMail.php b/app/Mail/CheckinComponentMail.php new file mode 100644 index 0000000000..5b62a2c4b8 --- /dev/null +++ b/app/Mail/CheckinComponentMail.php @@ -0,0 +1,71 @@ +item = $component; + $this->target = $checkedOutTo; + $this->admin = $checkedInby; + $this->note = $note; + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.Confirm_component_checkin'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'mail.markdown.checkin-component', + with: [ + 'item' => $this->item, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckoutComponentMail.php b/app/Mail/CheckoutComponentMail.php new file mode 100644 index 0000000000..e914d14196 --- /dev/null +++ b/app/Mail/CheckoutComponentMail.php @@ -0,0 +1,82 @@ +item = $component; + $this->admin = $checkedOutBy; + $this->note = $note; + $this->target = $checkedOutTo; + $this->acceptance = $acceptance; + $this->qty = $component->assets->first()?->pivot?->assigned_qty; + + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.Confirm_component_delivery'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + + $eula = $this->item->getEula(); + $req_accept = $this->item->requireAcceptance(); + + $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); + + return new Content( + markdown: 'mail.markdown.checkout-component', + with: [ + 'item' => $this->item, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + 'eula' => $eula, + 'req_accept' => $req_accept, + 'accept_url' => $accept_url, + 'qty' => $this->qty, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Models/Component.php b/app/Models/Component.php index be362b71bb..a83993c225 100644 --- a/app/Models/Component.php +++ b/app/Models/Component.php @@ -2,12 +2,14 @@ namespace App\Models; +use App\Helpers\Helper; use App\Models\Traits\HasUploads; use App\Models\Traits\Searchable; use App\Presenters\Presentable; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Facades\Storage; use Watson\Validating\ValidatingTrait; /** @@ -203,6 +205,36 @@ class Component extends SnipeModel { return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id'); } + /** + * Determine whether this asset requires acceptance by the assigned user + * + * @author [A. Gianotto] [] + * @since [v4.0] + * @return bool + */ + public function requireAcceptance() + { + return $this->category->require_acceptance; + } + + /** + * Checks for a category-specific EULA, and if that doesn't exist, + * checks for a settings level EULA + * + * @author [A. Gianotto] [] + * @since [v4.0] + * @return string | false + */ + public function getEula() + { + if ($this->category->eula_text) { + return Helper::parseEscapedMarkedown($this->category->eula_text); + } elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) { + return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text); + } else { + return null; + } + } /** * Establishes the component -> action logs relationship @@ -248,6 +280,19 @@ class Component extends SnipeModel } + /** + * Determine whether to send a checkin/checkout email based on + * asset model category + * + * @author [A. Gianotto] [] + * @since [v4.0] + * @return bool + */ + public function checkin_email() + { + return $this->category?->checkin_email; + } + /** * Check how many items within a component are remaining diff --git a/app/Notifications/CheckinComponentNotification.php b/app/Notifications/CheckinComponentNotification.php new file mode 100644 index 0000000000..4057c26812 --- /dev/null +++ b/app/Notifications/CheckinComponentNotification.php @@ -0,0 +1,165 @@ +target = $checkedOutTo; + $this->item = $component; + $this->admin = $checkedInBy; + $this->note = $note; + $this->settings = Setting::getSettings(); + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via() + { + $notifyBy = []; + + if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) { + + $notifyBy[] = GoogleChatChannel::class; + } + if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) { + + $notifyBy[] = MicrosoftTeamsChannel::class; + } + + if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { + $notifyBy[] = SlackWebhookChannel::class; + } + + return $notifyBy; + } + + public function toSlack() + { + $target = $this->target; + $admin = $this->admin; + $item = $this->item; + $note = $this->note; + $botname = ($this->settings->webhook_botname) ? $this->settings->webhook_botname : 'Snipe-Bot'; + $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; + + if ($admin) { + $fields = [ + trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + ]; + + if ($item->location) { + $fields[trans('general.location')] = $item->location->name; + } + + if ($item->company) { + $fields[trans('general.company')] = $item->company->name; + } + + } else { + $fields = [ + 'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + 'By' => 'CLI tool', + ]; + } + + return (new SlackMessage) + ->content(':arrow_down: :package: '.trans('mail.Component_checkin_notification')) + ->from($botname) + ->to($channel) + ->attachment(function ($attachment) use ($item, $note, $admin, $fields) { + $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + ->fields($fields) + ->content($note); + }); + } + public function toMicrosoftTeams() + { + $target = $this->target; + $admin = $this->admin; + $item = $this->item; + $note = $this->note; + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->addStartGroupToSection('activityTitle') + ->title(trans('mail.Component_checkin_notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->present()->name), '', 'header') + ->fact(trans('mail.Component_checkin_notification')." by ", $admin->present()->fullName() ?: 'CLI tool') + ->fact(trans('mail.checkedin_from'), $target->present()->fullName()) + ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) + ->fact(trans('mail.notes'), $note ?: ''); + } + + $message = trans('mail.Component_checkin_notification'); + $details = [ + trans('mail.checkedin_from')=> $target->present()->fullName(), + trans('mail.Component_checkin_notification')." by " => $admin->present()->fullName() ?: 'CLI tool', + trans('admin/consumables/general.remaining') => $item->numRemaining(), + trans('mail.notes') => $note ?: '', + ]; + + return array($message, $details); + } + public function toGoogleChat() + { + $target = $this->target; + $item = $this->item; + $note = $this->note; + + return GoogleChatMessage::create() + ->to($this->settings->webhook_endpoint) + ->card( + Card::create() + ->header( + ''.trans('mail.Component_checkin_notification').'' ?: '', + htmlspecialchars_decode($item->present()->name) ?: '', + ) + ->section( + Section::create( + KeyValue::create( + trans('mail.checkedin_from') ?: '', + $target->present()->fullName() ?: '', + trans('admin/consumables/general.remaining').': '.$item->numRemaining(), + ) + ->onClick(route('components.show', $item->id)) + ) + ) + ); + + } +} diff --git a/app/Notifications/CheckoutComponentNotification.php b/app/Notifications/CheckoutComponentNotification.php new file mode 100644 index 0000000000..fab303fb52 --- /dev/null +++ b/app/Notifications/CheckoutComponentNotification.php @@ -0,0 +1,164 @@ +item = $component; + $this->admin = $checkedOutBy; + $this->note = $note; + $this->target = $checkedOutTo; + $this->acceptance = $acceptance; + $this->qty = $component->checkout_qty; + + $this->settings = Setting::getSettings(); + } + + /**` + * Get the notification's delivery channels. + * + * @return array + */ + public function via() + { + $notifyBy = []; + if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) { + + $notifyBy[] = GoogleChatChannel::class; + } + + if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) { + + $notifyBy[] = MicrosoftTeamsChannel::class; + } + + if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { + $notifyBy[] = SlackWebhookChannel::class; + } + + return $notifyBy; + } + + public function toSlack() + { + $target = $this->target; + $admin = $this->admin; + $item = $this->item; + $note = $this->note; + $botname = ($this->settings->webhook_botname) ? $this->settings->webhook_botname : 'Snipe-Bot'; + $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; + + $fields = [ + trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + ]; + + if ($item->location) { + $fields[trans('general.location')] = $item->location->name; + } + + if ($item->company) { + $fields[trans('general.company')] = $item->company->name; + } + + return (new SlackMessage) + ->content(':arrow_up: :package: '.trans('mail.Component_checkout_notification')) + ->from($botname) + ->to($channel) + ->attachment(function ($attachment) use ($item, $note, $admin, $fields) { + $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + ->fields($fields) + ->content($note); + }); + } + public function toMicrosoftTeams() + { + $target = $this->target; + $admin = $this->admin; + $item = $this->item; + $note = $this->note; + + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->addStartGroupToSection('activityTitle') + ->title(trans('mail.Component_checkout_notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') + ->fact(trans('mail.Component_checkout_notification')." by ", $admin->present()->fullName()) + ->fact(trans('mail.assigned_to'), $target->present()->fullName()) + ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) + ->fact(trans('mail.notes'), $note ?: ''); + } + + $message = trans('mail.Component_checkout_notification'); + $details = [ + trans('mail.assigned_to') => $target->present()->fullName(), + trans('mail.item') => htmlspecialchars_decode($item->present()->name), + trans('mail.Component_checkout_notification').' by' => $admin->present()->fullName(), + trans('admin/consumables/general.remaining') => $item->numRemaining(), + trans('mail.notes') => $note ?: '', + ]; + + return array($message, $details); + } + public function toGoogleChat() + { + $target = $this->target; + $item = $this->item; + $note = $this->note; + + return GoogleChatMessage::create() + ->to($this->settings->webhook_endpoint) + ->card( + Card::create() + ->header( + ''.trans('mail.Component_checkout_notification').'' ?: '', + htmlspecialchars_decode($item->present()->name) ?: '', + ) + ->section( + Section::create( + KeyValue::create( + trans('mail.assigned_to') ?: '', + $target->present()->fullName() ?: '', + trans('admin/consumables/general.remaining').': '.$item->numRemaining(), + ) + ->onClick(route('api.assets.show', $target->id)) + ) + ) + ); + + } +} diff --git a/resources/lang/en-US/mail.php b/resources/lang/en-US/mail.php index 92dba460fa..5efd9c06fe 100644 --- a/resources/lang/en-US/mail.php +++ b/resources/lang/en-US/mail.php @@ -8,11 +8,15 @@ return [ 'Asset_Checkout_Notification' => 'Asset checked out', 'Confirm_Accessory_Checkin' => 'Accessory checkin confirmation', 'Confirm_Asset_Checkin' => 'Asset checkin confirmation', + 'Confirm_component_checkin' => 'Component checkin confirmation', 'Confirm_accessory_delivery' => 'Accessory delivery confirmation', 'Confirm_asset_delivery' => 'Asset delivery confirmation', 'Confirm_consumable_delivery' => 'Consumable delivery confirmation', + 'Confirm_component_delivery' => 'Component delivery confirmation', 'Confirm_license_delivery' => 'License delivery confirmation', 'Consumable_checkout_notification' => 'Consumable checked out', + 'Component_checkout_notification' => 'Component checked out', + 'Component_checkin_notification' => 'Component checked in', 'Days' => 'Days', 'Expected_Checkin_Date' => 'An asset checked out to you is due to be checked back in on :date', 'Expected_Checkin_Notification' => 'Reminder: :name checkin deadline approaching', diff --git a/resources/views/components/checkout.blade.php b/resources/views/components/checkout.blade.php index a55ca4b21e..8e554575f5 100644 --- a/resources/views/components/checkout.blade.php +++ b/resources/views/components/checkout.blade.php @@ -41,7 +41,31 @@ @endif + @if ($component->requireAcceptance() || $component->getEula() || ($snipeSettings->webhook_endpoint!='')) +
+
+
+ @if ($component->category->require_acceptance=='1') + + {{ trans('admin/categories/general.required_acceptance') }} +
+ @endif + + @if ($component->getEula()) + + {{ trans('admin/categories/general.required_eula') }} +
+ @endif + + @if ($snipeSettings->webhook_endpoint!='') + + {{ trans('general.webhook_msg_note') }} + @endif +
+
+
+ @endif
diff --git a/resources/views/mail/markdown/checkin-component.blade.php b/resources/views/mail/markdown/checkin-component.blade.php new file mode 100644 index 0000000000..70442f5e3e --- /dev/null +++ b/resources/views/mail/markdown/checkin-component.blade.php @@ -0,0 +1,25 @@ +@component('mail::message') +# {{ trans('mail.hello') }} {{ $target->assignedto->present()->fullName() }}, + +{{ trans('mail.the_following_item') }} + +@component('mail::table') +| | | +| ------------- | ------------- | +| **{{ trans('general.component') }}** | {{ $item->name }} | +@if (isset($item->manufacturer)) +| **{{ trans('general.manufacturer') }}** | {{ $item->manufacturer->name }} | +@endif +@if ($admin) +| **{{ trans('general.administrator') }}** | {{ $admin->present()->fullName() }} | +@endif +@if ($note) +| **{{ trans('mail.additional_notes') }}** | {{ $note }} | +@endif +@endcomponent + +{{ trans('mail.best_regards') }} + +{{ $snipeSettings->site_name }} + +@endcomponent diff --git a/resources/views/mail/markdown/checkout-component.blade.php b/resources/views/mail/markdown/checkout-component.blade.php new file mode 100644 index 0000000000..b28d291961 --- /dev/null +++ b/resources/views/mail/markdown/checkout-component.blade.php @@ -0,0 +1,50 @@ +@component('mail::message') +# {{ trans('mail.hello') }} {{ $target->assignedto->present()->fullName() }}, + +{{ trans('mail.new_item_checked') }} + +@component('mail::table') +| | | +| ------------- | ------------- | +@if (isset($checkout_date)) +| **{{ trans('mail.checkout_date') }}** | {{ $checkout_date }} | +@endif +| **{{ trans('general.component') }}** | {{ $item->name }} | +@if (isset($qty)) +| **{{ trans('general.qty') }}** | {{ $qty }} | +@endif +@if (isset($item->manufacturer)) +| **{{ trans('general.manufacturer') }}** | {{ $item->manufacturer->name }} | +@endif +@if ($note) +| **{{ trans('mail.additional_notes') }}** | {{ $note }} | +@endif +@if ($admin) +| **{{ trans('general.administrator') }}** | {{ $admin->present()->fullName() }} | +@endif +@endcomponent + +@if (($req_accept == 1) && ($eula!='')) + {{ trans('mail.read_the_terms_and_click') }} +@elseif (($req_accept == 1) && ($eula=='')) + {{ trans('mail.click_on_the_link_asset') }} +@elseif (($req_accept == 0) && ($eula!='')) + {{ trans('mail.read_the_terms') }} +@endif + +@if ($eula) +@component('mail::panel') + {!! $eula !!} +@endcomponent +@endif + +@if ($req_accept == 1) +**[✔ {{ trans('mail.i_have_read') }}]({{ $accept_url }})** +@endif + + +{{ trans('mail.best_regards') }} + +{{ $snipeSettings->site_name }} + +@endcomponent \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 9ce977309d..40a1d163ec 100644 --- a/routes/web.php +++ b/routes/web.php @@ -52,7 +52,12 @@ Route::group(['middleware' => 'auth'], function () { [LabelsController::class, 'show'] )->where('labelName', '.*')->name('labels.show'); + Route::get('/test-email', function () { + $mailable = new \App\Mail\CheckoutComponentMail( + ); + return $mailable->render(); // dumps HTML + }); /* * Manufacturers */ diff --git a/tests/Feature/Notifications/Webhooks/SlackNotificationsUponCheckinTest.php b/tests/Feature/Notifications/Webhooks/SlackNotificationsUponCheckinTest.php index e2a97c1603..d366103d74 100644 --- a/tests/Feature/Notifications/Webhooks/SlackNotificationsUponCheckinTest.php +++ b/tests/Feature/Notifications/Webhooks/SlackNotificationsUponCheckinTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature\Notifications\Webhooks; use App\Models\AssetModel; use App\Models\Category; +use App\Notifications\CheckinComponentNotification; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use App\Events\CheckoutableCheckedIn; @@ -96,6 +97,31 @@ class SlackNotificationsUponCheckinTest extends TestCase $this->assertNoSlackNotificationSent(CheckinAssetNotification::class); } + #[DataProvider('assetCheckInTargets')] + public function testComponentCheckinSendsSlackNotificationWhenSettingEnabled($checkoutTarget) + { + $this->settings->enableSlackWebhook(); + + $this->fireCheckInEvent( + Component::factory()->create(), + $checkoutTarget(), + ); + + $this->assertSlackNotificationSent(CheckinComponentNotification::class); + } + + #[DataProvider('assetCheckInTargets')] + public function testComponentCheckinDoesNotSendSlackNotificationWhenSettingDisabled($checkoutTarget) + { + $this->settings->disableSlackWebhook(); + + $this->fireCheckInEvent( + Component::factory()->create(), + $checkoutTarget(), + ); + + $this->assertNoSlackNotificationSent(CheckinComponentNotification::class); + } public function testSlackNotificationIsStillSentWhenCategoryEmailIsNotSetToSendEmails() { @@ -118,18 +144,6 @@ class SlackNotificationsUponCheckinTest extends TestCase $this->assertSlackNotificationSent(CheckinAssetNotification::class); } - public function testComponentCheckinDoesNotSendSlackNotification() - { - $this->settings->enableSlackWebhook(); - - $this->fireCheckInEvent( - Component::factory()->create(), - Asset::factory()->laptopMbp()->create(), - ); - - Notification::assertNothingSent(); - } - #[DataProvider('licenseCheckInTargets')] public function testLicenseCheckinSendsSlackNotificationWhenSettingEnabled($checkoutTarget) { diff --git a/tests/Feature/Notifications/Webhooks/SlackNotificationsUponCheckoutTest.php b/tests/Feature/Notifications/Webhooks/SlackNotificationsUponCheckoutTest.php index c9f426dd30..908acddf3b 100644 --- a/tests/Feature/Notifications/Webhooks/SlackNotificationsUponCheckoutTest.php +++ b/tests/Feature/Notifications/Webhooks/SlackNotificationsUponCheckoutTest.php @@ -4,6 +4,8 @@ namespace Tests\Feature\Notifications\Webhooks; use App\Models\AssetModel; use App\Models\Category; +use App\Notifications\CheckoutComponentNotification; +use Illuminate\Support\Facades\Mail; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use App\Events\CheckoutableCheckedOut; @@ -30,12 +32,13 @@ class SlackNotificationsUponCheckoutTest extends TestCase parent::setUp(); Notification::fake(); + Mail::fake(); } public static function assetCheckoutTargets(): array { return [ - 'Asset checked out to user' => [fn() => User::factory()->create()], + 'Asset checked out to user' => [fn() => User::factory()->create(['email' => null])], 'Asset checked out to asset' => [fn() => Asset::factory()->laptopMbp()->create()], 'Asset checked out to location' => [fn() => Location::factory()->create()], ]; @@ -44,7 +47,7 @@ class SlackNotificationsUponCheckoutTest extends TestCase public static function licenseCheckoutTargets(): array { return [ - 'License checked out to user' => [fn() => User::factory()->create()], + 'License checked out to user' => [fn() => User::factory()->create(['email' => null])], 'License checked out to asset' => [fn() => Asset::factory()->laptopMbp()->create()], ]; } @@ -98,7 +101,41 @@ class SlackNotificationsUponCheckoutTest extends TestCase $this->assertNoSlackNotificationSent(CheckoutAssetNotification::class); } + #[DataProvider('assetCheckoutTargets')] + public function testComponentCheckoutSendsSlackNotificationWhenSettingEnabled($checkoutTarget) + { + $this->settings->enableSlackWebhook(); + $component = Component::factory()->create([ + 'category_id' => Category::factory()->create([ + 'require_acceptance' => false, + 'eula_text' => null, + ]), + ]); + $this->fireCheckOutEvent( + $component, + $checkoutTarget(), + ); + $this->assertSlackNotificationSent(CheckoutComponentNotification::class); + } + + #[DataProvider('assetCheckoutTargets')] + public function testComponentCheckoutDoesNotSendSlackNotificationWhenSettingDisabled($checkoutTarget) + { + $this->settings->disableSlackWebhook(); + $component = Component::factory()->create([ + 'category_id' => Category::factory()->create([ + 'require_acceptance' => false, + 'eula_text' => null, + ]), + ]); + $this->fireCheckOutEvent( + $component, + $checkoutTarget(), + ); + + $this->assertNoSlackNotificationSent(CheckoutComponentNotification::class); + } public function testSlackNotificationIsStillSentWhenCategoryEmailIsNotSetToSendEmails() { $this->settings->enableSlackWebhook(); @@ -120,18 +157,6 @@ class SlackNotificationsUponCheckoutTest extends TestCase $this->assertSlackNotificationSent(CheckoutAssetNotification::class); } - public function testComponentCheckoutDoesNotSendSlackNotification() - { - $this->settings->enableSlackWebhook(); - - $this->fireCheckOutEvent( - Component::factory()->create(), - Asset::factory()->laptopMbp()->create(), - ); - - Notification::assertNothingSent(); - } - public function testConsumableCheckoutSendsSlackNotificationWhenSettingEnabled() { $this->settings->enableSlackWebhook();