
    {ii                     X   d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
 ddlZej                  j                  dd       dZej                  j!                  de      Z	 ej                  j%                  e      Zej(                  j+                  e       dZexr	  eed	      Zej:                  j=                  e d
e d      Zej:                  j=                  e d      Z dede
fdZ!ejD                  d        Z#ejD                  d        Z$ed        Z%ed        Z&ed        Z'ed        Z(ed        Z)ed        Z*ed        Z+ed        Z,ed        Z-ed        Z.ed        Z/ed        Z0ed        Z1e d         Z2d! Z3y# e$ rZdZ ee      ZdZY dZ[dZ[ww xY w)"u  
test_project_map_incremental.py - project-map.py IncrementalUpdater 기능 테스트

테스터: 헤임달 (dev2 team)
대상: /home/jay/workspace/scripts/project-map.py
설계 Spec: /home/jay/workspace/memory/specs/project-map-incremental-spec.md

테스트 항목:
1. test_validate_path_normal       - 정상 경로 통과
2. test_validate_path_traversal    - ../../ 경로 차단 (ValueError)
3. test_compute_hash               - 파일 hash 일관성
4. test_classify_file              - 파일 분류 정확성
5. test_full_scan_creates_cache    - full scan 후 캐시 파일 생성 확인
6. test_incremental_add_file       - 파일 추가 후 incremental → 구조맵 반영
7. test_incremental_delete_file    - 파일 삭제 후 incremental → 구조맵에서 제거
8. test_incremental_modify_file    - 파일 수정 후 incremental → 구조맵 업데이트
9. test_incremental_vs_full_scan   - incremental 결과 == full scan 결과 (shadow comparison)
10. test_rollback                  - 롤백 후 이전 상태 복원
11. test_render_markdown           - JSON → Markdown 렌더링 정확성
12. test_atomic_write              - 캐시 저장이 atomic인지 확인
13. test_sensitive_file_exclusion  - .env 파일이 구조맵에 포함되지 않음
    N)Pathz/home/jay/workspace/scripts/z*/home/jay/workspace/scripts/project-map.pyproject_mapTFIncrementalUpdaterul   IncrementalUpdater 클래스가 project-map.py에 아직 구현되지 않았습니다. (project_map loaded: ))reasonu   project-map.py 로드 실패base_dirreturnc                    t        |       }|dz  dz  }|j                  dd       |dz  j                  dd       |dz  d	z  d
z  dz  }|j                  dd       |dz  j                  dd       |dz  dz  }|j                  dd       |dz  j                  dd       |dz  j                  t        j                  ddddiddidddd      d       |dz  j                  dd       |S )u>   테스트용 프로젝트 디렉토리 구조를 생성한다.srctypesTparentsexist_okzfirestore.tszOexport interface UserRole { id: string; }
export type User = { name: string; };utf-8encodingappapihealthzroute.tszFexport async function GET(req: Request) { return new Response('ok'); }
componentszSearchModal.tsxz6export default function SearchModal() { return null; }package.jsonztest-projectz1.0.0reactz^18.0.0
typescriptz^5.0.0tscznext dev)builddev)nameversiondependenciesdevDependenciesscripts.envzSECRET_KEY=super_secret)r   mkdir
write_textjsondumps)r   root	types_dirapi_dircomp_dirs        >/home/jay/workspace/teams/dev2/test_project_map_incremental.pymake_sample_projectr,   M   s=   >D uw&IOOD4O0++Z ,  UlU"U*X5GMM$M.z%%P &  e|l*HNN4$N/!!--@ .  
N&&

"$i0 ,h7!&z:
 	  ' 	 
F]87KK    c               #      K   t        j                  d      } t        j                  d      }t        |       }t        |      dz  }||f t	        j
                  | d       t	        j
                  |d       yw)uI   임시 프로젝트 디렉토리 + 출력 경로를 제공하는 fixture.pm_test_project_)prefixpm_test_output_zproject-map.mdT)ignore_errorsN)tempfilemkdtempr,   r   shutilrmtree)baseout_dirr'   output_paths       r+   tmp_projectr:   {   sj      #56D&78Gt$Dw-"22K


