
    CiE                     h   d Z ddlZddlmc mZ ddlZddlZddl	Z	ddl
mZ ddlZddlmZmZmZmZ ej$                  dedefd       Zej$                  dedefd       Zej$                  dedefd	       Z G d
 d      Z G d d      Z G d d      Z G d d      Z G d d      Z G d d      Zy)u   aio_tracker.py 단위 테스트 모음.

TDD RED→GREEN 절차에 따라 구현 전에 먼저 작성.
모든 테스트 데이터는 샘플 데이터임.
    N)Path)
AioTrackercalculate_change_rategenerate_markdown_report	parse_csvtmp_pathreturnc                 `    t        j                  d      }| dz  }|j                  |d       |S )u+   샘플 데이터: Before 기간 CSV 파일.z        referrer,sessions
        chat.openai.com,120
        perplexity.ai,45
        gemini.google.com,30
        claude.ai,10
        search.naver.com,200
        google.com,500
    z
before.csvutf-8encodingtextwrapdedent
write_textr   contentps      A/home/jay/workspace/tools/geo-analytics/tests/test_aio_tracker.pysample_before_csvr      s8     oo  	G 	<ALL7L+H    c                 `    t        j                  d      }| dz  }|j                  |d       |S )u*   샘플 데이터: After 기간 CSV 파일.z        referrer,sessions
        chat.openai.com,185
        perplexity.ai,72
        gemini.google.com,28
        claude.ai,25
        search.naver.com,240
        google.com,480
        bing.com,15
    z	after.csvr   r   r   r   s      r   sample_after_csvr   -   s8     oo 	 		G 	;ALL7L+Hr   c                 `    t        j                  d      }| dz  }|j                  |d       |S )uB   샘플 데이터: 'source' 컬럼명 사용 CSV (referrer 대신).zQ        source,sessions
        chat.openai.com,100
        perplexity.ai,50
    zbefore_source.csvr   r   r   r   s      r   sample_before_csv_source_colr   ?   s9     oo  	G
 	&&ALL7L+Hr   c                   L    e Zd ZdZdefdZdefdZdefdZdefdZdefd	Z	y
)TestParseCsvu   CSV 파일 파싱 테스트.r   c                 Z   t        |      }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}|d   }d}||k(  }|slt        j                  d	|fd
||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|d   }d}||k(  }|slt        j                  d	|fd
||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|d   }d}||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>   referrer, sessions 컬럼을 올바르게 파싱해야 한다.5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstanceresultdictpy0py1py2py4Nchat.openai.comx   ==z%(py1)s == %(py4)sr%   r'   assert %(py6)spy6perplexity.ai-   z
google.comi  )r   r    r"   @py_builtinslocals
@pytest_ar_should_repr_global_name	_safereprAssertionError_format_explanation_call_reprcompare)selfr   r!   @py_assert3@py_format5@py_assert0@py_assert2@py_format7s           r   test_parse_referrer_sessionsz)TestParseCsv.test_parse_referrer_sessionsT   s   ,-&$''''''''z'''z''''''&'''&''''''$'''$'''''''''''(/C/(C////(C///(///C///////o&,",&",,,,&",,,&,,,",,,,,,,l#*s*#s****#s***#***s*******r   r   c                    t        |      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}|d	   }d
}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t	        t        j
                  |            dx}x}}y)uI   'source' 컬럼도 'referrer' 컬럼과 동일하게 파싱해야 한다.r(   d   r*   r,   r-   r.   r/   Nr0   2   )r   r4   r9   r6   r7   r8   )r:   r   r!   r=   r;   r>   r<   r?   s           r   test_parse_source_columnz%TestParseCsv.test_parse_source_column\   s    78'(/C/(C////(C///(///C///////o&,",&",,,,&",,,&,,,",,,,,,,r   c                    t        |      }|j                         D ]#  }t        |t              }|sddt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      nddt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d}& y)u1   sessions 값은 정수로 반환되어야 한다.r   r    vintr#   N)r   valuesr    rG   r2   r3   r4   r5   r6   r7   r8   )r:   r   r!   rF   r;   r<   s         r   test_parse_returns_int_sessionsz,TestParseCsv.test_parse_returns_int_sessionsb   s    ,- 	&Aa%%%%%%%%:%%%:%%%%%%a%%%a%%%%%%%%%%%%%%%%%%%	&r   r   c                    |dz  }|j                  dd       t        |      }i }||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)uD   헤더만 있는 빈 CSV는 빈 딕셔너리를 반환해야 한다.z	empty.csvreferrer,sessions
r   r   r*   z%(py0)s == %(py3)sr!   r$   py3assert %(py5)spy5N)
r   r   r4   r9   r2   r3   r5   r6   r7   r8   )r:   r   r   r!   r>   @py_assert1@py_format4@py_format6s           r   test_parse_empty_filez"TestParseCsv.test_parse_empty_fileh   s~    {"	*W=1v|vvvr   c                 |    t        j                  t              5  t        |dz         ddd       y# 1 sw Y   yxY w)uJ   존재하지 않는 파일은 FileNotFoundError를 발생시켜야 한다.znonexistent.csvN)pytestraisesFileNotFoundErrorr   )r:   r   s     r   test_parse_file_not_foundz&TestParseCsv.test_parse_file_not_foundo   s2    ]],- 	4h!223	4 	4 	4s   2;N)
__name__
__module____qualname____doc__r   r@   rD   rI   rT   rY    r   r   r   r   Q   sA    &+d +-T -& &d 4$ 4r   r   c                   4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestCalculateChangeRateu   변화율 계산 테스트.c                 ~   t        dd      }d}||z
  }t        |      }d}||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                  |      t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            dx}x}x}x}}y)u3   증가 시 양수 변화율을 반환해야 한다.r)      beforeafterh|?K@{Gz?<)z;%(py6)s
{%(py6)s = %(py0)s((%(py1)s - %(py3)s))
} < %(py9)sabsrate)r$   r%   rN   r/   py9assert %(py11)spy11N
r   rj   r4   r9   r2   r3   r5   r6   r7   r8   )	r:   rk   r>   @py_assert4@py_assert5@py_assert8@py_assert7@py_format10@py_format12s	            r   test_increasez%TestCalculateChangeRate.test_increase}   s    $Cs; (4&=(s=!(D(!D((((!D((((((s(((s((((((4(((4(((&(((!(((D((((((((r   c                    t        dd      }d}| }||z
  }t        |      }d}||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                  |      t        j                  |      t        j                  |      d
z  }dd|iz  }	t        t        j                  |	            dx}x}x}x}x}}y)u3   감소 시 음수 변화율을 반환해야 한다.rB   P   rc   g      4@rg   rh   )z=%(py7)s
{%(py7)s = %(py0)s((%(py1)s - -%(py3)s))
} < %(py10)srj   rk   )r$   r%   rN   py7py10assert %(py12)spy12Nro   )
r:   rk   r>   rp   rq   @py_assert6@py_assert9rr   @py_format11@py_format13s
             r   test_decreasez%TestCalculateChangeRate.test_decrease   s    $Cr: )D5)45>)s>")T)"T))))"T))))))s)))s))))))4)))4)))D)))")))T))))))))r   c                    d}d}t        ||      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}x}x}}y
)u,   변화 없으면 0.0을 반환해야 한다.rB   rc   g        r*   )zF%(py6)s
{%(py6)s = %(py0)s(before=%(py2)s, after=%(py4)s)
} == %(py9)sr   )r$   r&   r'   r/   rl   rm   rn   N	r   r4   r9   r2   r3   r5   r6   r7   r8   )r:   rQ   r;   rq   rr   rs   rt   ru   s           r   test_no_changez&TestCalculateChangeRate.test_no_change   s    ,/BsB$Cs;BsB;sBBBB;sBBBBBB$BBB$BBBCBBBsBBB;BBBsBBBBBBBBr   c                 h   t        dd      }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)u>   before가 0이면 None을 반환해야 한다 (NEW 표시용).r   rC   rc   Nisz%(py0)s is %(py3)sr!   rM   rO   rP   r   r:   r!   r>   rQ   rR   rS   s         r   test_before_zero_returns_nonez5TestCalculateChangeRate.test_before_zero_returns_none   sf    &ar:v~vvvr   c                 h   t        dd      }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)
u;   before와 after 모두 0이면 None을 반환해야 한다.r   rc   Nr   r   r!   rM   rO   rP   r   r   s         r   test_both_zero_returns_nonez3TestCalculateChangeRate.test_both_zero_returns_none   sf    &aq9v~vvvr   c                 ^   t        dd      }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d	}y	)
u#   반환값은 float이어야 한다.rB      rc   r   r    r!   floatr#   N)
r   r    r   r2   r3   r4   r5   r6   r7   r8   )r:   r!   r;   r<   s       r   test_returns_floatz*TestCalculateChangeRate.test_returns_float   s    &c=&%((((((((z(((z((((((&(((&((((((%(((%((((((((((r   N)
rZ   r[   r\   r]   rv   r   r   r   r   r   r^   r   r   r`   r`   z   s$    %)
*
C

)r   r`   c                   x    e Zd ZdZdedefdZdedefdZdedefdZdedefdZdedefdZ	d	efd
Z
d	efdZy)TestAioTrackeru#   AioTracker 핵심 기능 테스트.r   r   c                 :   t        ||      }|j                  }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z  }dd|iz  }t        t        j                  |            dx}x}}|j                  }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z  }dd|iz  }t        t        j                  |            dx}x}}y)
uB   CSV 경로로 초기화하면 데이터가 로드되어야 한다.
before_csv	after_csvNis not)z7%(py2)s
{%(py2)s = %(py0)s.before_data
} is not %(py5)stracker)r$   r&   rP   zassert %(py7)sry   )z6%(py2)s
{%(py2)s = %(py0)s.after_data
} is not %(py5)s)r   before_datar4   r9   r2   r3   r5   r6   r7   r8   
after_data)	r:   r   r   r   rQ   rp   r;   rS   @py_format8s	            r   test_init_with_csvz!TestAioTracker.test_init_with_csv   s    (&
 "".$."$...."$......w...w..."...$.......!!--!----!------w---w---!----------r   c                     t        ||      }|j                         }|D cg c]  }|d   	 }}d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }	dd	|	iz  }
t        t        j                  |
            d
x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }	dd	|	iz  }
t        t        j                  |
            d
