
    (<iJ                     l   d Z ddlZddlZddlZddlmZ ej                  j                  d e ee	      j                  j                               ddlmZmZmZmZ  ed      Z G d d      Z G d d	      Z G d
 d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Zy)u2  
Tests for ast_dependency_map.py — AST 기반 Python 의존성 맵 생성기

테스트 커버리지:
- AST 파싱 (유효/무효 파일)
- 임포트 모듈 추출
- 의존성 그래프 구축
- 직접 임포터 조회
- analyze() 출력 구조
- 함수 레벨 호출자 분석
- 테스트 파일 감지
    N)Path)DependencyGraph_extract_imported_modules_parse_fileanalyzez/home/jay/workspace/dashboardc                   "    e Zd ZdZd Zd Zd Zy)TestParseFileValidu(   유효한 Python 파일 파싱 테스트c                 V   t        j                  ddd      5 }|j                  d       t        |j                        }ddd       	 t              }|J t        |t        j                        sJ 	 |j                  d       y# 1 sw Y   IxY w# j                  d       w xY w)	uK   유효한 Python 파일을 파싱하면 None이 아닌 AST를 반환한다.w.pyFmodesuffixdeletezx = 1
print(x)
NT
missing_ok)
tempfileNamedTemporaryFilewriter   namer   
isinstanceastModuleunlinkselfftmp_pathresults       V/home/jay/workspace/.worktrees/task-2057-dev2/scripts/tests/test_ast_dependency_map.pytest_parse_valid_python_filez/TestParseFileValid.test_parse_valid_python_file*   s    ((U5
 	$GG'(AFF|H		$	- *F%%%fcjj111OOtO,	$ 	$ OOtO,s   'B	+B BB(c                    t        j                  ddd      5 }|j                  d       t        |j                        }ddd       	 t              }|J 	 |j                  d       y# 1 sw Y   -xY w# j                  d       w xY w)	uL   임포트가 포함된 유효한 파일을 파싱하면 AST를 반환한다.r   r   Fr   z:import os
from pathlib import Path

def hello():
    pass
NTr   r   r   r   r   r   r   r   r   s       r    "test_parse_valid_file_with_importsz5TestParseFileValid.test_parse_valid_file_with_imports9   s    ((U5
 	$GGQ AFF|H	$	- *F%%%OOtO,	$ 	$ OOtO,   'A,	A8 ,A58Bc                     t         dz  }|j                         sJ d       t        |      }|j                         j                  }|dkD  r|J y|J t        |t        j                        sJ y)ul   실제 data_loader.py 파일을 파싱한다. 100KB 초과 시 대형 파일 보호로 None을 반환한다.data_loader.pyu%   data_loader.py 파일이 없습니다i N)DASHBOARD_ROOTexistsr   statst_sizer   r   r   )r   data_loader_pathr   	file_sizes       r    test_parse_real_data_loaderz.TestParseFileValid.test_parse_real_data_loaderI   sw    ),<<&&(Q*QQ(-.$))+33	w>!>%%%fcjj111    N)__name__
__module____qualname____doc__r!   r$   r.    r/   r    r	   r	   '   s    2-- 2r/   r	   c                   "    e Zd ZdZd Zd Zd Zy)TestParseFileInvalidu/   구문 오류가 있는 파일 파싱 테스트c                    t        j                  ddd      5 }|j                  d       t        |j                        }ddd       	 t              }|J 	 |j                  d       y# 1 sw Y   -xY w# j                  d       w xY w)	uD   구문 오류가 있는 파일을 파싱하면 None을 반환한다.r   r   Fr   u3   def broken(
    # 괄호가 닫히지 않음
