§
    y¯½jc3  ã                   ó²   — d Z ddlZddlZddlZddlmZ ddlmZ ddlm	Z	m
Z
mZmZ ddlmZ  G d„ dej        ¦  «        Zed	k    r ej        ¦   «          dS dS )
zÛ
Comprehensive Unit Tests for Chat Multi-Channel Architecture & Service.
Verifies Channel Policy, Rate Limiting, Two-Tier Moderation, HMAC Item Linking,
Asynchronous Cluster Router Fan-out, and Redis 7 Sharded Pub/Sub.
é    N)ÚList)ÚRedisShardedPubSubBridge)ÚChatChannelTypeÚChatMessageDTOÚItemAffixDTOÚSendChatRequestDTO)ÚChatServicec                   óv   — e Zd Zdd„Zdd„Zdd„Zdd„Zdd„Zdd„Zdd	„Z	dd
„Z
dd„Zdd„Zdd„Zdd„Zdd„ZdS )ÚTestChatServiceÚreturnNc              ƒ   óN   K  — d| _         t          | j         d¬¦  «        | _        d S )Ns!   FREEEXILE_SECRET_SIGNING_KEY_2026é   )Úitem_secret_keyÚcluster_shards)Ú
secret_keyr	   Úchat_service)Úselfs    ú5C:\Projects\FreeExile\tests\unit\test_chat_service.pyÚ
asyncSetUpzTestChatService.asyncSetUp   s+   è è € Ø>ˆŒÝ'¸¼ÐXZÐ[Ñ[Ô[ˆÔÐÐó    c              ƒ   ó   K  — t          dddt          j        d¬¦  «        }| j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         |                      d|j        ¦  «         t          dd	d
t          j        d¬¦  «        }| j                             |¦  «        ƒ d{V —†}|  	                    |j        ¦  «         dS )z"Level < 20 cannot send World chat.ié  ÚNewbieé   zHello world!©Ú	sender_idÚsender_nameÚsender_levelÚchannelÚcontentNu   Cáº§n Ä‘áº¡t cáº¥p 20iê  ÚExperté   zHello world from veteran!)
r   r   ÚWORLDr   Úhandle_send_chatÚassertFalseÚsuccessÚassertInÚerror_messageÚ
assertTrue©r   Úreq_underlevelÚresÚ	req_validÚ	res_valids        r   Ú!test_world_chat_level_requirementz1TestChatService.test_world_chat_level_requirement   sï   è è € å+ØØ ØÝ#Ô)Ø"ð
ñ 
ô 
ˆð Ô%×6Ò6°~ÑFÔFÐFÐFÐFÐFÐFÐFˆØ×Ò˜œÑ%Ô%Ð%Ø�ŠÐ-¨sÔ/@ÑAÔAÐAå&ØØ ØÝ#Ô)Ø/ð
ñ 
ô 
ˆ	ð Ô+×<Ò<¸YÑGÔGÐGÐGÐGÐGÐGÐGˆ	Ø�Š˜	Ô)Ñ*Ô*Ð*Ð*Ð*r   c              ƒ   ó   K  — t          dddt          j        d¬¦  «        }| j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         |                      d|j        ¦  «         t          dd	d
t          j        d¬¦  «        }| j                             |¦  «        ƒ d{V —†}|  	                    |j        ¦  «         dS )z$Level < 10 cannot send Recruit chat.ië  ÚNoviceé   zLooking for party!r   Nu   Cáº§n Ä‘áº¡t cáº¥p 10iì  ÚLeaderé   u%   Tuyá»ƒn thÃ nh viÃªn Ä‘i phá»¥ báº£n!)
r   r   ÚRECRUITr   r#   r$   r%   r&   r'   r(   r)   s        r   Ú#test_recruit_chat_level_requirementz3TestChatService.test_recruit_chat_level_requirement2   sï   è è € å+ØØ ØÝ#Ô+Ø(ð
ñ 
ô 
ˆð Ô%×6Ò6°~ÑFÔFÐFÐFÐFÐFÐFÐFˆØ×Ò˜œÑ%Ô%Ð%Ø�ŠÐ-¨sÔ/@ÑAÔAÐAå&ØØ ØÝ#Ô+Ø;ð
ñ 
ô 
ˆ	ð Ô+×<Ò<¸YÑGÔGÐGÐGÐGÐGÐGÐGˆ	Ø�Š˜	Ô)Ñ*Ô*Ð*Ð*Ð*r   c              ƒ   óš  K  — t          dddt          j        d¬¦  «        }| j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         | j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         |                      d|j	        ¦  «         |  
                    |j        d¦  «         dS )	z4World chat enforces 15-second cooldown token bucket.iÑ  ÚWarrioré   z	Message 1r   Nu   Gá»­i tin quÃ¡ nhanhr   )r   r   r"   r   r#   r(   r%   r$   r&   r'   ÚassertGreaterÚcooldown_remaining_ms)r   ÚreqÚres1Úres2s       r   Ú#test_world_chat_cooldown_rate_limitz3TestChatService.test_world_chat_cooldown_rate_limitI   sá   è è € å ØØ!ØÝ#Ô)Øð