x}}y
c c}w )uP   ChatGPT, Perplexity 등 알려진 AI 소스가 요약에 포함되어야 한다.r   sourceChatGPTinz%(py1)s in %(py3)ssource_namesr%   rN   rO   rP   N
Perplexity
r   compute_ai_summaryr4   r9   r6   r2   r3   r5   r7   r8   r:   r   r   r   summaryrowr   r=   r>   rR   rS   s              r   %test_compute_ai_summary_known_sourcesz4TestAioTracker.test_compute_ai_summary_known_sources   s     (&
 ,,. 299#H99(yL((((yL(((y((((((L(((L(((((((+||++++||+++|++++++|+++|+++++++ :s   E;c                    t        ||      }|j                         }|D cg c]  }|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
c c}w )uD   google.com 등 비-AI 소스는 요약에서 제외되어야 한다.r   r      기타)not in)z%(py1)s not in %(py3)sr   r   rO   rP   Nr   r   s              r   'test_compute_ai_summary_excludes_non_aiz6TestAioTracker.test_compute_ai_summary_excludes_non_ai   s     (&
 ,,.189#H99+x|++++x|+++x++++++|+++|+++++++ :s   Cc                 t   t        ||      }|j                         }t        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}}|d   }d}||k(  }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }	dd	|	iz  }
