Skip to content

Commit ff3e190

Browse files
feat: add WhoAmI endpoint to DemosController with authentication and user profile retrieval; include unit tests
1 parent 129ffe7 commit ff3e190

2 files changed

Lines changed: 245 additions & 0 deletions

File tree

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
using Microsoft.ApplicationInsights;
2+
using Microsoft.ApplicationInsights.Extensibility;
3+
using Microsoft.AspNetCore.Authorization;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.AspNetCore.Identity;
6+
using Microsoft.AspNetCore.Mvc;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Extensions.Logging;
9+
using Microsoft.Extensions.Primitives;
10+
using Moq;
11+
using System.Net;
12+
using MX.Api.Abstractions;
13+
using Newtonsoft.Json;
14+
using XtremeIdiots.Portal.Repository.Api.Client.V1;
15+
using XtremeIdiots.Portal.Repository.Abstractions.Models.V1.UserProfiles;
16+
using XtremeIdiots.Portal.Web.ApiControllers;
17+
18+
namespace XtremeIdiots.Portal.Web.Tests.ApiControllers;
19+
20+
public class DemosControllerTests
21+
{
22+
private readonly Mock<IAuthorizationService> mockAuthorizationService = new();
23+
private readonly Mock<IRepositoryApiClient> mockRepositoryApiClient = new(MockBehavior.Default) { DefaultValue = DefaultValue.Mock };
24+
private readonly TelemetryClient telemetryClient = new(new TelemetryConfiguration());
25+
private readonly Mock<ILogger<DemosController>> mockLogger = new();
26+
private readonly Mock<IConfiguration> mockConfiguration = new();
27+
28+
private DemosController CreateSut(Dictionary<string, StringValues>? headers = null)
29+
{
30+
var mockUserStore = new Mock<IUserStore<IdentityUser>>();
31+
var mockUserManager = new Mock<UserManager<IdentityUser>>(
32+
mockUserStore.Object, null!, null!, null!, null!, null!, null!, null!, null!);
33+
var mockSignInManager = new Mock<SignInManager<IdentityUser>>(
34+
mockUserManager.Object,
35+
new Mock<IHttpContextAccessor>().Object,
36+
new Mock<IUserClaimsPrincipalFactory<IdentityUser>>().Object,
37+
null!, null!, null!, null!);
38+
39+
var controller = new DemosController(
40+
mockAuthorizationService.Object,
41+
mockUserManager.Object,
42+
mockSignInManager.Object,
43+
mockRepositoryApiClient.Object,
44+
telemetryClient,
45+
mockLogger.Object,
46+
mockConfiguration.Object);
47+
48+
var httpContext = new DefaultHttpContext();
49+
if (headers != null)
50+
{
51+
foreach (var header in headers)
52+
httpContext.Request.Headers[header.Key] = header.Value;
53+
}
54+
55+
controller.ControllerContext = new ControllerContext { HttpContext = httpContext };
56+
57+
return controller;
58+
}
59+
60+
private void SetupUserProfileApiMock(ApiResult<UserProfileDto> result)
61+
{
62+
mockRepositoryApiClient
63+
.Setup(x => x.UserProfiles.V1.GetUserProfileByDemoAuthKey(It.IsAny<string>(), It.IsAny<CancellationToken>()))
64+
.ReturnsAsync(result);
65+
}
66+
67+
[Fact]
68+
public async Task WhoAmI_WithNoAuthKeyHeader_ReturnsUnauthorized()
69+
{
70+
// Arrange
71+
var sut = CreateSut();
72+
73+
// Act
74+
var result = await sut.WhoAmI();
75+
76+
// Assert
77+
Assert.IsType<UnauthorizedObjectResult>(result);
78+
}
79+
80+
[Fact]
81+
public async Task WhoAmI_WithEmptyAuthKey_ReturnsUnauthorized()
82+
{
83+
// Arrange
84+
var sut = CreateSut(new Dictionary<string, StringValues>
85+
{
86+
{ "demo-manager-auth-key", "" }
87+
});
88+
89+
// Act
90+
var result = await sut.WhoAmI();
91+
92+
// Assert
93+
Assert.IsType<UnauthorizedObjectResult>(result);
94+
}
95+
96+
[Fact]
97+
public async Task WhoAmI_WithInvalidAuthKey_ReturnsUnauthorized()
98+
{
99+
// Arrange
100+
SetupUserProfileApiMock(new ApiResult<UserProfileDto>(HttpStatusCode.NotFound));
101+
102+
var sut = CreateSut(new Dictionary<string, StringValues>
103+
{
104+
{ "demo-manager-auth-key", "invalid-key" }
105+
});
106+
107+
// Act
108+
var result = await sut.WhoAmI();
109+
110+
// Assert
111+
Assert.IsType<UnauthorizedObjectResult>(result);
112+
}
113+
114+
[Fact]
115+
public async Task WhoAmI_WithValidAuthKey_ReturnsOkWithUserInfo()
116+
{
117+
// Arrange
118+
var userProfile = CreateTestUserProfile("TestPlayer", "forum-12345");
119+
var apiResult = new ApiResult<UserProfileDto>(
120+
HttpStatusCode.OK,
121+
new ApiResponse<UserProfileDto>(userProfile));
122+
123+
SetupUserProfileApiMock(apiResult);
124+
125+
var sut = CreateSut(new Dictionary<string, StringValues>
126+
{
127+
{ "demo-manager-auth-key", "valid-key-123" }
128+
});
129+
130+
// Act
131+
var result = await sut.WhoAmI();
132+
133+
// Assert
134+
var okResult = Assert.IsType<OkObjectResult>(result);
135+
Assert.NotNull(okResult.Value);
136+
137+
var json = System.Text.Json.JsonSerializer.Serialize(okResult.Value);
138+
var response = System.Text.Json.JsonDocument.Parse(json);
139+
140+
Assert.Equal("forum-12345", response.RootElement.GetProperty("userId").GetString());
141+
Assert.Equal("TestPlayer", response.RootElement.GetProperty("displayName").GetString());
142+
}
143+
144+
[Fact]
145+
public async Task WhoAmI_WithValidAuthKey_ReturnsCorrectDisplayName()
146+
{
147+
// Arrange
148+
var userProfile = CreateTestUserProfile("AnotherUser", "forum-99999");
149+
var apiResult = new ApiResult<UserProfileDto>(
150+
HttpStatusCode.OK,
151+
new ApiResponse<UserProfileDto>(userProfile));
152+
153+
SetupUserProfileApiMock(apiResult);
154+
155+
var sut = CreateSut(new Dictionary<string, StringValues>
156+
{
157+
{ "demo-manager-auth-key", "another-valid-key" }
158+
});
159+
160+
// Act
161+
var result = await sut.WhoAmI();
162+
163+
// Assert
164+
var okResult = Assert.IsType<OkObjectResult>(result);
165+
var json = System.Text.Json.JsonSerializer.Serialize(okResult.Value);
166+
var response = System.Text.Json.JsonDocument.Parse(json);
167+
168+
Assert.Equal("AnotherUser", response.RootElement.GetProperty("displayName").GetString());
169+
Assert.Equal("forum-99999", response.RootElement.GetProperty("userId").GetString());
170+
}
171+
172+
[Fact]
173+
public async Task WhoAmI_WhenApiThrows_ReturnsStatusCode500()
174+
{
175+
// Arrange
176+
mockRepositoryApiClient
177+
.Setup(x => x.UserProfiles.V1.GetUserProfileByDemoAuthKey(It.IsAny<string>(), It.IsAny<CancellationToken>()))
178+
.ThrowsAsync(new InvalidOperationException("API unavailable"));
179+
180+
var sut = CreateSut(new Dictionary<string, StringValues>
181+
{
182+
{ "demo-manager-auth-key", "valid-key" }
183+
});
184+
185+
// Act
186+
var result = await sut.WhoAmI();
187+
188+
// Assert
189+
var statusResult = Assert.IsType<ObjectResult>(result);
190+
Assert.Equal(500, statusResult.StatusCode);
191+
}
192+
193+
private static UserProfileDto CreateTestUserProfile(string displayName, string forumId)
194+
{
195+
var json = JsonConvert.SerializeObject(new
196+
{
197+
UserProfileId = Guid.NewGuid(),
198+
XtremeIdiotsForumId = forumId,
199+
DemoAuthKey = "test-key",
200+
DisplayName = displayName,
201+
UserProfileClaims = Array.Empty<object>()
202+
});
203+
204+
return JsonConvert.DeserializeObject<UserProfileDto>(json)!;
205+
}
206+
}

