Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
cfb8847
basic integration utility
rodrigobr-msft Oct 21, 2025
4d93a46
Integration test suite factory implementation
rodrigobr-msft Oct 23, 2025
be2fb1c
Implementing core models for integration testing
rodrigobr-msft Oct 23, 2025
0d5539a
AutoClient mockup
rodrigobr-msft Oct 23, 2025
bf4c006
Adding runner starter code
rodrigobr-msft Oct 23, 2025
ebe622d
Adding foundational classes
rodrigobr-msft Oct 25, 2025
45678e9
Drafting AgentClient and ResponseClient implementations
rodrigobr-msft Oct 27, 2025
c2b84cb
Spec test
rodrigobr-msft Oct 27, 2025
2816e68
Filling in more implementation details
rodrigobr-msft Oct 27, 2025
2214827
More files
rodrigobr-msft Oct 27, 2025
c90c8e4
Cleaning up implementations
rodrigobr-msft Oct 29, 2025
36f5c3b
Adding expect replies sending method
rodrigobr-msft Oct 29, 2025
dfdd959
Beginning unit tests
rodrigobr-msft Oct 30, 2025
d4828fb
Adding integration decor from sample test cases
rodrigobr-msft Oct 30, 2025
91b3c44
Integration from service url tests
rodrigobr-msft Oct 31, 2025
1eefe44
_handle_conversation implementation for response_client
rodrigobr-msft Oct 31, 2025
a06da9a
AgentClient tests completed
rodrigobr-msft Oct 31, 2025
d36ec7a
Creating response client tests
rodrigobr-msft Nov 3, 2025
c096048
Hosting server for response client
rodrigobr-msft Nov 3, 2025
5451ddf
Response client tests completed
rodrigobr-msft Nov 3, 2025
c2cdd40
Beginning refactor of aiohttp runner
rodrigobr-msft Nov 3, 2025
7d91ef9
Unit test updates
rodrigobr-msft Nov 3, 2025
87610a5
Fixing issues in refactor
rodrigobr-msft Nov 3, 2025
ddfdd0b
Fixed TestIntegrationFromSample unit test
rodrigobr-msft Nov 3, 2025
dd0a87d
Another commit
rodrigobr-msft Nov 3, 2025
6a4e8ef
Reorganizing files
rodrigobr-msft Nov 3, 2025
7793790
Completed TestIntegrationFromServiceUrl unit tests
rodrigobr-msft Nov 3, 2025
3ebbd44
Reformatting with black
rodrigobr-msft Nov 3, 2025
a6681bf
Quickstart integration test beginning
rodrigobr-msft Nov 3, 2025
1a7264b
Draft of quickstart sample setup
rodrigobr-msft Nov 3, 2025
9ba4a43
Environment config connection
rodrigobr-msft Nov 3, 2025
fab4368
Renaming messaging endpoint to agent url
rodrigobr-msft Nov 4, 2025
a111155
Removing unnecessary files
rodrigobr-msft Nov 4, 2025
a2a1092
Fixing agent client payload sending
rodrigobr-msft Nov 4, 2025
ae8a286
First successful integration test
rodrigobr-msft Nov 4, 2025
9a5a6a8
Beginning foundational test cases
rodrigobr-msft Nov 4, 2025
aad011a
TypingIndicator test
rodrigobr-msft Nov 4, 2025
be0032a
Adding more test cases
rodrigobr-msft Nov 4, 2025
e3f4324
More foundational integration test cases
rodrigobr-msft Nov 4, 2025
a077eed
Reorganizing testing tools into package
rodrigobr-msft Nov 5, 2025
49161c3
Polished the testing framework package
rodrigobr-msft Nov 6, 2025
69d68d4
Adding verbose logging for results with benchmark tool
rodrigobr-msft Nov 6, 2025
3dc3efa
General cleanup
rodrigobr-msft Nov 6, 2025
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
6 changes: 0 additions & 6 deletions dev/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +0,0 @@
This directory contains tools to aid the developers of the Microsoft 365 Agents SDK for Python.

### `benchmark`

