Files
portal-api/tests/Feature/Api/Admin/Customer/AdminRenewServiceTest.php

158 lines
5.7 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Api\Admin\Customer;
use App\Enums\Invoice\InvoiceStatus;
use App\Mail\ServiceRenewedMail;
use App\Enums\Order\OrderStatus;
use App\Models\Customer;
use App\Models\Invoice;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Product;
use App\Models\Service;
use App\Models\Setting;
use App\Models\User;
use Database\Seeders\TemplateSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
class AdminRenewServiceTest extends TestCase
{
use RefreshDatabase;
private function admin(): User
{
$role = Role::create(['name' => 'role-' . uniqid(), 'guard_name' => 'admin']);
$role->givePermissionTo(Permission::firstOrCreate(['name' => 'customer.edit', 'guard_name' => 'admin']));
$admin = User::factory()->create();
$admin->assignRole($role);
return $admin;
}
private function service(int $balance): Service
{
$customer = Customer::factory()->create(['total' => $balance]);
$product = Product::factory()->create(['product_type' => 'hosting', 'is_active' => true]);
$order = Order::create([
'order_num' => 'ORD-' . uniqid(),
'customer_id' => $customer->id,
'sub_total' => 90_000,
'total' => 90_000,
'status' => OrderStatus::ACTIVE->value,
]);
$item = OrderItem::create([
'order_id' => $order->id,
'product_id' => $product->id,
'billing_cycle' => 'monthly',
'months' => 1,
'quantity' => 1,
'price' => 90_000,
]);
$product->pricings()->create(['billing_cycle' => 'monthly', 'price' => 90_000, 'is_active' => 1]);
return Service::create([
'customer_id' => $customer->id,
'product_id' => $product->id,
'order_item_id' => $item->id,
'status' => 'suspended',
'server_name' => 'test-server',
'expires_at' => now()->subDay(),
]);
}
private function renew(Service $service, array $payload)
{
return $this->actingAs($this->admin(), 'admin')
->postJson("/api/v1/administrator/customers/{$service->customer->uuid}/services/{$service->id}/renew", $payload);
}
public function test_manual_payment_renews_without_touching_balance(): void
{
$service = $this->service(0);
$this->renew($service, ['months' => 1, 'payment' => 'manual', 'note' => 'CK VCB'])->assertOk();
$service->refresh();
$this->assertSame('active', $service->status);
$this->assertTrue($service->expires_at->isFuture());
$this->assertSame(InvoiceStatus::PAID->value, Invoice::find($service->invoice_id)->status);
$this->assertSame(0, (int) $service->customer->fresh()->total);
}
public function test_renewal_is_logged_with_actor_and_payment_method_and_listed(): void
{
$service = $this->service(0);
$admin = $this->admin();
$admin->givePermissionTo(\Spatie\Permission\Models\Permission::firstOrCreate(['name' => 'customer.view', 'guard_name' => 'admin']));
$base = "/api/v1/administrator/customers/{$service->customer->uuid}/services/{$service->id}";
$this->actingAs($admin, 'admin')->postJson("{$base}/renew", ['months' => 1, 'payment' => 'manual'])->assertOk();
$this->actingAs($admin, 'admin')->getJson("{$base}/renewals")
->assertOk()
->assertJsonCount(1, 'data')
->assertJsonPath('data.0.causer_type', 'admin')
->assertJsonPath('data.0.new_values.payment_method', 'admin')
->assertJsonPath('data.0.new_values.months', 1);
}
public function test_balance_payment_deducts_customer_balance(): void
{
$service = $this->service(200_000);
$this->renew($service, ['months' => 1, 'payment' => 'balance'])->assertOk();
$this->assertSame(110_000, (int) $service->customer->fresh()->total);
$this->assertSame('active', $service->fresh()->status);
}
public function test_balance_payment_with_insufficient_funds_leaves_invoice_unpaid(): void
{
$service = $this->service(1_000);
$this->renew($service, ['months' => 1, 'payment' => 'balance'])->assertStatus(422);
$this->assertSame(InvoiceStatus::UNPAID->value, Invoice::find($service->fresh()->invoice_id)->status);
}
public function test_rejects_months_outside_the_billing_cycle(): void
{
$service = $this->service(200_000);
$this->renew($service, ['months' => 25, 'payment' => 'manual'])->assertStatus(422);
}
public function test_renewal_queues_email_to_customer_and_admin(): void
{
Mail::fake();
(new TemplateSeeder())->run();
Setting::create(['key' => 'setting', 'value' => json_encode(['admin_email' => 'ops@example.com'])]);
$service = $this->service(0);
$this->renew($service, ['months' => 1, 'payment' => 'manual'])->assertOk();
Mail::assertQueued(ServiceRenewedMail::class, fn ($mail) => $mail->hasTo($service->customer->email));
Mail::assertQueued(ServiceRenewedMail::class, fn ($mail) => $mail->hasTo('ops@example.com'));
}
public function test_rejects_period_already_renewed_before(): void
{
$service = $this->service(0);
$this->renew($service, ['months' => 1, 'payment' => 'manual'])->assertOk();
$service->refresh()->update(['expires_at' => now()->addDays(5)]);
$this->renew($service, ['months' => 1, 'payment' => 'manual'])
->assertStatus(422)
->assertJsonPath('error', true);
}
}