MM$d+
MM'.s   A;A=c                 z    t         st        j                  d       | \  }}t        j	                  ||dd      ||fS )uN   IncrementalUpdater 인스턴스를 반환하는 fixture (skip if unavailable).u   IncrementalUpdater 미구현   F)project_rootr9   depthinclude_tests)_INCREMENTAL_AVAILABLEpytestskipr   r   )r:   r'   r9   s      r+   updaterrC      sN     "23#D+))	 * 
 [ r-   c                     | \  }}}g d}|D ]d  }||z  }|j                   j                  dd       |j                         s|j                  dd       |j	                  |      }|\J d|         y)	uD   정상적인 상대 경로는 ValueError 없이 통과해야 한다.)src/types/firestore.tssrc/components/SearchModal.tsxr   src/app/api/health/route.tsTr   z// dummyr   r   Nu!   validate_path가 None을 반환: )parentr#   existsr$   _validate_path)rC   updr'   _valid_pathsrel_pathabs_pathresults           r+   test_validate_path_normalrQ      s     LCqK   R(?dT: 
W=##H-!Q%Fxj#QQ!Rr-   c                     | \  }}}g d}|D ]=  }t        j                  t        t        fd      5  |j	                  |       ddd       ? y# 1 sw Y   JxY w)u6   ../../ 경로는 ValueError를 발생시켜야 한다.)z../../etc/passwdz../secret.envzsrc/../../outside.tszsrc/../../../root.tsu9   (?i)(traversal|invalid|outside|not allowed|범위|경로))matchN)rA   raises
ValueErrorPermissionErrorrJ   )rC   rK   rL   traversal_pathsbad_paths        r+   test_validate_path_traversalrY      sc     ICAO $ )]]J8@|} 	)x(	) 	))	) 	)s   AA	c                 &   | \  }}}|dz  }|j                  dd       |j                  |      }|j                  |      }||k(  sJ d       t        |      dk(  sJ d       |j                  dd       |j                  |      }||k7  sJ d	       y
)uz   동일 파일의 hash는 두 번 호출해도 동일해야 하며,
    내용이 달라지면 hash도 달라져야 한다.zhash_test.tszexport type HashTest = string;r   r   u,   동일 파일의 hash가 일관되지 않음@   u%   SHA-256 hex digest는 64자여야 함zexport type HashTest = number;u4   파일 내용 변경 후 hash가 변경되지 않음N)r$   _compute_hashlen)rC   rK   r'   rL   	test_filehash1hash2hash3s           r+   test_compute_hashrb      s     LCq~%I9GLi(Ei(EE>III>u:DDD 9GLi(EE>QQQ>r-   c           	      z    | \  }}}g d}|D ],  \  }}|j                  |      }||k(  rJ d| d| d| d        y)uF   파일 경로에 따라 올바른 섹션으로 분류되어야 한다.))rE   r   )rG   routes)rF   r   )r   config)zsrc/utils/helper.tsr   )z	README.mdother)zsrc/lib/utils.jsrf   z_classify_file('z') = 'z', expected ''N)_classify_file)rC   rK   rL   casesrN   expectedrP   s          r+   test_classify_filerk      si     ICA	E $ 
(##H-! 	
xjvhmH:QO	
!
r-   c                 f   | \  }}}|j                   j                  dd       |j                         }t        |t              sJ d       d|v sJ d       d|v sJ d       |j
                  }|j                         s
J d|        t        j                  |j                  d	
            }d|v sJ y)uM   full_scan_to_cache() 실행 후 캐시 JSON 파일이 생성되어야 한다.Tr   u0   full_scan_to_cache()는 dict를 반환해야 함filesu   캐시에 'files' 키가 없음r   u!   캐시에 'version' 키가 없음u&   캐시 파일이 생성되지 않음: r   r   N)
rH   r#   full_scan_to_cache
isinstancedict
cache_pathrI   r%   loads	read_text)rC   rK   r'   r9   cacherq   loadeds          r+   test_full_scan_creates_cacherv      s     %C{ TD9""$E eT"V$VV"e>>>BBB JU"H UU ZZ
,,g,>?Ffr-   c                    | \  }}}|j                   j                  dd       |j                          |dz  dz  dz  }|j                  dd       |j	                  d	gg 
       |j                         }|J d       t        |j                  di       j                               }|D cg c]  }|j                  dd       }}d	|v s
