Skip to content

Commit d8c1e4e

Browse files
committed
Resync tests
1 parent 631185d commit d8c1e4e

3 files changed

Lines changed: 312 additions & 15 deletions

File tree

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,24 @@
44
import unittest
55
from unittest.mock import Mock, patch
66

7+
from microsoft_agents_a365.observability.core.constants import GEN_AI_AGENT_ID_KEY, TENANT_ID_KEY
8+
from microsoft_agents_a365.observability.core.exporters.agent365_exporter import (
9+
Agent365Exporter,
10+
)
711
from opentelemetry.sdk.trace import ReadableSpan
812
from opentelemetry.sdk.trace.export import SpanExportResult
913
from opentelemetry.trace import StatusCode
1014
from opentelemetry.util.types import Attributes
1115

12-
from microsoft_agents_a365.observability.core.constants import GEN_AI_AGENT_ID_KEY, TENANT_ID_KEY
13-
from microsoft_agents_a365.observability.core.exporters.kairo_exporter import (
14-
KairoExporter,
15-
)
16-
1716

18-
class TestKairoExporter(unittest.TestCase):
17+
class TestAgent365Exporter(unittest.TestCase):
1918
def setUp(self):
2019
"""Set up test fixtures."""
2120
self.mock_token_resolver = Mock()
2221
self.mock_token_resolver.return_value = "test_token_123"
2322

