450 lines
19 KiB
PHP
450 lines
19 KiB
PHP
|
|
{{-- 예제 플로우 모달 --}}
|
||
|
|
<div id="examples-modal"
|
||
|
|
class="hidden fixed inset-0 z-50 overflow-y-auto"
|
||
|
|
aria-labelledby="examples-modal-title"
|
||
|
|
role="dialog"
|
||
|
|
aria-modal="true">
|
||
|
|
|
||
|
|
{{-- 배경 오버레이 --}}
|
||
|
|
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
||
|
|
onclick="ExamplesModal.close()"></div>
|
||
|
|
|
||
|
|
{{-- 모달 컨테이너 --}}
|
||
|
|
<div class="flex min-h-full items-start justify-center p-4 pt-8">
|
||
|
|
<div class="relative bg-white rounded-lg shadow-xl w-full max-w-5xl max-h-[90vh] flex flex-col">
|
||
|
|
|
||
|
|
{{-- 모달 헤더 --}}
|
||
|
|
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-200 flex-shrink-0">
|
||
|
|
<h2 id="examples-modal-title" class="text-xl font-semibold text-gray-800">
|
||
|
|
예제 플로우
|
||
|
|
</h2>
|
||
|
|
<button type="button"
|
||
|
|
onclick="ExamplesModal.close()"
|
||
|
|
class="text-gray-400 hover:text-gray-600 transition-colors">
|
||
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||
|
|
</svg>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{{-- 예제 선택 탭 --}}
|
||
|
|
<div class="px-6 py-3 border-b border-gray-200 flex-shrink-0">
|
||
|
|
<div class="flex gap-2 flex-wrap">
|
||
|
|
<button type="button" class="example-tab active px-4 py-2 text-sm font-medium rounded-lg transition-colors" data-example="item-master-full">
|
||
|
|
Item Master (전체)
|
||
|
|
</button>
|
||
|
|
<button type="button" class="example-tab px-4 py-2 text-sm font-medium rounded-lg transition-colors" data-example="item-master-simple">
|
||
|
|
Item Master (기본)
|
||
|
|
</button>
|
||
|
|
<button type="button" class="example-tab px-4 py-2 text-sm font-medium rounded-lg transition-colors" data-example="auth-flow">
|
||
|
|
인증 플로우
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{{-- 모달 콘텐츠 --}}
|
||
|
|
<div class="flex-1 overflow-y-auto p-6">
|
||
|
|
|
||
|
|
{{-- Item Master 전체 플로우 --}}
|
||
|
|
<div id="example-item-master-full" class="example-content">
|
||
|
|
<div class="mb-4">
|
||
|
|
<h3 class="text-lg font-semibold text-gray-800">Item Master 전체 테스트 (18 스텝)</h3>
|
||
|
|
<p class="text-sm text-gray-600 mt-1">페이지, 섹션, 필드의 CRUD + 연결/해제 전체 시나리오</p>
|
||
|
|
</div>
|
||
|
|
<div class="relative">
|
||
|
|
<button onclick="ExamplesModal.copy('item-master-full')"
|
||
|
|
class="absolute top-2 right-2 px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-white rounded transition-colors z-10">
|
||
|
|
복사
|
||
|
|
</button>
|
||
|
|
<pre id="json-item-master-full" class="bg-gray-800 text-gray-100 p-4 rounded-lg text-xs overflow-x-auto max-h-[60vh]"></pre>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{{-- Item Master 기본 플로우 --}}
|
||
|
|
<div id="example-item-master-simple" class="example-content hidden">
|
||
|
|
<div class="mb-4">
|
||
|
|
<h3 class="text-lg font-semibold text-gray-800">Item Master 기본 테스트 (3 스텝)</h3>
|
||
|
|
<p class="text-sm text-gray-600 mt-1">페이지 생성 → 조회 → 삭제 기본 CRUD</p>
|
||
|
|
</div>
|
||
|
|
<div class="relative">
|
||
|
|
<button onclick="ExamplesModal.copy('item-master-simple')"
|
||
|
|
class="absolute top-2 right-2 px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-white rounded transition-colors z-10">
|
||
|
|
복사
|
||
|
|
</button>
|
||
|
|
<pre id="json-item-master-simple" class="bg-gray-800 text-gray-100 p-4 rounded-lg text-xs overflow-x-auto max-h-[60vh]"></pre>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{{-- 인증 플로우 --}}
|
||
|
|
<div id="example-auth-flow" class="example-content hidden">
|
||
|
|
<div class="mb-4">
|
||
|
|
<h3 class="text-lg font-semibold text-gray-800">인증 플로우 테스트 (4 스텝)</h3>
|
||
|
|
<p class="text-sm text-gray-600 mt-1">로그인 → 프로필 조회 → 토큰 갱신 → 로그아웃</p>
|
||
|
|
</div>
|
||
|
|
<div class="relative">
|
||
|
|
<button onclick="ExamplesModal.copy('auth-flow')"
|
||
|
|
class="absolute top-2 right-2 px-3 py-1 text-xs bg-gray-700 hover:bg-gray-600 text-white rounded transition-colors z-10">
|
||
|
|
복사
|
||
|
|
</button>
|
||
|
|
<pre id="json-auth-flow" class="bg-gray-800 text-gray-100 p-4 rounded-lg text-xs overflow-x-auto max-h-[60vh]"></pre>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{{-- 모달 푸터 --}}
|
||
|
|
<div class="flex justify-end px-6 py-4 border-t border-gray-200 flex-shrink-0">
|
||
|
|
<button type="button"
|
||
|
|
onclick="ExamplesModal.close()"
|
||
|
|
class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors">
|
||
|
|
닫기
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<style>
|
||
|
|
.example-tab {
|
||
|
|
background-color: #f3f4f6;
|
||
|
|
color: #374151;
|
||
|
|
}
|
||
|
|
.example-tab:hover {
|
||
|
|
background-color: #e5e7eb;
|
||
|
|
}
|
||
|
|
.example-tab.active {
|
||
|
|
background-color: #7c3aed;
|
||
|
|
color: white;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
const ExampleFlows = {
|
||
|
|
'item-master-full': {
|
||
|
|
"version": "1.0",
|
||
|
|
"meta": {
|
||
|
|
"author": "SAM Admin",
|
||
|
|
"description": "품목관리 API 통합 테스트 - CRUD 및 연결/해제 검증",
|
||
|
|
"tags": ["item-master", "integration", "full-test"]
|
||
|
|
},
|
||
|
|
"config": {
|
||
|
|
"baseUrl": "https://sam.kr/api/v1",
|
||
|
|
"timeout": 30000,
|
||
|
|
"stopOnFailure": false,
|
||
|
|
"headers": {
|
||
|
|
"Accept": "application/json",
|
||
|
|
"Content-Type": "application/json"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"variables": {
|
||
|
|
"testPrefix": "TEST_",
|
||
|
|
"timestamp": "{{$timestamp}}"
|
||
|
|
},
|
||
|
|
"steps": [
|
||
|
|
{
|
||
|
|
"id": "page_create_1",
|
||
|
|
"name": "[페이지] 1차 생성",
|
||
|
|
"description": "테스트 페이지 최초 생성",
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/item-master/pages",
|
||
|
|
"body": {
|
||
|
|
"page_name": "{{variables.testPrefix}}Page_{{variables.timestamp}}",
|
||
|
|
"item_type": "PRODUCT",
|
||
|
|
"description": "테스트용 페이지 (삭제 예정)"
|
||
|
|
},
|
||
|
|
"expect": {
|
||
|
|
"status": [200, 201],
|
||
|
|
"jsonPath": { "$.success": true, "$.data.id": "@isNumber" }
|
||
|
|
},
|
||
|
|
"extract": { "tempPageId": "$.data.id" }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "page_delete_1",
|
||
|
|
"name": "[페이지] 1차 삭제",
|
||
|
|
"dependsOn": ["page_create_1"],
|
||
|
|
"method": "DELETE",
|
||
|
|
"endpoint": "/item-master/pages/{{page_create_1.tempPageId}}",
|
||
|
|
"expect": { "status": [200, 204] }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "page_create_2",
|
||
|
|
"name": "[페이지] 2차 생성 (유지)",
|
||
|
|
"dependsOn": ["page_delete_1"],
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/item-master/pages",
|
||
|
|
"body": {
|
||
|
|
"page_name": "{{variables.testPrefix}}MainPage_{{variables.timestamp}}",
|
||
|
|
"item_type": "PRODUCT",
|
||
|
|
"description": "통합 테스트 메인 페이지"
|
||
|
|
},
|
||
|
|
"expect": { "status": [200, 201] },
|
||
|
|
"extract": { "mainPageId": "$.data.id", "mainPageName": "$.data.page_name" }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "section_create_1",
|
||
|
|
"name": "[섹션] 독립 1차 생성",
|
||
|
|
"dependsOn": ["page_create_2"],
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/item-master/sections",
|
||
|
|
"body": { "title": "{{variables.testPrefix}}Section_Temp", "type": "form" },
|
||
|
|
"expect": { "status": [200, 201] },
|
||
|
|
"extract": { "tempSectionId": "$.data.id" }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "section_delete_1",
|
||
|
|
"name": "[섹션] 1차 삭제",
|
||
|
|
"dependsOn": ["section_create_1"],
|
||
|
|
"method": "DELETE",
|
||
|
|
"endpoint": "/item-master/sections/{{section_create_1.tempSectionId}}",
|
||
|
|
"expect": { "status": [200, 204] }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "section_create_2",
|
||
|
|
"name": "[섹션] 독립 2차 생성 (유지)",
|
||
|
|
"dependsOn": ["section_delete_1"],
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/item-master/sections",
|
||
|
|
"body": { "title": "{{variables.testPrefix}}MainSection_{{variables.timestamp}}", "type": "form" },
|
||
|
|
"expect": { "status": [200, 201] },
|
||
|
|
"extract": { "mainSectionId": "$.data.id" }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "field_create_1",
|
||
|
|
"name": "[필드] 독립 1차 생성",
|
||
|
|
"dependsOn": ["section_create_2"],
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/item-master/fields",
|
||
|
|
"body": { "field_name": "{{variables.testPrefix}}Field_Temp", "field_type": "text", "is_required": false },
|
||
|
|
"expect": { "status": [200, 201] },
|
||
|
|
"extract": { "tempFieldId": "$.data.id" }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "field_delete_1",
|
||
|
|
"name": "[필드] 1차 삭제",
|
||
|
|
"dependsOn": ["field_create_1"],
|
||
|
|
"method": "DELETE",
|
||
|
|
"endpoint": "/item-master/fields/{{field_create_1.tempFieldId}}",
|
||
|
|
"expect": { "status": [200, 204] }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "field_create_2",
|
||
|
|
"name": "[필드] 독립 2차 생성 (유지)",
|
||
|
|
"dependsOn": ["field_delete_1"],
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/item-master/fields",
|
||
|
|
"body": { "field_name": "{{variables.testPrefix}}MainField_{{variables.timestamp}}", "field_type": "text", "is_required": true },
|
||
|
|
"expect": { "status": [200, 201] },
|
||
|
|
"extract": { "mainFieldId": "$.data.id" }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "page_link_section_1",
|
||
|
|
"name": "[연결] 페이지에 섹션 연결",
|
||
|
|
"dependsOn": ["field_create_2"],
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/item-master/pages/{{page_create_2.mainPageId}}/link-section",
|
||
|
|
"body": { "section_id": "{{section_create_2.mainSectionId}}", "sort_order": 1 },
|
||
|
|
"expect": { "status": [200, 201] }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "page_unlink_section",
|
||
|
|
"name": "[연결] 페이지-섹션 연결 해제",
|
||
|
|
"dependsOn": ["page_link_section_1"],
|
||
|
|
"method": "DELETE",
|
||
|
|
"endpoint": "/item-master/pages/{{page_create_2.mainPageId}}/unlink-section/{{section_create_2.mainSectionId}}",
|
||
|
|
"expect": { "status": [200, 204] }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "page_link_section_2",
|
||
|
|
"name": "[연결] 페이지에 섹션 재연결",
|
||
|
|
"dependsOn": ["page_unlink_section"],
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/item-master/pages/{{page_create_2.mainPageId}}/link-section",
|
||
|
|
"body": { "section_id": "{{section_create_2.mainSectionId}}", "sort_order": 1 },
|
||
|
|
"expect": { "status": [200, 201] }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "section_link_field_1",
|
||
|
|
"name": "[연결] 섹션에 필드 연결",
|
||
|
|
"dependsOn": ["page_link_section_2"],
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/item-master/sections/{{section_create_2.mainSectionId}}/link-field",
|
||
|
|
"body": { "field_id": "{{field_create_2.mainFieldId}}", "sort_order": 1 },
|
||
|
|
"expect": { "status": [200, 201] }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "section_unlink_field",
|
||
|
|
"name": "[연결] 섹션-필드 연결 해제",
|
||
|
|
"dependsOn": ["section_link_field_1"],
|
||
|
|
"method": "DELETE",
|
||
|
|
"endpoint": "/item-master/sections/{{section_create_2.mainSectionId}}/unlink-field/{{field_create_2.mainFieldId}}",
|
||
|
|
"expect": { "status": [200, 204] }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cleanup_field",
|
||
|
|
"name": "[정리] 필드 삭제",
|
||
|
|
"dependsOn": ["section_unlink_field"],
|
||
|
|
"method": "DELETE",
|
||
|
|
"endpoint": "/item-master/fields/{{field_create_2.mainFieldId}}",
|
||
|
|
"expect": { "status": [200, 204] },
|
||
|
|
"continueOnFailure": true
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cleanup_section",
|
||
|
|
"name": "[정리] 섹션 삭제",
|
||
|
|
"dependsOn": ["cleanup_field"],
|
||
|
|
"method": "DELETE",
|
||
|
|
"endpoint": "/item-master/sections/{{section_create_2.mainSectionId}}",
|
||
|
|
"expect": { "status": [200, 204] },
|
||
|
|
"continueOnFailure": true
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cleanup_page",
|
||
|
|
"name": "[정리] 페이지 삭제",
|
||
|
|
"dependsOn": ["cleanup_section"],
|
||
|
|
"method": "DELETE",
|
||
|
|
"endpoint": "/item-master/pages/{{page_create_2.mainPageId}}",
|
||
|
|
"expect": { "status": [200, 204] },
|
||
|
|
"continueOnFailure": true
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
'item-master-simple': {
|
||
|
|
"version": "1.0",
|
||
|
|
"config": {
|
||
|
|
"baseUrl": "https://sam.kr/api/v1",
|
||
|
|
"timeout": 30000,
|
||
|
|
"stopOnFailure": true
|
||
|
|
},
|
||
|
|
"variables": {
|
||
|
|
"testPrefix": "SIMPLE_"
|
||
|
|
},
|
||
|
|
"steps": [
|
||
|
|
{
|
||
|
|
"id": "create_page",
|
||
|
|
"name": "페이지 생성",
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/item-master/pages",
|
||
|
|
"body": { "page_name": "{{variables.testPrefix}}Page_{{$timestamp}}", "item_type": "PRODUCT" },
|
||
|
|
"expect": { "status": [200, 201] },
|
||
|
|
"extract": { "pageId": "$.data.id" }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "get_page",
|
||
|
|
"name": "페이지 조회",
|
||
|
|
"dependsOn": ["create_page"],
|
||
|
|
"method": "GET",
|
||
|
|
"endpoint": "/item-master/pages",
|
||
|
|
"expect": { "status": [200], "jsonPath": { "$.success": true } }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "delete_page",
|
||
|
|
"name": "페이지 삭제",
|
||
|
|
"dependsOn": ["get_page"],
|
||
|
|
"method": "DELETE",
|
||
|
|
"endpoint": "/item-master/pages/{{create_page.pageId}}",
|
||
|
|
"expect": { "status": [200, 204] }
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
'auth-flow': {
|
||
|
|
"version": "1.0",
|
||
|
|
"config": {
|
||
|
|
"baseUrl": "https://sam.kr/api/v1",
|
||
|
|
"timeout": 30000,
|
||
|
|
"stopOnFailure": true
|
||
|
|
},
|
||
|
|
"variables": {
|
||
|
|
"email": "test@example.com",
|
||
|
|
"password": "password123"
|
||
|
|
},
|
||
|
|
"steps": [
|
||
|
|
{
|
||
|
|
"id": "login",
|
||
|
|
"name": "로그인",
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/auth/login",
|
||
|
|
"body": { "email": "{{variables.email}}", "password": "{{variables.password}}" },
|
||
|
|
"expect": { "status": [200], "jsonPath": { "$.success": true, "$.data.token": "@isString" } },
|
||
|
|
"extract": { "accessToken": "$.data.token", "refreshToken": "$.data.refresh_token" }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "get_profile",
|
||
|
|
"name": "프로필 조회",
|
||
|
|
"dependsOn": ["login"],
|
||
|
|
"method": "GET",
|
||
|
|
"endpoint": "/auth/me",
|
||
|
|
"headers": { "Authorization": "Bearer {{login.accessToken}}" },
|
||
|
|
"expect": { "status": [200], "jsonPath": { "$.data.email": "{{variables.email}}" } }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "refresh_token",
|
||
|
|
"name": "토큰 갱신",
|
||
|
|
"dependsOn": ["get_profile"],
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/auth/refresh",
|
||
|
|
"body": { "refresh_token": "{{login.refreshToken}}" },
|
||
|
|
"expect": { "status": [200], "jsonPath": { "$.data.token": "@isString" } },
|
||
|
|
"extract": { "newToken": "$.data.token" }
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "logout",
|
||
|
|
"name": "로그아웃",
|
||
|
|
"dependsOn": ["refresh_token"],
|
||
|
|
"method": "POST",
|
||
|
|
"endpoint": "/auth/logout",
|
||
|
|
"headers": { "Authorization": "Bearer {{refresh_token.newToken}}" },
|
||
|
|
"expect": { "status": [200, 204] }
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const ExamplesModal = {
|
||
|
|
open() {
|
||
|
|
document.getElementById('examples-modal').classList.remove('hidden');
|
||
|
|
document.body.style.overflow = 'hidden';
|
||
|
|
this.renderAllExamples();
|
||
|
|
},
|
||
|
|
close() {
|
||
|
|
document.getElementById('examples-modal').classList.add('hidden');
|
||
|
|
document.body.style.overflow = '';
|
||
|
|
},
|
||
|
|
switchExample(exampleId) {
|
||
|
|
document.querySelectorAll('.example-tab').forEach(tab => tab.classList.remove('active'));
|
||
|
|
document.querySelector(`.example-tab[data-example="${exampleId}"]`).classList.add('active');
|
||
|
|
|
||
|
|
document.querySelectorAll('.example-content').forEach(content => content.classList.add('hidden'));
|
||
|
|
document.getElementById(`example-${exampleId}`).classList.remove('hidden');
|
||
|
|
},
|
||
|
|
renderAllExamples() {
|
||
|
|
Object.keys(ExampleFlows).forEach(key => {
|
||
|
|
const element = document.getElementById(`json-${key}`);
|
||
|
|
if (element) {
|
||
|
|
element.textContent = JSON.stringify(ExampleFlows[key], null, 2);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
},
|
||
|
|
copy(exampleId) {
|
||
|
|
const json = JSON.stringify(ExampleFlows[exampleId], null, 2);
|
||
|
|
navigator.clipboard.writeText(json).then(() => {
|
||
|
|
alert('JSON이 클립보드에 복사되었습니다.');
|
||
|
|
}).catch(err => {
|
||
|
|
console.error('복사 실패:', err);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
document.querySelectorAll('.example-tab').forEach(tab => {
|
||
|
|
tab.addEventListener('click', function() {
|
||
|
|
ExamplesModal.switchExample(this.dataset.example);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
document.addEventListener('keydown', function(e) {
|
||
|
|
if (e.key === 'Escape' && !document.getElementById('examples-modal').classList.contains('hidden')) {
|
||
|
|
ExamplesModal.close();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
</script>
|