ñ 
ô 
ˆð Ô&×7Ò7¸Ñ<Ô<Ð<Ð<Ð<Ð<Ð<Ð<ˆØ�Š˜œÑ%Ô%Ð%ð Ô&×7Ò7¸Ñ<Ô<Ð<Ð<Ð<Ð<Ð<Ð<ˆØ×Ò˜œÑ&Ô&Ð&Ø�ŠÐ,¨dÔ.@ÑAÔAÐAØ×Ò˜4Ô5°qÑ9Ô9Ð9Ð9Ð9r   c              ƒ   ó(  K  — t          dddt          j        dd¬¦  «        }| j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         |                      d|j        ¦  «         |  	                    d	|j        ¦  «         dS )
z<Profane words with dots/leetspeak are masked with asterisks.i¹  ÚPlayerAé   Úzone_wastelandzthang d.m may c_a_c©r   r   r   r   Úzone_idr   Nz***zd.m)
r   r   ÚZONEr   r#   r(   r%   r&   Úfiltered_contentÚassertNotIn)r   r;   r+   s      r   Ú*test_two_tier_moderation_profanity_maskingz:TestChatService.test_two_tier_moderation_profanity_masking[   s£   è è € å ØØ!ØÝ#Ô(Ø$Ø)ð
ñ 
ô 
ˆð Ô%×6Ò6°sÑ;Ô;Ð;Ð;Ð;Ð;Ð;Ð;ˆØ�Š˜œÑ$Ô$Ð$Ø�Š�e˜SÔ1Ñ2Ô2Ð2Ø×Ò˜ Ô 4Ñ5Ô5Ð5Ð5Ð5r   c              ƒ   ó¸  K  — t          dddt          j        dd¬¦  «        }| j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         |                      d|j        ¦  «         |  	                    |j
        d	¦  «         t          dddt          j        dd
¬¦  «        }| j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         |                      d|j        ¦  «         | j                             d¦  «         | j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         dS )zORMT keywords and phone numbers trigger auto-mute and block subsequent messages.i¡  ÚScammeré2   Ú	zone_cityz4Ban vang gia re chuyen khoan atm qua zalo 0987654321rC   Nu
   khÃ³a chaté<   zXin loi toi khong spam nuau   táº¡m khÃ³a chat)r   r   rE   r   r#   r$   r%   r&   r'   ÚassertGreaterEqualÚ
