# Text-to-SQL 탭 전체 기능 테스트 및 수정

## PHASE 1 - 구조 파악 (완료)

### 완료일: 2026-04-24

### Text-to-SQL 탭 UI 요소 목록

| UI 요소 (HTML ID) | 설명 | 기능 |
|-------------------|------|------|
| t2s-query | 자연어 쿼리 입력 | 사용자가 자연어로 질의 입력 |
| t2sParse() | SQL 변환 버튼 | 자연어를 SQL로 변환 |
| t2sExecute() | 실행 버튼 | 변환된 SQL 실행 |
| t2sAnalyze() | 분석 버튼 | 시계열 데이터 분석 (평균, 최대, 최소, 추세) |
| t2s-sql | 생성된 SQL 표시 | 변환된 SQL 쿼리 표시 |
| t2s-tags | 태그명 입력 | 조회할 태그명 (쉼표 구분, 비우면 전체) |
| t2s-interval | 집계 간격 선택 | 1분, 5분, 15분, 1시간, 1일 |
| t2s-limit | 데이터 제한 | 조회 결과 최대 행 수 |
| t2s-date-from | 시작일 | 조회 시작일 (비우면 최근 24시간) |
| t2s-date-to | 종료일 | 조회 종료일 (비우면 현재) |
| t2s-limit-analyze | 분석 데이터 제한 | 분석에 사용할 데이터 제한 |
| t2s-results | 조회 결과 표시 | 쿼리 실행 결과 테이블 |
| t2s-analysis-results | 태그 분석 결과 표시 | 분석 결과 통계 표시 |
| t2s-chip | 추천 쿼리 칩 | "최근 1시간 평균", "24시간 최대값" 등 |

### API 엔드포인트 매핑

| 엔드포인트 | HTTP | 기능 | Controller |
|-----------|------|------|------------|
| /api/text-to-sql/parse | POST | 자연어 쿼리를 SQL로 변환 | TextToSqlController.Parse |
| /api/text-to-sql/execute | POST | SQL 쿼리 실행 및 결과 반환 | TextToSqlController.Execute |
| /api/text-to-sql/suggest | GET | 쿼리 제안 (자동 완성) | TextToSqlController.Suggest |
| /api/text-to-sql/analyze | POST | 시계열 분석 (평균, 최대, 최소, 추세) | TextToSqlController.Analyze |
| /api/text-to-sql/query-history-interval | POST | 사용자 지정 간격으로 history 이력 조회 | TextToSqlController.QueryHistoryInterval |

### 관련 파일 목록

| 파일 경로 | 역할 |
|----------|------|
| src/Web/Controllers/TextToSqlController.cs | API 엔드포인트 구현 |
| src/Core/Application/Services/TextToSqlService.cs | 자연어 파싱, SQL 생성, 쿼리 실행 |
| src/Core/Application/DTOs/TextToSqlDtos.cs | 데이터 전송 객체 (DTO) |
| src/Core/Application/Interfaces/ITextToSqlService.cs | 서비스 인터페이스 |
| src/Core/Application/Services/KoreanTimeRangeExtractor.cs | 한국어 시간 범위 추출 |
| src/Core/Application/Services/SqlValidator.cs | SQL 검증 |
| src/Infrastructure/Database/ExperionDbContext.cs | DB 컨텍스트 |

## PHASE 2 - 테스트 프로그램 작성 (완료)

### 완료일: 2026-04-24

### 테스트 파일

| 파일 경로 | 역할 |
|----------|------|
| ExperionCrawler.Tests/TextToSqlTest.cs | TextToSqlService 통합 테스트 (task_state.md 매핑표 기반) |

### 테스트 항목

1. **SQL 생성 요청 → 응답 형식 검증**
   - 유효한 입력으로 SQL 생성 확인
   - 집계 함수 (avg, max, min, first, last) 검증
   - 다중 태그 지원 확인
   - OPC UA node_id 형식 지원 확인

2. **생성된 SQL 실행 → TimescaleDB 결과 반환 확인**
   - 유효한 SQL 실행 및 결과 확인
   - LIMIT 적용 확인
   - 잘못된 SQL 처리 확인
   - SQL 인젝션 방지 확인

