set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32))); }); it('sends from the mailbox its purpose points at', function () { Mailbox::factory()->create([ 'key' => 'no-reply', 'address' => 'no-reply@clupilot.com', 'display_name' => 'CluPilot', 'no_reply' => true, ]); Settings::set(MailPurpose::settingKey(MailPurpose::MAINTENANCE), 'no-reply'); $mail = new MaintenanceAnnouncementMail( MaintenanceWindow::factory()->create(), Customer::factory()->create(), ); $envelope = $mail->envelope(); expect($envelope->from->address)->toBe('no-reply@clupilot.com') ->and($envelope->from->name)->toBe('CluPilot'); }); it('omits Reply-To on a no-reply mailbox, because that is what the name promises', function () { Mailbox::factory()->create(['key' => 'no-reply', 'address' => 'no-reply@clupilot.com', 'no_reply' => true]); Settings::set(MailPurpose::settingKey(MailPurpose::MAINTENANCE), 'no-reply'); $envelope = (new MaintenanceAnnouncementMail( MaintenanceWindow::factory()->create(), Customer::factory()->create(), ))->envelope(); expect($envelope->replyTo)->toBeEmpty(); }); it('sets Reply-To on an answerable mailbox, so a customer reply reaches a human', function () { Mailbox::factory()->create(['key' => 'support', 'address' => 'support@clupilot.com', 'no_reply' => false]); Settings::set(MailPurpose::settingKey(MailPurpose::MAINTENANCE), 'support'); $envelope = (new MaintenanceAnnouncementMail( MaintenanceWindow::factory()->create(), Customer::factory()->create(), ))->envelope(); expect($envelope->replyTo[0]->address)->toBe('support@clupilot.com'); }); it('names the purpose mailer so the queue worker picks the right transport', function () { $mail = new MaintenanceAnnouncementMail( MaintenanceWindow::factory()->create(), Customer::factory()->create(), ); expect($mail->mailer)->toBe('cp_maintenance'); }); it('gives the cancellation mail the same sender as the announcement', function () { Mailbox::factory()->create(['key' => 'no-reply', 'address' => 'no-reply@clupilot.com', 'no_reply' => true]); Settings::set(MailPurpose::settingKey(MailPurpose::MAINTENANCE), 'no-reply'); $mail = new MaintenanceCancelledMail( MaintenanceWindow::factory()->create(), Customer::factory()->create(), ); expect($mail->envelope()->from->address)->toBe('no-reply@clupilot.com') ->and($mail->mailer)->toBe('cp_maintenance'); }); it('falls back to the framework default envelope when no mailbox is configured for the purpose', function () { // Neither a purpose mapping nor a system fallback exists anywhere in // Settings — MailboxResolver::for() returns null. An installation // mid-setup must still hand back a usable Envelope (subject intact) // rather than dereferencing a null mailbox. $window = MaintenanceWindow::factory()->create(); $envelope = (new MaintenanceAnnouncementMail($window, Customer::factory()->create()))->envelope(); expect($envelope->from)->toBeNull() ->and($envelope->replyTo)->toBeEmpty() ->and($envelope->subject)->toBe(__('maintenance.mail_subject', ['title' => $window->title])); }); it('sends the provisioning notification from the provisioning mailbox', function () { Mailbox::factory()->create([ 'key' => 'no-reply', 'address' => 'no-reply@clupilot.com', 'display_name' => 'CluPilot', 'no_reply' => true, ]); Settings::set(MailPurpose::settingKey(MailPurpose::PROVISIONING), 'no-reply'); $notification = new CloudReady( Instance::factory()->create(), 'admin', Crypt::encryptString('geheim'), ); $message = $notification->toMail(new stdClass); expect($message->from[0])->toBe('no-reply@clupilot.com') ->and($message->mailer)->toBe('cp_provisioning') // no_reply is set, so no Reply-To — the same promise as everywhere else. ->and($message->replyTo)->toBeEmpty(); }); it('sets Reply-To on the provisioning notification when its mailbox allows replies', function () { // The mirror image of the no-reply case above: a support-style mailbox // behind PROVISIONING must still get a Reply-To, the same as maintenance // does. Without this, CloudReady's `if (! $box->no_reply)` branch is only // ever exercised by the no_reply=true side and a deleted replyTo() call // would go unnoticed. Mailbox::factory()->create(['key' => 'support', 'address' => 'support@clupilot.com', 'no_reply' => false]); Settings::set(MailPurpose::settingKey(MailPurpose::PROVISIONING), 'support'); $notification = new CloudReady( Instance::factory()->create(), 'admin', Crypt::encryptString('geheim'), ); $message = $notification->toMail(new stdClass); expect($message->replyTo[0][0])->toBe('support@clupilot.com'); }); it("leaves the provisioning notification's sender fields at MailMessage's defaults when no mailbox is configured", function () { // The mirror of the trait's own null-mailbox fallback test above, but for // CloudReady's own guard: neither a mailbox row nor a provisioning/system // Settings mapping exists, so MailboxResolver::for() returns null. Must // not throw dereferencing a null mailbox, and must leave from/replyTo // exactly as `new MailMessage` starts them — [] and []. $notification = new CloudReady( Instance::factory()->create(), 'admin', Crypt::encryptString('geheim'), ); $message = $notification->toMail(new stdClass); expect($message->from)->toBeEmpty() ->and($message->replyTo)->toBeEmpty() ->and($message->mailer)->toBe('cp_provisioning'); });