risk_scoreÚunmute_playerr(   )r   Úreq_rmtr+   Ú	req_cleanÚres_blockedÚres_alloweds         r   Ú6test_two_tier_moderation_rmt_auto_mute_and_persistencezFTestChatService.test_two_tier_moderation_rmt_auto_mute_and_persistencej   s~  è è € å$ØØ!ØÝ#Ô(ØØJð
ñ 
ô 
ˆð Ô%×6Ò6°wÑ?Ô?Ð?Ð?Ð?Ð?Ð?Ð?ˆØ×Ò˜œÑ%Ô%Ð%Ø�Š�l CÔ$5Ñ6Ô6Ð6Ø×Ò ¤°Ñ3Ô3Ð3õ 'ØØ!ØÝ#Ô(ØØ0ð
ñ 
ô 
ˆ	ð !Ô-×>Ò>¸yÑIÔIÐIÐIÐIÐIÐIÐIˆØ×Ò˜Ô,Ñ-Ô-Ð-Ø�ŠÐ(¨+Ô*CÑDÔDÐDð 	Ô×'Ò'¨Ñ-Ô-Ð-Ø Ô-×>Ò>¸yÑIÔIÐIÐIÐIÐIÐIÐIˆØ�Š˜Ô+Ñ,Ô,Ð,Ð,Ð,r   c              ƒ   óö  K  — | j         j                             dddddddt          dd	d
¬¦  «        f¬¦  «        }t	          dddt
          j        ddd¬¦  «        }| j                              |¦  «        ƒ d{V —†}|                      |j	        ¦  «         | j          
                    d|j        ¦  «        }|                      |j        ¦  «         |                      |j        ¦  «         |                      |j        j        d¦  «         |                      |j        j        d¦  «         | j          
                    dd¦  «        }|                      |j        ¦  «         |                      d|j        ¦  «         dS )zBItem snapshots are signed by HMAC-SHA256 and reject spoofed stats.Ú	sword_001zThanh Phong Huyen Kiemé   é   rA   éZ   z
Kiem ThanhÚaff_physz
Sat Thuongg     À’@)Úaffix_idÚ	stat_nameÚ
stat_value)Ú	item_uuidÚitem_name_keyÚrarityÚelementÚqualityÚ
item_levelÚcrafter_nameÚaffixesi‰  ÚLuckyCraftsmanrM   Úzone_sanctuaryzXem kiem ta vua duc!)rW   )r   r   r   r   rD   r   Úlink_item_uuidsNÚfake_signature_abc123Úmismatch)r   Úitem_link_serviceÚcreate_item_snapshotr   r   r   rE   r#   r(   r%   Úquery_item_snapshotÚhmac_signatureÚis_validÚassertIsNotNoneÚitem_snapshotÚassertEqualre   rd   r$   r&   r'   )r   Úsnapr;   r+   Ú	query_resÚ
fake_querys         r   Ú1test_hmac_item_link_snapshot_and_spoof_preventionzATestChatService.test_hmac_item_link_snapshot_and_spoof_prevention‹   sŠ  è è € àÔ Ô2×GÒGØ!Ø2ØØØØØ%Ý!¨:ÀÐZ`ÐaÑaÔaÐcð Hñ 	
ô 	
ˆõ !ØØ(ØÝ#Ô(Ø$Ø*Ø*ð
ñ 
ô 
ˆð Ô%×6Ò6°sÑ;Ô;Ð;Ð;Ð;Ð;Ð;Ð;ˆØ�Š˜œÑ$Ô$Ð$àÔ%×9Ò9¸+ÀtÔGZÑ[Ô[ˆ	Ø�Š˜	Ô*Ñ+Ô+Ð+Ø×Ò˜YÔ4Ñ5Ô5Ð5Ø×Ò˜Ô0Ô=¸|ÑLÔLÐLØ×Ò˜Ô0Ô;¸RÑ@Ô@Ð@àÔ&×:Ò:¸;ÐH_Ñ`Ô`ˆ
Ø×Ò˜Ô,Ñ-Ô-Ð-Ø�Š�j *Ô":Ñ;Ô;Ð;Ð;Ð;r   c           
   ƒ   ó&  K  — | j         j                             dddddddd¬	¦  «        }| j                              d|j        ¦  «        }|                      |j        ¦  «         |                      d
|j         	                    ¦   «         ¦  «         dS )z:Item snapshots expire after TTL and are purged from cache.Úexpired_swordzKiem CurY   é   r   é
   zTho Renéÿÿÿÿ)r_   r`   ra   rb   rc   rd   re   Úttl_secondsÚexpiredN)
