
    Hi                       d Z ddlm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ZddlZddlZddlmZmZmZ ddlmZ ddlZddlZ ed      Z ed      Z ej0                  d      Zh 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  test_absorption_health_check.py

absorption-health-check 시스템 단위/통합 테스트 (Heimdal, dev2-team)

커버리지:
  1. YAML 파싱 테스트
  2. 헬스체크 로직 단위 테스트 (file_exists, file_recent_activity,
     grep_pattern, audit_trail_recent, process_running)
  3. 출력 포맷 테스트
  4. CLI 필터 테스트 (--source, --status, --summary)
  5. 통합 테스트 (실제 스크립트 실행)
    )annotationsN)datetime	timedeltatimezone)Pathz6/home/jay/workspace/scripts/absorption-health-check.pyz3/home/jay/workspace/config/absorption-registry.yamlu      version: "1.0"
    sources:
      memory:
        description: "Memory & Learning 시스템"
        items:
          - id: "mem-001"
            name: "Memory Indexer"
            priority: high
            source_section: "memory"
            status: active
            health_check:
              type: file_exists
              path: ~/workspace/scripts/memory-indexer.py
          - id: "mem-002"
            name: "Memory Janitor"
            priority: medium
            source_section: "memory"
            status: implemented
            health_check:
              type: file_recent_activity
              path: ~/workspace/logs/memory-janitor.log
              max_age_hours: 48
      security:
        description: "보안 시스템"
        items:
          - id: "sec-001"
            name: "Secret Rotation Check"
            priority: high
            source_section: "security"
            status: recommended
            health_check:
              type: grep_pattern
              pattern: "ROTATION_POLICY"
              target: ~/workspace/scripts/secret-rotation-check.py
          - id: "sec-002"
            name: "Audit Trail"
            priority: medium
            source_section: "security"
            status: degraded
            health_check:
              type: audit_trail_recent
              path: ~/workspace/logs/audit.jsonl
              max_age_hours: 24
      infra:
        description: "인프라 시스템"
        items:
          - id: "inf-001"
            name: "Activity Watcher"
            priority: high
            source_section: "infra"
            status: active
            health_check:
              type: process_running
              process_name: activity-watcher
          - id: "inf-002"
            name: "Duplicate Checker"
            priority: low
            source_section: "infra"
            status: duplicate
            health_check:
              type: file_exists
              path: ~/workspace/scripts/activity-watcher.py
>   activedegraded	duplicateimplementedrecommendedc                      e Zd ZdZ ej
                         dd       Z ej
                         dd       ZddZddZ	ddZ
ddZddZdd	Zh d
ZddZddZddZddZddZddZy)TestYamlParsingu   YAML 파싱 및 구조 검증c                4    t        j                  t              S )u/   샘플 YAML 픽스처를 파싱한 딕셔너리)yaml	safe_loadSAMPLE_REGISTRY_YAMLselfs    9/home/jay/workspace/tests/test_absorption_health_check.pyregistry_datazTestYamlParsing.registry_datav   s     ~~233    c                >    |dz  }|j                  t        d       |S )u%   tmp_path에 샘플 YAML 파일 생성zabsorption-registry.yamlutf-8encoding)
write_textr   )r   tmp_pathps      r   registry_filezTestYamlParsing.registry_file{   s%     11	)G<r   c                   t        j                  |j                               }d}||u}|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)u4   YAML 파일이 오류 없이 로드되어야 한다.Nis notz%(py0)s is not %(py3)sdatapy0py3assert %(py5)spy5)r   r   	read_text
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation)r   r   r$   @py_assert2@py_assert1@py_format4@py_format6s          r   test_yaml_loads_without_errorz-TestYamlParsing.test_yaml_loads_without_error   sr    ~~m5578t4t4tt4r   c                   t         j                         st        j                  d       t	        j
                  t         j                               }d}||u}|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)	uF   실제 레지스트리가 존재하면 로드할 수 있어야 한다.u(   실제 registry 파일 없음 — 스킵Nr!   r#   r$   r%   r(   r)   )REGISTRY_PATHexistspytestskipr   r   r*   r+   r,   r-   r.   r/   r0   r1   r2   )r   r$   r3   r4   r5   r6   s         r   "test_real_registry_loads_if_existsz2TestYamlParsing.test_real_registry_loads_if_exists   s    ##%KKBC~~m5578t4t4tt4r   c                N   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)	u&   version 필드가 존재해야 한다.versioninz%(py1)s in %(py3)sr   py1r'   r(   r)   N)r+   r,   r0   r-   r.   r/   r1   r2   )r   r   @py_assert0r3   r5   r6   s         r   test_has_version_fieldz&TestYamlParsing.test_has_version_field   s[    )yM))))yM)))y))))))M)))M)))))))r   c                @   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              }|sd	d
t        j                         v st        j
                  t              rt        j                  t              nd
t        j                  |      dt        j                         v st        j
                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            dx}}y)u,   sources 필드가 딕셔너리여야 한다.sourcesr@   rB   r   rC   r(   r)   N5assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}
isinstancedictr&   py2r'   r)   )
r+   r,   r0   r-   r.   r/   r1   r2   rJ   rK   )r   r   rE   r3   r5   r6   r4   @py_assert4s           r   test_has_sources_dictz%TestYamlParsing.test_has_sources_dict   s    )yM))))yM)))y))))))M)))M)))))))'	29z2D99999999z999z9992999999D999D9999999999r   c                   |d   }t        |      }d}||kD  }|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	)
u=   sources 에 최소 1개 이상의 소스가 있어야 한다.rH   r   )>)z/%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} > %(py7)slen)r&   rM   py4py7assert %(py9)spy9N)	rR   r+   r,   r-   r.   r/   r0   r1   r2   )r   r   r4   @py_assert3@py_assert6@py_assert5@py_format8@py_format10s           r   test_sources_not_emptyz&TestYamlParsing.test_sources_not_empty   s     +0s+,0q0,q0000,q000000s000s000+000,000q0000000r   c                   |d   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  }t        j                  d| d      d	z   d
|iz  }t        t        j                  |            dx}}|d   }t        |t              }	|	st        j                  d| d      dz   dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |	      dz  }t        t        j                  |            dx}}	 y)u3   각 source 는 items 리스트를 가져야 한다.rH   itemsr@   rB   srcrC   'u   ' 에 items 없음
>assert %(py5)sr)   Nu   '.items 가 list 가 아님z7
>assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}rJ   listrL   )r^   r+   r,   r0   r-   r.   r/   _format_assertmsgr1   r2   rJ   rb   )
r   r   src_namer_   rE   r3   r5   r6   r4   rN   s
             r   test_each_source_has_items_listz/TestYamlParsing.test_each_source_has_items_list   s$   *95;;= 	]MHcC7c>CCC7cCCC7CCCCCCcCCCcCCCCQxj0B#CCCCCCC!'l\:lD1\1\\Qxj@[3\\\\\\\:\\\:\\\l\\\\\\D\\\D\\\1\\\\\\	]r   >   idnamestatuspriorityhealth_checksource_sectionc              #  t   K   |d   j                         D ]  }|j                  dg       E d {     y 7 w)NrH   r^   )valuesget)r   r   r_   s      r   
_all_itemszTestYamlParsing._all_items   s:      +224 	,Cwww+++	,+s   ,868c                   | j                  |      D ]  }| j                  t        |j                               z
  }| }|st	        j
                  d|j                  d       d|       dz   ddt        j                         v st	        j                  |      rt	        j                  |      ndiz  }t        t	        j                  |            d} y)u5   모든 item 에 필수 필드가 존재해야 한다.item 'rf      ' 에 필드 누락: 
>assert not %(py0)sr&   missingN)ro   REQUIRED_ITEM_FIELDSsetkeysr+   rc   rn   r-   r.   r/   r0   r1   r2   )r   r   itemrt   r4   @py_format2s         r   test_items_have_required_fieldsz/TestYamlParsing.test_items_have_required_fields   s    OOM2 	XD//#diik2BBG;W;WW&$(88MgY WWWWWWWwWWWwWWWWWW	Xr   c                   | j                  |      D ]  }|d   }|t        v }|st        j                  d|fd|t        f      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }t        j                  d|j                  d       d|d    d	      d
z   d|iz  }t        t        j                  |            dx}} y)u4   status 값이 허용된 값 중 하나여야 한다.rh   r@   rB   VALID_STATUSESrC   rq   rf   u   ' 의 status '   ' 가 유효하지 않음ra   r)   N)ro   r|   r+   r,   r0   r-   r.   r/   rc   rn   r1   r2   )r   r   rx   rE   r3   r5   r6   s          r   test_status_values_are_validz,TestYamlParsing.test_status_values_are_valid   s    OOM2 	D> >^3  >^  I "  v   &4  I &4    $(tH~6FF_`    	r   c                   | j                  |      D ]  }|j                  di       }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d|j                  d       d	      d
z   d|iz  }t        t        j                  |            dx}} y)u=   health_check 딕셔너리에 type 필드가 있어야 한다.rj   typer@   rB   hcrC   rq   rf   u"   ' 의 health_check 에 type 없음ra   r)   N)ro   rn   r+   r,   r0   r-   r.   r/   rc   r1   r2   )r   r   rx   r   rE   r3   r5   r6   s           r   test_health_check_has_typez*TestYamlParsing.test_health_check_has_type   s    OOM2 	]D."-B\6R<\\\6R\\\6\\\\\\R\\\R\\\\6$((4.)99[!\\\\\\\	]r   c                   | j                  |      D cg c]  }|d   	 }}t        |      }t        |      }t        |      }||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                  |      dt        j                         v st        j                  t              rt        j                  t              n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t        j                  |      t        j                  |      dz  }t        j                  d      d	z   d
|iz  }	t        t        j                  |	            dx}x}x}}yc c}w )u+   모든 item 의 id 가 고유해야 한다.rf   ==)zn%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py11)s
{%(py11)s = %(py5)s(%(py9)s
{%(py9)s = %(py6)s(%(py7)s)
})
}rR   idsrv   )r&   rD   r'   r)   py6rT   rV   py11u   중복 id 발견z
>assert %(py13)spy13N)ro   rR   rv   r+   r,   r-   r.   r/   r0   rc   r1   r2   )
r   r   rx   r   r3   @py_assert8@py_assert10rN   @py_format12@py_format14s
             r   test_id_values_are_uniquez)TestYamlParsing.test_id_values_are_unique   s5   &*oom&DEdtDzEE3x<s3x<3x=<x=(<<<x=<<<<<<s<<<s<<<<<<3<<<3<<<x<<<<<<3<<<3<<<<<<s<<<s<<<<<<3<<<3<<<x<<<=<<<*<<<<<<<< Fs   Ic                   |d   j                         D ]  \  }}|j                  dg       D ]  }|d   }||k(  }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d|d	    d
