Files
Gnuboard7/tests/Feature/Console/ApiEndpointProbeWriteTest.php
HeuJung 56a78e1d55 feat(api-docs): 요청/응답 예시 블록 + 8종 결함 근본 수정 + audit error 승격
API 레퍼런스에 요청 예시(raw HTTP)·응답 예시(envelope 전문)를 표준 포맷에
편입하고, 전 대상(코어+전 모듈+전 플러그인)에 방출했다. 예시 결함 8종
(multipart·Host 마스킹·write 실측·path 치환·현실적 바디·query 반영·중복
제거·민감값 마스킹)을 스캐폴더/프로브 공용 수정으로 근본 해소했다.

추가로 검증에서 드러난 결함을 마감했다:
- 응답 예시 body 의 절대 URL(페이지네이터·콜백) 호스트를 placeholder 로
 마스킹(maskResponseHost) — 요청 Host 헤더만 마스킹되던 누락 보완, 확장
 39건 정정
- api:docgen 커맨드 통합 테스트 신설(라우트 수집·확장 라우팅·rules 추출·
 멱등 재생성·check drift)
- module-routing.md URL 스킴 오류 정정(/api/admin → /api/modules)
- README·cheatsheet·api-documentation 에 API 문서 커맨드/레퍼런스 진입점 반영

전 대상 3칸 완료로 api-doc-coverage 룰을 warn→error 승격.
2026-07-08 18:22:34 +09:00

111 lines
3.9 KiB
PHP

<?php
namespace Tests\Feature\Console;
use App\Models\Role;
use App\Models\User;
use App\Support\ApiDoc\ApiDocSampleService;
use App\Support\ApiDoc\ApiEndpointProbe;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
/**
* ApiEndpointProbe 쓰기 메서드 in-process 실측 Feature 테스트.
*
* 쓰기 메서드(POST/PUT/DELETE)를 DB 트랜잭션 안에서 in-process dispatch 후 롤백해
* 응답 shape 만 관측하고 부수효과는 영속시키지 않는지, GET 실측이 오염되지 않는지 검증한다.
*/
class ApiEndpointProbeWriteTest extends TestCase
{
use RefreshDatabase;
/**
* 실측 프로브를 샘플 사용자 인증 상태로 준비합니다.
*
* @return array{0: ApiEndpointProbe, 1: User} 프로브와 인증 사용자
*/
private function preparedProbe(): array
{
$map = (new ApiDocSampleService)->seed();
$userId = User::where($map['users']['key'], $map['users']['value'])->value('id');
$probe = new ApiEndpointProbe(config('app.url'));
$probe->authenticate($userId);
return [$probe, User::find($userId)];
}
#[Test]
public function 쓰기_메서드는_트랜잭션_롤백으로_레코드를_영속시키지_않는다(): void
{
[$probe] = $this->preparedProbe();
$before = Role::count();
$result = $probe->probe('POST', '/api/admin/roles', [
'identifier' => 'probe_write_'.substr(uniqid(), 0, 8),
'name' => '실측 예시값',
'is_active' => true,
]);
// 응답은 관측되지만(성공 2xx) 레코드는 롤백되어 남지 않는다.
$this->assertNotNull($result['status']);
$this->assertSame($before, Role::count(), '쓰기 실측 후 레코드가 롤백되지 않고 남았습니다');
$probe->cleanup();
}
#[Test]
public function 성공한_쓰기_실측은_envelope_body_를_반환한다(): void
{
[$probe] = $this->preparedProbe();
$result = $probe->probe('POST', '/api/admin/roles', [
'identifier' => 'probe_env_'.substr(uniqid(), 0, 8),
'name' => '실측 예시값',
'is_active' => true,
]);
if ($result['ok']) {
$this->assertIsArray($result['body']);
$this->assertArrayHasKey('success', $result['body']);
$this->assertArrayHasKey('data', $result['body']);
} else {
// 검증 규칙이 도메인 특이해 실패할 수 있으나, 그 경우에도 skipped_reason 이 명확해야 한다.
$this->assertNotNull($result['skipped_reason']);
}
$probe->cleanup();
}
#[Test]
public function 연속_쓰기_실측이_서로_인증_컨텍스트를_오염시키지_않는다(): void
{
[$probe] = $this->preparedProbe();
// 연속 쓰기 실측(각각 guard 조작 후 forgetGuards)이 서로 오염되지 않아야 한다.
$first = $probe->probe('POST', '/api/admin/roles', ['identifier' => 'probe_a_'.uniqid(), 'name' => 'a', 'is_active' => true]);
$second = $probe->probe('POST', '/api/admin/roles', ['identifier' => 'probe_b_'.uniqid(), 'name' => 'b', 'is_active' => true]);
// 두 번째 실측이 첫 번째의 인증 컨텍스트 오염으로 401 이 되지 않아야 한다.
$this->assertNotSame(401, $first['status']);
$this->assertNotSame(401, $second['status']);
$probe->cleanup();
}
#[Test]
public function unresolved_path_는_실측하지_않고_사유를_남긴다(): void
{
[$probe] = $this->preparedProbe();
$result = $probe->probe('PUT', '/api/admin/roles/{role}', ['name' => 'x']);
$this->assertFalse($result['ok']);
$this->assertSame('unresolved-path-param', $result['skipped_reason']);
$probe->cleanup();
}
}