§
    fOÇjÄb  ã                   óp  — 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
Z
ddlZddlmZ ddlmZmZ ddlmZmZmZ ej        dk    rC	 ej                             d¬¦  «         ej                             d¬¦  «         n# e$ r Y nw xY w ee¦  «                             ¦   «         j        j        Zed	z  Zed
z  Z e e¦  «         e e¦  «         e e¦  «        fD ]&Z!e!ej"        vrej"         #                    de!¦  «         Œ'ddl$m%Z% ddl&Z' G d„ dej(        ¦  «        Z)e*dk    r ej+        ¦   «          dS dS )uÛ  
=============================================================================
KIEU STORY â€” EMPIRICAL ADVERSARIAL TEST SUITE: M4 API & CONCURRENCY
=============================================================================
Agent: teamwork_preview_challenger (M4 Challenger 1)
Target: web_review/server.py

Empirical Stress-Testing Criteria:
1. /api/matrix/ep01:
   - Returns HTTP 200 with total_shots == 188
   - Structured across exactly 15 scenes (C01 to C15)
   - Every scene has exact shot breakdown matching screenplay contract
   - Scene 05 (Ä�áº¡m TiÃªn) has exactly 27 shots
   - Closed-lips guard flags and audio guards present
2. Concurrency & State Machine Protection:
   - When gen_state["status"] == "generating": both POST /api/generate and POST /api/concat return HTTP 409 Conflict
   - When gen_state["status"] == "concatenating": both POST /api/generate and POST /api/concat return HTTP 409 Conflict
   - Rapid multi-request stress flood (50 requests) guarantees zero 500 errors and 100% 409 rejection
3. Request Validation Rigor:
   - POST /api/generate without prompt or shot_id returns HTTP 422 Unprocessable Entity
   - Empty JSON, empty string prompts, invalid types return HTTP 422
   - Valid payloads are accepted (HTTP 200, status "accepted")
4. Concat Endpoint Rigor:
   - POST /api/concat without scene_id or with blank whitespace returns HTTP 422
   - Valid ConcatRequest is accepted (HTTP 200)
5. Windows Encoding & Logging Stability:
   - log_msg() handles extensive Vietnamese diacritics without crashing
   - Emulated cp1252 / ASCII terminal encode errors trigger safe fallback without uncaught exceptions
   - Rolling buffer keeps exactly 30 entries
6. Query Filtering & Security Boundaries:
   - Filtering /api/shots by ?episode=ep01 returns 188 shots
   - Filtering /api/shots by ?scene=ep01_scene05 returns 27 shots
   - Directory traversal attempts on /api/episode/{filename} return HTTP 404
=============================================================================
é    N)ÚPath)ÚpatchÚ	MagicMock)ÚDictÚAnyÚListÚwin32úutf-8©ÚencodingÚ
web_reviewÚ05_Production_Pipeline)Ú
TestClientc                   ó®   — e Zd ZdZed„ ¦   «         Zed„ ¦   «         Zdedefd„Z	d„ Z
d„ Zd	„ Zd
„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ ZdS )ÚTestAdversarialM4ApiConcurrencyz<Empirical adversarial test harness for web_review/server.py.c                 óÊ   — t          t          j        ¦  «        | _        t          dz  dz  | _        dt          j        dt          j        ¦   «         ¦  «        ddi ddœ| _	        d S )NÚtestsz+adversarial_m4_api_concurrency_results.jsonz M4 API & Concurrency Stress Testz%Y-%m-%dT%H:%M:%SZr   ÚPENDING)Ú
test_suiteÚ	timestampÚtotal_assertionsÚpassed_assertionsÚmetricsÚverdict)
r   ÚsrvÚappÚclientÚBASE_DIRÚresults_json_pathÚtimeÚstrftimeÚgmtimeÚ	telemetry)Úclss    úBC:\Projects\KieuStory\tests\test_adversarial_m4_api_concurrency.pyÚ
setUpClassz*TestAdversarialM4ApiConcurrency.setUpClassG   s]   € å¥¤Ñ(Ô(ˆŒ
Ý (¨7Ñ 2Ð5bÑ bˆÔà<ÝœÐ';½T¼[¹]¼]ÑKÔKØ !Ø!"ØØ ð)
ð )
ˆŒˆˆó    c                 ór  — | j         d         | j         d         k    rdnd| j         d<   	 t          | j        dd¬¦  «        5 }t          j        | j         |d	d
¬¦  «         d d d ¦  «         n# 1 swxY w Y   t          d| j        › �¦  «         d S # t          $ r}t          d|› �¦  «         Y d }~d S d }~ww xY w)Nr   r   ÚPASSÚFAILr   Úwr
   r   é   F)ÚindentÚensure_asciiu%   