|d    d| d      dz   d|iz  }t        t        j                  |            dx}}  y)u@   source_section 값이 해당 sources 키와 일치해야 한다.rH   r^   rk   r   z%(py1)s == %(py3)srd   rC   rq   rf   u   ' 의 source_section 'u   ' 이 source key 'u   ' 와 불일치ra   r)   N)r^   rn   r+   r,   r0   r-   r.   r/   rc   r1   r2   )	r   r   rd   r_   rx   rE   r3   r5   r6   s	            r   &test_source_section_matches_source_keyz6TestYamlParsing.test_source_section_matches_source_key   s   *95;;= 	MHc, ,- -9   -   		 .   6   2:   		 2:     T$ZL(>tDT?U>V W##+*O=    	r   NreturnrK   )r   r   )r   r   r   Noner   r   )r   rK   r   r   )r   rK   )__name__
__module____qualname____doc__r;   fixturer   r   r7   r=   rF   rO   r\   re   ru   ro   rz   r~   r   r   r    r   r   r   r   s   s    'V^^4 4 V^^  
 *:
1] b,X]=
r   r   c                  @    e Zd ZdZd	dZd
dZd
dZd
dZd
dZd
dZ	y)TestHealthCheckFileExistszhealth_check type: file_existsc                ~    t         j                  j                  |      }t         j                  j                  |      S )uK   file_exists 체크 로직을 직접 구현하여 테스트 (모듈 독립).)ospath
expanduserr:   )r   r   expandeds      r   
_run_checkz$TestHealthCheckFileExists._run_check   s)    77%%d+ww~~h''r   c           
     R   |dz  }|j                  d       | j                  }t        |      } ||      }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                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      d	z  }d
d|iz  }	t        t        j                  |	            dx}x}x}x}}y)u#   존재하는 파일 → pass (True)z
target.txtr$   Tiszo%(py8)s
{%(py8)s = %(py2)s
{%(py2)s = %(py0)s._run_check
}(%(py6)s
{%(py6)s = %(py3)s(%(py4)s)
})
} is %(py11)sr   strfr&   rM   r'   rS   r   py8r   assert %(py13)sr   Nr   r   r   r+   r,   r-   r.   r/   r0   r1   r2   
r   r   r   r4   rY   @py_assert7r   @py_assert9r   r   s
             r   test_existing_file_passesz3TestHealthCheckFileExists.test_existing_file_passes   s    |#	V.s1v.v&.$.&$....&$......t...t.........s...s......1...1...v...&...$........r   c           
     0   |dz  }| j                   }t        |      } ||      }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                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }d	d
|iz  }	t        t        j                  |	            dx}x}x}x}}y)u+   존재하지 않는 파일 → fail (False)zno_such_file.txtFr   r   r   r   rt   r   r   r   N
r   r   r+   r,   r-   r.   r/   r0   r1   r2   )
r   r   rt   r4   rY   r   r   r   r   r   s
             r   test_nonexistent_file_failsz5TestHealthCheckFileExists.test_nonexistent_file_fails   s    //5s7|5|,55,5555,555555t555t555555555s555s55555575557555|555,55555555555r   c                `   |j                  dt        |             |dz  }|j                  d       | j                  }d} ||      }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |       rt	        j                  |       ndt	        j                  |      t	        j                  |      t	        j                  |      t	        j                  |      d	z  }	d
d|	iz  }
t        t	        j                  |
            dx}x}x}x}}y)u0   ~ 경로가 올바르게 확장되어야 한다.HOMEzsentinel.txtokz~/sentinel.txtTr   zP%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s._run_check
}(%(py4)s)
} is %(py9)sr   r&   rM   rS   r   rV   assert %(py11)sr   N)setenvr   r   r   r+   r,   r-   r.   r/   r0   r1   r2   )r   r   monkeypatchtargetr4   rW   rY   r   r   r[   r   s              r   test_tilde_path_expansionz3TestHealthCheckFileExists.test_tilde_path_expansion   s    63x=1N*$8/8/08D80D88880D888888t888t888888/8880888D88888888r   c                4   |j                  dt        |             | j                  }d} ||      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }	t        t        j                  |	            d
x}x}x}x}}y
)u)   ~ 경로 확장 후 파일 없으면 failr   z~/nonexistent_file.txtFr   r   r   r   r   r   N)r   r   r   r+   r,   r-   r.   r/   r0   r1   r2   )
r   r   r   r4   rW   rY   r   r   r[   r   s
             r   test_tilde_path_nonexistentz5TestHealthCheckFileExists.test_tilde_path_nonexistent   s    63x=1A7A78AEA8EAAAA8EAAAAAAtAAAtAAAAAA7AAA8AAAEAAAAAAAAr   c           
     &   | j                   }t        |      } ||      }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                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}x}x}}y
)u(   디렉토리 경로도 존재하면 passTr   r   r   r   r   r   r   r   Nr   )	r   r   r4   rY   r   r   r   r   r   s	            r   test_existing_directory_passesz8TestHealthCheckFileExists.test_existing_directory_passes   s    5s8}5}-55-5555-555555t555t555555555s555s55555585558555}555-55555555555r   N)r   r   r   boolr   r   r   r   )
r   r   r   r   r   r   r   r   r   r   r   r   r   r   r      s$    ((
/6
9B
6r   r   c                  B    e Zd ZdZd	d
dZddZddZddZddZddZ	y)!TestHealthCheckFileRecentActivityz'health_check type: file_recent_activityc                    t         j                  j                  |      }t         j                  j                  |      syt         j                  j	                  |      }t        j
                         |z
  dz  }||k  S )u   file_recent_activity 로직.F  )r   r   r   r:   getmtimetime)r   r   max_age_hoursr   mtime	age_hourss         r   r   z,TestHealthCheckFileRecentActivity._run_check   s]    77%%d+ww~~h'  *YY[5(D0	M))r   c                   |dz  }|j                  d       | j                  }t        |      }d} |||      }d}||u }|sst        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                  t              rt        j                  t              nd	d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}x}x}x}}y)u    방금 수정된 파일 → passz
recent.logz	log entry   r   Tr   z%(py10)s
{%(py10)s = %(py2)s
{%(py2)s = %(py0)s._run_check
}(%(py6)s
{%(py6)s = %(py3)s(%(py4)s)
}, max_age_hours=%(py8)s)
} is %(py13)sr   r   r   r&   rM   r'   rS   r   r   py10r   assert %(py15)spy15Nr   )r   r   r   r4   rY   r   r   @py_assert12@py_assert11r   @py_format16s              r   "test_recently_modified_file_passeszDTestHealthCheckFileRecentActivity.test_recently_modified_file_passes  s   |#	[!?s1v?Q?vQ7?4?74????74??????t???t?????????s???s??????1???1???v???Q???7???4????????r   c                   |dz  }|j                  d       t        j                         dz
  }t        j                  t	        |      ||f       | j
                  }t	        |      }d} |||      }d}||u }	|	sst        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                  t              rt        j                  t              nd
dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}x}x}x}	}y)u   오래된 파일 → failzold.logz	old entryi    r   Fr   r   r   r   r   r   r   r   Nr   r   r   utimer   r   r+   r,   r-   r.   r/   r0   r1   r2   )r   r   r   old_timer4   rY   r   r   r   r   r   r   s               r   test_old_file_failsz5TestHealthCheckFileRecentActivity.test_old_file_fails  s+   y 	[!99;*
Q(H-.As1vARAvR8AEA8EAAAA8EAAAAAAtAAAtAAAAAAAAAsAAAsAAAAAA1AAA1AAAvAAARAAA8AAAEAAAAAAAAr   c                d   | j                   }d}||z  }t        |      } ||      }d}||u }|sst        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                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }	d	d
|	iz  }
t        t        j                  |
            dx}x}x}x}x}x}}y)   파일 없음 → failzmissing.logFr   z}%(py11)s
{%(py11)s = %(py2)s
{%(py2)s = %(py0)s._run_check
}(%(py9)s
{%(py9)s = %(py3)s((%(py4)s / %(py6)s))
})
} is %(py14)sr   r   r   r&   rM   r'   rS   r   rV   r   py14assert %(py16)spy16Nr   r   r   r4   rY   r   r   r   @py_assert13r   @py_format15@py_format17s              r   r   z=TestHealthCheckFileRecentActivity.test_nonexistent_file_fails  s    FmF8m#;Fs#;<F<=FF=FFFF=FFFFFFtFFFtFFFFFFFFFsFFFsFFFFFF8FFF8FFFmFFF<FFF=FFFFFFFFFFFr   c                L   |dz  }|j                  d       t        j                         dz
  }t        j                  t	        |      ||f       | j
                  }t	        |      }d} |||      }d}||u }	|	sst        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                  t              rt        j                  t              nd
dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}x}x}x}	}| j
                  }t	        |      }d} |||      }d}||u }	|	sst        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                  t              rt        j                  t              nd
dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}x}x}x}	}y)u5   max_age_hours 파라미터가 적용되어야 한다.z
medium.logr$   i 0   r   Tr   r   r   r   r   r   r   r   Nr   Fr   r   r   r   pastr4   rY   r   r   r   r   r   r   s               r   test_custom_max_age_hoursz;TestHealthCheckFileRecentActivity.test_custom_max_age_hours  s   |#	Vyy{Y&
Q$&@s1v@R@vR8@D@8D@@@@8D@@@@@@t@@@t@@@@@@@@@s@@@s@@@@@@1@@@1@@@v@@@R@@@8@@@D@@@@@@@@As1vARAvR8AEA8EAAAA8EAAAAAAtAAAtAAAAAAAAAsAAAsAAAAAA1AAA1AAAvAAARAAA8AAAEAAAAAAAAr   c                   |dz  }|j                  d       t        j                         dz
  dz   }t        j                  t	        |      ||f       | j
                  }t	        |      }d} |||      }d}||u }	|	sst        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                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}x}x}x}	}y)u1   정확히 경계(max_age_hours)에서 pass 처리zboundary.logr$   r   
   r   r   Tr   r   r   r   r   r   r   r   Nr   r   s               r   test_edge_age_at_boundaryz;TestHealthCheckFileRecentActivity.test_edge_age_at_boundary%  s/   ~%	Vyy{T!B&
Q$&?s1v?Q?vQ7?4?74????74??????t???t?????????s???s??????1???1???v???Q???7???4????????r   Nr   r   r   r   intr   r   r   )
r   r   r   r   r   r   r   r   r   r   r   r   r   r   r      s(    1*@BG	B@r   r   c                  H    e Zd ZdZd
dZddZddZddZddZddZ	ddZ
y	)TestHealthCheckGrepPatternzhealth_check type: grep_patternc                v   ddl }t        j                  j                  |      }t        j                  j	                  |      rwt        j
                  |      D ]^  \  }}}|D ]S  }t        j                  j                  ||      }		 t        |	      j                  d      }
|j                  ||
      r  yU ` yt        j                  j                  |      sy	 t        |      j                  d      }
t        |j                  ||
            S # t        t        f$ r Y w xY w# t        t        f$ r Y yw xY w)u   grep_pattern 로직.r   NignoreerrorsTF)rer   r   r   isdirwalkjoinr   r*   searchIOErrorOSErrorr:   r   )r   patternr   r   r   root_filesfnamefpathcontents              r   r   z%TestHealthCheckGrepPattern._run_check2  s   77%%f-77==""$''("3 !a" !EGGLLu5E!"&u+"7"7x"7"H99Wg6#' 7	!! 77>>(+x.22(2CBIIgw788 $W- ! ! W% s$   -D5D& D#"D#&D87D8c                   |dz  }|j                  d       | j                  }d}t        |      } |||      }d}||u }|sst        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d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      t        j                  |      d
z  }	dd|	iz  }
t        t        j                  |
            dx}x}x}x}x}}y)u"   파일에 패턴이 있으면 pass	config.pyzROTATION_POLICY = 90
ROTATION_POLICYTr   zz%(py10)s
{%(py10)s = %(py2)s
{%(py2)s = %(py0)s._run_check
}(%(py4)s, %(py8)s
{%(py8)s = %(py5)s(%(py6)s)
})
} is %(py13)sr   r   r   r&   rM   rS   r)   r   r   r   r   r   r   Nr   r   r   r   r4   rW   r   r   r   r   r   r   s              r   $test_matching_pattern_in_file_passesz?TestHealthCheckGrepPattern.test_matching_pattern_in_file_passesK  s   {"	-.A0A#a&A0&9ATA9TAAAA9TAAAAAAtAAAtAAAAAA0AAAAAA#AAA#AAAAAAaAAAaAAA&AAA9AAATAAAAAAAAr   c                   |dz  }|j                  d       | j                  }d}t        |      } |||      }d}||u }|sst        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d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      t        j                  |      d
z  }	dd|	iz  }
t        t        j                  |
            dx}x}x}x}x}}y)u   패턴이 없으면 failr  zSOME_OTHER_SETTING = 10
r  Fr   r  r   r   r   r  r   r   Nr   r  s              r   test_nonmatching_pattern_failsz9TestHealthCheckGrepPattern.test_nonmatching_pattern_failsQ  s   {"	01B0B#a&B0&9BUB9UBBBB9UBBBBBBtBBBtBBBBBB0BBBBBB#BBB#BBBBBBaBBBaBBB&BBB9BBBUBBBBBBBBr   c                   |dz  j                  d       |dz  j                  d       | j                  }d}t        |      } |||      }d}||u }|sst        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
dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}x}x}x}}y)u0   디렉토리 내 파일에 패턴 있으면 passza.pyzTARGET_KEY = True
zb.pyzOTHER = False

TARGET_KEYTr   r  r   r   r   r  r   r   Nr   
r   r   r4   rW   r   r   r   r   r   r   s
             r    test_pattern_in_directory_passesz;TestHealthCheckGrepPattern.test_pattern_in_directory_passesW  s   	F	&&'<=	F	&&'89C|CS]C|];CtC;tCCCC;tCCCCCCtCCCtCCCCCC|CCCCCCSCCCSCCCCCCCCCCCC]CCC;CCCtCCCCCCCCr   c                   |dz  j                  d       |dz  j                  d       | j                  }d}t        |      } |||      }d}||u }|sst        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
dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}x}x}x}}y)u:   디렉토리 내 어떤 파일에도 패턴 없으면 failzx.pyzFOO = 1
zy.pyzBAR = 2
MISSING_PATTERN_XYZFr   r  r   r   r   r  r   r   Nr   r  s
             r   #test_pattern_not_in_directory_failsz>TestHealthCheckGrepPattern.test_pattern_not_in_directory_fails]  s   	F	&&{3	F	&&{3M4Mc(mM4mDMMDMMMMDMMMMMMtMMMtMMMMMM4MMMMMMcMMMcMMMMMM(MMM(MMMmMMMDMMMMMMMMMMMr   c                   |dz  }|j                  d       | j                  }d}t        |      } |||      }d}||u }|sst        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d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      t        j                  |      d
z  }	dd|	iz  }
t        t        j                  |
            dx}x}x}x}x}}y)u(   정규식 패턴이 동작해야 한다.zdata.txtzerror_code=404
zerror_code=\d+Tr   r  r   r   r   r  r   r   Nr   r  s              r   test_regex_pattern_worksz3TestHealthCheckGrepPattern.test_regex_pattern_worksc  s   z!	'(A0A#a&A0&9ATA9TAAAA9TAAAAAAtAAAtAAAAAA0AAAAAA#AAA#AAAAAAaAAAaAAA&AAA9AAATAAAAAAAAr   c                   | j                   }d}d}||z  }t        |      } |||      }d}||u }	|	st        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dt	        j
                         v st        j                  |      rt        j                  |      nd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)u   파일 없으면 failr  zno_file.txtFr   )z%(py13)s
{%(py13)s = %(py2)s
{%(py2)s = %(py0)s._run_check
}(%(py4)s, %(py11)s
{%(py11)s = %(py5)s((%(py6)s / %(py8)s))
})
} is %(py16)sr   r   r   )	r&   rM   rS   r)   r   r   r   r   r   zassert %(py18)spy18Nr   )r   r   r4   rW   r   r   r   r   @py_assert15@py_assert14r   @py_format19s               r   r   z6TestHealthCheckGrepPattern.test_nonexistent_file_failsi  s   QyQQh.FQ#.F*GQy*GHQEQHEQQQQHEQQQQQQtQQQtQQQQQQyQQQQQQ#QQQ#QQQQQQhQQQhQQQQQQ*GQQQHQQQEQQQQQQQQr   N)r  r   r   r   r   r   r   )r   r   r   r   r   r  r  r  r  r  r   r   r   r   r   r   /  s.    )2BCDNBRr   r   c                  R    e Zd ZdZdddZddZddZddZddZddZ	ddZ
dd	Zy
)TestHealthCheckAuditTrailRecentz%health_check type: audit_trail_recentc                   t         j                  j                  |      }t         j                  j                  |      syt	        |      j                  d      j                         j                         }|syt        j                  t        j                        j                  d      t        |      z
  }t        |      D ]  }|j                         }|s	 t        j                   |      }|j#                  d      xs$ |j#                  d      xs |j#                  d	      }|r;t        j$                  |j                  d
d      j'                  d            }	|	|k\  r y y# t        j(                  t*        f$ r Y w xY w)uC   audit_trail_recent 로직: JSONL 파일에서 최근 항목 확인.Fr   r   Ntzinfohours	timestamptsr   Zz+00:00T)r   r   r   r:   r   r*   strip
splitlinesr   nowr   utcreplacer   reversedjsonloadsrn   fromisoformatrstripJSONDecodeError
ValueError)
r   r   r   r   linescutofflineentryts_strr)  s
             r   r   z*TestHealthCheckAuditTrailRecent._run_checkq  s=   77%%d+ww~~h'X(((9??ALLNhll+3343@9S`CaaUO 	D::<D