J d|        yc c}w )uY   새 파일 추가 후 incremental update 시 구조맵(캐시)에 반영되어야 한다.Tr   r   r   z
newtype.tsz7export interface NewType { id: number; label: string; }r   r   zsrc/types/newtype.tschanged_filesdeleted_filesNu   캐시 로드 실패rm   \/u6   새 파일이 캐시에 없음. 현재 파일 목록: )
rH   r#   rn   r$   update
load_cachelistgetkeysreplace)	rC   rK   r'   r9   new_filert   	file_keysknormalized_keyss	            r+   test_incremental_add_filer     s     %C{TD9  e|g%4HA   JJ45RJH NNE444UYYw+0023I5>?qyys+?O?!_4 
@@QR4 @s   ,Cc                    | \  }}}|j                   j                  dd       |j                          |j                         }|J d}||z  }|j	                         sJ d       |j                          |j                  g |g       |j                         }|J |j                  di       j                         D cg c]  }|j                  dd	       }	}||	vs
J d
|        yc c}w )uP   파일 삭제 후 incremental update 시 구조맵에서 제거되어야 한다.Tr   NrE   u!   테스트 전제 파일이 없음rx   rm   r{   r|   u3   삭제된 파일이 캐시에 여전히 존재함: )
rH   r#   rn   r~   rI   unlinkr}   r   r   r   )
rC   rK   r'   r9   cache_before
target_rel
target_abscache_afterr   r   s
             r+   test_incremental_delete_filer   6  s    %C{TD9 >>#L### *J
"JC CC  JJR
|J< .."K"""/:w/K/P/P/RS!4%SISY& 
=j\J& Ts    C*c                    | \  }}}|j                   j                  dd       |j                          |j                         }|J d}||z  }|j	                  di       }|j                         D 	ci c]  \  }}	|j                  dd      |	 }
}}	|
j	                  |i       }|j	                  dd	      }|j                  d
d       |j                  |gg        |j                         }|J |j	                  di       }|j                         D 	ci c]  \  }}	|j                  dd      |	 }}}	|j	                  |i       }|j	                  dd	      }|d	k7  sJ d       ||k7  sJ d       yc c}	}w c c}	}w )uR   파일 수정 후 incremental update 시 캐시의 hash가 갱신되어야 한다.Tr   NrE   rm   r{   r|   hash zlexport interface UserRole { id: string; role: string; }
export type User = { name: string; email: string; };r   r   rx   u#   수정 후 캐시에 hash가 없음u4   파일 수정 후 캐시 hash가 갱신되지 않음)	rH   r#   rn   r~   r   itemsr   r$   r}   )rC   rK   r'   r9   r   r   r   files_beforer   vnorm_before	old_entryold_hashr   files_after
norm_after	new_entrynew_hashs                     r+   test_incremental_modify_filer   Z  s    %C{TD9>>#L###)J
"J  ##GR0L7C7I7I7KLtq!199T3'*LKL
B/I}}VR(H 	?   JJj\J<.."K"""//'2.K6A6G6G6IJda!))D#&)JJJz2.I}}VR(Hr>@@@>x >/ M$ Ks   4E5E;c                    | \  }}}|j                   j                  dd       |j                          |dz  dz  dz  }|j                  dd       |j	                  d	gg 
       |j                         }|j                         }t        d |j                  di       j                         D              }t        d |j                  di       j                         D              }||z
  }	|	r
J d|	        ||z
  }
