using System.Reflection; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Models.Business; using Bit.Core.Entities; using Bit.Core.Services; using Bit.Core.Settings; using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; namespace Bit.Core.Test.Services; public class HandlebarsMailServiceTests { private readonly HandlebarsMailService _sut; private readonly GlobalSettings _globalSettings; private readonly IMailDeliveryService _mailDeliveryService; private readonly IMailEnqueuingService _mailEnqueuingService; public HandlebarsMailServiceTests() { _globalSettings = new GlobalSettings(); _mailDeliveryService = Substitute.For(); _mailEnqueuingService = Substitute.For(); _sut = new HandlebarsMailService( _globalSettings, _mailDeliveryService, _mailEnqueuingService ); } [Fact(Skip = "For local development")] public async Task SendAllEmails() { // This test is only opt in and is more for development purposes. // This will send all emails to the test email address so that they can be viewed. var namedParameters = new Dictionary<(string, Type), object> { // TODO: Switch to use env variable { ("email", typeof(string)), "test@bitwarden.com" }, { ("user", typeof(User)), new User { Id = Guid.NewGuid(), Email = "test@bitwarden.com", }}, { ("userId", typeof(Guid)), Guid.NewGuid() }, { ("token", typeof(string)), "test_token" }, { ("fromEmail", typeof(string)), "test@bitwarden.com" }, { ("toEmail", typeof(string)), "test@bitwarden.com" }, { ("newEmailAddress", typeof(string)), "test@bitwarden.com" }, { ("hint", typeof(string)), "Test Hint" }, { ("organizationName", typeof(string)), "Test Organization Name" }, { ("orgUser", typeof(OrganizationUser)), new OrganizationUser { Id = Guid.NewGuid(), Email = "test@bitwarden.com", OrganizationId = Guid.NewGuid(), }}, { ("token", typeof(ExpiringToken)), new ExpiringToken("test_token", DateTime.UtcNow.AddDays(1))}, { ("organization", typeof(Organization)), new Organization { Id = Guid.NewGuid(), Name = "Test Organization Name", Seats = 5 }}, { ("initialSeatCount", typeof(int)), 5}, { ("ownerEmails", typeof(IEnumerable)), new [] { "test@bitwarden.com" }}, { ("maxSeatCount", typeof(int)), 5 }, { ("userIdentifier", typeof(string)), "test_user" }, { ("adminEmails", typeof(IEnumerable)), new [] { "test@bitwarden.com" }}, { ("returnUrl", typeof(string)), "https://bitwarden.com/" }, { ("amount", typeof(decimal)), 1.00M }, { ("dueDate", typeof(DateTime)), DateTime.UtcNow.AddDays(1) }, { ("items", typeof(List)), new List { "test@bitwarden.com" }}, { ("mentionInvoices", typeof(bool)), true }, { ("emails", typeof(IEnumerable)), new [] { "test@bitwarden.com" }}, { ("deviceType", typeof(string)), "Mobile" }, { ("timestamp", typeof(DateTime)), DateTime.UtcNow.AddDays(1)}, { ("ip", typeof(string)), "127.0.0.1" }, { ("emergencyAccess", typeof(EmergencyAccess)), new EmergencyAccess { Id = Guid.NewGuid(), Email = "test@bitwarden.com", }}, { ("granteeEmail", typeof(string)), "test@bitwarden.com" }, { ("grantorName", typeof(string)), "Test User" }, { ("initiatingName", typeof(string)), "Test" }, { ("approvingName", typeof(string)), "Test Name" }, { ("rejectingName", typeof(string)), "Test Name" }, { ("provider", typeof(Provider)), new Provider { Id = Guid.NewGuid(), }}, { ("name", typeof(string)), "Test Name" }, { ("ea", typeof(EmergencyAccess)), new EmergencyAccess { Id = Guid.NewGuid(), Email = "test@bitwarden.com", }}, { ("userName", typeof(string)), "testUser" }, { ("orgName", typeof(string)), "Test Org Name" }, { ("providerName", typeof(string)), "testProvider" }, { ("providerUser", typeof(ProviderUser)), new ProviderUser { ProviderId = Guid.NewGuid(), Id = Guid.NewGuid(), }}, { ("familyUserEmail", typeof(string)), "test@bitwarden.com" }, { ("sponsorEmail", typeof(string)), "test@bitwarden.com" }, { ("familyOrgName", typeof(string)), "Test Org Name" }, // Swap existingAccount to true or false to generate different versions of the SendFamiliesForEnterpriseOfferEmailAsync emails. { ("existingAccount", typeof(bool)), false }, { ("sponsorshipEndDate", typeof(DateTime)), DateTime.UtcNow.AddDays(1)}, { ("sponsorOrgName", typeof(string)), "Sponsor Test Org Name" }, { ("expirationDate", typeof(DateTime)), DateTime.Now.AddDays(3) }, { ("utcNow", typeof(DateTime)), DateTime.UtcNow }, }; var globalSettings = new GlobalSettings { Mail = new GlobalSettings.MailSettings { Smtp = new GlobalSettings.MailSettings.SmtpSettings { Host = "localhost", TrustServer = true, Port = 10250, }, ReplyToEmail = "noreply@bitwarden.com", }, SiteName = "Bitwarden", }; var mailDeliveryService = new MailKitSmtpMailDeliveryService(globalSettings, Substitute.For>()); var handlebarsService = new HandlebarsMailService(globalSettings, mailDeliveryService, new BlockingMailEnqueuingService()); var sendMethods = typeof(IMailService).GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(m => m.Name.StartsWith("Send") && m.Name != "SendEnqueuedMailMessageAsync"); foreach (var sendMethod in sendMethods) { await InvokeMethod(sendMethod); } async Task InvokeMethod(MethodInfo method) { var parameters = method.GetParameters(); var args = new object[parameters.Length]; for (var i = 0; i < parameters.Length; i++) { if (!namedParameters.TryGetValue((parameters[i].Name, parameters[i].ParameterType), out var value)) { throw new InvalidOperationException($"Couldn't find a parameter for name '{parameters[i].Name}' and type '{parameters[i].ParameterType.FullName}'"); } args[i] = value; } await (Task)method.Invoke(handlebarsService, args); } } // Remove this test when we add actual tests. It only proves that // we've properly constructed the system under test. [Fact] public void ServiceExists() { Assert.NotNull(_sut); } }