clusev/tests/Feature/TlsCertificateRequestTest.php

95 lines
3.7 KiB
PHP

<?php
namespace Tests\Feature;
use App\Livewire\System\Index as SystemIndex;
use App\Models\AuditEvent;
use App\Models\Setting;
use App\Models\User;
use App\Services\DeploymentService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Livewire\Livewire;
use Mockery;
use Tests\TestCase;
/**
* The dashboard "request certificate" flow: a DNS pre-check that must NOT trigger ACME on a
* mismatch (Let's-Encrypt rate-limit protection), and a per-user throttle on the action.
*/
class TlsCertificateRequestTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
Cache::flush();
}
public function test_dns_mismatch_does_not_trigger_a_handshake(): void
{
$svc = Mockery::mock(DeploymentService::class)->makePartial();
$svc->shouldReceive('domainResolvesHere')->once()->with('example.com')
->andReturn(['ok' => false, 'resolved' => ['203.0.113.9'], 'serverIp' => '198.51.100.1']);
$svc->shouldReceive('probeCertificate')->never(); // never trigger ACME against a wrong host
$result = $svc->requestCertificate('example.com');
$this->assertSame('dns_mismatch', $result['status']);
$this->assertSame('dns_mismatch', $svc->certStatus()['status']);
}
public function test_dns_ok_and_handshake_success_is_issued(): void
{
$svc = Mockery::mock(DeploymentService::class)->makePartial();
$svc->shouldReceive('domainResolvesHere')->once()
->andReturn(['ok' => true, 'resolved' => ['198.51.100.1'], 'serverIp' => '198.51.100.1']);
$svc->shouldReceive('probeCertificate')->once()->with('example.com')->andReturnTrue();
$this->assertSame('issued', $svc->requestCertificate('example.com')['status']);
}
public function test_dns_ok_but_handshake_failure_is_failed(): void
{
$svc = Mockery::mock(DeploymentService::class)->makePartial();
$svc->shouldReceive('domainResolvesHere')->once()->andReturn(['ok' => true, 'resolved' => [], 'serverIp' => null]);
$svc->shouldReceive('probeCertificate')->once()->andReturnFalse();
$this->assertSame('failed', $svc->requestCertificate('example.com')['status']);
}
public function test_external_tls_mode_is_not_applicable_and_never_probes(): void
{
Setting::put('tls_mode', 'external');
$svc = Mockery::mock(DeploymentService::class)->makePartial();
$svc->shouldReceive('domainResolvesHere')->never();
$svc->shouldReceive('probeCertificate')->never();
$this->assertSame('not_applicable', $svc->requestCertificate('example.com')['status']);
}
public function test_request_action_is_throttled_per_user(): void
{
$this->mock(DeploymentService::class, function ($m) {
$m->makePartial();
// Stub the serving state directly (domain() otherwise reads a shared snapshot file).
$m->shouldReceive('domain')->andReturn('example.com');
$m->shouldReceive('externalTls')->andReturn(false);
// The network-touching request is stubbed; the throttle must cap it at 5 calls.
$m->shouldReceive('requestCertificate')->times(5)
->andReturn(['status' => 'issued', 'checkedAt' => now()->toIso8601String()]);
});
$this->actingAs(User::factory()->create(['must_change_password' => false]));
for ($i = 0; $i < 6; $i++) {
Livewire::test(SystemIndex::class)->call('requestCertificate');
}
// 6th call was throttled before reaching the service; 5 audited requests.
$this->assertSame(5, AuditEvent::where('action', 'tls.cert_request')->count());
}
}