
    "֞iRD                    D   d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZddlZddlmZmZmZ d ZdddZ G d dej&                        Z G d	 d
ej&                        Z G d dej&                        Z G d dej&                        Z G d dej&                        Z G d dej&                        Z G d dej&                        Z G d dej&                        Z G d dej&                        Z G d dej&                        Zedk(  r ej>                          yy)ui  
Tests for core/task_queue — Dramatiq Redis broker module.

Coverage
--------
BB1  TaskManager.enqueue dispatches to correct actor (3 tests)
BB2  Unknown task name returns False (2 tests)
BB3  get_queue_depth returns int (2 tests)
BB4  get_all_queue_depths returns dict with 4 keys (1 test)
BB5  Workers have correct queue_name assignments (4 tests — one per worker)

WB1  configure_broker creates RedisBroker with the correct URL (2 tests)
WB2  Worker no-op decorator works when dramatiq not installed (2 tests)
WB3  get_broker returns None before configure_broker is called (1 test)
WB4  TaskManager logs on failure (1 test)

All tests mock dramatiq and Redis — ZERO live connections.

# VERIFICATION_STAMP
# Story: M6.04 — test_task_queue.py — full test suite
# Verified By: parallel-builder
# Verified At: 2026-02-25T00:00:00Z
# Tests: 18/18
# Coverage: 100%
    )annotationsN)	MagicMockpatchcallc                 &    ddl mc m}  d| _        | S )z;Reload broker to reset the _broker singleton between tests.r   N)core.task_queue.broker
task_queuebroker_broker)
broker_mods    4/mnt/e/genesis-system/tests/infra/test_task_queue.py_reload_broker_moduler   '   s    //J    c                    t        d      }t        d      }| xs t        d      }||j                  _        t               |_        ||j                  _        |||fS )z
    Return a fake ``dramatiq`` package subtree as a dict of module mocks.

    broker_instance : the object that RedisBroker() will return.
    dramatiqnamedramatiq.brokers.redisRedisBroker_instance)r   RedisBrokerreturn_valuebrokersredis)broker_instancefake_dramatiqfake_redis_brokerconstructed_brokers       r   _make_fake_dramatiqr   .   sa     :.M!'?@(RI;Q,R1C!!.%KM"3M+-???r   c                  .    e Zd ZdZd Zd Zd Zd Zd Zy)TestTaskManagerEnqueuez>BB1: enqueue() resolves the actor and calls send_with_options.c                    t        j                  d      | _        dD ]6  }t        |      }t        d       |_        t        | j                  ||       8 y )Ncore.task_queue.workers)send_notificationprocess_epoch_taskprocess_voice_webhookr   r   )types
ModuleTypefake_workersr   send_with_optionssetattr)selfr   
actor_mocks      r   setUpzTestTaskManagerEnqueue.setUpG   sN    !,,-FGX 	9D"-J+4$+GJ(D%%tZ8	9r   c                b    t        j                  t        j                  d| j                  i      S )z;Context: replace core.task_queue.workers with fake_workers.r"   )r   dictsysmodulesr)   )r,   managers     r   _patch_workersz%TestTaskManagerEnqueue._patch_workersO   s#    zz#++(A4CTCT'UVVr   c                h   ddl m}  |       }t        j                  t        j
                  d| j                  i      5  |j                  dddddid	
      }ddd       | j                         | j                  j                  j                  j                  ddddifi d	       y# 1 sw Y   RxY w)z<BB1a: send_notification is dispatched via send_with_options.r   TaskManagerr"   r#   emailztest@example.commsgHihighqueueNargskwargs
queue_name)core.task_queue.managerr7   r   r0   r1   r2   r)   enqueue
assertTruer#   r*   assert_called_once_withr,   r7   r3   results       r   6test_enqueue_send_notification_calls_send_with_optionszMTestTaskManagerEnqueue.test_enqueue_send_notification_calls_send_with_optionsS   s    7-ZZ&?ARAR%ST 	__#" % F	 	++==UU-t}= 	V 	
	 	s   B((B1c                R   ddl m}  |       }t        j                  t        j
                  d| j                  i      5  |j                  dd      }ddd       | j                         | j                  j                  j                  j                  di d	       y# 1 sw Y   MxY w)