x = 1
NTr   r#   r   s       r    $test_parse_syntax_error_returns_nonez9TestParseFileInvalid.test_parse_syntax_error_returns_none`   s    ((U5
 	$GGLMAFF|H		$	- *F>!>OOtO,	$ 	$ OOtO,r%   c                 4    t        t        d            }|J y)u@   존재하지 않는 파일을 파싱하면 None을 반환한다.z#/nonexistent/path/does_not_exist.pyN)r   r   )r   r   s     r    (test_parse_nonexistent_file_returns_nonez=TestParseFileInvalid.test_parse_nonexistent_file_returns_nonen   s    T"GHI~~r/   c                    t        j                  ddd      5 }|j                  d       t        |j                        }ddd       	 t              }|J 	 |j                  d       y# 1 sw Y   -xY w# j                  d       w xY w)	uC   불완전한 표현식 파일을 파싱하면 None을 반환한다.r   r   Fr   zclass Foo:
  def bar(self
NTr   r#   r   s       r    -test_parse_incomplete_expression_returns_nonezBTestParseFileInvalid.test_parse_incomplete_expression_returns_nones   s    ((U5
 	$GG23AFF|H		$	- *F>!>OOtO,	$ 	$ OOtO,r%   N)r0   r1   r2   r3   r8   r:   r<   r4   r/   r    r6   r6   ]   s    9-
-r/   r6   c                   ^    e Zd ZdZdedej                  fdZd Zd Z	d Z
d Zd	 Zd
 Zd Zy)TestExtractImportedModulesu!   임포트 모듈 추출 테스트codereturnc                 ,    t        j                  |      S N)r   parse)r   r?   s     r    _parse_codez&TestExtractImportedModules._parse_code   s    yyr/   c                 X    | j                  d      }t        |d      }d|v sJ d|v sJ y)u    import X 패턴을 처리한다.zimport os
import sys
	dashboardpkg_nameossysNrD   r   r   treemoduless      r    test_import_xz(TestExtractImportedModules.test_import_x   s9     9:+D;Gwr/   c                 X    | j                  d      }t        |d      }d|v sJ d|v sJ y)u'   from X import Y 패턴을 처리한다.z1from pathlib import Path
