diff --git a/app/Services/FlowTester/VariableBinder.php b/app/Services/FlowTester/VariableBinder.php index 22ef49af..21af9631 100644 --- a/app/Services/FlowTester/VariableBinder.php +++ b/app/Services/FlowTester/VariableBinder.php @@ -2,6 +2,8 @@ namespace App\Services\FlowTester; +use Faker\Factory as FakerFactory; +use Faker\Generator as FakerGenerator; use Illuminate\Support\Str; /** @@ -14,11 +16,14 @@ * - {{$timestamp}} - 현재 타임스탬프 * - {{$uuid}} - 랜덤 UUID * - {{$random:N}} - N자리 랜덤 숫자 + * - {{$faker.xxx}} - Faker 기반 랜덤 데이터 생성 */ class VariableBinder { private array $context = []; + private ?FakerGenerator $faker = null; + /** * 컨텍스트 초기화 */ @@ -136,9 +141,167 @@ function ($m) { // {{$auth.apiKey}} → .env의 API Key $input = str_replace('{{$auth.apiKey}}', env('FLOW_TESTER_API_KEY', ''), $input); + // {{$faker.xxx}} → Faker 기반 랜덤 데이터 생성 + $input = $this->resolveFaker($input); + return $input; } + /** + * Faker 인스턴스 가져오기 (지연 초기화) + */ + private function getFaker(): FakerGenerator + { + if ($this->faker === null) { + $this->faker = FakerFactory::create('ko_KR'); + } + + return $this->faker; + } + + /** + * Faker 변수 처리 + * + * 지원 패턴: + * - {{$faker.name}} - 랜덤 이름 + * - {{$faker.company}} - 랜덤 회사명 + * - {{$faker.email}} - 랜덤 이메일 + * - {{$faker.phone}} - 랜덤 전화번호 + * - {{$faker.word}} - 랜덤 단어 + * - {{$faker.sentence}} - 랜덤 문장 + * - {{$faker.text}} - 랜덤 텍스트 + * - {{$faker.number:MIN:MAX}} - 범위 내 랜덤 정수 + * - {{$faker.price:MIN:MAX}} - 범위 내 랜덤 가격 (소수점 2자리) + * - {{$faker.code:PREFIX:LENGTH}} - 코드 생성 (PREFIX + 숫자) + * - {{$faker.itemCode:PREFIX}} - 품목코드 생성 (PREFIX + 6자리 숫자) + * - {{$faker.address}} - 랜덤 주소 + * - {{$faker.url}} - 랜덤 URL + * - {{$faker.boolean}} - true/false + */ + private function resolveFaker(string $input): string + { + // {{$faker.xxx}} 또는 {{$faker.xxx:param1:param2}} 패턴 처리 + return preg_replace_callback( + '/\{\{\$faker\.([a-zA-Z]+)(?::([^}]*))?\}\}/', + fn ($m) => $this->generateFakerValue($m[1], $m[2] ?? ''), + $input + ); + } + + /** + * Faker 값 생성 + */ + private function generateFakerValue(string $type, string $params): string + { + $faker = $this->getFaker(); + $paramList = $params !== '' ? explode(':', $params) : []; + + return match ($type) { + // 기본 텍스트 + 'name' => $faker->name(), + 'firstName' => $faker->firstName(), + 'lastName' => $faker->lastName(), + 'company' => $faker->company(), + 'email' => $faker->unique()->safeEmail(), + 'phone' => $faker->phoneNumber(), + 'word' => $faker->word(), + 'sentence' => $faker->sentence(), + 'text' => $faker->text(100), + 'paragraph' => $faker->paragraph(), + + // 주소 + 'address' => $faker->address(), + 'city' => $faker->city(), + 'postcode' => $faker->postcode(), + + // 숫자 + 'number' => $this->fakerNumber($faker, $paramList), + 'price' => $this->fakerPrice($faker, $paramList), + 'quantity' => (string) $faker->numberBetween(1, 100), + + // 코드 생성 + 'code' => $this->fakerCode($paramList), + 'itemCode' => $this->fakerItemCode($paramList), + 'sku' => 'SKU-' . strtoupper($faker->bothify('??###')), + 'barcode' => $faker->ean13(), + + // 기타 + 'url' => $faker->url(), + 'boolean' => $faker->boolean() ? 'true' : 'false', + 'uuid' => $faker->uuid(), + 'date' => $faker->date('Y-m-d'), + 'datetime' => $faker->dateTime()->format('Y-m-d H:i:s'), + + // 품목 관련 + 'productName' => $this->fakerProductName($faker), + 'unit' => $faker->randomElement(['EA', 'SET', 'BOX', 'KG', 'M', 'L', 'PCS']), + 'category' => $faker->randomElement(['원자재', '부품', '반제품', '완제품', '소모품']), + + default => "{{faker.{$type}}}", + }; + } + + /** + * 범위 내 랜덤 정수 생성 + */ + private function fakerNumber(FakerGenerator $faker, array $params): string + { + $min = isset($params[0]) ? (int) $params[0] : 1; + $max = isset($params[1]) ? (int) $params[1] : 1000; + + return (string) $faker->numberBetween($min, $max); + } + + /** + * 범위 내 랜덤 가격 생성 (소수점 2자리) + */ + private function fakerPrice(FakerGenerator $faker, array $params): string + { + $min = isset($params[0]) ? (float) $params[0] : 1000; + $max = isset($params[1]) ? (float) $params[1] : 100000; + + return number_format($faker->randomFloat(2, $min, $max), 2, '.', ''); + } + + /** + * 커스텀 코드 생성 (PREFIX + 숫자) + */ + private function fakerCode(array $params): string + { + $prefix = $params[0] ?? 'CODE'; + $length = isset($params[1]) ? (int) $params[1] : 6; + + $digits = str_pad((string) random_int(0, (int) pow(10, $length) - 1), $length, '0', STR_PAD_LEFT); + + return $prefix . $digits; + } + + /** + * 품목코드 생성 (PREFIX + 6자리 숫자) + */ + private function fakerItemCode(array $params): string + { + $prefix = $params[0] ?? 'ITEM'; + $digits = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT); + + return $prefix . '-' . $digits; + } + + /** + * 제품명 생성 (한글) + */ + private function fakerProductName(FakerGenerator $faker): string + { + $prefixes = ['고급', '프리미엄', '스탠다드', '베이직', '프로', '울트라']; + $types = ['부품', '자재', '모듈', '키트', '세트', '패키지']; + $models = ['A', 'B', 'C', 'X', 'Y', 'Z', 'Pro', 'Plus', 'Max']; + + return $faker->randomElement($prefixes) . ' ' + . $faker->randomElement($types) . ' ' + . $faker->randomElement($models) . '-' + . $faker->numberBetween(100, 999); + } + /** * 현재 로그인 사용자의 API 토큰 조회 */ diff --git a/resources/views/dev-tools/flow-tester/partials/example-flows.blade.php b/resources/views/dev-tools/flow-tester/partials/example-flows.blade.php index 5132e035..8d577d26 100644 --- a/resources/views/dev-tools/flow-tester/partials/example-flows.blade.php +++ b/resources/views/dev-tools/flow-tester/partials/example-flows.blade.php @@ -39,6 +39,15 @@ class="text-gray-400 hover:text-gray-600 transition-colors"> + + + @@ -90,6 +99,57 @@ class="absolute top-2 right-2 px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 te + {{-- 품목 CRUD (Faker) --}} + + + {{-- 품목 검색/통계 --}} + + + {{-- 품목 BOM 관리 --}} + + {{-- 모달 푸터 --}} @@ -398,6 +458,349 @@ class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition- "expect": { "status": [200, 204] } } ] + }, + 'items-crud': { + "version": "1.0", + "meta": { + "name": "품목 CRUD 테스트 (Faker)", + "description": "Faker 변수를 활용한 품목 생성/조회/수정/삭제 테스트", + "tags": ["items", "crud", "faker"] + }, + "config": { + "baseUrl": "https://sam.kr/api/v1", + "timeout": 30000, + "stopOnFailure": false, + "headers": { + "Accept": "application/json", + "Content-Type": "application/json" + } + }, + "variables": {}, + "steps": [ + { + "id": "create_item", + "name": "[품목] Faker로 생성", + "description": "Faker 변수를 사용해 랜덤 품목 데이터 생성", + "method": "POST", + "endpoint": "/items", + "body": { + "code": "{{$faker.itemCode:TEST}}", + "name": "{{$faker.productName}}", + "item_type": "PRODUCT", + "unit": "{{$faker.unit}}", + "unit_price": "{{$faker.price:1000:50000}}", + "description": "{{$faker.sentence}}", + "is_active": true + }, + "expect": { + "status": [200, 201], + "jsonPath": { + "$.success": true, + "$.data.code": "@isString", + "$.data.name": "@notEmpty" + } + }, + "extract": { + "itemCode": "$.data.code", + "itemId": "$.data.id", + "itemName": "$.data.name" + } + }, + { + "id": "get_item_by_code", + "name": "[품목] 코드로 조회", + "dependsOn": ["create_item"], + "method": "GET", + "endpoint": "/items/code/{{create_item.itemCode}}", + "expect": { + "status": [200], + "jsonPath": { + "$.success": true, + "$.data.code": "{{create_item.itemCode}}" + } + } + }, + { + "id": "update_item", + "name": "[품목] 수정 (코드 기반)", + "dependsOn": ["get_item_by_code"], + "method": "PUT", + "endpoint": "/items/{{create_item.itemCode}}", + "body": { + "name": "{{$faker.productName}} (수정됨)", + "unit_price": "{{$faker.price:50000:100000}}", + "description": "수정된 설명 - {{$faker.sentence}}" + }, + "expect": { + "status": [200], + "jsonPath": { + "$.success": true + } + } + }, + { + "id": "verify_update", + "name": "[품목] 수정 확인", + "dependsOn": ["update_item"], + "method": "GET", + "endpoint": "/items/code/{{create_item.itemCode}}", + "expect": { + "status": [200], + "jsonPath": { + "$.data.description": "@contains:수정된 설명" + } + } + }, + { + "id": "delete_item", + "name": "[품목] 삭제 (코드 기반)", + "dependsOn": ["verify_update"], + "method": "DELETE", + "endpoint": "/items/{{create_item.itemCode}}", + "expect": { + "status": [200, 204] + } + }, + { + "id": "verify_delete", + "name": "[품목] 삭제 확인", + "dependsOn": ["delete_item"], + "method": "GET", + "endpoint": "/items/code/{{create_item.itemCode}}", + "expect": { + "status": [404] + }, + "continueOnFailure": true + } + ] + }, + 'items-search': { + "version": "1.0", + "meta": { + "name": "품목 검색/통계 테스트", + "description": "품목 목록 조회, 검색, 통계 API 테스트", + "tags": ["items", "search", "stats"] + }, + "config": { + "baseUrl": "https://sam.kr/api/v1", + "timeout": 30000, + "stopOnFailure": false + }, + "variables": { + "searchKeyword": "부품" + }, + "steps": [ + { + "id": "list_items", + "name": "[목록] 전체 품목 조회", + "method": "GET", + "endpoint": "/items", + "expect": { + "status": [200], + "jsonPath": { + "$.success": true, + "$.data": "@isArray" + } + }, + "extract": { + "totalCount": "$.meta.total" + } + }, + { + "id": "list_items_paginated", + "name": "[목록] 페이지네이션", + "dependsOn": ["list_items"], + "method": "GET", + "endpoint": "/items?page=1&per_page=10", + "expect": { + "status": [200], + "jsonPath": { + "$.success": true, + "$.meta.per_page": 10 + } + } + }, + { + "id": "search_by_keyword", + "name": "[검색] 키워드 검색", + "dependsOn": ["list_items_paginated"], + "method": "GET", + "endpoint": "/items/search?keyword={{variables.searchKeyword}}", + "expect": { + "status": [200], + "jsonPath": { + "$.success": true + } + }, + "extract": { + "searchCount": "$.meta.total" + } + }, + { + "id": "search_by_type", + "name": "[검색] 품목유형 필터", + "dependsOn": ["search_by_keyword"], + "method": "GET", + "endpoint": "/items/search?item_type=PRODUCT", + "expect": { + "status": [200], + "jsonPath": { + "$.success": true + } + } + }, + { + "id": "get_stats", + "name": "[통계] 품목 통계 조회", + "dependsOn": ["search_by_type"], + "method": "GET", + "endpoint": "/items/stats", + "expect": { + "status": [200], + "jsonPath": { + "$.success": true + } + }, + "extract": { + "productCount": "$.data.by_type.PRODUCT", + "materialCount": "$.data.by_type.MATERIAL" + } + } + ] + }, + 'items-bom': { + "version": "1.0", + "meta": { + "name": "품목 BOM 관리 테스트", + "description": "BOM(Bill of Materials) 등록/조회/수정/삭제 테스트", + "tags": ["items", "bom", "integration"] + }, + "config": { + "baseUrl": "https://sam.kr/api/v1", + "timeout": 30000, + "stopOnFailure": false + }, + "variables": {}, + "steps": [ + { + "id": "create_parent", + "name": "[부모품목] 완제품 생성", + "method": "POST", + "endpoint": "/items", + "body": { + "code": "{{$faker.itemCode:PROD}}", + "name": "{{$faker.productName}}", + "item_type": "PRODUCT", + "unit": "EA", + "unit_price": "{{$faker.price:100000:500000}}" + }, + "expect": { "status": [200, 201] }, + "extract": { + "parentCode": "$.data.code", + "parentId": "$.data.id" + } + }, + { + "id": "create_child1", + "name": "[자재1] 원자재 생성", + "dependsOn": ["create_parent"], + "method": "POST", + "endpoint": "/items", + "body": { + "code": "{{$faker.itemCode:MAT}}", + "name": "원자재 - {{$faker.word}}", + "item_type": "MATERIAL", + "unit": "{{$faker.unit}}", + "unit_price": "{{$faker.price:1000:10000}}" + }, + "expect": { "status": [200, 201] }, + "extract": { + "child1Code": "$.data.code", + "child1Id": "$.data.id" + } + }, + { + "id": "create_child2", + "name": "[자재2] 부품 생성", + "dependsOn": ["create_child1"], + "method": "POST", + "endpoint": "/items", + "body": { + "code": "{{$faker.itemCode:PART}}", + "name": "부품 - {{$faker.word}}", + "item_type": "MATERIAL", + "unit": "EA", + "unit_price": "{{$faker.price:5000:20000}}" + }, + "expect": { "status": [200, 201] }, + "extract": { + "child2Code": "$.data.code", + "child2Id": "$.data.id" + } + }, + { + "id": "add_bom1", + "name": "[BOM] 원자재 등록", + "dependsOn": ["create_child2"], + "method": "POST", + "endpoint": "/items/{{create_parent.parentCode}}/bom", + "body": { + "child_code": "{{create_child1.child1Code}}", + "quantity": "{{$faker.number:1:10}}", + "unit": "{{$faker.unit}}" + }, + "expect": { "status": [200, 201] } + }, + { + "id": "add_bom2", + "name": "[BOM] 부품 등록", + "dependsOn": ["add_bom1"], + "method": "POST", + "endpoint": "/items/{{create_parent.parentCode}}/bom", + "body": { + "child_code": "{{create_child2.child2Code}}", + "quantity": "{{$faker.number:1:5}}", + "unit": "EA" + }, + "expect": { "status": [200, 201] } + }, + { + "id": "get_bom", + "name": "[BOM] 조회", + "dependsOn": ["add_bom2"], + "method": "GET", + "endpoint": "/items/{{create_parent.parentCode}}/bom", + "expect": { + "status": [200], + "jsonPath": { + "$.success": true, + "$.data": "@isArray", + "$.data": "@minLength:2" + } + } + }, + { + "id": "cleanup_parent", + "name": "[정리] 부모품목 삭제", + "dependsOn": ["get_bom"], + "method": "DELETE", + "endpoint": "/items/{{create_parent.parentCode}}", + "expect": { "status": [200, 204] }, + "continueOnFailure": true + }, + { + "id": "cleanup_children", + "name": "[정리] 자재 삭제", + "dependsOn": ["cleanup_parent"], + "method": "DELETE", + "endpoint": "/items/batch", + "body": { + "codes": ["{{create_child1.child1Code}}", "{{create_child2.child2Code}}"] + }, + "expect": { "status": [200, 204] }, + "continueOnFailure": true + } + ] } }; diff --git a/resources/views/dev-tools/flow-tester/partials/guide-modal.blade.php b/resources/views/dev-tools/flow-tester/partials/guide-modal.blade.php index 47798870..16374440 100644 --- a/resources/views/dev-tools/flow-tester/partials/guide-modal.blade.php +++ b/resources/views/dev-tools/flow-tester/partials/guide-modal.blade.php @@ -166,6 +166,107 @@ class="text-gray-400 hover:text-gray-600 transition-colors"> +

Faker 변수 (랜덤 데이터 생성)

+

Create API 테스트 시 임의의 테스트 데이터를 자동 생성합니다. @{{$faker.타입}} 형식으로 사용합니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
패턴설명예시 결과
텍스트
@{{$faker.name}}랜덤 이름김철수
@{{$faker.company}}랜덤 회사명삼성전자
@{{$faker.email}}랜덤 이메일test@example.com
@{{$faker.phone}}랜덤 전화번호010-1234-5678
@{{$faker.sentence}}랜덤 문장테스트용 설명 문장입니다.
숫자/가격
@{{$faker.number:MIN:MAX}}범위 내 정수@{{$faker.number:1:100}} → 42
@{{$faker.price:MIN:MAX}}범위 내 가격@{{$faker.price:1000:50000}} → 25430.50
@{{$faker.quantity}}수량 (1~100)25
코드/ID 생성
@{{$faker.itemCode:PREFIX}}품목코드 생성@{{$faker.itemCode:TEST}} → TEST-482916
@{{$faker.code:PREFIX:LEN}}커스텀 코드@{{$faker.code:MAT:4}} → MAT0042
@{{$faker.sku}}SKU 코드SKU-AB123
@{{$faker.barcode}}바코드 (EAN13)4006381333931
품목 관련
@{{$faker.productName}}제품명 (한글)프리미엄 부품 Pro-425
@{{$faker.unit}}단위EA, SET, BOX, KG 등
@{{$faker.category}}품목 카테고리원자재, 부품, 반제품 등
+

사용 위치