@@ -117,6 +117,167 @@ def test_worker_metrics_params_default_to_none_and_false(self):
117117 assert worker .metrics_temporality_delta is False
118118
119119
120+ class TestAgentexWorkerAgentCard :
121+ """Tests that AgentexWorker publishes an optional AgentCard through the
122+ existing automatic registration lifecycle."""
123+
124+ @pytest .fixture (autouse = True )
125+ def cleanup_env (self ):
126+ yield
127+ for key in ("AGENT_ID" , "AGENT_NAME" , "AGENT_API_KEY" ):
128+ os .environ .pop (key , None )
129+
130+ @staticmethod
131+ def _env_vars_mock ():
132+ env = MagicMock ()
133+ env .AGENTEX_BASE_URL = "http://agentex.test"
134+ env .ACP_URL = "http://agent.test"
135+ env .ACP_PORT = 8000
136+ env .AGENT_DESCRIPTION = "test description"
137+ env .AGENT_NAME = "test-agent"
138+ env .ACP_TYPE = "agentic"
139+ env .AUTH_PRINCIPAL_B64 = None
140+ env .AGENTEX_DEPLOYMENT_ID = None
141+ env .AGENT_ID = None
142+ env .AGENT_INPUT_TYPE = None
143+ return env
144+
145+ @staticmethod
146+ def _httpx_client_mock (captured_payloads ):
147+ response = MagicMock ()
148+ response .status_code = 200
149+ response .json .return_value = {
150+ "id" : "agent-id" ,
151+ "name" : "test-agent" ,
152+ "agent_api_key" : "api-key" ,
153+ }
154+
155+ async def post (url , json = None , timeout = None ): # noqa: ARG001
156+ captured_payloads .append (json )
157+ return response
158+
159+ client = MagicMock ()
160+ client .__aenter__ = AsyncMock (return_value = MagicMock (post = AsyncMock (side_effect = post )))
161+ client .__aexit__ = AsyncMock (return_value = False )
162+ return MagicMock (return_value = client )
163+
164+ def test_worker_agent_card_defaults_to_none (self ):
165+ from agentex .lib .core .temporal .workers .worker import AgentexWorker
166+
167+ worker = AgentexWorker (task_queue = "test-queue" , health_check_port = 8080 )
168+
169+ assert worker .agent_card is None
170+
171+ async def test_default_registration_calls_register_agent_without_card (self ):
172+ """The default worker still registers automatically and passes no card,
173+ preserving existing callers and wire behavior."""
174+ from agentex .lib .core .temporal .workers .worker import AgentexWorker
175+
176+ worker = AgentexWorker (task_queue = "test-queue" , health_check_port = 8080 )
177+
178+ with patch (
179+ "agentex.lib.core.temporal.workers.worker.register_agent" , new = AsyncMock ()
180+ ) as mock_register , patch (
181+ "agentex.lib.core.temporal.workers.worker.assert_backend_compatible" ,
182+ new = AsyncMock (),
183+ ), patch (
184+ "agentex.lib.core.temporal.workers.worker.EnvironmentVariables"
185+ ) as mock_env_cls :
186+ env = self ._env_vars_mock ()
187+ mock_env_cls .refresh .return_value = env
188+
189+ await worker ._register_agent ()
190+
191+ mock_register .assert_awaited_once_with (env , agent_card = None )
192+
193+ async def test_supplied_card_forwarded_exactly_once_by_run_lifecycle (self ):
194+ """A card passed to the constructor reaches register_agent exactly once
195+ through the existing automatic registration in run(); no second
196+ registration call is introduced."""
197+ from agentex .lib .types .agent_card import AgentCard
198+ from agentex .lib .core .temporal .workers .worker import AgentexWorker
199+
200+ card = AgentCard (metadata = {"permits_capable" : True })
201+ worker = AgentexWorker (
202+ task_queue = "test-queue" , health_check_port = 8080 , agent_card = card
203+ )
204+
205+ with patch .object (
206+ worker , "start_health_check_server" , new = AsyncMock ()
207+ ), patch (
208+ "agentex.lib.core.temporal.workers.worker.register_agent" , new = AsyncMock ()
209+ ) as mock_register , patch (
210+ "agentex.lib.core.temporal.workers.worker.assert_backend_compatible" ,
211+ new = AsyncMock (),
212+ ), patch (
213+ "agentex.lib.core.temporal.workers.worker.EnvironmentVariables"
214+ ) as mock_env_cls , patch (
215+ "agentex.lib.core.temporal.workers.worker.get_temporal_client" ,
216+ new = AsyncMock (return_value = MagicMock ()),
217+ ), patch (
218+ "agentex.lib.core.temporal.workers.worker.Worker"
219+ ) as mock_worker_cls :
220+ env = self ._env_vars_mock ()
221+ mock_env_cls .refresh .return_value = env
222+ mock_worker_cls .return_value .run = AsyncMock ()
223+
224+ await worker .run (activities = [], workflows = [MagicMock ()])
225+
226+ mock_register .assert_awaited_once_with (env , agent_card = card )
227+
228+ async def test_worker_and_fastacp_paths_serialize_the_same_card_shape (self ):
229+ """The worker path and the FastACP/BaseACPServer lifespan path hand the
230+ same card to register_agent, so the registration payload's
231+ registration_metadata.agent_card is identical."""
232+ from agentex .lib .types .agent_card import AgentCard
233+ from agentex .lib .core .temporal .workers .worker import AgentexWorker
234+ from agentex .lib .sdk .fastacp .base .base_acp_server import BaseACPServer
235+
236+ card = AgentCard (metadata = {"permits_capable" : True , "region" : "us" })
237+
238+ worker_payloads = []
239+ worker = AgentexWorker (
240+ task_queue = "test-queue" , health_check_port = 8080 , agent_card = card
241+ )
242+ with patch (
243+ "agentex.lib.core.temporal.workers.worker.assert_backend_compatible" ,
244+ new = AsyncMock (),
245+ ), patch (
246+ "agentex.lib.core.temporal.workers.worker.EnvironmentVariables"
247+ ) as mock_env_cls , patch (
248+ "agentex.lib.utils.registration.httpx.AsyncClient" ,
249+ new = self ._httpx_client_mock (worker_payloads ),
250+ ):
251+ mock_env_cls .refresh .return_value = self ._env_vars_mock ()
252+ await worker ._register_agent ()
253+
254+ acp_payloads = []
255+ server = BaseACPServer .create ()
256+ server ._agent_card = card
257+ lifespan = server .get_lifespan_function ()
258+ with patch (
259+ "agentex.lib.sdk.fastacp.base.base_acp_server.assert_backend_compatible" ,
260+ new = AsyncMock (),
261+ ), patch (
262+ "agentex.lib.sdk.fastacp.base.base_acp_server.EnvironmentVariables"
263+ ) as mock_env_cls , patch (
264+ "agentex.lib.sdk.fastacp.base.base_acp_server.shutdown_default_span_queue" ,
265+ new = AsyncMock (),
266+ ), patch (
267+ "agentex.lib.utils.registration.httpx.AsyncClient" ,
268+ new = self ._httpx_client_mock (acp_payloads ),
269+ ):
270+ mock_env_cls .refresh .return_value = self ._env_vars_mock ()
271+ async with lifespan (MagicMock ()):
272+ pass
273+
274+ assert len (worker_payloads ) == 1
275+ assert len (acp_payloads ) == 1
276+ worker_card = worker_payloads [0 ]["registration_metadata" ]["agent_card" ]
277+ acp_card = acp_payloads [0 ]["registration_metadata" ]["agent_card" ]
278+ assert worker_card == acp_card == card .model_dump ()
279+
280+
120281class TestGetTemporalClientMetricsConfig :
121282 """Tests that metrics params reach OpenTelemetryConfig correctly."""
122283
0 commit comments