using System.Net; using HrynCo.NotificationService.DAL.Abstract.Repositories; using HrynCo.NotificationService.DAL.Abstract.Templates; using HrynCo.NotificationService.DAL.EF; using HrynCo.NotificationService.Services.EmailTemplates.Delete; using MediatR; using Microsoft.Extensions.DependencyInjection; namespace HrynCo.NotificationService.Web.IntegrationTests; public sealed class ServiceTemplateNavigationTests(TemplateDatabaseFixture fixture) : IClassFixture { [Fact] public async Task ServicesAreTheEntryPoint_AndEachServiceListsOnlyItsOwnTemplates() { var connection = await fixture.CreateDatabaseAsync(); using var provider = new ServiceCollection().AddNotificationDataAccess(connection).BuildServiceProvider(); using var scope = provider.CreateScope(); var services = scope.ServiceProvider.GetRequiredService(); var service = (await services.GetAllAsync()).Single(); var other = new NotificationServiceInfo { Name = "OtherService", Languages = [new ServiceLanguageInfo { Code = "en" }] }; await services.SaveAsync(other, true); var definitions = scope.ServiceProvider.GetRequiredService(); await definitions.SaveDefinitionAsync(new EmailTemplateDefinition { ServiceName = service.Name, Key = "OwnTemplate" }, true); await definitions.SaveDefinitionAsync(new EmailTemplateDefinition { ServiceName = other.Name, Key = "OtherTemplate" }, true); await using var app = new TemplateWebApplicationFactory(connection); using var client = app.CreateClient(new() { AllowAutoRedirect = false }); Assert.Equal("/admin/services", (await client.GetAsync("/")).Headers.Location!.OriginalString); Assert.Equal("/admin/services", (await client.GetAsync("/admin/templates")).Headers.Location!.OriginalString); Assert.Equal($"/admin/services/{service.Id}/templates/create?keyFilter=Welcome", (await client.GetAsync("/admin/templates/create?serviceNameFilter=TestService&keyFilter=Welcome")).Headers.Location!.OriginalString); var list = await client.GetStringAsync("/admin/services"); Assert.Contains($"href=\"/admin/services/{service.Id}\">TestService", list); Assert.Contains($"href=\"/admin/services/{service.Id}/edit\"", list); Assert.DoesNotContain("href=\"/admin/templates\"", list); var detail = await client.GetStringAsync($"/admin/services/{service.Id}"); Assert.Contains("OwnTemplate", detail); Assert.Contains($"href=\"/admin/services/{service.Id}/edit\" class=\"btn btn-outline-primary\"", detail); Assert.DoesNotContain("OtherTemplate", detail); Assert.Contains($"/admin/services/{service.Id}/templates/create", detail); Assert.Contains("data-state=\"missing\"", detail); Assert.Contains("No templates match this key.", await client.GetStringAsync($"/admin/services/{service.Id}?key=Missing")); Assert.Contains("Language codes", await client.GetStringAsync($"/admin/services/{service.Id}/edit")); Assert.Equal(HttpStatusCode.NotFound, (await client.GetAsync($"/admin/services/{Guid.NewGuid()}" )).StatusCode); } [Fact] public async Task AllConfiguredTabsAllowBlankDefinitions_PopulateAndClearWithoutLosingTheTemplate() { var connection = await fixture.CreateDatabaseAsync(); await using var app = new TemplateWebApplicationFactory(connection); using var client = app.CreateClient(new() { AllowAutoRedirect = false }); using var scope = app.Services.CreateScope(); var service = (await scope.ServiceProvider.GetRequiredService().GetAllAsync()).Single(); var baseUrl = $"/admin/services/{service.Id}"; var createHtml = await client.GetStringAsync(baseUrl + "/templates/create?keyFilter=Welcome"); Assert.Equal(5, System.Text.RegularExpressions.Regex.Matches(createHtml, "role=\"tab\"").Count); Assert.DoesNotContain("id=\"addLanguage\"", createHtml); Assert.DoesNotContain("remove-language", createHtml); var fields = TemplateEditorTestFields.Read(createHtml); fields["Key"] = "Welcome"; var response = await client.PostAsync(baseUrl + "/templates/save", new FormUrlEncodedContent(fields)); Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); Assert.Equal(baseUrl + "?key=Welcome", response.Headers.Location!.OriginalString); var definitions = scope.ServiceProvider.GetRequiredService(); var empty = (await definitions.GetDefinitionAsync("TestService", "Welcome"))!; Assert.Empty(empty.Languages); var editUrl = baseUrl + "/templates/" + empty.Id; fields = TemplateEditorTestFields.Read(await client.GetStringAsync(editUrl)); TemplateEditorTestFields.SetLanguage(fields, "uk", "Subject uk", "

Hello

", "Hello"); Assert.Equal(HttpStatusCode.Redirect, (await client.PostAsync(baseUrl + "/templates/save", new FormUrlEncodedContent(fields))).StatusCode); Assert.Equal("uk", Assert.Single((await definitions.GetDefinitionAsync("TestService", "Welcome"))!.Languages).LanguageCode); fields = TemplateEditorTestFields.Read(await client.GetStringAsync(editUrl)); TemplateEditorTestFields.SetLanguage(fields, "uk", "", "", ""); Assert.Equal(HttpStatusCode.Redirect, (await client.PostAsync(baseUrl + "/templates/save", new FormUrlEncodedContent(fields))).StatusCode); var cleared = (await definitions.GetDefinitionAsync("TestService", "Welcome"))!; Assert.Equal(empty.Id, cleared.Id); Assert.Empty(cleared.Languages); } [Fact] public async Task CrossServiceGetSaveAndDeleteAreRejected_WithoutChangingEitherService() { var connection = await fixture.CreateDatabaseAsync(); await using var app = new TemplateWebApplicationFactory(connection); using var client = app.CreateClient(new() { AllowAutoRedirect = false }); using var scope = app.Services.CreateScope(); var services = scope.ServiceProvider.GetRequiredService(); var service = (await services.GetAllAsync()).Single(); var other = new NotificationServiceInfo { Name = "OtherService", Languages = [new ServiceLanguageInfo { Code = "en" }] }; await services.SaveAsync(other, true); var repository = scope.ServiceProvider.GetRequiredService(); await repository.AddAsync(new EmailTemplate { ServiceName = service.Name, Key = "Welcome", LanguageCode = "en", Subject = "Original", HtmlBody = "Original", TextBody = "Original" }); await repository.AddAsync(new EmailTemplate { ServiceName = service.Name, Key = "Welcome", LanguageCode = "uk", Subject = "Retained", HtmlBody = "Retained", TextBody = "Retained" }); var retained = (await repository.GetAsync(service.Name, "Welcome", "uk"))!; var definitions = scope.ServiceProvider.GetRequiredService(); var definition = (await definitions.GetDefinitionAsync(service.Name, "Welcome"))!; var foreignBase = $"/admin/services/{other.Id}/templates"; Assert.Equal(HttpStatusCode.NotFound, (await client.GetAsync(foreignBase + "/" + definition.Id)).StatusCode); var fields = TemplateEditorTestFields.Read(await client.GetStringAsync($"/admin/services/{service.Id}/templates/{definition.Id}")); Assert.Equal(HttpStatusCode.NotFound, (await client.PostAsync(foreignBase + "/save", new FormUrlEncodedContent(fields))).StatusCode); fields["ServiceId"] = other.Id.ToString(); fields["ServiceName"] = other.Name; Assert.Equal(HttpStatusCode.NotFound, (await client.PostAsync(foreignBase + "/save", new FormUrlEncodedContent(fields))).StatusCode); Assert.Equal(HttpStatusCode.NotFound, (await client.PostAsync(foreignBase + $"/{definition.Id}/languages/en/delete", new FormUrlEncodedContent(fields))).StatusCode); Assert.Equal("Original", (await repository.GetAsync(service.Name, "Welcome", "en"))!.Subject); Assert.Empty(await definitions.GetDefinitionsAsync(other.Name)); var delete = await client.PostAsync($"/admin/services/{service.Id}/templates/{definition.Id}/languages/en/delete", new FormUrlEncodedContent(fields)); Assert.Equal(HttpStatusCode.Redirect, delete.StatusCode); Assert.Equal($"/admin/services/{service.Id}", delete.Headers.Location!.OriginalString); Assert.Equal("uk", Assert.Single((await definitions.GetDefinitionAsync(service.Name, "Welcome"))!.Languages).LanguageCode); Assert.Equal(retained.Updated, (await repository.GetAsync(service.Name, "Welcome", "uk"))!.Updated); var staleDelete = await scope.ServiceProvider.GetRequiredService() .Send(new DeleteEmailTemplateCommand(service.Name, "Welcome", "uk", Guid.NewGuid())); Assert.False(staleDelete.IsSuccess); Assert.NotNull(await repository.GetAsync(service.Name, "Welcome", "uk")); Assert.Equal(HttpStatusCode.NotFound, (await client.PostAsync("/admin/templates/save", new FormUrlEncodedContent(fields))).StatusCode); } }