|
| 1 | +using System.Security.Claims; |
| 2 | +using System.Text.Json; |
| 3 | +using EssentialCSharp.Chat.Common.Services; |
| 4 | +using EssentialCSharp.Web.Controllers; |
| 5 | +using EssentialCSharp.Web.Models; |
| 6 | +using EssentialCSharp.Web.Services; |
| 7 | +using Microsoft.AspNetCore.Http; |
| 8 | +using Microsoft.AspNetCore.Mvc; |
| 9 | +using Microsoft.Extensions.Logging; |
| 10 | +using Moq; |
| 11 | + |
| 12 | +namespace EssentialCSharp.Web.Tests; |
| 13 | + |
| 14 | +public class ChatControllerTests |
| 15 | +{ |
| 16 | + [Test] |
| 17 | + public async Task StreamMessage_MissingCaptchaToken_Returns403WithCaptchaRequired() |
| 18 | + { |
| 19 | + var controller = CreateController(); |
| 20 | + |
| 21 | + await controller.StreamMessage(new ChatMessageRequest { Message = "hello" }); |
| 22 | + |
| 23 | + var body = await ReadJsonResponse(controller.HttpContext.Response); |
| 24 | + await Assert.That(controller.HttpContext.Response.StatusCode).IsEqualTo(StatusCodes.Status403Forbidden); |
| 25 | + await Assert.That(body["errorCode"].GetString()).IsEqualTo("captcha_required"); |
| 26 | + } |
| 27 | + |
| 28 | + [Test] |
| 29 | + public async Task StreamMessage_InvalidCaptcha_Returns403WithCaptchaFailed() |
| 30 | + { |
| 31 | + var captchaService = new Mock<ICaptchaService>(); |
| 32 | + captchaService |
| 33 | + .Setup(service => service.VerifyAsync("bad-token", It.IsAny<string?>(), It.IsAny<CancellationToken>())) |
| 34 | + .ReturnsAsync(new HCaptchaResult { Success = false }); |
| 35 | + |
| 36 | + var controller = CreateController(captchaService: captchaService.Object); |
| 37 | + |
| 38 | + await controller.StreamMessage(new ChatMessageRequest { Message = "hello", CaptchaToken = "bad-token" }); |
| 39 | + |
| 40 | + var body = await ReadJsonResponse(controller.HttpContext.Response); |
| 41 | + await Assert.That(controller.HttpContext.Response.StatusCode).IsEqualTo(StatusCodes.Status403Forbidden); |
| 42 | + await Assert.That(body["errorCode"].GetString()).IsEqualTo("captcha_failed"); |
| 43 | + } |
| 44 | + |
| 45 | + [Test] |
| 46 | + public async Task StreamMessage_CaptchaServiceUnavailable_Returns503WithCaptchaUnavailable() |
| 47 | + { |
| 48 | + var captchaService = new Mock<ICaptchaService>(); |
| 49 | + captchaService |
| 50 | + .Setup(service => service.VerifyAsync("token", It.IsAny<string?>(), It.IsAny<CancellationToken>())) |
| 51 | + .ReturnsAsync((HCaptchaResult?)null); |
| 52 | + |
| 53 | + var controller = CreateController(captchaService: captchaService.Object); |
| 54 | + |
| 55 | + await controller.StreamMessage(new ChatMessageRequest { Message = "hello", CaptchaToken = "token" }); |
| 56 | + |
| 57 | + var body = await ReadJsonResponse(controller.HttpContext.Response); |
| 58 | + await Assert.That(controller.HttpContext.Response.StatusCode).IsEqualTo(StatusCodes.Status503ServiceUnavailable); |
| 59 | + await Assert.That(body["errorCode"].GetString()).IsEqualTo("captcha_unavailable"); |
| 60 | + } |
| 61 | + |
| 62 | + private static ChatController CreateController( |
| 63 | + IAIChatService? aiChatService = null, |
| 64 | + ICaptchaService? captchaService = null) |
| 65 | + { |
| 66 | + var httpContext = new DefaultHttpContext |
| 67 | + { |
| 68 | + User = new ClaimsPrincipal(new ClaimsIdentity([new Claim(ClaimTypes.Name, "test-user")], "TestAuth")) |
| 69 | + }; |
| 70 | + httpContext.Response.Body = new MemoryStream(); |
| 71 | + |
| 72 | + var controller = new ChatController( |
| 73 | + Mock.Of<ILogger<ChatController>>(), |
| 74 | + aiChatService ?? new Mock<IAIChatService>(MockBehavior.Strict).Object, |
| 75 | + captchaService ?? new Mock<ICaptchaService>(MockBehavior.Strict).Object) |
| 76 | + { |
| 77 | + ControllerContext = new ControllerContext { HttpContext = httpContext } |
| 78 | + }; |
| 79 | + |
| 80 | + return controller; |
| 81 | + } |
| 82 | + |
| 83 | + private static async Task<Dictionary<string, JsonElement>> ReadJsonResponse(HttpResponse response) |
| 84 | + { |
| 85 | + response.Body.Position = 0; |
| 86 | + using var reader = new StreamReader(response.Body, leaveOpen: true); |
| 87 | + var json = await reader.ReadToEndAsync(); |
| 88 | + return JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json)!; |
| 89 | + } |
| 90 | +} |
0 commit comments