[âœ“] Saved empirical telemetry to: z'[!] Warning: failed to save telemetry: )r#   Úopenr   ÚjsonÚdumpÚprintÚ	Exception)r$   ÚfÚes      r%   ÚtearDownClassz-TestAdversarialM4ApiConcurrency.tearDownClassT   s:  € à-0¬]Ð;NÔ-OÐSVÔS`ÐasÔStÒ-tÐ-t 6 6ð  {AˆŒ�iÑ ð	AÝ�cÔ+¨S¸7ÐCÑCÔCð JÀqÝ”	˜#œ-¨°1À5ÐIÑIÔIÐIðJð Jð Jñ Jô Jð Jð Jð Jð Jð Jð Jøøøð Jð Jð Jð JåÐR¸3Ô;PÐRÐRÑSÔSÐSÐSÐSøÝð 	Að 	Að 	AÝÐ?¸AÐ?Ð?Ñ@Ô@Ð@Ð@Ð@Ð@Ð@Ð@Ð@øøøøð	Aøøøs;   ªB ÁA+ÁB Á+A/Á/B Á2A/Á3B Â
B6ÂB1Â1B6Ú	conditionÚdescriptionc                 ó�   — | j         dxx         dz  cc<   |r| j         dxx         dz  cc<   |                      |d|› �¦  «         d S )Nr   é   r   zAssertion failed: )r#   Ú
assertTrue)Úselfr7   r8   s      r%   Úrecord_assertionz0TestAdversarialM4ApiConcurrency.record_assertion^   sk   € ØŒÐ)Ð*Ð*Ô*¨aÑ/Ð*Ð*Ñ*Øð 	5ØŒNÐ.Ð/Ð/Ô/°1Ñ4Ð/Ð/Ñ/Ø�Š˜	Ð#E¸Ð#EÐ#EÑFÔFÐFÐFÐFr'   c                 óŽ  — | j                              d¦  «        }|                      |j        dk    d¦  «         |                     ¦   «         }|                      |                     d¦  «        dk    d¦  «         |                      |                     d¦  «        dk    d	¦  «         |                     d
g ¦  «        }|                      t          |¦  «        dk    d¦  «         ddddddddddddddddœ}i }d}|D ]ˆ}|d         }|d         }	t          |	¦  «        }
|
||<   ||
z  }|                     |¦  «        }|                      |
|k    d|› d|› d|
› d�¦  «         |                      |d         |k    d|› d�¦  «         Œ‰|                      |dk    d|› d�¦  «         |t          |¦  «        |dœ| j        d          d!<   d"S )#zNVerify /api/matrix/ep01 returns exactly 188 shots structured across 15 scenes.ú/api/matrix/ep01éÈ   z GET /api/matrix/ep01 returns 200Ú
episode_idÚep01zepisode_id is ep01Útotal_shotsé¼   ztotal_shots is exactly 188Úscenesé   z#Structured across exactly 15 scenesé   é   é   é   é   é   é
   )Úep01_scene01Úep01_scene02Úep01_scene03Úep01_scene04Úep01_scene05Úep01_scene06Úep01_scene07Úep01_scene08Úep01_scene09Úep01_scene10Úep01_scene11Úep01_scene12Úep01_scene13Úep01_scene14Úep01_scene15r   Úscene_idÚshotszScene z shot count matches expected z
 (actual: ú)Úshots_totalz- shots_total attribute matches expected countz2Sum of shots across all 15 scenes == 188 (actual: )rC   Úscene_countÚ	breakdownr   Úmatrix_ep01N)r   Úgetr=   Ústatus_coder0   Úlenr#   )r<   ÚrespÚdatarE   Úexpected_breakdownÚactual_breakdownÚtotal_accumulated_shotsÚscÚsc_idÚsc_shotsÚ
shot_countÚexpected_cnts               r%   Ú*test_01_matrix_ep01_total_shots_and_sceneszJTestAdversarialM4ApiConcurrency.test_01_matrix_ep01_total_shots_and_scenesh   s?  € àŒ{�ŠÐ1Ñ2Ô2ˆØ×Ò˜dÔ.°#Ò5Ð7YÑZÔZÐZà�yŠy‰{Œ{ˆØ×Ò˜dŸhšh |Ñ4Ô4¸Ò>Ð@TÑUÔUÐUØ×Ò˜dŸhšh }Ñ5Ô5¸Ò<Ð>ZÑ[Ô[Ð[à—’˜( BÑ'Ô'ˆØ×Ò�c &™kœk¨RÒ/Ð1VÑWÔWÐWð ØØØØØØØØØØØØØØð
ð 
Ðð$ ÐØ"#Ðàð 	ð 	ˆBØ�z”NˆEØ˜'”{ˆHÝ˜X™œˆJØ&0Ð˜UÑ#Ø# zÑ1Ð#à-×1Ò1°%Ñ8Ô8ˆLØ×!Ò!Ø˜lÒ*Øb˜ÐbÐb¸\ÐbÐbÐU_ÐbÐbÐbñô ð ð ×!Ò!Ø�=Ô! \Ò1ØM˜ÐMÐMÐMñô ð ð ð
 	×ÒØ# sÒ*Ø[ÐAXÐ[Ð[Ð[ñ	
ô 	
ð 	
ð 3Ý˜v™;œ;Ø)ð4
ð 4
ˆŒ�yÔ! -Ñ0Ð0Ð0r'   c           	      ó>  — | j                              d¦  «        }|                     ¦   «         }|                     dg ¦  «        }d}d}d}d}|D ]Ý}|d         D ]Ò}	|	d         }
|                      t	          |
                     d¦  «        ¦  «        d|
