Files
sam-manage/docs/API_FLOW_TESTER_DESIGN.md
hskwon 76c8a94e4f docs: MNG 프로젝트 문서 정비
- 개발 단계별 문서 추가 (00_OVERVIEW ~ 06_PHASE)
- 기술 표준 문서 추가 (99_TECHNICAL_STANDARDS)
- 개발 프로세스 및 패턴 문서 추가
  - API_FLOW_TESTER_DESIGN, DEV_PROCESS
  - HTMX_API_PATTERN, LAYOUT_PATTERN
  - SETUP_GUIDE, MNG_PROJECT_PLAN
- 프로젝트 관리 문서 추가 (project-management/)
- INDEX.md, MNG_CRITICAL_RULES.md 업데이트
2025-11-30 21:04:19 +09:00

49 KiB
Raw Blame History

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: sam.kr/api/v1)                     │
└─────────────────────────────────────────────────────────────────┘

2.2 컴포넌트 설명

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

3. 데이터베이스 스키마

3.1 admin_api_flows 테이블

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 테이블

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 상태값 정의

// 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 플로우 정의 구조

{
  "version": "1.0",
  "meta": {
    "author": "사용자명",
    "created": "2025-11-27",
    "tags": ["item-master", "integration-test"]
  },
  "config": {
    "baseUrl": "https://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

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

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

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://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 내부 라우트

// 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 통합

<!-- 플로우 목록 새로고침 -->
<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

참고 문서


문서 끝