
    it              	          d Z ddlZddlmc mZ ddlZddlZddl	Z	ddl
m
Z
 ddlmZ ddlmZ ddlmZ ddlZej$                  j'                  d e ee      j,                  j,                               ddlZd!deded	ed
efdZd!ded	ed
ef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# G d d      Z$ G d d       Z%y)"u  Tests for file-cleanup.py - TDD approach (RED phase first).

파일 자동 정리 스크립트의 4가지 정리 카테고리, 실행 모드, 안전장치를
테스트합니다. 모든 테스트는 tmp_path fixture를 사용하여 실제 시스템
파일을 건드리지 않습니다.
    N)datetime)Path)Any)patchpathdayscontentreturnc                     | j                   j                  dd       | j                  |       t        j                         |dz  dz  z
  }t	        j
                  | ||f       | S )u?   파일을 생성하고 mtime을 days일 전으로 설정한다.Tparentsexist_ok   i  )parentmkdir
write_texttimeosutime)r   r   r	   old_times       6/home/jay/workspace/scripts/tests/test_file_cleanup.py_make_old_filer      sV    KKdT2OOGyy{dRi$./HHHTHh'(K    c                 b    | j                   j                  dd       | j                  |       | S )u,   최근 파일을 생성한다 (mtime = now).Tr   )r   r   r   )r   r	   s     r   _make_recent_filer   %   s*    KKdT2OOGKr   c                       e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfd	Z	deddfd
Z
deddfdZdeddfdZdeddfdZdeddfdZdeddfdZy)TestCokacDirCleanupuK   cokacdir 업로드 파일 정리 테스트 (30일 초과 미디어 파일).tmp_pathr
   Nc                    |dz  dz  dz  }t        |dz  d      }t        j                  |dz  dz        }|D cg c]  }|d   	 }}t        |      }||v }|s#t	        j
                  d|fd	||f      d
t        j                         v st	        j                  t              rt	        j                  t              nd
dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }	dd|	iz  }
t        t	        j                  |
            dx}}yc c}w )u@   31일 된 PDF 파일은 삭제 후보로 감지되어야 한다.	.cokacdir	workspaceproj1z
report.pdf   r   r   inz0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} in %(py5)sstrold_pdfpathspy0py1py3py5assert %(py7)spy7Nr   fc find_cokacdir_cleanup_candidatesr(   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation)selfr   cokac_wsr)   
candidatescr*   @py_assert2@py_assert4@py_format6@py_format8s              r   test_old_pdf_is_candidatez-TestCokacDirCleanup.test_old_pdf_is_candidate4   s    k)K7'A L!8rB88K9OR]9]^
$./q6//7|$|u$$$$|u$$$$$$s$$$s$$$$$$7$$$7$$$|$$$$$$u$$$u$$$$$$$ 0   Fc                    |dz  dz  dz  }t        |dz  d      }t        j                  |dz  dz        }|D cg c]  }|d   	 }}t        |      }||v }|s#t	        j
                  d|fd	||f      d
t        j                         v st	        j                  t              rt	        j                  t              nd
dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }	dd|	iz  }
t        t	        j                  |
            dx}}yc c}w )u@   31일 된 PNG 파일은 삭제 후보로 감지되어야 한다.r    r!   r"   z	image.png#   r$   r   r%   r'   r(   old_pngr*   r+   r0   r1   Nr2   )r=   r   r>   rI   r?   r@   r*   rA   rB   rC   rD   s              r   test_old_png_is_candidatez-TestCokacDirCleanup.test_old_png_is_candidate>   s    k)K7'A K!7bA88K9OR]9]^
$./q6//7|$|u$$$$|u$$$$$$s$$$s$$$$$$7$$$7$$$|$$$$$$u$$$u$$$$$$$ 0rF   c                    |dz  dz  dz  }t        |dz  d      }t        |dz  d      }t        j                  |dz  dz        }|D cg c]  }|d   	 }}t        |      }||v }	|	s#t	        j
                  d	|	fd
||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }
dd|
iz  }t        t	        j                  |            dx}}	t        |      }||v }	|	s#t	        j
                  d	|	fd
||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }
dd|
iz  }t        t	        j                  |            dx}}	yc c}w )uE   31일 된 JPG/JPEG 파일은 삭제 후보로 감지되어야 한다.r    r!   r"   z	photo.jpg(   r$   z	scan.jpegr   r%   r'   r(   old_jpgr*   r+   r0   r1   Nold_jpegr2   )r=   r   r>   rM   rN   r?   r@   r*   rA   rB   rC   rD   s               r   $test_old_jpg_and_jpeg_are_candidatesz8TestCokacDirCleanup.test_old_jpg_and_jpeg_are_candidatesH   s   k)K7'A K!7bA!(["8rB88K9OR]9]^
$./q6//7|$|u$$$$|u$$$$$$s$$$s$$$$$$7$$$7$$$|$$$$$$u$$$u$$$$$$$8}%}%%%%}%%%%%%s%%%s%%%%%%8%%%8%%%}%%%%%%%%%%%%%%%% 0s   Kc                    |dz  dz  dz  }t        |dz        }t        j                  |dz  dz        }|D cg c]  }|d   	 }}t        |      }||v}|s#t	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              ndd	t        j                         v st	        j                  |      rt	        j                  |      nd	t	        j                  |      d
t        j                         v st	        j                  |      rt	        j                  |      nd
dz  }	dd|	iz  }
t        t	        j                  |
            dx}}yc c}w )uF   30일 이내 미디어 파일은 삭제 후보가 아니어야 한다.r    r!   r"   z
recent.pdfr   not inz4%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} not in %(py5)sr(   
recent_pdfr*   r+   r0   r1   N)r   r3   r4   r(   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r>   rT   r?   r@   r*   rA   rB   rC   rD   s              r   "test_recent_media_is_not_candidatez6TestCokacDirCleanup.test_recent_media_is_not_candidateT   s    k)K7'A&x,'>?
88K9OR]9]^
$./q6//:+e++++e++++++s+++s++++++:+++:+++++++++e+++e+++++++ 0s   Fc                    |dz  dz  dz  }t        |dz  d      }t        j                  |dz  dz        }|D cg c]  }|d   	 }}t        |      }||v}|s#t	        j
                  d|fd	||f      d
t        j                         v st	        j                  t              rt	        j                  t              nd
dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }	dd|	iz  }
t        t	        j                  |
            dx}}yc c}w )uR   정확히 30일 된 파일은 삭제 후보가 아니다 (30일 초과만 대상).r    r!   r"   z
