Files
hrynco-notification-service/HrynCo.NotificationService.Web.IntegrationTests/ServiceTemplateNavigationTests.cs
T
agrynco 81654c0d33 feat(templates): add service language template management
Add normalized service and template catalogs, scoped administration, migration, validation, and regression coverage.

Ref: IT-1601
2026-10-17 22:02:43 +03:00

120 lines
9.1 KiB
C#

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<TemplateDatabaseFixture>
{
[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<INotificationServiceRepository>();
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<IEmailTemplateDefinitionRepository>();
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</a>", 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 email templates found", 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<INotificationServiceRepository>().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<IEmailTemplateDefinitionRepository>();
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", "<p>Hello</p>", "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<INotificationServiceRepository>();
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<IEmailTemplateRepository>();
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<IEmailTemplateDefinitionRepository>();
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<IMediator>()
.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);
}
}