r   rl   rm   rn   ro   r$   rp   r&   r'   Úlower)r   rt   ru   s      r   Ú"test_item_link_snapshot_expirationz2TestChatService.test_item_link_snapshot_expiration®   sœ   è è € àÔ Ô2×GÒGØ%Ø#ØØØØØ"Øð Hñ 	
ô 	
ˆð Ô%×9Ò9¸/È4ÔK^Ñ_Ô_ˆ	Ø×Ò˜Ô+Ñ,Ô,Ð,Ø�Š�i Ô!8×!>Ò!>Ñ!@Ô!@ÑAÔAÐAÐAÐAr   c              ƒ   óü  K  — t          dddt          j        dd¬¦  «        }| j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         |                      d|j        ¦  «         t          dddt          j        dd	¬¦  «        }| j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         |                      d
|j        ¦  «         | j         	                    ddd¬¦  «         t          dddt          j        dd¬¦  «        }| j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         |                      d|j        ¦  «         dS )zEWhisper rejects self-messaging, invalid targets, and blocked players.iq  ÚSolorA   zNote to self)r   r   r   r   Ú	target_idr   Nu   chÃ­nh mÃ¬nhr   zLost messageu   khÃ´ng há»£p lá»‡ir  T)Ú	player_idÚblocked_player_idÚ
is_blockedÚStalkerzCan we talk?u   Ä‘Ã£ cháº·n)
r   r   ÚWHISPERr   r#   r$   r%   r&   r'   Úset_blacklist_entry)r   Úreq_selfÚres_selfÚreq_invalidÚres_invalidÚreq_blockedrS   s          r   Útest_whisper_policiesz%TestChatService.test_whisper_policies¾   s¶  è è € õ &ØØØÝ#Ô+ØØ"ð
ñ 
ô 
ˆð Ô*×;Ò;¸HÑEÔEÐEÐEÐEÐEÐEÐEˆØ×Ò˜Ô)Ñ*Ô*Ð*Ø�Š�n hÔ&<Ñ=Ô=Ð=õ )ØØØÝ#Ô+ØØ"ð
ñ 
ô 
ˆð !Ô-×>Ò>¸{ÑKÔKÐKÐKÐKÐKÐKÐKˆØ×Ò˜Ô,Ñ-Ô-Ð-Ø�ŠÐ)¨;Ô+DÑEÔEÐEð 	Ô×-Ò-¸ÐPTÐaeÐ-ÑfÔfÐfÝ(ØØ!ØÝ#Ô+ØØ"ð
ñ 
ô 
ˆð !Ô-×>Ò>¸{ÑKÔKÐKÐKÐKÐKÐKÐKˆØ×Ò˜Ô,Ñ-Ô-Ð-Ø�Š�m [Ô%>Ñ?Ô?Ð?Ð?Ð?r   c              ƒ   óÎ  K  — t          dddt          j        dd¬¦  «        }| j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         t          j        ¦   «         dz
  | j        j        j	        dt          j        f<   | j                             |¦  «        ƒ d{V —†}|  
                    |j        ¦  «         |                      d	|j        ¦  «         dS )
z;Duplicate messages on World or Zone within 5s are rejected.iY  r@   r8   Úzone_marketzCan mua Kim Cuong So Thach!rC   Ng      @u   PhÃ¡t hiá»‡n spam)r   r   rE   r   r#   r(   r%   ÚtimeÚchannel_managerÚ_last_send_timer$   r&   r'   )r   Úreq1r<   r=   s       r   Útest_duplicate_spam_checkz)TestChatService.test_duplicate_spam_checkè   sù   è è € å!ØØ!ØÝ#Ô(Ø!Ø1ð
ñ 
ô 
ˆð Ô&×7Ò7¸Ñ=Ô=Ð=Ð=Ð=Ð=Ð=Ð=ˆØ�Š˜œÑ%Ô%Ð%õ [_ÔZcÑZeÔZeÐhkÑZkˆÔÔ)Ô9¸4ÅÔAUÐ:VÑWð Ô&×7Ò7¸Ñ=Ô=Ð=Ð=Ð=Ð=Ð=Ð=ˆØ×Ò˜œÑ&Ô&Ð&Ø�ŠÐ)¨4Ô+=Ñ>Ô>Ð>Ð>Ð>r   c              ƒ   ó  K  — d}t          d¦  «        D ]u}t          d|› �d|z   d|› �ddt          j        dd|› �d|› �t	          t          j        ¦   «         d	z  ¦  «        ¬
¦
  «
        }| j        j                             ||¦  «         Œv| j         	                    |¦  «        }|  
                    t          |¦  «        d¦  «         |  
                    |d         j        d¦  «         |  
                    |d         j        d¦  «         dS )z2Channel history is capped at exactly 100 messages.zzone:zone_arenaéi   Úmsg_i@  ÚDuelist_Ú r   zFight round iè  )