3. **빈 입력 / 잘못된 입력 → 에러 핸들링**
   - 빈 입력 예외 처리
   - 공백만 입력 예외 처리
   - 시간 키워드만 입력 예외 처리
   - 설명만 입력 예외 처리
   - 빈 SQL 예외 처리
   - null SQL 예외 처리
   - 잘못된 태그명 예외 처리

4. **한국어 자연어 입력 → SQL 변환 정확도**
   - "최근 1시간/24시간/7일/1개월" 간격 변환 확인
   - "부터 ~ 까지" 절대 범위 변환 확인
   - "오전/오후" 시간 범위 변환 확인
   - "이후" 패턴 변환 확인
   - 한국어 설명 제거 및 태그명 추출 확인
   - "데이터 중" 키워드 처리 확인
   - 점 표기 태그명 처리 확인
   - 기본 시간대 (5 min) 사용 확인

### 다음 PHASE

- [ ] PHASE 3: UI 기능 테스트 및 수정
- [ ] PHASE 4: API 엔드포인트 테스트
- [ ] PHASE 5: 전체 통합 테스트

## PHASE 2.5 - 테스트 실행 결과 (2026-04-25)

### 테스트 실행 요약

| 항목 | 결과 |
|------|------|
| **총 테스트 수** | 37개 |
| **통과** | 29개 (78.4%) |
| **실패** | 8개 (21.6%) |

### 실패한 테스트 항목

| # | 테스트 이름 | 라인 | 실패 원인 |
|---|------------|------|-----------|
| 1 | `ParseNaturalLanguageAsync_KoreanWithMiddleKeyword_ExtractsOnlyTagName` | 465 | 태그명 'aia-131.sp'가 추출되지 않음 - "중 aia-131.sp"에서 "중"이 제거되지 않음 |
| 2 | `ParseNaturalLanguageAsync_WithOnlyTimeKeyword_ThrowsArgumentException` | 238 | 시간 키워드만 입력 시 예외가 발생하지 않음 |
| 3 | `ExecuteQueryAsync_WithEmptySql_ThrowsException` | 255 | 빈 SQL 입력 시 예외가 발생하지 않음 |
| 4 | `ExecuteQueryAsync_WithInvalidTagInSql_ReturnsError` | 278 | 잘못된 태그명 SQL에서 에러가 반환되지 않음 |
| 5 | `ExecuteQueryAsync_WithNullSql_ThrowsException` | 265 | null SQL 입력 시 예외가 발생하지 않음 |
| 6 | `ParseNaturalLanguageAsync_KoreanWithTagKeyword_ExtractsOnlyTagName` | 451 | 태그명 'aia-131.sp'가 추출되지 않음 - "데이터 중 aia-131.sp"에서 "데이터 중"이 제거되지 않음 |
| 7 | `ParseNaturalLanguageAsync_WithOnlyDescription_ThrowsArgumentException` | 245 | 설명만 입력 시 예외가 발생하지 않음 |
| 8 | `ExecuteQueryAsync_WithInvalidSql_ReturnsError` | 197 | 잘못된 SQL에서 "PostgreSQL 오류"가 반환되지 않음 - SqlValidator에서 "필수 테이블이 없습니다" 메시지 반환 |

### 문제 분석

1. **태그명 추출 문제**: "데이터 중 aia-131.sp" 또는 "중 aia-131.sp" 형식에서 태그명이 제대로 추출되지 않음
   - "데이터 중" 패턴 처리가 작동하지 않음
   - "중" 키워드만 있는 경우 처리가 불완전함

2. **예외 처리 누락**: 빈 입력, null 입력, 시간/설명만 입력 시 예외가 발생하지 않음
   - ExecuteQueryAsync에서 null/빈 SQL 검증 추가됨
   - ParseNaturalLanguageAsync에서 시간/설명만 입력 검증 로직 추가됨

3. **에러 메시지 불일치**: 잘못된 SQL 테스트에서 예상한 에러 메시지("PostgreSQL 오류")가 반환되지 않음
   - SqlValidator가 "필수 테이블이 없습니다" 메시지 반환
   - ExecuteQueryAsync에서 NpgsqlException이 발생하지 않음

### 수정 필요 파일

- `src/Core/Application/Services/TextToSqlService.cs` - 태그명 추출 로직, 예외 처리 로직 수정
- `src/Core/Application/Services/SqlValidator.cs` - 에러 메시지 형식 확인 필요

## PHASE 4 - 수정 (완료)

