update email template

This commit is contained in:
baodev0
2026-09-09 16:56:22 +07:00
parent d3d9f76ba0
commit 2365ef72e9
210 changed files with 6335 additions and 4688 deletions
+114
View File
@@ -0,0 +1,114 @@
<?php
namespace Tests\Unit;
use App\Exceptions\InvalidTemplateVariableException;
use App\Models\Template;
use App\Services\Email\EmailTemplateRenderer;
use Tests\TestCase;
class EmailTemplateRendererTest extends TestCase
{
private function makeTemplate(string $subject, string $message, array $variables): Template
{
$template = new Template([
'scenario' => 'unit_test_scenario',
'subject' => $subject,
'message' => $message,
'metadata' => ['variables' => $variables],
]);
return $template;
}
public function test_it_replaces_known_placeholders_in_subject_and_html(): void
{
$template = $this->makeTemplate(
'Xin chào {{full_name}}',
'<p>Chào mừng {{full_name}} đến với {{app_name}}</p>',
[
['key' => 'full_name', 'label' => 'Họ tên', 'type' => 'string'],
['key' => 'app_name', 'label' => 'Tên app', 'type' => 'string'],
],
);
$result = (new EmailTemplateRenderer())->render($template, [
'full_name' => 'Nguyễn Văn A',
'app_name' => 'Thế Giới Số',
]);
$this->assertSame('Xin chào Nguyễn Văn A', $result['subject']);
$this->assertSame('<p>Chào mừng Nguyễn Văn A đến với Thế Giới Số</p>', $result['html']);
}
public function test_it_falls_back_to_example_value_when_data_missing(): void
{
$template = $this->makeTemplate(
'Mã: {{code}}',
'<p>{{code}}</p>',
[['key' => 'code', 'label' => 'Mã', 'type' => 'string', 'example' => '000000']],
);
$result = (new EmailTemplateRenderer())->render($template, []);
$this->assertSame('Mã: 000000', $result['subject']);
$this->assertSame('<p>000000</p>', $result['html']);
}
public function test_it_html_escapes_string_type_by_default(): void
{
$template = $this->makeTemplate(
'subj',
'<p>{{name}}</p>',
[['key' => 'name', 'label' => 'Tên', 'type' => 'string']],
);
$result = (new EmailTemplateRenderer())->render($template, ['name' => '<script>alert(1)</script>']);
$this->assertStringNotContainsString('<script>', $result['html']);
$this->assertStringContainsString('&lt;script&gt;', $result['html']);
}
public function test_it_rejects_invalid_url_type(): void
{
$template = $this->makeTemplate(
'subj',
'<a href="{{link}}">go</a>',
[['key' => 'link', 'label' => 'Link', 'type' => 'url']],
);
$result = (new EmailTemplateRenderer())->render($template, ['link' => 'javascript:alert(1)']);
$this->assertStringNotContainsString('javascript:', $result['html']);
}
public function test_it_throws_for_undeclared_variable(): void
{
$template = $this->makeTemplate(
'subj',
'<p>{{full_name}} {{secret_admin_flag}}</p>',
[['key' => 'full_name', 'label' => 'Họ tên', 'type' => 'string']],
);
$this->expectException(InvalidTemplateVariableException::class);
$this->expectExceptionMessage('secret_admin_flag');
(new EmailTemplateRenderer())->render($template, ['full_name' => 'A']);
}
public function test_it_never_executes_placeholder_content_as_code(): void
{
$template = $this->makeTemplate(
'subj',
'<p>{{note}}</p>',
[['key' => 'note', 'label' => 'Note', 'type' => 'string']],
);
$result = (new EmailTemplateRenderer())->render($template, [
'note' => '{{ 7*7 }} @php(system("id")) <?php echo 1; ?>',
]);
$this->assertStringNotContainsString('49', $result['html']);
$this->assertStringContainsString('&lt;?php', $result['html']);
}
}
-137
View File
@@ -1,137 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Unit;
use App\Enums\Order\OrderStatus;
use App\Jobs\VirtualMachine\ProcessOrderPaymentJob;
use App\Models\Customer;
use App\Models\Invoice;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Product;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ProcessOrderPaymentJobTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function it_can_process_order_payment_with_vm_creation()
{
// Arrange
$customer = Customer::factory()->create(['credit' => 1000000]);
$order = Order::factory()->create([
'customer_id' => $customer->id,
'amount' => 500000,
'status' => OrderStatus::PENDING->value
]);
$invoice = Invoice::factory()->create([
'order_id' => $order->id,
'amount' => 500000
]);
$product = Product::factory()->create(['product_type' => 4]); // VPS product
$orderItem = OrderItem::factory()->create([
'order_id' => $order->id,
'product_id' => $product->id,
'product_type' => 4
]);
// Act
$job = new ProcessOrderPaymentJob($order);
$job->handle(
app(\App\Http\Services\Invoice\InvoiceService::class),
app(\App\Http\Services\Discord::class)
);
// Assert
$this->assertEquals(500000, $customer->fresh()->credit); // Credit should be deducted
$this->assertEquals(OrderStatus::ACTIVE->value, $order->fresh()->status);
}
/** @test */
public function it_handles_insufficient_credit()
{
// Arrange
$customer = Customer::factory()->create(['credit' => 100000]); // Insufficient credit
$order = Order::factory()->create([
'customer_id' => $customer->id,
'amount' => 500000,
'status' => OrderStatus::PENDING->value
]);
$invoice = Invoice::factory()->create([
'order_id' => $order->id,
'amount' => 500000
]);
// Act
$job = new ProcessOrderPaymentJob($order);
$job->handle(
app(\App\Http\Services\Invoice\InvoiceService::class),
app(\App\Http\Services\Discord::class)
);
// Assert
$this->assertEquals(100000, $customer->fresh()->credit); // Credit should not be deducted
$this->assertEquals(OrderStatus::PENDING->value, $order->fresh()->status);
}
/** @test */
public function it_handles_missing_invoice()
{
// Arrange
$customer = Customer::factory()->create(['credit' => 1000000]);
$order = Order::factory()->create([
'customer_id' => $customer->id,
'amount' => 500000,
'status' => OrderStatus::PENDING->value
]);
// No invoice created
// Act
$job = new ProcessOrderPaymentJob($order);
$job->handle(
app(\App\Http\Services\Invoice\InvoiceService::class),
app(\App\Http\Services\Discord::class)
);
// Assert
$this->assertEquals(1000000, $customer->fresh()->credit); // Credit should not be deducted
$this->assertEquals(OrderStatus::PENDING->value, $order->fresh()->status);
}
/** @test */
public function it_handles_non_vps_products()
{
// Arrange
$customer = Customer::factory()->create(['credit' => 1000000]);
$order = Order::factory()->create([
'customer_id' => $customer->id,
'amount' => 500000,
'status' => OrderStatus::PENDING->value
]);
$invoice = Invoice::factory()->create([
'order_id' => $order->id,
'amount' => 500000
]);
$product = Product::factory()->create(['product_type' => 1]); // Non-VPS product
$orderItem = OrderItem::factory()->create([
'order_id' => $order->id,
'product_id' => $product->id,
'product_type' => 1
]);
// Act
$job = new ProcessOrderPaymentJob($order);
$job->handle(
app(\App\Http\Services\Invoice\InvoiceService::class),
app(\App\Http\Services\Discord::class)
);
// Assert
$this->assertEquals(1000000, $customer->fresh()->credit); // Credit should not be deducted for non-VPS
$this->assertEquals(OrderStatus::PENDING->value, $order->fresh()->status);
}
}