border.pdf   r$   r   rQ   rS   r(   
border_pdfr*   r+   r0   r1   Nr2   )r=   r   r>   rX   r?   r@   r*   rA   rB   rC   rD   s              r   "test_exactly_30_days_not_candidatez6TestCokacDirCleanup.test_exactly_30_days_not_candidate^   s    k)K7'A#H|$;"E
88K9OR]9]^
$./q6//:+e++++e++++++s+++s++++++:+++:+++++++++e+++e+++++++ 0rF   c                    |dz  dz  dz  }t        |dz  d       t        j                  |dz  dz        }|D cg c]  }|d   	 }}d |D        }t        |      }| }|sd	d
t	        j
                         v st        j                  t              rt        j                  t              nd
t        j                  |      t        j                  |      dz  }	t        t        j                  |	            dx}x}}yc c}w )uB   CLAUDE.md는 오래되어도 삭제 후보가 아니어야 한다.r    r!   r"   	CLAUDE.md<   r$   r   c              3   $   K   | ]  }d |v  
 yw)r[   N .0ps     r   	<genexpr>zBTestCokacDirCleanup.test_claude_md_is_protected.<locals>.<genexpr>p   s     7A{a'7   0assert not %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}anyr,   py2py4Nr   r3   r4   re   r7   r8   r5   r9   r:   r;   r<   
r=   r   r>   r?   r@   r*   @py_assert1@py_assert3@py_assert5rC   s
             r   test_claude_md_is_protectedz/TestCokacDirCleanup.test_claude_md_is_protectedh   s    k)K7'Ax+-B788K9OR]9]^
$./q6//77737777777777773777377777777777777 0   C>c                    |dz  dz  dz  }t        |dz  d       t        j                  |dz  dz        }|D cg c]  }|d   	 }}d |D        }t        |      }| }|sd	d
t	        j
                         v st        j                  t              rt        j                  t              nd
t        j                  |      t        j                  |      dz  }	t        t        j                  |	            dx}x}}yc c}w )uD   *.py 파일은 오래되어도 삭제 후보가 아니어야 한다.r    r!   r"   z	helper.pyr\   r$   r   c              3   $   K   | ]  }d |v  
 yw)z.pyNr^   r_   s     r   rb   zDTestCokacDirCleanup.test_py_scripts_are_protected.<locals>.<genexpr>z        1auz1rc   rd   re   rf   Nri   rj   s
             r   test_py_scripts_are_protectedz1TestCokacDirCleanup.test_py_scripts_are_protectedr   s    k)K7'Ax+-B788K9OR]9]^
$./q6//151131111111111113111311111111111111 0ro   c                    |dz  dz  dz  }t        |dz  d       t        j                  |dz  dz        }|D cg c]  }|d   	 }}d |D        }t        |      }| }|sd	d
t	        j
                         v st        j                  t              rt        j                  t              nd
t        j                  |      t        j                  |      dz  }	t        t        j                  |	            dx}x}}yc c}w )uD   *.sh 파일은 오래되어도 삭제 후보가 아니어야 한다.r    r!   r"   zsetup.shr\   r$   r   c              3   $   K   | ]  }d |v  
 yw)z.shNr^   r_   s     r   rb   zDTestCokacDirCleanup.test_sh_scripts_are_protected.<locals>.<genexpr>   rr   rc   rd   re   rf   Nri   rj   s
             r   test_sh_scripts_are_protectedz1TestCokacDirCleanup.test_sh_scripts_are_protected|   s    k)K7'Ax*,2688K9OR]9]^
$./q6//151131111111111113111311111111111111 0ro   c                    |dz  dz  dz  }t        |dz  d       t        |dz  d       t        j                  |dz  dz        }|D cg c]  }|d   	 }}d	 |D        }t        |      }| }|sd
dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      dz  }	t        t        j                  |	            dx}x}}d |D        }t        |      }| }|sd
dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      dz  }	t        t        j                  |	            dx}x}}yc c}w )uC   PDF/PNG/JPG/JPEG가 아닌 파일은 포함되지 않아야 한다.r    r!   r"   zdata.csvr\   r$   z	notes.txtr   c              3   $   K   | ]  }d |v  
 yw)z.csvNr^   r_   s     r   rb   zHTestCokacDirCleanup.test_non_media_files_not_included.<locals>.<genexpr>        2qv{2rc   rd   re   rf   Nc              3   $   K   | ]  }d |v  
 yw)z.txtNr^   r_   s     r   rb   zHTestCokacDirCleanup.test_non_media_files_not_included.<locals>.<genexpr>   ry   rc   ri   rj   s
             r   !test_non_media_files_not_includedz5TestCokacDirCleanup.test_non_media_files_not_included   s;   k)K7'Ax*,26x+-B788K9OR]9]^
$./q6//2E22322222222222232223222222222222222E2232222222222223222322222222222222 0s   Gc                    |dz  dz  }t        j                  |      }g }||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)
uC   존재하지 않는 workspace는 빈 목록을 반환해야 한다.r    r!   ==z%(py0)s == %(py3)sr?   r,   r.   assert %(py5)sr/   N)
r3   r4   r5   r6   r7   r8   r9   r:   r;   r<   r=   r   missingr?   rA   rk   @py_format4rC   s           r   (test_nonexistent_workspace_returns_emptyz<TestCokacDirCleanup.test_nonexistent_workspace_returns_empty   sz    [(;688A
zRzRzzRr   c                    |dz  dz  dz  }t        |dz  d       t        j                  |dz  dz        }t        |      }d}||k(  }|st	        j
                  d|fd	||f      d
t        j                         v st	        j                  t              rt	        j                  t              nd
dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}|d   }	d}
|
|	v }|st	        j
                  d|fd|
|	f      t	        j                  |
      dt        j                         v st	        j                  |	      rt	        j                  |	      nddz  }dd|iz  }t        t	        j                  |            dx}
}d}
|
|	v }|st	        j
                  d|fd|
|	f      t	        j                  |
      dt        j                         v st	        j                  |	      rt	        j                  |	      nddz  }dd|iz  }t        t	        j                  |            dx}
}d}
|
|	v }|st	        j
                  d|fd|
|	f      t	        j                  |
      dt        j                         v st	        j                  |	      rt	        j                  |	      nddz  }dd|iz  }t        t	        j                  |            dx}
}d}
|
|	v }|st	        j
                  d|fd|
|	f      t	        j                  |
      dt        j                         v st	        j                  |	      rt	        j                  |	      nddz  }dd|iz  }t        t	        j                  |            dx}
}|	d   }
d}|
|k\  }|slt	        j
                  d|fd|
|f      t	        j                  |
      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}