|
r
J d|
        y)uY   incremental update 결과가 full scan 결과와 동일해야 한다 (shadow comparison).Tr   r   r   zextra.tsz%export interface Extra { x: number; }r   r   zsrc/types/extra.tsrx   c              3   @   K   | ]  }|j                  d d        ywr{   r|   Nr   .0r   s     r+   	<genexpr>z0test_incremental_vs_full_scan.<locals>.<genexpr>  s     [A199T3'[   rm   c              3   @   K   | ]  }|j                  d d        ywr   r   r   s     r+   r   z0test_incremental_vs_full_scan.<locals>.<genexpr>  s     UQAIIdC(Ur   u$   incremental에서 누락된 파일: u.   incremental에 불필요하게 남은 파일: N)	rH   r#   rn   r$   r}   r~   setr   r   )rC   rK   r'   r9   new_tscache_incremental
cache_fullkeys_inc	keys_fullmissing_in_incextra_in_incs              r+   test_incremental_vs_full_scanr     s0    %C{TD9  E\G#j0F
=P JJ232JF( '')J [1B1F1FwPR1S1X1X1Z[[HU*.."2M2R2R2TUUI )N 
.~.>?
 i'L 
8G|r-   c                    | \  }}}|j                   j                  dd       |j                         }|j                  |      }|j	                  |       |j                         sJ d       |j                  d      }|dz  dz  dz  }|j                  d	d       |j                  d
gg        |j                  d      }t        |d      r|j                          net        t        |      dz         }	|	j                         r)t        j                  t        |	      t        |             nt        j                   d       |j                  d      }
|
|k(  sJ dt#        |       dt#        |
              y)uQ   롤백 후 .bak 파일로부터 이전 Markdown 상태가 복원되어야 한다.Tr   u'   full scan 후 Markdown 파일이 없음r   r   r   r   zrollback_test.tsz,export interface RollbackType { v: number; }zsrc/types/rollback_test.tsrx   rollbackz.baku/   .bak 파일이 없어 rollback 테스트 불가u5   롤백 후 내용이 원본과 다름
원본 길이: u   , 복원 길이: N)rH   r#   rn   render_markdown_write_markdown_atomicrI   rs   r$   r}   hasattrr   r   strr5   copy2rA   rB   r]   )rC   rK   r'   r9   rt   
md_contentoriginal_content
extra_fileupdated_contentbak_pathrestored_contents              r+   test_rollbackr     s    %C{TD9 ""$E$$U+Jz* J!JJ",,g,> '*<<JHSZ[JJ:;2JN!++W+=O sJ K(612??LLXK(89KKIJ",,g,>// 	./00A#FVBWAX	Z/r-   c                 D   | \  }}}|j                   j                  dd       |j                         }|j                  |      }t	        |t
              sJ d       t        |      dkD  sJ d       g d}|D ]  }||v rJ d| d        d	|vsJ d
       d|vsJ d       y)uT   JSON 캐시로부터 Markdown 렌더링 시 필수 섹션이 포함되어야 한다.Tr   u,   render_markdown()은 str을 반환해야 함r   u   Markdown이 비어 있음)z# Project Mapz## Directory Treez## Typesz## API Routesz## Componentsu   필수 섹션 누락: 'rg   super_secretu$   .env 내용이 Markdown에 노출됨
SECRET_KEYu!   .env 키가 Markdown에 노출됨N)rH   r#   rn   r   ro   r   r]   )rC   rK   r'   r9   rt   markdownrequired_sectionssections           r+   test_render_markdownr     s     %C{TD9""$E""5)Hh$T&TT$x=1999 % I("H&=gYa$HH"I )Q+QQ)x'L)LL'r-   c                    | \  }}}|j                   j                  dd       |j                         }|j                  }|j	                         sJ d       |j                         j                  }t        j                  d       |j                  |       |j	                         sJ d       	 t        j                  |j                  d            }d|v sJ 	 |j                   }	t!        |	j#                  d            t!        |	j#                  d            z   }
|
r
J d|
        y
# t        j                  $ r"}t        j                  d	|        Y d
}~~d
}~ww xY w)uX   save_cache()가 atomic write(temp → rename) 방식으로 동작하는지 확인한다.Tr   u   캐시 파일이 없음g?u)   save_cache 후 캐시 파일이 사라짐r   r   rm   u3   save_cache 후 캐시가 유효한 JSON이 아님: Nz*.tmpztmp_*u    임시 파일이 남아 있음: )rH   r#   rn   rq   rI   statst_mtimetimesleep
save_cacher%   rr   rs   JSONDecodeErrorrA   failr   glob)rC   rK   r'   r9   rt   rq   mtime_beforeru   e	cache_dir	tmp_filess              r+   test_atomic_writer      sO    %C{TD9 ""$EJ9 99 ??$--L 	JJt NN5 K KKOJ00'0BC&   
 !!IY^^G,-Y^^G5L0MMIH<YKHH=y  OI!MNNOs   /+D) )E<EEc                    | \  }}}|j                   j                  dd       |dz  |dz  |dz  |dz  |dz  g}|D ]&  }|j                         r|j                  dd	
       ( |j	                         }|j                  di       j                         D cg c]  }|j                  dd       }}|D ]:  }t        |j                  |            j                  dd      }	|	|vr2J d|	         d}