t        t        j                  |
            d
x}x}}|d   }d}||z
  }t        |      }d}||k  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j
                  t              ndt        j
                  |      t        j
                  |      t        j
                  |      t        j
                  |      dz  }dd|iz  }t        t        j                  |            d
x}x}x}x}x}}y
)uM   ChatGPT 변화율이 정확하게 계산되어야 한다 (120→185, +54.2%).r   c              3   2   K   | ]  }|d    dk(  s|  ywr   r   Nr^   .0rs     r   	<genexpr>zETestAioTracker.test_compute_ai_summary_change_rate.<locals>.<genexpr>        J8	1I1J   rd   r)   r*   r,   r-   r.   r/   Nre   rb   change_raterf   rg   rh   z<%(py7)s
{%(py7)s = %(py0)s((%(py2)s - %(py4)s))
} < %(py10)srj   r$   r&   r'   ry   rz   r{   r|   r   r   nextr4   r9   r6   r7   r8   rj   r2   r3   r5   )r:   r   r   r   r   chatgpt_rowr=   r;   r>   r<   r?   rQ   rq   r}   r~   rr   r   r   s                     r   #test_compute_ai_summary_change_ratez2TestAioTracker.test_compute_ai_summary_change_rate   s~    (&
 ,,.JgJJ8$++$++++$+++$++++++++++7#*s*#s****#s***#***s*******}->>-6>s67>$>7$>>>>7$>>>>>>s>>>s>>>->>>>>>7>>>$>>>>>>>>r   c                 >    t        ||      }|j                         }y)uE   Before에 없던 소스는 change_rate가 None이어야 한다 (NEW).r   N)r   r   )r:   r   r   r   r   s        r   "test_compute_ai_summary_new_sourcez1TestAioTracker.test_compute_ai_summary_new_source   s$     (&
 ,,.r   r   c                    |dz  }|dz  }|j                  dd       |j                  dd       t        ||      }|j                         }g }||k(  }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