Ú
message_idr   r   Úsender_titleÚsender_guild_tagr   rƒ   Úraw_contentrF   Útimestamp_mséd   Úmsg_5r|   Úmsg_104N)Úranger   r   rE   Úintr’   r   r“   Úadd_to_historyÚget_channel_historyrs   Úlenrœ   )r   Úchannel_keyÚiÚmsgÚhistorys        r   Ú&test_ring_buffer_history_capped_at_100z6TestChatService.test_ring_buffer_history_capped_at_100ý   s  è è € à'ˆÝ�s‘”ð 	Oð 	OˆAÝ Ø% !˜:˜:Ø ™(Ø* q˜N˜NØØ!#Ý'Ô,ØØ.¨1Ð.Ð.Ø!3°Ð!3Ð!3Ý ¥¤¡¤¨tÑ!3Ñ4Ô4ðñ ô ˆCð ÔÔ-×<Ò<¸[È#ÑNÔNÐNÐNàÔ#×7Ò7¸ÑDÔDˆØ×Ò�˜W™œ sÑ+Ô+Ð+Ø×Ò˜ œÔ.°Ñ8Ô8Ð8Ø×Ò˜ œÔ/°Ñ;Ô;Ð;Ð;Ð;r   c              ƒ   ó8  ‡‡K  — g Šg Šdt           ddfˆfd„}dt           ddfˆfd„}d}| j                             |d|¦  «         | j                             |d|¦  «         t          d	d
dt          j        dd¬¦  «        }| j                             |¦  «        ƒ d{V —†}|                      |j        ¦  «         t          j
        d¦  «        ƒ d{V —† |                      t          ‰¦  «        d¦  «         |                      t          ‰¦  «        d¦  «         dS )zGRouter supports both synchronous sinks and async coroutine subscribers.r«   r   Nc                 ó2   •— ‰                      | ¦  «         d S ©N©Úappend)r«   Úsync_receiveds    €r   Ú	sync_sinkzLTestChatService.test_cluster_router_sync_and_async_fanout.<locals>.sync_sink  s   ø€ Ø× Ò  Ñ%Ô%Ð%Ð%Ð%r   c              “   ó6   •K  — ‰                      | ¦  «         d S r°   r±   )r«   Úasync_receiveds    €r   Ú
