Files
Gnuboard7/tests/Unit/Http/Middleware/ExtensionMiddlewareGateTest.php
T
HeuJung c0e0d82459 refactor(extension): 확장 미들웨어 선언적 self-gate 정규화
확장이 SP Kernel 미들웨어 그룹을 직접 조작하거나 라우트 파일에 미들웨어
FQCN 을 직접 부착하던 임시 방식을, 확장이 부착 대상(targets)을 명시 선언하고
코어가 요청 시점에 라우트명·URI 로 매칭해 실행하는 self-gate 로 정규화.
번들 7건(ecommerce/gdpr/pay 3종) 이전, audit 룰 3건·문서·버전 동기화 동반.

부수적으로 generate-skills.cjs 가 재생성 시 신의성실(부수의무) 섹션을 소실
시키던 회귀를 수정 — 스킬별 불릿을 스크립트 SSoT 로 편입해 재생성에도 보존.
2026-07-16 10:22:51 +09:00

166 lines
5.3 KiB
PHP

<?php
namespace Tests\Unit\Http\Middleware;
use App\Contracts\Extension\ExtensionMiddlewareRegistryInterface;
use App\Http\Middleware\ExtensionMiddlewareGate;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Mockery;
use Tests\TestCase;
/**
* 확장 미들웨어 self-gate 실행기 테스트.
*
* 게이트가 registry 매칭 결과에 따라 확장 미들웨어를 Pipeline 으로 실행하거나
* 즉시 통과하는지, 무명 라우트·timing·실행 순서·예외 격리를 검증합니다.
*/
class ExtensionMiddlewareGateTest extends TestCase
{
/**
* registry 를 mock 하고 지정 매칭 결과를 반환하는 게이트를 만듭니다.
*
* @param array<int, class-string> $matched resolveForRoute 반환값
*/
private function makeGate(array $matched): ExtensionMiddlewareGate
{
$registry = Mockery::mock(ExtensionMiddlewareRegistryInterface::class);
$registry->shouldReceive('resolveForRoute')->andReturn($matched);
return new ExtensionMiddlewareGate($this->app, $registry);
}
/**
* @effects gate_runs_matched_middleware_via_pipeline
*/
public function test_matched_middleware_runs(): void
{
GateSpyMiddleware::$ran = false;
$gate = $this->makeGate([GateSpyMiddleware::class]);
$response = $gate->handle(Request::create('/api/foo', 'GET'), fn ($req) => new Response('ok'), 'api', 'after_core');
$this->assertTrue(GateSpyMiddleware::$ran, '매칭된 확장 미들웨어가 실행되어야 합니다.');
$this->assertSame('ok', $response->getContent());
}
/**
* @effects gate_passes_through_when_no_match
*/
public function test_no_match_passes_through(): void
{
GateSpyMiddleware::$ran = false;
$gate = $this->makeGate([]);
$response = $gate->handle(Request::create('/api/foo', 'GET'), fn ($req) => new Response('passed'), 'api', 'after_core');
$this->assertFalse(GateSpyMiddleware::$ran, '매칭 없으면 미들웨어를 실행하지 않습니다.');
$this->assertSame('passed', $response->getContent());
}
/**
* @effects gate_matched_middleware_can_short_circuit_response
*/
public function test_matched_middleware_can_short_circuit_response(): void
{
$gate = $this->makeGate([GateBlockMiddleware::class]);
$response = $gate->handle(Request::create('/api/foo', 'GET'), fn ($req) => new Response('should-not-reach'), 'api', 'after_core');
$this->assertSame(403, $response->getStatusCode());
$this->assertSame('blocked', $response->getContent());
}
/**
* @effects gate_execution_order_follows_matched_list
*/
public function test_execution_order_follows_matched_list(): void
{
GateOrderMiddlewareA::$log = [];
$gate = $this->makeGate([GateOrderMiddlewareA::class, GateOrderMiddlewareB::class]);
$gate->handle(Request::create('/api/foo', 'GET'), fn ($req) => new Response('ok'), 'api', 'after_core');
$this->assertSame(['A', 'B'], GateOrderMiddlewareA::$log, '매칭 리스트 순서대로 실행되어야 합니다.');
}
/**
* @effects gate_reaches_registry_and_runs_matched_for_unnamed_route
*/
public function test_unnamed_route_still_reaches_registry_and_runs_matched(): void
{
GateSpyMiddleware::$ran = false;
// 무명 라우트 요청 (route()->getName()=null) 에서도 게이트가 registry 를 조회하고
// URI 매칭으로 반환된 미들웨어를 실행한다 (조기 통과하지 않음).
$gate = $this->makeGate([GateSpyMiddleware::class]);
$gate->handle(Request::create('/', 'GET'), fn ($req) => new Response('ok'), 'web', 'after_core');
$this->assertTrue(GateSpyMiddleware::$ran, '무명 라우트에서도 매칭 미들웨어가 실행되어야 합니다.');
}
public function test_middleware_exception_propagates(): void
{
$gate = $this->makeGate([GateThrowMiddleware::class]);
$this->expectException(\RuntimeException::class);
$gate->handle(Request::create('/api/foo', 'GET'), fn ($req) => new Response('ok'), 'api', 'after_core');
}
}
/** 실행 여부만 기록하는 픽스처 미들웨어. */
class GateSpyMiddleware
{
public static bool $ran = false;
public function handle($request, \Closure $next)
{
self::$ran = true;
return $next($request);
}
}
/** 응답을 사전 차단하는 픽스처 미들웨어. */
class GateBlockMiddleware
{
public function handle($request, \Closure $next)
{
return new Response('blocked', 403);
}
}
/** 실행 순서를 기록하는 픽스처 미들웨어 A. */
class GateOrderMiddlewareA
{
/** @var array<int, string> */
public static array $log = [];
public function handle($request, \Closure $next)
{
self::$log[] = 'A';
return $next($request);
}
}
/** 실행 순서를 기록하는 픽스처 미들웨어 B. */
class GateOrderMiddlewareB
{
public function handle($request, \Closure $next)
{
GateOrderMiddlewareA::$log[] = 'B';
return $next($request);
}
}
/** 예외를 던지는 픽스처 미들웨어. */
class GateThrowMiddleware
{
public function handle($request, \Closure $next)
{
throw new \RuntimeException('boom');
}
}