› d	�¦  «         |	                     d
¦  «        dk    rd}t          |	                     dd¦  «        ¦  «                             d¦  «        sd}|	                     d¦  «        r
d|	d         vrd}|	                     d¦  «        r|dz  }ŒÓŒÞ|                      |d¦  «         |                      |d¦  «         |                      |d¦  «         |                      |dk    d¦  «         |||dœ| j        d         d<   dS )z?Stress-test detailed attributes of all 188 shots in the matrix.r?   rE   r   Tr^   Úshot_idÚ
ep01_scenezShot z has valid ID prefixÚduration_secrM   FÚtarget_video_fileÚ z_10s_v1.mp4Úaudio_guardu   KHÃ”NG sinh nháº¡c ná»�nÚhas_lip_sync_guardr:   z)All 188 shots have 10s duration specifiedz(All 188 shots target *_10s_v1.mp4 namingz(All 188 shots have mandatory Audio Guardz.Identified shots with Closed Lips Guard active)Úshots_with_closed_lipsÚall_shots_have_audio_guardÚall_shots_have_10s_durationr   Úmatrix_guardsN)	r   rd   r0   r=   ÚboolÚ
startswithÚstrÚendswithr#   )r<   rg   rh   rE   rz   r{   r|   Úall_shots_have_v1_targetrl   Úsrs   s              r%   Ú)test_02_matrix_shot_attributes_and_guardszITestAdversarialM4ApiConcurrency.test_02_matrix_shot_attributes_and_guards¦   sì  € àŒ{�ŠÐ1Ñ2Ô2ˆØ�yŠy‰{Œ{ˆØ—’˜( BÑ'Ô'ˆà!"ÐØ%)Ð"Ø&*Ð#Ø#'Ð àð 	0ð 	0ˆBØ˜”[ð 0ð 0�Ø˜Iœ,�à×%Ò%Ý˜×+Ò+¨LÑ9Ô9Ñ:Ô:Ø9˜GÐ9Ð9Ð9ñô ð ð —5’5˜Ñ(Ô(¨BÒ.Ð.Ø27Ð/Ý˜1Ÿ5š5Ð!4°bÑ9Ô9Ñ:Ô:×CÒCÀMÑRÔRð 5Ø/4Ð,Ø—u’u˜]Ñ+Ô+ð 7Ð/IÐQRÐS`ÔQaÐ/aÐ/aØ16Ð.Ø—5’5Ð-Ñ.Ô.ð 0Ø*¨aÑ/Ð*øð0ð  	×ÒÐ9Ð;fÑgÔgÐgØ×ÒÐ6Ð8bÑcÔcÐcØ×ÒÐ8Ð:dÑeÔeÐeØ×ÒÐ4°qÒ8Ð:jÑkÔkÐkð '=Ø*DØ+Fð6
ð 6
ˆŒ�yÔ! /Ñ2Ð2Ð2r'   c                 ó¸  — | j                              d¦  «        }|                     ¦   «         }t          d„ |d         D ¦   «         d¦  «        }|                      |dud¦  «         |                      t          |d         ¦  «        dk    d¦  «         d	„ |d         D ¦   «         }d
„ t          dd¦  «        D ¦   «         }|                      ||k    d¦  «         dS )uC   Empirically inspect reconstructed Ä�áº¡m TiÃªn Scene 05 (27 shots).r?   c              3   ó2   K  — | ]}|d          dk    ¯|V — ŒdS )r]   rR   N© ©Ú.0rƒ   s     r%   ú	<genexpr>zXTestAdversarialM4ApiConcurrency.test_03_dam_tien_scene05_subsequences.<locals>.<genexpr>Ñ   s0   è è € ÐRÐR˜1°!°J´-À>Ò2QÐ2Q�QÐ2QÐ2QÐ2QÐ2QÐRÐRr'   rE   NzScene 05 exists in matrixr^   rI   zScene 05 has exactly 27 shotsc                 ó   — g | ]
}|d          ‘ŒS )rs   r‡   rˆ   s     r%   ú
<listcomp>zYTestAdversarialM4ApiConcurrency.test_03_dam_tien_scene05_subsequences.<locals>.<listcomp>Ö   s   € Ð8Ð8Ð8 Q�A�i”LÐ8Ð8Ð8r'   c                 ó   — g | ]}d |d›�‘Œ	S )Úep01_scene05_shotÚ02dr‡   )r‰   Úis     r%   rŒ   zYTestAdversarialM4ApiConcurrency.test_03_dam_tien_scene05_subsequences.<locals>.<listcomp>×   s$   € ÐJÐJÐJ¸Ð3¨AÐ3Ð3Ð3ÐJÐJÐJr'   r:   é   z1Scene 05 shots are numbered 01 to 27 sequentially)r   rd   r0   Únextr=   rf   Úrange)r<   rg   rh   Úsc05Úshot_idsÚexpected_idss         r%   Ú%test_03_dam_tien_scene05_subsequenceszETestAdversarialM4ApiConcurrency.test_03_dam_tien_scene05_subsequencesÍ   sà   € àŒ{�ŠÐ1Ñ2Ô2ˆØ�yŠy‰{Œ{ˆÝÐRÐR  X¤ÐRÑRÔRÐTXÑYÔYˆØ×Ò˜d¨$Ð.Ð0KÑLÔLÐLØ×Ò�c $ w¤-Ñ0Ô0°BÒ6Ð8WÑXÔXÐXð 9Ð8¨$¨w¬-Ð8Ñ8Ô8ˆØJÐJ½UÀ1Àb¹\¼\ÐJÑJÔJˆØ×Ò˜h¨,Ò6Ð8kÑlÔlÐlÐlÐlr'   c                 óú  — t           j        d         }	 dt           j        d<   | j                             dddi¬¦  «        }|                      |j        dk    d¦  «         |                      |                     ¦   «                              d¦  «        d	k    d
¦  «         | j                             dddi¬¦  «        }|                      |j        dk    d¦  «         | j                             dddi¬¦  «        }|                      |j        dk    d¦  «         |                      |                     ¦   «                              d¦  «        d	k    d
¦  «         |t           j        d<   dS # |t           j        d<   w xY w)zVVerify both POST /api/generate and /api/concat return 409 when status is 'generating'.ÚstatusÚ
generatingú/api/generateÚpromptúAdversarial test prompt©r0   é™  z.POST /api/generate returns 409 when generatingÚbusyúStatus payload is 'busy'rs   Úep01_scene01_shot01z;POST /api/generate with shot_id returns 409 when generatingú/api/concatr]   rN   z,POST /api/concat returns 409 when generatingN©r   Ú	gen_stater   Úpostr=   re   r0   rd   )r<   Úorig_statusÚr_genÚ
r_gen_shotÚr_concats        r%   Ú0test_04_concurrency_generating_state_returns_409zPTestAdversarialM4ApiConcurrency.test_04_concurrency_generating_state_returns_409Þ   so  € å”m HÔ-ˆð	2Ø&2�CŒM˜(Ñ#ð ”K×$Ò$ _¸HÐF_Ð;`Ð$ÑaÔaˆEØ×!Ò! %Ô"3°sÒ":Ð<lÑmÔmÐmØ×!Ò! %§*¢*¡,¤,×"2Ò"2°8Ñ"<Ô"<ÀÒ"FÐHbÑcÔcÐcð œ×)Ò)¨/ÀÐLaÐ@bÐ)ÑcÔcˆJØ×!Ò! *Ô"8¸CÒ"?ÐA~ÑÔÐð ”{×'Ò'¨¸ZÈÐ<XÐ'ÑYÔYˆHØ×!Ò! (Ô"6¸#Ò"=Ð?mÑnÔnÐnØ×!Ò! (§-¢-¡/¤/×"5Ò"5°hÑ"?Ô"?À6Ò"IÐKeÑfÔfÐfà&1�CŒM˜(Ñ#Ð#Ð#ø k�CŒM˜(Ñ#Ð1Ð1Ð1Ð1s   ”EE) Å)E:c                 ó€  — t           j        d         }	 dt           j        d<   | j                             dddi¬¦  «        }|                      |j        dk    d¦  «         |                      |                     ¦   «                              d¦  «        d	k    d
¦  «         | j                             dddi¬¦  «        }|                      |j        dk    d¦  «         |                      |                     ¦   «                              d¦  «        d	k    d
¦  «         |t           j        d<   dS # |t           j        d<   w xY w)zYVerify both POST /api/generate and /api/concat return 409 when status is 'concatenating'.r™   Úconcatenatingr›   rœ   r�   rž   rŸ   z1POST /api/generate returns 409 when concatenatingr    r¡   r£   r]   rO   z/POST /api/concat returns 409 when concatenatingNr¤   )r<   r§   r¨   rª   s       r%   Ú3test_05_concurrency_concatenating_state_returns_409zSTestAdversarialM4ApiConcurrency.test_05_concurrency_concatenating_state_returns_409ô   s.  € å”m HÔ-ˆð	2Ø&5�CŒM˜(Ñ#ð ”K×$Ò$ _¸HÐF_Ð;`Ð$ÑaÔaˆEØ×!Ò! %Ô"3°sÒ":Ð<oÑpÔpÐpØ×!Ò! %§*¢*¡,¤,×"2Ò"2°8Ñ"<Ô"<ÀÒ"FÐHbÑcÔcÐcð ”{×'Ò'¨¸ZÈÐ<XÐ'ÑYÔYˆHØ×!Ò! (Ô"6¸#Ò"=Ð?pÑqÔqÐqØ×!Ò! (§-¢-¡/¤/×"5Ò"5°hÑ"?Ô"?À6Ò"IÐKeÑfÔfÐfà&1�CŒM˜(Ñ#Ð#Ð#ø k�CŒM˜(Ñ#Ð1Ð1Ð1Ð1s   ”DD, Ä,D=c                 óº  — t           j        d         }	 dt           j        d<   d}d}g }t          |¦  «        D ]�}|dz  dk    rdnd}|dz  dk    rdd	|› �ind
di}| j                             ||¬¦  «        }|j        dk    r1|                     ¦   «                              d¦  «        dk    r|dz  }Œ{|                     ||j        |j	        f¦  «         Œž|  
                    ||k    d|› d|› d�¦  «         |  
                    t          |¦  «        dk    d¦  «         ||||z  dz  dœ| j        d         d<   |t           j        d<   dS # |t           j        d<   w xY w)zYStress-test: Send 50 rapid sequential requests during busy state to verify 100% 409 rate.r™   rš   é2   r   r,   r›   r£   rœ   Úflood_r]   rN   rž   rŸ   r    r:   z100% of z/ flood requests returned 409 Conflict (actual: r_   z+Zero unexpected response codes during floodg      Y@)Útotal_requestsÚreturned_409Úrejection_rate_pctr   Úconcurrency_floodN)r   r¥   r“   r   r¦   re   r0   rd   ÚappendÚtextr=   rf   r#   )	r<   r§   Úflood_countÚresponses_409Úunexpected_responsesr�   ÚendpointÚpayloadÚrs	            r%   Ú&test_06_rapid_concurrency_stress_floodzFTestAdversarialM4ApiConcurrency.test_06_rapid_concurrency_stress_flood  s¯  € å”m HÔ-ˆð	2Ø&2�CŒM˜(Ñ#àˆKØˆMØ#%Ð å˜;Ñ'Ô'ð Sð S�Ø./°!©e°qªj¨j˜?˜?¸m�Ø67¸!±e¸q²j°j˜8 \¨a \ \Ð2Ð2ÀzÐSaÐFb�Ø”K×$Ò$ X°GÐ$Ñ<Ô<�Ø”= CÒ'Ð'¨A¯FªF©H¬H¯LªL¸Ñ,BÔ,BÀfÒ,LÐ,LØ! QÑ&�M�Mà(×/Ò/°¸1¼=È!Ì&Ð0QÑRÔRÐRÐRà×!Ò!Ø Ò,Øg˜;ÐgÐgÐWdÐgÐgÐgñô ð ð ×!Ò!¥#Ð&:Ñ";Ô";¸qÒ"@ÐBoÑpÔpÐpð #.Ø -Ø'4°{Ñ'BÀeÑ&Kð>ð >ˆDŒN˜9Ô%Ð&9Ñ:ð '2�CŒM˜(Ñ#Ð#Ð#ø k�CŒM˜(Ñ#Ð1Ð1Ð1Ð1s   ”D$E	 Å	Ec                 óô  — t           j        d         }	 dt           j        d<   | j                             di ¬¦  «        }|                      |j        dk    d¦  «         | j                             dddd	œ¬¦  «        }|                      |j        dk    d
¦  «         | j                             dddi¬¦  «        }|                      |j        dk    d¦  «         | j                             ddddœ¬¦  «        }|                      |j        dk    d¦  «         | j                             ddddœ¬¦  «        }|                      |j        dk    d¦  «         |t           j        d<   dS # |t           j        d<   w xY w)zNVerify invalid/empty generation requests return HTTP 422 Unprocessable Entity.r™   Úidler›   rž   é¦  zEmpty JSON returns 422rN   ÚTest)r]   Útitlez"Missing prompt/shot_id returns 422rœ   rw   zEmpty string prompt returns 422N)rœ   rs   z'None for prompt and shot_id returns 422zValid promptÚ
not_a_bool)rœ   Úforcez!Invalid boolean field returns 422©r   r¥   r   r¦   r=   re   ©r<   r§   Úr1Úr2Úr3Úr4Úr5s          r%   Ú6test_07_generate_validation_empty_and_invalid_payloadszVTestAdversarialM4ApiConcurrency.test_07_generate_validation_empty_and_invalid_payloads+  sŒ  € å”m HÔ-ˆð	2Ø&,�CŒM˜(Ñ#ð ”×!Ò! /¸Ð!Ñ;Ô;ˆBØ×!Ò! "¤.°CÒ"7Ð9QÑRÔRÐRð ”×!Ò! /À^Ð^dÐ8eÐ8eÐ!ÑfÔfˆBØ×!Ò! "¤.°CÒ"7Ð9]Ñ^Ô^Ð^ð ”×!Ò! /¸À2¸Ð!ÑGÔGˆBØ×!Ò! "¤.°CÒ"7Ð9ZÑ[Ô[Ð[ð ”×!Ò! /À4ÐTXÐ8YÐ8YÐ!ÑZÔZˆBØ×!Ò! "¤.°CÒ"7Ð9bÑcÔcÐcð ”×!Ò! /À>Ð\hÐ8iÐ8iÐ!ÑjÔjˆBØ×!Ò! "¤.°CÒ"7Ð9\Ñ]Ô]Ð]ð '2�CŒM˜(Ñ#Ð#Ð#ø k�CŒM˜(Ñ#Ð1Ð1Ð1Ð1s   ”EE& Å&E7c                 óL  — t           j        d         }	 dt           j        d<   t          d¦  «        5 }| j                             ddddœ¬¦  «        }|                      |j        d	k    d
¦  «         |                      |                     ¦   «                              d¦  «        dk    d¦  «         | j                             dddi¬¦  «        }|                      |j        d	k    d¦  «         |                      |                     ¦   «                              d¦  «        dk    d¦  «         | j                             ddddœ¬¦  «        }|                      |j        d	k    d¦  «         ddd¦  «         n# 1 swxY w Y   |t           j        d<   dS # |t           j        d<   w xY w)zLVerify valid generation requests are accepted (HTTP 200, status 'accepted').r™   rÀ   zserver.run_unified_generationr›   zValid test promptrÂ   )rœ   rÃ   rž   r@   zValid prompt accepted with 200ÚacceptedúResponse status is 'accepted'rs   r¢   zValid shot_id accepted with 200Úep01_scene01_shot02zCustom prompt)rs   rœ   z(Valid dual prompt/shot accepted with 200N©	r   r¥   r   r   r¦   r=   re   r0   rd   )r<   r§   Úmock_genrÈ   rÉ   rÊ   s         r%   Ú)test_08_generate_valid_payload_acceptancezITestAdversarialM4ApiConcurrency.test_08_generate_valid_payload_acceptanceH  sâ  € å”m HÔ-ˆð	2Ø&,�CŒM˜(Ñ#åÐ6Ñ7Ô7ð i¸8à”[×%Ò% oÐGZÐekÐ<lÐ<lÐ%ÑmÔm�Ø×%Ò% b¤n¸Ò&;Ð=]Ñ^Ô^Ð^Ø×%Ò% b§g¢g¡i¤i§m¢m°HÑ&=Ô&=ÀÒ&KÐMlÑmÔmÐmð ”[×%Ò% o¸YÐH]Ð<^Ð%Ñ_Ô_�Ø×%Ò% b¤n¸Ò&;Ð=^Ñ_Ô_Ð_Ø×%Ò% b§g¢g¡i¤i§m¢m°HÑ&=Ô&=ÀÒ&KÐMlÑmÔmÐmð ”[×%Ò% oÐH]ÐixÐ<yÐ<yÐ%ÑzÔz�Ø×%Ò% b¤n¸Ò&;Ð=gÑhÔhÐhðið ið iñ iô ið ið ið ið ið ið iøøøð ið ið ið ið  '2�CŒM˜(Ñ#Ð#Ð#ø k�CŒM˜(Ñ#Ð1Ð1Ð1Ð1s/   ”F ²D8E6Å*F Å6E:Å:F Å=E:Å>F ÆF#c                 óð  — t           j        d         }	 dt           j        d<   | j                             di ¬¦  «        }|                      |j        dk    d¦  «         | j                             dddi¬¦  «        }|                      |j        dk    d	¦  «         | j                             dd
di¬¦  «        }|                      |j        dk    d¦  «         | j                             dd
di¬¦  «        }|                      |j        dk    d¦  «         | j                             ddddœ¬¦  «        }|                      |j        dk    d¦  «         |t           j        d<   dS # |t           j        d<   w xY w)z/Verify invalid concat requests return HTTP 422.r™   rÀ   r£   rž   rÁ   zEmpty JSON concat returns 422Úoutput_pathzout.mp4zMissing scene_id returns 422r]   rw   z!Empty string scene_id returns 422z    z$Whitespace-only scene_id returns 422rN   Úinvalid_num)r]   Úcrossfade_durz!Invalid crossfade_dur returns 422NrÆ   rÇ   s          r%   Ú*test_09_concat_validation_invalid_payloadszJTestAdversarialM4ApiConcurrency.test_09_concat_validation_invalid_payloadsd  s…  € å”m HÔ-ˆð	2Ø&,�CŒM˜(Ñ#ð ”×!Ò! -°bÐ!Ñ9Ô9ˆBØ×!Ò! "¤.°CÒ"7Ð9XÑYÔYÐYð ”×!Ò! -°}ÀiÐ6PÐ!ÑQÔQˆBØ×!Ò! "¤.°CÒ"7Ð9WÑXÔXÐXð ”×!Ò! -°zÀ2Ð6FÐ!ÑGÔGˆBØ×!Ò! "¤.°CÒ"7Ð9\Ñ]Ô]Ð]ð ”×!Ò! -°zÀ6Ð6JÐ!ÑKÔKˆBØ×!Ò! "¤.°CÒ"7Ð9_Ñ`Ô`Ð`ð ”×!Ò! -À>ÐdqÐ6rÐ6rÐ!ÑsÔsˆBØ×!Ò! "¤.°CÒ"7Ð9\Ñ]Ô]Ð]ð '2�CŒM˜(Ñ#Ð#Ð#ø k�CŒM˜(Ñ#Ð1Ð1Ð1Ð1s   ”D?E$ Å$E5c                 óV  — t           j        d         }	 dt           j        d<   t          d¦  «        5 }| j                             dddddœ¬	¦  «        }|                      |j        d
k    d¦  «         |                      |                     ¦   «                              d¦  «        dk    d¦  «         |                      d|                     ¦   «                              dd¦  «        v d¦  «         ddd¦  «         n# 1 swxY w Y   |t           j        d<   dS # |t           j        d<   w xY w)z1Verify valid ConcatRequest is accepted when idle.r™   rÀ   zserver.run_scene_concatr£   rN   ztest_output.mp4g      ø?)r]   rÖ   rØ   rž   r@   zValid ConcatRequest returns 200rÏ   rÐ   Úmessagerw   zMessage confirms scene_idNrÒ   )r<   r§   Úmock_concatr½   s       r%   Ú'test_10_concat_valid_payload_acceptancezGTestAdversarialM4ApiConcurrency.test_10_concat_valid_payload_acceptance�  sn  € å”m HÔ-ˆð	2Ø&,�CŒM˜(Ñ#åÐ0Ñ1Ô1ð r°[Ø”K×$Ò$ ]Ø .Ø#4Ø%(ð:ð :Ð$ñ ô �ð
 ×%Ò% a¤m°sÒ&:Ð<]Ñ^Ô^Ð^Ø×%Ò% a§f¢f¡h¤h§l¢l°8Ñ&<Ô&<À
