Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- H/F/I/O SS304/GRAPHITE/CS/CS 패턴에서 4개 구성요소 모두 표시 - 기존 SS304 + GRAPHITE → SS304/GRAPHITE/CS/CS로 완전한 구성 표시 - 외부링/필러/내부링/추가구성 모든 정보 포함 - 구매수량 계산 모달에서 정확한 재질 정보 확인 가능
268 lines
10 KiB
Python
268 lines
10 KiB
Python
"""
|
|
파일 관리 API 테스트
|
|
"""
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
|
|
class TestFileManagementAPI:
|
|
"""파일 관리 API 테스트 클래스"""
|
|
|
|
def test_get_files_empty_list(self, client: TestClient):
|
|
"""빈 파일 목록 조회 테스트"""
|
|
response = client.get("/files")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert data["total_count"] == 0
|
|
assert data["data"] == []
|
|
assert data["cache_hit"] is False
|
|
|
|
def test_get_files_with_job_no(self, client: TestClient):
|
|
"""특정 작업 번호로 파일 조회 테스트"""
|
|
response = client.get("/files?job_no=TEST-001")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert "data" in data
|
|
assert "total_count" in data
|
|
|
|
def test_get_files_with_history(self, client: TestClient):
|
|
"""이력 포함 파일 조회 테스트"""
|
|
response = client.get("/files?show_history=true")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
|
|
@patch('app.api.file_management.tkmp_cache')
|
|
def test_get_files_cache_hit(self, mock_cache, client: TestClient):
|
|
"""캐시 히트 테스트"""
|
|
# 캐시에서 데이터 반환 설정
|
|
mock_cache.get_file_list.return_value = [
|
|
{
|
|
"id": 1,
|
|
"filename": "test.xlsx",
|
|
"original_filename": "test.xlsx",
|
|
"job_no": "TEST-001",
|
|
"status": "active"
|
|
}
|
|
]
|
|
|
|
response = client.get("/files?job_no=TEST-001")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert data["cache_hit"] is True
|
|
assert len(data["data"]) == 1
|
|
|
|
# 캐시 호출 확인
|
|
mock_cache.get_file_list.assert_called_once_with("TEST-001", False)
|
|
|
|
def test_get_files_no_cache(self, client: TestClient):
|
|
"""캐시 사용 안 함 테스트"""
|
|
response = client.get("/files?use_cache=false")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert data["cache_hit"] is False
|
|
|
|
def test_delete_file_not_found(self, client: TestClient):
|
|
"""존재하지 않는 파일 삭제 테스트"""
|
|
response = client.delete("/files/999")
|
|
|
|
# 파일이 없어도 에러 응답이 아닌 정상 응답 구조를 가져야 함
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
# 에러 케이스이므로 error 키가 있을 수 있음
|
|
assert "error" in data or "success" in data
|
|
|
|
@patch('app.api.file_management.tkmp_cache')
|
|
def test_delete_file_cache_invalidation(self, mock_cache, client: TestClient, db_session):
|
|
"""파일 삭제 시 캐시 무효화 테스트"""
|
|
# 실제 파일 데이터가 없으므로 mock으로 처리
|
|
with patch('app.api.file_management.db') as mock_db:
|
|
mock_result = MagicMock()
|
|
mock_result.fetchone.return_value = MagicMock(
|
|
id=1,
|
|
original_filename="test.xlsx",
|
|
job_no="TEST-001"
|
|
)
|
|
mock_db.execute.return_value = mock_result
|
|
|
|
response = client.delete("/files/1")
|
|
|
|
# 캐시 무효화 호출 확인
|
|
mock_cache.invalidate_file_cache.assert_called_once_with(1)
|
|
mock_cache.invalidate_job_cache.assert_called_once_with("TEST-001")
|
|
|
|
|
|
class TestFileValidation:
|
|
"""파일 검증 테스트"""
|
|
|
|
def test_file_extension_validation(self):
|
|
"""파일 확장자 검증 테스트"""
|
|
from app.utils.file_validator import file_validator
|
|
|
|
# 허용된 확장자
|
|
assert file_validator.validate_file_extension("test.xlsx") is True
|
|
assert file_validator.validate_file_extension("test.xls") is True
|
|
assert file_validator.validate_file_extension("test.csv") is True
|
|
|
|
# 허용되지 않은 확장자
|
|
assert file_validator.validate_file_extension("test.txt") is False
|
|
assert file_validator.validate_file_extension("test.pdf") is False
|
|
assert file_validator.validate_file_extension("test.exe") is False
|
|
|
|
def test_file_size_validation(self):
|
|
"""파일 크기 검증 테스트"""
|
|
from app.utils.file_validator import file_validator
|
|
|
|
# 허용된 크기 (50MB 이하)
|
|
assert file_validator.validate_file_size(1024) is True # 1KB
|
|
assert file_validator.validate_file_size(10 * 1024 * 1024) is True # 10MB
|
|
assert file_validator.validate_file_size(50 * 1024 * 1024) is True # 50MB
|
|
|
|
# 허용되지 않은 크기 (50MB 초과)
|
|
assert file_validator.validate_file_size(51 * 1024 * 1024) is False # 51MB
|
|
assert file_validator.validate_file_size(100 * 1024 * 1024) is False # 100MB
|
|
|
|
def test_filename_validation(self):
|
|
"""파일명 검증 테스트"""
|
|
from app.utils.file_validator import file_validator
|
|
|
|
# 안전한 파일명
|
|
assert file_validator.validate_filename("test.xlsx") is True
|
|
assert file_validator.validate_filename("BOM_Rev1.xlsx") is True
|
|
assert file_validator.validate_filename("자재목록_2025.csv") is True
|
|
|
|
# 위험한 파일명
|
|
assert file_validator.validate_filename("../test.xlsx") is False
|
|
assert file_validator.validate_filename("test/file.xlsx") is False
|
|
assert file_validator.validate_filename("test:file.xlsx") is False
|
|
assert file_validator.validate_filename("test*file.xlsx") is False
|
|
|
|
def test_filename_sanitization(self):
|
|
"""파일명 정화 테스트"""
|
|
from app.utils.file_validator import file_validator
|
|
|
|
# 위험한 문자 제거
|
|
assert file_validator.sanitize_filename("../test.xlsx") == "__test.xlsx"
|
|
assert file_validator.sanitize_filename("test/file.xlsx") == "test_file.xlsx"
|
|
assert file_validator.sanitize_filename("test:file*.xlsx") == "test_file_.xlsx"
|
|
|
|
# 연속된 언더스코어 제거
|
|
assert file_validator.sanitize_filename("test__file.xlsx") == "test_file.xlsx"
|
|
|
|
# 앞뒤 공백 및 점 제거
|
|
assert file_validator.sanitize_filename(" test.xlsx ") == "test.xlsx"
|
|
assert file_validator.sanitize_filename(".test.xlsx.") == "test.xlsx"
|
|
|
|
|
|
class TestCacheManager:
|
|
"""캐시 매니저 테스트"""
|
|
|
|
@patch('app.utils.cache_manager.redis')
|
|
def test_cache_set_get(self, mock_redis):
|
|
"""캐시 저장/조회 테스트"""
|
|
from app.utils.cache_manager import CacheManager
|
|
|
|
# Redis 클라이언트 mock 설정
|
|
mock_client = MagicMock()
|
|
mock_redis.from_url.return_value = mock_client
|
|
|
|
cache_manager = CacheManager()
|
|
|
|
# 데이터 저장
|
|
test_data = {"test": "data"}
|
|
result = cache_manager.set("test_key", test_data, 3600)
|
|
|
|
# Redis 호출 확인
|
|
mock_client.setex.assert_called_once()
|
|
|
|
def test_tkmp_cache_file_list(self):
|
|
"""TK-MP 캐시 파일 목록 테스트"""
|
|
from app.utils.cache_manager import TKMPCache
|
|
|
|
with patch('app.utils.cache_manager.CacheManager') as mock_cache_manager:
|
|
mock_cache = MagicMock()
|
|
mock_cache_manager.return_value = mock_cache
|
|
|
|
tkmp_cache = TKMPCache()
|
|
|
|
# 파일 목록 캐시 저장
|
|
files = [{"id": 1, "name": "test.xlsx"}]
|
|
tkmp_cache.set_file_list(files, "TEST-001", False)
|
|
|
|
# 캐시 호출 확인
|
|
mock_cache.set.assert_called_once()
|
|
|
|
# 파일 목록 캐시 조회
|
|
mock_cache.get.return_value = files
|
|
result = tkmp_cache.get_file_list("TEST-001", False)
|
|
|
|
assert result == files
|
|
mock_cache.get.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestFileProcessor:
|
|
"""파일 프로세서 테스트"""
|
|
|
|
def test_file_info_analysis(self, sample_excel_file):
|
|
"""파일 정보 분석 테스트"""
|
|
from app.utils.file_processor import file_processor
|
|
|
|
info = file_processor.get_file_info(sample_excel_file)
|
|
|
|
assert "file_path" in info
|
|
assert "file_size" in info
|
|
assert "file_extension" in info
|
|
assert info["file_extension"] == ".xlsx"
|
|
assert info["file_type"] == "excel"
|
|
assert "recommended_chunk_size" in info
|
|
|
|
def test_dataframe_memory_optimization(self):
|
|
"""DataFrame 메모리 최적화 테스트"""
|
|
import pandas as pd
|
|
from app.utils.file_processor import file_processor
|
|
|
|
# 테스트 데이터 생성
|
|
df = pd.DataFrame({
|
|
'int_col': [1, 2, 3, 4, 5],
|
|
'float_col': [1.1, 2.2, 3.3, 4.4, 5.5],
|
|
'str_col': ['A', 'B', 'A', 'B', 'A']
|
|
})
|
|
|
|
original_memory = df.memory_usage(deep=True).sum()
|
|
optimized_df = file_processor.optimize_dataframe_memory(df)
|
|
optimized_memory = optimized_df.memory_usage(deep=True).sum()
|
|
|
|
# 메모리 사용량이 감소했거나 같아야 함
|
|
assert optimized_memory <= original_memory
|
|
|
|
# 데이터 무결성 확인
|
|
assert len(optimized_df) == len(df)
|
|
assert list(optimized_df.columns) == list(df.columns)
|
|
|
|
def test_optimal_chunk_size_calculation(self):
|
|
"""최적 청크 크기 계산 테스트"""
|
|
from app.utils.file_processor import file_processor
|
|
|
|
# 작은 파일 (1MB 미만)
|
|
chunk_size = file_processor._calculate_optimal_chunk_size(500 * 1024) # 500KB
|
|
assert chunk_size == 500
|
|
|
|
# 중간 파일 (10MB 미만)
|
|
chunk_size = file_processor._calculate_optimal_chunk_size(5 * 1024 * 1024) # 5MB
|
|
assert chunk_size == 1000
|
|
|
|
# 큰 파일 (50MB 이상)
|
|
chunk_size = file_processor._calculate_optimal_chunk_size(100 * 1024 * 1024) # 100MB
|
|
assert chunk_size == 5000
|