4(;/W599T?WeiiPVFW!//sH0M0T0TU]0^_BV|#	  ((*5 s   BE((FFc                V    |j                  dj                  d |D              d       y )N
c              3  F   K   | ]  }t        j                  |        y wN)r1  dumps).0es     r   	<genexpr>z>TestHealthCheckAuditTrailRecent._make_jsonl.<locals>.<genexpr>  s     !AA$**Q-!As   !r   r   )r   r   )r   r   entriess      r   _make_jsonlz+TestHealthCheckAuditTrailRecent._make_jsonl  s!    		!A!AAGTr   c                   t        j                  t        j                        j	                  d      j                         }|dz  }| j                  ||ddg       | j                  }t        |      }d} |||      }d}||u }	|	sst        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                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                   |            dx}x}x}x}x}	}y)u   최근 항목이 있으면 passNr$  audit.jsonlloginr(  eventr   r   Tr   r   r   r   r   r   r   r   )r   r-  r   r.  r/  	isoformatrE  r   r   r+   r,   r-   r.   r/   r0   r1   r2   )r   r   now_strr   r4   rY   r   r   r   r   r   r   s               r   test_recent_entry_passesz8TestHealthCheckAuditTrailRecent.test_recent_entry_passes  s9   ,,x||,44D4AKKM}$7WEFG@s1v@R@vR8@D@8D@@@@8D@@@@@@t@@@t@@@@@@@@@s@@@s@@@@@@1@@@1@@@v@@@R@@@8@@@D@@@@@@@@r   c                ,   t        j                  t        j                        j	                  d      t        d      z
  j                         }|dz  }| j                  ||ddg       | j                  }t        |      }d} |||	      }d
}||u }	|	sst        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                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t!        t        j"                  |            dx}x}x}x}x}	}y)u"   오래된 항목만 있으면 failNr$  H   r&  rG  rH  rI  r   r   Fr   r   r   r   r   r   r   r   r   r-  r   r.  r/  r   rK  rE  r   r   r+   r,   r-   r.   r/   r0   r1   r2   )r   r   old_strr   r4   rY   r   r   r   r   r   r   s               r   test_old_entries_failz5TestHealthCheckAuditTrailRecent.test_old_entries_fail  sD   <<-55T5BYUWEXXcce}$7WEFGAs1vARAvR8AEA8EAAAA8EAAAAAAtAAAtAAAAAAAAAsAAAsAAAAAA1AAA1AAAvAAARAAA8AAAEAAAAAAAAr   c           
     R   |dz  }|j                  d       | j                  }t        |      } ||      }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                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      d	z  }d
d|iz  }	t        t        j                  |	            dx}x}x}x}}y)u   빈 파일 → failzempty.jsonl Fr   r   r   r   r   r   r   r   Nr   r   s
             r   test_empty_file_failsz5TestHealthCheckAuditTrailRecent.test_empty_file_fails  s    }$	R/s1v/v&/%/&%////&%//////t///t/////////s///s//////1///1///v///&///%////////r   c                d   | j                   }d}||z  }t        |      } ||      }d}||u }|sst        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                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }	d	d
|	iz  }
t        t        j                  |
            dx}x}x}x}x}x}}y)r   zmissing.jsonlFr   r   r   r   r   r   r   r   Nr   r   s              r   r   z;TestHealthCheckAuditTrailRecent.test_nonexistent_file_fails  s    HoH8o#=Hs#=>H>?H5H?5HHHH?5HHHHHHtHHHtHHHHHHHHHsHHHsHHHHHH8HHH8HHHoHHH>HHH?HHH5HHHHHHHHr   c                   t        j                  t        j                        j	                  d      t        d      z
  j                         }t        j                  t        j                        j	                  d      j                         }|dz  }| j                  ||dd|ddg       | j                  }t        |      }d	} |||
      }d}	||	u }
|
sst        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                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |	      dz  }dd|iz  }t!        t        j"                  |            dx}x}x}x}x}
}	y)u0   오래된 항목 + 최신 항목 혼재 → passNr$  rO  r&  zmixed.jsonloldrI  newr   r   Tr   r   r   r   r   r   r   r   rP  )r   r   rQ  rL  r   r4   rY   r   r   r   r   r   r   s                r   *test_mixed_entries_passes_if_recent_existszJTestHealthCheckAuditTrailRecent.test_mixed_entries_passes_if_recent_exists  s}   <<-55T5BYUWEXXcce,,x||,44D4AKKM}$%6%6	
 @s1v@R@vR8@D@8D@@@@8D@@@@@@t@@@t@@@@@@@@@s@@@s@@@@@@1@@@1@@@v@@@R@@@8@@@D@@@@@@@@r   c                   t        j                  t        j                        j	                  d      t        d      z
  j                         }|dz  }| j                  ||ddg       | j                  }t        |      }d} |||	      }d
}||u }	|	sst        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                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t!        t        j"                  |            dx}x}x}x}x}	}| j                  }t        |      }d} |||	      }d}||u }	|	sst        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                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t!        t        j"                  |            dx}x}x}x}x}	}y)u(   max_age_hours 파라미터 적용 확인Nr$  $   r&  rG  evtrI  r   r   Tr   r   r   r   r   r   r   r   r   FrP  )r   r   
entry_timer   r4   rY   r   r   r   r   r   r   s               r   r   z9TestHealthCheckAuditTrailRecent.test_custom_max_age_hours  s0   ll8<<0888E	XZH[[ffh
}$:FGH@s1v@R@vR8@D@8D@@@@8D@@@@@@t@@@t@@@@@@@@@s@@@s@@@@@@1@@@1@@@v@@@R@@@8@@@D@@@@@@@@As1vARAvR8AEA8EAAAA8EAAAAAAtAAAtAAAAAAAAAsAAAsAAAAAA1AAA1AAAvAAARAAA8AAAEAAAAAAAAr   Nr   r   )r   r   rD  
list[dict]r   r   r   )r   r   r   r   r   rE  rM  rR  rU  r   rZ  r   r   r   r   r"  r"  n  s3    /0UAB0IABr   r"  c                  0    e Zd ZdZddZddZddZddZy)	TestHealthCheckProcessRunningz"health_check type: process_runningc                    	 t        j                  dd|gdd      }|j                  dk(  S # t        $ r+ t        j                  ddgdd      }||j                  v cY S w xY w)u7   process_running 로직: ps aux 로 프로세스 확인.pgrepz-fT)capture_outputtextr   psaux)
subprocessrun
returncodeFileNotFoundErrorstdout)r   process_nameresults      r   r   z(TestHealthCheckProcessRunning._run_check  ss    	1^^$-#F
 $$))  	1^^u#F
  6==00	1s   ), 1A A c                Z   g }| j                   }d} ||      }d}||u }|}|s| j                   }d}	 ||	      }
d}|
|u }|}|st        j                  d|fd||f      dt        j                         v st        j
                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }|j                  |       |st        j                  dfd

f      dt        j                         v st        j
                  |       rt        j                  |       ndt        j                        t        j                  	      t        j                  |
      t        j                  |      dz  }dd|iz  }|j                  |       t        j                  |d      i z  }dd|iz  }t        t        j                  |            dx}x}x}x}x}x}x}x}x}	x}
x}}y)u*   현재 실행 중인 프로세스 → passpythonTpython3r   )zQ%(py8)s
{%(py8)s = %(py4)s
{%(py4)s = %(py2)s._run_check
}(%(py6)s)
} is %(py11)sr   )rM   rS   r   r   r   z%(py13)sr   )zW%(py21)s
{%(py21)s = %(py17)s
{%(py17)s = %(py15)s._run_check
}(%(py19)s)
} is %(py24)s)r   py17py19py21py24z%(py26)spy26r   zassert %(py29)spy29N)r   r+   r,   r-   r.   r/   r0   append_format_boolopr1   r2   )r   r4   rW   rY   r   r   r   rE   @py_assert16@py_assert18@py_assert20@py_assert23@py_assert22r   r   @py_format25@py_format27@py_format28@py_format30s                      r   test_running_process_passesz9TestHealthCheckProcessRunning.test_running_process_passes  sI    	WtVxVx(VDV(D0VDOOVIVOI4NVRVV4NRV4VVVVV(DVVVVVVtVVVtVVVVVVxVVV(VVVDVVVVVVV4NRVVVVVVVDVVVDVVVOVVVIVVV4NVVVRVVVVVVVVVVVVVVVVr   c                   | j                   }d} ||      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}x}x}}y	)
u)   존재하지 않는 프로세스 → fail!nonexistent_process_xyz_12345_abcFr   r   r   r   r   r   N	r   r+   r,   r-   r.   r/   r0   r1   r2   r   r4   rW   rY   r   r   r[   r   s           r   test_nonrunning_process_failsz;TestHealthCheckProcessRunning.test_nonrunning_process_fails  s    LBLBCLuLCuLLLLCuLLLLLLtLLLtLLLLLLBLLLCLLLuLLLLLLLLr   c                   | j                   }d} ||      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j
                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}x}x}}y	)