Ò&JÐLkÑlÔlÐlØ×%Ò% n¸¿º¹¼¿ºÀYÐPRÑ8SÔ8SÐ&SÐUpÑqÔqÐqðrð rð rñ rô rð rð rð rð rð rð røøøð rð rð rð rð '2�CŒM˜(Ñ#Ð#Ð#ø k�CŒM˜(Ñ#Ð1Ð1Ð1Ð1s/   ”D ²B=C;Ã/D Ã;C?Ã?D ÄC?ÄD ÄD(c                 ó   — g d¢}|D ]V}	 t          j        |¦  «         d}n# t          $ r}d}Y d}~nd}~ww xY w|                      | d|dd…         › d�¦  «         ŒWt           j        d         d	         }|                      d
|v d¦  «         dS )z;Verify log_msg() does not crash on complex Vietnamese text.)uD   Tháº­p NgÅ© NiÃªn: Ä�oáº¡n TrÆ°á»�ng KÃ½ - ThÃºy Kiá»�u & ThÃºy VÃ¢nuR   BÆ°á»›c láº§n theo ngá»�n tiá»ƒu khÃª, láº§n xem phong cáº£nh cÃ³ bá»� thanh thanhuQ   Ráº±ng sao trong tiáº¿t thanh minh, mÃ  Ä‘Ã¢y hÆ°Æ¡ng khÃ³i váº¯ng tanh tháº¿ mÃ uO   TrÄƒm nÄƒm trong cÃµi ngÆ°á»�i ta, chá»¯ tÃ i chá»¯ má»‡nh khÃ©o lÃ  ghÃ©t nhauu_   Tiáº¿t Thanh Minh táº£o má»™, náº¥m má»“ vÃ´ danh bÃªn Ä‘Æ°á»�ng, VÆ°Æ¡ng Quan thuáº­t chuyá»‡nuf  Ã€Ã�Ã‚ÃƒÃˆÃ‰ÃŠÃŒÃ�Ã’Ã“Ã”Ã•Ã™ÃšÃ�Ã Ã¡Ã¢Ã£Ã¨Ã©ÃªÃ¬Ã­Ã²Ã³Ã´ÃµÃ¹ÃºÃ½Ä‚ÄƒÄ�Ä‘Ä¨Ä©Å¨Å©Æ Æ¡Æ¯Æ°áº áº¡áº¢áº£áº¤áº¥áº¦áº§áº¨áº©áºªáº«áº¬áº­áº®áº¯áº°áº±áº²áº³áº´áºµáº¶áº·áº¸áº¹áººáº»áº¼áº½áº¾áº¿á»€á»�á»‚á»ƒá»„á»…á»†á»‡á»ˆá»‰á»Šá»‹á»Œá»�á»Žá»�á»�á»‘á»’á»“á»”á»•á»–á»—á»˜á»™á»šá»›á»œá»�á»žá»Ÿá» á»¡á»¢á»£á»¤á»¥á»¦á»§á»¨á»©á»ªá»«á»¬á»­á»®á»¯á»°á»±á»²á»³á»´á»µá»¶á»·á»¸á»¹FTNz+log_msg did not crash on Vietnamese text: 'é   z...'Úlogséÿÿÿÿu   Ã€Ã�Ã‚Ãƒz?Vietnamese characters correctly stored in gen_state logs buffer)r   Úlog_msgr3   r=   r¥   )r<   Útest_phrasesÚphraseÚcrashedr5   Ú
latest_logs         r%   Ú.test_11_windows_encoding_vietnamese_diacriticszNTestAdversarialM4ApiConcurrency.test_11_windows_encoding_vietnamese_diacritics˜  sÙ   € ð
ð 
ð 
ˆð #ð 	pð 	pˆFðÝ”˜FÑ#Ô#Ð#Ø��øÝð ð ð Ø������øøøøðøøøà×!Ò! g +Ð/nÐ]cÐdgÐegÐdgÔ]hÐ/nÐ/nÐ/nÑoÔoÐoÐoõ ”] 6Ô*¨2Ô.ˆ
Ø×Ò˜j¨JÐ6Ð8yÑzÔzÐzÐzÐzs   Š!¡
7«2²7c                 óö   ‡— d}dddœŠˆfd„}t          d|¬¦  «        5  	 t          j        |¦  «         d}n# t          $ r d}Y nw xY w|                      | d	¦  «         d
d
d
¦  «         d
S # 1 swxY w Y   d
S )zZAdversarially simulate UnicodeEncodeError (Windows cp1252) to verify ASCII fallback logic.uB   Kiá»�u gáº·p Ä�áº¡m TiÃªn: khÃ³c thÆ°Æ¡ng sá»‘ pháº­n báº¡c má»‡nhr   )ÚnormalÚfallbackc                 ó€   •— ‰dxx         dz  cc<   t          d„ | D ¦   «         ¦  «        rt          d| ddd¦  «        ‚d S )Nré   r:   c              3   ó<   K  — | ]}t          |¦  «        d k    V — ŒdS )é   N)Úord)r‰   Úcs     r%   rŠ   z‚TestAdversarialM4ApiConcurrency.test_12_windows_cp1252_encoding_fallback_simulation.<locals>.mock_failing_print.<locals>.<genexpr>¹  s,   è è € Ð.Ð. A•3�q‘6”6˜C’<Ð.Ð.Ð.Ð.Ð.Ð.r'   Úcp1252r   zcharacter maps to <undefined>)ÚanyÚUnicodeEncodeError)r·   Ú
call_counts    €r%   Úmock_failing_printzoTestAdversarialM4ApiConcurrency.test_12_windows_cp1252_encoding_fallback_simulation.<locals>.mock_failing_print·  sb   ø€ Ø�xÐ Ð Ô  AÑ%Ð Ð Ñ ÝÐ.Ð.¨Ð.Ñ.Ô.Ñ.Ô.ð `Ý(¨°4¸¸AÐ?^Ñ_Ô_Ð_ð`ð `r'   zbuiltins.print)Úside_effectFTz4log_msg survived simulated cp1252 UnicodeEncodeErrorN)r   r   râ   r3   r=   )r<   Útest_msgrô   rå   ró   s       @r%   Ú3test_12_windows_cp1252_encoding_fallback_simulationzSTestAdversarialM4ApiConcurrency.test_12_windows_cp1252_encoding_fallback_simulation°  s  ø€ àWˆð !"¨qÐ1Ð1ˆ
ð	`ð 	`ð 	`ð 	`ð 	`õ
 Ð#Ð1CÐDÑDÔDð 	gð 	gðÝ”˜HÑ%Ô%Ð%Ø��øÝð ð ð Ø���ðøøøð ×!Ò! g +Ð/eÑfÔfÐfð	gð 	gð 	gñ 	gô 	gð 	gð 	gð 	gð 	gð 	gð 	gð 	gøøøð 	gð 	gð 	gð 	gð 	gð 	gs1   ŸA.¡8·A.¸AÁA.ÁAÁA.Á.A2Á5A2c                 óF  — t          d¦  «        D ]}t          j        d|d›�¦  «         Œ|                      t	          t          j        d         ¦  «        dk    d¦  «         |                      t          j        d         d                              d¦  «        d	¦  «         d