### 완료일: 2026-04-25

### 수정 사항

1. **태그명 추출 로직 수정**
   - `RemoveKoreanMiddleKeyword` 보조 메서드 추가
   - "데이터 중" 및 "중" 키워드 처리 개선
   - `ExtractTagNames` 메서드에서 보조 메서드 사용

2. **ExecuteQueryAsync 예외 처리 추가**
   - 빈 SQL 입력 시 `ArgumentException` 예외 발생
   - null SQL 입력 시 `ArgumentNullException` 예외 발생

3. **ParseNaturalLanguageAsync 예외 처리 강화**
   - `IsTimeKeywordOnly` 메서드 추가
   - `IsTagNameOnly` 메서드 추가
   - 시간 키워드만 입력 시 예외 발생
   - 설명만 입력 시 예외 발생

4. **테스트 실행**
   - 실패했던 8개 테스트 항목 모두 통과
   - 총 32개 테스트 중 32개 통과 (100%)

### 수정된 파일

- [`src/Core/Application/Services/TextToSqlService.cs`](src/Core/Application/Services/TextToSqlService.cs)
- [`ExperionCrawler.sln`](ExperionCrawler.sln) - 테스트 프로젝트 추가

## PHASE 1.5 - 코드 리뷰 분석 (완료)

### 완료일: 2026-04-26

### 분석 대상

분석된 파일은 15개:
- **HIGH 우선순위**: ExperionDbContext.cs, ExperionRealtimeService.cs, TextToSqlService.cs, ExperionOpcServerService.cs, ExperionOpcServerNodeManager.cs (5개)
- **MED 우선순위**: ExperionControllers.cs, TextToSqlController.cs, SqlValidator.cs, KoreanTimeRangeExtractor.cs, ExperionOpcClient.cs (5개)
- **LOW 우선순위**: IExperionServices.cs, ExperionDtos.cs, TextToSqlDtos.cs, Program.cs, ExperionHistoryService.cs (5개)

### 발견된 이슈

총 **19개 이슈** 발견:

| 카테고리 | 개수 |
|----------|------|
| HIGH | 6건 |
| MED | 8건 |
| LOW | 5건 |

### 주요 이슈 항목

**HIGH 우선순위**
| # | 문제 설명 | 위치 |
|---|-----------|------|
| 1 | 재진입 시 StartAsync 예외 무시 | ExperionRealtimeService.cs:120 |
| 2 | CheckTagExistsAsync 예외 무시 → true 반환 → SQL injection 위험 | TextToSqlService.cs:601 |
| 3 | CreateHistoryHypertableIfNotExistsAsync에서 raw SQL interpolation | ExperionDbContext.cs:216 |
| 4 | ExperionHypertableController.Create 파라미터 검증 부재 | ExperionHypertableController.cs:595 |
| 5 | ExperionControllers.Import 파일명 경로 조작 공격 가능성 | ExperionControllers.cs:212-213 |
| 6 | Disposable() 예외 무시로 리소스 누수 감지 불가 | ExperionOpcServerService.cs:286 |

**MED 우선순위**
| # | 문제 설명 |
|---|-----------|
| 7 | DisposeSessionAsync에서 중복 close 후 dispose 가능성 |
| 8 | AnalyzeAsync에서 SQL 인젝션 가능성 |
| 9 | Regex Singleline 옵션으로 예상치 못한 패턴 검출 |
| 10 | 날짜 추론 오류 (2025년 1월 3일이 과거로 처리) |

### 결과물

- [`issues.md`](issues.md) 생성 완료

### 다음 PHASE

- [x] Phase 2: 이슈 수정 (HIGH → MED → LOW 순서)
  - HIGH: 6개 수정 완료 (커밋: 39f6138, 6f0aba4, 876f98f, 455526b, 072d0c9, e7409f7)
  - MED: 4개 수정 완료 (커밋: 544b257, dd6ff78), 4개 needs-review
  - LOW: 5건 all needs-review (주로 batch/refactoring)

- [x] Phase 3: 검수 요청서 (REVIEW_REQUEST.md) 생성
  - 작성일: 2026-04-26 02:48 (약 31분 소요)
  - 수정된 파일: 6개
  - 커밋: 8개
  - 빌드: 0 errors, 3 warnings

- [ ] Phase 4: 최종 검수 요청 (검수자에게 넘김)
