feat: Flow Tester 기능 개선 및 안정화

- FlowTesterController: 테스트 실행 로직 개선
  - 에러 핸들링 강화
  - 응답 형식 표준화
- FlowExecutor: API 호출 실행기 개선
  - 다단계 플로우 지원 강화
  - 변수 바인딩 및 검증 개선
- index.blade.php: UI 개선
  - 테스트 결과 표시 개선
  - 사용성 향상
- routes/web.php: 라우트 정리
- composer.lock: 의존성 업데이트

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-21 01:35:54 +09:00
parent fd50a6dba0
commit aa1fd76a99
5 changed files with 288 additions and 191 deletions

View File

@@ -342,73 +342,115 @@ public function runDetail(int $runId): View
*/
/**
* 현재 테넌트의 사용자 목록
* API 서버에 직접 로그인하여 토큰 발급
*
* MNG에서 토큰을 발급하면 API 서버에서 인식하지 못하므로,
* API 서버에 직접 로그인하여 토큰을 발급받습니다.
*/
public function users()
public function loginToApi(Request $request)
{
// 현재 선택된 테넌트 ID (세션 기반)
$tenantId = session('selected_tenant_id');
$validated = $request->validate([
'user_id' => 'required|string',
'user_pwd' => 'required|string',
]);
if (! $tenantId) {
// 세션에 없으면 기본 테넌트 사용
$currentTenant = auth()->user()->currentTenant();
$tenantId = $currentTenant?->id;
try {
// API Base URL 결정
$baseUrl = $this->getApiBaseUrl();
// Docker 환경에서는 내부 URL로 변환
$requestUrl = $baseUrl.'/login';
$headers = ['Accept' => 'application/json', 'Content-Type' => 'application/json'];
if ($this->isDockerEnvironment()) {
$parsedUrl = parse_url($baseUrl);
$host = $parsedUrl['host'] ?? '';
// *.sam.kr 도메인을 nginx 컨테이너로 라우팅
if (str_ends_with($host, '.sam.kr') || $host === 'sam.kr') {
$headers['Host'] = $host;
$requestUrl = preg_replace('#https?://[^/]+#', 'https://nginx', $baseUrl).'/login';
}
}
// API 서버에 로그인 요청
$response = \Illuminate\Support\Facades\Http::withHeaders($headers)
->withoutVerifying() // Docker 내부 SSL 인증서 무시
->timeout(10)
->post($requestUrl, [
'user_id' => $validated['user_id'],
'user_pwd' => $validated['user_pwd'],
]);
if (! $response->successful()) {
$body = $response->json();
return response()->json([
'success' => false,
'message' => $body['message'] ?? '로그인 실패: '.$response->status(),
], 401);
}
$body = $response->json();
$token = $body['access_token'] ?? $body['data']['token'] ?? null;
if (! $token) {
return response()->json([
'success' => false,
'message' => '토큰을 받지 못했습니다. 응답: '.json_encode($body, JSON_UNESCAPED_UNICODE),
], 500);
}
// 세션에 저장 (API Explorer와 공유)
session([
'api_explorer_token' => $token,
'api_explorer_user_id' => $body['user']['id'] ?? null,
'api_explorer_user_name' => $body['user']['name'] ?? $validated['user_id'],
]);
return response()->json([
'success' => true,
'message' => 'API 서버 로그인 성공!',
'user' => $body['user'] ?? ['name' => $validated['user_id']],
'token_preview' => substr($token, 0, 20).'...',
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'API 서버 연결 실패: '.$e->getMessage(),
], 500);
}
if (! $tenantId) {
return response()->json([]);
}
// user_tenants 피벗 테이블을 통해 해당 테넌트의 사용자 조회
$users = \App\Models\User::whereHas('tenants', function ($query) use ($tenantId) {
$query->where('tenants.id', $tenantId)
->where('user_tenants.is_active', true);
})
->select(['id', 'name', 'email'])
->orderBy('name')
->limit(100)
->get();
return response()->json($users);
}
/**
* 사용자 선택 (Sanctum 토큰 발급)
* API Base URL 결정
*/
public function selectUser(Request $request)
private function getApiBaseUrl(): string
{
$validated = $request->validate([
'user_id' => 'required|integer',
]);
$user = \App\Models\User::find($validated['user_id']);
if (! $user) {
return response()->json([
'success' => false,
'message' => '사용자를 찾을 수 없습니다.',
], 404);
// 환경변수 우선
$envUrl = env('FLOW_TESTER_API_BASE_URL');
if ($envUrl) {
return rtrim($envUrl, '/');
}
// Sanctum 토큰 발급
$token = $user->createToken('flow-tester', ['*'])->plainTextToken;
// config에서 로컬 환경 URL
$environments = config('api-explorer.default_environments', []);
foreach ($environments as $env) {
if ($env['name'] === '로컬') {
return rtrim($env['base_url'], '/');
}
}
// 세션에 저장 (API Explorer와 공유)
session([
'api_explorer_token' => $token,
'api_explorer_user_id' => $user->id,
]);
// 기본값
return 'https://api.sam.kr';
}
return response()->json([
'success' => true,
'message' => "'{$user->name}' 사용자로 인증되었습니다.",
'user' => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
],
'token_preview' => substr($token, 0, 20).'...',
]);
/**
* Docker 환경인지 확인
*/
private function isDockerEnvironment(): bool
{
return file_exists('/.dockerenv') || (getenv('DOCKER_CONTAINER') === 'true');
}
/**