S )z3Verify gen_state['logs'] strictly caps at 30 items.é-   zAdversarial rolling log entry #r�   rà   rß   z/gen_state['logs'] strictly capped at 30 entriesrá   z	entry #44z1Most recent log entry preserved at tail of bufferN)r“   r   râ   r=   rf   r¥   r�   )r<   r�   s     r%   Ú'test_13_log_buffer_rolling_window_limitzGTestAdversarialM4ApiConcurrency.test_13_log_buffer_rolling_window_limitÅ  s§   € õ �r‘”ð 	Cð 	CˆAÝŒKÐA¸!ÐAÐAÐAÑBÔBÐBÐBà×Ò�c¥#¤-°Ô"7Ñ8Ô8¸BÒ>Ð@qÑrÔrÐrØ×ÒÝŒM˜&Ô! "Ô%×.Ò.¨{Ñ;Ô;Ø?ñ	
ô 	
ð 	
ð 	
ð 	
r'   c                 ó’  — | j                              d¦  «        }|                      |j        dk    d¦  «         |                     ¦   «                              di ¦  «        }|                      t          |¦  «        dk    dt          |¦  «        › d�¦  «         |                      t          d„ |D ¦   «         ¦  «        d	¦  «         | j                              d
¦  «        }|                      |j        dk    d¦  «         |                     ¦   «                              di ¦  «        }|                      t          |¦  «        dk    dt          |¦  «        › d�¦  «         | j                              d¦  «        }|                      t          |                     ¦   «                              di ¦  «        ¦  «        dk    d¦  «         dS )z&Verify query parameters on /api/shots.z/api/shots?episode=ep01r@   z'GET /api/shots?episode=ep01 returns 200r^   rD   z1Filtered episode=ep01 returns 188 shots (actual: r_   c              3   ó@   K  — | ]}|                      d ¦  «        V — ŒdS )rB   N©r   ©r‰   Úks     r%   rŠ   zPTestAdversarialM4ApiConcurrency.test_14_shots_query_filtering.<locals>.<genexpr>Ü  s.   è è € Ð!IÐ!I¸1 !§,¢,¨vÑ"6Ô"6Ð!IÐ!IÐ!IÐ!IÐ!IÐ!Ir'   z"All returned shots start with ep01z/api/shots?scene=ep01_scene05z-GET /api/shots?scene=ep01_scene05 returns 200rI   z6Filtered scene=ep01_scene05 returns 27 shots (actual: z#/api/shots?episode=nonexistent_ep99r   z$Non-matching episode returns 0 shotsN)r   rd   r=   re   r0   rf   Úall)r<   Úr_epÚshots_epÚr_scÚshots_scÚr_nones         r%   Útest_14_shots_query_filteringz=TestAdversarialM4ApiConcurrency.test_14_shots_query_filteringÕ  s–  € ð Œ{�ŠÐ8Ñ9Ô9ˆØ×Ò˜dÔ.°#Ò5Ð7`ÑaÔaÐaØ—9’9‘;”;—?’? 7¨BÑ/Ô/ˆØ×Ò�c (™mœm¨sÒ2Ð4xÕhkÐltÑhuÔhuÐ4xÐ4xÐ4xÑyÔyÐyØ×Ò�cÐ!IÐ!IÀÐ!IÑ!IÔ!IÑIÔIÐKoÑpÔpÐpð Œ{�ŠÐ>Ñ?Ô?ˆØ×Ò˜dÔ.°#Ò5Ð7fÑgÔgÐgØ—9’9‘;”;—?’? 7¨BÑ/Ô/ˆØ×Ò�c (™mœm¨rÒ1Ð3|ÕloÐpxÑlyÔlyÐ3|Ð3|Ð3|Ñ}Ô}Ð}ð ”—’Ð!FÑGÔGˆØ×Ò�c &§+¢+¡-¤-×"3Ò"3°G¸RÑ"@Ô"@ÑAÔAÀQÒFÐHnÑoÔoÐoÐoÐor'   c           	      ón  — g d¢}|D ]J}| j                              d|› �¦  «        }|                      |j        dk    d|› d|j        › d�¦  «         ŒK| j                              d¦  «        }|                      |j        dk    d	¦  «         |                      d
|                     ¦   «         v d¦  «         dS )zIVerify path traversal attacks on /api/episode/{filename} return HTTP 404.)z../../windows/system32/cmd.exez../PROJECT.mdz..%2F..%2FPROJECT.mdz../../../boot.inizTAP_99_NONEXISTENT.mdz/api/episode/i”  z"Path traversal / invalid episode 'z$' safely rejected with 404 (actual: r_   zC/api/episode/TAP_01_XUAN_SAC_THE_NGUYEN_VA_GIONG_BAO_DOAN_TRUONG.mdr@   z Valid episode TAP_01 returns 200Úcontentz&Valid episode returns markdown contentN)r   rd   r=   re   r0   )r<   Úmalicious_pathsÚpr½   Úr_valids        r%   Útest_15_path_traversal_guardsz=TestAdversarialM4ApiConcurrency.test_15_path_traversal_guardsè  sà   € ð
ð 
ð 
ˆð !ð 	ð 	ˆAØ”—’Ð 3°Ð 3Ð 3Ñ4Ô4ˆAØ×!Ò!Ø” Ò$Øl°QÐlÐlÐ\]Ô\iÐlÐlÐlñô ð ð ð ”+—/’/Ð"gÑhÔhˆØ×Ò˜gÔ1°SÒ8Ð:\Ñ]Ô]Ð]Ø×Ò˜i¨7¯<ª<©>¬>Ð9Ð;cÑdÔdÐdÐdÐdr'   c                 óL  — | j                              d¦  «        }|                      |j        dk    d¦  «         |                     ¦   «         }d„ |                     ¦   «         D ¦   «         }|                      t          |¦  «        dk    dt          |¦  «        › d�¦  «         dS )	z>Verify /api/banana-prompts serves the 188 Start Frame prompts.z/api/banana-promptsr@   z#GET /api/banana-prompts returns 200c                 ó<   — g | ]}|                      d ¦  «        ¯|‘ŒS )rt   rý   rþ   s     r%   rŒ   zSTestAdversarialM4ApiConcurrency.test_16_banana_prompts_endpoint.<locals>.<listcomp>  s)   € ÐLÐLÐL˜Q°·²¸lÑ1KÔ1KÐL�qÐLÐLÐLr'   rD   z3Banana prompts catalog has 188 EP01 shots (actual: r_   N)r   rd   r=   re   r0   Úkeysrf   )r<   r½   rh   Úep01_bananas       r%   Útest_16_banana_prompts_endpointz?TestAdversarialM4ApiConcurrency.test_16_banana_prompts_endpointþ  s¶   € àŒK�OŠOÐ1Ñ2Ô2ˆØ×Ò˜aœm¨sÒ2Ð4YÑZÔZÐZØ�vŠv‰xŒxˆØLÐL $§)¢)¡+¤+ÐLÑLÔLˆØ×Ò�c +Ñ.Ô.°#Ò5ð  8AÕmpÐq|Ñm}Ôm}ð  8Að  8Að  8Añ  	Bô  	Bð  	Bð  	Bð  	Br'   N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__Úclassmethodr&   r6   r~   r€   r=   rq   r„   r—   r«   r®   r¾   rÍ   rÔ   rÙ   rÝ   rç   r÷   rú   r  r  r  r‡   r'   r%   r   r   D   s€  € € € € € ØFÐFàð

