using System.Text.Json; using Bit.Billing.Controllers; using Bit.Billing.Models; using Bit.Core.AdminConsole.Entities; using Bit.Core.Entities; using Bit.Core.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using NSubstitute; using Xunit; namespace Bit.Billing.Test.Controllers; [ControllerCustomize(typeof(FreshdeskController))] [SutProviderCustomize] public class FreshdeskControllerTests { private const string ApiKey = "TESTFRESHDESKAPIKEY"; private const string WebhookKey = "TESTKEY"; private const string UserFieldName = "cf_user"; private const string OrgFieldName = "cf_org"; [Theory] [BitAutoData((string)null, null)] [BitAutoData((string)null)] [BitAutoData(WebhookKey, null)] public async Task PostWebhook_NullRequiredParameters_BadRequest(string freshdeskWebhookKey, FreshdeskWebhookModel model, BillingSettings billingSettings, SutProvider sutProvider) { sutProvider.GetDependency>().Value.FreshDesk.WebhookKey.Returns(billingSettings.FreshDesk.WebhookKey); var response = await sutProvider.Sut.PostWebhook(freshdeskWebhookKey, model); var statusCodeResult = Assert.IsAssignableFrom(response); Assert.Equal(StatusCodes.Status400BadRequest, statusCodeResult.StatusCode); } [Theory] [BitAutoData] public async Task PostWebhook_Success(User user, FreshdeskWebhookModel model, List organizations, SutProvider sutProvider) { model.TicketContactEmail = user.Email; sutProvider.GetDependency().GetByEmailAsync(user.Email).Returns(user); sutProvider.GetDependency().GetManyByUserIdAsync(user.Id).Returns(organizations); var mockHttpMessageHandler = Substitute.ForPartsOf(); var mockResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK); mockHttpMessageHandler.Send(Arg.Any(), Arg.Any()) .Returns(mockResponse); var httpClient = new HttpClient(mockHttpMessageHandler); sutProvider.GetDependency().CreateClient("FreshdeskApi").Returns(httpClient); sutProvider.GetDependency>().Value.FreshDesk.WebhookKey.Returns(WebhookKey); sutProvider.GetDependency>().Value.FreshDesk.ApiKey.Returns(ApiKey); sutProvider.GetDependency>().Value.FreshDesk.UserFieldName.Returns(UserFieldName); sutProvider.GetDependency>().Value.FreshDesk.OrgFieldName.Returns(OrgFieldName); var response = await sutProvider.Sut.PostWebhook(WebhookKey, model); var statusCodeResult = Assert.IsAssignableFrom(response); Assert.Equal(StatusCodes.Status200OK, statusCodeResult.StatusCode); _ = mockHttpMessageHandler.Received(1).Send(Arg.Is(m => m.Method == HttpMethod.Put && m.RequestUri.ToString().EndsWith(model.TicketId)), Arg.Any()); _ = mockHttpMessageHandler.Received(1).Send(Arg.Is(m => m.Method == HttpMethod.Post && m.RequestUri.ToString().EndsWith($"{model.TicketId}/notes")), Arg.Any()); } [Theory] [BitAutoData((string)null, null)] [BitAutoData((string)null)] [BitAutoData(WebhookKey, null)] public async Task PostWebhookOnyxAi_InvalidWebhookKey_results_in_BadRequest( string freshdeskWebhookKey, FreshdeskWebhookModel model, BillingSettings billingSettings, SutProvider sutProvider) { sutProvider.GetDependency>() .Value.FreshDesk.WebhookKey.Returns(billingSettings.FreshDesk.WebhookKey); var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model); var statusCodeResult = Assert.IsAssignableFrom(response); Assert.Equal(StatusCodes.Status400BadRequest, statusCodeResult.StatusCode); } [Theory] [BitAutoData(WebhookKey)] public async Task PostWebhookOnyxAi_invalid_ticketid_results_in_BadRequest( string freshdeskWebhookKey, FreshdeskWebhookModel model, SutProvider sutProvider) { sutProvider.GetDependency>() .Value.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey); var mockHttpMessageHandler = Substitute.ForPartsOf(); var mockResponse = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest); mockHttpMessageHandler.Send(Arg.Any(), Arg.Any()) .Returns(mockResponse); var httpClient = new HttpClient(mockHttpMessageHandler); sutProvider.GetDependency().CreateClient("FreshdeskApi").Returns(httpClient); var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model); var result = Assert.IsAssignableFrom(response); Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode); } [Theory] [BitAutoData(WebhookKey)] public async Task PostWebhookOnyxAi_invalid_freshdesk_response_results_in_BadRequest( string freshdeskWebhookKey, FreshdeskWebhookModel model, SutProvider sutProvider) { sutProvider.GetDependency>() .Value.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey); var mockHttpMessageHandler = Substitute.ForPartsOf(); var mockResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StringContent("non json content. expect json deserializer to throw error") }; mockHttpMessageHandler.Send(Arg.Any(), Arg.Any()) .Returns(mockResponse); var httpClient = new HttpClient(mockHttpMessageHandler); sutProvider.GetDependency().CreateClient("FreshdeskApi").Returns(httpClient); var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model); var result = Assert.IsAssignableFrom(response); Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode); } [Theory] [BitAutoData(WebhookKey)] public async Task PostWebhookOnyxAi_invalid_onyx_response_results_in_BadRequest( string freshdeskWebhookKey, FreshdeskWebhookModel model, FreshdeskViewTicketModel freshdeskTicketInfo, SutProvider sutProvider) { var billingSettings = sutProvider.GetDependency>().Value; billingSettings.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey); billingSettings.Onyx.BaseUrl.Returns("http://simulate-onyx-api.com/api"); // mocking freshdesk Api request for ticket info var mockFreshdeskHttpMessageHandler = Substitute.ForPartsOf(); var mockFreshdeskResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(freshdeskTicketInfo)) }; mockFreshdeskHttpMessageHandler.Send(Arg.Any(), Arg.Any()) .Returns(mockFreshdeskResponse); var freshdeskHttpClient = new HttpClient(mockFreshdeskHttpMessageHandler); // mocking Onyx api response given a ticket description var mockOnyxHttpMessageHandler = Substitute.ForPartsOf(); var mockOnyxResponse = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest); mockOnyxHttpMessageHandler.Send(Arg.Any(), Arg.Any()) .Returns(mockOnyxResponse); var onyxHttpClient = new HttpClient(mockOnyxHttpMessageHandler); sutProvider.GetDependency().CreateClient("FreshdeskApi").Returns(freshdeskHttpClient); sutProvider.GetDependency().CreateClient("OnyxApi").Returns(onyxHttpClient); var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model); var result = Assert.IsAssignableFrom(response); Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode); } [Theory] [BitAutoData(WebhookKey)] public async Task PostWebhookOnyxAi_success( string freshdeskWebhookKey, FreshdeskWebhookModel model, FreshdeskViewTicketModel freshdeskTicketInfo, OnyxAnswerWithCitationResponseModel onyxResponse, SutProvider sutProvider) { var billingSettings = sutProvider.GetDependency>().Value; billingSettings.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey); billingSettings.Onyx.BaseUrl.Returns("http://simulate-onyx-api.com/api"); // mocking freshdesk Api request for ticket info (GET) var mockFreshdeskHttpMessageHandler = Substitute.ForPartsOf(); var mockFreshdeskResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(freshdeskTicketInfo)) }; mockFreshdeskHttpMessageHandler.Send( Arg.Is(_ => _.Method == HttpMethod.Get), Arg.Any()) .Returns(mockFreshdeskResponse); // mocking freshdesk api add note request (POST) var mockFreshdeskAddNoteResponse = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest); mockFreshdeskHttpMessageHandler.Send( Arg.Is(_ => _.Method == HttpMethod.Post), Arg.Any()) .Returns(mockFreshdeskAddNoteResponse); var freshdeskHttpClient = new HttpClient(mockFreshdeskHttpMessageHandler); // mocking Onyx api response given a ticket description var mockOnyxHttpMessageHandler = Substitute.ForPartsOf(); onyxResponse.ErrorMsg = string.Empty; var mockOnyxResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(onyxResponse)) }; mockOnyxHttpMessageHandler.Send(Arg.Any(), Arg.Any()) .Returns(mockOnyxResponse); var onyxHttpClient = new HttpClient(mockOnyxHttpMessageHandler); sutProvider.GetDependency().CreateClient("FreshdeskApi").Returns(freshdeskHttpClient); sutProvider.GetDependency().CreateClient("OnyxApi").Returns(onyxHttpClient); var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model); var result = Assert.IsAssignableFrom(response); Assert.Equal(StatusCodes.Status200OK, result.StatusCode); } public class MockHttpMessageHandler : HttpMessageHandler { protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return Send(request, cancellationToken); } public new virtual Task Send(HttpRequestMessage request, CancellationToken cancellationToken) { throw new NotImplementedException(); } } }