z9BB1b: process_epoch_task defaults to the 'default' queue.r   r6   r"   r$   2026-W09NrJ   defaultr>   )rB   r7   r   r0   r1   r2   r)   rC   rD   r$   r*   rE   rF   s       r   ;test_enqueue_process_epoch_task_dispatched_to_default_queuezRTestTaskManagerEnqueue.test_enqueue_process_epoch_task_dispatched_to_default_queuef   s    7-ZZ&?ARAR%ST 	G__%9:FF	G,,>>VV  	W 	
	G 	Gs   BB&c                `   ddl m}  |       }ddi}t        j                  t        j
                  d| j                  i      5  |j                  d|d      }d	d	d	       | j                         | j                  j                  j                  j                  |fi d
       y	# 1 sw Y   NxY w)z@BB1c: process_voice_webhook can be sent to the 'critical' queue.r   r6   call_idzabc-123r"   r%   criticalr<   Nr>   )rB   r7   r   r0   r1   r2   r)   rC   rD   r%   r*   rE   )r,   r7   r3   payloadrG   s        r   1test_enqueue_voice_webhook_dispatched_to_criticalzHTestTaskManagerEnqueue.test_enqueue_voice_webhook_dispatched_to_criticals   s    7-i(ZZ&?ARAR%ST 	Y__%<gZ_XF	Y//AAYY! 	Z 	
	Y 	Ys   B$$B-N)	__name__
__module____qualname____doc__r.   r4   rH   rM   rR    r   r   r    r    D   s    H9W
&

r   r    c                  $    e Zd ZdZddZd Zd Zy)TestTaskManagerUnknownTaskz:BB2: enqueue() returns False for unregistered actor names.c                ,    t        j                  d      S )Nr"   )r'   r(   r,   s    r   _empty_workers_modulez0TestTaskManagerUnknownTask._empty_workers_module   s     9::r   c                    ddl m}  |       }t        j                  t        j
                  d| j                         i      5  |j                  dd      }ddd       | j                         y# 1 sw Y   xY w)z3BB2a: a completely unknown task name returns False.r   r6   r"   nonexistent_taskarg1N)	rB   r7   r   r0   r1   r2   r\   rC   assertFalserF   s       r   'test_enqueue_unknown_task_returns_falsezBTestTaskManagerUnknownTask.test_enqueue_unknown_task_returns_false   se    7-ZZ&?A[A[A]%^_ 	A__%7@F	A 	A 	As   A//A8c                h   ddl m}  |       }t        j                  d      }t	               }t        |d|       t        j                  t        j                  d|i      5  |j                  dd      }ddd       | j                         |j                  j                          y# 1 sw Y   5xY w)z@BB2b: an invalid queue name is rejected before the actor lookup.r   r6   r"   r#   not_a_queuer<   N)rB   r7   r'   r(   r   r+   r   r0   r1   r2   rC   r`   r*   assert_not_called)r,   r7   r3   r)   r-   rG   s         r   -test_enqueue_invalid_queue_name_returns_falsezHTestTaskManagerUnknownTask.test_enqueue_invalid_queue_name_returns_false   s    7-''(AB[
1:>ZZ&?%NO 	O__%8_NF	O $$668	O 	Os    B((B1N)returnztypes.ModuleType)rS   rT   rU   rV   r\   ra   re   rW   r   r   rY   rY      s    D;!
9r   rY   c                      e Zd ZdZd Zd Zy)TestGetQueueDepthzEBB3: get_queue_depth always returns an int, even when broker is None.c                "   t               }d|j                  _        t               }||_        ddlm}  ||      }|j                  d      }| j                  |t               | j                  |d       |j                  j                  d       y)z'BB3a: returns Redis LLEN result as int.   r   r6   r
   rL   zdramatiq:defaultN)r   llenr   clientrB   r7   get_queue_depthassertIsInstanceintassertEqualrE   )r,   mock_redis_clientmock_brokerr7   r3   depths         r   -test_queue_depth_returns_int_with_mock_brokerz?TestGetQueueDepth.test_queue_depth_returns_int_with_mock_broker   sz    %K./+k.7[1''	2eS)"667IJr   c                    ddl m}  |d      }t        dd      5  |j                  d      }ddd       | j	                  d       | j                  |t               y# 1 sw Y   2xY w)z/BB3b: returns 0 gracefully when broker is None.r   r6   Nrk   z.core.task_queue.manager._get_configured_brokerr&   r;   )rB   r7   r   rn   rq   ro   rp   )r,   r7   r3   rt   s       r   ,test_queue_depth_returns_zero_when_no_brokerz>TestGetQueueDepth.test_queue_depth_returns_zero_when_no_broker   s`    7T*CRVW 	4++F3E	4"eS)	4 	4s   A  A)N)rS   rT   rU   rV   ru   rw   rW   r   r   rh   rh      s    OK*r   rh   c                      e Zd ZdZd Zy)TestGetAllQueueDepthszLBB4: get_all_queue_depths returns a dict with exactly the 4 priority queues.c                r   t               }d|j                  _        t               }||_        ddlm}  ||      }|j                         }| j                  |t               | j                  t        |j                               h d       |j                         D ]  }| j                  |t                y)z;BB4: all four queue names are present in the returned dict.r   r6   rk   >   lowr;   rL   rP   N)r   rl   r   rm   rB   r7   get_all_queue_depthsro   r0   rq   setkeysvaluesrp   )r,   rr   rs   r7   r3   depthsvs          r   test_returns_all_four_queuesz2TestGetAllQueueDepths.test_returns_all_four_queues   s    %K./+k.7[1--/fd+V[[]+-ST 	*A!!!S)	*r   N)rS   rT   rU   rV   r   rW   r   r   ry   ry      s
    V*r   ry   c                  .    e Zd ZdZd Zd Zd Zd Zd Zy)TestWorkerQueueAssignmentsaG  
    BB5: Verify that each worker function exists and has the correct
    queue_name attribute (set by the @actor decorator or the no-op stub).

    Because dramatiq is not installed in the test environment, we verify
    the no-op decorator attaches .send / .send_with_options stubs and that
    the function is callable.
    c                $    dd l mc m} || _        y )Nr   )core.task_queue.workersr	   workersworkers_mod)r,   ws     r   r.   z TestWorkerQueueAssignments.setUp   s    ++r   c                `    | j                  t        | j                  j                               y)z0BB5a: process_epoch_task exists and is callable.N)rD   callabler   r$   r[   s    r   #test_process_epoch_task_is_callablez>TestWorkerQueueAssignments.test_process_epoch_task_is_callable   s    !1!1!D!DEFr   c                `    | j                  t        | j                  j                               y)z/BB5b: send_notification exists and is callable.N)rD   r   r   r#   r[   s    r   "test_send_notification_is_callablez=TestWorkerQueueAssignments.test_send_notification_is_callable   s    !1!1!C!CDEr   c                `    | j                  t        | j                  j                               y)z3BB5c: process_voice_webhook exists and is callable.N)rD   r   r   r%   r[   s    r   &test_process_voice_webhook_is_callablezATestWorkerQueueAssignments.test_process_voice_webhook_is_callable   s    !1!1!G!GHIr   c                `    | j                  t        | j                  j                               y)z7BB5d: generate_analytics_report exists and is callable.N)rD   r   r   generate_analytics_reportr[   s    r   *test_generate_analytics_report_is_callablezETestWorkerQueueAssignments.test_generate_analytics_report_is_callable   s    !1!1!K!KLMr   N)	rS   rT   rU   rV   r.   r   r   r   r   rW   r   r   r   r      s$    GFJNr   r   c                  (    e Zd ZdZd Zd Zd Zd Zy)TestConfigureBrokerzGWB1: configure_broker() builds the URL correctly and calls RedisBroker.c                    t                y Nr   r[   s    r   r.   zTestConfigureBroker.setUp      r   c                    t                y r   r   r[   s    r   tearDownzTestConfigureBroker.tearDown  r   r   c                B   ddl mc m} t               \  }}}t	        j
                  t        j                  ||j                  |d      5  |j                  dd      }ddd       |j                  j                  d       | j                  |       y# 1 sw Y   8xY w)z;WB1a: explicit redis_url is passed directly to RedisBroker.r   Nr   zdramatiq.brokersr   z"redis://user:pass@custom-host:9999T)	redis_urlforce)url)r   r	   r
   r   r   r0   r1   r2   r   configure_brokerr   rE   assertIs)r,   r   r   fake_redis_pkgr   rG   s         r   'test_configure_broker_uses_provided_urlz;TestConfigureBroker.test_configure_broker_uses_provided_url	  s    33<O<Q9~'9ZZKK)$1$9$9*8
 
	  00>d 1 F
	 	""::4 	; 	
 	f01
	 
	s   
