 # API Flow Tester 설계문서

> **Version**: 1.0
> **Date**: 2025-11-27
> **Author**: Claude Code
> **Status**: Draft

---

## 1. 개요

### 1.1 목적
API Flow Tester는 MNG 관리자 패널에서 복수의 API를 순차적으로 실행하고,
이전 응답 데이터를 다음 요청에 바인딩하여 통합 API 플로우를 테스트하는 도구입니다.

### 1.2 핵심 기능 (B 버전)
- **플로우 관리**: 생성, 조회, 수정, 삭제 (CRUD)
- **JSON 에디터**: 구문 강조 기능이 있는 플로우/데이터 편집기
- **변수 바인딩**: `{{stepN.response.path}}` 형식으로 이전 응답 참조
- **순차 실행**: 의존성 기반 순서대로 API 호출
- **결과 추적**: 단계별 성공/실패 표시 및 실행 결과 저장

### 1.3 범위 제외 (B 버전)
- 시스템 내 AI 통합 (사용자가 외부에서 Claude로 JSON 생성)
- 자동 플로우 생성
- AI 기반 오류 분석

---

## 2. 시스템 아키텍처

### 2.1 전체 구조

```
┌─────────────────────────────────────────────────────────────────┐
│                        MNG Application                          │
├─────────────────────────────────────────────────────────────────┤
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────┐  │
│  │   Flow List  │  │ Flow Editor  │  │   Flow Executor      │  │
│  │    (Index)   │  │   (Create/   │  │   (Run/Monitor)      │  │
│  │              │  │    Edit)     │  │                      │  │
│  └──────┬───────┘  └──────┬───────┘  └──────────┬───────────┘  │
│         │                 │                      │              │
│  ┌──────▼─────────────────▼──────────────────────▼───────────┐  │
│  │                   FlowTesterController                    │  │
│  └───────────────────────────┬───────────────────────────────┘  │
│                              │                                  │
│  ┌───────────────────────────▼───────────────────────────────┐  │
│  │                   FlowTesterService                       │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐   │  │
│  │  │ FlowManager │  │  Executor   │  │ VariableBinder  │   │  │
│  │  └─────────────┘  └─────────────┘  └─────────────────┘   │  │
│  └───────────────────────────┬───────────────────────────────┘  │
│                              │                                  │
│  ┌───────────────────────────▼───────────────────────────────┐  │
│  │                    Database (MySQL - samdb)               │  │
│  │  ┌──────────────────┐  ┌────────────────────────────┐    │  │
│  │  │ admin_api_flows  │  │   admin_api_flow_runs      │    │  │
│  │  └──────────────────┘  └────────────────────────────┘    │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                      External API Server                        │
│                    (SAM API: api.sam.kr/api/v1)                     │
└─────────────────────────────────────────────────────────────────┘
```

### 2.2 컴포넌트 설명

| 컴포넌트 | 역할 |
|---------|------|
| Flow List | 등록된 플로우 목록 조회, 검색, 필터링 |
| Flow Editor | JSON 기반 플로우 정의 생성/수정, 구문 검증 |
| Flow Executor | 플로우 실행, 실시간 진행상황 표시 |
| FlowTesterService | 비즈니스 로직 처리 (CRUD, 실행, 바인딩) |
| VariableBinder | `{{...}}` 변수 파싱 및 치환 |

---

## 3. 데이터베이스 스키마

### 3.1 admin_api_flows 테이블

```sql
CREATE TABLE admin_api_flows (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    name            VARCHAR(100) NOT NULL,           -- 플로우 이름
    description     TEXT,                            -- 설명
    category        VARCHAR(50),                     -- 카테고리 (선택)
    flow_definition JSON NOT NULL,                   -- 플로우 정의 (JSON)
    is_active       BOOLEAN DEFAULT 1,               -- 활성화 여부
    created_by      INTEGER,                         -- 생성자
    updated_by      INTEGER,                         -- 수정자
    created_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 인덱스
CREATE INDEX idx_admin_api_flows_category ON admin_api_flows(category);
CREATE INDEX idx_admin_api_flows_is_active ON admin_api_flows(is_active);
CREATE INDEX idx_admin_api_flows_name ON admin_api_flows(name);
```

### 3.2 admin_api_flow_runs 테이블