async_sinkzMTestChatService.test_cluster_router_sync_and_async_fanout.<locals>.async_sink  s!   øè è € Ø×!Ò! #Ñ&Ô&Ð&Ð&Ð&r   zzone:battlefieldi)#  i*#  i+#  ÚGeneralrK   Úbattlefieldz	Tien len!rC   g{®Gáz„?rY   )r   r   Úsubscribe_clientr   r   rE   r#   r(   r%   ÚasyncioÚsleeprs   r¨   )r   r´   r·   Úch_keyr;   r+   r¶   r³   s         @@r   Ú)test_cluster_router_sync_and_async_fanoutz9TestChatService.test_cluster_router_sync_and_async_fanout  sr  øøè è € à.0ˆØ/1ˆð	&�>ð 	&¨dð 	&ð 	&ð 	&ð 	&ð 	&ð 	&ð	'¥.ð 	'°Tð 	'ð 	'ð 	'ð 	'ð 	'ð 	'ð $ˆØÔ×*Ò*¨6°4¸ÑCÔCÐCØÔ×*Ò*¨6°4¸ÑDÔDÐDå ØØ!ØÝ#Ô(Ø!Øð
ñ 
ô 
ˆð Ô%×6Ò6°sÑ;Ô;Ð;Ð;Ð;Ð;Ð;Ð;ˆØ�Š˜œÑ$Ô$Ð$åŒm˜DÑ!Ô!Ð!Ð!Ð!Ð!Ð!Ð!Ð!Ø×Ò�˜]Ñ+Ô+¨QÑ/Ô/Ð/Ø×Ò�˜^Ñ,Ô,¨aÑ0Ô0Ð0Ð0Ð0r   c              ƒ   óª  K  — t          ¦   «         }|                      |                     d¦  «        d¦  «         |                      |                     d¦  «        d¦  «         |                      |                     d¦  «        d¦  «         | j        j                             |¦  «         t          ddd	t          j        d
d¬¦  «        }| j         	                    |¦  «        ƒ d{V —†}|  
                    |j        ¦  «         |                      t          |j        ¦  «        d¦  «         |                      |j        d         d         d¦  «         dS )zCTests Redis 7 Sharded Pub/Sub bridge formatting and broadcast hook.Úworldz{world}zzone:barrowz{zone:barrow}z	guild:101z{guild:101}i%  ÚBroadcasterr8   Úancient_barrowzThong bao toan khu vuc!rC   NrY   r   z{zone:ancient_barrow})r   rs   Úto_sharded_channelr   Úcluster_routerÚattach_redis_bridger   r   rE   r#   r(   r%   rN   r¨   Úpublished_messages)r   Úbridger;   r+   s       r   Ú,test_redis_sharded_pubsub_bridge_integrationz<TestChatService.test_redis_sharded_pubsub_bridge_integration2  sI  è è € å)Ñ+Ô+ˆØ×Ò˜×2Ò2°7Ñ;Ô;¸YÑGÔGÐGØ×Ò˜×2Ò2°=ÑAÔAÀ?ÑSÔSÐSØ×Ò˜×2Ò2°;Ñ?Ô?ÀÑOÔOÐOàÔÔ(×<Ò<¸VÑDÔDÐDå ØØ%ØÝ#Ô(Ø$Ø-ð
ñ 
ô 
ˆð Ô%×6Ò6°sÑ;Ô;Ð;Ð;Ð;Ð;Ð;Ð;ˆØ�Š˜œÑ$Ô$Ð$Ø×Ò¥ FÔ$=Ñ >Ô >ÀÑBÔBÐBØ×Ò˜Ô2°1Ô5°aÔ8Ð:QÑRÔRÐRÐRÐRr   )r   N)Ú__name__Ú
__module__Ú__qualname__r   r.   r5   r>   rH   rU   rw   r€   r�   r–   r­   r¾   rÈ   © r   r   r   r      s-  € € € € € ð\ð \ð \ð \ð+ð +ð +ð +ð.+ð +ð +ð +ð.:ð :ð :ð :ð$6ð 6ð 6ð 6ð-ð -ð -ð -ðB!<ð !<ð !<ð !<ðFBð Bð Bð Bð (@ð (@ð (@ð (@ðT?ð ?ð ?ð ?ð*<ð <ð <ð <ð.1ð 1ð 1ð 1ð<Sð Sð Sð Sð Sð Sr   r   Ú__main__)Ú__doc__r»   r’   ÚunittestÚtypingr   Úserver.chat.chat_cluster_routerr   Úserver.chat.chat_typesr   r   r   r   Úserver.chat.chat_servicer	   ÚIsolatedAsyncioTestCaser   rÉ   ÚmainrÌ   r   r   ú<module>rÖ      s  ððð ð €€€Ø €€€Ø €€€Ø Ð Ð Ð Ð Ð à DÐ DÐ DÐ DÐ DÐ Dðð ð ð ð ð ð ð ð ð ð ð ð 1Ð 0Ð 0Ð 0Ð 0Ð 0ðpSð pSð pSð pSð pS�hÔ6ñ pSô pSð pSðf	 ˆzÒÐØ€H„M�O„O€O€O€Oð Ðr   