Files
portal-api/tests/Feature/Api/Admin/Service/SslCertificateTest.php
baodev0andClaude Sonnet 5.5 b44a88bb52 feat(service): huỷ dịch vụ, SSL, đổi mật khẩu hosting, lịch sử thanh toán theo hình thức
- Lịch sử thanh toán hoá đơn trả thêm `method` (tự động gia hạn / số dư / admin / chuyển khoản / hoàn tiền), bỏ đuôi `(method)` khỏi ghi chú
- `DnsServer::panelUrl()` = https://<domain>/ dùng cho email cấp hosting và nút Plesk của khách
- Order item trả thêm `uuid`/`type` của dịch vụ để FE dẫn tới trang quản lý
- Yêu cầu huỷ dịch vụ, chứng chỉ SSL, lịch sử gia hạn, đổi mật khẩu hosting cho khách

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 16:40:24 +07:00

118 lines
4.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Api\Admin\Service;
use App\Http\Resources\Api\Client\Service\ServiceClientDetailResource;
use App\Models\Customer;
use App\Models\Product;
use App\Models\Service;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Http\Request;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
use ZipArchive;
class SslCertificateTest extends TestCase
{
use RefreshDatabase;
private User $admin;
protected function setUp(): void
{
parent::setUp();
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
$this->admin = User::factory()->create();
$this->admin->assignRole('Full Administrator');
}
private function service(string $productType = 'ssl'): Service
{
$product = Product::factory()->create(['product_type' => $productType, 'name' => 'SSL - Positive']);
return Service::create(['customer_id' => Customer::factory()->create()->id, 'product_id' => $product->id, 'status' => 'active']);
}
private function zip(array $entries): UploadedFile
{
$path = tempnam(sys_get_temp_dir(), 'ssl') . '.zip';
$zip = new ZipArchive();
$zip->open($path, ZipArchive::CREATE);
foreach ($entries as $name => $content) {
$zip->addFromString($name, $content);
}
$zip->close();
return new UploadedFile($path, 'cert.zip', 'application/zip', null, true);
}
public function test_archive_is_extracted_to_text_files_and_visible_to_client(): void
{
$service = $this->service();
$base = "/api/v1/administrator/services/{$service->id}/ssl-info";
$this->actingAs($this->admin, 'admin')
->post("{$base}/archive", ['archive' => $this->zip([
'bundle/example.com.crt' => "-----BEGIN CERTIFICATE-----\nabc\n-----END CERTIFICATE-----",
'../../evil.key' => 'key-data',
'__MACOSX/._example.com.crt' => 'junk',
'logo.png' => "\0\1binary",
'dir/' => '',
])], ['Accept' => 'application/json'])
->assertOk()
->assertJsonCount(2, 'data.files')
->assertJsonPath('data.files.0.name', 'example.com.crt')
->assertJsonPath('data.files.1.name', 'evil.key');
$this->actingAs($this->admin, 'admin')
->putJson($base, [
'fields' => [['label' => 'CNAME host', 'value' => '_abc.example.com']],
'files' => [['name' => 'a.pem', 'content' => 'pem']],
])
->assertOk();
$this->actingAs($this->admin, 'admin')->getJson($base)
->assertJsonPath('data.fields.0.value', '_abc.example.com')
->assertJsonCount(1, 'data.files');
$this->assertStringNotContainsString('pem', (string) \DB::table('service_ssl_certificates')->value('data'));
$payload = (new ServiceClientDetailResource($service->fresh()))->toArray(new Request());
$this->assertSame('a.pem', $payload['ssl_certificate']['files'][0]['name']);
}
public function test_non_ssl_service_is_rejected(): void
{
$service = $this->service('domain');
$this->actingAs($this->admin, 'admin')
->getJson("/api/v1/administrator/services/{$service->id}/ssl-info")
->assertStatus(422);
}
public function test_customer_downloads_own_certificate_zip_only(): void
{
$service = $this->service();
$service->sslCertificate()->create(['data' => [
'fields' => [['label' => 'CNAME', 'value' => 'abc']],
'files' => [['name' => 'a.pem', 'content' => 'pem-data']],
]]);
$owner = Customer::find($service->customer_id);
$path = "/v1/client/client-service/{$service->uuid}/ssl-archive";
$response = $this->actingAs($owner, 'client')->withHeaders(['x-access-token' => $this->buildClientAccessToken()])->get($path);
$response->assertOk();
$zip = new ZipArchive();
$zip->open($response->baseResponse->getFile()->getPathname());
$this->assertSame('pem-data', $zip->getFromName('a.pem'));
$this->assertStringContainsString('CNAME: abc', $zip->getFromName('thong-tin.txt'));
$this->actingAs(Customer::factory()->create(), 'client')->withHeaders(['x-access-token' => $this->buildClientAccessToken()])->get($path)->assertNotFound();
}
}