src/XtremeIdiots.Portal.Web/ApiControllers/DemosController.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,45 @@ public async Task<IActionResult> ClientDemoList(CancellationToken cancellationTo
218218
}
219219
}
220220

221+
[HttpGet("WhoAmI")]
222+
public async Task<IActionResult> WhoAmI(CancellationToken cancellationToken = default)
223+
{
224+
try
225+
{
226+
const string authKeyHeader = "demo-manager-auth-key";
227+
228+
if (!Request.Headers.TryGetValue(authKeyHeader, out var value))
229+
return Unauthorized(new { error = "No auth key provided" });
230+
231+
var authKey = value.FirstOrDefault();
232+
233+
if (string.IsNullOrWhiteSpace(authKey))
234+
return Unauthorized(new { error = "Empty auth key" });
235+
236+
var userProfileApiResponse = await repositoryApiClient.UserProfiles.V1
237+
.GetUserProfileByDemoAuthKey(authKey, cancellationToken).ConfigureAwait(false);
238+
239+
if (userProfileApiResponse.IsNotFound || userProfileApiResponse.Result?.Data is null)
240+
return Unauthorized(new { error = "Invalid auth key" });
241+
242+
var profile = userProfileApiResponse.Result.Data;
243+
244+
Logger.LogInformation("{MethodName} - Identity resolved for user {DisplayName} ({UserId})",
245+
nameof(WhoAmI), profile.DisplayName, profile.XtremeIdiotsForumId);
246+
247+
return Ok(new
248+
{
249+
userId = profile.XtremeIdiotsForumId,
250+
displayName = profile.DisplayName
251+
});
252+
}
253+
catch (Exception ex)
254+
{
255+
Logger.LogError(ex, "Error in {MethodName} endpoint", nameof(WhoAmI));
256+
return StatusCode(500, new { error = "Internal server error" });
257+
}
258+
}
259+
221260
private static DemoOrder GetDemoOrderFromDataTable(DataTableAjaxPostModel model)
222261
{
223262
var order = DemoOrder.CreatedDesc;

0 commit comments

Comments
 (0)