microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/sub-pr-199

Branches

Tags

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

Clone

HTTPS

Download ZIP

Tests/Microsoft.Teams.Plugins.AspNetCore.Tests/AspNetCorePluginTests.cs

206lines · modecode

1using System.Net;
2using System.Text;
3using System.Text.Json;
4
5using Microsoft.AspNetCore.Http;
6using Microsoft.Extensions.Logging;
7using Microsoft.Extensions.Logging.Abstractions;
8using Microsoft.Teams.Api;
9using Microsoft.Teams.Api.Activities;
10using Microsoft.Teams.Api.Auth;
11using Microsoft.Teams.Apps;
12using Microsoft.Teams.Apps.Events;
13
14using Moq;
15
16namespace Microsoft.Teams.Plugins.AspNetCore.Tests;
17
18public class AspNetCorePluginTests
19{
20 private static AspNetCorePlugin CreatePlugin(ILogger<AspNetCorePlugin> logger, EventFunction? events = null)
21 {
22 var plugin = new AspNetCorePlugin(logger);
23
24 plugin.Client = new Mock<Microsoft.Teams.Common.Http.IHttpClient>().Object;
25 if (events is not null)
26 {
27 plugin.Events += events;
28 }
29 return plugin;
30 }
31
32 private static DefaultHttpContext CreateHttpContext(IActivity activity, string bearer = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjQ3MDI1MTUyMDB9.signature")
33 {
34 var ctx = new DefaultHttpContext();
35 ctx.TraceIdentifier = Guid.NewGuid().ToString();
36 ctx.Request.Headers.Append("Authorization", $"Bearer {bearer}");
37 var json = JsonSerializer.Serialize(activity, new JsonSerializerOptions { DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull });
38 var bytes = Encoding.UTF8.GetBytes(json);
39 ctx.Request.Body = new MemoryStream(bytes);
40 ctx.Request.ContentLength = bytes.Length;
41 return ctx;
42 }
43
44 private static MessageActivity CreateMessageActivity()
45 {
46 return new MessageActivity("hi")
47 {
48 From = new() { Id = "user" },
49 Recipient = new() { Id = "bot" },
50 Conversation = new Conversation() { Id = "conv", Type = ConversationType.Personal }
51 };
52 }
53
54 [Fact]
55 public async Task Test_Do_Http_CallsExtractTokenAndActivity_AndCallsCoreDo()
56 {
57 // Arrange
58 var activity = CreateMessageActivity();
59 var coreResponse = new Response(HttpStatusCode.Accepted, new { ok = true });
60 var eventsCalled = new List<string>();
61
62 EventFunction events = (plugin, name, payload, ct) =>
63 {
64 eventsCalled.Add(name);
65 if (name == "activity") return Task.FromResult<object?>(coreResponse); // returned directly by core Do
66 return Task.FromResult<object?>(null);
67 };
68
69 var plugin = CreatePlugin(NullLogger<AspNetCorePlugin>.Instance, events);
70 var ctx = CreateHttpContext(activity);
71
72 // Act
73 var result = await plugin.Do(ctx);
74
75 // Assert
76 Assert.Contains("activity", eventsCalled);
77 var jsonResult = Assert.IsType<Microsoft.AspNetCore.Http.HttpResults.JsonHttpResult<object?>>(result);
78 Assert.Equal((int)coreResponse.Status, jsonResult.StatusCode);
79 }
80
81 [Fact]
82 public async Task Test_Do_Http_SetsHeadersFromResponseMeta()
83 {
84 // Arrange
85 var activity = CreateMessageActivity();
86 var response = new Response(HttpStatusCode.OK, new { hello = "world" });
87 response.Meta.Add("routes", 3);
88 response.Meta.Add("custom", "value");
89
90 EventFunction events = (plugin, name, payload, ct) =>
91 {
92 if (name == "activity") return Task.FromResult<object?>(response);
93 return Task.FromResult<object?>(null);
94 };
95
96 var plugin = CreatePlugin(NullLogger<AspNetCorePlugin>.Instance, events);
97 var ctx = CreateHttpContext(activity);
98
99 // Act
100 var result = await plugin.Do(ctx);
101
102 // Assert body result type
103 var jsonResult = Assert.IsType<Microsoft.AspNetCore.Http.HttpResults.JsonHttpResult<object?>>(result);
104 Assert.Equal((int)HttpStatusCode.OK, jsonResult.StatusCode);
105 // Headers: routes & custom should be present
106 Assert.Contains("X-Teams-Routes", ctx.Response.Headers.Keys); // capitalized first char
107 Assert.Contains("X-Teams-Custom", ctx.Response.Headers.Keys);
108 }
109
110 [Fact]
111 public async Task Test_Do_Http_ErrorPath_ProducesProblemResult()
112 {
113 // Arrange -> throw inside events
114 EventFunction events = (plugin, name, payload, ct) =>
115 {
116 if (name == "activity") throw new InvalidOperationException("boom");
117 return Task.FromResult<object?>(null);
118 };
119
120 var logger = new Mock<ILogger<AspNetCorePlugin>>();
121 var plugin = CreatePlugin(logger.Object, events);
122 var ctx = CreateHttpContext(CreateMessageActivity());
123
124 // Act
125 var result = await plugin.Do(ctx);
126
127 // Assert
128 var problem = Assert.IsType<Microsoft.AspNetCore.Http.HttpResults.JsonHttpResult<object>>(result);
129 Assert.Equal(500, problem.StatusCode);
130 Assert.Contains("boom", problem.Value!.ToString());
131 logger.Verify(
132 x => x.Log(
133 LogLevel.Error,
134 It.IsAny<EventId>(),
135 It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("Activity event error")),
136 It.IsAny<Exception>(),
137 It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
138 Times.AtLeastOnce);
139 }
140
141 [Fact]
142 public void Test_ExtractToken_ReturnsToken()
143 {
144 var plugin = CreatePlugin(NullLogger<AspNetCorePlugin>.Instance);
145 var ctx = CreateHttpContext(CreateMessageActivity(), "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjQ3MDI1MTUyMDB9.token123");
146
147 var token = plugin.ExtractToken(ctx.Request);
148 Assert.NotNull(token);
149 Assert.Contains("token123", token.ToString());
150 }
151
152 [Fact]
153 public async Task Test_ExtractActivity_ReturnsActivity()
154 {
155 var plugin = CreatePlugin(NullLogger<AspNetCorePlugin>.Instance);
156 var activity = CreateMessageActivity();
157 var ctx = CreateHttpContext(activity);
158
159 var extracted = await plugin.ParseActivity(ctx.Request);
160 Assert.NotNull(extracted);
161 Assert.True(activity.Type.Equals(extracted.Type));
162 }
163
164 [Fact]
165 public async Task Test_ExtractActivity_HttpRequestBodyAlreadyRead_ReturnsActivity()
166 {
167 var plugin = CreatePlugin(NullLogger<AspNetCorePlugin>.Instance);
168 var activity = CreateMessageActivity();
169 var ctx = CreateHttpContext(activity);
170 // simulate body already read by setting position to end
171 ctx.Request.Body.Position = ctx.Request.Body.Length;
172
173 var extracted = await plugin.ParseActivity(ctx.Request);
174 Assert.NotNull(extracted);
175 Assert.True(activity.Type.Equals(extracted.Type));
176 }
177
178 [Fact]
179 public async Task Test_Do_Core_ReturnsResponseAndLogs()
180 {
181 // Arrange core path tests the ActivityEvent Do(ActivityEvent)
182 var response = new Response(HttpStatusCode.OK, new { test = 1 });
183 EventFunction events = (plugin, name, payload, ct) =>
184 {
185 if (name == "activity") return Task.FromResult<object?>(response);
186 return Task.FromResult<object?>(null);
187 };
188 var logger = new Mock<ILogger<AspNetCorePlugin>>();
189 var plugin = CreatePlugin(logger.Object, events);
190 var evt = new ActivityEvent() { Token = new JsonWebToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjQ3MDI1MTUyMDB9.signature"), Activity = CreateMessageActivity() };
191
192 // Act
193 var res = await plugin.Do(evt);
194
195 // Assert
196 Assert.Same(response, res);
197 logger.Verify(
198 x => x.Log(
199 LogLevel.Debug,
200 It.IsAny<EventId>(),
201 It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("test")),
202 It.IsAny<Exception>(),
203 It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
204 Times.AtLeastOnce);
205 }
206}