diff --git a/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemable.html.hbs b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemable.html.hbs new file mode 100644 index 000000000..d233381b9 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemable.html.hbs @@ -0,0 +1,16 @@ +{{#>FullHtmlLayout}} + + + + + + + +
+ {{OrganizationName}} has offered to sponsor a family organization for you with Bitwarden. +
+ + Redeem + +
+{{/FullHtmlLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemable.text.hbs b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemable.text.hbs new file mode 100644 index 000000000..a6bd7bb7e --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemable.text.hbs @@ -0,0 +1,5 @@ +{{#>BasicTextLayout}} +{{OrganizationName}} has offered to sponsor a family organization for you with Bitwarden. To redeem please click the following link: + +{{Url}} +{{/BasicTextLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToFamilyUser.html.hbs b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToFamilyUser.html.hbs new file mode 100644 index 000000000..346982ab2 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToFamilyUser.html.hbs @@ -0,0 +1,9 @@ +{{#>FullHtmlLayout}} + + + + +
+ You have redeemed a family organization sponsorship from {{OrganizationName}}. +
+{{/FullHtmlLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToFamilyUser.text.hbs b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToFamilyUser.text.hbs new file mode 100644 index 000000000..994c88358 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToFamilyUser.text.hbs @@ -0,0 +1,3 @@ +{{#>BasicTextLayout}} +You have redeemed a family organization sponsorship from {{OrganizationName}}. +{{/BasicTextLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToOrgUser.html.hbs b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToOrgUser.html.hbs new file mode 100644 index 000000000..f2615c374 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToOrgUser.html.hbs @@ -0,0 +1,9 @@ +{{#>FullHtmlLayout}} + + + + +
+ You have redeemed a Families for Enterprise sponsorship from {{OrganizationName}}. +
+{{/FullHtmlLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToOrgUser.text.hbs b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToOrgUser.text.hbs new file mode 100644 index 000000000..8dd8e05ef --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToOrgUser.text.hbs @@ -0,0 +1,3 @@ +{{#>BasicTextLayout}} +A user in your organization has redeemed a family invitation. +{{/BasicTextLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseReconfirmationRequired.html.hbs b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseReconfirmationRequired.html.hbs new file mode 100644 index 000000000..51750c24f --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseReconfirmationRequired.html.hbs @@ -0,0 +1,16 @@ +{{#>FullHtmlLayout}} + + + + + + + +
+ Your Families for Enterprise sponsorship requires reconfirmation. +
+ + Reconfirm + +
+{{/FullHtmlLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseReconfirmationRequired.text.hbs b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseReconfirmationRequired.text.hbs new file mode 100644 index 000000000..82bb6387e --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseReconfirmationRequired.text.hbs @@ -0,0 +1,5 @@ +{{#>BasicTextLayout}} +Your Families for Enterprise sponsorship requires reconfirmation. To redeem please click the following link: + +{{Url}} +{{/BasicTextLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipEnding.html.hbs b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipEnding.html.hbs new file mode 100644 index 000000000..b5d45565a --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipEnding.html.hbs @@ -0,0 +1,9 @@ +{{#>FullHtmlLayout}} + + + + +
+ Your Families for Enterprise sponsorship has ended and you will lose premium access at the end of the current billing cycle. +
+{{/FullHtmlLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipEnding.text.hbs b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipEnding.text.hbs new file mode 100644 index 000000000..a2c98521f --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipEnding.text.hbs @@ -0,0 +1,3 @@ +{{#>BasicTextLayout}} +Your Families for Enterprise sponsorship has ended and you will lose premium access at the end of the current billing cycle. +{{/BasicTextLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipReverting.html.hbs b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipReverting.html.hbs new file mode 100644 index 000000000..0b3f90eae --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipReverting.html.hbs @@ -0,0 +1,9 @@ +{{#>FullHtmlLayout}} + + + + +
+ Your Families for Enterprise sponsorship will revert back to your existing payment method at the end of the current billing cycle. +
+{{/FullHtmlLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipReverting.text.hbs b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipReverting.text.hbs new file mode 100644 index 000000000..16017d79a --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipReverting.text.hbs @@ -0,0 +1,3 @@ +{{#>BasicTextLayout}} +Your Families for Enterprise sponsorship will revert back to your existing payment method at the end of the current billing cycle. +{{/BasicTextLayout}} \ No newline at end of file diff --git a/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemableViewModel.cs b/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemableViewModel.cs new file mode 100644 index 000000000..57e01e710 --- /dev/null +++ b/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemableViewModel.cs @@ -0,0 +1,8 @@ +namespace Bit.Core.Models.Mail.FamiliesForEnterprise +{ + public class FamiliesForEnterpriseInviteRedeemableViewModel : BaseMailModel + { + public string OrganizationName { get; set; } + public string Url { get; set; } + } +} diff --git a/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToFamilyUserViewModel.cs b/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToFamilyUserViewModel.cs new file mode 100644 index 000000000..bda9c907c --- /dev/null +++ b/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToFamilyUserViewModel.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Models.Mail.FamiliesForEnterprise +{ + public class FamiliesForEnterpriseInviteRedeemedToFamilyUserViewModel : BaseMailModel + { + public string OrganizationName { get; set; } + } +} diff --git a/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToOrgUserViewModel.cs b/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToOrgUserViewModel.cs new file mode 100644 index 000000000..a547b4f35 --- /dev/null +++ b/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseInviteRedeemedToOrgUserViewModel.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Models.Mail.FamiliesForEnterprise +{ + public class FamiliesForEnterpriseInviteRedeemedToOrgUserViewModel : BaseMailModel + { + public string OrganizationName { get; set; } + } +} diff --git a/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseReconfirmationRequiredViewModel.cs b/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseReconfirmationRequiredViewModel.cs new file mode 100644 index 000000000..5cf7ca2e4 --- /dev/null +++ b/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseReconfirmationRequiredViewModel.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Models.Mail.FamiliesForEnterprise +{ + public class FamiliesForEnterpriseReconfirmationRequiredViewModel : BaseMailModel + { + + } +} diff --git a/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipEndingViewModel.cs b/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipEndingViewModel.cs new file mode 100644 index 000000000..e6568562a --- /dev/null +++ b/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipEndingViewModel.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Models.Mail.FamiliesForEnterprise +{ + public class FamiliesForEnterpriseSponsorshipEndingViewModel : BaseMailModel + { + + } +} diff --git a/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipRevertingViewModel.cs b/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipRevertingViewModel.cs new file mode 100644 index 000000000..cef96099f --- /dev/null +++ b/src/Core/Models/Mail/FamiliesForEnterprise/FamiliesForEnterpriseSponsorshipRevertingViewModel.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Models.Mail.FamiliesForEnterprise +{ + public class FamiliesForEnterpriseSponsorshipRevertingViewModel : BaseMailModel + { + + } +} diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index 8abf5a0d5..c0eb047c1 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -49,5 +49,13 @@ namespace Bit.Core.Services Task SendProviderConfirmedEmailAsync(string providerName, string email); Task SendProviderUserRemoved(string providerName, string email); Task SendUpdatedTempPasswordEmailAsync(string email, string userName); + // TODO: Change signature to hold data needed for email + Task SendFamiliesForEnterpriseInviteRedeemableEmailAsync(string email, string organizationName, string token); + // NOTE: Not married to these next two names + Task SendFamiliesForEnterpriseInviteRedeemedToFamilyUserEmailAsync(string email); + Task SendFamiliesForEnterpriseInviteRedeemedToOrgUserEmailAsync(string email, string organizationName); + Task SendFamiliesForEnterpriseReconfirmationRequiredEmailAsync(string email); + Task SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(string email); + Task SendFamiliesForEnterpriseSponsorshipEndingEmailAsync(string email); } } diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index 8e981f386..ada022a19 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -9,6 +9,7 @@ using System.Net; using Bit.Core.Utilities; using System.Linq; using System.Reflection; +using Bit.Core.Models.Mail.FamiliesForEnterprise; using Bit.Core.Models.Mail.Provider; using Bit.Core.Models.Table.Provider; using HandlebarsDotNet; @@ -755,5 +756,93 @@ namespace Bit.Core.Services message.Category = "UpdatedTempPassword"; await _mailDeliveryService.SendEmailAsync(message); } + + public async Task SendFamiliesForEnterpriseInviteRedeemableEmailAsync(string email, string organizationName, string token) + { + // TODO: Complete emails + var message = CreateDefaultMessage("A Family Organization Invite Is Redeemable", email); + + // NOTE: If somehow cloud vault changes this will need to change/be injected + var url = CoreHelpers.ExtendQuery(new Uri($"https://vault.bitwarden.com/#/sponsored/families-for-enterprise"), + new Dictionary + { + ["sponsorshipToken"] = token, + }); + + var model = new FamiliesForEnterpriseInviteRedeemableViewModel + { + Url = url.ToString(), + OrganizationName = organizationName, + }; + + await AddMessageContentAsync(message, "FamiliesForEnterprise.FamiliesForEnterpriseInviteRedeemable", model); + message.Category = "FamiliesForEnterpriseInviteRedeemable"; + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendFamiliesForEnterpriseInviteRedeemedToFamilyUserEmailAsync(string email) + { + // TODO: Complete emails + var message = CreateDefaultMessage("You Have Redeemed A Family Organization Sponsorship", email); + var model = new FamiliesForEnterpriseInviteRedeemedToFamilyUserViewModel + { + + }; + await AddMessageContentAsync(message, "FamiliesForEnterprise.FamiliesForEnterpriseInviteRedeemedToFamilyUser", model); + message.Category = "FamilyForEnterpriseInviteRedeemedToFamilyUser"; + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendFamiliesForEnterpriseInviteRedeemedToOrgUserEmailAsync(string email, string organizationName) + { + // TODO: Complete emails + var message = CreateDefaultMessage("A User Has Redeemeed Your Sponsorship", email); + var model = new FamiliesForEnterpriseInviteRedeemedToOrgUserViewModel + { + OrganizationName = organizationName, + }; + await AddMessageContentAsync(message, "FamiliesForEnterprise.FamiliesForEnterpriseInviteRedeemedToOrgUser", model); + message.Category = "FamilyForEnterpriseInviteRedeemedToOrgUser"; + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendFamiliesForEnterpriseReconfirmationRequiredEmailAsync(string email) + { + // TODO: Complete emails + var message = CreateDefaultMessage("Your Sponsorship Requires Reconfirmation", email); + var model = new FamiliesForEnterpriseReconfirmationRequiredViewModel + { + + }; + await AddMessageContentAsync(message, "FamiliesForEnterprise.FamiliesForEnterpriseReconfirmationRequired", model); + message.Category = "FamiliesForEnterpriseReconfirmationRequired"; + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(string email) + { + // TODO: Complete emails + var message = CreateDefaultMessage("A Family Organization Sponsorship Is Reverting", email); + var model = new FamiliesForEnterpriseSponsorshipRevertingViewModel + { + + }; + await AddMessageContentAsync(message, "FamiliesForEnterprise.FamiliesForEnterpriseSponsorshipReverting", model); + message.Category = "FamiliesForEnterpriseSponsorshipReverting"; + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendFamiliesForEnterpriseSponsorshipEndingEmailAsync(string email) + { + // TODO: Complete emails + var message = CreateDefaultMessage("A Family Organization Sponsorship Is Ending", email); + var model = new FamiliesForEnterpriseSponsorshipEndingViewModel + { + + }; + await AddMessageContentAsync(message, "FamiliesForEnterprise.FamiliesForEnterpriseSponsorshipEnding", model); + message.Category = "FamiliesForEnterpriseSponsorshipEnding"; + await _mailDeliveryService.SendEmailAsync(message); + } } } diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index f514993d1..7a648a447 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -200,5 +200,35 @@ namespace Bit.Core.Services { return Task.FromResult(0); } + + public Task SendFamiliesForEnterpriseInviteRedeemableEmailAsync(string email, string organizationName, string token) + { + return Task.FromResult(0); + } + + public Task SendFamiliesForEnterpriseInviteRedeemedToFamilyUserEmailAsync(string email) + { + return Task.FromResult(0); + } + + public Task SendFamiliesForEnterpriseInviteRedeemedToOrgUserEmailAsync(string email, string organizationName) + { + return Task.FromResult(0); + } + + public Task SendFamiliesForEnterpriseReconfirmationRequiredEmailAsync(string email) + { + return Task.FromResult(0); + } + + public Task SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(string email) + { + return Task.FromResult(0); + } + + public Task SendFamiliesForEnterpriseSponsorshipEndingEmailAsync(string email) + { + return Task.FromResult(0); + } } } diff --git a/test/Core.Test/Services/HandlebarsMailServiceTests.cs b/test/Core.Test/Services/HandlebarsMailServiceTests.cs index 1204a0613..64b1da193 100644 --- a/test/Core.Test/Services/HandlebarsMailServiceTests.cs +++ b/test/Core.Test/Services/HandlebarsMailServiceTests.cs @@ -1,6 +1,14 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Bit.Core.Models.Business; +using Bit.Core.Models.Table; +using Bit.Core.Models.Table.Provider; using Bit.Core.Services; using Bit.Core.Settings; +using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; @@ -27,6 +35,131 @@ namespace Bit.Core.Test.Services ); } + [Fact(Skip = "Only 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: Swith 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(), + }}, + }; + + 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]