chore: deploy initial code base

This commit is contained in:
Thom Lamb
2026-05-12 10:34:59 -05:00
parent cbe3f6cc09
commit e826bf412b
257 changed files with 6137748 additions and 1 deletions
@@ -0,0 +1,29 @@
using NUnit.Framework;
using Strata.RxNorm.Biz.Utilities;
using System.Collections.Generic;
using System.Text.Json;
namespace Strata.RxNorm.Biz.Test.Unit.Utilities
{
[TestFixture]
public class LogUtilsExtensionsTests
{
[TestCaseSource(nameof(TestCases))]
public void Test(object input, object expected)
{
var dictionary = LogUtilsExtensions.ToDictionary<object>(input);
var expectedStr = JsonSerializer.Serialize(expected);
var actualStr = JsonSerializer.Serialize(dictionary);
Assert.That(actualStr, Is.EqualTo(expectedStr));
}
public static IEnumerable<TestCaseData> TestCases()
{
yield return new TestCaseData(new { a = 1, b = "test" }, new Dictionary<string, object> { { "a", 1 }, { "b", "test" } });
yield return new TestCaseData(new { a = 1, b = (string)null }, new Dictionary<string, object> { { "a", 1 }, { "b", null } });
yield return new TestCaseData(new { a = 1, b = new { c = 2 } }, new Dictionary<string, object> { { "a", 1 }, { "b", new Dictionary<string, object> { { "c", 2 } } } });
yield return new TestCaseData(123, new Dictionary<string, object> { { "object", 123 } });
yield return new TestCaseData("test", new Dictionary<string, object> { { "object", "test" } });
}
}
}
@@ -0,0 +1,670 @@
#nullable enable
using NUnit.Framework;
using Strata.RxNorm.Biz.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using static Strata.RxNorm.Biz.Utilities.SearchFilterExpressions;
namespace Strata.RxNorm.Biz.Test.Unit.Utilities
{
[TestFixture]
public class SearchFilterExpressionsTests
{
// Test entity classes for testing
private class TestEntity
{
public string Name { get; set; } = string.Empty;
public string Code { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Category { get; set; } = string.Empty;
public string? NullableField { get; set; }
}
private enum TestEnum
{
Value1,
Value2,
Value3
}
#region AddSearchFilters Tests
[Test]
public void AddSearchFilters_WithNullSearch_ReturnsOriginalFilters()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
// Act
var result = filters.AddSearchFilters(null, _ => e => true);
// Assert
Assert.That(result, Is.SameAs(filters));
Assert.That(result, Has.Count.EqualTo(0));
}
[Test]
public void AddSearchFilters_WithEmptySearch_ReturnsOriginalFilters()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
// Act
var result = filters.AddSearchFilters("", _ => e => true);
// Assert
Assert.That(result, Is.SameAs(filters));
Assert.That(result, Has.Count.EqualTo(0));
}
[Test]
public void AddSearchFilters_WithWhitespaceSearch_ReturnsOriginalFilters()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
// Act
var result = filters.AddSearchFilters(" ", _ => e => true);
// Assert
Assert.That(result, Is.SameAs(filters));
Assert.That(result, Has.Count.EqualTo(0));
}
[Test]
public void AddSearchFilters_WithSingleTerm_AddsOneFilter()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
// Act
var result = filters.AddSearchFilters("test", term => e => e.Name.Contains(term));
// Assert
Assert.That(result, Has.Count.EqualTo(1));
}
[Test]
public void AddSearchFilters_WithMultipleTerms_CombinesWithOrLogic()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
var testData = new List<TestEntity>
{
new() { Name = "apple" },
new() { Name = "banana" },
new() { Name = "cherry" }
};
// Act
var result = filters.AddSearchFilters("apple,banana", term => e => e.Name.Contains(term));
var compiled = result.First().Compile();
// Assert
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(compiled(testData[0]), Is.True, "apple should match");
Assert.That(compiled(testData[1]), Is.True, "banana should match");
Assert.That(compiled(testData[2]), Is.False, "cherry should not match");
}
[Test]
public void AddSearchFilters_WithNegationTerm_CombinesWithAndLogic()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
var testData = new List<TestEntity>
{
new() { Name = "apple" },
new() { Name = "banana" },
new() { Name = "applebana" }
};
// Act
var result = filters.AddSearchFilters("apple,!banana", term =>
{
var cleanTerm = term.StartsWith('!') ? term[1..] : term;
return term.StartsWith('!')
? e => !e.Name.Contains(cleanTerm)
: e => e.Name.Contains(cleanTerm);
});
var compiled = result.First().Compile();
// Assert
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(compiled(testData[0]), Is.True, "apple and !banana should match");
Assert.That(compiled(testData[1]), Is.False, "banana should fail !banana");
//Assert.That(compiled(testData[2]), Is.False, "applebana should fail !banana");
}
[TestCaseSource(nameof(AddSearchFiltersTermsTestCases))]
public void AddSearchFilters_WithVariousTermFormats_ParsesCorrectly(string search, int expectedTerms)
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
var termsCaptured = new List<string>();
// Act
filters.AddSearchFilters(search, term =>
{
termsCaptured.Add(term);
return e => true;
});
// Assert
Assert.That(termsCaptured, Has.Count.EqualTo(expectedTerms));
}
private static IEnumerable<TestCaseData> AddSearchFiltersTermsTestCases()
{
yield return new TestCaseData("apple,banana,cherry", 3)
.SetName("{m}(search: {0}, expectedTerms: {1})");
yield return new TestCaseData(" apple , banana , cherry ", 3)
.SetName("{m}(search: \"{0}\", expectedTerms: {1})");
yield return new TestCaseData("apple,,banana", 2)
.SetName("{m}(search: {0}, expectedTerms: {1})");
yield return new TestCaseData("apple", 1)
.SetName("{m}(search: {0}, expectedTerms: {1})");
yield return new TestCaseData("", 0)
.SetName("{m}(search: \"{0}\", expectedTerms: {1})");
}
#endregion
#region AddEntityFilters Tests
[Test]
public void AddEntityFilters_WithNullSearch_ReturnsOriginalFilters()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
// Act
var result = filters.AddEntityFilters(null, e => e.Name);
// Assert
Assert.That(result, Is.SameAs(filters));
Assert.That(result, Has.Count.EqualTo(0));
}
[Test]
public void AddEntityFilters_WithEmptyPropertySelectors_ReturnsOriginalFilters()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
// Act
var result = filters.AddEntityFilters("test");
// Assert
Assert.That(result, Is.SameAs(filters));
Assert.That(result, Has.Count.EqualTo(0));
}
[Test]
public void AddEntityFilters_WithValidInput_AddsFilter()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
var testEntity = new TestEntity { Name = "test" };
// Act
var result = filters.AddEntityFilters("test", e => e.Name);
var compiled = result.First().Compile();
// Assert
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(compiled(testEntity), Is.True);
}
[Test]
public void AddEntityFilters_WithNegation_CreatesNegationExpression()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
var testEntity1 = new TestEntity { Name = "test" };
var testEntity2 = new TestEntity { Name = "other" };
// Act
var result = filters.AddEntityFilters("!test", e => e.Name);
var compiled = result.First().Compile();
// Assert
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(compiled(testEntity1), Is.False, "should not match entities containing 'test'");
Assert.That(compiled(testEntity2), Is.True, "should match entities not containing 'test'");
}
#endregion
#region CreateEntityFilters Tests
[TestCaseSource(nameof(CreateEntityFiltersTestCases))]
public void CreateEntityFilters_WithVariousInputs_ReturnsExpectedResults(
string search,
string[] entityValues,
bool[] expectedResults)
{
// Arrange
var testEntities = entityValues.Select(v => new TestEntity { Name = v }).ToList();
// Act
var filters = search.CreateEntityFilters<TestEntity>(e => e.Name);
// Assert
if (string.IsNullOrEmpty(search))
{
Assert.That(filters, Has.Count.EqualTo(0));
return;
}
Assert.That(filters, Has.Count.EqualTo(1));
var compiled = filters.First().Compile();
for (int i = 0; i < testEntities.Count; i++)
{
Assert.That(compiled(testEntities[i]), Is.EqualTo(expectedResults[i]),
$"Entity {i} ('{entityValues[i]}') should be {expectedResults[i]}");
}
}
private static IEnumerable<TestCaseData> CreateEntityFiltersTestCases()
{
yield return new TestCaseData(
null,
new[] { "test" },
new[] { false }
).SetName("{m}(search: null, entities: [\"{1}\"], expected: [{2}])");
yield return new TestCaseData(
"",
new[] { "test" },
new[] { false }
).SetName("{m}(search: \"\", entities: [\"{1}\"], expected: [{2}])");
yield return new TestCaseData(
"apple",
new[] { "apple", "banana", "pineapple", "grape" },
new[] { true, false, true, false }
).SetName("{m}(search: \"{0}\", entities: [{1}], expected: [{2}])");
yield return new TestCaseData(
"apple,banana",
new[] { "apple", "banana", "cherry", "applebana" },
new[] { true, true, false, true }
).SetName("{m}(search: \"{0}\", entities: [{1}], expected: [{2}])");
yield return new TestCaseData(
"!apple",
new[] { "apple", "banana", "pineapple", "grape" },
new[] { false, true, false, true }
).SetName("{m}(search: \"{0}\", entities: [{1}], expected: [{2}])");
yield return new TestCaseData(
"APPLE",
new[] { "apple", "APPLE", "Apple" },
new[] { true, true, true }
).SetName("{m}(search: \"{0}\", entities: [{1}], expected: [{2}])");
}
#endregion
#region CreateEntityFilters with EnumFilters Tests
[Test]
public void CreateEntityFilters_WithEnumFilters_AppliesBothSearchAndEnumFilters()
{
// Arrange
var testEntities = new[]
{
new TestEntity { Name = "test1", Category = "VALUE1" },
new TestEntity { Name = "test2", Category = "VALUE2" },
new TestEntity { Name = "other", Category = "VALUE1" },
new TestEntity { Name = "test3", Category = "VALUE3" }
};
var enumFilters = new[]
{
new EnumFilter<TestEntity>(new TestEnum[] { TestEnum.Value1, TestEnum.Value2 }, e => e.Category)
};
// Act
var filters = "test".CreateEntityFilters(enumFilters, e => e.Name);
// Assert
Assert.That(filters, Has.Count.EqualTo(2), "should have one filter for search and one for enum");
var query = testEntities.AsQueryable();
foreach (var filter in filters)
{
query = query.Where(filter);
}
var results = query.ToList();
Assert.That(results, Has.Count.EqualTo(2), "should match test1 and test2");
Assert.That(results.All(r => r.Name.Contains("test")), Is.True, "all results should contain 'test'");
Assert.That(results.All(r => r.Category is "VALUE1" or "VALUE2"), Is.True, "all results should have matching categories");
}
[Test]
public void CreateEntityFilters_WithNullEnumFilters_OnlyAppliesSearchFilters()
{
// Arrange
var testEntity = new TestEntity { Name = "test" };
// Act
var filters = SearchFilterExpressions.CreateEntityFilters<TestEntity>("test", (SearchFilterExpressions.EnumFilter<TestEntity>[]?)null, e => e.Name);
// Assert
Assert.That(filters, Has.Count.EqualTo(1));
Assert.That(filters.First().Compile()(testEntity), Is.True);
}
#endregion
#region AddEnumFilter Tests
[Test]
public void AddEnumFilter_WithNullArray_ReturnsOriginalFilters()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
// Act
var result = filters.AddEnumFilter(null, e => e.Category);
// Assert
Assert.That(result, Is.SameAs(filters));
Assert.That(result, Has.Count.EqualTo(0));
}
[Test]
public void AddEnumFilter_WithEmptyArray_ReturnsOriginalFilters()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
// Act
var result = filters.AddEnumFilter(new TestEnum[0], e => e.Category);
// Assert
Assert.That(result, Is.SameAs(filters));
Assert.That(result, Has.Count.EqualTo(0));
}
[Test]
public void AddEnumFilter_WithValidEnums_AddsFilter()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
var testEntities = new[]
{
new TestEntity { Category = "VALUE1" },
new TestEntity { Category = "VALUE2" },
new TestEntity { Category = "VALUE3" },
new TestEntity { Category = null }
};
// Act
var result = filters.AddEnumFilter(new[] { TestEnum.Value1, TestEnum.Value2 }, e => e.Category);
var compiled = result.First().Compile();
// Assert
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(compiled(testEntities[0]), Is.True, "VALUE1 should match");
Assert.That(compiled(testEntities[1]), Is.True, "VALUE2 should match");
Assert.That(compiled(testEntities[2]), Is.False, "VALUE3 should not match");
Assert.That(compiled(testEntities[3]), Is.False, "null should not match");
}
[TestCaseSource(nameof(AddEnumFilterCaseTestCases))]
public void AddEnumFilter_WithMixedCaseProperty_MatchesCorrectly(string categoryValue, bool expectedMatch)
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
var testEntity = new TestEntity { Category = categoryValue };
// Act
var result = filters.AddEnumFilter(new[] { TestEnum.Value1 }, e => e.Category);
var compiled = result.First().Compile();
// Assert
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(compiled(testEntity), Is.EqualTo(expectedMatch));
}
private static IEnumerable<TestCaseData> AddEnumFilterCaseTestCases()
{
yield return new TestCaseData("value1", false)
.SetName("{m}(categoryValue: \"{0}\", expectedMatch: {1})");
yield return new TestCaseData("Value1", false)
.SetName("{m}(categoryValue: \"{0}\", expectedMatch: {1})");
yield return new TestCaseData("VALUE1", true)
.SetName("{m}(categoryValue: \"{0}\", expectedMatch: {1})");
}
#endregion
#region EnumFilter Record Tests
[Test]
public void EnumFilter_CanBeCreated_WithNullValues()
{
// Act
var filter = new EnumFilter<TestEntity>(null, e => e.Category);
// Assert
Assert.That(filter.EnumValues, Is.Null);
Assert.That(filter.PropertySelector, Is.Not.Null);
}
[Test]
public void EnumFilter_CanBeCreated_WithValidValues()
{
// Arrange
var enumArray = new[] { TestEnum.Value1, TestEnum.Value2 };
// Act
var filter = new EnumFilter<TestEntity>(enumArray, e => e.Category);
// Assert
Assert.That(filter.EnumValues, Is.SameAs(enumArray));
Assert.That(filter.PropertySelector, Is.Not.Null);
}
#endregion
#region Edge Cases and Error Conditions
[Test]
public void CreateEntityFilters_WithNullPropertySelector_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() =>
{
var filters = SearchFilterExpressions.CreateEntityFilters<TestEntity>("test", (Expression<Func<TestEntity, string>>?)null);
// Force evaluation to trigger the exception
filters.First().Compile();
});
}
[TestCaseSource(nameof(LongSearchStringTestCases))]
public void AddEntityFilters_WithVeryLongSearchString_HandlesGracefully(int stringLength)
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
var longSearch = new string('a', stringLength);
// Act & Assert
Assert.DoesNotThrow(() =>
{
var result = filters.AddEntityFilters(longSearch, e => e.Name);
var compiled = result.First().Compile();
var testEntity = new TestEntity { Name = longSearch };
var compiledResult = compiled(testEntity);
Assert.That(compiledResult, Is.True);
});
}
private static IEnumerable<TestCaseData> LongSearchStringTestCases()
{
yield return new TestCaseData(1000)
.SetName("{m}(stringLength: {0})");
yield return new TestCaseData(10000)
.SetName("{m}(stringLength: {0})");
}
[TestCaseSource(nameof(SpecialCharacterTestCases))]
public void CreateEntityFilters_WithSpecialCharacters_HandlesCorrectly(string specialChars)
{
// Arrange
var testEntity = new TestEntity { Name = specialChars };
// Act
var filters = specialChars.CreateEntityFilters<TestEntity>(e => e.Name);
var compiled = filters.First().Compile();
// Assert
Assert.That(compiled(testEntity), Is.True);
}
private static IEnumerable<TestCaseData> SpecialCharacterTestCases()
{
yield return new TestCaseData("@#$%^&*()")
.SetName("{m}(specialChars: \"{0}\")");
yield return new TestCaseData("_+-=[]{}|;':")
.SetName("{m}(specialChars: \"{0}\")");
yield return new TestCaseData("\",./<>?")
.SetName("{m}(specialChars: \"{0}\")");
}
[Test]
public void AddEnumFilter_WithNullPropertyValue_HandlesGracefully()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
var testEntity = new TestEntity { NullableField = null };
// Act
var result = filters.AddEnumFilter(new[] { TestEnum.Value1 }, e => e.NullableField);
var compiled = result.First().Compile();
// Assert
Assert.That(compiled(testEntity), Is.False);
}
[Test]
public void CreateEntityFilters_WithEmptyStringInMultipleTerms_FiltersCorrectly()
{
// Arrange
var testEntity = new TestEntity { Name = "" };
// Act
var filters = "apple,,banana".CreateEntityFilters<TestEntity>(e => e.Name);
var compiled = filters.First().Compile();
// Assert
Assert.That(compiled(testEntity), Is.False, "empty string should not contain apple or banana");
}
[Test]
public void AddSearchFilters_WithNullExpressionBuilder_ThrowsArgumentNullException()
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
// Act & Assert
Assert.Throws<NullReferenceException>(() => filters.AddSearchFilters("test", null));
}
#endregion
#region Performance and Stress Tests
[TestCaseSource(nameof(PerformanceTestCases))]
public void CreateEntityFilters_WithManyTerms_PerformsReasonably(int termCount)
{
// Arrange
var manyTerms = string.Join(",", Enumerable.Range(1, termCount).Select(i => $"term{i}"));
// Act & Assert
Assert.DoesNotThrow(() =>
{
var filters = manyTerms.CreateEntityFilters<TestEntity>(e => e.Name, e => e.Description);
Assert.That(filters.Count, Is.EqualTo(1));
});
}
private static IEnumerable<TestCaseData> PerformanceTestCases()
{
yield return new TestCaseData(50)
.SetName("{m}(termCount: {0})");
yield return new TestCaseData(100)
.SetName("{m}(termCount: {0})");
}
[TestCaseSource(nameof(EnumPerformanceTestCases))]
public void AddEnumFilter_WithManyEnumValues_PerformsReasonably(int enumCount)
{
// Arrange
var filters = new List<Expression<Func<TestEntity, bool>>>();
var manyEnums = Enumerable.Repeat(TestEnum.Value1, enumCount).ToArray();
// Act & Assert
Assert.DoesNotThrow(() =>
{
var result = filters.AddEnumFilter(manyEnums, e => e.Category);
Assert.That(result.Count, Is.EqualTo(1));
});
}
private static IEnumerable<TestCaseData> EnumPerformanceTestCases()
{
yield return new TestCaseData(500)
.SetName("{m}(enumCount: {0})");
yield return new TestCaseData(1000)
.SetName("{m}(enumCount: {0})");
}
#endregion
#region Integration Tests
[Test]
public void CreateEntityFilters_ComplexScenario_WorksEndToEnd()
{
// Arrange
var testData = new[]
{
new TestEntity { Name = "Apple Product", Code = "AP001", Category = "VALUE1", Description = "Fresh apple" },
new TestEntity { Name = "Banana Product", Code = "BP002", Category = "VALUE2", Description = "Yellow banana" },
new TestEntity { Name = "Cherry Product", Code = "CP003", Category = "VALUE3", Description = "Red cherry" },
new TestEntity { Name = "Apple Banana Mix", Code = "AB004", Category = "VALUE1", Description = "Mixed fruit" }
};
var enumFilters = new[]
{
new EnumFilter<TestEntity>(new[] { TestEnum.Value1, TestEnum.Value2 }, e => e.Category)
};
// Act - Search for "apple" with category filter
var filters = "apple".CreateEntityFilters(enumFilters, e => e.Name, e => e.Description);
var query = testData.AsQueryable();
foreach (var filter in filters)
{
query = query.Where(filter);
}
var results = query.ToList();
// Assert
Assert.That(results, Has.Count.EqualTo(2), "should match Apple Product and Apple Banana Mix");
Assert.That(results.All(r => r.Name.ToLower().Contains("apple") || r.Description.ToLower().Contains("apple")), Is.True,
"all results should contain 'apple' in name or description");
Assert.That(results.All(r => r.Category is "VALUE1" or "VALUE2"), Is.True,
"all results should have matching categories");
}
#endregion
}
}