namespace HrynCo.NotificationService.Services.Tests.EmailProcessing; using HrynCo.NotificationService.DAL.Abstract.Repositories; using HrynCo.NotificationService.DAL.Abstract.Templates; using HrynCo.NotificationService.Worker.Services.EmailProcessing; using NSubstitute; public sealed class EmailTemplateServiceTests { [Theory] [InlineData("RU")] [InlineData(" ru ")] [InlineData(" RU ")] public async Task GetAsync_WhenExactVersionIsMissing_RetriesLegacyTrimmedLowercaseCode(string requestedCode) { IEmailTemplateRepository repository = Substitute.For(); var expected = Template("ru"); repository.GetAsync("TestService", "Welcome", "ru", Arg.Any()).Returns(expected); var service = new EmailTemplateService(repository); Assert.Same(expected, await service.GetAsync("TestService", "Welcome", requestedCode, default)); Assert.Equal(new[] { requestedCode, "ru" }, RequestedCodes(repository)); } [Fact] public async Task GetAsync_WhenBothExactAndLowercaseVersionsExist_PrefersExactVersion() { IEmailTemplateRepository repository = Substitute.For(); var expected = Template("RU"); repository.GetAsync("TestService", "Welcome", "RU", Arg.Any()).Returns(expected); repository.GetAsync("TestService", "Welcome", "ru", Arg.Any()).Returns(Template("ru")); var service = new EmailTemplateService(repository); Assert.Same(expected, await service.GetAsync("TestService", "Welcome", "RU", default)); Assert.Equal(new[] { "RU" }, RequestedCodes(repository)); } [Fact] public async Task GetAsync_WhenExactRegionalVersionExists_DoesNotNormalizeIt() { IEmailTemplateRepository repository = Substitute.For(); var expected = Template("ru-RU"); repository.GetAsync("TestService", "Welcome", "ru-RU", Arg.Any()).Returns(expected); var service = new EmailTemplateService(repository); Assert.Same(expected, await service.GetAsync("TestService", "Welcome", "ru-RU", default)); Assert.Equal(new[] { "ru-RU" }, RequestedCodes(repository)); } [Fact] public async Task GetAsync_WhenMissingCodeIsAlreadyCanonical_QueriesOnlyOnce() { IEmailTemplateRepository repository = Substitute.For(); var service = new EmailTemplateService(repository); var error = await Assert.ThrowsAsync(() => service.GetAsync("TestService", "Welcome", "zz", default)); Assert.Contains("language='zz'", error.Message); Assert.Equal(new[] { "zz" }, RequestedCodes(repository)); } [Fact] public async Task GetAsync_WhenRegionalLookupMisses_DoesNotScanOtherCaseOrRegionalVariants() { IEmailTemplateRepository repository = Substitute.For(); repository.GetAsync("TestService", "Welcome", "ru-RU", Arg.Any()).Returns(Template("ru-RU")); repository.GetAsync("TestService", "Welcome", "ru", Arg.Any()).Returns(Template("ru")); var service = new EmailTemplateService(repository); await Assert.ThrowsAsync(() => service.GetAsync("TestService", "Welcome", "RU-ru", default)); Assert.Equal(new[] { "RU-ru", "ru-ru" }, RequestedCodes(repository)); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] public async Task GetAsync_WhenCodeIsBlank_RejectsBeforeRepositoryLookup(string? code) { IEmailTemplateRepository repository = Substitute.For(); var service = new EmailTemplateService(repository); await Assert.ThrowsAsync(() => service.GetAsync("TestService", "Welcome", code, default)); Assert.Empty(repository.ReceivedCalls()); } private static EmailTemplate Template(string code) => new() { ServiceName = "TestService", Key = "Welcome", LanguageCode = code, Subject = "Subject " + code, HtmlBody = "

Hello

", TextBody = "Hello" }; private static string[] RequestedCodes(IEmailTemplateRepository repository) => repository.ReceivedCalls() .Select(call => (string)call.GetArguments()[2]!).ToArray(); }