x}}y) uU   각 후보 항목은 path, size_bytes, days_old, category 필드를 가져야 한다.r    r!   r"   doc.pdf-   r$      r}   )z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenr?   r,   r-   r.   py6assert %(py8)spy8Nr   r   r%   z%(py1)s in %(py3)sr@   r-   r.   r   r/   
size_bytesdays_oldcategory>=z%(py1)s >= %(py4)sr-   rh   assert %(py6)sr   )r   r3   r4   r   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r>   r?   rA   rm   rB   @py_format7@py_format9r@   @py_assert0r   rC   rl   @py_format5s                  r   "test_candidate_has_required_fieldsz6TestCokacDirCleanup.test_candidate_has_required_fields   s   k)K7'Ax)+"588K9OR]9]^
:#!#!####!######s###s######:###:######!#######qMv{vv |q    |q   |      q   q       zQzQzQQzQzQzQQ}""}""""}"""}""""""""""r   )__name__
__module____qualname____doc__r   rE   rJ   rO   rU   rY   rn   rs   rv   r{   r   r   r^   r   r   r   r   1   s    U%$ %4 %%$ %4 %
&T 
&d 
&,4 ,D ,,4 ,D ,8D 8T 82d 2t 22d 2t 2
3$ 
34 
3   $  #4 #D #r   r   c                   `    e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfd	Z	y)
TestDoneClearCleanupu3   memory/events/*.done.clear 파일 정리 테스트.r   r
   Nc                    |dz  dz  }t        |dz  d      }t        j                  |      }|D cg c]  }|d   	 }}t        |      }||v }|s#t	        j
                  d|fd||f      d	t        j                         v st	        j                  t              rt	        j                  t              nd	d
t        j                         v st	        j                  |      rt	        j                  |      nd
t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }	dd|	iz  }
t        t	        j                  |
            dx}}yc c}w )uH   31일 된 .done.clear 파일은 삭제 후보로 감지되어야 한다.memoryeventsztask-2025.done.clearr#   r$   r   r%   r'   r(   old_filer*   r+   r0   r1   Nr   r3   find_done_clear_candidatesr(   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   
events_dirr   r?   r@   r*   rA   rB   rC   rD   s              r    test_old_done_clear_is_candidatez5TestDoneClearCleanup.test_old_done_clear_is_candidate   s    (83
!*/E"EBO22:>
$./q6//8}%}%%%%}%%%%%%s%%%s%%%%%%8%%%8%%%}%%%%%%%%%%%%%%%% 0   E:c                    |dz  dz  }t        |dz        }t        j                  |      }|D cg c]  }|d   	 }}t        |      }||v}|s#t	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      d	t        j                         v st	        j                  |      rt	        j                  |      nd	d
z  }	dd|	iz  }
t        t	        j                  |
            dx}}yc c}w )uH   30일 이내 .done.clear 파일은 삭제 후보가 아니어야 한다.r   r   zrecent.done.clearr   rQ   rS   r(   recentr*   r+   r0   r1   N)r   r3   r   r(   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r   r   r?   r@   r*   rA   rB   rC   rD   s              r   $test_recent_done_clear_not_candidatez9TestDoneClearCleanup.test_recent_done_clear_not_candidate   s    (83
":0C#CD22:>
$./q6//6{'{%''''{%''''''s'''s''''''6'''6'''{''''''%'''%''''''' 0   E8c                    |dz  dz  }t        |dz  d       t        j                  |      }g }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}}y)u?   *.done.clear가 아닌 파일은 포함되지 않아야 한다.r   r   znotes.mdr\   r$   r}   r   r?   r   r   r/   N)r   r3   r   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r   r?   rA   rk   r   rC   s           r   &test_non_done_clear_files_not_includedz;TestDoneClearCleanup.test_non_done_clear_files_not_included   s    (83
zJ.R822:>
zRzRzzRr   c                    |dz  dz  }t        j                  |      }g }||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)
uM   존재하지 않는 events 디렉토리는 빈 목록을 반환해야 한다.r   r   r}   r   r?   r   r   r/   N)
r3   r   r5   r6   r7   r8   r9   r:   r;   r<   r   s           r   )test_nonexistent_events_dir_returns_emptyz>TestDoneClearCleanup.test_nonexistent_events_dir_returns_empty   sz    X%0227;
zRzRzzRr   c                 "   |dz  dz  }t        |dz  d      }t        |dz  d      }t        |dz  d	      }t        j                  |      }|D cg c]  }|d
   	 }}t        |      }	|	|v }
|
s#t	        j
                  d|
fd|	|f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |	      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            dx}	}
t        |      }	|	|v }
|
s#t	        j
                  d|
fd|	|f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |	      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            dx}	}
t        |      }	|	|v }
|
s#t	        j
                  d|
