- 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>
118 lines
4.4 KiB
PHP
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();
|
|
}
|
|
}
|