z  }dd|iz  }	t        t        j                  |	            dx}}y)uH   빈 CSV 두 개로 초기화하면 요약이 빈 리스트여야 한다.zempty_before.csvzempty_after.csvrK   r   r   r   r*   rL   r   rM   rO   rP   N)r   r   r   r4   r9   r2   r3   r5   r6   r7   r8   )
r:   r   before_pafter_pr   r   r>   rQ   rR   rS   s
             r   "test_compute_ai_summary_empty_dataz1TestAioTracker.test_compute_ai_summary_empty_data   s    00..1GD07CGD,,.w"}w"ww"r   c                    |dz  }|dz  }|j                  dd       |j                  dd       t        ||      }|j                         }t        d |D        d	      }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}}|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>   감소 케이스: gemini.google.com 30→28, 변화율 음수.zb.csvza.csvz'referrer,sessions
gemini.google.com,30
r   r   z'referrer,sessions
gemini.google.com,28
r   c              3   2   K   | ]  }|d    dk(  s|  yw)r   GeminiNr^   r   s     r   r   zBTestAioTracker.test_compute_ai_summary_decrease.<locals>.<genexpr>   s     I81H1Ir   Nr   )z%(py0)s is not %(py3)s
gemini_rowrM   rO   rP   r   r   rh   )z%(py1)s < %(py4)sr-   r.   r/   )r   r   r   r   r4   r9   r2   r3   r5   r6   r7   r8   )r:   r   r   r   r   r   r   r>   rQ   rR   rS   r=   r;   r<   r?   s                  r    test_compute_ai_summary_decreasez/TestAioTracker.test_compute_ai_summary_decrease   s   g%W$GRYZFQXYGD,,.IgI4P
!%%z%%%%z%%%%%%z%%%z%%%%%%%%%%-(,1,(1,,,,(1,,,(,,,1,,,,,,,r   N)rZ   r[   r\   r]   r   r   r   r   r   r   r   r   r^   r   r   r   r      s    -.D .D .,!%,9=,
,!%
,9=
,?!%?9=?/!%/9=/	4 	- -r   r   c                   d    e Zd ZdZej
                  d        Zd Zd Zd Z	d Z
d Zd Zd	 Zd
 Zy)TestGenerateMarkdownReportu(   마크다운 리포트 생성 테스트.c                 6    ddddddddd	dd
dddddddddgS )u6   샘플 데이터: compute_ai_summary 반환값 형태.r   r)   rb   rf   )r   rd   re   r   r   r1   H   g      N@r         gDlClauder      Nr^   )r:   s    r   sample_summaryz)TestGenerateMarkdownReport.sample_summary  s>     !C#fU#rBtT26R1r$O	
 	
r   c                 h   t        |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
)uG   리포트에 '# AIO 성과 리포트' 제목이 포함되어야 한다.
2026-03-26
date_label   # AIO 성과 리포트r   r   reportr   rO   rP   N	r   r4   r9   r6   r2   r3   r5   r7   r8   r:   r   r   r=   r>   rR   rS   s          r   test_report_contains_titlez5TestGenerateMarkdownReport.test_report_contains_title  sj    ).\R'1'61111'6111'111111611161111111r   c                 F   t        |d      }d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}y
)u?   리포트에 마크다운 표 헤더가 포함되어야 한다.r   r   u
   | 소스 |r   r   r   r   rO   rP   Nz