fd|	|f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |	      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            dx}	}
yc c}w )uL   여러 개의 오래된 .done.clear 파일이 모두 포함되어야 한다.r   r   za.done.clearrH   r$   zb.done.clearrL   zc.done.clearr   r   r%   r'   r(   f1r*   r+   r0   r1   Nf2f3r   )r=   r   r   r   r   r   r?   r@   r*   rA   rB   rC   rD   s                r   $test_multiple_old_files_all_includedz9TestDoneClearCleanup.test_multiple_old_files_all_included   sA   (83
J7bAJ7bAJ7bA22:>
$./q6//2ww%w%ss22w%%2ww%w%ss22w%%2ww%w%ss22w%% 0s   P)
r   r   r   r   r   r   r   r   r   r   r^   r   r   r   r      sd    =& &$ &(T (d ( t     $  4   T  d  r   r   c                   `    e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfd	Z	y)
TestDispatchCleanupuB   memory/tasks/dispatch-*.md 파일 정리 테스트 (90일 초과).r   r
   Nc                    |dz  dz  }t        |dz  d      }t        j                  |      }|D cg c]  }|d   	 }}t        |      }||v }|s#t	        j
                  d|fd||f      d	t        j                         v st	        j                  t              rt	        j                  t              nd	d
t        j                         v st	        j                  |      rt	        j                  |      nd
t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }	dd|	iz  }
t        t	        j                  |
            dx}}yc c}w )uJ   91일 된 dispatch-*.md 파일은 삭제 후보로 감지되어야 한다.r   taskszdispatch-2024-001.md[   r$   r   r%   r'   r(   old_dispatchr*   r+   r0   r1   Nr   r3   find_dispatch_candidatesr(   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   	tasks_dirr   r?   r@   r*   rA   rB   rC   rD   s              r   test_old_dispatch_is_candidatez2TestDispatchCleanup.test_old_dispatch_is_candidate   s    x''1	%i2H&HrR00;
$./q6//< ) E)))) E))))))s)))s))))))<)))<))) ))))))E)))E))))))) 0r   c                    |dz  dz  }t        |dz        }t        j                  |      }|D cg c]  }|d   	 }}t        |      }||v}|s#t	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      d	t        j                         v st	        j                  |      rt	        j                  |      nd	d
z  }	dd|	iz  }
t        t	        j                  |
            dx}}yc c}w )uJ   90일 이내 dispatch-*.md 파일은 삭제 후보가 아니어야 한다.r   r   zdispatch-recent.mdr   rQ   rS   r(   r   r*   r+   r0   r1   N)r   r3   r   r(   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r   r   r?   r@   r*   rA   rB   rC   rD   s              r   "test_recent_dispatch_not_candidatez6TestDispatchCleanup.test_recent_dispatch_not_candidate   s    x''1	"9/C#CD00;
$./q6//6{'{%''''{%''''''s'''s''''''6'''6'''{''''''%'''%''''''' 0r   c                    |dz  dz  }t        |dz  d      }t        j                  |      }|D cg c]  }|d   	 }}t        |      }||v}|s#t	        j
                  d|fd||f      d	t        j                         v st	        j                  t              rt	        j                  t              nd	d
t        j                         v st	        j                  |      rt	        j                  |      nd
t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }	dd|	iz  }
t        t	        j                  |
            dx}}yc c}w )uR   정확히 90일 된 파일은 삭제 후보가 아니다 (90일 초과만 대상).r   r   zdispatch-border.mdZ   r$   r   rQ   rS   r(   borderr*   r+   r0   r1   Nr   )r=   r   r   r   r?   r@   r*   rA   rB   rC   rD   s              r   "test_exactly_90_days_not_candidatez6TestDispatchCleanup.test_exactly_90_days_not_candidate  s    x''1		,@ @rJ00;
$./q6//6{'{%''''{%''''''s'''s''''''6'''6'''{''''''%'''%''''''' 0r   c                    |dz  dz  }t        |dz  d       t        |dz  d       t        j                  |      }g }||k(  }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
z  }dd|iz  }t        t        j                  |            dx}}y)uI   dispatch-로 시작하지 않는 파일은 포함되지 않아야 한다.r   r   ztask-notes.mdd   r$   zother-dispatch.mdr}   r   r?   r   r   r/   N)r   r3   r   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r   r?   rA   rk   r   rC   s           r   !test_non_dispatch_md_not_includedz5TestDispatchCleanup.test_non_dispatch_md_not_included  s    x''1	y?2=y#66SA00;
zRzRzzRr   c                    |dz  dz  }t        j                  |      }g }||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)
uL   존재하지 않는 tasks 디렉토리는 빈 목록을 반환해야 한다.r   r   r}   r   r?   r   r   r/   N)
r3   r   r5   r6   r7   r8   r9   r:   r;   r<   r   s           r   (test_nonexistent_tasks_dir_returns_emptyz<TestDispatchCleanup.test_nonexistent_tasks_dir_returns_empty  sz    X%/009
zRzRzzRr   )
r   r   r   r   r   r   r   r   r   r   r^   r   r   r   r      sd    L*t * *(4 (D ((4 (D ( $  4     $  r   r   c                   p    e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfd	Z	deddfd
Z
y)TestLogCleanupu<   workspace/logs/*.log 파일 정리 테스트 (60일 초과).r   r
   Nc                    |dz  }t        |dz  d      }t        j                  |      }|D cg c]  }|d   	 }}t        |      }||v }|s#t	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              ndd	t        j                         v st	        j                  |      rt	        j                  |      nd	t	        j                  |      d
t        j                         v st	        j                  |      rt	        j                  |      nd
dz  }	dd|	iz  }
t        t	        j                  |
            dx}}yc c}w )uA   61일 된 .log 파일은 삭제 후보로 감지되어야 한다.logsapp.log=   r$   r   r%   r'   r(   old_logr*   r+   r0   r1   Nr   r3   find_log_candidatesr(   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   logs_dirr   r?   r@   r*   rA   rB   rC   rD   s              r   test_old_log_is_candidatez(TestLogCleanup.test_old_log_is_candidate'  s    f$ I!5B?++H5
$./q6//7|$|u$$$$|u$$$$$$s$$$s$$$$$$7$$$7$$$|$$$$$$u$$$u$$$$$$$ 0   E7c                    |dz  }t        |dz        }t        j                  |      }|D cg c]  }|d   	 }}t        |      }||v}|s#t	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      ndd	z  }	d
d|	iz  }
t        t	        j                  |
            dx}}yc c}w )uA   60일 이내 .log 파일은 삭제 후보가 아니어야 한다.r   zcurrent.logr   rQ   rS   r(   r   r*   r+   r0   r1   N)r   r3   r   r(   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r   r   r?   r@   r*   rA   rB   rC   rD   s              r   test_recent_log_not_candidatez,TestLogCleanup.test_recent_log_not_candidate1  s    f$"8m#;<++H5
$./q6//6{'{%''''{%''''''s'''s''''''6'''6'''{''''''%'''%''''''' 0s   E5c                 P   |dz  }t        |dz  d      }t        j                         }t        j                  |||f       t	        j
                  |      }|D cg c]  }|d   	 }}t        |      }||v}	|	s#t        j                  d|	fd||f      dt        j                         v st        j                  t              rt        j                  t              ndd	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
dz  }
dd|
iz  }t        t        j                  |            dx}}	yc c}w )uP   오늘 수정된 로그 파일은 스킵되어야 한다 (활성 로그 보존).r   z
active.logF   r$   r   rQ   rS   r(   
active_logr*   r+   r0   r1   N)r   r   r   r   r3   r   r(   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r   r   nowr?   r@   r*   rA   rB   rC   rD   s               r   test_today_modified_log_skippedz.TestLogCleanup.test_today_modified_log_skipped;  s   f$#H|$;"E
iik
c3Z(++H5
$./q6//:+e++++e++++++s+++s++++++:+++:+++++++++e+++e+++++++ 0s   F#c                    |dz  }t        |dz  d       t        j                  |      }g }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}}y)u8   *.log가 아닌 파일은 포함되지 않아야 한다.r   z	debug.txtr   r$   r}   r   r?   r   r   r/   N)r   r3   r   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r   r?   rA   rk   r   rC   s           r   test_non_log_files_not_includedz.TestLogCleanup.test_non_log_files_not_includedI  s    f$x+-C8++H5
zRzRzzRr   c                    |dz  }t        |dz  d      }t        j                  |      }|D cg c]  }|d   	 }}t        |      }||v}|s#t	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              ndd	t        j                         v st	        j                  |      rt	        j                  |      nd	t	        j                  |      d
t        j                         v st	        j                  |      rt	        j                  |      nd
dz  }	dd|	iz  }
t        t	        j                  |
            dx}}yc c}w )uR   정확히 60일 된 파일은 삭제 후보가 아니다 (60일 초과만 대상).r   z
border.logr\   r$   r   rQ   rS   r(   r   r*   r+   r0   r1   Nr   )r=   r   r   r   r?   r@   r*   rA   rB   rC   rD   s              r   "test_exactly_60_days_not_candidatez1TestLogCleanup.test_exactly_60_days_not_candidateR  s    f$< 7bA++H5
$./q6//6{'{%''''{%''''''s'''s''''''6'''6'''{''''''%'''%''''''' 0r   c                    |dz  }t        j                  |      }g }||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}y)	uK   존재하지 않는 logs 디렉토리는 빈 목록을 반환해야 한다.r   r}   r   r?   r   r   r/   N)
r3   r   r5   r6   r7   r8   r9   r:   r;   r<   r   s           r   'test_nonexistent_logs_dir_returns_emptyz6TestLogCleanup.test_nonexistent_logs_dir_returns_empty\  su    V#++G4
zRzRzzRr   )r   r   r   r   r   r   r   r   r   r   r   r^   r   r   r   r   $  sw    F%$ %4 %(d (t (, , ,     (4 (D (     r   r   c                       e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfd	Z	deddfd
Z
deddfdZdeddfdZy)TestSafetyGuardsu7   삭제 금지 대상 보호 및 안전장치 테스트.r   r
   Nc                    g d}|D ]*  }||z  }|j                  dd       t        |dz  d       , t        j                  |      }|D ]  }||z  dz  }|j                  } ||      }|st        j                  | d      d	z   d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }	t        t        j                  |	            dx}} y)u^   보호된 디렉토리 내 파일은 어떤 카테고리에서도 대상이 되면 안 된다.)zmemory/reportszmemory/researchzmemory/specszmemory/meetingszmemory/plansTr   zimportant.mdr   r$   base_dir should be protectedP
>assert %(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s.is_protected
}(%(py3)s)
}checker	test_pathr,   rg   r.   r/   N)r   r   r3   SafetyCheckeris_protectedr5   _format_assertmsgr7   r8   r9   r:   r;   r<   )
r=   r   protected_dirsdra   r   r   rk   rB   rC   s
             r   "test_protected_dirs_never_targetedz3TestSafetyGuards.test_protected_dirs_never_targetedm  s   
   	9A1AGGD4G01~-C8	9
 ""H5 	OA 1~5I''N'	2N2NNqc9M4NNNNNNN7NNN7NNN'NNNNNN	NNN	NNN2NNNNNN	Or   c                 l   g d}t        j                  |      }|D ]  }||z  }t        |       |j                  } ||      }|st	        j
                  | d      dz   dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }t        t	        j                  |            dx}} y)	uK   CLAUDE.md, MEMORY.md, .env, .env.keys는 절대 삭제 대상이 아니다.)r[   z	MEMORY.mdz.envz	.env.keysr   r   r   r   r   r   N)r3   r   r   r   r5   r   r7   r8   r9   r:   r;   r<   )	r=   r   protected_namesr   namer   rk   rB   rC   s	            r   test_protected_filenamesz)TestSafetyGuards.test_protected_filenames  s    I""H5# 	RD 4Ii(''Q'	2Q2QQtf<P4QQQQQQQ7QQQ7QQQ'QQQQQQ	QQQ	QQQ2QQQQQQ	Rr   c                    t        j                  |      }|dz  dz  }|dz  dz  dz  }|j                  } ||      }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
z  }t        t        j                  |            dx}}|j                  } ||      }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d
z  }t        t        j                  |            dx}}y)u9   .git, .worktrees 관련 파일은 보호되어야 한다.r   z.gitconfigz
.worktreesbranchzfile.pyNassert %(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s.is_protected
}(%(py3)s)
}r   git_pathr   Nworktree_path
r3   r   r   r7   r8   r5   r9   r:   r;   r<   )r=   r   r   r   r  rk   rB   rC   s           r   test_git_files_protectedz)TestSafetyGuards.test_git_files_protected  s3   ""H5f$x/ </(:YF##-#H--------w---w---#------H---H----------##2#M22222222w222w222#222222M222M2222222222r   c                    t        j                  |      }|dz  dz  dz  dz  }|j                  } ||      }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }t        t        j                  |            d
x}}y
)uG   /home/jay/projects/ 내 파일은 절대 건드리지 않아야 한다.r   projectsmyappsrczmain.pyr   r   projects_pathr   Nr  )r=   r   r   r  rk   rB   rC   s          r   test_projects_dir_protectedz,TestSafetyGuards.test_projects_dir_protected  s    ""H5 :-7%?)K##2#M22222222w222w222#222222M222M2222222222r   c                    |dz  }t        |dz  d      }t        j                  |      }t        j                  |d      }|j                  } |       }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d	z  }t        t        j                  |            d
x}}|d   }	d}|	|k(  }
|
slt        j                  d|
fd|	|f      t        j                  |	      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}	x}
}|d   }	d}|	|u }
|
slt        j                  d|
fd|	|f      t        j                  |	      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}	x}
}y
)uJ   dry-run 모드에서는 파일이 실제로 삭제되지 않아야 한다.r   old.logr   r$   Tdry_runAassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}r   rf   Ndeleted_countr   r}   z%(py1)s == %(py4)sr   r   r   r  isz%(py1)s is %(py4)sr   r3   r   execute_cleanupexistsr7   r8   r5   r9   r:   r;   r<   r6   )r=   r   r   r   r?   resultrk   rl   r   r   rA   r   s               r   test_dry_run_does_not_deletez-TestSafetyGuards.test_dry_run_does_not_delete  s?   f$ I!5B?++H5
##J= ~~~ww~o&+!+&!++++&!+++&+++!+++++++i (D( D(((( D((( (((D(((((((r   c                 P   |dz  }t        |dz  d      }t        |dz  d      }t        j                  |      }t        j                  |d      }|j                  } |       }| }	|	sd	d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }
t        t        j                  |
            dx}x}}	|j                  } |       }| }	|	sd	dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }
t        t        j                  |
            dx}x}}	|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)uE   --execute 모드에서는 파일이 실제로 삭제되어야 한다.r   zold1.logr   r$   zold2.logA   Fr  Eassert not %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}old_log1rf   Nold_log2r     r}   r  r   r   r   r  r  r  r  )r=   r   r   r  r  r?   r  rk   rl   rm   rC   r   rA   r   r   s                  r   test_execute_mode_deletes_filesz0TestSafetyGuards.test_execute_mode_deletes_files  s   f$!(Z"7bA!(Z"7bA++H5
##J>??$?$$$$$$$$$$$8$$$8$$$?$$$$$$$$$$??$?$$$$$$$$$$$8$$$8$$$?$$$$$$$$$$o&+!+&!++++&!+++&+++!+++++++i )E) E)))) E))) )))E)))))))r   c                    |dz  }t        |dz  dd       t        |dz  dd       t        |d	z  d
d       t        j                  |      }t        j                  |d      }t	        |      }d}||k  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}y)u[   각 카테고리에서 최소 1개 파일은 보존되어야 한다 (전체 삭제 방지).r   a.logr   aaar   r	   b.logr  bbbzc.logr   cccr   keepr  )<=)z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} <= %(py6)sr   safe_candidatesr   r   r   N)r   r3   r   apply_minimum_keepr   r5   r6   r7   r8   r9   r:   r;   r<   )
r=   r   r   r?   r*  rA   rm   rB   r   r   s
             r   "test_minimum_keep_one_per_categoryz3TestSafetyGuards.test_minimum_keep_one_per_category  s    f$x')EBx')EBx')EB++H5
//
C ?#(q(#q((((#q((((((s(((s((((((?(((?(((#(((q(((((((r   c                 ~   t        j                  g d      }g }||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	)
uR   후보가 없는 경우 최소 보존 규칙은 빈 목록을 반환해야 한다.r   r'  r}   r   r  r   r   r/   N)
r3   r+  r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r  rA   rk   r   rC   s          r   "test_minimum_keep_empty_candidatesz3TestSafetyGuards.test_minimum_keep_empty_candidates  sl    &&r2v|vvvr   )r   r   r   r   r   r   r   r  r	  r  r  r,  r.  r^   r   r   r   r   j  s    AO4 OD O&R R$ R3 3$ 33D 3T 3)T )d )* * *)4 )D )4 D r   r   c                   0    e Zd ZdZdeddfdZdeddfdZy)TestCleanupLogu   삭제 로그 기록 테스트.r   r
   Nc                 H   |dz  }|dz  }t        |dz  d       t        j                  |      }t        j                  |d|       |j                  } |       }|sdd	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }t        t        j                  |            dx}}|j                         }d}	|	|v }
|
st        j                  d|
fd|	|f      t        j                  |	      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}	}
y)uN   --execute 모드에서 삭제 후 로그 파일에 기록이 남아야 한다.r   file-cleanup.logr  r   r$   Fr  cleanup_log_pathr  cleanup_logrf   Nr%   r   r	   r   r   r/   )r   r3   r   r  r  r7   r8   r5   r9   r:   r;   r<   	read_textr6   )r=   r   r   r5  r?   rk   rl   r   r	   r   rA   r   rC   s                r   #test_cleanup_log_written_on_executez2TestCleanupLog.test_cleanup_log_written_on_execute  s   f$!33x)+"5++H5

:u{S!!#!########{###{###!##########'')#yG####yG###y######G###G#######r   c                    |dz  }|dz  }t        |dz  d       t        j                  |      }t        j                  |d|       |j                  } |       }| }|sdd	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }t        t        j                  |            dx}x}}y)uG   dry-run 모드에서는 로그 파일이 생성되지 않아야 한다.r   r2  r  r   r$   Tr3  r  r5  rf   N)r   r3   r   r  r  r7   r8   r5   r9   r:   r;   r<   )	r=   r   r   r5  r?   rk   rl   rm   rC   s	            r   'test_cleanup_log_not_written_on_dry_runz6TestCleanupLog.test_cleanup_log_not_written_on_dry_run  s    f$!33x)+"5++H5

:tkR%%'%''''''''''';''';'''%''''''''''r   )r   r   r   r   r   r7  r9  r^   r   r   r0  r0    s+    )$D $T $	( 	( 	(r   r0  c                   P    e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZy)	TestReportModeu   --report 모드 테스트.r   r
   Nc                    t        j                  |      }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d	x}}d
}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d	x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d	x}}y	)uB   report 함수는 필수 키를 갖는 dict를 반환해야 한다.r   
disk_usager%   r   r  r   r   r/   Ncleanup_candidatestotal_reclaimable_mb)
r3   generate_reportr5   r6   r:   r7   r8   r9   r;   r<   r=   r   r  r   rA   r   rC   s          r   +test_report_returns_dict_with_required_keysz:TestReportMode.test_report_returns_dict_with_required_keys  s#   ##X6%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%#-#v----#v---#------v---v-------%/%////%///%////////////////r   c                    t        j                  |      }|d   }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}|d   }t        t        f}	t        ||	      }
|
sddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |	      t        j                  |
      dz  }t        t        j                  |            d
x}x}	}
y
)u@   disk_usage 섹션에는 total_mb와 free_mb가 있어야 한다.r   r=  total_mbr%   r   dur   r   r/   Nfree_mbused_mbz5assert %(py6)s
{%(py6)s = %(py0)s(%(py2)s, %(py4)s)
}
isinstance)r,   rg   rh   r   )r3   r@  r5   r6   r:   r7   r8   r9   r;   r<   intfloatrH  )r=   r   r  rE  r   rA   r   rC   rk   rl   rm   r   s               r   )test_report_disk_usage_has_total_and_freez8TestReportMode.test_report_disk_usage_has_total_and_free  s   ##X6L!zRzRzRRyByByBByByByBBZ.73,7z.,77777777z777z777.777,7777777777r   c                    |dz  }t        |dz  dd       t        |dz  dd       t        j                  ||	      }|d
   }d}||k\  }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)uN   total_reclaimable_mb는 삭제 가능 파일들의 크기 합계여야 한다.r   r!  r      AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAr#  r$  r     BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB)r   r   r?  r   r   r   r   r   r   N)r   r3   r@  r5   r6   r:   r;   r<   )	r=   r   r   r  r   rl   rA   r   r   s	            r   ,test_report_reclaimable_is_sum_of_candidatesz;TestReportMode.test_report_reclaimable_is_sum_of_candidates  s    f$x')JGx')JG##XI,-22-2222-222-2222222222r   c                 z   t        j                  |      }|d   }t        |t              }|sddt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      nddt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d}y)	u@   cleanup_candidates는 카테고리별로 분류되어야 한다.r   r>  z5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}rH  r?   dict)r,   r-   rg   rh   N)r3   r@  rH  rQ  r7   r8   r5   r9   r:   r;   r<   )r=   r   r  r?   rl   r   s         r   *test_report_cleanup_candidates_by_categoryz9TestReportMode.test_report_cleanup_candidates_by_category  s    ##X601
*d++++++++z+++z++++++*+++*++++++d+++d++++++++++r   )	r   r   r   r   r   rB  rK  rO  rR  r^   r   r   r;  r;    sQ    $0D 0T 08$ 84 83T 3d 3,4 ,D ,r   r;  c                   p    e Zd ZdZdeddfdZdeddfdZdeddfdZdeddfdZdeddfd	Z	deddfd
Z
y)TestOrganizeModeu   --organize 모드 테스트.r   r
   Nc                    |dz  dz  dz  }t        |dz        }t        j                  |dz  dz  |dz  |dz        }|D cg c]  }|d   	 }}t        |      }||v }|s#t	        j
                  d	|fd
||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }	dd|	iz  }
t        t	        j                  |
            dx}}yc c}w )uB   cokacdir workspace에서 이동 대상 파일을 찾아야 한다.r    r!   r"   zdocument.pdfuploadsr  cokacdir_workspaceuploads_baseprojects_basesourcer%   r'   r(   pdf_filesourcesr+   r0   r1   N)r   r3   find_organize_candidatesr(   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r>   r\  movesmr]  rA   rB   rC   rD   s              r   $test_organize_find_files_in_cokacdirz5TestOrganizeMode.test_organize_find_files_in_cokacdir  s   k)K7'A$X%>?++'+5C!I-"Z/
 )..11X;..8}'}''''}''''''s'''s''''''8'''8'''}'''''''''''''''' /s   F
c                 0   |dz  dz  dz  }t        |dz         t        j                  |dz  dz  |dz  |dz        }t        |      }d}||k\  }|st	        j
                  d	|fd
||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}|d   }	d}
|	d   }|
|v }|slt	        j
                  d|fd|
|f      t	        j                  |
      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}
x}}t        |	d         }|j                  }|j                  }t        j                   } |       }|j"                  }d} ||      }||k(  }|sZt	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      t	        j                  |      t	        j                  |      t	        j                  |      t	        j                  |      d	z  }dd|iz  }t        t	        j                  |            dx}x}x}x}x}x}x}}y) uP   프로젝트명이 없는 파일은 uploads/YYYY-MM/ 으로 이동해야 한다.r    r!   r"   zrandom-doc.pdfrV  r  rW  r   r   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} >= %(py6)sr   r_  r   r   r   Nr   destinationr%   )z%(py1)s in %(py4)sr   r   r   z%Y-%mr}   )z%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.parent
}.name
} == %(py16)s
{%(py16)s = %(py12)s
{%(py12)s = %(py10)s
{%(py10)s = %(py8)s
{%(py8)s = %(py6)s.now
}()
}.strftime
}(%(py14)s)
}destr   )	r,   rg   rh   r   r   py10py12py14py16zassert %(py18)spy18)r   r3   r^  r   r5   r6   r7   r8   r9   r:   r;   r<   r   r   r   r   r   strftime)r=   r   r>   r_  rA   rm   rB   r   r   r`  r   rl   r   re  rk   @py_assert7@py_assert9@py_assert11@py_assert13@py_assert15@py_format17@py_format19s                         r   )test_organize_moves_to_uploads_by_defaultz:TestOrganizeMode.test_organize_moves_to_uploads_by_default)  sF   k)K7'A(%556++'+5C!I-"Z/
 5zQzQzQss55zQ!H,Am,,y,,,,,y,,,,y,,,,,,,,,,,Am$%{{C{C8<<C<>C>#:#:C7C#:7#CC#CCCCC#CCCCCCCtCCCtCCC{CCCCCCCCC8CCC8CCC<CCC>CCC#:CCC7CCC#CCCCCCCCCr   c                    |dz  dz  dz  }t        |dz        }t        j                  |dz  dz  |dz  |dz        }t        j                  |d	       |j                  } |       }|sd
dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}y)uJ   dry-run 모드에서는 파일이 실제로 이동되지 않아야 한다.r    r!   r"   r   rV  r  rW  Tr  r  r\  rf   Nr   r3   r^  execute_organizer  r7   r8   r5   r9   r:   r;   r<   )r=   r   r>   r\  r_  rk   rl   r   s           r   #test_organize_dry_run_does_not_movez4TestOrganizeMode.test_organize_dry_run_does_not_move;  s    k)K7'A$X	%9:++'+5C!I-"Z/

 	E40          x   x             r   c                    |dz  dz  dz  }t        |dz        }t        j                  |dz  dz  |dz  |dz        }t        j                  |d	       |j                  } |       }| }|sd
dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}x}}y)uC   execute 모드에서는 파일이 실제로 이동되어야 한다.r    r!   r"   r   rV  r  rW  Fr  r  r\  rf   Nru  )	r=   r   r>   r\  r_  rk   rl   rm   rC   s	            r   !test_organize_execute_moves_filesz2TestOrganizeMode.test_organize_execute_moves_filesJ  s    k)K7'A$X	%9:++'+5C!I-"Z/

 	E51 ??$?$$$$$$$$$$$8$$$8$$$?$$$$$$$$$$r   c                    t        j                  |dz  dz  |dz  |dz        }g }||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}}y)uJ   존재하지 않는 workspace는 빈 이동 목록을 반환해야 한다.r    r!   rV  r  rW  r}   r   r_  r   r   r/   N)
r3   r^  r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r_  rA   rk   r   rC   s          r   1test_organize_nonexistent_workspace_returns_emptyzBTestOrganizeMode.test_organize_nonexistent_workspace_returns_emptyY  s    ++'+5C!I-"Z/

 u{uuur   c                    |dz  dz  dz  }t        |dz         t        j                  |dz  dz  |dz  |dz        }t        |      }d}||k\  }|st	        j
                  d	|fd
||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}|d   }	d}
|
|	v }|st	        j
                  d|fd|