```sql
CREATE TABLE admin_api_flow_runs (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    flow_id         INTEGER NOT NULL,                -- 플로우 ID
    status          VARCHAR(20) DEFAULT 'PENDING',   -- PENDING, RUNNING, SUCCESS, FAILED, PARTIAL
    started_at      TIMESTAMP,                       -- 시작 시간
    completed_at    TIMESTAMP,                       -- 완료 시간
    duration_ms     INTEGER,                         -- 실행 시간 (ms)
    total_steps     INTEGER,                         -- 총 단계 수
    completed_steps INTEGER DEFAULT 0,               -- 완료된 단계 수
    failed_step     INTEGER,                         -- 실패한 단계 (있는 경우)
    execution_log   JSON,                            -- 실행 로그 (단계별 결과)
    input_variables JSON,                            -- 입력 변수 (실행 시 제공)
    error_message   TEXT,                            -- 에러 메시지
    executed_by     INTEGER,                         -- 실행자
    created_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    FOREIGN KEY (flow_id) REFERENCES admin_api_flows(id) ON DELETE CASCADE
);

-- 인덱스
CREATE INDEX idx_admin_api_flow_runs_flow_id ON admin_api_flow_runs(flow_id);
CREATE INDEX idx_admin_api_flow_runs_status ON admin_api_flow_runs(status);
CREATE INDEX idx_admin_api_flow_runs_created_at ON admin_api_flow_runs(created_at);
```

### 3.3 상태값 정의

```php
// FlowRun Status
const STATUS_PENDING  = 'PENDING';   // 대기 중
const STATUS_RUNNING  = 'RUNNING';   // 실행 중
const STATUS_SUCCESS  = 'SUCCESS';   // 모든 단계 성공
const STATUS_FAILED   = 'FAILED';    // 단계 실패로 중단
const STATUS_PARTIAL  = 'PARTIAL';   // 일부 성공 (선택적 단계 실패)
```

---

## 4. JSON 플로우 정의 스키마

### 4.1 플로우 정의 구조

```json
{
  "version": "1.0",
  "meta": {
    "author": "사용자명",
    "created": "2025-11-27",
    "tags": ["item-master", "integration-test"]
  },
  "config": {
    "baseUrl": "https://api.sam.kr/api/v1",
    "timeout": 30000,
    "stopOnFailure": true,
    "headers": {
      "Accept": "application/json",
      "Content-Type": "application/json"
    }
  },
  "variables": {
    "testPrefix": "TEST_",
    "timestamp": "{{$timestamp}}"
  },
  "steps": [
    {
      "id": "step1",
      "name": "페이지 생성",
      "description": "새 아이템 페이지 생성",
      "method": "POST",
      "endpoint": "/item-master/pages",
      "headers": {},
      "body": {
        "page_name": "{{variables.testPrefix}}Page_{{variables.timestamp}}",
        "item_type": "PRODUCT",
        "absolute_path": "/test/page"
      },
      "expect": {
        "status": [200, 201],
        "jsonPath": {
          "$.success": true,
          "$.data.id": "@isNumber"
        }
      },
      "extract": {
        "pageId": "$.data.id",
        "pageName": "$.data.page_name"
      },
      "continueOnFailure": false,
      "retries": 0,
      "delay": 0
    },
    {
      "id": "step2",
      "name": "섹션 생성",
      "description": "페이지에 섹션 추가",
      "dependsOn": ["step1"],
      "method": "POST",
      "endpoint": "/item-master/pages/{{step1.pageId}}/sections",
      "body": {
        "title": "기본 정보",
        "type": "form"
      },
      "expect": {
        "status": [200, 201]
      },
      "extract": {
        "sectionId": "$.data.id"
      }
    },
    {
      "id": "step3",
      "name": "필드 생성",
      "description": "섹션에 필드 추가",
      "dependsOn": ["step2"],
      "method": "POST",
      "endpoint": "/item-master/sections/{{step2.sectionId}}/fields",
      "body": {
        "field_name": "제품명",
        "field_type": "text",
        "is_required": true
      },
      "expect": {
        "status": [200, 201]
      },
      "extract": {
        "fieldId": "$.data.id"
      }
    },
    {
      "id": "cleanup",
      "name": "테스트 데이터 정리",
      "description": "생성된 페이지 삭제",
      "dependsOn": ["step3"],
      "method": "DELETE",
      "endpoint": "/item-master/pages/{{step1.pageId}}",
      "expect": {
        "status": [200, 204]
      },
      "continueOnFailure": true
    }
  ]
}
```

### 4.2 스키마 상세 설명

