Skip to content

Commit 67ba54d

Browse files
Add Temporal adapter BDD tests with persistent event loop
- Add comprehensive Temporal adapter feature tests covering workflows, signals, queries, workers, and schedules - Implement persistent event loop per scenario to keep workers alive across steps - Add test workflows and activities for various scenarios (greeting, signal/query, timeout, retry, etc.) - Add Temporal container support with dev mode SQLite backend - Fix workflow execution to use run_async helper for proper async handling
1 parent c188221 commit 67ba54d

11 files changed

Lines changed: 2272 additions & 5 deletions

File tree

.env.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ FASTAPI__SWAGGER_UI_PARAMS={}
1919
TESTCONTAINERS_RYUK_CONTAINER_IMAGE=testcontainers/ryuk:0.14.0
2020

2121
# Temporal Configuration
22-
TEMPORAL__IMAGE=temporalio/auto-setup:1.29.2
22+
TEMPORAL__IMAGE=temporalio/temporal:1.5.1
2323
TEMPORAL__HOST=localhost
2424
TEMPORAL__PORT=7233
2525
TEMPORAL__NAMESPACE=default

archipy/adapters/temporal/adapters.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,13 @@ async def start_workflow(
211211
workflow_id = workflow_id or str(uuid4())
212212
task_queue = task_queue or self.config.TASK_QUEUE
213213

214+
# Build positional args: only include arg if it's not None,
215+
# so workflows that take no parameters aren't given an extra argument.
216+
positional_args: list[Any] = [] if arg is None else [arg]
217+
214218
return await client.start_workflow(
215219
workflow,
216-
arg,
220+
*positional_args,
217221
id=workflow_id,
218222
task_queue=task_queue,
219223
execution_timeout=timedelta(seconds=execution_timeout or self.config.WORKFLOW_EXECUTION_TIMEOUT),
@@ -259,9 +263,13 @@ async def execute_workflow(
259263
workflow_id = workflow_id or str(uuid4())
260264
task_queue = task_queue or self.config.TASK_QUEUE
261265

266+
# Build positional args: only include arg if it's not None,
267+
# so workflows that take no parameters aren't given an extra argument.
268+
positional_args: list[Any] = [] if arg is None else [arg]
269+
262270
return await client.execute_workflow(
263271
workflow,
264-
arg,
272+
*positional_args,
265273
id=workflow_id,
266274
task_queue=task_queue,
267275
execution_timeout=timedelta(seconds=execution_timeout or self.config.WORKFLOW_EXECUTION_TIMEOUT),
@@ -329,7 +337,9 @@ async def signal_workflow(
329337
If None, signals the latest run. Defaults to None.
330338
"""
331339
handle = await self.get_workflow_handle(workflow_id, run_id)
332-
await handle.signal(signal_name, arg)
340+
# Only pass arg if it's not None, so signal handlers with no parameters work correctly.
341+
positional_args: list[Any] = [] if arg is None else [arg]
342+
await handle.signal(signal_name, *positional_args)
333343

334344
@override
335345
async def query_workflow(
@@ -352,7 +362,9 @@ async def query_workflow(
352362
Any: The query result from the workflow.
353363
"""
354364
handle = await self.get_workflow_handle(workflow_id, run_id)
355-
return await handle.query(query_name, arg)
365+
# Only pass arg if it's not None, so query handlers with no parameters work correctly.
366+
positional_args: list[Any] = [] if arg is None else [arg]
367+
return await handle.query(query_name, *positional_args)
356368

357369
@override
358370
async def list_workflows(

archipy/adapters/temporal/worker.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,14 @@ async def start_worker(
283283
Raises:
284284
WorkerConnectionError: If the worker fails to start.
285285
"""
286+
if not task_queue or not task_queue.strip():
287+
raise WorkerConnectionError(
288+
additional_data={
289+
"message": "Task queue name cannot be empty",
290+
"task_queue": task_queue,
291+
},
292+
)
293+
286294
client = await self._get_client()
287295
worker_id = str(uuid4())
288296
worker_identity = identity or f"worker-{worker_id[:8]}"

features/environment.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class TestConfig(BaseConfig):
3535
KEYCLOAK__IMAGE: str
3636
SCYLLADB__IMAGE: str
3737
STARROCKS__IMAGE: str
38+
TEMPORAL__IMAGE: str
3839
TESTCONTAINERS_RYUK_CONTAINER_IMAGE: str | None = None
3940

4041
def __init__(self, **kwargs) -> None:
@@ -173,6 +174,14 @@ def after_scenario(context: Context, scenario: Scenario) -> None:
173174
scenario_id = getattr(scenario, "id", "unknown")
174175
logger.info(f"Cleaning up scenario: {scenario.name} (ID: {scenario_id})")
175176

177+
# Clean up Temporal event loop and workers (must happen before context cleanup)
178+
try:
179+
from features.steps.temporal_adapter_steps import cleanup_event_loop
180+
181+
cleanup_event_loop(context)
182+
except Exception:
183+
pass
184+
176185
# Clean up the scenario context and remove from pool
177186
if hasattr(context, "scenario_context_pool"):
178187
context.scenario_context_pool.cleanup_context(scenario_id)

0 commit comments

Comments
 (0)