2423
# Don't patch the class in setUp, do it per test
25-
self.exporter = KairoExporter(
24+
self.exporter = Agent365Exporter(
2625
token_resolver=self.mock_token_resolver, cluster_category="test"
2726
)
2827

@@ -95,7 +94,7 @@ def test_export_success(self):
9594

9695
# Mock the PowerPlatformApiDiscovery class that gets created inside export()
9796
with patch(
98-
"microsoft_agents_a365.observability.core.exporters.kairo_exporter.PowerPlatformApiDiscovery"
97+
"microsoft_agents_a365.observability.core.exporters.agent365_exporter.PowerPlatformApiDiscovery"
9998
) as mock_discovery_class:
10099
mock_discovery = Mock()
101100
mock_discovery.get_tenant_island_cluster_endpoint.return_value = "test-endpoint.com"
@@ -135,7 +134,7 @@ def test_export_failure_with_retries(self):
135134

136135
# Mock the PowerPlatformApiDiscovery class
137136
with patch(
138-
"microsoft_agents_a365.observability.core.exporters.kairo_exporter.PowerPlatformApiDiscovery"
137+
"microsoft_agents_a365.observability.core.exporters.agent365_exporter.PowerPlatformApiDiscovery"
139138
) as mock_discovery_class:
140139
mock_discovery = Mock()
141140
mock_discovery.get_tenant_island_cluster_endpoint.return_value = "test-endpoint.com"
@@ -170,7 +169,7 @@ def test_partitioning_by_scope(self):
170169

171170
# Mock the PowerPlatformApiDiscovery class
172171
with patch(
173-
"microsoft_agents_a365.observability.core.exporters.kairo_exporter.PowerPlatformApiDiscovery"
172+
"microsoft_agents_a365.observability.core.exporters.agent365_exporter.PowerPlatformApiDiscovery"
174173
) as mock_discovery_class:
175174
mock_discovery = Mock()
176175
mock_discovery.get_tenant_island_cluster_endpoint.return_value = "test-endpoint.com"

tests/test_baggage_builder.py

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@
44
import os
55
import unittest
66

7-
from opentelemetry import baggage, context, trace
8-
from opentelemetry.sdk.trace import TracerProvider
9-
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
10-
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
11-
127
from microsoft_agents_a365.observability.core import config as telemetry_config
138
from microsoft_agents_a365.observability.core.constants import (
149
CORRELATION_ID_KEY,
@@ -22,6 +17,10 @@
2217
TENANT_ID_KEY,
2318
)
2419
from microsoft_agents_a365.observability.core.middleware.baggage_builder import BaggageBuilder
20+
from opentelemetry import baggage, context, trace
21+
from opentelemetry.sdk.trace import TracerProvider
22+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
23+
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
2524

2625

2726
class TestBaggageBuilder(unittest.TestCase):
@@ -220,6 +219,90 @@ def test_baggage_reset_after_scope_exit(self):
220219

221220
print("✅ All baggage values are properly reset after scope exit!")
222221

222+
def test_set_pairs_accepts_dict_and_iterable(self):
223+
"""set_pairs should accept both dict and iterable[(k,v)] and apply them to baggage."""
224+
dict_pairs = {
225+
TENANT_ID_KEY: "tenant-x",
226+
GEN_AI_AGENT_ID_KEY: "agent-x",
227+
CORRELATION_ID_KEY: "corr-x",
228+
}
229+
iter_pairs = [
230+
(GEN_AI_AGENT_AUID_KEY, "auid-x"),
231+
(GEN_AI_AGENT_UPN_KEY, "upn-x"),
232+
]
233+
234+
# Also verify that None / whitespace values are ignored
235+
dict_pairs_with_ignored = {
236+
OPERATION_SOURCE_KEY: "sdk",
237+
GEN_AI_CALLER_ID_KEY: None, # ignored
238+
}
239+
iter_pairs_with_ignored = [
240+
(HIRING_MANAGER_ID_KEY, " "), # ignored (whitespace)
241+
]
242+
243+
with (
244+
BaggageBuilder()
245+
.set_pairs(dict_pairs)
246+
.set_pairs(iter_pairs)
247+
.set_pairs(dict_pairs_with_ignored)
248+
.set_pairs(iter_pairs_with_ignored)
249+
.build()
250+
):
251+
baggage_contents = baggage.get_all()
252+
self.assertEqual(baggage_contents.get(TENANT_ID_KEY), "tenant-x")
253+
self.assertEqual(baggage_contents.get(GEN_AI_AGENT_ID_KEY), "agent-x")
254+
self.assertEqual(baggage_contents.get(CORRELATION_ID_KEY), "corr-x")
255+
self.assertEqual(baggage_contents.get(GEN_AI_AGENT_AUID_KEY), "auid-x")
256+
self.assertEqual(baggage_contents.get(GEN_AI_AGENT_UPN_KEY), "upn-x")
257+
self.assertEqual(baggage_contents.get(OPERATION_SOURCE_KEY), "sdk")
258+
# Ignored values should not be present
259+
self.assertIsNone(baggage_contents.get(GEN_AI_CALLER_ID_KEY))
260+
self.assertIsNone(baggage_contents.get(HIRING_MANAGER_ID_KEY))
261+
262+
def test_from_turn_context_delegates_and_merges(self):
263+
"""from_turn_context should delegate to baggage_turn_context.from_turn_context and merge returned pairs."""
264+
# Import the module to monkeypatch the symbol that BaggageBuilder closed over
265+
from microsoft_agents_a365.observability.core.middleware import (
266+
baggage_builder as tempBaggageBuilder,
267+
)
268+
269+
original_fn = tempBaggageBuilder.from_turn_context
270+
271+
# Fake turn_context -> returns a mix of valid/ignored values
272+
def fake_from_turn_context(turn_ctx: any):
273+
self.assertEqual(turn_ctx, {"k": "v"}) # ensure pass-through of arg
274+
return {
275+
TENANT_ID_KEY: "tenant-ctx",
276+
GEN_AI_AGENT_ID_KEY: "agent-ctx",
277+
CORRELATION_ID_KEY: " ", # will be ignored
278+
GEN_AI_AGENT_UPN_KEY: None, # will be ignored
279+
OPERATION_SOURCE_KEY: "sdk-ctx",
280+
}
281+
282+
try:
283+
tempBaggageBuilder.from_turn_context = fake_from_turn_context
284+
285+
with (
286+
BaggageBuilder()
287+
.tenant_id("tenant-pre") # will be overridden if same key is set later
288+
.from_turn_context({"k": "v"}) # merges keys from fake function
289+
.agent_auid("auid-pre") # ensure pre-existing values remain
290+
.build()
291+
):
292+
baggage_contents = baggage.get_all()
293+
# Values from turn_context
294+
self.assertEqual(baggage_contents.get(TENANT_ID_KEY), "tenant-ctx")
295+
self.assertEqual(baggage_contents.get(GEN_AI_AGENT_ID_KEY), "agent-ctx")
296+
self.assertEqual(baggage_contents.get(OPERATION_SOURCE_KEY), "sdk-ctx")
297+
# Pre-existing (non-overlapping) still present
298+
self.assertEqual(baggage_contents.get(GEN_AI_AGENT_AUID_KEY), "auid-pre")
299+
# Ignored values should not be present
300+
self.assertIsNone(baggage_contents.get(CORRELATION_ID_KEY))
301+
self.assertIsNone(baggage_contents.get(GEN_AI_AGENT_UPN_KEY))
302+
finally:
303+
# Restore original
304+
tempBaggageBuilder.from_turn_context = original_fn
305+
223306

224307
if __name__ == "__main__":
225308
unittest.main()

tests/test_turn_context_baggage.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import unittest
2+
3+
from microsoft_agents_a365.observability.core.constants import (
4+
GEN_AI_AGENT_AUID_KEY,
5+
GEN_AI_AGENT_DESCRIPTION_KEY,
6+
GEN_AI_AGENT_ID_KEY,
7+
GEN_AI_AGENT_NAME_KEY,
8+
GEN_AI_AGENT_UPN_KEY,
9+
GEN_AI_CALLER_ID_KEY,
10+
GEN_AI_CALLER_NAME_KEY,
11+
GEN_AI_CALLER_TENANT_ID_KEY,
12+
GEN_AI_CALLER_UPN_KEY,
13+
GEN_AI_CONVERSATION_ID_KEY,
14+
GEN_AI_CONVERSATION_ITEM_LINK_KEY,
15+
GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY,
16+
GEN_AI_EXECUTION_SOURCE_ID_KEY,
17+
GEN_AI_EXECUTION_SOURCE_NAME_KEY,
18+
GEN_AI_EXECUTION_TYPE_KEY,
19+
TENANT_ID_KEY,
20+
)
21+
from microsoft_agents_a365.observability.core.execution_type import ExecutionType
22+
from microsoft_agents_a365.observability.core.middleware import turn_context_baggage as tcb
23+
24+
25+
class FakeEntity:
26+
def __init__(self, **kwargs):
27+
for k, v in kwargs.items():
28+
setattr(self, k, v)
29+
30+
31+
class FakeActivity:
32+
def __init__(self, **kwargs):
33+
for k, v in kwargs.items():
34+
setattr(self, k, v)
35+
36+
37+
class FakeTurnContext:
38+
def __init__(self, activity):
39+
self.activity = activity
40+
41+
42+
class TestTurnContextBaggage(unittest.TestCase):
43+
def test_iter_caller_pairs_basic_and_upn_fallback(self):
44+
activity = FakeActivity(**{
45+
"from": FakeEntity(id="caller-123", name="Alice", tenant_id="tenant-xyz"),
46+
})
47+
pairs = dict(tcb._iter_caller_pairs(activity))
48+
self.assertEqual(pairs[GEN_AI_CALLER_ID_KEY], "caller-123")
49+
self.assertEqual(pairs[GEN_AI_CALLER_NAME_KEY], "Alice")
50+
# upn missing, should fallback to name
51+
self.assertEqual(pairs[GEN_AI_CALLER_UPN_KEY], "Alice")
52+
# user id may be absent -> None (not included if None)
53+
self.assertIn(GEN_AI_CALLER_TENANT_ID_KEY, pairs)
54+
self.assertEqual(pairs[GEN_AI_CALLER_TENANT_ID_KEY], "tenant-xyz")
55+
56+
def test_iter_execution_type_agent_to_agent(self):
57+
activity = FakeActivity(**{
58+
"from": FakeEntity(role="agenticUser", agentic_user_id="u1"),
59+
"recipient": FakeEntity(role="agenticUser", agentic_user_id="u2"),
60+
})
61+
pairs = dict(tcb._iter_execution_type_pair(activity))
62+
self.assertEqual(pairs[GEN_AI_EXECUTION_TYPE_KEY], ExecutionType.AGENT_TO_AGENT.value)
63+
64+
def test_iter_execution_type_human_to_agent(self):
65+
activity = FakeActivity(**{
66+
"from": FakeEntity(role="user"),
67+
"recipient": FakeEntity(role="agenticUser", agentic_user_id="u2"),
68+
})
69+
pairs = dict(tcb._iter_execution_type_pair(activity))
70+
self.assertEqual(pairs[GEN_AI_EXECUTION_TYPE_KEY], ExecutionType.HUMAN_TO_AGENT.value)
71+
72+
def test_iter_target_agent_pairs_with_fallbacks(self):
73+
activity = FakeActivity(**{
74+
"recipient": FakeEntity(
75+
agentic_app_id="app-456",
76+
name="MyAgent",
77+
agentic_user_id="auid-789",
78+
role="agenticUser",
79+
)
80+
})
81+
pairs = dict(tcb._iter_target_agent_pairs(activity))
82+
self.assertEqual(pairs[GEN_AI_AGENT_ID_KEY], "app-456")
83+
self.assertEqual(pairs[GEN_AI_AGENT_NAME_KEY], "MyAgent")
84+
self.assertEqual(pairs[GEN_AI_AGENT_AUID_KEY], "auid-789")
85+
# upn missing -> fallback to name
86+
self.assertEqual(pairs[GEN_AI_AGENT_UPN_KEY], "MyAgent")
87+
self.assertEqual(pairs[GEN_AI_AGENT_DESCRIPTION_KEY], "agenticUser")
88+
89+
def test_iter_tenant_id_pair_primary_and_channel_data_fallback(self):
90+
# Case 1: tenant_id present directly
91+
activity_direct = FakeActivity(**{
92+
"recipient": FakeEntity(tenant_id="t-direct"),
93+
})
94+
direct = dict(tcb._iter_tenant_id_pair(activity_direct))
95+
self.assertEqual(direct[TENANT_ID_KEY], "t-direct")
96+
97+
# Case 2: missing recipient tenant_id but present in channel_data
98+
activity_fallback = FakeActivity(**{
99+
"recipient": FakeEntity(), # no tenant_id
100+
"channel_data": {
101+
"tenant": {"id": "t-channel"},
102+
},
103+
})
104+
fallback = dict(tcb._iter_tenant_id_pair(activity_fallback))
105+
self.assertEqual(fallback[TENANT_ID_KEY], "t-channel")
106+
107+
# Case 3: no tenant anywhere
108+
activity_none = FakeActivity(**{
109+
"recipient": FakeEntity(),
110+
})
111+
none_val = dict(tcb._iter_tenant_id_pair(activity_none))
112+
self.assertIsNone(none_val[TENANT_ID_KEY])
113+
114+
def test_iter_source_metadata_pairs(self):
115+
activity = FakeActivity(**{
116+
"channel_id": "msteams",
117+
"type": "message",
118+
})
119+
pairs = dict(tcb._iter_source_metadata_pairs(activity))
120+
self.assertEqual(pairs[GEN_AI_EXECUTION_SOURCE_ID_KEY], "msteams")
121+
self.assertEqual(pairs[GEN_AI_EXECUTION_SOURCE_NAME_KEY], "msteams")
122+
self.assertEqual(pairs[GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY], "message")
123+
124+
def test_iter_conversation_pairs_wpxcomment(self):
125+
activity = FakeActivity(**{
126+
"channel_id": "agents",
127+
"entities": [
128+
FakeEntity(
129+
type="wpxcomment",
130+
documentId="doc-100",
131+
parentCommentId="parent-200",
132+
)
133+
],
134+
"service_url": "https://service/link",
135+
})
136+
pairs = dict(tcb._iter_conversation_pairs(activity))
137+
# Expect composed id doc-100_parent-200
138+
self.assertEqual(pairs[GEN_AI_CONVERSATION_ID_KEY], "doc-100_parent-200")
139+
self.assertEqual(pairs[GEN_AI_CONVERSATION_ITEM_LINK_KEY], "https://service/link")
140+
141+
def test_iter_conversation_pairs_email_notification(self):
142+
activity = FakeActivity(**{
143+
"channel_id": "agents",
144+
"entities": [
145+
FakeEntity(
146+
type="emailNotification",
147+
conversationId="email-conv-123",
148+
)
149+
],
150+
"service_url": "http://service/url",
151+
})
152+
pairs = dict(tcb._iter_conversation_pairs(activity))
153+
self.assertEqual(pairs[GEN_AI_CONVERSATION_ID_KEY], "email-conv-123")
154+
self.assertEqual(pairs[GEN_AI_CONVERSATION_ITEM_LINK_KEY], "http://service/url")
155+
156+
def test_iter_conversation_pairs_fallback_conversation(self):
157+
activity = FakeActivity(**{
158+
"channel_id": "msteams",
159+
"conversation": FakeEntity(id="conv-777"),
160+
"service_url": "svc",
161+
})
162+
pairs = dict(tcb._iter_conversation_pairs(activity))
163+
self.assertEqual(pairs[GEN_AI_CONVERSATION_ID_KEY], "conv-777")
164+
self.assertEqual(pairs[GEN_AI_CONVERSATION_ITEM_LINK_KEY], "svc")
165+
166+
def test_from_turn_context_aggregates_all(self):
167+
activity = FakeActivity(**{
168+
"from": FakeEntity(id="caller", name="CallerName"),
169+
"recipient": FakeEntity(
170+
agentic_app_id="app-id",
171+
name="AgentName",
172+
agentic_user_id="auid-1",
173+
tenant_id="t-1",
174+
role="agenticUser",
175+
),
176+
"channel_id": "agents",
177+
"type": "message",
178+
"entities": [
179+
FakeEntity(
180+
type="emailNotification",
181+
conversationId="email-conv-123",
182+
)
183+
],
184+
"service_url": "svc-url",
185+
})
186+
ctx = FakeTurnContext(activity)
187+
result = tcb.from_turn_context(ctx)
188+
189+
# Caller fields
190+
self.assertEqual(result[GEN_AI_CALLER_ID_KEY], "caller")
191+
self.assertEqual(result[GEN_AI_CALLER_NAME_KEY], "CallerName")
192+
# Agent fields
193+
self.assertEqual(result[GEN_AI_AGENT_ID_KEY], "app-id")
194+
self.assertEqual(result[GEN_AI_AGENT_NAME_KEY], "AgentName")
195+
self.assertEqual(result[GEN_AI_AGENT_AUID_KEY], "auid-1")
196+
# Tenant
197+
self.assertEqual(result[TENANT_ID_KEY], "t-1")
198+
# Execution type (agent-to-agent)
199+
self.assertEqual(result[GEN_AI_EXECUTION_TYPE_KEY], ExecutionType.HUMAN_TO_AGENT.value)
200+
# Conversation
201+
self.assertEqual(result[GEN_AI_CONVERSATION_ID_KEY], "email-conv-123")
202+
self.assertEqual(result[GEN_AI_CONVERSATION_ITEM_LINK_KEY], "svc-url")
203+
# Source metadata
204+
self.assertEqual(result[GEN_AI_EXECUTION_SOURCE_ID_KEY], "agents")
205+
self.assertEqual(result[GEN_AI_EXECUTION_SOURCE_NAME_KEY], "agents")
206+
self.assertEqual(result[GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY], "message")
207+
208+
def test_from_turn_context_missing_activity(self):
209+
ctx = FakeTurnContext(activity=None)
210+
result = tcb.from_turn_context(ctx)
211+
self.assertEqual(result, {}, "Expected empty dict when activity missing")
212+
213+
214+
if __name__ == "__main__":
215+
unittest.main()

0 commit comments

Comments
 (0)