Skip to content
Draft
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
3cb2022
Prelim work to handle sending invokes
rodrigobr-msft Dec 1, 2025
6e25a90
Reorganizing AgentClient interface
rodrigobr-msft Dec 1, 2025
fd25c32
Removing AutoClient file
rodrigobr-msft Dec 1, 2025
464ecc6
Adding basic stream activity handling for ResponseClient
rodrigobr-msft Dec 1, 2025
d1ec76e
CLI starter
rodrigobr-msft Dec 1, 2025
6c0a56e
Adding input response handling for DDT
rodrigobr-msft Dec 1, 2025
6a57b4c
Adding resolve_env utility function
rodrigobr-msft Dec 1, 2025
943fd71
Fixing test cases
rodrigobr-msft Dec 1, 2025
efc7da2
Handling of InvokeResponse assertions in DataDrivenTest class
rodrigobr-msft Dec 1, 2025
5c77c65
Expect replies tests for DDT
rodrigobr-msft Dec 2, 2025
ecb2c6e
Fixing integration tests
rodrigobr-msft Dec 2, 2025
3048e19
Fixing integration tests with streaming
rodrigobr-msft Dec 2, 2025
930bf09
More adjustments to test cases
rodrigobr-msft Dec 2, 2025
603050d
Adding CopilotClient test case using integration package features
rodrigobr-msft Dec 5, 2025
415264b
Reformatting
rodrigobr-msft Dec 5, 2025
cadb843
CLI grouping
rodrigobr-msft Dec 5, 2025
902d412
Moving ResponseClient to use AiohttpRunner instead of async version
rodrigobr-msft Dec 5, 2025
b373f31
Created DDT cli command
rodrigobr-msft Dec 6, 2025
5329bc4
Adding executor and CLI unit tests
rodrigobr-msft Dec 8, 2025
a37a136
Adding create_payload_sender test
rodrigobr-msft Dec 8, 2025
bcca57f
Implementing auth_test command
rodrigobr-msft Dec 8, 2025
4be418a
Finalizing auth sample from cli
rodrigobr-msft Dec 8, 2025
329fefa
Updating README
rodrigobr-msft Dec 8, 2025
4e8dca3
Reorganizing SDK tests
rodrigobr-msft Dec 8, 2025
61739f4
Revising assertion core
rodrigobr-msft Dec 9, 2025
223a1f8
Selector changes
rodrigobr-msft Dec 9, 2025
fae00e9
Unset and DynamicObject refined
rodrigobr-msft Dec 9, 2025
2d6a9eb
expand function implementation
rodrigobr-msft Dec 9, 2025
a4e3a25
Evaluation implementation steps
rodrigobr-msft Dec 9, 2025
12f9d90
Assertion evaluation fixes
rodrigobr-msft Dec 9, 2025
995d44e
Robust query function handling
rodrigobr-msft Dec 10, 2025
f077099
Adding documentation
rodrigobr-msft Dec 10, 2025
209c56d
Adding AssertionContext tests
rodrigobr-msft Dec 10, 2025
31d5f2f
Assertions tests
rodrigobr-msft Dec 10, 2025
73205e0
Adding basic fixtures
rodrigobr-msft Dec 10, 2025
42846ed
Stable version
rodrigobr-msft Dec 10, 2025
ba50824
Updating old tests
rodrigobr-msft Dec 16, 2025
8dc344c
Merge branch 'main' of https://github.com/microsoft/Agents-for-python…
rodrigobr-msft Dec 16, 2025
ba92b0b
Adding CopilotClient integration test
rodrigobr-msft Dec 16, 2025
66b818c
Quickstart E2E test port
rodrigobr-msft Dec 16, 2025
284ab17
Fixed wrong model query in test case
rodrigobr-msft Dec 16, 2025
a21a856
Added FutureVars
rodrigobr-msft Dec 17, 2025
54c4981
Porting test cases
rodrigobr-msft Jan 5, 2026
16b3366
Fixing implementation of AgentClient.send_invoke_activity
rodrigobr-msft Jan 7, 2026
055367a
Ported webchat and msteams tests
rodrigobr-msft Jan 7, 2026
df11807
Removing test cases in old format
rodrigobr-msft Jan 7, 2026
a70cbe7
Adding docs
rodrigobr-msft Jan 8, 2026
bc55f30
Adding code for quickly running an interactive agent
rodrigobr-msft Jan 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
2 changes: 0 additions & 2 deletions dev/integration/samples/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from .basic_sample import BasicSample
from .quickstart_sample import QuickstartSample

