Files
portal-api/tests/Feature/Console/ServiceExpiryUnificationTest.php
T

134 lines
4.7 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Console;
use App\Mail\AutoRenewFailedMail;
use App\Models\Customer;
use App\Models\ModuleVmware;
use App\Models\Notification;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Product;
use App\Models\Service;
use App\Models\Setting;
use Database\Seeders\TemplateSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
class ServiceExpiryUnificationTest extends TestCase
{
use RefreshDatabase;
private function service(array $attributes = [], int $balance = 0): 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' => 1,
]);
$item = OrderItem::create([
'order_id' => $order->id,
'product_id' => $product->id,
'billing_cycle' => 'monthly',
'months' => 1,
'quantity' => 1,
'price' => 90_000,
]);
return Service::create($attributes + [
'customer_id' => $customer->id,
'product_id' => $product->id,
'order_item_id' => $item->id,
'status' => 'active',
'server_name' => 'srv',
'expires_at' => now()->addDays(3),
]);
}
private function vmFor(Service $service, ?int $dueDate): ModuleVmware
{
$vm = ModuleVmware::create([
'name' => 'vm-' . uniqid(),
'customer_id' => $service->customer_id,
'next_due_date' => $dueDate,
]);
$service->update(['vm_id' => $vm->id]);
return $vm;
}
public function test_changing_service_expiry_moves_vmware_due_date(): void
{
$service = $this->service();
$vm = $this->vmFor($service, 1);
$newExpiry = now()->addMonths(2)->startOfSecond();
$service->update(['expires_at' => $newExpiry]);
$this->assertSame($newExpiry->timestamp, (int) $vm->fresh()->next_due_date);
}
public function test_reconcile_is_idempotent_and_dry_run_writes_nothing(): void
{
$service = $this->service();
$vm = $this->vmFor($service, null);
ModuleVmware::whereKey($vm->id)->update(['next_due_date' => 1]);
$this->artisan('service:reconcile-expiry', ['--dry-run' => true])->assertExitCode(0);
$this->assertSame(1, (int) $vm->fresh()->next_due_date);
$this->artisan('service:reconcile-expiry')->assertExitCode(0);
$this->assertSame($service->fresh()->expires_at->timestamp, (int) $vm->fresh()->next_due_date);
$before = $vm->fresh()->updated_at;
$this->artisan('service:reconcile-expiry')->expectsOutputToContain('lệch module_vmware: 0')->assertExitCode(0);
$this->assertEquals($before, $vm->fresh()->updated_at);
}
public function test_reconcile_backfills_service_expiry_from_legacy_vm(): void
{
$service = $this->service(['expires_at' => null]);
$due = now()->addDays(20)->startOfSecond();
$this->vmFor($service, $due->timestamp);
$this->artisan('service:reconcile-expiry')->assertExitCode(0);
$this->assertSame($due->timestamp, $service->fresh()->expires_at->timestamp);
}
public function test_auto_renew_without_balance_notifies_once_per_day(): void
{
Mail::fake();
$this->seed(TemplateSeeder::class);
$service = $this->service(['auto_renew' => true], balance: 1_000);
$this->artisan('service:expiry')->assertExitCode(0);
$this->artisan('service:expiry')->assertExitCode(0);
Mail::assertQueued(AutoRenewFailedMail::class, 1);
$this->assertSame(1, Notification::where('type', 'auto_renew_failed')->where('recipient_id', $service->customer_id)->count());
$this->assertNotNull($service->fresh()->invoice_id);
$this->assertSame(1_000, (int) $service->customer->fresh()->total);
}
public function test_auto_renew_failure_mail_respects_auto_email_gate(): void
{
Mail::fake();
$this->seed(TemplateSeeder::class);
Setting::updateOrCreate(['key' => 'email_settings'], ['value' => json_encode(['auto_disabled' => ['auto_renew_failed']])]);
$service = $this->service(['auto_renew' => true], balance: 0);
$this->artisan('service:expiry')->assertExitCode(0);
Mail::assertNotQueued(AutoRenewFailedMail::class);
$this->assertSame(1, Notification::where('type', 'auto_renew_failed')->where('recipient_id', $service->customer_id)->count());
}
}