관리자 환경설정이 값을 저장은 하지만 실제 동작에는 닿지 않던 결함군을 정리했다. 설정 화면(min/max)·저장 검증(FormRequest)·런타임 적용(Service) 세 계층이 각자 리터럴을 들고 있어 서로 조용히 갈라지던 것이 공통 원인이다. 경계값 SSoT 도입: - config/core.php `settings_limits` 를 단일 출처로 두고, FormRequest 는 이 값으로 규칙을 만들고 화면은 설정 응답의 `_meta.limits` 로 같은 값을 받아 바인딩한다. - 게시판·이커머스도 각 모듈 config 의 limits 를 동일 구조로 노출한다. 적용되지 않던 정책을 실제로 적용: - 비밀번호 최소 길이·특수문자 필수(신규 지정 7개 경로. 로그인은 제외 — 정책을 올렸을 때 기존 회원이 자기 계정에 접근조차 못 하는 데드락을 막기 위함) - 계정 잠금 시간 0 = 무한대(전용 컬럼 분리. timestamp 상한과 NULL 규약 때문에 기존 컬럼 재사용이 불가능하다) + 관리자 수동 해제 경로 - 업로드 최대 크기·허용 확장자, 게시판 첨부 개수·댓글 깊이, 페이지 첨부 정책, 장바구니 수량·구매 한도, 마일리지 사용 한도, 주문 부가세 파생 결함: - 큐 Job(sync 드라이버)이 진행 중인 요청의 Request 바인딩을 덮어써, 뒤이어 실행되는 훅 리스너가 헤더를 잃고 조용히 건너뛰었다. Job 이 원 바인딩을 복원하도록 고치고, 전송 헤더가 필요한 리스너는 동기 실행으로 선언한다. - 확장 업데이트 실패 시 상태만 되돌리고 상태 캐시를 비우지 않아, 그 확장의 관리자 화면만 캐시 TTL(기본 하루) 동안 404 로 남았다. 실패 경로에도 무효화를 대칭으로 두고, 빈 목록 자체를 캐시하지 않는다. - 글 작성 시 첨부 파일이 Service 로 전달되지 않아 검증·권한은 통과하고 첨부만 사라졌다. - 환경설정 저장 실패 안내가 내부 설정 키를 그대로 노출했다(105개 항목에 라벨 부여). 재발 방지: - audit 룰 3종(레이아웃 경계 리터럴 / FormRequest 경계 불일치 / Service 재클램프) - 테스트 실행 범위 강제 훅(check-test-scope) - 트러블슈팅 사례 2건(큐 Job 요청 오염 / 확장 상태 캐시 고착) 공개 이슈: gnuboard/g7
119 lines
4.1 KiB
PHP
119 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Services;
|
|
|
|
use App\Extension\IdentityVerification\Providers\MailIdentityProvider;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Log;
|
|
use ReflectionMethod;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* IDV 인증코드 길이 경계 테스트 (C3)
|
|
*
|
|
* 스키마가 경계를 선언하지 않아 서비스가 자체 상수(4~10)로 조용히 클램프했습니다.
|
|
* 관리자가 12를 설정하면 화면은 12로 보이는데 실제로는 10자리 코드가 발급됐습니다.
|
|
*/
|
|
class MailIdentityProviderCodeLengthTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
/**
|
|
* 프로바이더 인스턴스를 반환합니다.
|
|
*
|
|
* @return MailIdentityProvider 프로바이더
|
|
*/
|
|
private function provider(): MailIdentityProvider
|
|
{
|
|
return app(MailIdentityProvider::class);
|
|
}
|
|
|
|
/**
|
|
* 스키마가 경계를 선언하는지 테스트합니다.
|
|
*
|
|
* 관리자 화면은 스키마를 그대로 노출하므로, 경계 선언이 곧 UI 계약이 된다.
|
|
*
|
|
* @scenario provider=mail, code_length=schema_declaration
|
|
*
|
|
* @effects schema_declares_min_max_boundaries
|
|
*/
|
|
public function test_schema_declares_code_length_boundaries(): void
|
|
{
|
|
$schema = $this->provider()->getSettingsSchema();
|
|
|
|
// 수정 전: min/max 키 자체가 없었다
|
|
$this->assertArrayHasKey('min', $schema['code_length']);
|
|
$this->assertArrayHasKey('max', $schema['code_length']);
|
|
$this->assertSame(MailIdentityProvider::MIN_CODE_LENGTH, $schema['code_length']['min']);
|
|
$this->assertSame(MailIdentityProvider::MAX_CODE_LENGTH, $schema['code_length']['max']);
|
|
$this->assertSame(MailIdentityProvider::DEFAULT_CODE_LENGTH, $schema['code_length']['default']);
|
|
}
|
|
|
|
/**
|
|
* 경계 내 길이는 그대로 적용되는지 테스트합니다.
|
|
*
|
|
* @scenario provider=mail, code_length=within_bounds
|
|
*
|
|
* @effects configured_length_applied_verbatim
|
|
*/
|
|
public function test_code_length_within_bounds_is_applied(): void
|
|
{
|
|
$method = new ReflectionMethod(MailIdentityProvider::class, 'generateNumericCode');
|
|
|
|
$this->assertSame(8, strlen($method->invoke($this->provider(), 8)));
|
|
$this->assertSame(4, strlen($method->invoke($this->provider(), 4)));
|
|
$this->assertSame(10, strlen($method->invoke($this->provider(), 10)));
|
|
}
|
|
|
|
/**
|
|
* 경계를 벗어나면 조용한 클램프 대신 기본값 폴백 + 경고 로그를 남기는지 테스트합니다.
|
|
*
|
|
* @scenario provider=mail, code_length=above_max
|
|
*
|
|
* @effects out_of_range_falls_back_to_default_with_warning
|
|
*/
|
|
public function test_out_of_range_falls_back_to_default_with_warning(): void
|
|
{
|
|
Log::spy();
|
|
|
|
$method = new ReflectionMethod(MailIdentityProvider::class, 'generateNumericCode');
|
|
|
|
// 수정 전: 12 → 10 으로 조용히 클램프
|
|
$code = $method->invoke($this->provider(), 12);
|
|
|
|
$this->assertSame(MailIdentityProvider::DEFAULT_CODE_LENGTH, strlen($code));
|
|
Log::shouldHaveReceived('warning')->once();
|
|
}
|
|
|
|
/**
|
|
* 하한 미만도 동일하게 폴백하는지 테스트합니다.
|
|
*
|
|
* @scenario provider=mail, code_length=below_min
|
|
*
|
|
* @effects out_of_range_falls_back_to_default_with_warning
|
|
*/
|
|
public function test_below_minimum_falls_back_to_default(): void
|
|
{
|
|
$method = new ReflectionMethod(MailIdentityProvider::class, 'generateNumericCode');
|
|
|
|
$this->assertSame(
|
|
MailIdentityProvider::DEFAULT_CODE_LENGTH,
|
|
strlen($method->invoke($this->provider(), 2))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 생성된 코드가 숫자로만 구성되는지 테스트합니다.
|
|
*
|
|
* @scenario provider=mail, code_length=within_bounds
|
|
*
|
|
* @effects generated_code_is_numeric
|
|
*/
|
|
public function test_generated_code_is_numeric(): void
|
|
{
|
|
$method = new ReflectionMethod(MailIdentityProvider::class, 'generateNumericCode');
|
|
|
|
$this->assertMatchesRegularExpression('/^\d{6}$/', $method->invoke($this->provider(), 6));
|
|
}
|
|
}
|