Initial Ladill Hosting app with Gitea deploy pipeline.
Deploy Ladill Hosting / deploy (push) Failing after 17s

Shared web hosting extracted from the platform monolith, with CI deploy
to /var/www/ladill-hosting matching Bird/Domains/Email.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-06 16:24:20 +00:00
co-authored by Cursor
commit e251a4cf60
367 changed files with 66268 additions and 0 deletions
+172
View File
@@ -0,0 +1,172 @@
<?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');
}
}