diff --git a/ReflectorNet.Tests/src/ReflectorTests/IsTypeBlacklistedTests.cs b/ReflectorNet.Tests/src/ReflectorTests/IsTypeBlacklistedTests.cs index 530c690..05bbd91 100644 --- a/ReflectorNet.Tests/src/ReflectorTests/IsTypeBlacklistedTests.cs +++ b/ReflectorNet.Tests/src/ReflectorTests/IsTypeBlacklistedTests.cs @@ -844,6 +844,335 @@ public void IsTypeBlacklisted_DeeplyDerivedFromGenericWithBlacklistedArg_Returns #endregion + #region Batch Blacklist Method Tests + + [Fact] + public void BlacklistTypes_MultipleTypes_AllAreBlacklisted() + { + // Arrange + var reflector = new Reflector(); + + // Act + var result = reflector.Converters.BlacklistTypes( + typeof(BlacklistedBaseClass), + typeof(NonBlacklistedClass), + typeof(IBlacklistedInterface)); + + // Assert + Assert.True(result, "BlacklistTypes should return true when types are added"); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(BlacklistedBaseClass))); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(NonBlacklistedClass))); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(IBlacklistedInterface))); + _output.WriteLine("All types from batch blacklist are correctly blacklisted"); + } + + [Fact] + public void BlacklistTypes_WithNullValues_IgnoresNulls() + { + // Arrange + var reflector = new Reflector(); + + // Act - Include null values which should be ignored + var result = reflector.Converters.BlacklistTypes( + typeof(BlacklistedBaseClass), + null!, + typeof(NonBlacklistedClass)); + + // Assert + Assert.True(result, "BlacklistTypes should return true when at least one type is added"); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(BlacklistedBaseClass))); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(NonBlacklistedClass))); + Assert.Equal(2, reflector.Converters.GetAllBlacklistedTypes().Count); + _output.WriteLine("Null values in batch blacklist are correctly ignored"); + } + + [Fact] + public void BlacklistTypes_EmptyArray_NoChange() + { + // Arrange + var reflector = new Reflector(); + + // Act + var result = reflector.Converters.BlacklistTypes(Array.Empty()); + + // Assert + Assert.False(result, "BlacklistTypes should return false when no types are added"); + Assert.Empty(reflector.Converters.GetAllBlacklistedTypes()); + _output.WriteLine("Empty batch blacklist results in no changes"); + } + + [Fact] + public void BlacklistTypes_DuplicateTypes_AddsOnlyOnce() + { + // Arrange + var reflector = new Reflector(); + + // Act - Add same type multiple times + var result = reflector.Converters.BlacklistTypes( + typeof(BlacklistedBaseClass), + typeof(BlacklistedBaseClass), + typeof(BlacklistedBaseClass)); + + // Assert + Assert.True(result, "BlacklistTypes should return true when at least one type is added"); + Assert.Single(reflector.Converters.GetAllBlacklistedTypes()); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(BlacklistedBaseClass))); + _output.WriteLine("Duplicate types in batch are correctly deduplicated"); + } + + [Fact] + public void BlacklistTypes_DerivedTypesInBatch_AllDetected() + { + // Arrange + var reflector = new Reflector(); + + // Act + var result = reflector.Converters.BlacklistTypes(typeof(BlacklistedBaseClass)); + + // Assert - Derived types should also be blacklisted + Assert.True(result, "BlacklistTypes should return true when type is added"); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(DerivedFromBlacklisted))); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(DeeplyDerivedFromBlacklisted))); + _output.WriteLine("Derived types are correctly blacklisted after batch operation"); + } + + [Fact] + public void BlacklistTypes_AlreadyBlacklisted_ReturnsFalse() + { + // Arrange + var reflector = new Reflector(); + reflector.Converters.BlacklistTypes(typeof(BlacklistedBaseClass)); + + // Act - Try to add the same type again + var result = reflector.Converters.BlacklistTypes(typeof(BlacklistedBaseClass)); + + // Assert + Assert.False(result, "BlacklistTypes should return false when all types are already blacklisted"); + _output.WriteLine("BlacklistTypes returns false when type already blacklisted"); + } + + [Fact] + public void BlacklistTypes_OnlyNulls_ReturnsFalse() + { + // Arrange + var reflector = new Reflector(); + + // Act - Only null values + var result = reflector.Converters.BlacklistTypes((Type)null!, (Type)null!); + + // Assert + Assert.False(result, "BlacklistTypes should return false when only null values provided"); + Assert.Empty(reflector.Converters.GetAllBlacklistedTypes()); + _output.WriteLine("BlacklistTypes returns false when only nulls provided"); + } + + #endregion + + #region String-Based Blacklist Method Tests + + [Fact] + public void BlacklistType_ByStringName_TypeIsBlacklisted() + { + // Arrange + var reflector = new Reflector(); + var typeFullName = typeof(BlacklistedBaseClass).FullName!; + + // Act + var result = reflector.Converters.BlacklistType(typeFullName); + + // Assert + Assert.True(result, "BlacklistType should return true when type is added"); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(BlacklistedBaseClass))); + _output.WriteLine($"Type blacklisted by string name '{typeFullName}' is correctly detected"); + } + + [Fact] + public void BlacklistType_ByStringName_SystemType_IsBlacklisted() + { + // Arrange + var reflector = new Reflector(); + + // Act + var result = reflector.Converters.BlacklistType("System.String"); + + // Assert + Assert.True(result, "BlacklistType should return true when type is added"); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(string))); + _output.WriteLine("System.String blacklisted by string name is correctly detected"); + } + + [Fact] + public void BlacklistType_ByStringName_InvalidTypeName_ReturnsFalse() + { + // Arrange + var reflector = new Reflector(); + + // Act - Should not throw for invalid type name + var result = reflector.Converters.BlacklistType("This.Type.Does.Not.Exist"); + + // Assert - No types should be blacklisted + Assert.False(result, "BlacklistType should return false for invalid type name"); + Assert.Empty(reflector.Converters.GetAllBlacklistedTypes()); + _output.WriteLine("Invalid type name returns false and is silently ignored"); + } + + [Fact] + public void BlacklistType_ByStringName_AlreadyBlacklisted_ReturnsFalse() + { + // Arrange + var reflector = new Reflector(); + reflector.Converters.BlacklistType("System.String"); + + // Act - Try to add the same type again + var result = reflector.Converters.BlacklistType("System.String"); + + // Assert + Assert.False(result, "BlacklistType should return false when type already blacklisted"); + _output.WriteLine("BlacklistType returns false when type already blacklisted"); + } + + [Fact] + public void BlacklistTypes_ByStringNames_AllTypesAreBlacklisted() + { + // Arrange + var reflector = new Reflector(); + + // Act + var result = reflector.Converters.BlacklistTypes( + "System.String", + "System.Int32", + "System.DateTime"); + + // Assert + Assert.True(result, "BlacklistTypes should return true when types are added"); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(string))); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(int))); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(DateTime))); + _output.WriteLine("Multiple types blacklisted by string names are correctly detected"); + } + + [Fact] + public void BlacklistTypes_ByStringNames_MixedValidAndInvalid_OnlyValidAdded() + { + // Arrange + var reflector = new Reflector(); + + // Act - Mix of valid and invalid type names + var result = reflector.Converters.BlacklistTypes( + "System.String", + "This.Does.Not.Exist", + "System.Int32"); + + // Assert + Assert.True(result, "BlacklistTypes should return true when at least one type is added"); + Assert.Equal(2, reflector.Converters.GetAllBlacklistedTypes().Count); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(string))); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(int))); + _output.WriteLine("Only valid type names from batch are blacklisted"); + } + + [Fact] + public void BlacklistTypes_ByStringNames_EmptyArray_NoChange() + { + // Arrange + var reflector = new Reflector(); + + // Act + var result = reflector.Converters.BlacklistTypes(Array.Empty()); + + // Assert + Assert.False(result, "BlacklistTypes should return false when no types are added"); + Assert.Empty(reflector.Converters.GetAllBlacklistedTypes()); + _output.WriteLine("Empty string array batch results in no changes"); + } + + [Fact] + public void BlacklistTypes_ByStringNames_DuplicateNames_AddsOnlyOnce() + { + // Arrange + var reflector = new Reflector(); + + // Act + var result = reflector.Converters.BlacklistTypes( + "System.String", + "System.String", + "System.String"); + + // Assert + Assert.True(result, "BlacklistTypes should return true when at least one type is added"); + Assert.Single(reflector.Converters.GetAllBlacklistedTypes()); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(string))); + _output.WriteLine("Duplicate string names in batch are correctly deduplicated"); + } + + [Fact] + public void BlacklistTypes_ByStringNames_GenericType_IsBlacklisted() + { + // Arrange + var reflector = new Reflector(); + var listStringTypeName = typeof(List).FullName!; + + // Act + var result = reflector.Converters.BlacklistTypes(listStringTypeName); + + // Assert + Assert.True(result, "BlacklistTypes should return true when type is added"); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(List))); + _output.WriteLine($"Generic type '{listStringTypeName}' blacklisted by string name is correctly detected"); + } + + [Fact] + public void BlacklistTypes_ByStringNames_CacheInvalidation_WorksCorrectly() + { + // Arrange + var reflector = new Reflector(); + + // Prime cache with false result + Assert.False(reflector.Converters.IsTypeBlacklisted(typeof(string))); + + // Act + var result = reflector.Converters.BlacklistTypes("System.String"); + + // Assert - Cache should be invalidated + Assert.True(result, "BlacklistTypes should return true when type is added"); + Assert.True(reflector.Converters.IsTypeBlacklisted(typeof(string))); + _output.WriteLine("Cache is correctly invalidated after string-based batch blacklist"); + } + + [Fact] + public void BlacklistTypes_ByStringNames_AllInvalid_ReturnsFalse() + { + // Arrange + var reflector = new Reflector(); + + // Act - All invalid type names + var result = reflector.Converters.BlacklistTypes( + "This.Does.Not.Exist", + "Neither.Does.This"); + + // Assert + Assert.False(result, "BlacklistTypes should return false when no types could be resolved"); + Assert.Empty(reflector.Converters.GetAllBlacklistedTypes()); + _output.WriteLine("BlacklistTypes returns false when all type names are invalid"); + } + + [Fact] + public void BlacklistTypes_ByStringNames_AlreadyBlacklisted_ReturnsFalse() + { + // Arrange + var reflector = new Reflector(); + reflector.Converters.BlacklistTypes("System.String", "System.Int32"); + + // Act - Try to add the same types again + var result = reflector.Converters.BlacklistTypes("System.String", "System.Int32"); + + // Assert + Assert.False(result, "BlacklistTypes should return false when all types already blacklisted"); + _output.WriteLine("BlacklistTypes returns false when all types already blacklisted"); + } + + #endregion + #region Concurrency and Cache Tests [Fact] diff --git a/ReflectorNet/ReflectorNet.csproj b/ReflectorNet/ReflectorNet.csproj index f888fc4..155d801 100644 --- a/ReflectorNet/ReflectorNet.csproj +++ b/ReflectorNet/ReflectorNet.csproj @@ -9,7 +9,7 @@ com.IvanMurzak.ReflectorNet - 3.5.0 + 3.6.0 Ivan Murzak Copyright © Ivan Murzak 2025 ReflectorNet is an advanced .NET reflection toolkit designed for AI-driven scenarios. Effortlessly search for C# methods using natural language queries, invoke any method by supplying arguments as JSON, and receive results as JSON. The library also provides a powerful API to inspect, modify, and manage in-memory object instances dynamically via JSON data. Ideal for automation, testing, and AI integration workflows. diff --git a/ReflectorNet/src/Reflector/Reflector.Registry.cs b/ReflectorNet/src/Reflector/Reflector.Registry.cs index c0903cf..ad008c3 100644 --- a/ReflectorNet/src/Reflector/Reflector.Registry.cs +++ b/ReflectorNet/src/Reflector/Reflector.Registry.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using com.IvanMurzak.ReflectorNet.Converter; +using com.IvanMurzak.ReflectorNet.Utils; namespace com.IvanMurzak.ReflectorNet { @@ -107,14 +108,73 @@ public void Remove() where T : IReflectionConverter /// /// Adds a type to the blacklist, preventing it from being processed by any converter. /// - /// - public void BlacklistType(Type type) + /// The type to add to the blacklist. + /// True if the type was added; false if it was null or already blacklisted. + public bool BlacklistType(Type type) { if (type == null) - return; + return false; if (_blacklistedTypes.TryAdd(type, 0)) + { + _blacklistCache = new ConcurrentDictionary(); // Invalidate cache when blacklist changes + return true; + } + return false; + } + + /// + /// Adds multiple types to the blacklist, preventing them from being processed by any converter. + /// The blacklist cache is only invalidated if at least one new type was added. + /// + /// The types to add to the blacklist. Null values are ignored. + /// True if at least one type was added; false if all types were null or already blacklisted. + public bool BlacklistTypes(params Type[] types) + { + var changed = false; + foreach (var type in types) + { + if (type != null && _blacklistedTypes.TryAdd(type, 0)) + changed = true; + } + if (changed) + _blacklistCache = new ConcurrentDictionary(); // Invalidate cache when blacklist changes + return changed; + } + + /// + /// Adds a type to the blacklist by its full name, preventing it from being processed by any converter. + /// The type is resolved using . + /// + /// The full name of the type to blacklist (e.g., "System.String"). + /// True if the type was resolved and added; false if the type could not be resolved or was already blacklisted. + public bool BlacklistType(string typeFullName) + { + var type = TypeUtils.GetType(typeFullName); + if (type != null) + return BlacklistType(type); + return false; + } + + /// + /// Adds multiple types to the blacklist by their full names, preventing them from being processed by any converter. + /// Types are resolved using . The blacklist cache is only invalidated + /// if at least one new type was successfully resolved and added. + /// + /// The full names of the types to blacklist (e.g., "System.String", "System.Int32"). + /// True if at least one type was resolved and added; false if all types could not be resolved or were already blacklisted. + public bool BlacklistTypes(params string[] typeFullNames) + { + var changed = false; + foreach (var typeFullName in typeFullNames) + { + var type = TypeUtils.GetType(typeFullName); + if (type != null && _blacklistedTypes.TryAdd(type, 0)) + changed = true; + } + if (changed) _blacklistCache = new ConcurrentDictionary(); // Invalidate cache when blacklist changes + return changed; } ///