|j                  |
gg        |j                         }|J |j                  di       j                         D cg c]  }|j                  dd       }}d|vsJ d       yc c}w c c}w )uB   .env 파일이 캐시(구조맵)에 포함되지 않아야 한다.Tr   r"   z
.env.localzsecrets.pemzcredentials.jsonzapi.keyz SENSITIVE_DATA=should_not_appearr   r   rm   r{   r|   u&   민감 파일이 캐시에 포함됨: rx   Nu+   .env가 incremental 후 캐시에 추가됨)rH   r#   rI   r$   rn   r   r   r   r   relative_tor}   r~   )rC   rK   r'   r9   sensitive_filesfrt   r   r   relenv_relr   file_keys_afters                r+   test_sensitive_file_exclusionr   &  s    %C{TD9 	v|}!!yO  OxxzLL;gLNO
 ""$E/4yy"/E/J/J/LM!4%MIM  
!--%&..tS9)# 	
4SE:	
#
 GJJgYbJ9.."K"""5@__Wb5Q5V5V5XYqyys+YOY(W*WW(# N  Zs   E$=E)c                  V    t         J dD ]  } t        t         |       rJ d|          y)u7   project-map.py 자체는 정상 로드되어야 한다.N)
build_treeextract_types_interfacesextract_api_routesextract_componentssummarize_package_jsongenerate_markdownu   기존 함수 누락: )r   r   )	func_names    r+   test_project_map_module_loadsr   S  sD     """ 

	 {I. 	
$YK0	
.

r-   c                  N   t         s$t        j                  dt         st        nd        t        st        j
                  d       t        t        j                  d      sJ t        t        j                  d      sJ t        t        j                  d      sJ t        t        j                  d      sJ t        t        j                  d      sJ t        t        j                  d	      sJ t        t        j                  d
      sJ t        t        j                  d      sJ y)u   IncrementalUpdater 클래스 존재 여부를 확인하는 플레이스홀더 테스트.

    이 테스트는 구현 전에도 실행 가능하며,
    IncrementalUpdater가 없으면 경고 메시지를 출력한다.
    u   project-map.py 로드 실패: r   u   IncrementalUpdater 클래스가 아직 구현되지 않았습니다. 개발자(dev1/dev3)가 project-map.py에 구현하면 자동으로 활성화됩니다.r}   rn   r~   r   r   rh   r\   rJ   N)	_PROJECT_MAP_LOADEDrA   rB   _LOAD_ERRORr@   xfailr   r   r    r-   r+   *test_incremental_updater_class_placeholderr   e  s     4H[[ac4def!f	

 ;118<<<;113GHHH;11<@@@;11<@@@;113DEEE;113CDDD;11?CCC;113CDDDr-   )4__doc__	importlibimportlib.utilr%   osr5   sysr3   r   pathlibr   rA   pathinsertPROJECT_MAP_PATHutilspec_from_file_location_specmodule_from_specr   loaderexec_moduler   	Exception_er   r   r   r@   markskipifskip_if_no_incrementalskip_if_no_project_mapr,   fixturer:   rC   rQ   rY   rb   rk   rv   r   r   r   r   r   r   r   r   r   r   r   r-   r+   <module>r      s  .    	  
     1 2?  	..}>NO..11%8K	LL[)  312   ++	  34A	7 ,    ++) ,  +# +$ +\ / / 
 
" R R2 ) )$ R R, 
 
4  6  B  F ( (^ ! !P % %X M M> I IJ %X %XX 
 
"Em  b'KKs   (8F F)F$$F)