16 Commits

Author SHA1 Message Date
b3dfdd1e5c Update namespaces to Common.Dto for DTO-related files
Refactored PlaceholderExtensions, DtoMappingProfile, and InvokeActionTests
to use the ReC.Application.Common.Dto namespace instead of
ReC.Application.Common.Behaviors.Action. Updated using directives and
namespaces to improve code organization for DTO-related logic.
2026-03-26 16:52:54 +01:00
bd78ada686 Remove all DTO class definitions from application
Deleted the contents of several DTO files, including ConnectionDto, EndpointAuthDto, EndpointDto, EndpointParamDto, OutResDto, ProfileDto, and RecActionDto. These files previously contained record definitions for data transfer objects used throughout the application. All related code, including properties and using directives, has been removed, leaving the files empty.
2026-03-26 15:48:49 +01:00
2b4773a4c0 Remove Root and ActionId from ResultViewDto
Removed the Root (OutResDto?) and ActionId (long?) properties from the ResultViewDto record to simplify its structure and remove unused fields.
2026-03-26 15:48:35 +01:00
ff7d6c99ae Update RecActionViewDto mapping and add Profile property
Refactored DtoMappingProfile to pass both the source object and its Profile to ReplacePlaceholders when mapping queries. Added a nullable Profile property to RecActionViewDto to include full profile details in the DTO.
2026-03-26 15:48:06 +01:00
fa438e70cb Allow ReplacePlaceholders to handle null objects safely
Updated ReplacePlaceholders to accept nullable objects and skip nulls during placeholder resolution, preventing NullReferenceExceptions when nulls are passed in the objects array.
2026-03-26 15:32:00 +01:00
4931d3b8aa Map queries with placeholders replaced in DTOs
Updated RecActionView to RecActionViewDto mapping to replace placeholders in PreprocessingQuery and PostprocessingQuery using ReplacePlaceholders. Added necessary using directive for the extension method.
2026-03-26 15:28:08 +01:00
6aae26bfb6 Update using directive to new Action namespace
Replaced the using directive for InvokeAction with Action in InvokeActionTests.cs to reflect recent namespace reorganization. No other changes were made.
2026-03-26 15:23:30 +01:00
38d8ef6e93 Update namespace in PlaceholderExtensions.cs
Changed namespace from ReC.Application.Common.Behaviors.InvokeAction to ReC.Application.Common.Behaviors.Action for consistency. No other code changes were made.
2026-03-26 14:58:01 +01:00
7bc5428bd4 Refactor: move query behaviors to Action namespace
Updated the namespaces for BodyQueryBehavior and HeaderQueryBehavior from ReC.Application.Common.Behaviors to ReC.Application.Common.Behaviors.Action. Adjusted related imports in DependencyInjection.cs to reflect this change for improved code organization.
2026-03-26 14:40:50 +01:00
c405f369ac Add unit tests for InvokeAction placeholder replacement
Introduce InvokeActionTests.cs with comprehensive tests for:
- Placeholder replacement logic (ReplacePlaceholders) across int, bool, string, DateTime, DateTimeOffset types, multiple placeholders, and various prefix formats.
- Value extraction by column name (GetValueByColumnName), including models with and without [Column] attributes.
- Exception handling for unresolvable placeholders and invalid input scenarios.
These tests ensure robust coverage of both normal and error cases.
2026-03-26 14:37:58 +01:00
c2e073dade Add ReplacePlaceholders for SQL-style string interpolation
Introduce ReplacePlaceholders to PlaceholderExtensions, enabling replacement of {#...#COLUMN_NAME} placeholders in strings with property values from provided objects. Uses a generated regex for matching and converts values to SQL-compatible literals. Throws PlaceholderResolutionException if a column cannot be resolved. Refactored class to partial to support regex generation.
2026-03-26 14:37:29 +01:00
7a11ac3635 Add PlaceholderResolutionException for unresolved placeholders
Introduced PlaceholderResolutionException in the ReC.Application.Common.Exceptions namespace. This exception provides detailed context when a placeholder cannot be resolved due to a missing property with a specific column name, including the placeholder, column name, and input string.
2026-03-26 14:36:35 +01:00
a91e3264b4 Remove Action folder reference from project file
The <Folder Include="Common\Behaviors\Action\" /> entry was removed from ReC.Application.csproj, so the folder is no longer explicitly included in the project structure. No code or project references were affected.
2026-03-26 14:35:39 +01:00
2ae5251550 Rename class and update namespace for placeholder logic
Renamed the ReflectionExtensions class to PlaceholderExtensions and moved it from the ReC.Domain.Extensions namespace to ReC.Application.Common.Behaviors.InvokeAction to better reflect its purpose and location within the project structure.
2026-03-26 13:48:16 +01:00
56730c0d4e Make GetValueByColumnName a generic extension method
Refactored GetValueByColumnName to use a generic type parameter constrained to class types. This enhances type safety and enables better type inference and static analysis when accessing property values by column name.
2026-03-26 13:42:45 +01:00
d8aa032a57 Add ReflectionExtensions for property lookup by column name
Introduced a static ReflectionExtensions class with a GetValueByColumnName extension method to retrieve property values by their [Column] attribute name. Also removed an unused folder reference from the project file.
2026-03-26 13:16:04 +01:00
17 changed files with 343 additions and 260 deletions

View File

@@ -3,7 +3,7 @@ using ReC.Application.Common.Dto;
using ReC.Application.Common.Interfaces; using ReC.Application.Common.Interfaces;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace ReC.Application.Common.Behaviors; namespace ReC.Application.Common.Behaviors.Action;
public class BodyQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext) : IPipelineBehavior<TRequest, TResponse> public class BodyQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext) : IPipelineBehavior<TRequest, TResponse>
where TRequest : RecActionViewDto where TRequest : RecActionViewDto