This folder contains benchmarking utilities built in Python to send concurrent requests
to an agent.
Empty file added dev/benchmark/__init__.py
Empty file.
3 changes: 2 additions & 1 deletion dev/benchmark/payload.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
"id": "user-id-0",
"aadObjectId": "00000000-0000-0000-0000-0000000000020"
},
"type": "message"
"type": "message",
"text": "Hello, Bot!"
}
13 changes: 9 additions & 4 deletions dev/benchmark/src/main.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
import json
import json, sys
from io import StringIO
Comment on lines +1 to +2
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

Unused imports. The sys and StringIO imports are not used in the code. Remove them.

Suggested change
import json, sys
from io import StringIO
import json

Copilot uses AI. Check for mistakes.
import logging
from datetime import datetime, timezone
from contextlib import contextmanager
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

Unused import. The contextmanager decorator is imported but never used. Remove this import statement.

Suggested change
from contextlib import contextmanager

Copilot uses AI. Check for mistakes.

import click

from .payload_sender import create_payload_sender
from .executor import Executor, CoroutineExecutor, ThreadExecutor
from .aggregated_results import AggregatedResults
from .config import BenchmarkConfig
from .output import output_results

LOG_FORMAT = "%(asctime)s: %(message)s"
logging.basicConfig(format=LOG_FORMAT, level=logging.INFO, datefmt="%H:%M:%S")

BenchmarkConfig.load_from_env()


@click.command()
@click.option(
"--payload_path", "-p", default="./payload.json", help="Path to the payload file."
)
@click.option("--num_workers", "-n", default=1, help="Number of workers to use.")
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging.")
@click.option(
"--async_mode",
"-a",
is_flag=True,
help="Run coroutine workers rather than thread workers.",
)
def main(payload_path: str, num_workers: int, async_mode: bool):
def main(payload_path: str, num_workers: int, verbose: bool, async_mode: bool):
"""Main function to run the benchmark."""

with open(payload_path, "r", encoding="utf-8") as f:
Expand All @@ -35,10 +38,12 @@ def main(payload_path: str, num_workers: int, async_mode: bool):
func = create_payload_sender(payload)

executor: Executor = CoroutineExecutor() if async_mode else ThreadExecutor()

start_time = datetime.now(timezone.utc).timestamp()
results = executor.run(func, num_workers=num_workers)
end_time = datetime.now(timezone.utc).timestamp()
if verbose:
output_results(results)

agg = AggregatedResults(results)
agg.display(start_time, end_time)
Expand Down
9 changes: 9 additions & 0 deletions dev/benchmark/src/output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .executor import ExecutionResult

def output_results(results: list[ExecutionResult]) -> None:
"""Output the results of the benchmark to the console."""

for result in results:
status = "Success" if result.success else "Failure"
print(f"Execution ID: {result.exe_id}, Duration: {result.duration:.4f} seconds, Status: {status}")
print(result.result)
1 change: 1 addition & 0 deletions dev/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pip install -e ./microsoft-agents-testing/ --config-settings editable_mode=compat
1 change: 1 addition & 0 deletions dev/microsoft-agents-testing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Microsoft 365 Agents SDK for Python - Testing Framework
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from .sdk_config import SDKConfig

from .auth import (
generate_token,
generate_token_from_config
)

from .utils import (
populate_activity,
get_host_and_port
)

from .integration import (
Sample,
Environment,
ApplicationRunner,
AgentClient,
ResponseClient,
AiohttpEnvironment,
Integration
)

