set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32))); clearMailboxSeed(); }); it('stores the password encrypted and never in the clear', function () { $box = Mailbox::factory()->create(['key' => 'support', 'password' => 'sehr-geheim']); $stored = DB::table('mailboxes')->where('id', $box->id)->value('password'); expect($stored)->not->toContain('sehr-geheim') ->and($box->fresh()->password)->toBe('sehr-geheim'); }); it('is keyed to SECRETS_KEY — rotating it makes the password unreadable', function () { // The POSITIVE proof, and the one that discriminates. $box = Mailbox::factory()->create(['password' => 'geheim']); config()->set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32))); expect(fn () => $box->fresh()->password) ->toThrow(DecryptException::class); }); it('is NOT keyed to APP_KEY — rotating that leaves mail working', function () { $box = Mailbox::factory()->create(['password' => 'bleibt-lesbar']); config()->set('app.key', 'base64:'.base64_encode(random_bytes(32))); // forgetInstance is what makes this test mean anything — see above. app()->forgetInstance('encrypter'); expect($box->fresh()->password)->toBe('bleibt-lesbar'); }); it('falls back to the address when no separate username is given', function () { $box = Mailbox::factory()->create([ 'address' => 'support@clupilot.com', 'username' => null, ]); expect($box->smtpUsername())->toBe('support@clupilot.com'); }); // --- Codex R15#4, P1b: isConfigured() must require a password only when the // mailbox actually authenticates — a trusted relay that needs no login is // not the same state as one nobody has finished setting up yet. it('is configured without a stored password when it does not authenticate', function () { // ->fresh(), not the in-memory instance create() returns: authenticates // needs its own boolean cast for this to mean anything (a raw DB read // comes back "0"/"1", not a real PHP bool), the same gap // "stores the password encrypted and never in the clear" above guards // against for the password column. $box = Mailbox::factory()->create(['authenticates' => false, 'password' => null]); expect($box->fresh()->isConfigured())->toBeTrue(); }); it('is NOT configured without a stored password when it does authenticate', function () { // The control case: the fix must not make isConfigured() stop requiring // a password altogether — only when authenticates is explicitly false. $box = Mailbox::factory()->create(['authenticates' => true, 'password' => null]); expect($box->fresh()->isConfigured())->toBeFalse(); }); it('still requires active and a non-blank address regardless of authenticates', function () { // authenticates=false must widen nothing except the password // requirement — an inactive or addressless row stays unconfigured. $inactive = Mailbox::factory()->create(['authenticates' => false, 'password' => null, 'active' => false]); $blank = Mailbox::factory()->make(['authenticates' => false, 'password' => null, 'address' => '']); expect($inactive->fresh()->isConfigured())->toBeFalse() ->and($blank->isConfigured())->toBeFalse(); }); it('finds a mailbox by its key', function () { Mailbox::factory()->create(['key' => 'billing']); expect(Mailbox::findByKey('billing'))->not->toBeNull() ->and(Mailbox::findByKey('gibtsnicht'))->toBeNull(); }); it('auto-assigns a uuid and routes by it', function () { $box = Mailbox::factory()->create(); expect($box->uuid)->not->toBeNull() ->and($box->getRouteKeyName())->toBe('uuid'); });