View File

@@ -5,7 +5,7 @@ using ReC.Application.Common.Dto;
using ReC.Application.Common.Interfaces; using ReC.Application.Common.Interfaces;
using System.Text.Json; using System.Text.Json;
namespace ReC.Application.Common.Behaviors; namespace ReC.Application.Common.Behaviors.Action;
public class HeaderQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext, ILogger<HeaderQueryBehavior<TRequest, TResponse>>? logger = null) : IPipelineBehavior<TRequest, TResponse> public class HeaderQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext, ILogger<HeaderQueryBehavior<TRequest, TResponse>>? logger = null) : IPipelineBehavior<TRequest, TResponse>
where TRequest : RecActionViewDto where TRequest : RecActionViewDto

View File

@@ -1,32 +0,0 @@
namespace ReC.Application.Common.Dto;
public record ConnectionDto
{
public short? Id { get; set; }
public string? Bezeichnung { get; set; }
public string? SqlProvider { get; set; }
public string? Server { get; set; }
public string? Datenbank { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public string? Bemerkung { get; set; }
public bool? Aktiv { get; set; }
public string? ErstelltWer { get; set; }
public DateTime? ErstelltWann { get; set; }
public string? GeandertWer { get; set; }
public DateTime? GeaendertWann { get; set; }
public bool? SysConnection { get; set; }
}

View File

@@ -1,4 +1,4 @@
using ReC.Domain.Views; using ReC.Domain.Views;
namespace ReC.Application.Common.Dto; namespace ReC.Application.Common.Dto;
@@ -6,7 +6,12 @@ public class DtoMappingProfile : AutoMapper.Profile
{ {
public DtoMappingProfile() public DtoMappingProfile()
{ {
CreateMap<RecActionView, RecActionViewDto>(); CreateMap<RecActionView, RecActionViewDto>()
.ForMember(dest => dest.PreprocessingQuery, opt => opt.MapFrom((src, _) =>
src.PreprocessingQuery?.ReplacePlaceholders(src, src.Profile)))
.ForMember(dest => dest.PostprocessingQuery, opt => opt.MapFrom((src, _) =>
src.PostprocessingQuery?.ReplacePlaceholders(src, src.Profile)));
CreateMap<ResultView, ResultViewDto>(); CreateMap<ResultView, ResultViewDto>();
CreateMap<ProfileView, ProfileViewDto>(); CreateMap<ProfileView, ProfileViewDto>();
} }

View File

@@ -1,39 +0,0 @@
using ReC.Domain.Constants;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
public record EndpointAuthDto
{
public long? Id { get; set; }
public bool? Active { get; set; }
public string? Description { get; set; }
public EndpointAuthType? Type { get; set; }
public string? ApiKey { get; set; }
public string? ApiValue { get; set; }
public ApiKeyLocation? ApiKeyAddTo { get; set; }
public string? Token { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public string? Domain { get; set; }
public string? Workstation { get; set; }
public string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -1,22 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
public record EndpointDto
{
public long Id { get; set; }
public bool? Active { get; set; }
public string? Description { get; set; }
public string? Uri { get; set; }
public string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -1,33 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
/// <summary>
/// Represents the TBREC_CFG_ENDPOINT_PARAMS table.
/// All properties are nullable to provide flexibility on the database side,
/// preventing breaking changes if columns are altered to be nullable in production.
/// </summary>
public record EndpointParamDto
{
public long? Id { get; set; }
public bool? Active { get; set; }
public string? Description { get; set; }
public short? GroupId { get; set; }
public byte? Sequence { get; set; }
public string? Key { get; set; }
public string? Value { get; set; }
public string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -1,40 +0,0 @@
using ReC.Domain.Constants;
namespace ReC.Application.Common.Dto;
public record OutResDto
{
public long Id { get; set; }
public long? ActionId { get; set; }
public RecActionDto? Action { get; set; }
public long? ProfileId { get; set; }
public ProfileDto? Profile { get; set; }
public string? ProfileName { get; set; }
public short? StatusCode { get; set; }
public string? StatusName { get; set; }
public ResultType? Type { get; set; }
public string? Header { get; set; }
public string? Body { get; set; }
public string? Info { get; set; }
public string? Error { get; set; }
public string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -0,0 +1,63 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using System.Text.RegularExpressions;
using ReC.Application.Common.Exceptions;
namespace ReC.Application.Common.Dto;
public static partial class PlaceholderExtensions
{
[GeneratedRegex(@"\{#[^#]+#[^}]+\}")]
private static partial Regex PlaceholderRegex();
/// <summary>
/// Replaces placeholders in the format <c>{#ANY_STRING#COLUMN_NAME}</c> with the corresponding
/// property value resolved via <see cref="GetValueByColumnName{T}"/> from the provided objects.
/// Values are converted to SQL-compatible string representations.
/// </summary>
/// <exception cref="PlaceholderResolutionException">
/// Thrown when a placeholder's column name cannot be resolved from any of the provided objects.
/// </exception>
public static string ReplacePlaceholders(this string str, params object?[] objects)
{
return PlaceholderRegex().Replace(str, match =>
{
var placeholder = match.Value;
var inner = placeholder[2..^1]; // remove {# and }
var lastHash = inner.LastIndexOf('#');
var columnName = inner[(lastHash + 1)..];
foreach (var obj in objects)
{
if (obj is null)
continue;
var value = obj.GetValueByColumnName(columnName);
if (value is not null)
return ToSqlLiteral(value);
}
throw new PlaceholderResolutionException(placeholder, columnName, str);
});
}
private static string ToSqlLiteral(object value) => value switch
{
bool b => b ? "TRUE" : "FALSE",
DateTime dt => dt.ToString("yyyy-MM-dd HH:mm:ss"),
DateTimeOffset dto => dto.ToString("yyyy-MM-dd HH:mm:ss zzz"),
_ => value.ToString() ?? string.Empty
};
/// <summary>
/// Gets the value of a property by its column name defined in <see cref="ColumnAttribute"/>.
/// Returns <c>null</c> if no property with the given column name exists.
/// </summary>
public static object? GetValueByColumnName<T>(this T obj, string columnName) where T : class
{
var property = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
.FirstOrDefault(p => p.GetCustomAttribute<ColumnAttribute>()?.Name == columnName);
return property?.GetValue(obj);
}
}

View File

@@ -1,30 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
public record ProfileDto
{
public long Id { get; set; }
public bool? Active { get; set; }
public string? Type { get; set; }
public string? Mandantor { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public string? LogLevel { get; set; }
public string? Language { get; set; }
public string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -1,53 +0,0 @@
using ReC.Domain.Constants;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
public record RecActionDto
{
public long? Id { get; set; }
public long? ProfileId { get; set; }
public ProfileDto? Profile { get; set; }
public bool? Active { get; set; }
public byte? Sequence { get; set; }
public long? EndpointId { get; set; }
public EndpointDto? Endpoint { get; set; }
public long? EndpointAuthId { get; set; }
public EndpointAuthDto? EndpointAuth { get; set; }
public short? EndpointParamsId { get; set; }
public short? SqlConnectionId { get; set; }
public ConnectionDto? SqlConnection { get; set; }
public string? Type { get; set; }
public string? PreprocessingQuery { get; set; }
public string? HeaderQuery { get; set; }
public string? BodyQuery { get; set; }
public string? PostprocessingQuery { get; set; }
public ErrorAction? ErrorAction { get; set; }
public string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
public OutResDto? OutRes { get; set; }
}

View File

@@ -8,6 +8,8 @@ public record RecActionViewDto
public long? ProfileId { get; init; } public long? ProfileId { get; init; }
public ProfileViewDto? Profile { get; init; }
public string? ProfileName { get; init; } public string? ProfileName { get; init; }
public ProfileType? ProfileType { get; init; } public ProfileType? ProfileType { get; init; }

View File

@@ -4,8 +4,6 @@ public record ResultViewDto
{ {
public long Id { get; init; } public long Id { get; init; }
public OutResDto? Root { get; init; }
public long? ActionId { get; init; } public long? ActionId { get; init; }
public RecActionViewDto? Action { get; init; } public RecActionViewDto? Action { get; init; }

View File

@@ -0,0 +1,11 @@
namespace ReC.Application.Common.Exceptions;
public class PlaceholderResolutionException(string placeholder, string columnName, string input)
: Exception($"Failed to resolve placeholder '{placeholder}'. No object contains a property with column name '{columnName}'. Input: '{input}'")
{
public string Placeholder { get; } = placeholder;
public string ColumnName { get; } = columnName;
public string Input { get; } = input;
}

View File

@@ -3,6 +3,7 @@ using MediatR;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using ReC.Application.Common.Behaviors; using ReC.Application.Common.Behaviors;
using ReC.Application.Common.Behaviors.Action;
using ReC.Application.Common.Behaviors.InvokeAction; using ReC.Application.Common.Behaviors.InvokeAction;
using ReC.Application.Common.Constants; using ReC.Application.Common.Constants;
using ReC.Application.Common.Options; using ReC.Application.Common.Options;

View File

@@ -23,9 +23,4 @@
<ProjectReference Include="..\ReC.Domain\ReC.Domain.csproj" /> <ProjectReference Include="..\ReC.Domain\ReC.Domain.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Common\Behaviors\Action\" />
<Folder Include="Common\Options\DbModel\" />
</ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,257 @@
using System.ComponentModel.DataAnnotations.Schema;
using ReC.Application.Common.Dto;
using ReC.Application.Common.Exceptions;
namespace ReC.Tests.Application.Behaviors;
#region Test Models
public class Foo
{
[Column("BAZ")]
public int Baz { get; set; }
[Column("FOO_NAME")]
public string FooName { get; set; } = string.Empty;
}
public class Bar
{
[Column("QUX")]
public bool Qux { get; set; }
[Column("BAR_DATE")]
public DateTime BarDate { get; set; }
}
public class Fuz
{
[Column("QUZ")]
public string Quz { get; set; } = string.Empty;
[Column("FUZ_OFFSET")]
public DateTimeOffset FuzOffset { get; set; }
}
public class NoColumnModel
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
#endregion
[TestFixture]
public class InvokeActionTests
{
private Foo _foo = null!;
private Bar _bar = null!;
private Fuz _fuz = null!;
[SetUp]
public void Setup()
{
_foo = new Foo { Baz = 2, FooName = "TestFoo" };
_bar = new Bar { Qux = true, BarDate = new DateTime(2025, 6, 15, 14, 30, 0) };
_fuz = new Fuz { Quz = "QuZ", FuzOffset = new DateTimeOffset(2025, 6, 15, 14, 30, 0, TimeSpan.FromHours(3)) };
}
#region GetValueByColumnName Tests
[Test]
public void GetValueByColumnName_ExistingColumn_ReturnsValue()
{
var result = _foo.GetValueByColumnName("BAZ");
Assert.That(result, Is.EqualTo(2));
}
[Test]
public void GetValueByColumnName_NonExistingColumn_ReturnsNull()
{
var result = _foo.GetValueByColumnName("NON_EXISTING");
Assert.That(result, Is.Null);
}
[Test]
public void GetValueByColumnName_PropertyNameInsteadOfColumnName_ReturnsNull()
{
var result = _foo.GetValueByColumnName("Baz");
Assert.That(result, Is.Null);
}
[Test]
public void GetValueByColumnName_ModelWithoutColumnAttribute_ReturnsNull()
{
var model = new NoColumnModel { Id = 1, Name = "Test" };
var result = model.GetValueByColumnName("Id");
Assert.That(result, Is.Null);
}
#endregion
#region ReplacePlaceholders - Basic Type Tests
[TestCase("SELECT * FROM T WHERE BAZ = {#INT#BAZ}", "SELECT * FROM T WHERE BAZ = 2")]
[TestCase("... WHERE BAZ = {#INT#BAZ}", "... WHERE BAZ = 2")]
public void ReplacePlaceholders_IntValue_ReplacesCorrectly(string input, string expected)
{
var result = input.ReplacePlaceholders(_foo, _bar, _fuz);
Assert.That(result, Is.EqualTo(expected));
}
[TestCase("SELECT * FROM T WHERE QUX = {#BOOL#QUX}", "SELECT * FROM T WHERE QUX = TRUE")]
[TestCase("... WHERE QUX = {#INT#QUX}", "... WHERE QUX = TRUE")]
public void ReplacePlaceholders_BoolTrueValue_ReplacesWithTRUE(string input, string expected)
{
var result = input.ReplacePlaceholders(_foo, _bar, _fuz);
Assert.That(result, Is.EqualTo(expected));
}
[Test]
public void ReplacePlaceholders_BoolFalseValue_ReplacesWithFALSE()
{
var bar = new Bar { Qux = false };
var result = "WHERE QUX = {#BOOL#QUX}".ReplacePlaceholders(bar);
Assert.That(result, Is.EqualTo("WHERE QUX = FALSE"));
}
[TestCase("... WHERE QUZ <> '{#STR#QUZ}'", "... WHERE QUZ <> 'QuZ'")]
[TestCase("SELECT * FROM T WHERE QUZ = '{#TEXT#QUZ}'", "SELECT * FROM T WHERE QUZ = 'QuZ'")]
public void ReplacePlaceholders_StringValue_ReplacesCorrectly(string input, string expected)
{
var result = input.ReplacePlaceholders(_foo, _bar, _fuz);
Assert.That(result, Is.EqualTo(expected));
}
[Test]
public void ReplacePlaceholders_DateTimeValue_FormatsAsSqlDateTime()
{
var result = "WHERE D = '{#DATE#BAR_DATE}'".ReplacePlaceholders(_bar);
Assert.That(result, Is.EqualTo("WHERE D = '2025-06-15 14:30:00'"));
}
[Test]
public void ReplacePlaceholders_DateTimeOffsetValue_FormatsWithOffset()
{
var result = "WHERE D = '{#DTO#FUZ_OFFSET}'".ReplacePlaceholders(_fuz);
Assert.That(result, Is.EqualTo("WHERE D = '2025-06-15 14:30:00 +03:00'"));
}
#endregion
#region ReplacePlaceholders - Multiple Placeholders
[Test]
public void ReplacePlaceholders_MultiplePlaceholdersInSameString_ReplacesAll()
{
var input = "WHERE BAZ = {#INT#BAZ} AND QUX = {#BOOL#QUX} AND QUZ = '{#STR#QUZ}'";
var expected = "WHERE BAZ = 2 AND QUX = TRUE AND QUZ = 'QuZ'";
var result = input.ReplacePlaceholders(_foo, _bar, _fuz);
Assert.That(result, Is.EqualTo(expected));
}
[Test]
public void ReplacePlaceholders_SamePlaceholderTwice_ReplacesBoth()
{
var input = "WHERE BAZ = {#INT#BAZ} OR BAZ = {#INT#BAZ}";
var expected = "WHERE BAZ = 2 OR BAZ = 2";
var result = input.ReplacePlaceholders(_foo);
Assert.That(result, Is.EqualTo(expected));
}
#endregion
#region ReplacePlaceholders - Object Resolution Order
[Test]
public void ReplacePlaceholders_ValueFoundInSecondObject_ReturnsValueFromSecondObject()
{
var result = "WHERE QUX = {#BOOL#QUX}".ReplacePlaceholders(_foo, _bar);
Assert.That(result, Is.EqualTo("WHERE QUX = TRUE"));
}
[Test]
public void ReplacePlaceholders_ValueFoundInThirdObject_ReturnsValueFromThirdObject()
{
var result = "WHERE QUZ = '{#STR#QUZ}'".ReplacePlaceholders(_foo, _bar, _fuz);
Assert.That(result, Is.EqualTo("WHERE QUZ = 'QuZ'"));
}
#endregion
#region ReplacePlaceholders - No Placeholder
[TestCase("SELECT * FROM T WHERE X = 1")]
[TestCase("")]
[TestCase("some random text")]
public void ReplacePlaceholders_NoPlaceholders_ReturnsOriginalString(string input)
{
var result = input.ReplacePlaceholders(_foo, _bar, _fuz);
Assert.That(result, Is.EqualTo(input));
}
#endregion
#region ReplacePlaceholders - Different Prefix Strings
[TestCase("WHERE BAZ = {#VAR#BAZ}", "WHERE BAZ = 2")]
[TestCase("WHERE BAZ = {#PARAM#BAZ}", "WHERE BAZ = 2")]
[TestCase("WHERE BAZ = {#X#BAZ}", "WHERE BAZ = 2")]
[TestCase("WHERE BAZ = {#SOME_LONG_PREFIX#BAZ}", "WHERE BAZ = 2")]
public void ReplacePlaceholders_DifferentPrefixes_AllResolveCorrectly(string input, string expected)
{
var result = input.ReplacePlaceholders(_foo);
Assert.That(result, Is.EqualTo(expected));
}
#endregion
#region ReplacePlaceholders - Exception Tests
[Test]
public void ReplacePlaceholders_UnresolvableColumn_ThrowsPlaceholderResolutionException()
{
var input = "WHERE X = {#INT#NON_EXISTING}";
var ex = Assert.Throws<PlaceholderResolutionException>(() =>
input.ReplacePlaceholders(_foo, _bar, _fuz));
Assert.That(ex!.ColumnName, Is.EqualTo("NON_EXISTING"));
Assert.That(ex.Placeholder, Is.EqualTo("{#INT#NON_EXISTING}"));
Assert.That(ex.Input, Is.EqualTo(input));
}
[Test]
public void ReplacePlaceholders_NoObjectsProvided_ThrowsPlaceholderResolutionException()
{
var input = "WHERE X = {#INT#BAZ}";
Assert.Throws<PlaceholderResolutionException>(() =>
input.ReplacePlaceholders());
}
[Test]
public void ReplacePlaceholders_ObjectWithoutColumnAttributes_ThrowsPlaceholderResolutionException()
{
var model = new NoColumnModel { Id = 1, Name = "Test" };
var input = "WHERE X = {#INT#Id}";
Assert.Throws<PlaceholderResolutionException>(() =>
input.ReplacePlaceholders(model));
}
[Test]
public void ReplacePlaceholders_MixedResolvableAndUnresolvable_ThrowsOnUnresolvable()
{
var input = "WHERE BAZ = {#INT#BAZ} AND X = {#INT#UNKNOWN}";
var ex = Assert.Throws<PlaceholderResolutionException>(() =>
input.ReplacePlaceholders(_foo, _bar, _fuz));
Assert.That(ex!.ColumnName, Is.EqualTo("UNKNOWN"));
}
#endregion
}