__all__ = [
"SDKConfig",
"generate_token",
"generate_token_from_config",
"Sample",
"Environment",
"ApplicationRunner",
"AgentClient",
"ResponseClient",
"AiohttpEnvironment",
"Integration",
"populate_activity",
"get_host_and_port"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .generate_token import generate_token, generate_token_from_config

__all__ = [
"generate_token",
"generate_token_from_config"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import requests

from microsoft_agents.hosting.core import AgentAuthConfiguration
from microsoft_agents.testing.sdk_config import SDKConfig

def generate_token(app_id: str, app_secret: str, tenant_id: str) -> str:
"""Generate a token using the provided app credentials.

:param app_id: Application (client) ID.
:param app_secret: Application client secret.
:param tenant_id: Directory (tenant) ID.
:return: Generated access token as a string.
"""

authority_endpoint = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"

res = requests.post(
authority_endpoint,
headers={
"Content-Type": "application/x-www-form-urlencoded",
},
data={
"grant_type": "client_credentials",
"client_id": app_id,
"client_secret": app_secret,
"scope": f"{app_id}/.default",
},
timeout=10,
)
return res.json().get("access_token")

def generate_token_from_config(sdk_config: SDKConfig) -> str:
"""Generates a token using a provided config object.

:param config: Configuration dictionary containing connection settings.
:return: Generated access token as a string.
"""

settings: AgentAuthConfiguration = sdk_config.get_connection()

app_id = settings.CLIENT_ID
app_secret = settings.CLIENT_SECRET
tenant_id = settings.TENANT_ID

if not app_id or not app_secret or not tenant_id:
raise ValueError("Incorrect configuration provided for token generation.")
return generate_token(app_id, app_secret, tenant_id)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from .core import (
AgentClient,
ApplicationRunner,
AiohttpEnvironment,
ResponseClient,
Environment,
Integration,
Sample,
)

__all__ = [
"AgentClient",
"ApplicationRunner",
"AiohttpEnvironment",
"ResponseClient",
"Environment",
"Integration",
"Sample",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from .application_runner import ApplicationRunner
from .aiohttp import AiohttpEnvironment
from .client import (
AgentClient,
ResponseClient,
)
from .environment import Environment
from .integration import Integration
from .sample import Sample


__all__ = [
"AgentClient",
"ApplicationRunner",
"AiohttpEnvironment",
"ResponseClient",
"Environment",
"Integration",
"Sample",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .aiohttp_environment import AiohttpEnvironment
from .aiohttp_runner import AiohttpRunner

__all__ = ["AiohttpEnvironment", "AiohttpRunner"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from tkinter import E
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

Unused import. The E class from tkinter is imported but never used in this module. Remove this import statement.

Suggested change
from tkinter import E

Copilot uses AI. Check for mistakes.
from aiohttp.web import Request, Response, Application, run_app
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

Import of 'run_app' is not used.

Suggested change
from aiohttp.web import Request, Response, Application, run_app
from aiohttp.web import Request, Response, Application

Copilot uses AI. Check for mistakes.

from microsoft_agents.hosting.aiohttp import (
CloudAdapter,
jwt_authorization_middleware,
start_agent_process,
)
from microsoft_agents.hosting.core import (
Authorization,
AgentApplication,
TurnState,
MemoryStorage,
)
from microsoft_agents.authentication.msal import MsalConnectionManager
from microsoft_agents.activity import load_configuration_from_env

from ..application_runner import ApplicationRunner
from ..environment import Environment
from .aiohttp_runner import AiohttpRunner


class AiohttpEnvironment(Environment):
"""An environment for aiohttp-hosted agents."""

async def init_env(self, environ_config: dict) -> None:
environ_config = environ_config or {}

self.config = load_configuration_from_env(environ_config)

self.storage = MemoryStorage()
self.connection_manager = MsalConnectionManager(**self.config)
self.adapter = CloudAdapter(connection_manager=self.connection_manager)
self.authorization = Authorization(
self.storage, self.connection_manager, **self.config
)

self.agent_application = AgentApplication[TurnState](
storage=self.storage,
adapter=self.adapter,
authorization=self.authorization,
**self.config
)

def create_runner(self, host: str, port: int) -> ApplicationRunner:
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

This method requires 3 positional arguments, whereas overridden Environment.create_runner requires 1.

Suggested change
def create_runner(self, host: str, port: int) -> ApplicationRunner:
def create_runner(self, **kwargs) -> ApplicationRunner:
host = kwargs.get("host", "127.0.0.1")
port = kwargs.get("port", 8080)

Copilot uses AI. Check for mistakes.

async def entry_point(req: Request) -> Response:
agent: AgentApplication = req.app["agent_app"]
adapter: CloudAdapter = req.app["adapter"]
return await start_agent_process(req, agent, adapter)

APP = Application(middlewares=[jwt_authorization_middleware])
APP.router.add_post("/api/messages", entry_point)
APP["agent_configuration"] = self.connection_manager.get_default_connection_configuration()
APP["agent_app"] = self.agent_application
APP["adapter"] = self.adapter

return AiohttpRunner(APP, host, port)
Loading