BBc                   ddl mc m} t               \  }}}t	        j
                  t        j                  ||j                  |d      5  t	        j
                  di d      5  ddl	}|j                  j                  dd       |j                  d	      }ddd       ddd       |j                  j                  }|d
   r|d
   d   n|d   d   }| j                  d|       | j                  d|       y# 1 sw Y   axY w# 1 sw Y   exY w)zLWB1b: when no URL is given, the Elestio host appears in the constructed URL.r   Nr   z
os.environF)clear	REDIS_URLT)r      r   z#redis-genesis-u50607.vm.elestio.app26379)r   r	   r
   r   r   r0   r1   r2   r   osenvironpopr   r   	call_argsassertIn)	r,   r   r   r   r   r   rG   call_kwargsused_urls	            r   7test_configure_broker_uses_elestio_defaults_when_no_urlzKTestConfigureBroker.test_configure_broker_uses_elestio_defaults_when_no_url   s    33<O<Q9~'9 ZZKK)$1$9$9*8
 	= ::lBe4	= JJNN;-00t0<F	= 	= %00::1<QAu-[QR^TUEV;XFgx(!	= 	= 	= 	=s$   
D#3C6D6C?	;DDN)rS   rT   rU   rV   r.   r   r   r   rW   r   r   r   r      s    Q  2.)r   r   c                      e Zd ZdZd Zd Zy) TestWorkerNoOpWhenDramatiqAbsentzUWB2: when dramatiq is absent the no-op .send stub logs a warning instead of crashing.c                    ddl mc m} |j                  r| j	                  d       |j
                  j                  ddi        y)z6WB2a: calling .send() on a no-op actor does not raise.r   N/   dramatiq is installed — no-op path not activer8   za@b.com)r   r	   r   DRAMATIQ_AVAILABLEskipTestr#   sendr,   r   s     r   test_send_noop_does_not_raisez>TestWorkerNoOpWhenDramatiqAbsent.test_send_noop_does_not_raiseA  s8    55))MMKL 	%%**7IrBr   c                    ddl mc m} |j                  r| j	                  d       |j
                  j                  di d       y)zCWB2b: calling .send_with_options() on a no-op actor does not raise.r   Nr   rK   rL   r>   )r   r	   r   r   r   r$   r*   r   s     r   *test_send_with_options_noop_does_not_raisezKTestWorkerNoOpWhenDramatiqAbsent.test_send_with_options_noop_does_not_raiseK  s>    55))MMKL&&88ri 	9 	
