Add DTOs and tests for placeholder replacement
Added new record types `CreateEnvelopeDocumentDto`, `CreateEnvelopeFieldDto`, and `CreateEnvelopeReceiverDto` with XML documentation in `CreateEnvelopeCommand.cs`. Marked `ConfigService` as obsolete in `CreateEnvelopeCommandHandler.cs`. Modified `CertificationType` usage in `Fake.cs` to use the enum directly. Updated `EnvelopeGenerator.Tests.csproj` with a new project reference and compile directives. Introduced `MailTemplatePlaceholderDbTests` and `ReplacePlaceholderExtensionsTests` to verify placeholder replacement functionality. Added supporting model classes for testing. These changes improve code functionality and test coverage.
This commit is contained in:
@@ -130,10 +130,27 @@ public record CreateEnvelopeCommand : IRequest<EnvelopeDto?>
|
||||
public bool UseSQLExecutor { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="DataAsBase64"></param>
|
||||
public record CreateEnvelopeDocumentDto(string DataAsBase64);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="X"></param>
|
||||
/// <param name="Y"></param>
|
||||
/// <param name="Page"></param>
|
||||
public record CreateEnvelopeFieldDto(double X, double Y, int Page);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="EmailAddress"></param>
|
||||
/// <param name="Name"></param>
|
||||
/// <param name="PhoneNumber"></param>
|
||||
/// <param name="Fields"></param>
|
||||
public record CreateEnvelopeReceiverDto(
|
||||
string EmailAddress,
|
||||
string? Name,
|
||||
|
||||
@@ -49,6 +49,7 @@ public class CreateEnvelopeCommandHandler : IRequestHandler<CreateEnvelopeComman
|
||||
|
||||
private SignatureFieldParams SignatureFieldParams => _provider.GetRequiredService<IOptionsMonitor<SignatureFieldParams>>().CurrentValue;
|
||||
|
||||
[Obsolete("Use IConfigService instead.")]
|
||||
private IConfigService ConfigService => _provider.GetRequiredService<IConfigService>();
|
||||
|
||||
private IMediator Mediator => _provider.GetRequiredService<IMediator>();
|
||||
|
||||
@@ -215,7 +215,7 @@ public static class Extensions
|
||||
Message = faker.Lorem.Paragraph(faker.Random.Number(2, 5)),
|
||||
TfaEnabled = tfaEnabled,
|
||||
AddedWhen = DateTime.Now,
|
||||
CertificationType = (int)CertificationType.AdvancedElectronicSignature,
|
||||
CertificationType = CertificationType.AdvancedElectronicSignature,
|
||||
UseAccessCode = false,
|
||||
ContractType = (int)ContractType.Contract,
|
||||
Language = "de",
|
||||
|
||||
@@ -71,10 +71,12 @@
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure.Crypt\EnvelopeGenerator.Infrastructure.Crypt.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure.Notification\EnvelopeGenerator.Infrastructure.Notification.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<Compile Remove="Application\Crypt\**\*.cs" />
|
||||
<Compile Remove="Infrastructure\**\*.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
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<TestCaseData> 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<TemplateRow> 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<TemplateRow>();
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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__";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.Services.Notification;
|
||||
using EnvelopeGenerator.Infrastructure.Notification;
|
||||
|
||||
namespace EnvelopeGenerator.Tests.Infrastructure;
|
||||
|
||||
[TestFixture]
|
||||
public class ReplacePlaceholderExtensionsTests
|
||||
{
|
||||
#region Naming
|
||||
|
||||
private static IEnumerable<TestCaseData> NamingCases()
|
||||
{
|
||||
yield return Naming<PersonModel>("[FIRST_NAME]");
|
||||
yield return Naming<PersonModel>("[LAST_NAME]");
|
||||
yield return Naming<IdModel>("[ID]");
|
||||
yield return Naming<UpperIdModel>("[ID]");
|
||||
yield return Naming<AcronymModel>("[URL_VALUE]");
|
||||
yield return Naming<AcronymModel>("[HTTP_STATUS_CODE]");
|
||||
yield return Naming<AcronymModel>("[XML_HTTP_REQUEST]");
|
||||
yield return Naming<AcronymModel>("[OTP]");
|
||||
yield return Naming<DigitModel>("[FIELD1]");
|
||||
yield return Naming<DigitModel>("[VALUE2_TEXT]");
|
||||
yield return Naming<DigitModel>("[A1_B2]");
|
||||
yield return Naming<DigitModel>("[NAME2]");
|
||||
yield return Naming<MyUrl2ValueModel>("[MY_URL2_VALUE]");
|
||||
yield return Naming<SingleLetterModel>("[X]");
|
||||
yield return Naming<LowerCaseModel>("[FIRST_NAME]");
|
||||
yield return Naming<SnakeCaseModel>("[FIRST_NAME]");
|
||||
yield return Naming<TfaModel>("[TFA_EXPIRATION]");
|
||||
}
|
||||
|
||||
private static TestCaseData Naming<T>(string placeholder) where T : new()
|
||||
=> new(new T(), placeholder)
|
||||
{
|
||||
TestName = $"Placeholder_{typeof(T).Name}_{placeholder.Trim('[', ']')}"
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(NamingCases))]
|
||||
public void Property_names_are_converted_to_upper_snake_case(object data, string placeholder)
|
||||
{
|
||||
var result = placeholder.ReplacePlaceholders(data);
|
||||
|
||||
Assert.That(result, Is.EqualTo("v"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[SetCulture("tr-TR")]
|
||||
public void Placeholder_names_are_culture_invariant()
|
||||
{
|
||||
var result = "[INFO]".ReplacePlaceholders(new LowerCaseInfoModel());
|
||||
|
||||
Assert.That(result, Is.EqualTo("v"), "Turkish casing would produce [\u0130NFO]");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Basic replacement
|
||||
|
||||
[Test]
|
||||
public void Replaces_single_placeholder()
|
||||
{
|
||||
var result = "[FIRST_NAME]".ReplacePlaceholders(new PersonModel { FirstName = "Ada" });
|
||||
|
||||
Assert.That(result, Is.EqualTo("Ada"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Replaces_multiple_placeholders()
|
||||
{
|
||||
var data = new PersonModel { FirstName = "Ada", LastName = "Lovelace" };
|
||||
|
||||
var result = "[FIRST_NAME] [LAST_NAME]".ReplacePlaceholders(data);
|
||||
|
||||
Assert.That(result, Is.EqualTo("Ada Lovelace"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Replaces_all_repeated_occurrences()
|
||||
{
|
||||
var data = new PersonModel { FirstName = "Ada" };
|
||||
|
||||
var result = "[FIRST_NAME]-[FIRST_NAME]-[FIRST_NAME]".ReplacePlaceholders(data);
|
||||
|
||||
Assert.That(result, Is.EqualTo("Ada-Ada-Ada"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Leaves_unknown_placeholder_untouched()
|
||||
{
|
||||
var result = "[UNKNOWN_TOKEN]".ReplacePlaceholders(new PersonModel());
|
||||
|
||||
Assert.That(result, Is.EqualTo("[UNKNOWN_TOKEN]"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Returns_template_unchanged_when_it_has_no_placeholders()
|
||||
{
|
||||
var result = "Hello world".ReplacePlaceholders(new PersonModel());
|
||||
|
||||
Assert.That(result, Is.EqualTo("Hello world"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Returns_empty_string_for_empty_template()
|
||||
{
|
||||
var result = string.Empty.ReplacePlaceholders(new PersonModel());
|
||||
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Returns_template_unchanged_when_data_has_no_readable_properties()
|
||||
{
|
||||
var result = "[ANYTHING]".ReplacePlaceholders(new object());
|
||||
|
||||
Assert.That(result, Is.EqualTo("[ANYTHING]"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Matching_is_case_sensitive()
|
||||
{
|
||||
var result = "[name]|[Name]".ReplacePlaceholders(new PersonNameModel { Name = "v" });
|
||||
|
||||
Assert.That(result, Is.EqualTo("[name]|[Name]"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Placeholder_without_brackets_is_untouched()
|
||||
{
|
||||
var result = "NAME Receiver".ReplacePlaceholders(new PersonNameModel { Name = "v" });
|
||||
|
||||
Assert.That(result, Is.EqualTo("NAME Receiver"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Double_brackets_leave_the_outer_brackets()
|
||||
{
|
||||
var result = "[[NAME]]".ReplacePlaceholders(new PersonNameModel { Name = "v" });
|
||||
|
||||
Assert.That(result, Is.EqualTo("[v]"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Values
|
||||
|
||||
[Test]
|
||||
public void Null_value_is_replaced_with_empty_string()
|
||||
{
|
||||
var result = "[NAME]".ReplacePlaceholders(new { Name = (string?)null });
|
||||
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Custom_ToString_is_used()
|
||||
{
|
||||
var result = "[VALUE]".ReplacePlaceholders(new { Value = new CustomValue() });
|
||||
|
||||
Assert.That(result, Is.EqualTo("custom"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Primitive_values_use_ToString()
|
||||
{
|
||||
var result = "[COUNT]|[ENABLED]".ReplacePlaceholders(new { Count = 3, Enabled = true });
|
||||
|
||||
Assert.That(result, Is.EqualTo("3|True"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Regex_like_characters_are_inserted_literally()
|
||||
{
|
||||
var result = "[PATTERN]".ReplacePlaceholders(new { Pattern = @"$1 {0} \ $&" });
|
||||
|
||||
Assert.That(result, Is.EqualTo(@"$1 {0} \ $&"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Value_containing_its_own_placeholder_is_not_expanded_recursively()
|
||||
{
|
||||
var result = "[NAME]".ReplacePlaceholders(new EchoNameModel());
|
||||
|
||||
Assert.That(result, Is.EqualTo("[NAME]"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unicode_value_is_preserved()
|
||||
{
|
||||
var result = "[NAME]".ReplacePlaceholders(new PersonNameModel { Name = "Z\u00fcrich \ud83d\ude00" });
|
||||
|
||||
Assert.That(result, Is.EqualTo("Z\u00fcrich \ud83d\ude00"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Property discovery
|
||||
|
||||
[Test]
|
||||
public void Public_readable_property_is_replaced()
|
||||
{
|
||||
var result = "[SHOWN]".ReplacePlaceholders(new DiscoveryModel());
|
||||
|
||||
Assert.That(result, Is.EqualTo("shown"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_only_property_is_replaced()
|
||||
{
|
||||
var result = "[READ_ONLY]".ReplacePlaceholders(new DiscoveryModel());
|
||||
|
||||
Assert.That(result, Is.EqualTo("computed"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Static_private_write_only_and_indexer_properties_are_ignored()
|
||||
{
|
||||
const string template = "[HIDDEN]|[STATIC]|[WRITE_ONLY]|[ITEM]";
|
||||
|
||||
var result = template.ReplacePlaceholders(new DiscoveryModel());
|
||||
|
||||
Assert.That(result, Is.EqualTo(template));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Inherited_public_property_is_replaced()
|
||||
{
|
||||
var result = "[ID]|[NAME]".ReplacePlaceholders(new DerivedModel());
|
||||
|
||||
Assert.That(result, Is.EqualTo("base|derived"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Record_properties_are_replaced()
|
||||
{
|
||||
var result = "[FIRST_NAME]/[AGE]".ReplacePlaceholders(new PersonRecord("Ada", 36));
|
||||
|
||||
Assert.That(result, Is.EqualTo("Ada/36"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Struct_data_is_supported()
|
||||
{
|
||||
var result = "[NAME]".ReplacePlaceholders(new StructModel { Name = "struct" });
|
||||
|
||||
Assert.That(result, Is.EqualTo("struct"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Anonymous_type_is_supported()
|
||||
{
|
||||
var data = new { Name = "anon", Age = 42 };
|
||||
|
||||
var result = "[NAME]|[AGE]".ReplacePlaceholders(data);
|
||||
|
||||
Assert.That(result, Is.EqualTo("anon|42"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Generic_type_is_supported_for_reference_type()
|
||||
{
|
||||
var result = "[VALUE]".ReplacePlaceholders(new Box<string> { Value = "boxed" });
|
||||
|
||||
Assert.That(result, Is.EqualTo("boxed"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Generic_type_is_supported_for_value_type()
|
||||
{
|
||||
var result = "[VALUE]".ReplacePlaceholders(new Box<int> { Value = 5 });
|
||||
|
||||
Assert.That(result, Is.EqualTo("5"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cache and thread safety
|
||||
|
||||
[Test]
|
||||
public void Cached_metadata_uses_values_of_current_instance()
|
||||
{
|
||||
var first = "[FIRST_NAME]".ReplacePlaceholders(new PersonModel { FirstName = "first" });
|
||||
var second = "[FIRST_NAME]".ReplacePlaceholders(new PersonModel { FirstName = "second" });
|
||||
|
||||
Assert.That(new[] { first, second }, Is.EqualTo(new[] { "first", "second" }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Two_types_with_same_property_name_are_resolved_independently()
|
||||
{
|
||||
var a = "[NAME]".ReplacePlaceholders(new TypeA());
|
||||
var b = "[NAME]".ReplacePlaceholders(new TypeB());
|
||||
|
||||
Assert.That(new[] { a, b }, Is.EqualTo(new[] { "a", "b" }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Concurrent_first_calls_are_thread_safe()
|
||||
{
|
||||
var failures = 0;
|
||||
|
||||
Parallel.For(0, 500, i =>
|
||||
{
|
||||
var expected = $"n{i}";
|
||||
var actual = "[NAME]".ReplacePlaceholders(new ConcurrentModel { Name = expected });
|
||||
|
||||
if (actual != expected)
|
||||
Interlocked.Increment(ref failures);
|
||||
});
|
||||
|
||||
Assert.That(failures, Is.Zero);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Real DTO
|
||||
|
||||
[Test]
|
||||
public void Document_received_notification_is_fully_replaced()
|
||||
{
|
||||
var data = new DocumentReceivedNotification
|
||||
{
|
||||
DocumentTitle = "Vertrag",
|
||||
NameReceiver = "Max",
|
||||
NameSender = "Erika",
|
||||
IsReadAndConfirm = true,
|
||||
AdvancedSignature = false,
|
||||
LinkToDocument = "https://x.io/a",
|
||||
Message = "Bitte",
|
||||
NamePortal = "Portal"
|
||||
};
|
||||
|
||||
const string template =
|
||||
"[NAME_RECEIVER]|[NAME_SENDER]|[SIGNATURE_TYPE]|[DOCUMENT_TITLE]|[MESSAGE]|[NAME_PORTAL]|[LINK_TO_DOCUMENT]|[LINK_TO_DOCUMENT_TEXT]";
|
||||
|
||||
var result = template.ReplacePlaceholders(data);
|
||||
|
||||
Assert.That(result, Is.EqualTo("Max|Erika|Signieren|Vertrag|Bitte|Portal|https://x.io/a|https://x.io/a"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Models
|
||||
|
||||
private sealed class PersonModel
|
||||
{
|
||||
public string FirstName { get; set; } = "v";
|
||||
public string LastName { get; set; } = "v";
|
||||
}
|
||||
|
||||
private sealed class PersonNameModel
|
||||
{
|
||||
public string Name { get; set; } = "v";
|
||||
}
|
||||
|
||||
private sealed class IdModel
|
||||
{
|
||||
public string Id { get; set; } = "v";
|
||||
}
|
||||
|
||||
private sealed class UpperIdModel
|
||||
{
|
||||
public string ID { get; set; } = "v";
|
||||
}
|
||||
|
||||
private sealed class AcronymModel
|
||||
{
|
||||
public string URLValue { get; set; } = "v";
|
||||
public string HTTPStatusCode { get; set; } = "v";
|
||||
public string XMLHttpRequest { get; set; } = "v";
|
||||
public string OTP { get; set; } = "v";
|
||||
}
|
||||
|
||||
private sealed class DigitModel
|
||||
{
|
||||
public string Field1 { get; set; } = "v";
|
||||
public string Value2Text { get; set; } = "v";
|
||||
public string A1B2 { get; set; } = "v";
|
||||
public string Name2 { get; set; } = "v";
|
||||
}
|
||||
|
||||
private sealed class MyUrl2ValueModel
|
||||
{
|
||||
public string MyURL2Value { get; set; } = "v";
|
||||
}
|
||||
|
||||
private sealed class SingleLetterModel
|
||||
{
|
||||
public string X { get; set; } = "v";
|
||||
}
|
||||
|
||||
private sealed class LowerCaseModel
|
||||
{
|
||||
public string firstName { get; set; } = "v";
|
||||
}
|
||||
|
||||
private sealed class LowerCaseInfoModel
|
||||
{
|
||||
public string info { get; set; } = "v";
|
||||
}
|
||||
|
||||
private sealed class SnakeCaseModel
|
||||
{
|
||||
public string First_Name { get; set; } = "v";
|
||||
}
|
||||
|
||||
private sealed class TfaModel
|
||||
{
|
||||
public string TFAExpiration { get; set; } = "v";
|
||||
}
|
||||
|
||||
private sealed class CustomValue
|
||||
{
|
||||
public override string ToString() => "custom";
|
||||
}
|
||||
|
||||
private sealed class EchoNameModel
|
||||
{
|
||||
public string Name { get; set; } = "[NAME]";
|
||||
}
|
||||
|
||||
private sealed class DiscoveryModel
|
||||
{
|
||||
public static string Static { get; set; } = "static";
|
||||
|
||||
public string ReadOnly => "computed";
|
||||
|
||||
public string WriteOnly
|
||||
{
|
||||
set { }
|
||||
}
|
||||
|
||||
public string this[int index] => "indexer";
|
||||
|
||||
public string Shown { get; set; } = "shown";
|
||||
|
||||
private string Hidden { get; set; } = "hidden";
|
||||
}
|
||||
|
||||
private class BaseModel
|
||||
{
|
||||
public string Id { get; set; } = "base";
|
||||
}
|
||||
|
||||
private sealed class DerivedModel : BaseModel
|
||||
{
|
||||
public string Name { get; set; } = "derived";
|
||||
}
|
||||
|
||||
private sealed record PersonRecord(string FirstName, int Age);
|
||||
|
||||
private struct StructModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
private sealed class Box<T>
|
||||
{
|
||||
public T Value { get; set; } = default!;
|
||||
}
|
||||
|
||||
private sealed class TypeA
|
||||
{
|
||||
public string Name { get; set; } = "a";
|
||||
}
|
||||
|
||||
private sealed class TypeB
|
||||
{
|
||||
public string Name { get; set; } = "b";
|
||||
}
|
||||
|
||||
private sealed class ConcurrentModel
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user