Files
Gnuboard7/modules/_bundled/sirsoft-ecommerce/tests/Unit/Services/DefaultShippingPolicyFallbackTest.php
T
2026-07-01 10:30:32 +09:00

202 lines
7.2 KiB
PHP

<?php
namespace Modules\Sirsoft\Ecommerce\Tests\Unit\Services;
use Modules\Sirsoft\Ecommerce\Database\Factories\ProductFactory;
use Modules\Sirsoft\Ecommerce\Database\Factories\ProductOptionFactory;
use Modules\Sirsoft\Ecommerce\DTO\CalculationInput;
use Modules\Sirsoft\Ecommerce\DTO\CalculationItem;
use Modules\Sirsoft\Ecommerce\DTO\ShippingAddress;
use Modules\Sirsoft\Ecommerce\Enums\ChargePolicyEnum;
use Modules\Sirsoft\Ecommerce\Models\Product;
use Modules\Sirsoft\Ecommerce\Models\ProductOption;
use Modules\Sirsoft\Ecommerce\Models\ShippingPolicy;
use Modules\Sirsoft\Ecommerce\Services\OrderCalculationService;
use Modules\Sirsoft\Ecommerce\Tests\ModuleTestCase;
/**
* 기본 배송정책 폴백 계산 Unit 테스트
*
* 상품에 배송정책이 부여되지 않은 경우(shipping_policy_id=null) 계산 단계에서
* 기본 배송정책(is_default=true)으로 폴백되어 배송비가 부과되는지 검증합니다.
*
* 회귀 배경: 기존 groupByShippingPolicy 는 정책이 null 인 상품을 배송비 그룹에서
* 제외(continue)해 "무료"로 처리했고, 일본(JP) 선택 시 배송불가로 판정되었습니다.
*/
class DefaultShippingPolicyFallbackTest extends ModuleTestCase
{
protected OrderCalculationService $service;
protected function setUp(): void
{
parent::setUp();
$this->setupTestCurrencySettings();
$this->service = app(OrderCalculationService::class);
}
/**
* 테스트용 KRW 단일 통화 설정을 저장합니다.
*/
protected function setupTestCurrencySettings(): void
{
$settingsPath = storage_path('framework/testing/modules/sirsoft-ecommerce/settings');
if (! is_dir($settingsPath)) {
mkdir($settingsPath, 0755, true);
}
$settings = [
'default_language' => 'ko',
'default_currency' => 'KRW',
'currencies' => [
[
'code' => 'KRW',
'name' => ['ko' => 'KRW (원)', 'en' => 'KRW (Won)'],
'exchange_rate' => null,
'rounding_unit' => '1',
'rounding_method' => 'floor',
'is_default' => true,
],
],
];
file_put_contents(
$settingsPath.'/language_currency.json',
json_encode($settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
}
/**
* 기본 배송정책(KR/JP 각 FIXED 600)을 생성합니다.
*
* @return ShippingPolicy 생성된 기본 배송정책
*/
protected function makeDefaultPolicy(): ShippingPolicy
{
$policy = ShippingPolicy::create([
'name' => ['ko' => '기본 배송정책', 'en' => 'Default Policy'],
'is_default' => true,
'is_active' => true,
]);
foreach (['KR', 'JP'] as $code) {
$policy->countrySettings()->create([
'country_code' => $code,
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => ChargePolicyEnum::FIXED,
'base_fee' => 600,
'is_active' => true,
]);
}
return $policy->load('countrySettings');
}
/**
* 배송정책 없는 상품+옵션을 생성합니다.
*
* @return array{0: Product, 1: ProductOption}
*/
protected function makeProductWithoutPolicy(int $price = 30000): array
{
$product = ProductFactory::new()->create([
'tax_status' => 'taxable',
'selling_price' => $price,
'list_price' => $price,
'shipping_policy_id' => null,
]);
$option = ProductOptionFactory::new()->forProduct($product)->create([
'price_adjustment' => 0,
'stock_quantity' => 100,
'is_default' => true,
]);
return [$product, $option];
}
/**
* 정책 없는 상품이 KR 배송지에서 기본 배송정책의 배송비(600)를 부과받는다.
*
* 회귀: 기존엔 정책 null 상품이 배송비 그룹에서 제외되어 0원(무료)이었음.
*/
public function test_null_policy_product_charges_default_policy_fee_for_kr(): void
{
$this->makeDefaultPolicy();
[$product, $option] = $this->makeProductWithoutPolicy();
$input = new CalculationInput(
items: [new CalculationItem(productId: $product->id, productOptionId: $option->id, quantity: 1)],
shippingAddress: new ShippingAddress(countryCode: 'KR'),
);
$result = $this->service->calculate($input);
$this->assertEquals(600, $result->summary->totalShipping);
}
/**
* 정책 없는 상품이 JP 배송지에서도 기본 배송정책의 배송비(600)를 부과받는다.
*
* 회귀: 기존엔 JP 선택 시 배송불가 + 무료였음.
*/
public function test_null_policy_product_charges_default_policy_fee_for_jp(): void
{
$this->makeDefaultPolicy();
[$product, $option] = $this->makeProductWithoutPolicy();
$input = new CalculationInput(
items: [new CalculationItem(productId: $product->id, productOptionId: $option->id, quantity: 1)],
shippingAddress: new ShippingAddress(countryCode: 'JP'),
);
$result = $this->service->calculate($input);
$this->assertEquals(600, $result->summary->totalShipping);
}
/**
* 기본 배송정책이 없으면 정책 없는 상품은 배송비 0원(국내 기본 배송)으로 처리된다.
*/
public function test_null_policy_product_is_free_when_no_default_policy_exists(): void
{
[$product, $option] = $this->makeProductWithoutPolicy();
$input = new CalculationInput(
items: [new CalculationItem(productId: $product->id, productOptionId: $option->id, quantity: 1)],
shippingAddress: new ShippingAddress(countryCode: 'KR'),
);
$result = $this->service->calculate($input);
$this->assertEquals(0, $result->summary->totalShipping);
}
/**
* 폴백 계산 결과의 적용 배송정책이 기본 배송정책 ID 와 스냅샷을 동결한다.
*
* 환불 재계산은 이 동결된 스냅샷(policy_id + country_setting)을 기준으로 동작하므로,
* 주문 시점의 기본 배송정책이 이후 변경되어도 원 주문 기준이 보존된다.
*/
public function test_fallback_freezes_default_policy_into_applied_snapshot(): void
{
$default = $this->makeDefaultPolicy();
[$product, $option] = $this->makeProductWithoutPolicy();
$input = new CalculationInput(
items: [new CalculationItem(productId: $product->id, productOptionId: $option->id, quantity: 1)],
shippingAddress: new ShippingAddress(countryCode: 'JP'),
);
$result = $this->service->calculate($input);
$applied = $result->items[0]->appliedShippingPolicy;
$this->assertNotNull($applied);
$this->assertSame($default->id, $applied->policyId);
$this->assertSame('JP', $applied->policySnapshot['country_code'] ?? null);
$this->assertSame(600, $applied->policySnapshot['base_fee'] ?? null);
}
}