
    CiE                     N   d 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J |d   dk(  sJ |d   dk(  sJ |d   dk(  sJ y)u>   referrer, sessions 컬럼을 올바르게 파싱해야 한다.chat.openai.comx   perplexity.ai-   z
google.comi  N)r   
isinstancedict)selfr   results      r   test_parse_referrer_sessionsz)TestParseCsv.test_parse_referrer_sessionsT   sW    ,-&$''''(C///o&",,,l#s***r   r   c                 B    t        |      }|d   dk(  sJ |d   dk(  sJ y)uI   'source' 컬럼도 'referrer' 컬럼과 동일하게 파싱해야 한다.r   d   r!   2   N)r   )r%   r   r&   s      r   test_parse_source_columnz%TestParseCsv.test_parse_source_column\   s4    78'(C///o&",,,r   c                 h    t        |      }|j                         D ]  }t        |t              rJ  y)u1   sessions 값은 정수로 반환되어야 한다.N)r   valuesr#   int)r%   r   r&   vs       r   test_parse_returns_int_sessionsz,TestParseCsv.test_parse_returns_int_sessionsb   s2    ,- 	&Aa%%%	&r   r   c                 X    |dz  }|j                  dd       t        |      }|i k(  sJ y)uD   헤더만 있는 빈 CSV는 빈 딕셔너리를 반환해야 한다.z	empty.csvreferrer,sessions
r   r   N)r   r   )r%   r   r   r&   s       r   test_parse_empty_filez"TestParseCsv.test_parse_empty_fileh   s2    {"	*W=1||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'   r+   r0   r3   r8    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                 D    t        dd      }t        |dz
        dk  sJ y)u3   증가 시 양수 변화율을 반환해야 한다.r       beforeafterh|?K@{Gz?Nr   absr%   rates     r   test_increasez%TestCalculateChangeRate.test_increase}   s%    $Cs;4&=!D(((r   c                 D    t        dd      }t        |dz
        dk  sJ y)u3   감소 시 음수 변화율을 반환해야 한다.r)   P   rB   g      4rF   NrG   rI   s     r   test_decreasez%TestCalculateChangeRate.test_decrease   s%    $Cr:45>"T)))r   c                 (    t        dd      dk(  sJ y)u,   변화 없으면 0.0을 반환해야 한다.r)   rB   g        Nr   r%   s    r   test_no_changez&TestCalculateChangeRate.test_no_change   s    $Cs;sBBBr   c                 &    t        dd      }|J y)u>   before가 0이면 None을 반환해야 한다 (NEW 표시용).r   r*   rB   NrP   r%   r&   s     r   test_before_zero_returns_nonez5TestCalculateChangeRate.test_before_zero_returns_none   s    &ar:~~r   c                 &    t        dd      }|J y)u;   before와 after 모두 0이면 None을 반환해야 한다.r   rB   NrP   rT   s     r   test_both_zero_returns_nonez3TestCalculateChangeRate.test_both_zero_returns_none   s    &aq9~~r   c                 B    t        dd      }t        |t              sJ y)u#   반환값은 float이어야 한다.r)      rB   N)r   r#   floatrT   s     r   test_returns_floatz*TestCalculateChangeRate.test_returns_float   s    &c=&%(((r   N)
r9   r:   r;   r<   rK   rN   rR   rU   rW   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                 V    t        ||      }|j                  J |j                  J y)uB   CSV 경로로 초기화하면 데이터가 로드되어야 한다.
before_csv	after_csvN)r   before_data
after_data)r%   r   r   trackers       r   test_init_with_csvz!TestAioTracker.test_init_with_csv   s8    (&
 ""...!!---r   c                     t        ||      }|j                         }|D cg c]  }|d   	 }}d|v sJ d|v sJ yc c}w )uP   ChatGPT, Perplexity 등 알려진 AI 소스가 요약에 포함되어야 한다.r_   sourceChatGPT
PerplexityNr   compute_ai_summaryr%   r   r   rd   summaryrowsource_namess          r   %test_compute_ai_summary_known_sourcesz4TestAioTracker.test_compute_ai_summary_known_sources   s^     (&
 ,,. 299#H99L(((|+++ :s   =c                 x    t        ||      }|j                         }|D cg c]  }|d   	 }}d|vsJ yc c}w )uD   google.com 등 비-AI 소스는 요약에서 제외되어야 한다.r_   rg      기타Nrj   rl   s          r   'test_compute_ai_summary_excludes_non_aiz6TestAioTracker.test_compute_ai_summary_excludes_non_ai   sN     (&
 ,,.189#H99|+++ :s   7c                     t        ||      }|j                         }t        d |D              }|d   dk(  sJ |d   dk(  sJ t        |d   dz
        d	k  sJ y
)uM   ChatGPT 변화율이 정확하게 계산되어야 한다 (120→185, +54.2%).r_   c              3   2   K   | ]  }|d    dk(  s|  ywrg   rh   Nr=   .0rs     r   	<genexpr>zETestAioTracker.test_compute_ai_summary_change_rate.<locals>.<genexpr>        J8	1I1J   rC   r    rD   rA   change_raterE   rF   Nr   rk   nextrH   )r%   r   r   rd   rm   chatgpt_rows         r   #test_compute_ai_summary_change_ratez2TestAioTracker.test_compute_ai_summary_change_rate   sw     (&
 ,,.JgJJ8$+++7#s***;}-67$>>>r   c                 >    t        ||      }|j                         }y)uE   Before에 없던 소스는 change_rate가 None이어야 한다 (NEW).r_   Nrj   )r%   r   r   rd   rm   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(  sJ y)uH   빈 CSV 두 개로 초기화하면 요약이 빈 리스트여야 한다.zempty_before.csvzempty_after.csvr2   r   r   r_   N)r   r   rk   )r%   r   before_pafter_prd   rm   s         r   "test_compute_ai_summary_empty_dataz1TestAioTracker.test_compute_ai_summary_empty_data   sf    00..1GD07CGD,,."}}r   c                     |dz  }|dz  }|j                  dd       |j                  dd       t        ||      }|j                         }t        d |D        d	      }|J |d
   dk  sJ 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)rg   GeminiNr=   rw   s     r   rz   zBTestAioTracker.test_compute_ai_summary_decrease.<locals>.<genexpr>   s     I81H1Ir|   Nr}   r   )r   r   rk   r   )r%   r   r   r   rd   rm   
gemini_rows          r    test_compute_ai_summary_decreasez/TestAioTracker.test_compute_ai_summary_decrease   s    g%W$GRYZFQXYGD,,.IgI4P
%%%-(1,,,r   N)r9   r:   r;   r<   r   re   rp   rs   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 반환값 형태.rh   r    rA   rE   )rg   rC   rD   r}   ri   r"   H   g      N@r         gDlClauder      Nr=   rQ   s    r   sample_summaryz)TestGenerateMarkdownReport.sample_summary  s>     !C#fU#rBtT26R1r$O	
 	
r   c                 *    t        |d      }d|v sJ y)uG   리포트에 '# AIO 성과 리포트' 제목이 포함되어야 한다.
2026-03-26
date_label   # AIO 성과 리포트Nr   r%   r   reports      r   test_report_contains_titlez5TestGenerateMarkdownReport.test_report_contains_title  s    ).\R'6111r   c                 N    t        |d      }d|v sJ d|v sJ d|v sJ d|v sJ y)u?   리포트에 마크다운 표 헤더가 포함되어야 한다.r   r   u
   | 소스 |z
| Before |z	| After |u   | 변화율 |Nr   r   s      r   !test_report_contains_table_headerz<TestGenerateMarkdownReport.test_report_contains_table_header  sF    ).\Rv%%%v%%%f$$$&(((r   c                 N    t        |d      }d|v sJ d|v sJ d|v sJ d|v sJ y)u:   ChatGPT 행이 올바른 값으로 포함되어야 한다.r   r   rh   120185z+54.2%Nr   r   s      r   test_report_chatgpt_rowz2TestGenerateMarkdownReport.test_report_chatgpt_row  sB    ).\RF"""6!!!r   c                 *    t        |d      }d|v sJ y)uR   change_rate가 None인 소스는 변화율 셀에 'NEW'가 표시되어야 한다.r   r   NEWNr   r   s      r   test_report_new_displayz2TestGenerateMarkdownReport.test_report_new_display   s    ).\Rr   c                 *    t        |d      }d|v sJ y)u=   음수 변화율은 '-6.7%' 형태로 표시되어야 한다.r   r   z-6.7%Nr   r   s      r   test_report_negative_changez6TestGenerateMarkdownReport.test_report_negative_change%  s    ).\R&   r   c                 *    t        |d      }d|v sJ y)uB   지정한 날짜 레이블이 리포트에 포함되어야 한다.r   r   Nr   r   s      r   test_report_date_labelz1TestGenerateMarkdownReport.test_report_date_label*  s    ).\Rv%%%r   c                 *    t        g d      }d|v sJ y)uG   빈 요약 데이터도 에러 없이 리포트를 생성해야 한다.r   r   r   Nr   )r%   r   s     r   test_report_empty_summaryz4TestGenerateMarkdownReport.test_report_empty_summary/  s    )"F'6111r   c                 *    t        |d      }d|v sJ y)u@   양수 변화율은 '+' 기호와 함께 표시되어야 한다.r   r   z+60.0%Nr   r   s      r    test_report_perplexity_plus_signz;TestGenerateMarkdownReport.test_report_perplexity_plus_sign4  s    ).\R6!!!r   N)r9   r:   r;   r<   r5   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(  sJ y)u?   chat.openai.com 레퍼러는 ChatGPT로 분류되어야 한다.r   classify_ai_sourcezhttps://chat.openai.com/rh   Nconfigr   )r%   r   r   s      r   test_chatgpt_classifiedz2TestAiSourceClassification.test_chatgpt_classifiedB  s    -!"<=JJJr   c                 *    ddl m}  |d      dk(  sJ y)u@   perplexity.ai 레퍼러는 Perplexity로 분류되어야 한다.r   r   u)   https://www.perplexity.ai/search?q=보험ri   Nr   r%   r   s     r   test_perplexity_classifiedz5TestAiSourceClassification.test_perplexity_classifiedH  s    -!"MNR^^^^r   c                 *    ddl m}  |d      dk(  sJ y)u>   search.naver.com은 '네이버 AIO'로 분류되어야 한다.r   r   u.   https://search.naver.com/search.naver?q=보험   네이버 AIONr   r   s     r   test_naver_aio_classifiedz4TestAiSourceClassification.test_naver_aio_classifiedN  s    -!"RSWffffr   c                 *    ddl m}  |d      dk(  sJ y)u?   알 수 없는 레퍼러는 '기타'로 분류되어야 한다.r   r   u"   https://google.com/search?q=보험rr   Nr   r   s     r   test_unknown_referrer_is_otherz9TestAiSourceClassification.test_unknown_referrer_is_otherT  s    -!"FG8SSSr   c                 2    ddl m}  |dd      }|dk7  sJ y)uR   utm_source가 'ai_' 접두사로 시작하면 AI 소스로 분류되어야 한다.r   r    ai_perplexity)
utm_sourcerr   Nr   )r%   r   r&   s      r   test_utm_ai_prefix_classifiedz8TestAiSourceClassification.test_utm_ai_prefix_classifiedZ  s    -#B?C!!!r   N)
r9   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J t        |      dkD  sJ y)uZ   보험 샘플 데이터로 전체 파이프라인이 에러 없이 실행되어야 한다.r_   r   r   r   N)r   rk   r   r#   strlenr%   r   r   rd   rm   r   s         r   test_insurance_pipeline_runsz6TestInsuranceDomainSample.test_insurance_pipeline_runs  sP     +)
 ,,.)'lK&#&&&6{Qr   c                     t        ||      }|j                         }t        d |D              }|d   dk(  sJ |d   dk(  sJ t        |d   dz
        d	k  sJ 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rv   r=   rw   s     r   rz   zLTestInsuranceDomainSample.test_insurance_chatgpt_increase.<locals>.<genexpr>  r{   r|   rC   i@  rD   iI  r}   g     T@rF   Nr~   )r%   r   r   rd   rm   r   s         r   test_insurance_chatgpt_increasez9TestInsuranceDomainSample.test_insurance_chatgpt_increase  sw     +)
 ,,.JgJJ8$+++7#s***;}-784???r   c                     t        ||      }|j                         }t        d |D              }|d   dk(  sJ |d   dk(  sJ t        |d   dz
        d	k  sJ y
)u;   보험 샘플: 네이버 AIO 850→1250, 약 +47.1% 증가.r_   c              3   2   K   | ]  }|d    dk(  s|  yw)rg   r   Nr=   rw   s     r   rz   zNTestInsuranceDomainSample.test_insurance_naver_aio_increase.<locals>.<genexpr>  s     Nqq{o/MNr|   rC   iR  rD   i  r}   gClG@rF   Nr~   )r%   r   r   rd   rm   	naver_rows         r   !test_insurance_naver_aio_increasez;TestInsuranceDomainSample.test_insurance_naver_aio_increase  sw     +)
 ,,.NGNN	"c)))!T)))9]+f45<<<r   c                 |    t        ||      }|j                         }t        |d      }d|v sJ d|v sJ d|v sJ y)uJ   보험 샘플 리포트가 마크다운 표 형식을 포함해야 한다.r_   r   r   z|---rh   r   N)r   rk   r   r   s         r   %test_insurance_report_markdown_outputz?TestInsuranceDomainSample.test_insurance_report_markdown_output  sZ     +)
 ,,.)'lKF"""&(((r   N)r9   r:   r;   r<   r5   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<   csvior   pathlibr   r5   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   