Files
Gnuboard7/tests/Feature/Seo/UpdateLayoutSeoValidationTest.php
2026-04-01 10:30:52 +09:00

264 lines
11 KiB
PHP

<?php
namespace Tests\Feature\Seo;
use App\Http\Requests\Layout\UpdateLayoutContentRequest;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
/**
* UpdateLayoutContentRequest SEO 검증 규칙 테스트
*
* 레이아웃 content의 meta.seo 필드에 대한 유효성 검증 규칙이
* 올바르게 동작하는지 Validator를 통해 검증합니다.
*/
class UpdateLayoutSeoValidationTest extends TestCase
{
/**
* 검증에 사용할 기본 유효 content 데이터를 반환합니다.
*
* @param array $seoOverrides SEO 필드 오버라이드
* @return array 유효한 content 배열
*/
private function makeValidContent(array $seoOverrides = []): array
{
$seo = array_merge([
'enabled' => true,
'data_sources' => ['product'],
'priority' => 0.8,
'changefreq' => 'daily',
'og' => ['type' => 'website'],
'structured_data' => ['@type' => 'WebSite'],
], $seoOverrides);
return [
'content' => [
'version' => '1.0.0',
'layout_name' => 'test-layout',
'endpoint' => '/api/test',
'components' => [
['component' => 'Div', 'children' => []],
],
'meta' => [
'seo' => $seo,
],
],
];
}
/**
* UpdateLayoutContentRequest의 rules()에서 SEO 관련 규칙만 추출하여
* Validator로 검증합니다.
*
* @param array $data 검증할 데이터
* @return \Illuminate\Validation\Validator 검증기 인스턴스
*/
private function makeSeoValidator(array $data): \Illuminate\Validation\Validator
{
// SEO 관련 규칙만 추출하여 검증 (Custom Rule 의존성 회피)
$seoRules = [
'content.meta.seo' => ['nullable', 'array'],
'content.meta.seo.enabled' => ['nullable', 'boolean'],
'content.meta.seo.data_sources' => ['nullable', 'array'],
'content.meta.seo.data_sources.*' => ['string'],
'content.meta.seo.priority' => ['nullable', 'numeric', 'min:0', 'max:1'],
'content.meta.seo.changefreq' => ['nullable', 'string', 'in:always,hourly,daily,weekly,monthly,yearly,never'],
'content.meta.seo.og' => ['nullable', 'array'],
'content.meta.seo.structured_data' => ['nullable', 'array'],
];
return Validator::make($data, $seoRules);
}
/**
* 유효한 SEO 메타 객체가 검증을 통과하는지 확인합니다.
*/
public function test_valid_seo_meta_passes_validation(): void
{
$data = $this->makeValidContent();
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), '유효한 SEO 메타가 검증을 통과해야 합니다');
}
/**
* seo.enabled가 boolean이 아닐 때 검증에 실패하는지 확인합니다.
*/
public function test_seo_enabled_must_be_boolean(): void
{
// 문자열 'yes'는 boolean이 아니므로 실패
$data = $this->makeValidContent(['enabled' => 'yes']);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->fails(), 'enabled에 문자열 "yes"는 실패해야 합니다');
$this->assertArrayHasKey('content.meta.seo.enabled', $validator->errors()->toArray());
// 정수 2는 boolean이 아니므로 실패
$data = $this->makeValidContent(['enabled' => 2]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->fails(), 'enabled에 정수 2는 실패해야 합니다');
// true/false는 통과
$data = $this->makeValidContent(['enabled' => true]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), 'enabled에 true는 통과해야 합니다');
$data = $this->makeValidContent(['enabled' => false]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), 'enabled에 false는 통과해야 합니다');
}
/**
* seo.priority가 0~1 범위 밖일 때 검증에 실패하는지 확인합니다.
*/
public function test_seo_priority_must_be_between_0_and_1(): void
{
// 1.5 실패
$data = $this->makeValidContent(['priority' => 1.5]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->fails(), 'priority 1.5는 실패해야 합니다');
$this->assertArrayHasKey('content.meta.seo.priority', $validator->errors()->toArray());
// -0.1 실패
$data = $this->makeValidContent(['priority' => -0.1]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->fails(), 'priority -0.1는 실패해야 합니다');
// 0.8 통과
$data = $this->makeValidContent(['priority' => 0.8]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), 'priority 0.8는 통과해야 합니다');
// 0 통과
$data = $this->makeValidContent(['priority' => 0]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), 'priority 0는 통과해야 합니다');
// 1 통과
$data = $this->makeValidContent(['priority' => 1]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), 'priority 1은 통과해야 합니다');
// 1.0 통과
$data = $this->makeValidContent(['priority' => 1.0]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), 'priority 1.0은 통과해야 합니다');
}
/**
* seo.changefreq가 허용된 값이 아닐 때 검증에 실패하는지 확인합니다.
*/
public function test_seo_changefreq_must_be_valid_value(): void
{
// 'invalid' 실패
$data = $this->makeValidContent(['changefreq' => 'invalid']);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->fails(), 'changefreq "invalid"는 실패해야 합니다');
$this->assertArrayHasKey('content.meta.seo.changefreq', $validator->errors()->toArray());
// 'biweekly' 실패 (허용 목록에 없음)
$data = $this->makeValidContent(['changefreq' => 'biweekly']);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->fails(), 'changefreq "biweekly"는 실패해야 합니다');
// 허용된 모든 값 통과 확인
$validValues = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'];
foreach ($validValues as $value) {
$data = $this->makeValidContent(['changefreq' => $value]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), "changefreq \"{$value}\"는 통과해야 합니다");
}
}
/**
* seo.data_sources 항목이 문자열이 아닐 때 검증에 실패하는지 확인합니다.
*/
public function test_seo_data_sources_items_must_be_strings(): void
{
// 정수 배열 실패
$data = $this->makeValidContent(['data_sources' => [123, 456]]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->fails(), 'data_sources에 정수는 실패해야 합니다');
// 문자열 배열 통과
$data = $this->makeValidContent(['data_sources' => ['product', 'categories']]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), 'data_sources에 문자열은 통과해야 합니다');
// 빈 배열 통과
$data = $this->makeValidContent(['data_sources' => []]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), '빈 data_sources는 통과해야 합니다');
// 혼합 배열 실패 (문자열 + 정수)
$data = $this->makeValidContent(['data_sources' => ['product', 123]]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->fails(), '혼합 data_sources는 실패해야 합니다');
}
/**
* seo.og가 배열이 아닐 때 검증에 실패하는지 확인합니다.
*/
public function test_seo_og_must_be_array(): void
{
// 문자열 실패
$data = $this->makeValidContent(['og' => 'not-an-array']);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->fails(), 'og에 문자열은 실패해야 합니다');
// 배열 통과
$data = $this->makeValidContent(['og' => ['type' => 'article', 'title' => 'Test']]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), 'og에 배열은 통과해야 합니다');
// null 통과
$data = $this->makeValidContent(['og' => null]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), 'og에 null은 통과해야 합니다');
}
/**
* seo.structured_data가 배열이 아닐 때 검증에 실패하는지 확인합니다.
*/
public function test_seo_structured_data_must_be_array(): void
{
// 문자열 실패
$data = $this->makeValidContent(['structured_data' => 'not-an-array']);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->fails(), 'structured_data에 문자열은 실패해야 합니다');
// 배열 통과
$data = $this->makeValidContent(['structured_data' => ['@type' => 'Product', 'name' => 'Test']]);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), 'structured_data에 배열은 통과해야 합니다');
}
/**
* seo 전체가 null일 때 검증을 통과하는지 확인합니다.
*/
public function test_seo_can_be_null(): void
{
$data = [
'content' => [
'meta' => [
'seo' => null,
],
],
];
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->passes(), 'seo가 null이면 통과해야 합니다');
}
/**
* seo.priority가 숫자가 아닐 때 검증에 실패하는지 확인합니다.
*/
public function test_seo_priority_must_be_numeric(): void
{
$data = $this->makeValidContent(['priority' => 'high']);
$validator = $this->makeSeoValidator($data);
$this->assertTrue($validator->fails(), 'priority에 문자열 "high"는 실패해야 합니다');
$this->assertArrayHasKey('content.meta.seo.priority', $validator->errors()->toArray());
}
}