using System.Net; using System.Text.RegularExpressions; using HrynCo.NotificationService.DAL.Abstract.Repositories; using HrynCo.NotificationService.DAL.Abstract.Templates; using Microsoft.Extensions.DependencyInjection; namespace HrynCo.NotificationService.Web.IntegrationTests; public sealed class TemplateCatalogPresentationTests(TemplateDatabaseFixture fixture) : IClassFixture { [Fact] public async Task EditorTabsShowSavedVersionsAfterInvalidUnsavedChanges() { var connection = await fixture.CreateDatabaseAsync(); await using var app = new TemplateWebApplicationFactory(connection); using var scope = app.Services.CreateScope(); var service = (await scope.ServiceProvider.GetRequiredService().GetAllAsync()).Single(); var versions = scope.ServiceProvider.GetRequiredService(); await versions.AddAsync(new EmailTemplate { ServiceName = service.Name, Key = "TabStatus", LanguageCode = "en", Subject = "Saved", HtmlBody = "Saved", TextBody = "Saved" }); var definition = (await scope.ServiceProvider.GetRequiredService() .GetDefinitionAsync(service.Name, "TabStatus"))!; using var client = app.CreateClient(new() { AllowAutoRedirect = false }); var baseUrl = $"/admin/services/{service.Id}/templates"; var html = await client.GetStringAsync($"{baseUrl}/{definition.Id}"); Assert.Matches("(?s)en\\s*Present", html); Assert.Matches("(?s)uk\\s*Missing", html); var fields = TemplateEditorTestFields.Read(html); TemplateEditorTestFields.SetLanguage(fields, "uk", "Unsaved", "", ""); fields["Languages[0].IsPresent"] = "false"; var response = await client.PostAsync(baseUrl + "/save", new FormUrlEncodedContent(fields)); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var invalidHtml = await response.Content.ReadAsStringAsync(); Assert.Matches("(?s)en\\s*Present", invalidHtml); Assert.Matches("(?s)uk\\s*Missing", invalidHtml); } [Fact] public async Task CatalogStatusesUseExactCodes_AndEmptyDefinitionsShowEveryLanguageMissing() { var connection = await fixture.CreateDatabaseAsync(); await using var app = new TemplateWebApplicationFactory(connection); using var scope = app.Services.CreateScope(); var services = scope.ServiceProvider.GetRequiredService(); var service = (await services.GetAllAsync()).Single(); service.Languages = new[] { "EN", "en", "ru", "ru-RU" }.Select(code => new ServiceLanguageInfo { Code = code }).ToList(); await services.SaveAsync(service, false); var versions = scope.ServiceProvider.GetRequiredService(); await versions.AddAsync(new EmailTemplate { ServiceName = service.Name, Key = "CaseCheck", LanguageCode = "en", Subject = "Hello", HtmlBody = "Hello", TextBody = "Hello" }); await versions.AddAsync(new EmailTemplate { ServiceName = service.Name, Key = "CaseCheck", LanguageCode = "ru", Subject = "Hello", HtmlBody = "Hello", TextBody = "Hello" }); var definitions = scope.ServiceProvider.GetRequiredService(); await definitions.SaveDefinitionAsync(new EmailTemplateDefinition { ServiceName = service.Name, Key = "Empty" }, true); using var client = app.CreateClient(); var html = await client.GetStringAsync($"/admin/services/{service.Id}"); var catalog = Regex.Match(html, "
", RegexOptions.Singleline).Value; Assert.Equal(4, Regex.Matches(catalog, "class=\"service-template-language-code\"").Count); Assert.Contains(">EN", catalog); Assert.Contains(">en", catalog); Assert.Contains(">ru-RU", catalog); var row = TemplateRow(html, "CaseCheck"); Assert.Contains("data-language-code=\"en\" data-state=\"present\"", row); Assert.Contains("data-language-code=\"EN\" data-state=\"missing\"", row); Assert.Contains("data-language-code=\"ru\" data-state=\"present\"", row); Assert.Contains("data-language-code=\"ru-RU\" data-state=\"missing\"", row); Assert.Equal(2, Regex.Matches(row, ">Present").Count); Assert.Equal(2, Regex.Matches(row, ">Missing").Count); var empty = TemplateRow(html, "Empty"); Assert.Equal(4, Regex.Matches(empty, "data-state=\"missing\"").Count); Assert.DoesNotContain("data-state=\"present\"", empty); Assert.Equal(2, (await versions.GetAllAsync(service.Name)).Count); } [Fact] public async Task EmbeddedKeyFilterSurvivesMiss_AndClearAndEditorLinksStayWithinTheService() { var connection = await fixture.CreateDatabaseAsync(); await using var app = new TemplateWebApplicationFactory(connection); using var scope = app.Services.CreateScope(); var service = (await scope.ServiceProvider.GetRequiredService().GetAllAsync()).Single(); var definitions = scope.ServiceProvider.GetRequiredService(); await definitions.SaveDefinitionAsync(new EmailTemplateDefinition { ServiceName = service.Name, Key = "Welcome" }, true); await definitions.SaveDefinitionAsync(new EmailTemplateDefinition { ServiceName = service.Name, Key = "Other" }, true); using var client = app.CreateClient(); var baseUrl = $"/admin/services/{service.Id}"; var miss = await client.GetStringAsync(baseUrl + "?key=Absent"); Assert.Matches("(?s)]*>\\s*]*method=\"get\"[^>]*class=\"template-key-filter\"", miss); Assert.Contains("value=\"Absent\"", miss); Assert.Contains("No templates match this key.", miss); Assert.Contains("class=\"template-actions\">Action", miss); Assert.Contains($"href=\"{baseUrl}\" class=\"btn btn-outline-secondary btn-sm\">Clear", miss); var filtered = await client.GetStringAsync(baseUrl + "?key=Welcome"); Assert.Contains("Welcome", filtered); Assert.DoesNotContain("Other", filtered); Assert.Contains("keyFilter=Welcome", filtered); Assert.Contains("aria-label=\"Edit template Welcome\"", filtered); var cleared = await client.GetStringAsync(baseUrl); Assert.Contains("Welcome", cleared); Assert.Contains("Other", cleared); } [Fact] public async Task NoCatalogAndNoDefinitionsHaveExplicitStates_AndRetainTheTableHeader() { var connection = await fixture.CreateDatabaseAsync(); await using var app = new TemplateWebApplicationFactory(connection); using var scope = app.Services.CreateScope(); var services = scope.ServiceProvider.GetRequiredService(); var service = new NotificationServiceInfo { Name = "NoLanguages" }; await services.SaveAsync(service, true); using var client = app.CreateClient(); var baseUrl = $"/admin/services/{service.Id}"; var empty = await client.GetStringAsync(baseUrl); Assert.Contains("No languages configured for this service.", empty); Assert.Contains("template-key-filter", empty); Assert.Contains("No templates for this service yet.", empty); await scope.ServiceProvider.GetRequiredService() .SaveDefinitionAsync(new EmailTemplateDefinition { ServiceName = service.Name, Key = "Empty" }, true); var withDefinition = await client.GetStringAsync(baseUrl); Assert.Contains("No configured languages", TemplateRow(withDefinition, "Empty")); Assert.DoesNotContain("data-state=", withDefinition); Assert.DoesNotContain("No templates match this key.", withDefinition); } private static string TemplateRow(string html, string key) { var row = Regex.Match(html, "\\s*" + Regex.Escape(WebUtility.HtmlEncode(key)) + ".*?", RegexOptions.Singleline).Value; Assert.NotEmpty(row); return row; } }