microsoft/teams.net

Public

mirrored from https://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
fix/msal-agentic-cache

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

core/test/Microsoft.Teams.Apps.UnitTests/PromptPreviewTests.cs

232lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using Microsoft.AspNetCore.Http;
5using Microsoft.Extensions.Configuration;
6using Microsoft.Extensions.Logging.Abstractions;
7using Microsoft.Teams.Apps.Api.Clients;
8using Microsoft.Teams.Apps.Schema;
9using Microsoft.Teams.Apps.Schema.Entities;
10using Microsoft.Teams.Core;
11using Microsoft.Teams.Core.Schema;
12using Moq;
13
14namespace Microsoft.Teams.Apps.UnitTests;
15
16public class PromptPreviewTests
17{
18 [Fact]
19 public async Task SendActivityAsync_AutoPopulatesTargetedMessageInfo_WhenInboundIsTargeted()
20 {
21 TestHarness harness = CreateHarness();
22 CaptureSlot captured = SetupCapture(harness);
23
24 MessageActivity inbound = BuildInbound(targetedInbound: true, inboundId: "1772129782775", convType: ConversationType.GroupChat);
25 Context<MessageActivity> ctx = new(harness.App, inbound);
26
27 await ctx.SendActivityAsync(new MessageActivity("response text"));
28
29 Assert.NotNull(captured.Value);
30 TeamsActivity teamsActivity = (TeamsActivity)captured.Value!;
31 TargetedMessageInfoEntity? entity = teamsActivity.Entities?.OfType<TargetedMessageInfoEntity>().SingleOrDefault();
32 Assert.NotNull(entity);
33 Assert.Equal("1772129782775", entity.MessageId);
34 }
35
36 [Fact]
37 public async Task SendActivityAsync_StringOverload_AutoPopulatesTargetedMessageInfo_WhenInboundIsTargeted()
38 {
39 TestHarness harness = CreateHarness();
40 CaptureSlot captured = SetupCapture(harness);
41
42 MessageActivity inbound = BuildInbound(targetedInbound: true, inboundId: "1772129782775", convType: ConversationType.GroupChat);
43 Context<MessageActivity> ctx = new(harness.App, inbound);
44
45 await ctx.SendActivityAsync("plain text response");
46
47 Assert.NotNull(captured.Value);
48 TeamsActivity teamsActivity = (TeamsActivity)captured.Value!;
49 TargetedMessageInfoEntity? entity = teamsActivity.Entities?.OfType<TargetedMessageInfoEntity>().SingleOrDefault();
50 Assert.NotNull(entity);
51 Assert.Equal("1772129782775", entity.MessageId);
52 }
53
54 [Fact]
55 public async Task SendActivityAsync_StringOverload_Succeeds_InPersonalChat()
56 {
57 // The string overload constructs a MessageActivity with no recipient, so IsTargeted is
58 // never set and the 1:1 guard cannot fire. This test pins that behavior: plain string
59 // sends from a personal chat go through without throwing, even though the typed overload
60 // would throw if a caller explicitly built a targeted MessageActivity.
61 TestHarness harness = CreateHarness();
62 CaptureSlot captured = SetupCapture(harness);
63
64 MessageActivity inbound = BuildInbound(targetedInbound: false, inboundId: "1234", convType: ConversationType.Personal);
65 Context<MessageActivity> ctx = new(harness.App, inbound);
66
67 await ctx.SendActivityAsync("hello");
68
69 Assert.NotNull(captured.Value);
70 }
71
72 [Fact]
73 public async Task SendActivityAsync_DoesNotAddTargetedMessageInfo_WhenInboundNotTargeted()
74 {
75 TestHarness harness = CreateHarness();
76 CaptureSlot captured = SetupCapture(harness);
77
78 MessageActivity inbound = BuildInbound(targetedInbound: false, inboundId: "1234", convType: ConversationType.GroupChat);
79 Context<MessageActivity> ctx = new(harness.App, inbound);
80
81 await ctx.SendActivityAsync(new MessageActivity("hello"));
82
83 Assert.NotNull(captured.Value);
84 TeamsActivity teamsActivity = (TeamsActivity)captured.Value!;
85 Assert.Null(teamsActivity.Entities?.OfType<TargetedMessageInfoEntity>().SingleOrDefault());
86 }
87
88 [Fact]
89 public async Task SendActivityAsync_DoesNotDuplicate_WhenEntityAlreadyPresent()
90 {
91 TestHarness harness = CreateHarness();
92 CaptureSlot captured = SetupCapture(harness);
93
94 MessageActivity inbound = BuildInbound(targetedInbound: true, inboundId: "1772129782775", convType: ConversationType.GroupChat);
95 Context<MessageActivity> ctx = new(harness.App, inbound);
96
97 MessageActivity outbound = new("response");
98 outbound.AddEntity(new TargetedMessageInfoEntity { MessageId = "9999" });
99
100 await ctx.SendActivityAsync(outbound);
101
102 Assert.NotNull(captured.Value);
103 TeamsActivity teamsActivity = (TeamsActivity)captured.Value!;
104 List<TargetedMessageInfoEntity> entities = teamsActivity.Entities!.OfType<TargetedMessageInfoEntity>().ToList();
105 Assert.Single(entities);
106 Assert.Equal("9999", entities[0].MessageId);
107 }
108
109 [Fact]
110 public async Task SendActivityAsync_Throws_WhenTargetedMessage_InPersonalChat()
111 {
112 TestHarness harness = CreateHarness();
113 SetupCapture(harness);
114
115 MessageActivity inbound = BuildInbound(targetedInbound: false, inboundId: "1234", convType: ConversationType.Personal);
116 Context<MessageActivity> ctx = new(harness.App, inbound);
117
118 MessageActivity outbound = new("secret");
119 outbound.Recipient = new TeamsConversationAccount { Id = "user-1", IsTargeted = true };
120
121 InvalidOperationException ex = await Assert.ThrowsAsync<InvalidOperationException>(
122 () => ctx.SendActivityAsync(outbound));
123 Assert.Contains("personal", ex.Message, StringComparison.OrdinalIgnoreCase);
124 }
125
126 [Fact]
127 public async Task SendActivityAsync_Succeeds_WhenNonTargetedMessage_InPersonalChat()
128 {
129 TestHarness harness = CreateHarness();
130 CaptureSlot captured = SetupCapture(harness);
131
132 MessageActivity inbound = BuildInbound(targetedInbound: false, inboundId: "1234", convType: ConversationType.Personal);
133 Context<MessageActivity> ctx = new(harness.App, inbound);
134
135 await ctx.SendActivityAsync(new MessageActivity("hi"));
136
137 Assert.NotNull(captured.Value);
138 }
139
140 [Fact]
141 public async Task SendActivityAsync_Succeeds_WhenTargetedMessage_InGroupChat()
142 {
143 TestHarness harness = CreateHarness();
144 CaptureSlot captured = SetupCapture(harness);
145
146 MessageActivity inbound = BuildInbound(targetedInbound: false, inboundId: "1234", convType: ConversationType.GroupChat);
147 Context<MessageActivity> ctx = new(harness.App, inbound);
148
149 MessageActivity outbound = new("only you can see this");
150 outbound.Recipient = new TeamsConversationAccount { Id = "user-1", IsTargeted = true };
151
152 await ctx.SendActivityAsync(outbound);
153
154 Assert.NotNull(captured.Value);
155 }
156
157 // ==================== Helpers ====================
158
159 private sealed class CaptureSlot
160 {
161 public CoreActivity? Value { get; set; }
162 }
163
164 private static MessageActivity BuildInbound(bool targetedInbound, string inboundId, string convType)
165 {
166 return new MessageActivity("inbound text")
167 {
168 Id = inboundId,
169 ChannelId = "msteams",
170 ServiceUrl = new Uri("https://smba.trafficmanager.net/test/"),
171 From = new TeamsConversationAccount { Id = "user-1", Name = "User" },
172 Recipient = new TeamsConversationAccount
173 {
174 Id = "bot-1",
175 Name = "Bot",
176 IsTargeted = targetedInbound ? true : null
177 },
178 Conversation = new TeamsConversation
179 {
180 Id = "conv-1",
181 ConversationType = convType
182 }
183 };
184 }
185
186 private static CaptureSlot SetupCapture(TestHarness harness)
187 {
188 CaptureSlot slot = new();
189 harness.MockConversationClient
190 .Setup(c => c.SendActivityAsync(
191 It.IsAny<CoreActivity>(),
192 It.IsAny<Dictionary<string, string>?>(),
193 It.IsAny<CancellationToken>()))
194 .Callback<CoreActivity, Dictionary<string, string>?, CancellationToken>(
195 (activity, _, _) => slot.Value = activity)
196 .ReturnsAsync(new SendActivityResponse { Id = "sent-id" });
197 return slot;
198 }
199
200 private sealed class TestHarness
201 {
202 public required TeamsBotApplication App { get; init; }
203 public required Mock<ConversationClient> MockConversationClient { get; init; }
204 }
205
206 private static TestHarness CreateHarness()
207 {
208 Mock<IConfiguration> mockConfig = new();
209 Mock<UserTokenClient> mockUserTokenClient = new(
210 new HttpClient(),
211 mockConfig.Object,
212 NullLogger<UserTokenClient>.Instance);
213 Mock<ConversationClient> mockConversationClient = new(new HttpClient(), NullLogger<ConversationClient>.Instance);
214
215 ApiClient apiClient = new(
216 new HttpClient(),
217 mockConversationClient.Object,
218 mockUserTokenClient.Object);
219
220 TeamsBotApplication app = new(
221 apiClient,
222 new HttpContextAccessor(),
223 NullLogger<TeamsBotApplication>.Instance,
224 new TeamsBotApplicationOptions { AppId = "test-app-id" });
225
226 return new TestHarness
227 {
228 App = app,
229 MockConversationClient = mockConversationClient,
230 };
231 }
232}