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>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
a6b641015e
commit
b44a88bb52
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Api\Client;
|
||||
|
||||
use App\Mail\Admin\ServiceCancellationRequestMail;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Notification;
|
||||
use App\Models\Service;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ServiceCancellationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private Customer $owner;
|
||||
private Service $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Mail::fake();
|
||||
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
|
||||
User::factory()->create()->assignRole('Full Administrator');
|
||||
Setting::updateOrCreate(['key' => 'setting'], ['value' => json_encode(['admin_email' => 'admin@example.com'])]);
|
||||
|
||||
$this->owner = Customer::factory()->create();
|
||||
$this->service = Service::create(['customer_id' => $this->owner->id, 'type' => 'ssl', 'status' => 'active', 'server_name' => 'example.com']);
|
||||
}
|
||||
|
||||
private function as(Customer $customer): static
|
||||
{
|
||||
return $this->actingAs($customer, 'client')->withHeaders(['x-access-token' => $this->buildClientAccessToken()]);
|
||||
}
|
||||
|
||||
public function test_request_notifies_admins_and_blocks_duplicates_until_withdrawn(): void
|
||||
{
|
||||
$url = "/v1/client/client-service/{$this->service->uuid}/cancel-request";
|
||||
|
||||
$this->as($this->owner)->postJson($url, ['reason_code' => 'other', 'reason' => 'Chuyển sang nhà cung cấp khác'])
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.reason', 'Chuyển sang nhà cung cấp khác');
|
||||
|
||||
$this->assertSame(1, Notification::where('type', 'service_cancellation_request')->where('recipient_type', 'admin')->count());
|
||||
Mail::assertQueued(ServiceCancellationRequestMail::class, fn ($mail) => $mail->hasTo('admin@example.com'));
|
||||
|
||||
$this->as($this->owner)->postJson($url, ['reason' => 'Lặp lại'])->assertStatus(422);
|
||||
|
||||
$this->as($this->owner)->deleteJson($url)->assertOk();
|
||||
$this->as($this->owner)->postJson($url, ['reason' => 'Gửi lại sau khi rút'])->assertCreated();
|
||||
}
|
||||
|
||||
public function test_other_customer_cannot_request_and_reason_is_required(): void
|
||||
{
|
||||
$url = "/v1/client/client-service/{$this->service->uuid}/cancel-request";
|
||||
|
||||
$this->as(Customer::factory()->create())->postJson($url, ['reason' => 'Hủy giúp'])->assertNotFound();
|
||||
$this->as($this->owner)->postJson($url, [])->assertStatus(422);
|
||||
$this->assertSame(0, Notification::where('type', 'service_cancellation_request')->count());
|
||||
}
|
||||
|
||||
public function test_admin_approves_or_rejects_once_and_customer_is_notified(): void
|
||||
{
|
||||
$admin = User::first();
|
||||
$pending = $this->service->cancellationRequests()->create(['customer_id' => $this->owner->id, 'reason' => 'Không dùng nữa']);
|
||||
$base = "/api/v1/administrator/services/{$this->service->id}/cancellation-requests";
|
||||
|
||||
$this->actingAs($admin, 'admin')->postJson("{$base}/{$pending->id}/resolve", ['action' => 'reject'])->assertStatus(422);
|
||||
$this->actingAs($admin, 'admin')->postJson("{$base}/{$pending->id}/resolve", ['action' => 'reject', 'note' => 'Còn hợp đồng'])->assertOk();
|
||||
$this->assertSame('active', $this->service->fresh()->status);
|
||||
$this->actingAs($admin, 'admin')->postJson("{$base}/{$pending->id}/resolve", ['action' => 'approve'])->assertStatus(422);
|
||||
|
||||
$second = $this->service->cancellationRequests()->create(['customer_id' => $this->owner->id, 'reason' => 'Lần hai']);
|
||||
$this->actingAs($admin, 'admin')->postJson("{$base}/{$second->id}/resolve", ['action' => 'approve'])->assertOk();
|
||||
|
||||
$this->assertSame('cancelled', $this->service->fresh()->status);
|
||||
$this->assertSame(2, Notification::where('recipient_type', 'client')->where('type', 'service_cancellation_resolved')->count());
|
||||
$this->actingAs($admin, 'admin')->getJson($base)->assertJsonCount(2, 'data');
|
||||
}
|
||||
|
||||
public function test_approved_cancellation_keeps_service_until_expiry_then_cancels(): void
|
||||
{
|
||||
$this->service->update(['expires_at' => now()->addDays(5), 'auto_renew' => true]);
|
||||
$pending = $this->service->cancellationRequests()->create(['customer_id' => $this->owner->id, 'reason' => 'Không dùng nữa']);
|
||||
|
||||
$this->actingAs(User::first(), 'admin')
|
||||
->postJson("/api/v1/administrator/services/{$this->service->id}/cancellation-requests/{$pending->id}/resolve", ['action' => 'approve'])
|
||||
->assertOk();
|
||||
|
||||
$service = $this->service->fresh();
|
||||
$this->assertSame('active', $service->status);
|
||||
$this->assertTrue($service->cancel_at_expiry);
|
||||
$this->assertFalse($service->auto_renew);
|
||||
$this->assertNull($service->cancelled_at);
|
||||
|
||||
$this->as($this->owner)->postJson("/v1/client/client-service/{$service->uuid}/cancel-request", ['reason' => 'Gửi lại'])->assertStatus(422);
|
||||
|
||||
$this->artisan('service:expiry')->assertSuccessful();
|
||||
$this->assertSame('active', $service->fresh()->status);
|
||||
|
||||
$service->update(['expires_at' => now()->subHour()]);
|
||||
$this->artisan('service:expiry')->assertSuccessful();
|
||||
$this->assertSame('cancelled', $service->fresh()->status);
|
||||
$this->assertNotNull($service->fresh()->cancelled_at);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user