feat(admin): improve payments and server settings
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Api\Admin\DnsManager;
|
||||
|
||||
use App\Models\DnsServer;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DnsServerDomainTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private function admin(): User
|
||||
{
|
||||
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Full Administrator');
|
||||
|
||||
return $admin;
|
||||
}
|
||||
|
||||
public function test_public_host_prefers_domain_over_ip(): void
|
||||
{
|
||||
$server = new DnsServer(['ip_address' => '103.42.58.99', 'domain' => 'host99.tgs.com.vn']);
|
||||
$this->assertSame('host99.tgs.com.vn', $server->publicHost());
|
||||
|
||||
$server->domain = null;
|
||||
$this->assertSame('103.42.58.99', $server->publicHost());
|
||||
}
|
||||
|
||||
public function test_store_saves_domain_and_rejects_invalid_hostname(): void
|
||||
{
|
||||
$admin = $this->admin();
|
||||
$payload = ['name' => 'Host 99', 'ip_address' => '103.42.58.99', 'username' => 'root', 'password' => 'secret'];
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->postJson('/api/v1/administrator/dns-servers', $payload + ['domain' => 'not a domain'])
|
||||
->assertStatus(422);
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->postJson('/api/v1/administrator/dns-servers', $payload + ['domain' => 'host99.tgs.com.vn'])
|
||||
->assertStatus(201)
|
||||
->assertJsonPath('data.domain', 'host99.tgs.com.vn');
|
||||
|
||||
$this->assertDatabaseHas('dns_manager_servers', ['ip_address' => '103.42.58.99', 'domain' => 'host99.tgs.com.vn']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Api\Service;
|
||||
|
||||
use App\Enums\Invoice\InvoiceStatus;
|
||||
use App\Enums\Order\OrderStatus;
|
||||
use App\Jobs\Mail\Admin\AdminPaymentReceivedJob;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Order;
|
||||
use App\Services\Api\Order\OrderService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Tests\TestCase;
|
||||
|
||||
class OrderPaymentAdminNotificationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private function orderFor(Customer $customer, int $invoiceStatus): Order
|
||||
{
|
||||
$order = Order::create([
|
||||
'order_num' => 'ORD-' . uniqid(),
|
||||
'customer_id' => $customer->id,
|
||||
'sub_total' => 100_000,
|
||||
'discount_amount' => 0,
|
||||
'total' => 100_000,
|
||||
'status' => OrderStatus::PENDING->value,
|
||||
]);
|
||||
|
||||
Invoice::create([
|
||||
'invoice_number' => 'INV-' . uniqid(),
|
||||
'customer_id' => $customer->id,
|
||||
'order_id' => $order->id,
|
||||
'invoice_type' => 1,
|
||||
'sub_total' => 100_000,
|
||||
'tax' => 0,
|
||||
'total' => 100_000,
|
||||
'status' => $invoiceStatus,
|
||||
]);
|
||||
|
||||
return $order->fresh(['invoice', 'customer']);
|
||||
}
|
||||
|
||||
public function test_successful_payment_notifies_admin_once(): void
|
||||
{
|
||||
Bus::fake([AdminPaymentReceivedJob::class]);
|
||||
$customer = Customer::factory()->create(['total' => 200_000]);
|
||||
$order = $this->orderFor($customer, InvoiceStatus::UNPAID->value);
|
||||
|
||||
app(OrderService::class)->pay($order, 'balance');
|
||||
|
||||
Bus::assertDispatchedTimes(AdminPaymentReceivedJob::class, 1);
|
||||
}
|
||||
|
||||
public function test_insufficient_balance_does_not_notify_admin(): void
|
||||
{
|
||||
Bus::fake([AdminPaymentReceivedJob::class]);
|
||||
$customer = Customer::factory()->create(['total' => 0]);
|
||||
$order = $this->orderFor($customer, InvoiceStatus::UNPAID->value);
|
||||
|
||||
try {
|
||||
app(OrderService::class)->pay($order, 'balance');
|
||||
$this->fail('Expected InvalidArgumentException');
|
||||
} catch (\InvalidArgumentException) {
|
||||
}
|
||||
|
||||
Bus::assertNotDispatched(AdminPaymentReceivedJob::class);
|
||||
}
|
||||
|
||||
public function test_paying_an_already_paid_invoice_is_rejected_without_notifying_admin(): void
|
||||
{
|
||||
Bus::fake([AdminPaymentReceivedJob::class]);
|
||||
$customer = Customer::factory()->create(['total' => 200_000]);
|
||||
$order = $this->orderFor($customer, InvoiceStatus::PAID->value);
|
||||
|
||||
try {
|
||||
app(OrderService::class)->pay($order, 'balance');
|
||||
$this->fail('Expected InvalidArgumentException');
|
||||
} catch (\InvalidArgumentException) {
|
||||
}
|
||||
|
||||
Bus::assertNotDispatched(AdminPaymentReceivedJob::class);
|
||||
$this->assertSame(200_000, (int) $customer->fresh()->total);
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,17 @@ declare(strict_types=1);
|
||||
namespace Tests\Feature\Api\Transaction;
|
||||
|
||||
use App\Enums\TransactionStatus;
|
||||
use App\Events\AdminNotificationCreated;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Transaction;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ManualDepositProofTest extends TestCase
|
||||
@@ -108,8 +113,13 @@ class ManualDepositProofTest extends TestCase
|
||||
public function test_upload_then_confirm_binds_proof_to_owner(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
Mail::fake();
|
||||
Event::fake([AdminNotificationCreated::class]);
|
||||
|
||||
$customer = Customer::factory()->create();
|
||||
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
|
||||
$admin = User::factory()->create();
|
||||
$admin->assignRole('Full Administrator');
|
||||
|
||||
$transaction = Transaction::create([
|
||||
'uuid' => Str::uuid()->toString(),
|
||||
@@ -138,5 +148,15 @@ class ManualDepositProofTest extends TestCase
|
||||
|
||||
$confirm->assertStatus(200)->assertJsonPath('error', false);
|
||||
$this->assertSame($path, $transaction->fresh()->proof_image);
|
||||
$this->assertDatabaseHas('notifications', [
|
||||
'recipient_type' => 'admin',
|
||||
'recipient_id' => $admin->id,
|
||||
'type' => 'manual_deposit_pending',
|
||||
'link' => '/transactions',
|
||||
]);
|
||||
Event::assertDispatched(
|
||||
AdminNotificationCreated::class,
|
||||
fn (AdminNotificationCreated $event) => $event->type === 'manual_deposit_pending',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@ namespace Tests\Feature\Mail;
|
||||
use App\Http\Facade\SignRSA;
|
||||
use App\Models\Setting;
|
||||
use App\Support\MailSettings;
|
||||
use App\Listeners\Mail\ApplySystemRecipients;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Mail\Events\MessageSending;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Tests\TestCase;
|
||||
|
||||
class MailSettingsQueueTest extends TestCase
|
||||
@@ -45,4 +48,21 @@ class MailSettingsQueueTest extends TestCase
|
||||
$this->saveSmtp('smtp.settings-two.test');
|
||||
$this->assertSame('smtp.settings-two.test', $this->hostSeenByQueuedJob());
|
||||
}
|
||||
|
||||
public function test_system_cc_and_bcc_are_applied_without_duplicate_recipients(): void
|
||||
{
|
||||
$this->saveSmtp('smtp.settings.test');
|
||||
$smtp = Setting::where('key', 'smtp')->firstOrFail();
|
||||
$values = json_decode((string) $smtp->value, true);
|
||||
$values['cc'] = ['audit@example.com', 'customer@example.com'];
|
||||
$values['bcc'] = ['archive@example.com', 'audit@example.com'];
|
||||
$smtp->update(['value' => json_encode($values)]);
|
||||
Cache::forget(MailSettings::cacheKey());
|
||||
|
||||
$message = (new Email())->to('customer@example.com')->subject('Test');
|
||||
app(ApplySystemRecipients::class)->handle(new MessageSending($message, []));
|
||||
|
||||
$this->assertSame(['audit@example.com'], array_map(fn ($address) => $address->getAddress(), $message->getCc()));
|
||||
$this->assertSame(['archive@example.com'], array_map(fn ($address) => $address->getAddress(), $message->getBcc()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Enums\Product\ProductBillingCycle;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ProductBillingCycleLabelTest extends TestCase
|
||||
{
|
||||
public function test_code_is_translated_to_vietnamese_label(): void
|
||||
{
|
||||
$this->assertSame('Hàng tháng', ProductBillingCycle::vietnameseLabelFor('monthly'));
|
||||
$this->assertSame('6 tháng', ProductBillingCycle::vietnameseLabelFor('semi_annually'));
|
||||
$this->assertSame('Hàng năm', ProductBillingCycle::vietnameseLabelFor('annually'));
|
||||
}
|
||||
|
||||
public function test_unknown_or_missing_code_falls_back_safely(): void
|
||||
{
|
||||
$this->assertSame('custom', ProductBillingCycle::vietnameseLabelFor('custom'));
|
||||
$this->assertSame('N/A', ProductBillingCycle::vietnameseLabelFor(null));
|
||||
}
|
||||
|
||||
public function test_months_are_translated_with_fallback(): void
|
||||
{
|
||||
$this->assertSame('Hàng tháng', ProductBillingCycle::vietnameseLabelForMonths(1));
|
||||
$this->assertSame('2 năm', ProductBillingCycle::vietnameseLabelForMonths(24));
|
||||
$this->assertSame('9 tháng', ProductBillingCycle::vietnameseLabelForMonths(9));
|
||||
$this->assertSame('0 tháng', ProductBillingCycle::vietnameseLabelForMonths(0));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user