코드에만 존재하던 REST API 계약(약 663 엔드포인트)을 코어/확장 책임별로
분리된 마크다운 레퍼런스로 전면 문서화한다. 674개 규모에서 수기 문서는
반드시 drift 하므로 "코드 추출 → 스캐폴딩 → 사람이 서술 채움 → 하네스가
커버리지 강제" 하이브리드로 구성했다.
추출 파이프라인 (app/Support/ApiDoc):
- ApiRouteInventory / FormRequestIntrospector / ApiEndpointProbe(실측 HTTP) /
ResponseSchemaInferrer / ApiDocScaffolder / ColumnCommentResolver /
ResourceFieldDescriber / ParameterDescriber
- api:docgen 커맨드(--scope/--seed/--check/--dry-run/--base-url/--user) +
응답/파라미터 in-place 백필 커맨드 2종(재생성 없이 TODO 셀만 치환, 멱등)
- ApiDocSampleSeeder 계약 + 코어/확장 시더로 실측용 완전 샘플 멱등 생성
문서화 (실측 기반, GET read-only 실호출):
- 코어 291엔드포인트 35파일(docs/backend/api) + 규정 docs/backend/api-documentation.md
- 확장: ecommerce(231)·board(80)·page(17)·hello_module(2)·pay_kginicis(22)·
gdpr(15)·ckeditor5(2)·marketing(2)·verification_kginicis(1)
- 표준 4구성(헤더·요청 파라미터·응답 필드·에러 표) + 엔드포인트 용도 서술
- 파라미터 용도·응답 필드 설명 셀 전수 채움(도메인 지식 수기)
하네스:
- audit 룰 api-doc-coverage — API 표면(라우트/컨트롤러/FormRequest/Resource)
변경 시 대응 문서 미동반이면 차단. 전 대상 문서 완비로 error 승격
- file-rules 리마인더(컨트롤러/라우트 편집 시), coverage.json, dev-dashboard 카드
- docs/backend/routing.md 확장 공개 API URL 스킴 정정(/api/modules|plugins/{id})
- /AGENTS.md/docs-index 동기
121 lines
4.4 KiB
PHP
121 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace Modules\Sirsoft\Ecommerce\Tests\Unit\Support;
|
|
|
|
// ModuleTestCase를 수동으로 require (autoload 전에 로드 필요)
|
|
require_once __DIR__.'/../../ModuleTestCase.php';
|
|
|
|
use App\Contracts\ApiDoc\ApiDocSampleSeeder;
|
|
use App\Models\User;
|
|
use Modules\Sirsoft\Ecommerce\Models\Category;
|
|
use Modules\Sirsoft\Ecommerce\Models\Product;
|
|
use Modules\Sirsoft\Ecommerce\Support\ApiDoc\ApiDocSampleService;
|
|
use Modules\Sirsoft\Ecommerce\Tests\ModuleTestCase;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
|
|
/**
|
|
* sirsoft-ecommerce API 문서 실측용 완전 샘플 시더 테스트.
|
|
*
|
|
* 이커머스 도메인 대표 샘플(상품·주문·카테고리·리뷰 등)이 생성되고, 문자열 path
|
|
* 파라미터(product_code·slug·order_number) 실측을 위한 도메인별 path_params 맵이
|
|
* 반환되며, 재실행 시 멱등한지 검증한다.
|
|
*/
|
|
class ApiDocSampleServiceTest extends ModuleTestCase
|
|
{
|
|
/**
|
|
* @var string 샘플 상품 product_code 마커
|
|
*/
|
|
private const SAMPLE_PRODUCT_CODE = 'APIDOCSAMPLE01';
|
|
|
|
/**
|
|
* @var string 샘플 카테고리 slug 마커
|
|
*/
|
|
private const SAMPLE_CATEGORY_SLUG = 'apidoc-sample-category';
|
|
|
|
#[Test]
|
|
public function 시더는_계약을_구현한다(): void
|
|
{
|
|
$this->assertInstanceOf(ApiDocSampleSeeder::class, new ApiDocSampleService);
|
|
}
|
|
|
|
#[Test]
|
|
public function 이커머스_핵심_도메인_대표_샘플_맵을_반환한다(): void
|
|
{
|
|
// 회원 소유 도메인(orders/reviews/inquiries/addresses/mileage)은 실측 사용자를
|
|
// 전제한다 — 실측 커맨드 컨텍스트(코어 완전 샘플 사용자)를 재현하기 위해 먼저 생성.
|
|
User::factory()->create();
|
|
|
|
$map = (new ApiDocSampleService)->seed();
|
|
|
|
// 핵심 상세 GET 대상 도메인 키가 모두 존재
|
|
foreach (['products', 'categories', 'orders', 'reviews', 'settings'] as $domain) {
|
|
$this->assertArrayHasKey($domain, $map, "도메인 '{$domain}' 누락");
|
|
}
|
|
|
|
$this->assertSame(Product::class, $map['products']['model']);
|
|
$this->assertNotEmpty($map['products']['value']);
|
|
}
|
|
|
|
#[Test]
|
|
public function 문자열_path_param_실측용_맵을_제공한다(): void
|
|
{
|
|
$map = (new ApiDocSampleService)->seed();
|
|
|
|
// products: 공개 show(id)·admin show(identifier/code)·상품 하위(productId)
|
|
$product = Product::query()->where('product_code', self::SAMPLE_PRODUCT_CODE)->firstOrFail();
|
|
$params = $map['products']['path_params'];
|
|
|
|
$this->assertSame((string) $product->getKey(), $params['id']);
|
|
$this->assertSame((string) $product->getKey(), $params['identifier']);
|
|
$this->assertSame(self::SAMPLE_PRODUCT_CODE, $params['code']);
|
|
$this->assertSame((string) $product->getKey(), $params['productId']);
|
|
|
|
// categories: 공개 show 는 slug
|
|
$this->assertSame(self::SAMPLE_CATEGORY_SLUG, $map['categories']['path_params']['slug']);
|
|
|
|
// settings: 레코드 없이 유효 카테고리 문자열 고정
|
|
$this->assertSame('basic_info', $map['settings']['path_params']['category']);
|
|
}
|
|
|
|
#[Test]
|
|
public function 공개_상세_실측용_상품은_전시_상태다(): void
|
|
{
|
|
(new ApiDocSampleService)->seed();
|
|
|
|
$product = Product::query()->where('product_code', self::SAMPLE_PRODUCT_CODE)->firstOrFail();
|
|
|
|
// products/{id} 공개 show 는 display_status===visible 필터 → 실측 가능해야 함
|
|
$this->assertSame('visible', $product->display_status->value);
|
|
$this->assertSame('on_sale', $product->sales_status->value);
|
|
}
|
|
|
|
#[Test]
|
|
public function 공개_slug_조회용_카테고리는_활성이며_slug를_갖는다(): void
|
|
{
|
|
(new ApiDocSampleService)->seed();
|
|
|
|
$category = Category::query()->where('slug', self::SAMPLE_CATEGORY_SLUG)->firstOrFail();
|
|
|
|
$this->assertTrue((bool) $category->is_active);
|
|
$this->assertSame(self::SAMPLE_CATEGORY_SLUG, $category->slug);
|
|
}
|
|
|
|
#[Test]
|
|
public function 재실행_시_샘플이_중복_생성되지_않는다(): void
|
|
{
|
|
$service = new ApiDocSampleService;
|
|
|
|
$service->seed();
|
|
$service->seed();
|
|
|
|
$this->assertSame(
|
|
1,
|
|
Product::query()->where('product_code', self::SAMPLE_PRODUCT_CODE)->count()
|
|
);
|
|
$this->assertSame(
|
|
1,
|
|
Category::query()->where('slug', self::SAMPLE_CATEGORY_SLUG)->count()
|
|
);
|
|
}
|
|
}
|