#### 4.2.1 Step 속성

| 속성 | 타입 | 필수 | 설명 |
|------|------|------|------|
| id | string | O | 고유 식별자 (변수 참조용) |
| name | string | O | 단계 이름 (UI 표시용) |
| description | string | X | 상세 설명 |
| dependsOn | string[] | X | 의존하는 이전 단계 ID 목록 |
| method | string | O | HTTP 메서드 (GET, POST, PUT, PATCH, DELETE) |
| endpoint | string | O | API 엔드포인트 (변수 치환 가능) |
| headers | object | X | 추가 헤더 |
| body | object | X | 요청 바디 (변수 치환 가능) |
| expect | object | X | 기대 응답 검증 |
| extract | object | X | 응답에서 추출할 변수 |
| continueOnFailure | boolean | X | 실패 시 계속 진행 여부 (기본: false) |
| retries | number | X | 재시도 횟수 (기본: 0) |
| delay | number | X | 실행 전 지연 시간 (ms) |

#### 4.2.2 Expect 검증 옵션

| 속성 | 타입 | 설명 |
|------|------|------|
| status | number[] | 허용되는 HTTP 상태 코드 |
| jsonPath | object | JSONPath 기반 값 검증 |

**JSONPath 검증 연산자**:
- 직접 값: `"$.success": true`
- 타입 체크: `"$.data.id": "@isNumber"`
- 존재 체크: `"$.data.items": "@exists"`
- 배열 길이: `"$.data.items": "@minLength:1"`
- 정규식: `"$.data.code": "@regex:^[A-Z]+$"`

#### 4.2.3 변수 바인딩 문법

| 패턴 | 설명 | 예시 |
|------|------|------|
| `{{variables.xxx}}` | 전역 변수 | `{{variables.testPrefix}}` |
| `{{stepN.xxx}}` | 이전 단계 추출값 | `{{step1.pageId}}` |
| `{{stepN.response.xxx}}` | 이전 단계 전체 응답 | `{{step1.response.data.name}}` |
| `{{$timestamp}}` | 현재 타임스탬프 | `1701100800` |
| `{{$uuid}}` | 랜덤 UUID | `550e8400-e29b-41d4-a716-446655440000` |
| `{{$random:N}}` | N자리 랜덤 숫자 | `{{$random:6}}` → `482916` |

---

## 5. 변수 바인딩 엔진

### 5.1 처리 흐름

```
┌─────────────────────────────────────────────────────────────┐
│                    Variable Binding Engine                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Input: "POST /pages/{{step1.pageId}}/sections"            │
│                        │                                    │
│                        ▼                                    │
│  ┌─────────────────────────────────────────────┐           │
│  │  1. Pattern Detection                        │           │
│  │     /\{\{([^}]+)\}\}/g                       │           │
│  │     Found: ["{{step1.pageId}}"]              │           │
│  └─────────────────────────────────────────────┘           │
│                        │                                    │
│                        ▼                                    │
│  ┌─────────────────────────────────────────────┐           │
│  │  2. Reference Resolution                     │           │
│  │     "step1.pageId" → context.steps.step1    │           │
│  │                    → extracted.pageId = 42   │           │
│  └─────────────────────────────────────────────┘           │
│                        │                                    │
│                        ▼                                    │
│  ┌─────────────────────────────────────────────┐           │
│  │  3. Value Substitution                       │           │
│  │     "{{step1.pageId}}" → "42"               │           │
│  └─────────────────────────────────────────────┘           │
│                        │                                    │
│                        ▼                                    │
│  Output: "POST /pages/42/sections"                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### 5.2 구현 클래스

```php
<?php

namespace App\Services\FlowTester;

class VariableBinder
{
    private array $context = [];

    /**
     * 컨텍스트에 변수 추가
     */
    public function setVariable(string $key, mixed $value): void
    {
        data_set($this->context, $key, $value);
    }

    /**
     * 단계 결과 저장
     */
    public function setStepResult(string $stepId, array $extracted, array $fullResponse): void
    {
        $this->context['steps'][$stepId] = [
            'extracted' => $extracted,
            'response' => $fullResponse,
        ];
    }

    /**
     * 문자열 내 모든 변수 치환
     */
    public function bind(mixed $input): mixed
    {
        if (is_string($input)) {
            return $this->bindString($input);
        }

        if (is_array($input)) {
            return array_map(fn($v) => $this->bind($v), $input);
        }

        return $input;
    }

