Files
baodev0andClaude Sonnet 5.5 6315a392c0 feat(service): custom product, lịch sử giám sát VM và mã hoá response theo phiên
- Dịch vụ custom: bảng custom_products + services.custom_product_id; createService nhận
  custom{} thay product_id, giá = đơn giá tháng x số tháng chu kỳ (1/3/6/12/24/36),
  gia hạn tính theo cùng đơn giá, ServiceClientDetailResource trả custom_product và specs.
- Giám sát VM: GET v1/system/vm-metrics/history gom lịch sử về tối đa 240 điểm;
  lỗi lấy dung lượng đĩa không còn làm hỏng cả thẻ metrics.
- SealResponse: AES-256-GCM theo khoá phiên do proxy Next cấp qua header x-seal-key
  (HMAC của session id). Client opt-in bằng x-seal; chi tiết dịch vụ, mật khẩu VM và
  console ticket luôn được mã hoá. Body gửi lên dạng {enc} được giải mã trước validate;
  khoá lệch trả 428 để client lấy khoá mới. Đây là che mắt DevTools, không phải bảo mật
  chống chính chủ tài khoản.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 13:58:50 +07:00

70 lines
2.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Api\Admin\Customer;
use App\Models\Customer;
use App\Models\CustomProduct;
use App\Models\Invoice;
use App\Models\Service;
use App\Models\User;
use App\Support\RenewalPricing;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
class CreateCustomServiceTest extends TestCase
{
use RefreshDatabase;
public function test_custom_service_stores_own_product_and_prices_by_cycle(): void
{
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
$admin = User::factory()->create();
$admin->assignRole('Full Administrator');
$customer = Customer::factory()->create();
$response = $this->actingAs($admin, 'admin')->postJson(
"/api/v1/administrator/customers/{$customer->uuid}/services",
[
'custom' => [
'name' => 'VPS riêng',
'category' => 'vps',
'unit_price' => 200000,
'cpu' => 8,
'ram_mb' => 16384,
'disk_gb' => 200,
'specs' => [['label' => 'Vị trí', 'value' => 'HCM']],
],
'billing_cycle' => 'quarterly',
'payment_status' => 'unpaid',
],
)->assertCreated();
$service = Service::findOrFail($response->json('data.id'));
$custom = CustomProduct::findOrFail($service->custom_product_id);
$this->assertNull($service->product_id);
$this->assertSame('vps', $service->type);
$this->assertSame(16384, $custom->ram_mb);
$this->assertSame(600000, (int) Invoice::where('customer_id', $customer->id)->value('total'));
$this->assertSame(
1200000,
collect(RenewalPricing::options($custom))->firstWhere('months', 6)['price'],
);
}
public function test_requires_product_or_custom(): void
{
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
$admin = User::factory()->create();
$admin->assignRole('Full Administrator');
$customer = Customer::factory()->create();
$this->actingAs($admin, 'admin')
->postJson("/api/v1/administrator/customers/{$customer->uuid}/services", ['payment_status' => 'unpaid'])
->assertStatus(422);
}
}