from typing import List
rF   rG   pathlibtypingNrK   rL   s      r    test_from_x_import_yz/TestExtractImportedModules.test_from_x_import_y   s;     UV+D;GG###7"""r/   c                 X    | j                  d      }t        |d      }d|v sJ d|v sJ y)u9   from dashboard.X import Y 패턴에서 X를 추출한다.zWfrom dashboard.data_loader import DataLoader
from dashboard.server_utils import helper
rF   rG   data_loaderserver_utilsNrK   rL   s      r    test_from_dashboard_x_import_yz9TestExtractImportedModules.test_from_dashboard_x_import_y   sA    :
 ,D;G'''(((r/   c                 L    | j                  d      }t        |d      }d|v sJ y)u0   from .X import Y 패턴에서 X를 추출한다.z$from .data_loader import DataLoader
rF   rG   rU   NrK   rL   s      r    test_relative_import_from_dot_xz:TestExtractImportedModules.test_relative_import_from_dot_x   s-     GH+D;G'''r/   c                 L    | j                  d      }t        |d      }d|v sJ y)u2   import dashboard.X 패턴에서 X를 추출한다.zimport dashboard.data_loader
rF   rG   rU   NrK   rL   s      r    test_import_dashboard_xz2TestExtractImportedModules.test_import_dashboard_x   s-     @A+D;G'''r/   c                 ^    | j                  d      }t        |d      }|t               k(  sJ y)u9   임포트가 없는 파일은 빈 집합을 반환한다.zx = 1
y = 2
rF   rG   N)rD   r   setrL   s      r    !test_no_imports_returns_empty_setz<TestExtractImportedModules.test_no_imports_returns_empty_set   s/     01+D;G#%r/   c                 P    d}| j                  |      }t        |d      }d|v sJ y)u:   try/except 블록 내의 임포트 패턴을 처리한다.zqtry:
    from dashboard.data_loader import DataLoader
except ImportError:
    from data_loader import DataLoader
rF   rG   rU   NrK   )r   r?   rM   rN   s       r    test_try_except_import_patternz9TestExtractImportedModules.test_try_except_import_pattern   s8    7 	 %+D;G'''r/   N)r0   r1   r2   r3   strr   r   rD   rO   rS   rW   rY   r[   r^   r`   r4   r/   r    r>   r>      s?    + 

  #)(( 
(r/   r>   c                   :    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
y	)
TestDependencyGraphBuildu$   의존성 그래프 구축 테스트c                 B    t        t              }d|j                  v sJ y)uL   DependencyGraph 빌드 후 data_loader.py가 module_to_file에 포함된다.rU   Nr   r(   module_to_filer   graphs     r    "test_data_loader_in_module_to_filez;TestDependencyGraphBuild.test_data_loader_in_module_to_file   s     / 4 4444r/   c                 B    t        t              }d|j                  v sJ y)uG   DependencyGraph 빌드 후 server.py가 module_to_file에 포함된다.serverNre   rg   s     r    test_server_in_module_to_filez6TestDependencyGraphBuild.test_server_in_module_to_file   s     /5/////r/   c                     t        t              }|j                  d   }t        |t              sJ |j                         sJ y)u?   module_to_file의 값이 실제 존재하는 Path 객체이다.rU   N)r   r(   rf   r   r   r)   )r   rh   r,   s      r     test_module_to_file_maps_to_pathz9TestDependencyGraphBuild.test_module_to_file_maps_to_path   s@    / //>*D111&&(((r/   c                 V    t        t              }t        |j                        dkD  sJ y)u2   file_imports 딕셔너리가 비어 있지 않다.r   N)r   r(   lenfile_importsrg   s     r    test_graph_has_file_importsz4TestDependencyGraphBuild.test_graph_has_file_imports   s%    /5%%&***r/   c                 D    t        t              }|j                  dk(  sJ y)uD   그래프의 pkg_name이 루트 디렉토리 이름과 일치한다.rF   N)r   r(   rH   rg   s     r    test_graph_pkg_namez,TestDependencyGraphBuild.test_graph_pkg_name   s    /~~,,,r/   c                 p    t        t              }|j                  d      }|J |j                         sJ y)uE   get_file_path()가 .py 확장자가 있는 파일명도 처리한다.r'   Nr   r(   get_file_pathr)   r   rh   paths      r    !test_get_file_path_with_extensionz:TestDependencyGraphBuild.test_get_file_path_with_extension   s7    /""#34{{}}r/   c                 p    t        t              }|j                  d      }|J |j                         sJ y)u>   get_file_path()가 확장자 없는 모듈명도 처리한다.rU   Nrv   rx   s      r    $test_get_file_path_without_extensionz=TestDependencyGraphBuild.test_get_file_path_without_extension   s6    /""=1{{}}r/   N)r0   r1   r2   r3   ri   rl   rn   rr   rt   rz   r|   r4   r/   r    rc   rc      s(    .5
0
)+
-
r/   rc   c                   (    e Zd ZdZd Zd Zd Zd Zy)TestGetDirectImportersu!   직접 임포터 조회 테스트c                     t        t              }|j                  d      }|D ch c]  }|j                   }}d|v s
J d|        yc c}w )uK   data_loader를 직접 임포트하는 파일 중 server.py가 포함된다.rU   	server.pyuW   server.py가 data_loader의 직접 임포터 목록에 없습니다. 현재 임포터: N)r   r(   get_direct_importersr   )r   rh   	importerspimporter_namess        r    'test_data_loader_has_server_as_importerz>TestGetDirectImporters.test_data_loader_has_server_as_importer   s]    /..}=	*34Q!&&44n, 	
!!/ 02	
, 5s   A	c                     t        t              }|j                  d      }t        |t              sJ |D ]  }t        |t
              rJ  y)u@   get_direct_importers()가 Path 객체의 집합을 반환한다.rU   N)r   r(   r   r   r]   r   )r   rh   r   imps       r    $test_data_loader_importers_are_pathsz;TestGetDirectImporters.test_data_loader_importers_are_paths	  sK    /..}=	)S))) 	)Cc4(((	)r/   c                 b    t        t              }|j                  d      }|t               k(  sJ y)u@   존재하지 않는 모듈에 대해 빈 집합을 반환한다.nonexistent_module_xyzN)r   r(   r   r]   )r   rh   r   s      r    )test_nonexistent_module_returns_empty_setz@TestGetDirectImporters.test_nonexistent_module_returns_empty_set  s-    /../GH	CE!!!r/   c                 l    t        t              }|j                  dd      }t        |t              sJ y)u6   get_transitive_dependents()가 집합을 반환한다.rU      )hopsN)r   r(   get_transitive_dependentsr   r]   )r   rh   
dependentss      r    *test_get_transitive_dependents_returns_setzATestGetDirectImporters.test_get_transitive_dependents_returns_set  s1    /44]4K
*c***r/   N)r0   r1   r2   r3   r   r   r   r   r4   r/   r    r~   r~      s    +	
)"+r/   r~   c                   L    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zd Zy)TestAnalyzeBasicu(   analyze() 기본 출력 구조 테스트c                 J    t        t        dg      }t        |t              sJ y)u'   analyze()는 리스트를 반환한다.r'   Nr   r(   r   listr   resultss     r    test_analyze_returns_listz*TestAnalyzeBasic.test_analyze_returns_list&  s"    .+;*<='4(((r/   c                 F    t        t        dg      }t        |      dk(  sJ y)u=   단일 파일 분석은 결과 항목 하나를 반환한다.r'      Nr   r(   rp   r   s     r    +test_analyze_single_file_returns_one_resultz<TestAnalyzeBasic.test_analyze_single_file_returns_one_result+  s$    .+;*<=7|q   r/   c                 R    t        t        dg      }d|d   v sJ |d   d   dk(  sJ y)u%   결과에 changed_file 키가 있다.r'   changed_filer   Nr   r(   r   s     r    $test_analyze_result_has_changed_filez5TestAnalyzeBasic.test_analyze_result_has_changed_file0  s<    .+;*<=+++qz.)-====r/   c                 8    t        t        dg      }d|d   v sJ y)u%   결과에 blast_radius 키가 있다.r'   blast_radiusr   Nr   r   s     r    $test_analyze_result_has_blast_radiusz5TestAnalyzeBasic.test_analyze_result_has_blast_radius6  s$    .+;*<=+++r/   c                 j    t        t        dg      }|d   d   }h d}|D ]  }||v rJ d| d        y)u/   blast_radius에 필수 키들이 모두 있다.r'   r   r   >   callers
test_filestotal_affecteddirect_importerstransitive_dependentsu   blast_radius에 'u   ' 키가 없습니다Nr   )r   r   blastrequired_keyskeys        r    #test_blast_radius_has_required_keysz4TestAnalyzeBasic.test_blast_radius_has_required_keys;  sV    .+;*<=
>*
 ! 	PC%<O#4SE9N!OO<	Pr/   c                 \    t        t        dg      }t        |d   d   d   t              sJ y)u+   direct_importers가 리스트 타입이다.r'   r   r   r   Nr   r   s     r    *test_blast_radius_direct_importers_is_listz;TestAnalyzeBasic.test_blast_radius_direct_importers_is_listI  s1    .+;*<='!*^45GH$OOOr/   c                 \    t        t        dg      }t        |d   d   d   t              sJ y)u&   total_affected가 정수 타입이다.r'   r   r   r   N)r   r(   r   intr   s     r    'test_blast_radius_total_affected_is_intz8TestAnalyzeBasic.test_blast_radius_total_affected_is_intN  s1    .+;*<='!*^45EFLLLr/   c                 t    t        t        dg      }|d   d   d   }t        d |D              s
J d|        y)u@   data_loader.py의 direct_importers에 server.py가 포함된다.r'   r   r   r   c              3   $   K   | ]  }d |v  
 yw)rk   Nr4   ).0r   s     r    	<genexpr>zPTestAnalyzeBasic.test_blast_radius_server_in_direct_importers.<locals>.<genexpr>W  s     5s8s?5s   u;   server.py가 direct_importers에 없습니다. 현재 값: N)r   r(   any)r   r   directs      r    ,test_blast_radius_server_in_direct_importersz=TestAnalyzeBasic.test_blast_radius_server_in_direct_importersS  sK    .+;*<=N+,>?5f55 	
I&R	
5r/   c                 t    t        t        dg      }d|d   v sJ t        |d   d   t        t        f      sJ y)u)   결과에 analysis_time_ms 키가 있다.r'   analysis_time_msr   N)r   r(   r   r   floatr   s     r    (test_analyze_result_has_analysis_time_msz9TestAnalyzeBasic.test_analyze_result_has_analysis_time_ms[  sB    .+;*<=!WQZ///'!*%783,GGGr/   c                     t        t        ddg      }t        |      dk(  sJ |D cg c]  }|d   	 }}d|v sJ d|v sJ yc c}w )uD   여러 파일 분석은 각 파일에 대한 결과를 반환한다.r'   r   r   r   Nr   )r   r   rchanged_filess       r    test_analyze_multiple_filesz,TestAnalyzeBasic.test_analyze_multiple_filesa  s_    .+;[*IJ7|q   4;<q>*<<=000m+++ =s   AN)r0   r1   r2   r3   r   r   r   r   r   r   r   r   r   r   r4   r/   r    r   r   #  s;    2)
!
>,
PP
M

H,r/   r   c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestAnalyzeWithFunctionu(   analyze() 함수 레벨 분석 테스트c                 V    t        t        dgd      }d|d   v sJ |d   d   dk(  sJ y)u;   함수 지정 시 결과에 changed_function 키가 있다.r'   get_member_statusfunction_namechanged_functionr   Nr   r   s     r    3test_analyze_with_function_has_changed_function_keyzKTestAnalyzeWithFunction.test_analyze_with_function_has_changed_function_keyr  sE    -

 "WQZ///qz,-1DDDDr/   c                 8    t        t        dg      }d|d   vsJ y)u>   함수 미지정 시 결과에 changed_function 키가 없다.r'   r   r   Nr   r   s     r    5test_analyze_without_function_no_changed_function_keyzMTestAnalyzeWithFunction.test_analyze_without_function_no_changed_function_key|  s$    .+;*<=!333r/   c                 `    t        t        dgd      }t        |d   d   d   t              sJ y)u4   함수 지정 시 callers가 리스트 타입이다.r'   r   r   r   r   r   Nr   r   s     r    *test_analyze_with_function_callers_is_listzBTestAnalyzeWithFunction.test_analyze_with_function_callers_is_list  s8    -

 '!*^4Y?FFFr/   c                 |    t        t        dgd      }|d   d   d   }t        |      dkD  sJ d|d   d           y)	uL   get_member_status 함수 분석 시 호출자가 하나 이상 발견된다.r'   r   r   r   r   r   uO   get_member_status 함수의 호출자를 찾지 못했습니다. blast_radius: Nr   r   r   r   s      r    (test_analyze_with_function_callers_foundz@TestAnalyzeWithFunction.test_analyze_with_function_callers_found  s_    -

 !*^,Y77|a 	
$QZ78:	
r/   c                     t        t        dgd      }|d   d   d   }|D ]R  }d|v s
J d|        |j                  dd	      }t        |      d
k(  sJ |d	   j	                         rGJ d|d	            y)u;   callers 목록의 각 항목이 'path:lineno' 형식이다.r'   r   r   r   r   r   :u+   호출자 형식이 잘못되었습니다: r   r   u%   줄번호가 숫자가 아닙니다: N)r   r(   rsplitrp   isdigit)r   r   r   callerpartss        r    )test_analyze_with_function_callers_formatzATestAnalyzeWithFunction.test_analyze_with_function_callers_format  s    -

 !*^,Y7 	ZF&=X$OPVx"XX=MM#q)Eu:?"?8##%Y)NuUVxj'YY%		Zr/   c                 N    t        t        dgd      }|d   d   d   }|g k(  sJ y)uE   존재하지 않는 함수 분석 시 callers가 빈 리스트이다.r'   $this_function_does_not_exist_xyz_abcr   r   r   r   Nr   r   s      r    4test_analyze_with_nonexistent_function_callers_emptyzLTestAnalyzeWithFunction.test_analyze_with_nonexistent_function_callers_empty  s9    @

 !*^,Y7"}}r/   N)