__all__ = [
"BasicSample",
"QuickstartSample",
]
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ test:
name: Test Bot
text: hi 5
locale: en-US
- skip
- type: assertion
quantifier: one
activity:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ test:
name: Bot
text: 'w: What''s the weather in Seattle today?'
locale: en-US
- type: skip
- type: assertion
selector:
index: -1
activity:
attachments:
- contentType: application/vnd.microsoft.card.adaptive
activity:
type: message
attachments:
- contentType: application/vnd.microsoft.card.adaptive
content: ["RE_MATCH", "(�|\\u00B0|Missing temperature inside adaptive card:)"]
- content: ["RE_MATCH", "(�|\\u00B0|Missing temperature inside adaptive card:)"]
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@ test:
name: Bot
text: poem
locale: en-US
- type: skip
# - type: assertion
# selector:
# activity:
# type: typing
# activity:
# text: ["CONTAINS", "Hold on for an awesome poem about Apollo"]
# - type: assertion
# selector:
# index: -1
# activity:
# text: ["CONTAINS", "Apollo"]
# - type: breakpoint
- type: assertion
selector:
activity:
type: typing
quantifier: any
activity:
text: ["CONTAINS", "Hold on for an awesome poem about Apollo"]
- type: assertion
selector:
index: -1
activity:
text: ["CONTAINS", "Apollo"]
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ test:
name: Bot
text: 'w: What''s the weather in Seattle today?'''
locale: en-US
- type: skip
- type: assertion
selector:
index: -1
activity:
attachments:
- contentType: application/vnd.microsoft.card.adaptive
activity:
type: message
attachments:
- contentType: application/vnd.microsoft.card.adaptive
content: ["RE_MATCH", "(�|\\u00B0|Missing temperature inside adaptive card:)"]
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ test:
name: composeExtension/selectItem
locale: en-US
- type: assertion
invokeResponse:
composeExtension:
type: result
text: ["CONTAINS", "Newtonsoft.Json"]
attachments:
contentType: application/vnd.microsoft.card.thumbnail
- type: skip
activity:
type: invoke_response
value:
body:
composeExtension:
type: result
text: ["CONTAINS", "Newtonsoft.Json"]
attachments:
contentType: application/vnd.microsoft.card.thumbnail
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ test:
parameters:
- value: hi`
- type: assertion
invokeResponse:
message: ["EQUALS", "Invoke received."]
status: 200
data:
parameters:
- value: ["CONTAINS", "hi"]
activity:
value:
body:
composeExtension:
message: ["EQUALS", "Invoke received."]
status: 200
data:
parameters:
- value: ["CONTAINS", "hi"]
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,4 @@ test:
selector:
index: -1
activity:
text: ["CONTAINS", "Apollo"]
- type: breakpoint
text: ["CONTAINS", "Apollo"]
11 changes: 8 additions & 3 deletions dev/integration/tests/basic_agent/test_basic_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@
Integration,
)

TEST_BASIC_AGENT = False


@ddt("tests/basic_agent/directline", prefix="directline")
@ddt("tests/basic_agent/webchat", prefix="webchat")
@ddt("tests/basic_agent/msteams", prefix="msteams")
class TestBasicAgent(Integration):
# @ddt("tests/basic_agent/webchat", prefix="webchat")
# @ddt("tests/basic_agent/msteams", prefix="msteams")
@pytest.mark.skipif(
not TEST_BASIC_AGENT, reason="Skipping external agent tests for now."
)
class TestBasicAgentExternal(Integration):
_agent_url = "http://localhost:3978/"
_service_url = "http://localhost:8001/"
_config_path = "agents/basic_agent/python/.env"
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ test:
type: message
activity:
type: message
text: ["CONTAINS", "Welcome to the empty agent!"]
text: ["CONTAINS", "Welcome"]
5 changes: 0 additions & 5 deletions dev/integration/tests/quickstart/test_quickstart_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,3 @@
class TestQuickstartDirectline(Integration):
_sample_cls = QuickstartSample
_environment_cls = AiohttpEnvironment


@ddt("tests/quickstart/directline")
@pytest.mark.skipif(True, reason="Skipping external agent tests for now.")
class TestQuickstartExternalDirectline(Integration): ...
115 changes: 115 additions & 0 deletions dev/integration/tests/test_copilot_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import pytest

from typing import Awaitable, Callable, Iterable

from aiohttp.web import Request, Response, Application, StreamResponse

from microsoft_agents.activity import Activity

from microsoft_agents.copilotstudio.client import (
CopilotClient,
ConnectionSettings,
PowerPlatformEnvironment,
)

from microsoft_agents.testing.integration.core import AiohttpRunner


def mock_mcs_handler(activity: Activity) -> Callable[[Request], Awaitable[Response]]:
"""Creates a mock handler for MCS endpoint returning the given activity."""

async def handler(request: Request) -> Response:
activity_data = activity.model_dump_json(exclude_unset=True)
return Response(body=activity_data)

return handler


Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate function definition. The function mock_mcs_handler is defined twice with different signatures (lines 19-26 and 28-45), which will cause the second definition to override the first. The second definition is the one actually used on line 60. Consider renaming one of them or removing the unused first definition.

Suggested change
def mock_mcs_handler(activity: Activity) -> Callable[[Request], Awaitable[Response]]:
"""Creates a mock handler for MCS endpoint returning the given activity."""
async def handler(request: Request) -> Response:
activity_data = activity.model_dump_json(exclude_unset=True)
return Response(body=activity_data)
return handler

Copilot uses AI. Check for mistakes.
def mock_mcs_handler(
activities: Iterable[Activity],
) -> Callable[[Request], Awaitable[StreamResponse]]:
"""Creates a mock handler for MCS endpoint returning SSE-formatted activity."""

async def handler(request: Request) -> StreamResponse:
response = StreamResponse(status=200)
response.headers["Content-Type"] = "text/event-stream"
response.headers["x-ms-conversationid"] = "test-conv-id"
# response.headers['Content-Length'] = str(len(activity_data))
await response.prepare(request)

# Proper SSE format
for activity in activities:
activity_data = activity.model_dump_json(exclude_unset=True)
await response.write(b"event: activity\n")
await response.write(f"data: {activity_data}\n\n".encode("utf-8"))

await response.write_eof()
return response

return handler
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function mock_mcs_handler is defined twice with different signatures. The first definition (line 18) returns Callable[[Request], Awaitable[Response]] and takes a single activity: Activity, while the second definition (line 28) returns Callable[[Request], Awaitable[StreamResponse]] and takes activities: Iterable[Activity]. This will cause the first definition to be overwritten. Consider renaming one of these functions, such as mock_mcs_single_handler and mock_mcs_stream_handler.

Copilot uses AI. Check for mistakes.


def mock_mcs_endpoint(
mocker, activities: Iterable[Activity], path: str, port: int
) -> AiohttpRunner:
"""Mock MCS responses for testing."""

PowerPlatformEnvironment.get_copilot_studio_connection_url = mocker.MagicMock(
return_value=f"http://localhost:{port}{path}"
)

app = Application()
app.router.add_post(path, mock_mcs_handler(activities))

return AiohttpRunner(app, port=port)


@pytest.mark.asyncio
async def test_start_conversation_and_ask_question_large_message(mocker):

activity = Activity(
type="message", text="*" * 1_000_000, conversation={"id": "conv-id"}
)

runner = mock_mcs_endpoint(mocker, [activity], "/mcs-endpoint", port=8081)

async with runner:
settings = ConnectionSettings("environment-id", "agent-id")
client = CopilotClient(settings=settings, token="test-token")

with pytest.raises(Exception, match="Chunk too big"):
async for conv_activity in client.start_conversation():
assert conv_activity.type == "message"

# with pytest.raises(Exception, match="Chunk too big"):
# async for question_activity in client.ask_question("Hello!", "conv-id"):
# assert question_activity.type == "message"


def activity_generator(activity: Activity, n: int) -> Iterable[Activity]:
for i in range(n):
yield activity


@pytest.mark.asyncio
async def test_start_conversation_many(mocker):

activity = Activity(type="message", conversation={"id": "conv-id"})
activities = activity_generator(activity, 100_000)

runner = mock_mcs_endpoint(mocker, activities, "/mcs-endpoint", port=8081)

async with runner:
settings = ConnectionSettings("environment-id", "agent-id")
client = CopilotClient(settings=settings, token="test-token")

for i in range(100):
# try:
async for conv_activity in client.start_conversation():
assert conv_activity.type == "message"
# except Exception as e:
# assert str(e) == "Chunk too big"

# with pytest.raises(Exception, match="Chunk too big"):
# async for question_activity in client.ask_question("Hello!", "conv-id"):
# assert question_activity.type == "message"
22 changes: 6 additions & 16 deletions dev/integration/tests/test_expect_replies.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import pytest
import logging

from microsoft_agents.activity import Activity

Expand All @@ -9,20 +8,11 @@
AiohttpEnvironment,
)

from ..samples import BasicSample
from ..samples import QuickstartSample


class BasicSampleWithLogging(BasicSample):

async def init_app(self):

logging.getLogger("microsoft_agents").setLevel(logging.DEBUG)

await super().init_app()


class TestBasicDirectline(Integration):
_sample_cls = BasicSampleWithLogging
class TestExpectReplies(Integration):
_sample_cls = QuickstartSample
_environment_cls = AiohttpEnvironment

@pytest.mark.asyncio
Expand All @@ -36,12 +26,12 @@ async def test_expect_replies_without_service_url(
conversation={"id": "conv-id"},
channel_id="test",
from_property={"id": "from-id"},
to={"id": "to-id"},
recipient={"id": "to-id"},
delivery_mode="expectReplies",
locale="en-US",
)

res = await agent_client.send_expect_replies(activity)

breakpoint()
res = Activity.model_validate(res)
assert len(res) > 0
assert isinstance(res[0], Activity)
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from .assertions import (
ModelAssertion,
Selector,
ModelSelector,
AssertionQuantifier,
assert_model,
assert_field,
Expand All @@ -31,6 +31,8 @@
DataDrivenTest,
)

from .cli import cli
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module cli is being imported and exported in __init__.py, but cli.py is an empty file. This will cause the import to succeed but cli will be an empty module object with no useful functionality. Either implement the CLI functionality in this file or remove the import/export from __init__.py.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing from .cli which is an empty file (cli.py has 0 lines). This will cause an ImportError when the module is imported. The cli module needs to be properly defined or this import should be removed.

Copilot uses AI. Check for mistakes.

__all__ = [
"SDKConfig",
"generate_token",
Expand All @@ -45,7 +47,7 @@
"populate_activity",
"get_host_and_port",
"ModelAssertion",
"Selector",
"ModelSelector",
"AssertionQuantifier",
"assert_model",
"assert_field",
Expand All @@ -56,4 +58,5 @@
"FieldAssertionType",
"ddt",
"DataDrivenTest",
"cli",
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exporting cli in __all__ but it's imported from an empty module. This will cause an ImportError. Either implement the cli module or remove this export.

Copilot uses AI. Check for mistakes.
]
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
or (a is UNSET_FIELD and b is not None),
FieldAssertionType.GREATER_THAN: lambda a, b: a > b,
FieldAssertionType.LESS_THAN: lambda a, b: a < b,
FieldAssertionType.CONTAINS: lambda a, b: b in a,
FieldAssertionType.NOT_CONTAINS: lambda a, b: b not in a,
FieldAssertionType.CONTAINS: lambda a, b: b in a if a is not UNSET_FIELD else False,
FieldAssertionType.NOT_CONTAINS: lambda a, b: (
b not in a if a is not UNSET_FIELD else True
),
FieldAssertionType.RE_MATCH: lambda a, b: re.match(b, a) is not None,
}

Expand Down
Loading