r   N)rS   rT   rU   rV   r   r   rW   r   r   r   r   >  s    _C	
r   r   c                  "    e Zd ZdZd Zd Zd Zy)TestGetBrokerBeforeConfigurezBWB3: get_broker() returns None when configure_broker() hasn't run.c                    t                y r   r   r[   s    r   r.   z"TestGetBrokerBeforeConfigure.setUp^  r   r   c                    t                y r   r   r[   s    r   r   z%TestGetBrokerBeforeConfigure.tearDowna  r   r   c                X    ddl mc m} |j                         }| j	                  |       y)u5   WB3: fresh module state — broker singleton is None.r   N)r   r	   r
   
get_brokerassertIsNone)r,   r   rG   s      r   &test_get_broker_returns_none_initiallyzCTestGetBrokerBeforeConfigure.test_get_broker_returns_none_initiallyd  s$    33&&(&!r   N)rS   rT   rU   rV   r.   r   r   rW   r   r   r   r   [  s    L  "r   r   c                      e Zd ZdZd Zy)TestTaskManagerLogsOnFailurezPWB4: enqueue() emits a logger.exception message when an unexpected error occurs.c                   ddl m}  |       }t        j                  d      } G d d      }d }||_        t        j                  t        j                  d|i      5  | j                  dd	      5 }|j                  d
d      }ddd       ddd       | j                         dj                  j                        }| j                  d
|       y# 1 sw Y   PxY w# 1 sw Y   TxY w)z7WB4: ImportError inside enqueue() is caught and logged.r   r6   r"   c                      e Zd ZddZy)hTestTaskManagerLogsOnFailure.test_enqueue_logs_exception_on_unexpected_error.<locals>._RaisingDescriptorNc                    t        d      Nzsimulated broker crashRuntimeError)r,   objobjtypes      r   __get__zpTestTaskManagerLogsOnFailure.test_enqueue_logs_exception_on_unexpected_error.<locals>._RaisingDescriptor.__get__{  s    "#;<<r   r   )rS   rT   rU   r   rW   r   r   _RaisingDescriptorr   z  s    =r   r   c                    t        d      r   r   r   s    r   _raise_on_getzcTestTaskManagerLogsOnFailure.test_enqueue_logs_exception_on_unexpected_error.<locals>._raise_on_get  s    788r   zcore.task_queue.managerERROR)levelany_taskrL   r<   N
)rB   r7   r'   r(   __getattr__r   r0   r1   r2   
assertLogsrC   r`   joinoutputr   )	r,   r7   r3   exploding_workersr   r   log_ctxrG   combineds	            r   /test_enqueue_logs_exception_on_unexpected_errorzLTestTaskManagerLogsOnFailure.test_enqueue_logs_exception_on_unexpected_errorr  s    7- ",,-FG	= 	=
	9 )6%ZZ&?AR%ST 	F!:'J Fg 9EF	F 	 99W^^,j(+F F	F 	Fs$   C 1CC C	C  C)N)rS   rT   rU   rV   r   rW   r   r   r   r   o  s
    Z,r   r   c                      e Zd ZdZd Zy)TestModuleLevelEnqueuezAExtra: module-level enqueue() delegates to TaskManager.enqueue().c                    ddl mc m} t        j                  d      }t        j                  t        j                  d|i      5  |j                  d      }ddd       | j                         y# 1 sw Y   xY w)zEModule-level enqueue mirrors TaskManager behaviour for unknown tasks.r   Nr"   does_not_exist)rB   r	   r3   r'   r(   r   r0   r1   r2   rC   r`   )r,   mgr_modempty_workersrG   s       r   2test_module_enqueue_returns_false_for_unknown_taskzITestModuleLevelEnqueue.test_module_enqueue_returns_false_for_unknown_task  sc    11(()BCZZ&?%OP 	7__%56F	7 	7 	7s   A11A:N)rS   rT   rU   rV   r   rW   r   r   r   r     s
    K!r   r   __main__r   )r   zMagicMock | None) rV   
__future__r   builtins@py_builtins_pytest.assertion.rewrite	assertionrewrite
@pytest_ar	importlibr1   r'   unittestunittest.mockr   r   r   r   r   TestCaser    rY   rh   ry   r   r   r   r   r   r   rS   mainrW   r   r   <module>r      s  2 #     
   0 0@,;
X.. ;
D9!2!2 9>*)) *@*H-- *0N!2!2 NF7)(++ 7)|
x'8'8 
:"8#4#4 "(,8#4#4 ,F	!X.. 	! zHMMO r   