    /**
     * 문자열 내 변수 패턴 치환
     */
    private function bindString(string $input): string
    {
        // 내장 변수 처리
        $input = $this->resolveBuiltins($input);

        // 컨텍스트 변수 처리
        return preg_replace_callback(
            '/\{\{([^}]+)\}\}/',
            fn($matches) => $this->resolveReference($matches[1]),
            $input
        );
    }

    /**
     * 내장 변수 처리 ($timestamp, $uuid, $random:N)
     */
    private function resolveBuiltins(string $input): string
    {
        $input = str_replace('{{$timestamp}}', time(), $input);
        $input = str_replace('{{$uuid}}', (string) \Illuminate\Support\Str::uuid(), $input);
        $input = preg_replace_callback(
            '/\{\{\$random:(\d+)\}\}/',
            fn($m) => str_pad(random_int(0, pow(10, $m[1]) - 1), $m[1], '0', STR_PAD_LEFT),
            $input
        );

        return $input;
    }

    /**
     * 참조 경로 해석 (step1.pageId → 실제 값)
     */
    private function resolveReference(string $path): mixed
    {
        // variables.xxx → $this->context['variables']['xxx']
        if (str_starts_with($path, 'variables.')) {
            return data_get($this->context, $path, '');
        }

        // stepN.xxx → $this->context['steps']['stepN']['extracted']['xxx']
        if (preg_match('/^(step\w+)\.(.+)$/', $path, $m)) {
            $stepId = $m[1];
            $subPath = $m[2];

            // stepN.response.xxx → 전체 응답에서 추출
            if (str_starts_with($subPath, 'response.')) {
                $responsePath = substr($subPath, 9);
                return data_get($this->context['steps'][$stepId]['response'] ?? [], $responsePath, '');
            }

            // stepN.xxx → extracted에서 추출
            return data_get($this->context['steps'][$stepId]['extracted'] ?? [], $subPath, '');
        }

        return data_get($this->context, $path, '');
    }
}
```

---

## 6. 플로우 실행 엔진

### 6.1 실행 흐름

```
┌──────────────────────────────────────────────────────────────────┐
│                      Flow Execution Engine                        │
├──────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐          │
│  │ Load Flow   │───▶│ Build Order │───▶│ Init Binder │          │
│  │ Definition  │    │ (TopSort)   │    │ + Variables │          │
│  └─────────────┘    └─────────────┘    └─────────────┘          │
│                                               │                   │
│                                               ▼                   │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                    For Each Step (Ordered)                  │  │
│  │  ┌─────────────────────────────────────────────────────┐   │  │
│  │  │  1. Check Dependencies (all completed?)              │   │  │
│  │  │  2. Apply Delay (if configured)                      │   │  │
│  │  │  3. Bind Variables (endpoint, headers, body)         │   │  │
│  │  │  4. Execute HTTP Request                             │   │  │
│  │  │  5. Validate Response (expect)                       │   │  │
│  │  │  6. Extract Variables (extract)                      │   │  │
│  │  │  7. Store Result in Context                          │   │  │
│  │  │  8. Update Progress                                  │   │  │
│  │  └─────────────────────────────────────────────────────┘   │  │
│  │                            │                                │  │
│  │              ┌─────────────┴─────────────┐                 │  │
│  │              ▼                           ▼                 │  │
│  │        ┌──────────┐               ┌──────────┐             │  │
│  │        │ Success  │               │  Failed  │             │  │
│  │        │ → Next   │               │ → Check  │             │  │
│  │        │   Step   │               │   Config │             │  │
│  │        └──────────┘               └────┬─────┘             │  │
│  │                                        │                    │  │
│  │                        ┌───────────────┴───────────────┐   │  │
│  │                        ▼                               ▼   │  │
│  │                 ┌────────────┐                 ┌──────────┐│  │
│  │                 │ Continue   │                 │   Stop   ││  │
│  │                 │ On Failure │                 │ Execution││  │
│  │                 └────────────┘                 └──────────┘│  │
│  └────────────────────────────────────────────────────────────┘  │
│                                                                   │
│  ┌─────────────┐                                                 │
│  │ Save Run    │                                                 │
│  │ Results     │                                                 │
│  └─────────────┘                                                 │
│                                                                   │
└──────────────────────────────────────────────────────────────────┘
```

### 6.2 의존성 정렬 (Topological Sort)

```php
<?php

namespace App\Services\FlowTester;

