Ctrl K
ComunidadGuías…

test/a2a/test_terminal_states.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Copyright (c) 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from uuid import uuid4

import pytest
from a2a.server.agent_execution import AgentExecutor as A2AAgentExecutorBase
from a2a.server.agent_execution import RequestContext
from a2a.server.events import EventQueue
from a2a.server.tasks import TaskUpdater
from a2a.types import Task, TaskState, TaskStatus

from ag2.a2a.errors import (
    A2ATaskAuthRequiredError,
    A2ATaskFailedError,
    A2ATaskRejectedError,
    A2ATaskTerminalError,
)

from ._helpers import make_executor_pair

_Finish = Callable[[TaskUpdater], Awaitable[None]]


@dataclass(slots=True)
class _TerminalExecutor(A2AAgentExecutorBase):
    """Drives a fresh task straight to one terminal state.

    ``finish`` is the ``TaskUpdater`` transition that ends the task — the
    only thing that differs between the states under test, so the rest of
    the choreography stays in one place.
    """

    finish: _Finish

    async def execute(self, request_context: RequestContext, event_queue: EventQueue) -> None:
        msg = request_context.message
        assert msg is not None
        task_id = msg.task_id or uuid4().hex
        context_id = msg.context_id or uuid4().hex
        await event_queue.enqueue_event(
            Task(id=task_id, context_id=context_id, status=TaskStatus(state=TaskState.TASK_STATE_SUBMITTED)),
        )
        updater = TaskUpdater(event_queue, task_id, context_id)
        await updater.start_work()
        await self.finish(updater)

    async def cancel(self, request_context: RequestContext, event_queue: EventQueue) -> None:
        return None


@pytest.mark.asyncio
@pytest.mark.parametrize("streaming", [True, False], ids=["streaming", "polling"])
@pytest.mark.parametrize(
    ("finish", "error", "state"),
    [
        pytest.param(
            lambda updater: updater.failed(),
            A2ATaskFailedError,
            TaskState.TASK_STATE_FAILED,
            id="failed",
        ),
        pytest.param(
            lambda updater: updater.reject(),
            A2ATaskRejectedError,
            TaskState.TASK_STATE_REJECTED,
            id="rejected",
        ),
        pytest.param(
            lambda updater: updater.requires_auth(),
            A2ATaskAuthRequiredError,
            TaskState.TASK_STATE_AUTH_REQUIRED,
            id="auth-required",
        ),
    ],
)
async def test_terminal_state_raises_and_carries_the_task(
    finish: _Finish,
    error: type[A2ATaskTerminalError],
    state: int,
    streaming: bool,
) -> None:
    pair = make_executor_pair(_TerminalExecutor(finish), streaming=streaming)

    with pytest.raises(error) as exc_info:
        await pair.client.ask("ping")

    # The error carries the terminal Task, so callers can inspect why it ended.
    assert exc_info.value.task.status.state == state