r0   r1   r2   r3   r   r   r   r   r   r   r4   r/   r    r   r   o  s&    2E4
G
Zr/   r   c                   @    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
d	 Zy
)TestIsTestFileu!   테스트 파일 감지 테스트c                 ,    t        t              | _        y rB   )r   r(   rh   )r   s    r    setup_methodzTestIsTestFile.setup_method  s    $^4
r/   c                 T    t         dz  }| j                  j                  |      du sJ y)uE   test_로 시작하는 .py 파일은 테스트 파일로 감지된다.ztest_server.pyTNr(   rh   is_test_filer   	test_paths     r    test_test_prefix_file_is_testz,TestIsTestFile.test_test_prefix_file_is_test  s*    "%55	zz&&y1T999r/   c                 T    t         dz  }| j                  j                  |      du sJ y)u3   일반 .py 파일은 테스트 파일이 아니다.r   FNr   )r   regular_paths     r    test_regular_file_is_not_testz,TestIsTestFile.test_regular_file_is_not_test  s)    %3zz&&|4===r/   c                 T    t         dz  }| j                  j                  |      du sJ y)u0   data_loader.py는 테스트 파일이 아니다.r'   FNr   )r   r,   s     r    test_data_loader_is_not_testz+TestIsTestFile.test_data_loader_is_not_test  s,    ),<<zz&&'78EAAAr/   c                 Z    t         dz  dz  }| j                  j                  |      du sJ y)uF   tests/ 디렉토리 내의 파일은 테스트 파일로 감지된다.testszsome_test.pyTNr   )r   
tests_paths     r    test_file_in_tests_dir_is_testz-TestIsTestFile.test_file_in_tests_dir_is_test  s.    #g->
zz&&z2d:::r/   c                 T    t         dz  }| j                  j                  |      du sJ y)u@   test_blog_image_classify.py가 테스트 파일로 감지된다.ztest_blog_image_classify.pyTNr   r   s     r    %test_test_blog_image_classify_is_testz4TestIsTestFile.test_test_blog_image_classify_is_test  s*    "%BB	zz&&y1T999r/   c                 X    t        d      }| j                  j                  |      du sJ y)uE   임의의 test_ 접두사 파일이 테스트 파일로 감지된다.z/some/dir/test_anything.pyTN)r   rh   r   )r   arbitrary_tests     r    test_arbitrary_test_prefix_filez.TestIsTestFile.test_arbitrary_test_prefix_file  s*    :;zz&&~6$>>>r/   c                 T    t         dz  }| j                  j                  |      du sJ y)uO   test_로 시작하지만 .py가 아닌 파일은 테스트 파일이 아니다.ztest_session_watchdog.shFNr   )r   non_pys     r    "test_non_py_test_file_not_detectedz1TestIsTestFile.test_non_py_test_file_not_detected  s*    "<<zz&&v.%777r/   N)r0   r1   r2   r3   r   r   r   r   r   r   r   r   r4   r/   r    r   r     s.    +5:
>
B
;
:
?8r/   r   )r3   r   rJ   r   rQ   r   ry   insertra   __file__parentast_dependency_mapr   r   r   r   r(   r	   r6   r>   rc   r~   r   r   r   r4   r/   r    <module>r      s     
   3tH~,,334 5  56.2 .2l"- "-T:( :(D* *d +  +PD, D,X> >L)8 )8r/   