ð 

ñ „[ð

ð ðAð Añ „[ðAðG¨$ð G¸Sð Gð Gð Gð Gð<
ð <
ð <
ð|%
ð %
ð %
ðNmð mð mð"2ð 2ð 2ð,2ð 2ð 2ð$2ð 2ð 2ðJ2ð 2ð 2ð:2ð 2ð 2ð82ð 2ð 2ð:2ð 2ð 2ð.{ð {ð {ð0gð gð gð*

ð 

ð 

ð pð pð pð&eð eð eð,Bð Bð Bð Bð Br'   r   Ú__main__),r  ÚbuiltinsÚ@py_builtinsÚ_pytest.assertion.rewriteÚ	assertionÚrewriteÚ
@pytest_arÚosÚsysr0   r    ÚunittestÚpathlibr   Úunittest.mockr   r   Útypingr   r   r   ÚplatformÚstdoutÚreconfigureÚstderrr3   Ú__file__ÚresolveÚparentr   ÚWEB_REVIEW_DIRÚPIPELINE_DIRr€   r
  ÚpathÚinsertÚstarlette.testclientr   Úserverr   ÚTestCaser   r  Úmainr‡   r'   r%   ú<module>r3     s  ðð"ð "ðH € € € € € € € € € € € € €	€	€	€	Ø 
€
€
€
Ø €€€Ø €€€Ø €€€Ø Ð Ð Ð Ð Ð Ø *Ð *Ð *Ð *Ð *Ð *Ð *Ð *Ø "Ð "Ð "Ð "Ð "Ð "Ð "Ð "Ð "Ð "ð „<�7ÒÐðØŒ
×Ò¨ÐÑ0Ô0Ð0ØŒ
×Ò¨ÐÑ0Ô0Ð0Ð0øØð ð ð Øˆðøøøð ˆ4�‰>Œ>×!Ò!Ñ#Ô#Ô*Ô1€Ø˜LÑ(€ØÐ2Ñ2€à
ˆ#ˆnÑ
Ô
˜s˜s <Ñ0Ô0°#°#°h±-´-Ð	@ð ð €AØ�”ÐÐØŒ�Š˜˜1ÑÔÐøà +Ð +Ð +Ð +Ð +Ð +Ø Ð Ð Ð ð@Bð @Bð @Bð @Bð @B hÔ&7ñ @Bô @Bð @BðF ˆzÒÐØ€H„M�O„O€O€O€Oð Ðs   Á6A? Á?BÂB