8e81572386
Add revision-safe service and template APIs, channel mapping, and validated admin controls. Ref: IT-1601
120 lines
9.1 KiB
C#
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 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<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);
|
|
}
|
|
}
|