class DependencyResolver
{
    /**
     * 의존성 기반 실행 순서 결정
     *
     * @param array $steps 스텝 정의 배열
     * @return array 정렬된 스텝 ID 배열
     * @throws \Exception 순환 의존성 발견 시
     */
    public function resolve(array $steps): array
    {
        $graph = [];
        $inDegree = [];

        // 그래프 초기화
        foreach ($steps as $step) {
            $id = $step['id'];
            $graph[$id] = [];
            $inDegree[$id] = 0;
        }

        // 간선 추가 (의존성)
        foreach ($steps as $step) {
            $id = $step['id'];
            $deps = $step['dependsOn'] ?? [];

            foreach ($deps as $dep) {
                if (!isset($graph[$dep])) {
                    throw new \Exception("Unknown dependency: {$dep} in step {$id}");
                }
                $graph[$dep][] = $id;
                $inDegree[$id]++;
            }
        }

        // Kahn's Algorithm
        $queue = [];
        foreach ($inDegree as $id => $degree) {
            if ($degree === 0) {
                $queue[] = $id;
            }
        }

        $sorted = [];
        while (!empty($queue)) {
            $current = array_shift($queue);
            $sorted[] = $current;

            foreach ($graph[$current] as $neighbor) {
                $inDegree[$neighbor]--;
                if ($inDegree[$neighbor] === 0) {
                    $queue[] = $neighbor;
                }
            }
        }

        if (count($sorted) !== count($steps)) {
            throw new \Exception("Circular dependency detected in flow");
        }

        return $sorted;
    }
}
```

### 6.3 응답 검증기

```php
<?php

namespace App\Services\FlowTester;

class ResponseValidator
{
    /**
     * 응답 검증
     *
     * @param array $response HTTP 응답 [status, headers, body]
     * @param array $expect 기대값 정의
     * @return array [success, errors]
     */
    public function validate(array $response, array $expect): array
    {
        $errors = [];

        // 상태 코드 검증
        if (isset($expect['status'])) {
            $allowedStatus = (array) $expect['status'];
            if (!in_array($response['status'], $allowedStatus)) {
                $errors[] = "Expected status {$this->formatList($allowedStatus)}, got {$response['status']}";
            }
        }

        // JSONPath 검증
        if (isset($expect['jsonPath'])) {
            foreach ($expect['jsonPath'] as $path => $expected) {
                $actual = data_get($response['body'], ltrim($path, '$.'));
                $pathError = $this->validateValue($actual, $expected, $path);
                if ($pathError) {
                    $errors[] = $pathError;
                }
            }
        }

        return [
            'success' => empty($errors),
            'errors' => $errors,
        ];
    }

    /**
     * 개별 값 검증
     */
    private function validateValue(mixed $actual, mixed $expected, string $path): ?string
    {
        // 직접 값 비교
        if (!is_string($expected) || !str_starts_with($expected, '@')) {
            if ($actual !== $expected) {
                return "Path {$path}: expected " . json_encode($expected) . ", got " . json_encode($actual);
            }
            return null;
        }

        // 연산자 처리
        $operator = substr($expected, 1);

        return match (true) {
            $operator === 'exists' => $actual === null ? "Path {$path}: expected to exist" : null,
            $operator === 'isNumber' => !is_numeric($actual) ? "Path {$path}: expected number, got " . gettype($actual) : null,
            $operator === 'isString' => !is_string($actual) ? "Path {$path}: expected string, got " . gettype($actual) : null,
            $operator === 'isArray' => !is_array($actual) ? "Path {$path}: expected array, got " . gettype($actual) : null,
            $operator === 'isBoolean' => !is_bool($actual) ? "Path {$path}: expected boolean, got " . gettype($actual) : null,
            str_starts_with($operator, 'minLength:') => $this->validateMinLength($actual, $operator, $path),
            str_starts_with($operator, 'regex:') => $this->validateRegex($actual, $operator, $path),
            default => "Path {$path}: unknown operator @{$operator}",
        };
    }

    private function validateMinLength(mixed $actual, string $operator, string $path): ?string
    {
        $min = (int) substr($operator, 10);
        if (!is_array($actual) || count($actual) < $min) {
            return "Path {$path}: expected array with min length {$min}";
        }
        return null;
    }

    private function validateRegex(mixed $actual, string $operator, string $path): ?string
    {
        $pattern = '/' . substr($operator, 6) . '/';
        if (!is_string($actual) || !preg_match($pattern, $actual)) {
            return "Path {$path}: value does not match pattern {$pattern}";
        }
        return null;
    }