u@   프로세스명 서브스트링으로도 매칭되어야 한다.r;   Tr   r   r   r   r   r   Nr  r  s           r   !test_process_name_substring_matchz?TestHealthCheckProcessRunning.test_process_name_substring_match  s     0x0x(0D0(D0000(D000000t000t000000x000(000D00000000r   N)rm  r   r   r   r   )r   r   r   r   r   r  r  r  r   r   r   ra  ra    s    ,1$W
M1r   ra  c                      e Zd ZdZ ej
                         dd       ZddZddZddZ	ddZ
ddZddZdd	Zdd
ZddZddZddZddZddZddZddZy)TestOutputFormatu=   출력 JSON 구조 검증 (실제 스크립트 포맷 기준)c                4   t        j                  t        j                        j	                         dddddddddddddddddddddddddddddddgd	d
gdddddddddddddddddddddddddddddddddd d!dd"d#d$ddddd%ddd&d$d'd(d(dd)dgd*S )+uH   실제 스크립트가 생성하는 출력 포맷과 일치하는 샘플      r   )totalr   r   r   r	   r
   r   )memorysecurityinfrazinf-002zinf-003u   중복 활동 감지기)r^   descriptionmem-001zMemory Indexerr  highr   passz5exists: /home/jay/workspace/scripts/memory-indexer.pyrf   rg   sourceri   declared_statusrh   health_check_resulthealth_check_detailmem-002zMemory Janitormediumr   z"last modified 2.0h ago (limit 48h)sec-001zSecret Rotation Checkr  r   z*pattern found in: secret-rotation-check.pysec-002zAudit Trailr	   failzno entries within last 24hzinf-001zActivity Watcherr  z.process 'activity-watcher' found (1 match(es))zDuplicate Checkerlowr
   z7exists: /home/jay/workspace/scripts/activity-watcher.py)r(  summary	by_source
duplicatesr^   )r   r-  r   r.  rK  r   s    r   sample_outputzTestOutputFormat.sample_output  sf    "hll3==?   #$#$ !!" #$#$ !!" #$#$ !!"#6 %i0AZ[
 $,& &'/&+1+b	 $,& ('4++1+O	 $3( &'4++1+W	 $)( ('/(+1+G	 $.% &'/&+1+[	 $/% %'2)+1+d	g=Oe
 e	
r   c           	        h d}|j                   } |       }t        |      }||k  }|sKt        j                  d|fd||f      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dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}x}}y
)u3   출력에 필수 최상위 키가 있어야 한다.>   r^   r  r  r(  r  )<=)za%(py0)s <= %(py9)s
{%(py9)s = %(py2)s(%(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.keys
}()
})
}requiredrv   r  )r&   rM   r'   r)   rT   rV   r   r   N)
rw   rv   r+   r,   r-   r.   r/   r0   r1   r2   )	r   r  r  rN   rX   r   r4   r[   r   s	            r   !test_output_has_required_top_keysz2TestOutputFormat.test_output_has_required_top_keysX  s    O,1141343344x44444x4444444x444x44444434443444444}444}4441444344444444444r   c                x   |d   }t        j                  |      }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)u8   timestamp 가 유효한 ISO 8601 형식이어야 한다.r(  5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}rJ   parsedr   r&   rD   rM   rS   N)
r   r3  rJ   r-   r.   r+   r/   r0   r1   r2   )r   r  r)  r  rW   @py_format5s         r   test_timestamp_is_valid_isoz,TestOutputFormat.test_timestamp_is_valid_iso]  s    ;'''+&(++++++++z+++z++++++&+++&++++++(+++(++++++++++r   c                   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}}y)	u-   summary 에 total 필드가 있어야 한다.r  r  r@   )z%(py1)s in %(py4)srD   rS   assert %(py6)sr   N)r+   r,   r0   r1   r2   )r   r  rE   rW   r3   r  @py_format7s          r   test_summary_has_total_fieldz-TestOutputFormat.test_summary_has_total_fieldc  sT    2-	22w22222w2222w22222222222r   c                   |d   }dD ]  }||v }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j
                  |      nddt        j                         v st        j                  |      rt        j
                  |      nddz  }t        j                  d| d	      d
z   d|iz  }t        t        j                  |            d} y)u<   summary 에 모든 status 카운트 키가 있어야 한다.r  r   r   r   r	   r
   r@   z%(py0)s in %(py2)srh   sr&   rM   u   summary 에 'u   ' 키 없음
>assert %(py4)srS   N)	r+   r,   r-   r.   r/   r0   rc   r1   r2   )r   r  r  rh   r4   @py_format3r  s          r   "test_summary_has_all_status_fieldsz3TestOutputFormat.test_summary_has_all_status_fieldsg  s    )$W 	EFQ;DDD6QDDDDDD6DDD6DDDDDDQDDDQDDDD-x| DDDDDDD	Er   c                   |d   t        fddD              }d   }||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d	| d
d    d      dz   d|iz  }t        t        j                  |            dx}}y)u?   summary 내 status 카운트 합이 total 과 같아야 한다.r  c              3  (   K   | ]	  }|     y wr?  r   )rA  kr  s     r   rC  zKTestOutputFormat.test_summary_status_counts_sum_to_total.<locals>.<genexpr>p  s     i!1is   r  r  r   z%(py0)s == %(py3)s
status_sumr%      status 합계() != total()ra   r)   N)
sumr+   r,   r-   r.   r/   r0   rc   r1   r2   )r   r  r  r3   r4   r5   r6   r  s          @r   'test_summary_status_counts_sum_to_totalz8TestOutputFormat.test_summary_status_counts_sum_to_totalm  s    )$i'hii
wZ 	
zZ' 	
 	
zZ 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ( 	
 	
  ZLAgJ<qA	
 	
 	
 	
 	
r   c                   |d   d   }|d   }t        |      }||k(  }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}x}}y
)u=   summary.total 이 items 리스트 길이와 같아야 한다.r  r  r^   r   z0%(py1)s == %(py7)s
{%(py7)s = %(py3)s(%(py5)s)
}rR   rD   r'   r)   rT   rU   rV   N	rR   r+   r,   r0   r-   r.   r/   r1   r2   )r   r  rE   rN   rX   r3   rZ   r[   s           r   %test_summary_total_matches_items_listz6TestOutputFormat.test_summary_total_matches_items_listu  s    Y'0Og8NOC8N4OO04OOOOO04OOOO0OOOOOOCOOOCOOO8NOOO4OOOOOOOOr   c                P   t        j                  t              }t        |d   j	                               }|d   D ]  }||v }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d|       d	z   d
|iz  }t        t        j                  |            d} y)u8   by_source 키가 YAML 소스 키와 일치해야 한다.rH   r  r@   r  src_keysource_keysr  u   알 수 없는 source: r  rS   N)r   r   r   rv   rw   r+   r,   r-   r.   r/   r0   rc   r1   r2   )r   r  registryr  r  r4   r  r  s           r   "test_by_source_grouping_is_correctz3TestOutputFormat.test_by_source_grouping_is_correcty  s    >>"67(9-2245$[1 	OGk)NNN7kNNNNNN7NNN7NNNNNNkNNNkNNNN-DWI+NNNNNNN	Or   c                0   |d   j                         D ]  \  }}t        |t              }|s%t        j                  d| d      dz   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	}d
}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d| d      dz   d|iz  }	t        t        j                  |	            d	x}} y	)u=   by_source 내 각 값은 카운트 딕셔너리여야 한다.r  r`   u   ' 의 값이 dict 가 아님z7
>assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}rJ   countsrK   r  Nr  r@   rB   rC   u   ' 카운트에 total 없음ra   r)   )r^   rJ   rK   r+   rc   r-   r.   r/   r0   r1   r2   r,   )
r   r  r_   r  rW   r  rE   r3   r5   r6   s
             r   'test_by_source_each_entry_is_count_dictz8TestOutputFormat.test_by_source_each_entry_is_count_dict  s3   (5;;= 	KKCfd+R+RRq5Q-RRRRRRR:RRR:RRRRRRfRRRfRRRRRRdRRRdRRR+RRRRRRJ7f$JJJ7fJJJ7JJJJJJfJJJfJJJJ#.I&JJJJJJJJ	Kr   c                   t        d |d   j                         D              }|d   d   }||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>   by_source 의 total 합이 summary.total 과 같아야 한다.c              3  &   K   | ]	  }|d      ywr  Nr   )rA  vs     r   rC  zNTestOutputFormat.test_by_source_totals_sum_to_summary_total.<locals>.<genexpr>  s     S!1W:Ss   r  r  r  r   r  source_totalr%   r(   r)   N)
r  rm   r+   r,   r-   r.   r/   r0   r1   r2   )r   r  r  r3   r4   r5   r6   s          r   *test_by_source_totals_sum_to_summary_totalz;TestOutputFormat.test_by_source_totals_sum_to_summary_total  s    S}[/I/P/P/RSS,Y7@@|@@@@@|@@@@@@@|@@@|@@@@@@@@@@@r   c                   |d   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  }t        j                  d|j                  d       d	      d
z   d|iz  }t        t        j                  |            dx}} y)uS   items 리스트의 모든 항목에 health_check_result 필드가 있어야 한다.r^   r  r@   rB   rx   rC   rq   rf       ' 에 health_check_result 없음ra   r)   N
r+   r,   r0   r-   r.   r/   rc   rn   r1   r2   )r   r  rx   rE   r3   r5   r6   s          r   &test_each_item_has_health_check_resultz7TestOutputFormat.test_each_item_has_health_check_result  s    !'* 	D( (D0  (D  I )  v   -1  I -1    $((HI    	r   c                   h d}|d   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  }t        j                  d|j                  d	       d
|d    d      dz   d|iz  }t        t        j                  |            dx}} y)B   health_check_result 값이 pass/fail/skip 중 하나여야 한다.>   r  r  r<   r^   r  r@   rB   validrC   rq   rf      ' 의 result 'r}   ra   r)   Nr  )r   r  r  rx   rE   r3   r5   r6   s           r   )test_health_check_result_values_are_validz:TestOutputFormat.test_health_check_result_values_are_valid  s    (!'* 	D-. .%7  .%  I /  v   38  I 38    $(t<Q7R6SSlm    	r   c                   h d}|d   D ]  }|t        |j                               z
  }| }|st        j                  d|j	                  d       d|       dz   ddt        j                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            d	} y	)
u:   items 의 각 항목에 필수 필드가 있어야 한다.>   rf   rg   r  rh   ri   r  r  r  r^   rq   rf   rr   rs   r&   rt   N)rv   rw   r+   rc   rn   r-   r.   r/   r0   r1   r2   )r   r  r  rx   rt   r4   ry   s          r   rz   z0TestOutputFormat.test_items_have_required_fields  s    B!'* 	XDTYY[!11G;W;WW&$(88MgY WWWWWWWwWWWwWWWWWW	Xr   c                   |d   }t        |t              }|sddt        j                         v st	        j
                  t               rt	        j                  t               ndt	        j                  |      dt        j                         v st	        j
                  t              rt	        j                  t              ndt	        j                  |      dz  }t        t	        j                  |            dx}}|d   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  }t	        j                  d      dz   d|iz  }t        t	        j                  |            dx}} y)uN   duplicates 는 리스트이고 각 항목에 items 필드가 있어야 한다.r  rI   rJ   rb   rL   Nr^   r@   rB   duprC   u)   duplicate 항목에 'items' 필드 없음ra   r)   )rJ   rb   r-   r.   r+   r/   r0   r1   r2   r,   rc   )	r   r  r4   rN   r6   r  rE   r3   r5   s	            r   test_duplicate_detectionz)TestOutputFormat.test_duplicate_detection  s    '5<z5t<<<<<<<<z<<<z<<<5<<<<<<t<<<t<<<<<<<<<< . 	OCN7c>NNN7cNNN7NNNNNNcNNNcNNNN#NNNNNNN	Or   c                   |d   D cg c]  }|d   dk(  r
|d   dk(  r| }}|D ]  }|d   }d}||k(  }|st        j                  d|fd	||f      t        j                  |      t        j                  |      d
z  }t        j                  d|d    d|d    d      dz   d|iz  }	t	        t        j
                  |	            dx}x}} yc c}w )uY   active 항목이 health check fail 이면 status 가 degraded 로 변경되어야 한다.r^   r  r   r  r  rh   r	   r   z%(py1)s == %(py4)sr  rq   rf   u!   ': active+fail 이지만 status='r`   
>assert %(py6)sr   N)r+   r,   r0   rc   r1   r2   )
r   r  idegraded_itemsrx   rE   rW   r3   r  r  s
             r   !test_active_fail_becomes_degradedz2TestOutputFormat.test_active_fail_becomes_degraded  s     %W-
"#x/A6K4LPV4V 
 
 # 	D> Z >Z/  >Z  I "  I &0    d$Ed8nEUUVW     		
s   Cc                j   t        j                  |      }t        j                  |      }|d   d   }|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)	u4   출력 전체가 JSON 직렬화 가능해야 한다.r  r  r   r  r  r  r   N)r1  r@  r2  r+   r,   r0   r1   r2   )	r   r  
serializedparsed_backrE   rW   r3   r  r  s	            r    test_output_is_json_serializablez1TestOutputFormat.test_output_is_json_serializable  s    ZZ.
jj,9%g.S-	2J72SS.2SSSSS.2SSSS.SSS2SSSSSSSSr   Nr   )r  rK   r   r   )r   r   r   r   r;   r   r  r  r  r  r  r  r  r  r  r  r  r  rz   r  r  r  r   r   r   r  r    sv    GV^^g
 g
R5
,3E
POKA
XO	Tr   r  c                      e Zd ZdZ ej
                         dd       ZddZddZddZ	ddZ
ddZddZdd	Zdd
ZddZddZddZddZddZddZy)TestCliFiltersu;   CLI 필터 옵션 테스트 (--source, --status, --summary)c                V    dddddddddd	d
dddddddddddddddddddddddddgS )u9   전체 items 픽스처 (실제 스크립트 items 포맷)r  M1r  r  r   r  r   r  r  M2r  r   r  S1r  r  S2r   r  z	not foundr   r   s    r   
full_itemszTestCliFilters.full_items  sx     dhH$*4I dh!m}$*4I djH$*4I dj!m}$*;P
 	
r   c                >    |D cg c]  }|d   |k(  s| c}S c c}w )u&   --source 필터 로직 시뮬레이션r  r   )r   r^   r  r  s       r   _filter_by_sourcez TestCliFilters._filter_by_source  !     :aAhK6$9:::   c                >    |D cg c]  }|d   |k(  s| c}S c c}w )u&   --status 필터 로직 시뮬레이션rh   r   )r   r^   rh   r  s       r   _filter_by_statusz TestCliFilters._filter_by_status  r  r  c                    d}i }|D ]T  }|d   }||vr|D ci c]  }|d c}||<   d||   d<   ||   dxx   dz  cc<   |d   }|||   v sE||   |xx   dz  cc<   V |S c c}w )u:   items 리스트로 by_source 카운트 딕셔너리 구성r  r  r   r  r   rh   r   )r   r^   STATUS_KEYSrn  rx   r_   r  sts           r   _build_by_sourcezTestCliFilters._build_by_source  s    W"$ 	%Dx.C& -89q!t9s'(sG$3K A% hBVC[ sB1$	%  :s   
A c                6   | j                  |d      }|D ]  }|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)	u+   --source memory → memory 항목만 반환r  r  r   r  r  r  r   N)r  r+   r,   r0   r1   r2   	r   r  rn  rx   rE   rW   r3   r  r  s	            r   (test_source_filter_returns_only_matchingz7TestCliFilters.test_source_filter_returns_only_matching  q    ''
H= 	.D>-X->X---->X--->---X-------	.r   c                   | j                  |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}}y)u<   --source memory 필터 후 security 항목 없어야 한다.r  c              3  ,   K   | ]  }|d    dk7    yw)r  r  Nr   rA  r  s     r   rC  zKTestCliFilters.test_source_filter_excludes_other_sources.<locals>.<genexpr>  s     =1X;*,=s   z,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}all)r&   rM   rS   N)	r  r  r-   r.   r+   r/   r0   r1   r2   )r   r  rn  r4   rW   r  s         r   )test_source_filter_excludes_other_sourcesz8TestCliFilters.test_source_filter_excludes_other_sources  ss    ''
H==f==s=========s===s==============r   c                   | j                  |d      }t        d |D              }t        |      }||k(  }|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)u2   --source 필터 후 count 가 정확해야 한다.r  c              3  2   K   | ]  }|d    dk(  sd  yw)r  r  r   Nr   r
  s     r   rC  zBTestCliFilters.test_source_filter_correct_count.<locals>.<genexpr>  s     JQ(z0IqJ   r   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py5)srR   rn  expectedr&   rD   r'   r)   assert %(py7)srT   N)r  r  rR   r+   r,   r-   r.   r/   r0   r1   r2   r   r  rn  r  r3   rN   r6   rZ   s           r    test_source_filter_correct_countz/TestCliFilters.test_source_filter_correct_count  s    ''
J?J*JJ6{&{h&&&&{h&&&&&&s&&&s&&&&&&6&&&6&&&{&&&&&&h&&&h&&&&&&&r   c                t   | j                  |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)	u3   존재하지 않는 source 필터 → 빈 리스트nonexistent_sourcer   r  rn  r%   r(   r)   N)	r  r+   r,   r-   r.   r/   r0   r1   r2   r   r  rn  r3   r4   r5   r6   s          r   ,test_source_filter_nonexistent_returns_emptyz;TestCliFilters.test_source_filter_nonexistent_returns_empty  m    ''
4HIv|vvvr   c                
   | j                  |d      }| 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   d   }t        |      }||k(  }|st        j                  d|fd||f      t        j                  |      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z  }	dd|	iz  }
t        t        j                  |
            dx}x}}y)u;   필터된 items 로 by_source 구성이 올바른지 확인r  r@   rB   by_srcrC   r(   r)   Nr  not inz%(py1)s not in %(py3)sr  r   z0%(py1)s == %(py6)s
{%(py6)s = %(py3)s(%(py4)s)
}rR   filteredrD   r'   rS   r   assert %(py8)sr   )r  r  r+   r,   r0   r-   r.   r/   r1   r2   rR   )r   r  r!  r  rE   r3   r5   r6   rY   r  @py_format9s              r   "test_by_source_build_from_filteredz1TestCliFilters.test_by_source_build_from_filtered  so   ))*h?&&x0!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!'z''''z'''z''''''''''''''''h(9CM9(M9999(M999(999999C999C999999999999M9999999r   c                6   | j                  |d      }|D ]  }|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)	u+   --status active → active 항목만 반환r   rh   r   r  r  r  r   Nr  r+   r,   r0   r1   r2   r  s	            r   (test_status_filter_returns_only_matchingz7TestCliFilters.test_status_filter_returns_only_matching  r  r   c                   | j                  |d      }t        d |D              }t        |      }||k(  }|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)u$   --status 필터 후 카운트 검증r   c              3  2   K   | ]  }|d    dk(  sd  yw)rh   r   r   Nr   r
  s     r   rC  zBTestCliFilters.test_status_filter_correct_count.<locals>.<genexpr>  s     HQ(x0GqHr  r   r  rR   rn  r  r  r  rT   N)r  r  rR   r+   r,   r-   r.   r/   r0   r1   r2   r  s           r    test_status_filter_correct_countz/TestCliFilters.test_status_filter_correct_count  s    ''
H=H*HH6{&{h&&&&{h&&&&&&s&&&s&&&&&&6&&&6&&&{&&&&&&h&&&h&&&&&&&r   c                6   | j                  |d      }|D ]  }|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)	u.   --status recommended → recommended 항목만r   rh   r   r  r  r  r   Nr'  r  s	            r   test_status_filter_recommendedz-TestCliFilters.test_status_filter_recommended  sq    ''
MB 	3D>2]2>]2222>]222>222]2222222	3r   c                t   | j                  |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)	u0   존재하지 않는 status 필터 → 빈 결과nonexistent_statusr   r  rn  r%   r(   r)   N)	r  r+   r,   r-   r.   r/   r0   r1   r2   r  s          r   ,test_status_filter_nonexistent_returns_emptyz;TestCliFilters.test_status_filter_nonexistent_returns_empty  r  r   c                   t        j                  t        j                        j	                         dt        |      i| j                  |      g 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}}y
)u<   --summary 출력에 items 상세 목록이 없어야 한다.r  )r(  r  r  r  r^   r  r  summary_onlyrC   r(   r)   N)r   r-  r   r.  rK  rR   r  r+   r,   r0   r-   r.   r/   r1   r2   )r   r  r2  rE   r3   r5   r6   s          r   'test_summary_output_excludes_items_listz6TestCliFilters.test_summary_output_excludes_items_list&  s     "hll3==?Z1..z:	
 *wl****wl***w******l***l*******r   c                   dt        |      i}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        |      }||k(  }|st        j                  d	|fd
||f      t        j                  |      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z  }dd|iz  }	t        t        j                  |	            dx}x}}y)u   summary 에 total 포함r  r@   rB   r  rC   r(   r)   Nr   r   rR   r  r"  r#  r   r  )
r   r  r  rE   r3   r5   r6   rY   r  r$  s
             r   test_summary_has_totalsz&TestCliFilters.test_summary_has_totals1  s    C
O,!w'!!!!w'!!!w!!!!!!'!!!'!!!!!!!w23z?2?2222?22222222232223222222z222z222?2222222r   N)r   r_  )r^   r_  r  r   r   r_  )r^   r_  rh   r   r   r_  )r^   r_  r   rK   )r  r_  r   r   )r   r   r   r   r;   r   r  r  r  r  r  r  r  r  r%  r(  r+  r-  r0  r3  r5  r   r   r   r  r    sf    EV^^
 
";;".>
'
:.'3	+3r   r  c                      e Zd ZdZ ej
                  d      dd       ZddZ ej
                  d      dd       ZddZ	ddZ
dd	Zdd
ZddZddZddZddZddZddZddZy)TestIntegrationu+   실제 스크립트 실행 통합 테스트T)autousec                h    t         j                         st        j                  dt          d       yy)u8   스크립트가 없으면 통합 테스트 전체 스킵u   스크립트 없음:     — 통합 테스트 스킵N)SCRIPT_PATHr:   r;   r<   r   s    r   skip_if_no_scriptz!TestIntegration.skip_if_no_scriptB  s,     !!#KK/}<XYZ $r   c                    t         j                  t        t              gt	        |      z   }t        j                  |ddd      S )u[   스크립트를 실행한다. registry 경로는 스크립트에 하드코딩되어 있음.T   )rd  re  timeout)sys
executabler   r;  rb   rh  ri  )r   
extra_argscmds      r   _run_scriptzTestIntegration._run_scriptH  s4    ~~s;/04
3CC~~c$T2NNr   c                h    t         j                         st        j                  dt          d       yy)uB   레지스트리 파일이 없으면 통합 테스트 전체 스킵u   레지스트리 없음: r:  N)r9   r:   r;   r<   r   s    r   skip_if_no_registryz#TestIntegration.skip_if_no_registryM  s,     ##%KK2=/A]^_ &r   c                    | j                         }|j                  }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d|j                  dd  d	|j                  dd        d
z   d|iz  }t        t        j                  |            dx}x}}y)u5   스크립트가 오류 없이 실행되어야 한다.r   r   z2%(py2)s
{%(py2)s = %(py0)s.returncode
} == %(py5)srn  r&   rM   r)   u   스크립트 실패
stdout: N  z	
stderr: 
>assert %(py7)srT   )rD  rj  r+   r,   r-   r.   r/   r0   rc   rl  stderrr1   r2   )r   rn  r4   rN   rW   r6   rZ   s          r   test_script_runs_successfullyz-TestIntegration.test_script_runs_successfullyS  s    !!#   	
A 	
 A% 	
 	
 A 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ! 	
 	
 		 %& 	
 	
  ,FMM$3,?+@
6==Y]Z]K^J_`	
 	
 	
 	
 	
 	
r   c           	        | j                         }|j                  }d}||k(  }|st        j                  d|fd||f      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}}	 t        j                  |j                        }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# t        j                  $ r3}t        j                  d	| d
|j                  dd         Y d}~bd}~ww xY w)u8   스크립트 stdout 이 유효한 JSON 이어야 한다.r   r   rH  rn  rI  r  rT   Nu&   stdout 이 유효한 JSON 이 아님: u	   
출력: rJ  r  rJ   r$   rK   r  )rD  rj  r+   r,   r-   r.   r/   r0   r1   r2   r1  r2  rl  r5  r;   r  rJ   rK   )
r   rn  r4   rN   rW   r6   rZ   r$   excr  s
             r   test_output_is_valid_jsonz)TestIntegration.test_output_is_valid_jsonZ  sl   !!#  %A% A%%%% A%%%%%%v%%%v%%% %%%A%%%%%%%	g::fmm,D $%%%%%%%%z%%%z%%%%%%$%%%$%%%%%%%%%%%%%%%%%%% ## 	gKK@ZPVP]P]^b_bPcOdeff	gs   H I -(II c                <   | j                         }|j                  }d}||k(  }|st        j                  d|fd||f      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}}t        j                  |j                        }h d	}|t        |j                               z
  }	|	 }|s~t        j                  d
|	       dz   ddt	        j
                         v st        j                  |	      rt        j                  |	      ndiz  }
t        t        j                  |
            d}y)u/   출력 JSON 에 필수 키가 있어야 한다.r   r   rH  rn  rI  r  rT   N>   r^   r  r  r(  r  u   출력에 필수 키 누락: rs   r&   rt   )rD  rj  r+   r,   r-   r.   r/   r0   r1   r2   r1  r2  rl  rv   rw   rc   )r   rn  r4   rN   rW   r6   rZ   r$   r  rt   ry   s              r   test_output_has_required_keysz-TestIntegration.test_output_has_required_keysd  s    !!#  %A% A%%%% A%%%%%%v%%%v%%% %%%A%%%%%%%zz&--(OS--{E{EE;G9EEEEEEE7EEE7EEEEEEr   c                   | j                         }|j                  }d}||k(  }|st        j                  d|fd||f      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}}t        j                  |j                        }|j                  d	g       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  }t        j                  d|j                  d       d      dz   d|iz  }t        t        j                  |            dx}	}
 y)uT   출력 items 리스트의 모든 항목에 health_check_result 가 있어야 한다.r   r   rH  rn  rI  r  rT   Nr^   r  r@   rB   rx   rC   rq   rf   r  ra   r)   rD  rj  r+   r,   r-   r.   r/   r0   r1   r2   r1  r2  rl  rn   rc   )r   rn  r4   rN   rW   r6   rZ   r$   rx   rE   r3   r5   s               r   'test_all_items_have_health_check_resultz7TestIntegration.test_all_items_have_health_check_resultm  s^   !!#  %A% A%%%% A%%%%%%v%%%v%%% %%%A%%%%%%%zz&--(HHWb) 	D( (D0  (D  I )  v   -1  I -1    $((HI    	r   c                   | j                         }|j                  }d}||k(  }|st        j                  d|fd||f      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}}t        j                  |j                        }h d	}|j                  d
g       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  }t        j                  d|	j                  d       d|	d    d      dz   d|iz  }t        t        j                  |            dx}
} y)r  r   r   rH  rn  rI  r  rT   N>   r  r  r<   r^   r  r@   rB   r  rC   rq   rf   r  r}   ra   r)   rT  )r   rn  r4   rN   rW   r6   rZ   r$   r  rx   rE   r3   r5   s                r   %test_health_check_result_values_validz5TestIntegration.test_health_check_result_values_validw  sv   !!#  %A% A%%%% A%%%%%%v%%%v%%% %%%A%%%%%%%zz&--((HHWb) 	D-. .%7  .%  I /  v   38  I 38    $(t<Q7R6SSlm    	r   c           	     0   | j                         }|j                  }d}||k(  }|st        j                  d|fd||f      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}}t        j                  |j                        }|d	   d
   }|d   }t        |      }	||	k(  }
|
st        j                  d|
fd||	f      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |	      dz  }t        j                  d|d	   d
    dt        |d          d      dz   d|iz  }t        t        j                  |            dx}x}
x}}	y)u=   summary.total 이 items 리스트 개수와 같아야 한다.r   r   rH  rn  rI  r  rT   Nr  r  r^   r  rR   r  zsummary.total(z) != len(items)(r  z
>assert %(py9)srV   )rD  rj  r+   r,   r-   r.   r/   r0   r1   r2   r1  r2  rl  rR   rc   )r   rn  r4   rN   rW   r6   rZ   r$   rE   rX   r3   r[   s               r   &test_summary_total_matches_items_countz6TestIntegration.test_summary_total_matches_items_count  s   !!#  %A% A%%%% A%%%%%%v%%%v%%% %%%A%%%%%%%zz&--(Iw' 	
tG} 	
3}+= 	
'+== 	
 	
'+= 	
 	
 		 ( 	
 	
	6	
 	
  ,/ 	
 	
 		 ,/ 	
 	
 		 0= 	
 	
 		 ,> 	
 	
  T)_W566Fs4PW=GYFZZ[\	
 	
 	
 	
 	
 	
r   c                   | j                         }|j                  }d}||k(  }|st        j                  d|fd||f      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}}t        j                  |j                        }|d	   }t        d
 |j                         D              }	|d   }
|	|
k(  }|st        j                  d|fd|	|
f      dt	        j
                         v st        j                  |	      rt        j                  |	      ndt        j                  |
      dz  }t        j                  d|	 d|d    d      dz   d|iz  }t        t        j                  |            dx}}
y)u?   summary 의 status 카운트 합이 total 과 같아야 한다.r   r   rH  rn  rI  r  rT   Nr  c              3  \   K   | ]$  \  }}|d k7  rt        |t        t        f      r| & ywr  )rJ   r   float)rA  r  r  s      r   rC  zJTestIntegration.test_summary_status_counts_sum_to_total.<locals>.<genexpr>  s0      
!QG|
1sEl ; 
s   *,r  r  r  r%   r  r  r  ra   r)   )rD  rj  r+   r,   r-   r.   r/   r0   r1   r2   r1  r2  rl  r  r^   rc   )r   rn  r4   rN   rW   r6   rZ   r$   r  r  r3   r5   s               r   r  z7TestIntegration.test_summary_status_counts_sum_to_total  sn   !!#  %A% A%%%% A%%%%%%v%%%v%%% %%%A%%%%%%%zz&--(O 
'')
 

 wZ 	
zZ' 	
 	
zZ 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ( 	
 	
  ZLAgJ<qA	
 	
 	
 	
 	
r   c                r   t        j                  t        j                               }t	        t        |d               }| j                  d|      }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d|j                         d	z   d
|iz  }t!        t        j"                  |            dx}x}}t%        j&                  |j(                        }	|	j+                  dg       D ]  }
|
d   }||k(  }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d|
d    d      dz   d|iz  }t!        t        j"                  |            dx}} |	j+                  di       D ]  }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d| d      dz   d|iz  }t!        t        j"                  |            d} y)u'   --source 옵션이 동작해야 한다.rH   z--sourcer   r   rH  rn  rI  u   --source 실패: rK  rT   Nr^   r  r   first_sourcerC   u   필터 후 다른 source 'u   ' 가 포함됨ra   r)   r  )z%(py0)s == %(py2)sr_   r  u   by_source 에 다른 source 'r  rS   )r   r   r9   r*   nextiterrD  rj  r+   r,   r-   r.   r/   r0   rc   rL  r1   r2   r1  r2  rl  rn   )r   r   r^  rn  r4   rN   rW   r6   rZ   r$   rx   rE   r3   r5   r_   r  r  s                    r   test_source_filter_cliz&TestIntegration.test_source_filter_cli  s<   }'>'>'@ADy!9:;!!*l;  JAJ A%JJJ AJJJJJJvJJJvJJJ JJJAJJJ):6==/'JJJJJJJJzz&--(HHWb) 	D> >\1  >\  I "  v   &2  I &2    -T(^,<OL    	
 88K, 	]C,&\\\3,\\\\\\3\\\3\\\\\\,\\\,\\\\*GuO(\\\\\\\	]r   c                   | j                  dd      }|j                  }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d|j                         d	z   d
|iz  }t        t        j                  |            dx}x}}t        j                  |j                        }|j                  dg       D ]  }|d   }	d}|	|k(  }
|
st        j                  d|
fd|	|f      t        j                  |	      t        j                  |      dz  }t        j                  d|d    d      dz   d|iz  }t        t        j                  |            dx}	x}
} y)u'   --status 옵션이 동작해야 한다.z--statusr   r   r   rH  rn  rI  u   --status 실패: rK  rT   Nr^   rh   r  r  u   필터 후 status='u   ' 항목이 포함됨r  r   )rD  rj  r+   r,   r-   r.   r/   r0   rc   rL  r1   r2   r1  r2  rl  rn   )r   rn  r4   rN   rW   r6   rZ   r$   rx   rE   r3   r  r  s                r   test_status_filter_cliz&TestIntegration.test_status_filter_cli  sa   !!*h7  JAJ A%JJJ AJJJJJJvJJJvJJJ JJJAJJJ):6==/'JJJJJJJJzz&--(HHWb) 	D> X >X-  >X  I "  I &.    &d8n%55JK     	r   c                   | j                  d      }|j                  }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d|j                         dz   d	|iz  }t        t        j                  |            d
x}x}}t        j                  |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  }
t        j                  d      dz   d|
iz  }t        t        j                  |            d
x}}	y
)u:   --summary 옵션 실행 시 items 키가 없어야 한다.z	--summaryr   r   rH  rn  rI  u   --summary 실패: rK  rT   Nr  r@   rB   r$   rC   r(   r)   r^   r  r  u2   items 가 --summary 출력에 포함되면 안 됨ra   )rD  rj  r+   r,   r-   r.   r/   r0   rc   rL  r1   r2   r1  r2  rl  r   rn  r4   rN   rW   r6   rZ   r$   rE   r3   r5   s              r    test_summary_flag_excludes_itemsz0TestIntegration.test_summary_flag_excludes_items  sk   !!+.  KAK A%KKK AKKKKKKvKKKvKKK KKKAKKK);FMM?'KKKKKKKKzz&--( yD    yD   y      D   D       Xwd"XXXwdXXXwXXXXXXdXXXdXXXX$XXXXXXXr   c                   | j                         }|j                  }d}||k(  }|st        j                  d|fd||f      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}}t        j                  |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}}	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}}	t        j                  |d          y)uL   실제 레지스트리로 실행한 출력의 전체 구조를 검증한다.r   r   rH  rn  rI  r  rT   Nr  r@   rB   r$   rC   r(   r)   r  r^   r  r(  )rD  rj  r+   r,   r-   r.   r/   r0   r1   r2   r1  r2  rl  r   r3  re  s              r   #test_real_registry_output_structurez3TestIntegration.test_real_registry_output_structure  sc   !!#  %A% A%%%% A%%%%%%v%%%v%%% %%%A%%%%%%%zz&--( yD    yD   y      D   D       "{d""""{d"""{""""""d"""d"""""""w$w$w$$#|t####|t###|######t###t#######"{d""""{d"""{""""""d"""d"""""""tK01r   Nr   )rB  r   r   zsubprocess.CompletedProcess)r   r   r   r   r;   r   r<  rD  rF  rM  rP  rR  rU  rW  rY  r  ra  rc  rf  rh  r   r   r   r7  r7  ?  s    5V^^D![ "[
O
 V^^D!` "`

&F	

] Y2r   r7  )$r   
__future__r   builtinsr-   _pytest.assertion.rewrite	assertionrewriter+   r1  r   rh  r@  textwrapr   r   r   r   pathlibr   r;   r   r;  r9   dedentr   r|   r   r   r   r   r"  ra  r  r  r7  r   r   r   <module>rq     s    #    	  
   2 2    KLJK 'x ?( ? B S[ [F"6 "6J1@ 1@h<R <R~JB JBZ!1 !1`LT LThv3 v3@I2 I2r   