|	f      t	        j                  |
      dt        j                         v st	        j                  |	      rt	        j                  |	      nddz  }dd|iz  }t        t	        j                  |            dx}
}d}
|
|	v }|st	        j
                  d|fd|
|	f      t	        j                  |
      dt        j                         v st	        j                  |	      rt	        j                  |	      nddz  }dd|iz  }t        t	        j                  |            dx}
}d}
|
|	v }|st	        j
                  d|fd|
|	f      t	        j                  |
      dt        j                         v st	        j                  |	      rt	        j                  |	      nddz  }dd|iz  }t        t	        j                  |            dx}
}y)uL   각 이동 후보는 source, destination, reason 필드를 가져야 한다.r    r!   r"   r   rV  r  rW  r   r   rc  r   r_  r   r   r   Nr   r[  r%   r   r`  r   r   r/   rd  reason)r   r3   r^  r   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r>   r_  rA   rm   rB   r   r   r`  r   r   rC   s                r   +test_organize_candidate_has_required_fieldsz<TestOrganizeMode.test_organize_candidate_has_required_fieldsb  s   k)K7'A(Y./++'+5C!I-"Z/
 5zQzQzQss55zQ!Hx1}x1x11!}!!!!}!!!}!!!!!!!!!!!!!!!!x1}x1x11r   )r   r   r   r   r   ra  rs  rw  ry  r{  r~  r^   r   r   rT  rT    s{    &(T (d (D$ D4 D$!D !T !%$ %4 %$ SW D T r   rT  c                   @    e Zd ZdZdeddfdZdeddfdZdeddfdZy)TestAllCandidatesu&   전체 정리 후보 수집 테스트.r   r
   Nc                    t        j                  |dz  dz  |dz  dz  |dz  dz  |dz        }d}||v }|st        j                  d	|fd
||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}d}||v }|st        j                  d	|fd
||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}d}||v }|st        j                  d	|fd
||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}d}||v }|st        j                  d	|fd
||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}y)uN   collect_all_candidates는 카테고리별 딕셔너리를 반환해야 한다.r    r!   r   r   r   r   rX  r   r   r   cokacdirr%   r   r  r   r   r/   N
done_cleardispatch)
r3   collect_all_candidatesr5   r6   r:   r7   r8   r9   r;   r<   rA  s          r   (test_collect_all_candidates_returns_dictz:TestAllCandidates.test_collect_all_candidates_returns_dict|  s   **'+5C(*X5)G3&	
 #zV####zV###z######V###V#######%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%#zV####zV###z######V###V#######vvvr   c                 `   t        j                  |dz  dz  |dz  dz  |dz  dz  |dz        }|d   }g }||k(  }|slt        j                  d	|fd
||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|d   }g }||k(  }|slt        j                  d	|fd
||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|d   }g }||k(  }|slt        j                  d	|fd
||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|d   }g }||k(  }|slt        j                  d	|fd
||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)uW   모든 디렉토리가 비어 있으면 각 카테고리가 빈 목록이어야 한다.r    r!   r   r   r   r   r  r  r}   r  r   r   r   Nr  r  )r3   r  r5   r6   r:   r;   r<   )r=   r   r  r   rl   rA   r   r   s           r   &test_collect_all_candidates_empty_dirsz8TestAllCandidates.test_collect_all_candidates_empty_dirs  s   **'+5C(*X5)G3&	
 j!'R'!R''''!R'''!'''R'''''''l#)r)#r))))#r)))#)))r)))))))j!'R'!R''''!R'''!'''R'''''''f~##~####~###~##########r   c                 R   |dz  dz  dz  }|dz  dz  }|dz  dz  }|dz  }t        |dz  d	
       t        |dz  d	
       t        |dz  d
       t        |dz  d
       t        j                  |dz  dz  |||      }|d   }t        |      }d}	||	k\  }
|
st	        j
                  d|
fd||	f      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      t	        j                  |      t	        j                  |	      dz  }dd|iz  }t        t	        j                  |            dx}x}x}
}	|d   }t        |      }d}	||	k\  }
|
st	        j
                  d|
fd||	f      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      t	        j                  |      t	        j                  |	      dz  }dd|iz  }t        t	        j                  |            dx}x}x}
}	|d   }t        |      }d}	||	k\  }
|
st	        j
                  d|
fd||	f      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      t	        j                  |      t	        j                  |	      dz  }dd|iz  }t        t	        j                  |            dx}x}x}
}	|d   }t        |      }d}	||	k\  }
|
st	        j
                  d|
fd||	f      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      t	        j                  |      t	        j                  |	      dz  }dd|iz  }t        t	        j                  |            dx}x}x}
}	y)uG   여러 카테고리에 후보가 있을 때 모두 수집해야 한다.r    r!   p1r   r   r   r   zold.pdfr#   r$   zevent.done.clearzdispatch-001.mdr   z
system.logr   r  r  r   r   )z0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} >= %(py7)sr   )r,   rg   rh   r1   zassert %(py9)spy9Nr  r  )r   r3   r  r   r5   r6   r7   r8   r9   r:   r;   r<   )r=   r   r>   r   r   r   r  rk   rl   @py_assert6rm   rD   @py_format10s                r   /test_collect_all_candidates_multiple_categorieszATestAllCandidates.test_collect_all_candidates_multiple_categories  s   k)K7$>(83
x''1	f$x)+"5z$66R@y#442>x,.R8**'+5C!	
 *%+s%&+!+&!++++&!++++++s+++s+++%+++&+++!+++++++,'-s'(-A-(A----(A------s---s---'---(---A-------*%+s%&+!+&!++++&!++++++s+++s+++%+++&+++!+++++++&>'s>"'a'"a''''"a''''''s'''s'''>'''"'''a'''''''r   )r   r   r   r   r   r  r  r  r^   r   r   r  r  y  s?    0   $  $t $ $( (QU (r   r  c                   0    e Zd ZdZdeddfdZdeddfdZy)TestSafetyCheckerIntegrationuP   SafetyChecker가 실제 후보 수집과 통합되어 동작하는지 테스트.r   r
   Nc                    |dz  }t        |dz  d      }t        |dz  d      }t        j                  |      }|D cg c]  }|d   	 }}t        |      }||v}	|	s#t	        j
                  d|	fd	||f      d