    private function formatList(array $items): string
    {
        return '[' . implode(', ', $items) . ']';
    }
}
```

---

## 7. UI 설계

### 7.1 화면 구성

```
┌─────────────────────────────────────────────────────────────────────────┐
│  SAM MNG                                              admin@sam.kr  ▼   │
├─────────────┬───────────────────────────────────────────────────────────┤
│             │                                                           │
│  대시보드   │   API Flow Tester                                         │
│             │  ─────────────────────────────────────────────────────── │
│  시스템관리 │                                                           │
│  ├ 사용자   │   ┌─────────────────────────────────────────────────────┐ │
│  ├ 역할     │   │  플로우 목록                        [+ 새 플로우]   │ │
│  └ 권한     │   ├─────────────────────────────────────────────────────┤ │
│             │   │ □ 이름           카테고리    상태     최근실행  액션 │ │
│  개발 도구  │   │ ─────────────────────────────────────────────────── │ │
│  ├ API 플로우│   │ □ ItemMaster     item-master 성공     5분 전   ▶ ✎ │ │
│     테스터  │   │ □ Auth Flow      auth        실패     1시간전  ▶ ✎ │ │
│             │   │ □ BOM Test       bom         대기     -        ▶ ✎ │ │
│             │   │                                                     │ │
│             │   └─────────────────────────────────────────────────────┘ │
│             │                                                           │
└─────────────┴───────────────────────────────────────────────────────────┘
```

### 7.2 플로우 편집기

```
┌─────────────────────────────────────────────────────────────────────────┐
│  플로우 편집: ItemMaster Integration Test                    [저장] [×] │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  기본 정보                                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 이름:     [ItemMaster Integration Test        ]                 │   │
│  │ 카테고리: [item-master ▼]   설명: [페이지-섹션-필드 통합 테스트]│   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  플로우 정의 (JSON)                                    [검증] [포맷]   │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  1  {                                                           │   │
│  │  2    "version": "1.0",                                         │   │
│  │  3    "config": {                                               │   │
│  │  4      "baseUrl": "https://api.sam.kr/api/v1",                    │   │
│  │  5      "timeout": 30000                                        │   │
│  │  6    },                                                        │   │
│  │  7    "steps": [                                                │   │
│  │  8      {                                                       │   │
│  │  9        "id": "step1",                                        │   │
│  │ 10        "name": "페이지 생성",                                │   │
│  │ 11        "method": "POST",                                     │   │
│  │ ...       ...                                                   │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ✓ JSON 문법 유효  │  스텝 4개  │  의존성 그래프 유효                  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

### 7.3 플로우 실행 화면

```
┌─────────────────────────────────────────────────────────────────────────┐
│  플로우 실행: ItemMaster Integration Test                        [×]   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  실행 상태                                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  ████████████████░░░░░░░░  3/4 단계 완료                       │   │
│  │  경과 시간: 2.4s                                                │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  단계별 결과                                                            │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  ✅ step1: 페이지 생성                          201 Created    │   │
│  │     └─ pageId: 42, pageName: "TEST_Page_1701100800"            │   │
│  │                                                                 │   │
│  │  ✅ step2: 섹션 생성                            201 Created    │   │
│  │     └─ sectionId: 108                                          │   │
│  │                                                                 │   │
│  │  ✅ step3: 필드 생성                            201 Created    │   │
│  │     └─ fieldId: 256                                            │   │
│  │                                                                 │   │
│  │  🔄 cleanup: 테스트 데이터 정리                  실행 중...     │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  상세 로그  ───────────────────────────────────────────                │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ [14:32:01] step1 시작                                          │   │
│  │ [14:32:01] POST /item-master/pages                             │   │
│  │ [14:32:01] Response: 201 Created (234ms)                       │   │
│  │ [14:32:01] Extracted: pageId=42, pageName=TEST_Page_1701100800 │   │
│  │ [14:32:01] step2 시작                                          │   │
│  │ ...                                                            │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│                                              [실행 중지] [다시 실행]   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

### 7.4 실행 이력 화면

```
┌─────────────────────────────────────────────────────────────────────────┐
│  실행 이력: ItemMaster Integration Test                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 실행 ID    상태      시작 시간           소요시간    실행자      │   │
│  │ ─────────────────────────────────────────────────────────────── │   │
│  │ #127      ✅ 성공    2025-11-27 14:32:01  2.8s      admin       │   │
│  │ #126      ❌ 실패    2025-11-27 14:28:15  1.2s      admin       │   │
│  │ #125      ✅ 성공    2025-11-27 13:45:00  2.6s      admin       │   │
│  │ #124      ⚠️ 부분    2025-11-27 11:20:33  3.1s      admin       │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  실행 #126 상세 ────────────────────────────────                       │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 실패 단계: step2 (섹션 생성)                                    │   │
│  │ 에러 메시지: Expected status [200, 201], got 422               │   │
│  │                                                                 │   │
│  │ 응답 내용:                                                      │   │
│  │ {                                                               │   │
│  │   "success": false,                                             │   │
│  │   "message": "validation_error",                                │   │
│  │   "errors": { "type": ["type 필드는 필수입니다."] }            │   │
│  │ }                                                               │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## 8. API 엔드포인트

### 8.1 MNG 내부 라우트

```php
// routes/web.php

Route::prefix('dev-tools')->name('dev-tools.')->middleware(['auth'])->group(function () {
    // 플로우 목록
    Route::get('/flow-tester', [FlowTesterController::class, 'index'])
        ->name('flow-tester.index');

    // 플로우 생성 폼
    Route::get('/flow-tester/create', [FlowTesterController::class, 'create'])
        ->name('flow-tester.create');

    // 플로우 저장
    Route::post('/flow-tester', [FlowTesterController::class, 'store'])
        ->name('flow-tester.store');

    // 플로우 상세/편집
    Route::get('/flow-tester/{id}', [FlowTesterController::class, 'edit'])
        ->name('flow-tester.edit');

    // 플로우 수정
    Route::put('/flow-tester/{id}', [FlowTesterController::class, 'update'])
        ->name('flow-tester.update');

    // 플로우 삭제
    Route::delete('/flow-tester/{id}', [FlowTesterController::class, 'destroy'])
        ->name('flow-tester.destroy');

    // 플로우 복제
    Route::post('/flow-tester/{id}/clone', [FlowTesterController::class, 'clone'])
        ->name('flow-tester.clone');

    // JSON 검증 (HTMX)
    Route::post('/flow-tester/validate-json', [FlowTesterController::class, 'validateJson'])
        ->name('flow-tester.validate-json');

    // 플로우 실행
    Route::post('/flow-tester/{id}/run', [FlowTesterController::class, 'run'])
        ->name('flow-tester.run');

    // 실행 상태 조회 (Polling/SSE)
    Route::get('/flow-tester/runs/{runId}/status', [FlowTesterController::class, 'runStatus'])
        ->name('flow-tester.run-status');

    // 실행 이력
    Route::get('/flow-tester/{id}/history', [FlowTesterController::class, 'history'])
        ->name('flow-tester.history');

    // 실행 상세
    Route::get('/flow-tester/runs/{runId}', [FlowTesterController::class, 'runDetail'])
        ->name('flow-tester.run-detail');
});
```

### 8.2 HTMX 통합

```html
<!-- 플로우 목록 새로고침 -->
<div hx-get="{{ route('dev-tools.flow-tester.index') }}"
     hx-trigger="load, flowUpdated from:body"
     hx-target="#flow-list">
</div>

<!-- JSON 실시간 검증 -->
<textarea name="flow_definition"
          hx-post="{{ route('dev-tools.flow-tester.validate-json') }}"
          hx-trigger="keyup changed delay:500ms"
          hx-target="#validation-result">
</textarea>

<!-- 실행 상태 폴링 -->
<div id="run-status"
     hx-get="{{ route('dev-tools.flow-tester.run-status', $runId) }}"
     hx-trigger="every 1s"
     hx-swap="innerHTML">
</div>
```

---

## 9. 파일 구조

```
mng/
├── app/
│   ├── Http/
│   │   └── Controllers/
│   │       └── FlowTesterController.php
│   ├── Models/
│   │   ├── AdminApiFlow.php
│   │   └── AdminApiFlowRun.php
│   └── Services/
│       └── FlowTester/
│           ├── FlowTesterService.php      # 메인 서비스
│           ├── FlowExecutor.php           # 실행 엔진
│           ├── VariableBinder.php         # 변수 바인딩
│           ├── DependencyResolver.php     # 의존성 정렬
│           ├── ResponseValidator.php      # 응답 검증
│           └── HttpClient.php             # HTTP 클라이언트 래퍼
├── database/
│   └── migrations/
│       └── xxxx_create_admin_api_flow_tables.php
├── resources/
│   └── views/
│       └── dev-tools/
│           └── flow-tester/
│               ├── index.blade.php        # 플로우 목록
│               ├── create.blade.php       # 생성 폼
│               ├── edit.blade.php         # 편집 폼
│               ├── run.blade.php          # 실행 화면
│               ├── history.blade.php      # 실행 이력
│               └── partials/
│                   ├── flow-list.blade.php
│                   ├── step-result.blade.php
│                   └── run-status.blade.php
└── public/
    └── js/
        └── flow-tester/
            ├── json-editor.js             # JSON 에디터 기능
            └── flow-runner.js             # 실행 UI 제어
```

---

## 10. 구현 일정 (4-5일)

### Day 1: 기반 구축
- [ ] 데이터베이스 마이그레이션 생성 및 실행
- [ ] Model 클래스 생성 (AdminApiFlow, AdminApiFlowRun)
- [ ] 사이드바 메뉴 추가 ("개발 도구" 그룹)
- [ ] 기본 라우트 설정
- [ ] FlowTesterController 스캐폴딩

### Day 2: 핵심 서비스
- [ ] VariableBinder 구현 (변수 바인딩 엔진)
- [ ] DependencyResolver 구현 (의존성 정렬)
- [ ] ResponseValidator 구현 (응답 검증)
- [ ] HttpClient 구현 (API 호출 래퍼)
- [ ] FlowExecutor 구현 (실행 엔진)

### Day 3: CRUD UI
- [ ] 플로우 목록 화면 (index.blade.php)
- [ ] 플로우 생성/편집 화면 (create.blade.php, edit.blade.php)
- [ ] JSON 에디터 통합 (CodeMirror 또는 Monaco)
- [ ] JSON 실시간 검증 (HTMX)
- [ ] 플로우 삭제/복제 기능

### Day 4: 실행 및 모니터링
- [ ] 플로우 실행 화면 (run.blade.php)
- [ ] 실시간 진행상황 표시 (Polling/SSE)
- [ ] 단계별 결과 표시
- [ ] 실행 이력 화면 (history.blade.php)
- [ ] 실행 상세 보기

### Day 5: 마무리 및 테스트
- [ ] 에러 핸들링 강화
- [ ] UI 폴리싱
- [ ] 테스트 플로우 작성 (ItemMaster 예제)
- [ ] 문서화
- [ ] 버그 수정

---

## 11. 기술 스택

| 영역 | 기술 |
|------|------|
| Backend | Laravel 12 + PHP 8.4 |
| Database | MySQL (samdb - admin_* 접두사 테이블) |
| Frontend | Blade + Tailwind CSS + DaisyUI |
| Interactivity | HTMX 1.9 |
| JSON Editor | CodeMirror 6 또는 Monaco Editor (lite) |
| HTTP Client | Guzzle 또는 Laravel HTTP Client |

---

## 12. 보안 고려사항

### 12.1 접근 제어
- MNG 관리자 인증 필수
- 특정 권한 보유자만 접근 가능 (설정 가능)

### 12.2 API 호출 보안
- 저장된 API 키 사용 (환경변수에서 로드)
- 외부 URL 호출 제한 (화이트리스트)
- 민감 데이터 마스킹 (로그에서 Authorization 헤더 등)

### 12.3 데이터 보호
- 실행 로그 보존 기간 설정
- 민감 정보 저장 금지 (실제 비밀번호 등)

---

## 13. 확장 가능성 (향후)

### Phase 2 (선택적)
- 스케줄 기반 자동 실행
- Slack/Teams 알림 연동
- 플로우 템플릿 공유
- 환경별 설정 (dev/staging/prod)

### Phase 3 (AI 통합 - A 버전)
- Claude API 연동으로 플로우 자동 생성
- 에러 분석 및 수정 제안
- 테스트 데이터 자동 생성

---

## 14. 참고 자료

### 유사 도구
- Postman Collections & Runner
- Insomnia Request Chaining
- Bruno Sequential Requests
- k6 Load Testing Scripts

### 참고 문서
- [HTMX 공식 문서](https://htmx.org/docs/)
- [Laravel HTTP Client](https://laravel.com/docs/12.x/http-client)
- [JSONPath 표준](https://goessner.net/articles/JsonPath/)

---

**문서 끝**
