feat(templates): add service language template management
Add normalized service and template catalogs, scoped administration, migration, validation, and regression coverage. Ref: IT-1601
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
using HrynCo.NotificationService.Contracts.Messages;
|
||||
using HrynCo.NotificationService.DAL.Abstract.Repositories;
|
||||
using HrynCo.NotificationService.DAL.Abstract.Templates;
|
||||
using HrynCo.NotificationService.DAL.EF;
|
||||
using HrynCo.NotificationService.Worker.Services;
|
||||
using HrynCo.NotificationService.Worker.Services.EmailProcessing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace HrynCo.NotificationService.Web.IntegrationTests;
|
||||
|
||||
public sealed class WorkerTemplateIntegrationTests(TemplateDatabaseFixture fixture) : IClassFixture<TemplateDatabaseFixture>
|
||||
{
|
||||
[Fact]
|
||||
public async Task Worker_UsesExactLanguageContentAndCurrentSharedContract()
|
||||
{
|
||||
var connection = await fixture.CreateDatabaseAsync();
|
||||
using var provider = new ServiceCollection().AddNotificationDataAccess(connection).AddNotificationWorkerServices().BuildServiceProvider();
|
||||
var codes = new[] { "de", "en", "ru", "uk", "ru-RU" };
|
||||
foreach (var code in codes)
|
||||
await AddVersionAsync(provider, code);
|
||||
foreach (var code in codes)
|
||||
{
|
||||
using var readScope = provider.CreateScope();
|
||||
var template = await readScope.ServiceProvider.GetRequiredService<IEmailTemplateService>().GetAsync("TestService", "Delivery", code, default);
|
||||
var rendered = readScope.ServiceProvider.GetRequiredService<IEmailTemplateRenderingService>().Render(template, Message(code));
|
||||
Assert.Equal(code + " A < B", rendered.Subject);
|
||||
Assert.Equal("<p>A < B</p>", rendered.HtmlBody);
|
||||
Assert.Equal("A < B", rendered.TextBody);
|
||||
}
|
||||
using (var updateScope = provider.CreateScope())
|
||||
{
|
||||
var definitions = updateScope.ServiceProvider.GetRequiredService<IEmailTemplateDefinitionRepository>();
|
||||
var definition = (await definitions.GetDefinitionAsync("TestService", "Delivery"))!;
|
||||
definition.Variables = [new EmailTemplateVariable { Name = "Extra", Required = true }];
|
||||
await definitions.SaveDefinitionAsync(definition, false);
|
||||
}
|
||||
using var finalScope = provider.CreateScope();
|
||||
var loader = finalScope.ServiceProvider.GetRequiredService<IEmailTemplateService>();
|
||||
var updated = await loader.GetAsync("TestService", "Delivery", "ru-RU", default);
|
||||
var error = Assert.Throws<InvalidDataException>(() => finalScope.ServiceProvider.GetRequiredService<IEmailTemplateRenderingService>().Render(updated, Message("ru-RU")));
|
||||
Assert.Contains("Extra", error.Message);
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => loader.GetAsync("TestService", "Delivery", "RU-ru", default));
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => loader.GetAsync("TestService", "Missing", "en", default));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Worker_CompatibilityRetryPreservesExactVersionPriorityAndRepositoryCodes()
|
||||
{
|
||||
var connection = await fixture.CreateDatabaseAsync();
|
||||
using var provider = new ServiceCollection().AddNotificationDataAccess(connection).AddNotificationWorkerServices().BuildServiceProvider();
|
||||
await AddVersionAsync(provider, "ru");
|
||||
await AddVersionAsync(provider, "ru-RU");
|
||||
using (var legacyScope = provider.CreateScope())
|
||||
{
|
||||
var loader = legacyScope.ServiceProvider.GetRequiredService<IEmailTemplateService>();
|
||||
Assert.Equal("ru", (await loader.GetAsync("TestService", "Delivery", "RU", default)).LanguageCode);
|
||||
Assert.Equal("ru", (await loader.GetAsync("TestService", "Delivery", " ru ", default)).LanguageCode);
|
||||
Assert.Null(await legacyScope.ServiceProvider.GetRequiredService<IEmailTemplateRepository>().GetAsync("TestService", "Delivery", "RU"));
|
||||
}
|
||||
using (var catalogScope = provider.CreateScope())
|
||||
{
|
||||
var services = catalogScope.ServiceProvider.GetRequiredService<INotificationServiceRepository>();
|
||||
var service = (await services.GetAllAsync()).Single(s => s.Name == "TestService");
|
||||
service.Languages = [..service.Languages, new ServiceLanguageInfo { Code = "RU" }];
|
||||
await services.SaveAsync(service, false);
|
||||
}
|
||||
await AddVersionAsync(provider, "RU");
|
||||
using var exactScope = provider.CreateScope();
|
||||
var exactLoader = exactScope.ServiceProvider.GetRequiredService<IEmailTemplateService>();
|
||||
Assert.Equal("RU", (await exactLoader.GetAsync("TestService", "Delivery", "RU", default)).LanguageCode);
|
||||
Assert.Equal("ru-RU", (await exactLoader.GetAsync("TestService", "Delivery", "ru-RU", default)).LanguageCode);
|
||||
Assert.Equal("ru", (await exactLoader.GetAsync("TestService", "Delivery", "ru", default)).LanguageCode);
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => exactLoader.GetAsync("TestService", "Delivery", "RU-ru", default));
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => exactLoader.GetAsync("TestService", "Delivery", "zz", default));
|
||||
}
|
||||
|
||||
private static async Task AddVersionAsync(ServiceProvider provider, string code)
|
||||
{
|
||||
using var scope = provider.CreateScope();
|
||||
await scope.ServiceProvider.GetRequiredService<IEmailTemplateRepository>().AddAsync(new EmailTemplate
|
||||
{
|
||||
ServiceName = "TestService", Key = "Delivery", LanguageCode = code,
|
||||
Subject = code + " {{Name}}", HtmlBody = "<p>{{Name}}</p>", TextBody = "{{Name}}",
|
||||
Variables = [new EmailTemplateVariable { Name = "Name", Required = true }]
|
||||
});
|
||||
}
|
||||
|
||||
private static SendEmailMessageData Message(string code) => new()
|
||||
{
|
||||
ServiceName = "TestService", TemplateKey = "Delivery", LanguageCode = code,
|
||||
RecipientEmail = "test@example.invalid", RecipientName = "Test", Variables = new Dictionary<string, string> { ["Name"] = "A < B" }
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user