| Before |z	| After |u   | 변화율 |r   r   s          r   !test_report_contains_table_headerz<TestGenerateMarkdownReport.test_report_contains_table_header  sr   ).\R%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%${f$$$${f$$${$$$$$$f$$$f$$$$$$$(&((((&(((((((((&(((&(((((((r   c                 F   t        |d      }d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}y
)u:   ChatGPT 행이 올바른 값으로 포함되어야 한다.r   r   r   r   r   r   r   rO   rP   N120185z+54.2%r   r   s          r   test_report_chatgpt_rowz2TestGenerateMarkdownReport.test_report_chatgpt_row  sp   ).\R"yF""""yF"""y""""""F"""F"""""""uuuuuu!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!r   c                 h   t        |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
)uR   change_rate가 None인 소스는 변화율 셀에 'NEW'가 표시되어야 한다.r   r   NEWr   r   r   r   rO   rP   Nr   r   s          r   test_report_new_displayz2TestGenerateMarkdownReport.test_report_new_display   sf    ).\Ruuur   c                 h   t        |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=   음수 변화율은 '-6.7%' 형태로 표시되어야 한다.r   r   z-6.7%r   r   r   r   rO   rP   Nr   r   s          r   test_report_negative_changez6TestGenerateMarkdownReport.test_report_negative_change%  sg    ).\R w&    w&   w      &   &       r   c                 h   t        |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	)
uB   지정한 날짜 레이블이 리포트에 포함되어야 한다.r   r   r   r   r   r   rO   rP   Nr   r   s          r   test_report_date_labelz1TestGenerateMarkdownReport.test_report_date_label*  sg    ).\R%|v%%%%|v%%%|%%%%%%v%%%v%%%%%%%r   c                 h   t        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
)uG   빈 요약 데이터도 에러 없이 리포트를 생성해야 한다.r   r   r   r   r   r   r   rO   rP   Nr   )r:   r   r=   r>   rR   rS   s         r   test_report_empty_summaryz4TestGenerateMarkdownReport.test_report_empty_summary/  sj    )"F'1'61111'6111'111111611161111111r   c                 h   t        |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@   양수 변화율은 '+' 기호와 함께 표시되어야 한다.r   r   z+60.0%r   r   r   r   rO   rP   Nr   r   s          r    test_report_perplexity_plus_signz;TestGenerateMarkdownReport.test_report_perplexity_plus_sign4  sg    ).\R!x6!!!!x6!!!x!!!!!!6!!!6!!!!!!!r   N)rZ   r[   r\   r]   rV   fixturer   r   r   r   r   r   r   r   r   r^   r   r   r   r      sC    2^^
 
2
)"
!
&
2
"r   r   c                   4    e Zd ZdZdefdZd Zd Zd Zd Z	y)	TestAiSourceClassificationu7   config.py의 classify_ai_source와의 통합 테스트.r   c                    ddl m} d} ||      }d}||k(  }|st        j                  d|fd||f      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)u?   chat.openai.com 레퍼러는 ChatGPT로 분류되어야 한다.r   classify_ai_sourcezhttps://chat.openai.com/r   r*   z0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py7)sr   r$   r&   r'   ry   assert %(py9)srl   N
configr   r4   r9   r2   r3   r5   r6   r7   r8   )	r:   r   r   rQ   r;   r}   rq   r   rt   s	            r   test_chatgpt_classifiedz2TestAiSourceClassification.test_chatgpt_classifiedB  s    -"<J!"<=JJ=JJJJ=JJJJJJ!JJJ!JJJ"<JJJ=JJJJJJJJJJr   c                    ddl m} d} ||      }d}||k(  }|st        j                  d|fd||f      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)u@   perplexity.ai 레퍼러는 Perplexity로 분류되어야 한다.r   r   u)   https://www.perplexity.ai/search?q=보험r   r*   r   r   r   r   rl   Nr   r:   r   rQ   r;   r}   rq   r   rt   s           r   test_perplexity_classifiedz5TestAiSourceClassification.test_perplexity_classifiedH  s    -"M^!"MN^R^^NR^^^^^NR^^^^^^^!^^^!^^^"M^^^N^^^R^^^^^^^^r   c                    ddl m} d} ||      }d}||k(  }|st        j                  d|fd||f      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)u>   search.naver.com은 '네이버 AIO'로 분류되어야 한다.r   r   u.   https://search.naver.com/search.naver?q=보험   네이버 AIOr*   r   r   r   r   rl   Nr   r   s           r   test_naver_aio_classifiedz4TestAiSourceClassification.test_naver_aio_classifiedN  s    -"Rf!"RSfWffSWfffffSWfffffff!fff!fff"RfffSfffWffffffffr   c                    ddl m} d} ||      }d}||k(  }|st        j                  d|fd||f      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)u?   알 수 없는 레퍼러는 '기타'로 분류되어야 한다.r   r   u"   https://google.com/search?q=보험r   r*   r   r   r   r   rl   Nr   r   s           r   test_unknown_referrer_is_otherz9TestAiSourceClassification.test_unknown_referrer_is_otherT  s    -"FS!"FGS8SG8SSSSG8SSSSSS!SSS!SSS"FSSSGSSS8SSSSSSSr   c                 p   ddl m}  |dd      }d}||k7  }|st        j                  d|fd||f      d	t	        j
                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
z  }dd|iz  }t        t        j                  |            dx}}y)uR   utm_source가 'ai_' 접두사로 시작하면 AI 소스로 분류되어야 한다.r   r    ai_perplexity)
utm_sourcer   )!=)z%(py0)s != %(py3)sr!   rM   rO   rP   Nr   )r:   r   r!   r>   rQ   rR   rS   s          r   test_utm_ai_prefix_classifiedz8TestAiSourceClassification.test_utm_ai_prefix_classifiedZ  sj    -#B?C!!v!!!!v!!!!!!v!!!v!!!!!!!!!!r   N)
rZ   r[   r\   r]   r   r   r   r   r  r  r^   r   r   r   r   ?  s*    AK K_gT"r   r   c                       e Zd ZdZej
                  dedefd       Zej
                  dedefd       ZdedefdZ	dedefd	Z
dedefd
ZdedefdZy)TestInsuranceDomainSampleuF   보험 도메인 샘플 데이터로 전체 파이프라인 테스트.r   r	   c                 `    t        j                  d      }|dz  }|j                  |d       |S )u4   샘플 데이터: 보험 사이트 Before 트래픽.a              referrer,sessions
            chat.openai.com,320
            perplexity.ai,180
            gemini.google.com,95
            claude.ai,40
            search.naver.com,850
            google.com,4200
            naver.com,3100
            direct,1200
        zinsurance_before.csvr   r   r   r:   r   r   r   s       r   insurance_before_csvz.TestInsuranceDomainSample.insurance_before_csvj  s9     // 
# 
 --	Ww/r   c                 `    t        j                  d      }|dz  }|j                  |d       |S )u3   샘플 데이터: 보험 사이트 After 트래픽.a+              referrer,sessions
            chat.openai.com,510
            perplexity.ai,295
            gemini.google.com,88
            claude.ai,120
            search.naver.com,1250
            google.com,4050
            naver.com,2980
            direct,1350
            chatgpt.com,75
        zinsurance_after.csvr   r   r   r  s       r   insurance_after_csvz-TestInsuranceDomainSample.insurance_after_csv|  s9     // #  ,,	Ww/r   r  r  c                    t        ||      }|j                         }t        |d      }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d	}t        |      }d
}	||	kD  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            d	x}x}
}	y	)uZ   보험 샘플 데이터로 전체 파이프라인이 에러 없이 실행되어야 한다.r   r   r   r   r    r   strr#   Nr   )>)z/%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} > %(py6)slen)r$   r%   rN   r/   zassert %(py8)spy8)r   r   r   r    r  r2   r3   r4   r5   r6   r7   r8   r  r9   )r:   r  r  r   r   r   r;   r<   r>   rq   rp   r?   @py_format9s                r   test_insurance_pipeline_runsz6TestInsuranceDomainSample.test_insurance_pipeline_runs  sC    +)
 ,,.)'lK&#&&&&&&&&z&&&z&&&&&&&&&&&&&&&&&#&&&#&&&&&&&&&&6{Q{Q{Qss66{Qr   c                 t   t        ||      }|j                         }t        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}}|d   }d}||k(  }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }	dd	|	iz  }
t        t        j                  |
            d
x}x}}|d   }d}||z
  }t        |      }d}||k  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j
                  t              ndt        j
                  |      t        j
                  |      t        j
                  |      t        j
                  |      dz  }dd|iz  }t        t        j                  |            d
x}x}x}x}x}}y
)u   보험 샘플: ChatGPT 320→585 증가 (chat.openai.com 510 + chatgpt.com 75).

        config.AI_SOURCES에서 ChatGPT 패턴이 chatgpt.com도 포함하므로
        after 합계는 510+75=585.
        변화율 = (585-320)/320*100 ≈ +82.8%
        r   c              3   2   K   | ]  }|d    dk(  s|  ywr   r^   r   s     r   r   zLTestInsuranceDomainSample.test_insurance_chatgpt_increase.<locals>.<genexpr>  r   r   rd   i@  r*   r,   r-   r.   r/   Nre   iI  r   g     T@rg   rh   r   rj   r   r{   r|   r   )r:   r  r  r   r   r   r=   r;   r>   r<   r?   rQ   rq   r}   r~   rr   r   r   s                     r   test_insurance_chatgpt_increasez9TestInsuranceDomainSample.test_insurance_chatgpt_increase  s~    +)
 ,,.JgJJ8$++$++++$+++$++++++++++7#*s*#s****#s***#***s*******}-??-7?s78?4?84????84??????s???s???-??????8???4????????r   c                 t   t        ||      }|j                         }t        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}}|d   }d}||k(  }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }	dd	|	iz  }
t        t        j                  |
            d
x}x}}|d   }d}||z
  }t        |      }d}||k  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j
                  t              ndt        j
                  |      t        j
                  |      t        j
                  |      t        j
                  |      dz  }dd|iz  }t        t        j                  |            d
x}x}x}x}x}}y
)u;   보험 샘플: 네이버 AIO 850→1250, 약 +47.1% 증가.r   c              3   2   K   | ]  }|d    dk(  s|  yw)r   r   Nr^   r   s     r   r   zNTestInsuranceDomainSample.test_insurance_naver_aio_increase.<locals>.<genexpr>  s     Nqq{o/MNr   rd   iR  r*   r,   r-   r.   r/   Nre   i  r   gClG@rg   rh   r   rj   r   r{   r|   r   )r:   r  r  r   r   	naver_rowr=   r;   r>   r<   r?   rQ   rq   r}   r~   rr   r   r   s                     r   !test_insurance_naver_aio_increasez;TestInsuranceDomainSample.test_insurance_naver_aio_increase  s~    +)
 ,,.NGNN	")c)"c))))"c)))")))c)))))))!)T)!T))))!T)))!)))T)))))))]+<f<+f4<s45<<5<<<<5<<<<<<s<<<s<<<+<<<f<<<5<<<<<<<<<<<r   c                 6   t        ||      }|j                         }t        |d      }d}||v }|st        j                  d|fd||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      nddz  }d	d
|iz  }	t        t        j                  |	            dx}}d}||v }|st        j                  d|fd||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      nddz  }d	d
|iz  }	t        t        j                  |	            dx}}d}||v }|st        j                  d|fd||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      nddz  }d	d
|iz  }	t        t        j                  |	            dx}}y)uJ   보험 샘플 리포트가 마크다운 표 형식을 포함해야 한다.r   r   r   z|---r   r   r   r   rO   rP   Nr   r   )r   r   r   r4   r9   r6   r2   r3   r5   r7   r8   )
r:   r  r  r   r   r   r=   r>   rR   rS   s
             r   %test_insurance_report_markdown_outputz?TestInsuranceDomainSample.test_insurance_report_markdown_output  s;    +)
 ,,.)'lKvvv"yF""""yF"""y""""""F"""F"""""""(&((((&(((((((((&(((&(((((((r   N)rZ   r[   r\   r]   rV   r   r   r  r  r  r  r  r  r^   r   r   r
  r
  g  s    P^^T d  " ^^D T  $$(?C@$(@?C@&=$(=?C=)$()?C)r   r
  )r]   builtinsr2   _pytest.assertion.rewrite	assertionrewriter4   csvior   pathlibr   rV   aio_trackerr   r   r   r   r   r   r   r   r   r`   r   r   r   r
  r^   r   r   <module>r(     s     
 	          t   " 	4 	D 	 	"!4 !4R) )LV- V-|9" 9"B "  "Pc) c)r   