t        j                         v st	        j                  t              rt	        j                  t              nd
dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }
dd|
iz  }t        t	        j                  |            dx}}	t        |      }||v }	|	s#t	        j
                  d|	fd||f      d
t        j                         v st	        j                  t              rt	        j                  t              nd
dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }
dd|
iz  }t        t	        j                  |            dx}}	yc c}w )uF   file-cleanup.log 자체는 로그 후보에서 제외되어야 한다.r   r2  r   r$   r   r   r   rQ   rS   r(   r5  r*   r+   r0   r1   Nr%   r'   old_app_logr   )r=   r   r   r5  r  r?   r@   r*   rA   rB   rC   rD   s               r   0test_protected_path_excluded_from_log_candidateszMTestSafetyCheckerIntegration.test_protected_path_excluded_from_log_candidates  s   f$$X0B%BL$X	%9C++H5
$./q6//;,u,,,,u,,,,,,s,,,s,,,,,,;,,,;,,,,,,,,,u,,,u,,,,,,,;(5((((5((((((s(((s((((((;(((;(((((((((5(((5(((((((	 0s   K c                    t        j                  |      }|dz  dz  }t        |       |j                  } ||      }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      d	z  }d
d|iz  }	t        t	        j                  |	            dx}x}x}}y)u>   일반 파일은 is_protected가 False를 반환해야 한다.r   r   r  Fr  )zR%(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s.is_protected
}(%(py3)s)
} is %(py8)sr   normal_file)r,   rg   r.   r/   r   zassert %(py10)srf  N)r3   r   r   r   r5   r6   r7   r8   r9   r:   r;   r<   )
r=   r   r   r  rk   rB   rl  r  r   @py_format11s
             r   $test_safety_checker_is_not_protectedzATestSafetyCheckerIntegration.test_safety_checker_is_not_protected  s    ""H5')3+&##9#K09E90E99990E999999w999w999#999999K999K9990999E9999999r   )r   r   r   r   r   r  r  r^   r   r   r  r    s,    Z) )RV ):T :d :r   r  )data)&r   builtinsr7   _pytest.assertion.rewrite	assertionrewriter5   r   sysr   r   pathlibr   typingr   unittest.mockr   pytestr   insertr(   __file__r   file_cleanupr3   rI  r   r   r   r   r   r   r   r0  r;  rT  r  r  r^   r   r   <module>r     s    	 
       3tH~,,334 5  S 3 D D 3 D v# v#|4  4 x1  1 r>  > LY YB( (B$, $,XY YB5( 5(z: :r   