using System.Text.Json; using System.Text.RegularExpressions; using EnvelopeGenerator.Infrastructure.Notification; using Microsoft.Data.SqlClient; namespace EnvelopeGenerator.Tests.Infrastructure; [TestFixture] public class MailTemplatePlaceholderDbTests { private static readonly Regex PlaceholderPattern = new(@"\[[A-Z0-9_]+\]", RegexOptions.Compiled); private static IEnumerable DbTemplateCases() { foreach (var template in ReadTemplates()) { yield return new TestCaseData(template) { TestName = $"Template_{template.Name}_{template.LangCode}_{template.Guid}" }; } } [Test] public void Database_contains_templates() { Assert.That(ReadTemplates(), Is.Not.Empty); } [TestCaseSource(nameof(DbTemplateCases))] public void Every_placeholder_in_every_db_template_is_replaced(TemplateRow template) { var data = new TemplatePlaceholders(); var subject = template.Subject.ReplacePlaceholders(data); var body = template.Body.ReplacePlaceholders(data); var originalTokens = PlaceholderPattern .Matches(template.Subject + template.Body) .Select(m => m.Value) .Distinct() .ToArray(); Assert.Multiple(() => { Assert.That(Unresolved(subject), Is.Empty, $"'{template.Name}' subject has unresolved placeholders."); Assert.That(Unresolved(body), Is.Empty, $"'{template.Name}' body has unresolved placeholders."); foreach (var token in originalTokens) { Assert.That(subject + body, Does.Contain(Expected(token)), $"'{template.Name}' placeholder {token} was not replaced with its value."); } }); } private static string[] Unresolved(string text) => PlaceholderPattern.Matches(text) .Select(m => m.Value) .Distinct() .ToArray(); private static string Expected(string token) => $"__{token.Trim('[', ']')}__"; private static List ReadTemplates() { const string sql = "SELECT GUID, NAME, SUBJECT, BODY, LANG_CODE FROM TBSIG_EMAIL_TEMPLATE"; using var connection = new SqlConnection(ReadConnectionString()); connection.Open(); using var command = new SqlCommand(sql, connection); using var reader = command.ExecuteReader(); var templates = new List(); while (reader.Read()) { templates.Add(new TemplateRow( reader.GetInt32(0), reader.GetString(1), reader.IsDBNull(2) ? string.Empty : reader.GetString(2), reader.IsDBNull(3) ? string.Empty : reader.GetString(3), reader.IsDBNull(4) ? string.Empty : reader.GetString(4))); } return templates; } private static string ReadConnectionString() { var directory = new DirectoryInfo(AppContext.BaseDirectory); while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "EnvelopeGenerator.sln"))) directory = directory.Parent; if (directory is null) throw new InvalidOperationException("Solution root (EnvelopeGenerator.sln) was not found."); var settingsPath = Path.Combine( directory.FullName, "EnvelopeGenerator.Server", "EnvelopeGenerator.Server", "appsettings.json"); using var stream = File.OpenRead(settingsPath); using var document = JsonDocument.Parse(stream, new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true }); return document.RootElement .GetProperty("ConnectionStrings") .GetProperty("Default") .GetString() ?? throw new InvalidOperationException($"ConnectionStrings:Default is missing in '{settingsPath}'."); } public sealed record TemplateRow(int Guid, string Name, string Subject, string Body, string LangCode); /// /// One property per placeholder used by any row in TBSIG_EMAIL_TEMPLATE. /// The sentinel value equals the placeholder name, so a wrong mapping is detected. /// private sealed class TemplatePlaceholders { public string DocumentProcess { get; set; } = "__DOCUMENT_PROCESS__"; public string DocumentTitle { get; set; } = "__DOCUMENT_TITLE__"; public string LinkToDocumentText { get; set; } = "__LINK_TO_DOCUMENT_TEXT__"; public string LinkToDocument { get; set; } = "__LINK_TO_DOCUMENT__"; public string Message { get; set; } = "__MESSAGE__"; public string NamePortal { get; set; } = "__NAME_PORTAL__"; public string NameReceiver { get; set; } = "__NAME_RECEIVER__"; public string NameSender { get; set; } = "__NAME_SENDER__"; public string SignatureType { get; set; } = "__SIGNATURE_TYPE__"; public string Reason { get; set; } = "__REASON__"; public string ReceiverAction { get; set; } = "__RECEIVER_ACTION__"; public string FinalAction { get; set; } = "__FINAL_ACTION__"; public string FinalStatus { get; set; } = "__FINAL_STATUS__"; public string DocumentAccessCode { get; set; } = "__DOCUMENT_ACCESS_CODE__"; public string TfaExpiration { get; set; } = "__TFA_EXPIRATION__"; public string TfaQrCode { get; set; } = "__TFA_QR_CODE__"; } }