VPS and dedicated server ordering, managed panels, SSO, billing, and launcher integration — forked from hosting infrastructure with server-focused routes and dashboard. Co-authored-by: Cursor <cursoragent@cursor.com>
173 lines
7.1 KiB
PHP
173 lines
7.1 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\User;
|
|
use App\Services\EmailDomain\EmailDomainClient;
|
|
use App\Services\Mailbox\MailboxClient;
|
|
use App\Services\Payment\WalletPaymentService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Str;
|
|
use Mockery;
|
|
use Tests\TestCase;
|
|
|
|
class MailboxesTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
Mockery::close();
|
|
parent::tearDown();
|
|
}
|
|
|
|
private function user(): User
|
|
{
|
|
return User::create(['public_id' => (string) Str::uuid(), 'name' => 'A', 'email' => 'a+'.uniqid().'@x.com']);
|
|
}
|
|
|
|
private function mailboxClient(): \Mockery\MockInterface
|
|
{
|
|
$m = Mockery::mock(MailboxClient::class);
|
|
$this->app->instance(MailboxClient::class, $m);
|
|
|
|
return $m;
|
|
}
|
|
|
|
private function wallet(): \Mockery\MockInterface
|
|
{
|
|
$w = Mockery::mock(WalletPaymentService::class);
|
|
$w->shouldReceive('balanceMinor')->andReturn(0)->byDefault();
|
|
$this->app->instance(WalletPaymentService::class, $w);
|
|
|
|
return $w;
|
|
}
|
|
|
|
public function test_root_redirects_guest_to_sso(): void
|
|
{
|
|
$this->get('/')->assertRedirect(route('sso.connect'));
|
|
}
|
|
|
|
public function test_mailboxes_require_login(): void
|
|
{
|
|
$this->get(route('email.mailboxes.index'))->assertRedirect(route('login'));
|
|
}
|
|
|
|
public function test_index_lists_from_api(): void
|
|
{
|
|
$this->mailboxClient()->shouldReceive('forUser')->andReturn([
|
|
['id' => 1, 'address' => 'me@acme.com', 'display_name' => 'Me', 'quota_mb' => 10240, 'status' => 'active'],
|
|
]);
|
|
|
|
$this->actingAs($this->user())->get(route('email.mailboxes.index'))->assertOk()->assertSee('me@acme.com');
|
|
}
|
|
|
|
public function test_create_form_shows_free_banner_within_allowance(): void
|
|
{
|
|
config(['email.free_mailboxes' => 1]);
|
|
$this->mailboxClient()->shouldReceive('forUser')->andReturn([]);
|
|
$this->wallet();
|
|
$dc = Mockery::mock(EmailDomainClient::class);
|
|
$dc->shouldReceive('verified')->andReturn([['id' => 7, 'domain' => 'acme.com']]);
|
|
$this->app->instance(EmailDomainClient::class, $dc);
|
|
|
|
$this->actingAs($this->user())->get(route('email.mailboxes.create'))
|
|
->assertOk()->assertSee('free, forever', false);
|
|
}
|
|
|
|
public function test_store_free_1gb_mailbox_creates_without_charge(): void
|
|
{
|
|
$mc = $this->mailboxClient();
|
|
$mc->shouldReceive('create')->once()
|
|
->with(Mockery::any(), 7, 'sales', 'Sales', 'secret123', 1024, false, null) // 1 GB = free
|
|
->andReturn(['id' => 9, 'address' => 'sales@acme.com']);
|
|
$w = $this->wallet();
|
|
$w->shouldNotReceive('charge');
|
|
|
|
$this->actingAs($this->user())->post(route('email.mailboxes.store'), [
|
|
'email_domain_id' => 7, 'local_part' => 'Sales', 'display_name' => 'Sales',
|
|
'password' => 'secret123', 'password_confirmation' => 'secret123', 'quota_mb' => 1024,
|
|
])->assertRedirect(route('email.mailboxes.show', 9));
|
|
}
|
|
|
|
public function test_store_paid_mailbox_charges_tier_price(): void
|
|
{
|
|
// 25 GB tier = GHS 30 = 3000 (doubled)
|
|
$mc = $this->mailboxClient();
|
|
$mc->shouldReceive('create')->once()
|
|
->withArgs(fn ($pid, $did, $lp, $dn, $pw, $q, $paid, $ref) => $did === 7 && $q === 25600 && $paid === true && is_string($ref))
|
|
->andReturn(['id' => 5, 'address' => 'sales@acme.com']);
|
|
$w = $this->wallet();
|
|
$w->shouldReceive('canAfford')->once()->with(Mockery::any(), 3000)->andReturn(true);
|
|
$w->shouldReceive('charge')->once()->withArgs(fn ($u, $amt, ...$r) => $amt === 3000)->andReturn(true);
|
|
|
|
$this->actingAs($this->user())->post(route('email.mailboxes.store'), [
|
|
'email_domain_id' => 7, 'local_part' => 'sales', 'display_name' => 'Sales',
|
|
'password' => 'secret123', 'password_confirmation' => 'secret123', 'quota_mb' => 25600,
|
|
])->assertRedirect(route('email.mailboxes.show', 5));
|
|
}
|
|
|
|
public function test_store_rejects_invalid_quota(): void
|
|
{
|
|
$mc = $this->mailboxClient();
|
|
$mc->shouldNotReceive('create');
|
|
$this->wallet();
|
|
|
|
$this->actingAs($this->user())->from(route('email.mailboxes.create'))->post(route('email.mailboxes.store'), [
|
|
'email_domain_id' => 7, 'local_part' => 'sales', 'display_name' => 'Sales',
|
|
'password' => 'secret123', 'password_confirmation' => 'secret123', 'quota_mb' => 999,
|
|
])->assertSessionHasErrors('quota_mb');
|
|
}
|
|
|
|
public function test_store_paid_blocks_when_wallet_insufficient(): void
|
|
{
|
|
$mc = $this->mailboxClient();
|
|
$mc->shouldNotReceive('create');
|
|
$w = $this->wallet();
|
|
$w->shouldReceive('canAfford')->once()->andReturn(false);
|
|
|
|
$this->actingAs($this->user())->from(route('email.mailboxes.create'))->post(route('email.mailboxes.store'), [
|
|
'email_domain_id' => 7, 'local_part' => 'sales', 'display_name' => 'Sales',
|
|
'password' => 'secret123', 'password_confirmation' => 'secret123', 'quota_mb' => 10240,
|
|
])->assertRedirect(route('email.mailboxes.create'))->assertSessionHas('error');
|
|
}
|
|
|
|
public function test_upgrade_charges_new_tier_and_updates_quota(): void
|
|
{
|
|
$mc = $this->mailboxClient();
|
|
$mc->shouldReceive('show')->andReturn(['id' => 3, 'address' => 'me@acme.com', 'quota_mb' => 1024]); // currently free 1 GB
|
|
$mc->shouldReceive('updateQuota')->once()
|
|
->withArgs(fn ($pid, $id, $mb, $paid, $ref) => $id === 3 && $mb === 10240 && $paid === true && is_string($ref))
|
|
->andReturn(['id' => 3, 'quota_mb' => 10240]);
|
|
$w = $this->wallet();
|
|
$w->shouldReceive('canAfford')->once()->with(Mockery::any(), 2000)->andReturn(true); // 10 GB = GHS 20
|
|
$w->shouldReceive('charge')->once()->withArgs(fn ($u, $amt, ...$r) => $amt === 2000)->andReturn(true);
|
|
|
|
$this->actingAs($this->user())->patch(route('email.mailboxes.upgrade.store', 3), ['quota_mb' => 10240])
|
|
->assertRedirect(route('email.mailboxes.show', 3));
|
|
}
|
|
|
|
public function test_upgrade_rejects_smaller_or_equal_plan(): void
|
|
{
|
|
$mc = $this->mailboxClient();
|
|
$mc->shouldReceive('show')->andReturn(['id' => 3, 'address' => 'me@acme.com', 'quota_mb' => 10240]);
|
|
$mc->shouldNotReceive('updateQuota');
|
|
$w = $this->wallet();
|
|
$w->shouldNotReceive('charge');
|
|
|
|
$this->actingAs($this->user())->from(route('email.mailboxes.show', 3))
|
|
->patch(route('email.mailboxes.upgrade.store', 3), ['quota_mb' => 5120])
|
|
->assertRedirect(route('email.mailboxes.show', 3))->assertSessionHas('error');
|
|
}
|
|
|
|
public function test_domains_index_lists_from_api(): void
|
|
{
|
|
$dc = Mockery::mock(EmailDomainClient::class);
|
|
$dc->shouldReceive('forUser')->andReturn([['id' => 1, 'domain' => 'acme.com', 'active' => false]]);
|
|
$this->app->instance(EmailDomainClient::class, $dc);
|
|
|
|
$this->actingAs($this->user())->get(route('email.domains.index'))
|
|
->assertOk()->assertSee('acme.com')->assertSee('Pending verification');
|
|
}
|
|
}
|