Skip to content

Commit e22b878

Browse files
add tests
1 parent 97612e7 commit e22b878

5 files changed

Lines changed: 121 additions & 0 deletions

File tree

tests/observability/hosting/test_populate_baggage.py renamed to tests/observability/hosting/scope_helpers/test_populate_baggage.py

File renamed without changes.

tests/observability/hosting/test_populate_invoke_agent_scope.py renamed to tests/observability/hosting/scope_helpers/test_populate_invoke_agent_scope.py

File renamed without changes.
File renamed without changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
"""Tests for AgenticTokenCache and AgenticTokenStruct."""
5+
6+
from unittest.mock import AsyncMock, MagicMock
7+
8+
import pytest
9+
from microsoft_agents.hosting.core.app.oauth.authorization import Authorization
10+
from microsoft_agents.hosting.core.turn_context import TurnContext
11+
from microsoft_agents_a365.observability.hosting.token_cache_helpers import (
12+
AgenticTokenCache,
13+
AgenticTokenStruct,
14+
)
15+
16+
17+
@pytest.fixture
18+
def mock_authorization():
19+
"""Create a mock Authorization instance."""
20+
auth = MagicMock(spec=Authorization)
21+
auth.exchange_token = AsyncMock()
22+
return auth
23+
24+
25+
@pytest.fixture
26+
def mock_turn_context():
27+
"""Create a mock TurnContext instance."""
28+
return MagicMock(spec=TurnContext)
29+
30+
31+
@pytest.fixture
32+
def token_cache():
33+
"""Create a fresh AgenticTokenCache instance."""
34+
return AgenticTokenCache()
35+
36+
37+
@pytest.mark.asyncio
38+
async def test_register_and_retrieve_token_success(
39+
token_cache, mock_authorization, mock_turn_context
40+
):
41+
"""Test complete flow: create struct, register, and retrieve token successfully."""
42+
agent_id = "agent-123"
43+
tenant_id = "tenant-456"
44+
expected_token = "mock-token-xyz"
45+
scopes = ["https://example.com/.default"]
46+
47+
# Setup mock
48+
mock_authorization.exchange_token.return_value = expected_token
49+
50+
# Create struct with default auth handler
51+
token_struct = AgenticTokenStruct(
52+
authorization=mock_authorization,
53+
turn_context=mock_turn_context,
54+
)
55+
assert token_struct.auth_handler_name == "AGENTIC"
56+
57+
# Register
58+
token_cache.register_observability(
59+
agent_id=agent_id,
60+
tenant_id=tenant_id,
61+
token_generator=token_struct,
62+
observability_scopes=scopes,
63+
)
64+
65+
# Retrieve token
66+
token = await token_cache.get_observability_token(agent_id, tenant_id)
67+
68+
assert token == expected_token
69+
mock_authorization.exchange_token.assert_called_once_with(
70+
context=mock_turn_context,
71+
scopes=scopes,
72+
auth_handler_id="AGENTIC",
73+
)
74+
75+
76+
@pytest.mark.parametrize(
77+
"agent_id,tenant_id,token_generator,error_type,error_match",
78+
[
79+
("", "tenant-456", "valid", ValueError, "agent_id cannot be None or whitespace"),
80+
("agent-123", None, "valid", ValueError, "tenant_id cannot be None or whitespace"),
81+
("agent-123", "tenant-456", None, TypeError, "token_generator cannot be None"),
82+
],
83+
)
84+
@pytest.mark.asyncio
85+
def test_thread_safety(token_cache, mock_authorization, mock_turn_context):
86+
"""Test that cache is thread-safe with concurrent registrations."""
87+
import threading
88+
89+
agent_id = "agent-123"
90+
tenant_id = "tenant-456"
91+
results = []
92+
93+
def register_token(scope_suffix):
94+
try:
95+
struct = AgenticTokenStruct(
96+
authorization=mock_authorization,
97+
turn_context=mock_turn_context,
98+
)
99+
token_cache.register_observability(
100+
agent_id=agent_id,
101+
tenant_id=tenant_id,
102+
token_generator=struct,
103+
observability_scopes=[f"scope-{scope_suffix}"],
104+
)
105+
results.append(scope_suffix)
106+
except Exception as e:
107+
results.append(f"error: {e}")
108+
109+
# Create 10 concurrent registrations
110+
threads = [threading.Thread(target=register_token, args=(i,)) for i in range(10)]
111+
for thread in threads:
112+
thread.start()
113+
for thread in threads:
114+
thread.join()
115+
116+
# All registrations should succeed
117+
assert len(results) == 10
118+
# Only one entry should exist (idempotent)
119+
assert f"{agent_id}:{tenant_id}" in token_cache._map

0 commit comments

Comments
 (0)