134 lines
4.7 KiB
PHP
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());
|
|
}
|
|
}
|