microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/update-sample-to-blazor

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

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