admin = User::factory()->canViewReports()->create(); $this->assignee = User::factory()->create(); } public function test_must_have_permission_to_send_reminder() { $checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create(); $userWithoutPermission = User::factory()->create(); $this->actingAs($userWithoutPermission) ->post(route('reports/unaccepted_assets_sent_reminder', [ 'acceptance_id' => $checkoutAcceptance->id, ])) ->assertForbidden(); Mail::assertNotSent(CheckoutAssetMail::class); } public function test_reminder_not_sent_if_acceptance_does_not_exist() { $this->actingAs($this->admin) ->post(route('reports/unaccepted_assets_sent_reminder', [ 'acceptance_id' => 999999, ])); Mail::assertNotSent(CheckoutAssetMail::class); } public function test_reminder_not_sent_if_acceptance_already_accepted() { $checkoutAcceptanceAlreadyAccepted = CheckoutAcceptance::factory()->accepted()->create(); $this->actingAs($this->admin) ->post(route('reports/unaccepted_assets_sent_reminder', [ 'acceptance_id' => $checkoutAcceptanceAlreadyAccepted->id, ])); Mail::assertNotSent(CheckoutAssetMail::class); } public static function checkoutAcceptancesToUsersWithoutEmailAddresses(): Generator { yield 'User with null email address' => [ function () { return CheckoutAcceptance::factory() ->pending() ->forAssignedTo(['email' => null]) ->create(); }, ]; yield 'User with empty string email address' => [ function () { return CheckoutAcceptance::factory() ->pending() ->forAssignedTo(['email' => '']) ->create(); }, ]; } #[DataProvider('checkoutAcceptancesToUsersWithoutEmailAddresses')] public function test_user_without_email_address_handled_gracefully($callback) { $checkoutAcceptance = $callback(); $this->actingAs($this->admin) ->post(route('reports/unaccepted_assets_sent_reminder', [ 'acceptance_id' => $checkoutAcceptance->id, ])) // check we didn't crash... ->assertRedirect(); Mail::assertNotSent(CheckoutAssetMail::class); } public function test_reminder_is_sent_to_user_for_accessory() { $accessory = Accessory::factory()->requiringAcceptance()->create(); $acceptance = $this->createCheckoutAcceptance($accessory, $this->assignee); $this->createActionLogEntry($accessory, $this->admin, $this->assignee, $acceptance); AccessoryCheckout::factory() ->for($this->admin, 'adminuser') ->for($accessory) ->for($this->assignee, 'assignedTo') ->create(); $this->actingAs($this->admin) ->post(route('reports/unaccepted_assets_sent_reminder', [ 'acceptance_id' => $acceptance->id, ])) ->assertRedirect(route('reports/unaccepted_assets')); Mail::assertSent(CheckoutAccessoryMail::class, 1); Mail::assertSent(CheckoutAccessoryMail::class, function (CheckoutAccessoryMail $mail) { return $mail->hasTo($this->assignee); }); Mail::assertSent(CheckoutAccessoryMail::class, function (CheckoutAccessoryMail $mail) { return $mail->hasSubject(trans('mail.unaccepted_asset_reminder')); }); Mail::assertSent(CheckoutAccessoryMail::class, function (CheckoutAccessoryMail $mail) { return str_contains($mail->render(), trans('mail.recent_item_checked')); }); } public function test_reminder_is_sent_to_user_for_asset() { $asset = Asset::factory()->requiresAcceptance()->create(); $acceptance = $this->createCheckoutAcceptance($asset, $this->assignee); $this->createActionLogEntry($asset, $this->admin, $this->assignee, $acceptance); $this->actingAs($this->admin) ->post(route('reports/unaccepted_assets_sent_reminder', [ 'acceptance_id' => $acceptance->id, ])) ->assertRedirect(route('reports/unaccepted_assets')); Mail::assertSent(CheckoutAssetMail::class, 1); Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) { return $mail->hasTo($this->assignee); }); Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) { return $mail->hasSubject(trans('mail.unaccepted_asset_reminder')); }); Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) { return str_contains($mail->render(), trans('mail.recent_item_checked')); }); } public function test_reminder_is_sent_to_user_for_consumable() { $consumable = Consumable::factory()->requiringAcceptance()->create(); $acceptance = $this->createCheckoutAcceptance($consumable, $this->assignee); $this->createActionLogEntry($consumable, $this->admin, $this->assignee, $acceptance); $this->actingAs($this->admin) ->post(route('reports/unaccepted_assets_sent_reminder', [ 'acceptance_id' => $acceptance->id, ])) ->assertRedirect(route('reports/unaccepted_assets')); Mail::assertSent(CheckoutConsumableMail::class, 1); Mail::assertSent(CheckoutConsumableMail::class, function (CheckoutConsumableMail $mail) { return $mail->hasTo($this->assignee); }); Mail::assertSent(CheckoutConsumableMail::class, function (CheckoutConsumableMail $mail) { return $mail->hasSubject(trans('mail.unaccepted_asset_reminder')); }); Mail::assertSent(CheckoutConsumableMail::class, function (CheckoutConsumableMail $mail) { return str_contains($mail->render(), trans('mail.recent_item_checked')); }); } public function test_reminder_is_sent_to_user_for_license_seat() { $licenseSeat = LicenseSeat::factory()->requiringAcceptance()->create(); $acceptance = $this->createCheckoutAcceptance($licenseSeat, $this->assignee); $this->createActionLogEntry($licenseSeat, $this->admin, $this->assignee, $acceptance); $this->actingAs($this->admin) ->post(route('reports/unaccepted_assets_sent_reminder', [ 'acceptance_id' => $acceptance->id, ])) ->assertRedirect(route('reports/unaccepted_assets')); Mail::assertSent(CheckoutLicenseMail::class, 1); Mail::assertSent(CheckoutLicenseMail::class, function (CheckoutLicenseMail $mail) { return $mail->hasTo($this->assignee); }); Mail::assertSent(CheckoutLicenseMail::class, function (CheckoutLicenseMail $mail) { return $mail->hasSubject(trans('mail.unaccepted_asset_reminder')); }); Mail::assertSent(CheckoutLicenseMail::class, function (CheckoutLicenseMail $mail) { return str_contains($mail->render(), trans('mail.recent_item_checked')); }); } private function createCheckoutAcceptance(Model $item, Model $assignee): CheckoutAcceptance { return CheckoutAcceptance::factory() ->for($item, 'checkoutable') ->for($assignee, 'assignedTo') ->withoutActionLog() ->pending() ->create(); } private function createActionLogEntry(Model $item, Model $admin, Model $assignee, CheckoutAcceptance $acceptance): Actionlog { $itemId = $item->id; $itemType = get_class($item); if (get_class($item) === LicenseSeat::class) { $itemId = $item->license->id; $itemType = License::class; } return Actionlog::factory() ->for($admin, 'adminuser') ->for($assignee, 'target') ->create([ 'action_type' => 'checkout', 'item_id' => $itemId, 'item_type' => $itemType, 'created_at' => $acceptance->created_at, ]); } }