Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(unit-tests): increase code coverage #252

Merged
merged 1 commit into from
Oct 1, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feat(unit-tests): increase code coverage
This commit adds:
- Improvements to existing unit test code
- Improvements to existing unit test naming
- Unit tests for `EFCacheKeyPrefixProvider` class
- Unit tests for `EFCacheKeyProvider` class
- Unit tests for `EFCacheKey` class
- Unit tests for `EFCacheServiceCheck` class
- Unit tests for `EFDataReaderLoader` class
- Unit tests for `EFDebugLogger` class
- Unit tests for `EFServiceCollectionExtensions` class
- Unit tests for `LockProvider` class
- Unit tests for `StringExtensions` class
- Unit tests for `TableEntityInfo` class
- Unit tests for `XxHash64Unsafe` class
dmitrii-kiselev committed Oct 1, 2024

Verified

This commit was signed with the committer’s verified signature.
Rigidity Rigidity
commit ea968f59b1acac4ffe50e3818d4a80b9af8070ca
3 changes: 3 additions & 0 deletions src/EFCoreSecondLevelCacheInterceptor/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("EFCoreSecondLevelCacheInterceptor.UnitTests")]

namespace EFCoreSecondLevelCacheInterceptor;

Original file line number Diff line number Diff line change
@@ -12,40 +12,39 @@ public class DbCommandInterceptorProcessorTests
{
private readonly IDbCommandInterceptorProcessor _processor;
private readonly Mock<IEFDebugLogger> _loggerMock;
private readonly Mock<ILogger<DbCommandInterceptorProcessor>> _interceptorProcessorLoggerMock;
private readonly Mock<IEFCacheServiceProvider> _cacheServiceMock;
private readonly Mock<IEFCacheDependenciesProcessor> _cacheDependenciesProcessorMock;
private readonly Mock<IEFCacheKeyProvider> _cacheKeyProviderMock;
private readonly Mock<IEFCachePolicyParser> _cachePolicyParserMock;
private readonly Mock<IEFSqlCommandsProcessor> _sqlCommandsProcessorMock;
private readonly Mock<IOptions<EFCoreSecondLevelCacheSettings>> _cacheSettingsMock;
private readonly Mock<IEFCacheServiceCheck> _cacheServiceCheckMock;
private readonly EFCoreSecondLevelCacheSettings _cacheSettings;

public DbCommandInterceptorProcessorTests()
{
var interceptorProcessorLoggerMock = new Mock<ILogger<DbCommandInterceptorProcessor>>();
var cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();

_loggerMock = new Mock<IEFDebugLogger>();
_interceptorProcessorLoggerMock = new Mock<ILogger<DbCommandInterceptorProcessor>>();
_cacheServiceMock = new Mock<IEFCacheServiceProvider>();
_cacheDependenciesProcessorMock = new Mock<IEFCacheDependenciesProcessor>();
_cacheKeyProviderMock = new Mock<IEFCacheKeyProvider>();
_cachePolicyParserMock = new Mock<IEFCachePolicyParser>();
_sqlCommandsProcessorMock = new Mock<IEFSqlCommandsProcessor>();
_cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();
_cacheServiceCheckMock = new Mock<IEFCacheServiceCheck>();
_cacheSettings = new EFCoreSecondLevelCacheSettings();

_cacheSettingsMock.SetupGet(x => x.Value).Returns(_cacheSettings);
cacheSettingsMock.SetupGet(x => x.Value).Returns(_cacheSettings);

_processor = new DbCommandInterceptorProcessor(
_loggerMock.Object,
_interceptorProcessorLoggerMock.Object,
interceptorProcessorLoggerMock.Object,
_cacheServiceMock.Object,
_cacheDependenciesProcessorMock.Object,
_cacheKeyProviderMock.Object,
_cachePolicyParserMock.Object,
_sqlCommandsProcessorMock.Object,
_cacheSettingsMock.Object,
cacheSettingsMock.Object,
_cacheServiceCheckMock.Object);
}

@@ -71,6 +70,7 @@ public void Constructor_ThrowsArgumentNullException_WhenCacheSettingsIsNull()
cacheKeyProvider,
cachePolicyParser,
sqlCommandsProcessor,
// ReSharper disable once AssignNullToNotNullAttribute
null,
cacheServiceCheck));
}
@@ -112,6 +112,8 @@ public void ProcessExecutedCommands_ReturnsExpectedResultWithoutPrecessing_WhenD
DbContext context = null;

// Act
// ReSharper disable once ExpressionIsAlwaysNull
// ReSharper disable once AssignNullToNotNullAttribute
var actual = _processor.ProcessExecutedCommands<object>(null, context, null);

// Assert
@@ -125,6 +127,7 @@ public void ProcessExecutedCommands_ReturnsExpectedResultWithoutPrecessing_WhenC
var context = Mock.Of<DbContext>();

// Act
// ReSharper disable once AssignNullToNotNullAttribute
var actual = _processor.ProcessExecutedCommands<object>(null, context, null);

// Assert
@@ -222,7 +225,7 @@ public void
_cacheSettings.SkipCachingDbContexts = new List<Type> { context.GetType() };

// Act
var actual = _processor.ProcessExecutedCommands(command, context, result);
_processor.ProcessExecutedCommands(command, context, result);

// Assert
_loggerMock.Verify(x => x.NotifyCacheableEvent(
@@ -861,7 +864,7 @@ public void ProcessExecutedCommands_ReturnsExpectedResult_WhenObjectDataAddedToC
public void ProcessExecutedCommands_ReturnsNull_WhenResultIsNull()
{
// Arrange
object expected = null;
object result = null;

var commandMock = new Mock<DbCommand>();
var transaction = Mock.Of<DbTransaction>();
@@ -879,7 +882,8 @@ public void ProcessExecutedCommands_ReturnsNull_WhenResultIsNull()
_cacheSettings.AllowCachingWithExplicitTransactions = true;

// Act
var actual = _processor.ProcessExecutedCommands(commandMock.Object, context, expected);
// ReSharper disable once ExpressionIsAlwaysNull
var actual = _processor.ProcessExecutedCommands(commandMock.Object, context, result);

// Assert
Assert.Null(actual);
@@ -892,6 +896,8 @@ public void ProcessExecutingCommands_ReturnsExpectedResultWithoutPrecessing_When
DbContext context = null;

// Act
// ReSharper disable once AssignNullToNotNullAttribute
// ReSharper disable once ExpressionIsAlwaysNull
var actual = _processor.ProcessExecutingCommands<object>(null, context, null);

// Assert
@@ -905,6 +911,7 @@ public void ProcessExecutingCommands_ReturnsExpectedResultWithoutPrecessing_When
var context = Mock.Of<DbContext>();

// Act
// ReSharper disable once AssignNullToNotNullAttribute
var actual = _processor.ProcessExecutingCommands<object>(null, context, null);

// Assert
@@ -1367,6 +1374,7 @@ public void ProcessExecutingCommands_NotifiesCachingSkippedEvent_WhenResultIsNul
_cacheSettings.AllowCachingWithExplicitTransactions = true;

// Act
// ReSharper disable once ExpressionIsAlwaysNull
_processor.ProcessExecutingCommands(commandMock.Object, context, result);

// Assert
@@ -1397,6 +1405,7 @@ public void ProcessExecutingCommands_ReturnsNull_WhenResultIsNull()
_cacheSettings.AllowCachingWithExplicitTransactions = true;

// Act
// ReSharper disable once ExpressionIsAlwaysNull
var actual = _processor.ProcessExecutingCommands(commandMock.Object, context, result);

// Assert
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Microsoft.Extensions.Options;
using Moq;

namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

// ReSharper disable once InconsistentNaming
public class EFCacheKeyPrefixProviderTests
{
[Fact]
public void Constructor_ThrowsArgumentNullException_WhenCacheSettingsIsNull()
{
// Arrange
var serviceProvider = new Mock<IServiceProvider>().Object;

// ReSharper disable once ObjectCreationAsStatement
void Act() => new EFCacheKeyPrefixProvider(serviceProvider, null!);

// Act && Assert
Assert.Throws<ArgumentNullException>("cacheSettings", Act);
}

[Fact]
public void GetCacheKeyPrefix_ReturnsCacheKeyPrefixSelectorResult_WhenSelectorIsNotNull()
{
// Arrange
var serviceProvider = new Mock<IServiceProvider>().Object;
var cacheSettings = Options.Create(new EFCoreSecondLevelCacheSettings
{
CacheKeyPrefixSelector = _ => "CustomPrefix"
});

var provider = new EFCacheKeyPrefixProvider(serviceProvider, cacheSettings);

// Act
var actual = provider.GetCacheKeyPrefix();

// Assert
Assert.Equal("CustomPrefix", actual);
}

[Fact]
public void GetCacheKeyPrefix_ReturnsCacheKeyPrefix_WhenSelectorIsNull()
{
// Arrange
var serviceProvider = new Mock<IServiceProvider>().Object;
var cacheSettings = Options.Create(new EFCoreSecondLevelCacheSettings
{
CacheKeyPrefix = "DefaultPrefix",
CacheKeyPrefixSelector = null
});

var provider = new EFCacheKeyPrefixProvider(serviceProvider, cacheSettings);

// Act
var actual = provider.GetCacheKeyPrefix();

// Assert
Assert.Equal("DefaultPrefix", actual);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
using System.Data;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Moq.Protected;

namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

// ReSharper disable once InconsistentNaming
public class EFCacheKeyProviderTests
{
private readonly Mock<IEFCacheDependenciesProcessor> _cacheDependenciesProcessorMock;
private readonly Mock<IEFCacheKeyPrefixProvider> _cacheKeyPrefixProviderMock;
private readonly Mock<IEFHashProvider> _hashProviderMock;
private readonly Mock<IEFDebugLogger> _loggerMock;
private readonly IEFCacheKeyProvider _cacheKeyProvider;

public EFCacheKeyProviderTests()
{
var cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();
var settings = new EFCoreSecondLevelCacheSettings();
var cachePolicyParserMock = new Mock<IEFCachePolicyParser>();
var keyProviderLoggerMock = new Mock<ILogger<EFCacheKeyProvider>>();

_cacheDependenciesProcessorMock = new Mock<IEFCacheDependenciesProcessor>();
_cacheKeyPrefixProviderMock = new Mock<IEFCacheKeyPrefixProvider>();
_hashProviderMock = new Mock<IEFHashProvider>();
_loggerMock = new Mock<IEFDebugLogger>();

cacheSettingsMock.Setup(c => c.Value).Returns(settings);

_cacheKeyProvider = new EFCacheKeyProvider(
_cacheDependenciesProcessorMock.Object,
cachePolicyParserMock.Object,
_loggerMock.Object,
keyProviderLoggerMock.Object,
_hashProviderMock.Object,
_cacheKeyPrefixProviderMock.Object,
cacheSettingsMock.Object);
}

[Fact]
public void EFCacheKeyProvider_ThrowsArgumentNullException_WhenHashProviderIsNull()
{
// Arrange
var cacheDependenciesProcessorMock = new Mock<IEFCacheDependenciesProcessor>();
var cachePolicyParserMock = new Mock<IEFCachePolicyParser>();
var loggerMock = new Mock<IEFDebugLogger>();
var keyProviderLoggerMock = new Mock<ILogger<EFCacheKeyProvider>>();
var cacheKeyPrefixProviderMock = new Mock<IEFCacheKeyPrefixProvider>();
#if NET5_0 || NET6_0 || NET7_0 || NET8_0
var cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();
#endif

// Act && Assert
Assert.Throws<ArgumentNullException>(() => new EFCacheKeyProvider(
cacheDependenciesProcessorMock.Object,
cachePolicyParserMock.Object,
loggerMock.Object,
keyProviderLoggerMock.Object,
// ReSharper disable once AssignNullToNotNullAttribute
null,
cacheKeyPrefixProviderMock.Object
#if NET5_0 || NET6_0 || NET7_0 || NET8_0
,
cacheSettingsMock.Object
#endif
));
}

[Fact]
public void EFCacheKeyProvider_ThrowsArgumentNullException_WhenCacheSettingsIsNull()
{
// Arrange
#if NET5_0 || NET6_0 || NET7_0 || NET8_0
var cacheDependenciesProcessorMock = new Mock<IEFCacheDependenciesProcessor>();
var cachePolicyParserMock = new Mock<IEFCachePolicyParser>();
var loggerMock = new Mock<IEFDebugLogger>();
var keyProviderLoggerMock = new Mock<ILogger<EFCacheKeyProvider>>();
var hashProviderMock = new Mock<IEFHashProvider>();
var cacheKeyPrefixProviderMock = new Mock<IEFCacheKeyPrefixProvider>();

// Act && Assert
Assert.Throws<ArgumentNullException>(() => new EFCacheKeyProvider(
cacheDependenciesProcessorMock.Object,
cachePolicyParserMock.Object,
loggerMock.Object,
keyProviderLoggerMock.Object,
hashProviderMock.Object,
cacheKeyPrefixProviderMock.Object,
// ReSharper disable once AssignNullToNotNullAttribute
null
));
#endif
}

[Fact]
public void EFCacheKeyProvider_CreatesInstanceSuccessfully()
{
// Arrange
var cacheDependenciesProcessorMock = new Mock<IEFCacheDependenciesProcessor>();
var cachePolicyParserMock = new Mock<IEFCachePolicyParser>();
var loggerMock = new Mock<IEFDebugLogger>();
var keyProviderLoggerMock = new Mock<ILogger<EFCacheKeyProvider>>();
var hashProviderMock = new Mock<IEFHashProvider>();
var cacheKeyPrefixProviderMock = new Mock<IEFCacheKeyPrefixProvider>();
#if NET5_0 || NET6_0 || NET7_0 || NET8_0
var cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();
cacheSettingsMock.Setup(c => c.Value).Returns(new EFCoreSecondLevelCacheSettings());
#endif

// Act
var provider = new EFCacheKeyProvider(
cacheDependenciesProcessorMock.Object,
cachePolicyParserMock.Object,
loggerMock.Object,
keyProviderLoggerMock.Object,
hashProviderMock.Object,
cacheKeyPrefixProviderMock.Object
#if NET5_0 || NET6_0 || NET7_0 || NET8_0
,
cacheSettingsMock.Object
#endif
);

// Assert
Assert.NotNull(provider);
}

[Fact]
[SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")]
public void GetEFCacheKey_ThrowsArgumentNullException_WhenContextIsNull()
{
// Arrange
DbCommand command = null;
DbContext context = null;
EFCachePolicy cachePolicy = null;

// Act
void Act() => _cacheKeyProvider.GetEFCacheKey(command, context, cachePolicy);

// Assert
Assert.Throws<ArgumentNullException>("context", Act);
}

[Fact]
[SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")]
public void GetEFCacheKey_ThrowsArgumentNullException_WhenCommandIsNull()
{
// Arrange
DbCommand command = null;
DbContext context = Mock.Of<DbContext>();
EFCachePolicy cachePolicy = null;

// Act
void Act() => _cacheKeyProvider.GetEFCacheKey(command, context, cachePolicy);

// Assert
Assert.Throws<ArgumentNullException>("command", Act);
}

[Fact]
public void GetEFCacheKey_ThrowsArgumentNullException_WhenCachePolicyIsNull()
{
// Arrange
DbCommand command = Mock.Of<DbCommand>();
DbContext context = Mock.Of<DbContext>();
EFCachePolicy cachePolicy = null;

// Act
// ReSharper disable once AssignNullToNotNullAttribute
void Act() => _cacheKeyProvider.GetEFCacheKey(command, context, cachePolicy);

// Assert
Assert.Throws<ArgumentNullException>("cachePolicy", Act);
}

[Fact]
public void GetEFCacheKey_ReturnsExpectedCacheKey()
{
// Arrange
var context = Mock.Of<DbContext>();
var expected = new EFCacheKey(new SortedSet<string>())
{
KeyHash = "75BCD15",
DbContext = context.GetType()
};

var commandMock = new Mock<DbCommand>();
var cachePolicy = new EFCachePolicy().SaltKey("CacheSaltKey");
var dbParameterCollectionMock = new Mock<DbParameterCollection>();
var dbParameterMock = new Mock<DbParameter>();
var parameters = new List<DbParameter>
{
dbParameterMock.Object,
};

_hashProviderMock
.Setup(x => x.ComputeHash(@"ConnectionString
=Name=""Value"",Size=2147483647,Precision=255,Scale=255,Direction=Input,SaltKey
=CacheSaltKey"))
.Returns(123456789);

_loggerMock.Setup(x => x.IsLoggerEnabled).Returns(true);

commandMock
.Protected()
.SetupGet<DbParameterCollection>("DbParameterCollection")
.Returns(dbParameterCollectionMock.Object);

dbParameterCollectionMock
.Setup(x => x.GetEnumerator())
.Returns(parameters.GetEnumerator());

_cacheDependenciesProcessorMock
.Setup(x => x.GetCacheDependencies(commandMock.Object, context, cachePolicy))
.Returns(expected.CacheDependencies as SortedSet<string>);

dbParameterMock.Setup(x => x.ParameterName).Returns("Name");
dbParameterMock.Setup(x => x.Value).Returns("Value");
dbParameterMock.Setup(x => x.Size).Returns(int.MaxValue);
dbParameterMock.Setup(x => x.Precision).Returns(byte.MaxValue);
dbParameterMock.Setup(x => x.Scale).Returns(byte.MaxValue);
dbParameterMock.Setup(x => x.Direction).Returns(ParameterDirection.Input);

// Act
var actual = _cacheKeyProvider.GetEFCacheKey(commandMock.Object, context, cachePolicy);

// Assert
Assert.Equal(expected, actual);
}

[Fact]
public void GetEFCacheKey_ReturnsExpectedCacheKeyWithPrefix()
{
// Arrange
var context = Mock.Of<DbContext>();
var expected = new EFCacheKey(new SortedSet<string>())
{
KeyHash = "CacheKeyPrefix75BCD15",
DbContext = context.GetType()
};

var commandMock = new Mock<DbCommand>();
var cachePolicy = new EFCachePolicy().SaltKey("CacheSaltKey");
var dbParameterCollectionMock = new Mock<DbParameterCollection>();
var dbParameterMock = new Mock<DbParameter>();
var parameters = new List<DbParameter>
{
dbParameterMock.Object,
};

_cacheKeyPrefixProviderMock
.Setup(x => x.GetCacheKeyPrefix())
.Returns("CacheKeyPrefix");

_hashProviderMock
.Setup(x => x.ComputeHash(@"ConnectionString
=Name=""Value"",Size=2147483647,Precision=255,Scale=255,Direction=Input,SaltKey
=CacheSaltKey"))
.Returns(123456789);

_loggerMock.Setup(x => x.IsLoggerEnabled).Returns(true);

commandMock
.Protected()
.SetupGet<DbParameterCollection>("DbParameterCollection")
.Returns(dbParameterCollectionMock.Object);

dbParameterCollectionMock
.Setup(x => x.GetEnumerator())
.Returns(parameters.GetEnumerator());

_cacheDependenciesProcessorMock
.Setup(x => x.GetCacheDependencies(commandMock.Object, context, cachePolicy))
.Returns(expected.CacheDependencies as SortedSet<string>);

dbParameterMock.Setup(x => x.ParameterName).Returns("Name");
dbParameterMock.Setup(x => x.Value).Returns("Value");
dbParameterMock.Setup(x => x.Size).Returns(int.MaxValue);
dbParameterMock.Setup(x => x.Precision).Returns(byte.MaxValue);
dbParameterMock.Setup(x => x.Scale).Returns(byte.MaxValue);
dbParameterMock.Setup(x => x.Direction).Returns(ParameterDirection.Input);

// Act
var actual = _cacheKeyProvider.GetEFCacheKey(commandMock.Object, context, cachePolicy);

// Assert
Assert.Equal(expected, actual);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using Microsoft.EntityFrameworkCore;
using Moq;

namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

// ReSharper disable once InconsistentNaming
public class EFCacheKeyTests
{
[Fact]
public void Equals_ReturnsTrue_WhenKeyHashAndDbContextAreEqual()
{
// Arrange
var cacheKey1 = new EFCacheKey(new HashSet<string> { "Entity1" })
{
KeyHash = "hash1",
DbContext = typeof(DbContext)
};

var cacheKey2 = new EFCacheKey(new HashSet<string> { "Entity2" })
{
KeyHash = "hash1",
DbContext = typeof(DbContext)
};

// Act
var actual = cacheKey1.Equals(cacheKey2);

// Assert
Assert.True(actual);
}

[Fact]
public void Equals_ReturnsFalse_WhenObjIsNotEFCacheKey()
{
// Arrange
var cacheKey1 = new EFCacheKey(new HashSet<string> { "Entity1" })
{
KeyHash = "hash1",
DbContext = typeof(DbContext)
};

var obj = new object();

// Act
var actual = cacheKey1.Equals(obj);

// Assert
Assert.False(actual);
}

[Fact]
public void Equals_ReturnsFalse_WhenKeyHashIsDifferent()
{
// Arrange
var cacheKey1 = new EFCacheKey(new HashSet<string> { "Entity1" })
{
KeyHash = "hash1",
DbContext = typeof(DbContext)
};

var cacheKey2 = new EFCacheKey(new HashSet<string> { "Entity2" })
{
KeyHash = "hash2",
DbContext = typeof(DbContext)
};

// Act
var actual = cacheKey1.Equals(cacheKey2);

// Assert
Assert.False(actual);
}

[Fact]
public void Equals_ReturnsFalse_WhenDbContextIsDifferent()
{
// Arrange
var cacheKey1 = new EFCacheKey(new HashSet<string> { "Entity1" })
{
KeyHash = "hash1",
DbContext = typeof(DbContext)
};

var cacheKey2 = new EFCacheKey(new HashSet<string> { "Entity2" })
{
KeyHash = "hash1",
DbContext = Mock.Of<DbContext>().GetType()
};

// Act
var actual = cacheKey1.Equals(cacheKey2);

// Assert
Assert.False(actual);
}

[Fact]
public void GetHashCode_ReturnsSameHashCode_ForEqualObjects()
{
// Arrange
var cacheKey1 = new EFCacheKey(new HashSet<string> { "Entity1" })
{
KeyHash = "hash1",
DbContext = typeof(DbContext)
};

var cacheKey2 = new EFCacheKey(new HashSet<string> { "Entity2" })
{
KeyHash = "hash1",
DbContext = typeof(DbContext)
};

// Act
var hashCode1 = cacheKey1.GetHashCode();
var hashCode2 = cacheKey2.GetHashCode();

// Assert
Assert.Equal(hashCode1, hashCode2);
}

[Fact]
public void GetHashCode_ReturnsDifferentHashCode_ForDifferentObjects()
{
// Arrange
var cacheKey1 = new EFCacheKey(new HashSet<string> { "Entity1" })
{
KeyHash = "hash1",
DbContext = typeof(DbContext)
};

var cacheKey2 = new EFCacheKey(new HashSet<string> { "Entity2" })
{
KeyHash = "hash2",
DbContext = typeof(DbContext)
};

// Act
var hashCode1 = cacheKey1.GetHashCode();
var hashCode2 = cacheKey2.GetHashCode();

// Assert
Assert.NotEqual(hashCode1, hashCode2);
}

[Fact]
public void ToString_ReturnsExpectedFormat()
{
// Arrange
var cacheKey = new EFCacheKey(new HashSet<string> { "Entity1", "Entity2" })
{
KeyHash = "hash1",
DbContext = typeof(DbContext)
};

// Act
var actual = cacheKey.ToString();

// Assert
Assert.Equal("KeyHash: hash1, DbContext: DbContext, CacheDependencies: Entity1, Entity2.", actual);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using Microsoft.Extensions.Options;
using Moq;

namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

// ReSharper disable once InconsistentNaming
public class EFCacheServiceCheckTests
{
private readonly Mock<IEFCacheServiceProvider> _cacheServiceProviderMock;
private readonly EFCoreSecondLevelCacheSettings _cacheSettings;
private readonly IEFCacheServiceCheck _serviceCheck;

public EFCacheServiceCheckTests()
{
var cacheOptionsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();

_cacheServiceProviderMock = new Mock<IEFCacheServiceProvider>();
_cacheSettings = new EFCoreSecondLevelCacheSettings();

cacheOptionsMock.Setup(c => c.Value).Returns(_cacheSettings);

_serviceCheck = new EFCacheServiceCheck(cacheOptionsMock.Object, _cacheServiceProviderMock.Object);
}

[Fact]
public void EFCacheServiceCheck_ThrowsArgumentNullException_WhenCacheSettingsIsNull()
{
// Arrange
var cacheServiceProviderMock = new Mock<IEFCacheServiceProvider>();

// Act && Assert
// ReSharper disable once AssignNullToNotNullAttribute
Assert.Throws<ArgumentNullException>(() => new EFCacheServiceCheck(null, cacheServiceProviderMock.Object));
}

[Fact]
public void EFCacheServiceCheck_CreatesInstanceSuccessfully()
{
// Arrange
var cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();

cacheSettingsMock.Setup(c => c.Value).Returns(new EFCoreSecondLevelCacheSettings());

var cacheServiceProviderMock = new Mock<IEFCacheServiceProvider>();

// Act
var serviceCheck = new EFCacheServiceCheck(cacheSettingsMock.Object, cacheServiceProviderMock.Object);

// Assert
Assert.NotNull(serviceCheck);
}

[Fact]
public void EFCacheServiceCheck_ShouldNotThrowArgumentNullException_WhenCacheServiceProviderIsNull()
{
// Arrange
var cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();

cacheSettingsMock.Setup(c => c.Value).Returns(new EFCoreSecondLevelCacheSettings());

// ReSharper disable once AssignNullToNotNullAttribute
// ReSharper disable once ObjectCreationAsStatement
void Act() => new EFCacheServiceCheck(cacheSettingsMock.Object, null);

// Act
var actual = Record.Exception(Act);

// Assert
Assert.Null(actual);
}

[Fact]
public void IsCacheServiceAvailable_ReturnsFalse_WhenIsCachingInterceptorEnabledIsFalse()
{
// Arrange
_cacheSettings.IsCachingInterceptorEnabled = false;

// Act
var result = _serviceCheck.IsCacheServiceAvailable();

// Assert
Assert.False(result);
}

[Fact]
public void IsCacheServiceAvailable_ReturnsTrue_WhenUseDbCallsIfCachingProviderIsDownIsFalse()
{
// Arrange
_cacheSettings.IsCachingInterceptorEnabled = true;
_cacheSettings.UseDbCallsIfCachingProviderIsDown = false;

// Act
var result = _serviceCheck.IsCacheServiceAvailable();

// Assert
Assert.True(result);
}

[Fact]
public void
IsCacheServiceAvailable_ReturnsTrue_WhenUseDbCallsIfCachingProviderIsDownIsTrue_And_CacheServerIsAvailable()
{
// Arrange
_cacheSettings.IsCachingInterceptorEnabled = true;
_cacheSettings.UseDbCallsIfCachingProviderIsDown = true;

_cacheServiceProviderMock
.Setup(c => c.GetValue(It.IsAny<EFCacheKey>(), It.IsAny<EFCachePolicy>()))
.Returns(new EFCachedData());

// Act
var result = _serviceCheck.IsCacheServiceAvailable();

// Assert
Assert.True(result);
}

[Fact]
public void
IsCacheServiceAvailable_ThrowsInvalidOperationException_WhenUseDbCallsIfCachingProviderIsDownIsTrue_And_CacheServerIsNotAvailable()
{
// Arrange
_cacheSettings.IsCachingInterceptorEnabled = true;
_cacheSettings.UseDbCallsIfCachingProviderIsDown = true;

_cacheServiceProviderMock
.Setup(c => c.GetValue(It.IsAny<EFCacheKey>(), It.IsAny<EFCachePolicy>()))
.Throws<InvalidOperationException>();

// Act
void Act() => _serviceCheck.IsCacheServiceAvailable();

// Assert
Assert.Throws<InvalidOperationException>(Act);
}

[Fact]
public void IsCacheServiceAvailable_ReturnsTrue_WhenNotEnoughTimeHasPassedSinceTheLastCheck()
{
// Arrange
_cacheSettings.IsCachingInterceptorEnabled = true;
_cacheSettings.UseDbCallsIfCachingProviderIsDown = true;
_cacheSettings.NextCacheServerAvailabilityCheck = TimeSpan.MaxValue;

_cacheServiceProviderMock
.Setup(c => c.GetValue(It.IsAny<EFCacheKey>(), It.IsAny<EFCachePolicy>()))
.Returns(new EFCachedData());

_serviceCheck.IsCacheServiceAvailable();

// Act
var result = _serviceCheck.IsCacheServiceAvailable();

// Assert
Assert.True(result);
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;

namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

// ReSharper disable once InconsistentNaming
public class EFDebugLoggerTests
{
private readonly Mock<Action<EFCacheableLogEvent>> _cacheableEventMock;
private readonly Mock<Action<EFCacheInvalidationInfo>> _cacheInvalidationEventMock;
private readonly Mock<IServiceProvider> _serviceProviderMock;
private readonly IEFDebugLogger _debugLogger;

public EFDebugLoggerTests()
{
_cacheableEventMock = new Mock<Action<EFCacheableLogEvent>>();
_cacheInvalidationEventMock = new Mock<Action<EFCacheInvalidationInfo>>();
_serviceProviderMock = new Mock<IServiceProvider>();

var cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();
var loggerMock = new Mock<ILogger<EFDebugLogger>>();
var cacheSettings = new EFCoreSecondLevelCacheSettings
{
EnableLogging = true,
CacheableEvent = _cacheableEventMock.Object,
CacheInvalidationEvent = _cacheInvalidationEventMock.Object
};

cacheSettingsMock.Setup(c => c.Value).Returns(cacheSettings);
loggerMock.Setup(l => l.IsEnabled(LogLevel.Debug)).Returns(true);

_debugLogger = new EFDebugLogger(
cacheSettingsMock.Object,
loggerMock.Object,
_serviceProviderMock.Object);
}

[Fact]
public void EFDebugLogger_ThrowsArgumentNullException_WhenCacheSettingsIsNull()
{
// Arrange
var loggerMock = new Mock<ILogger<EFDebugLogger>>();
var serviceProviderMock = new Mock<IServiceProvider>();

Assert.Throws<ArgumentNullException>(() =>
new EFDebugLogger(null!, loggerMock.Object, serviceProviderMock.Object));
}

[Fact]
public void EFDebugLogger_ThrowsArgumentNullException_WhenLoggerIsNull()
{
// Arrange
var cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();
var serviceProviderMock = new Mock<IServiceProvider>();

// Act && Assert
Assert.Throws<ArgumentNullException>(() =>
new EFDebugLogger(cacheSettingsMock.Object, null!, serviceProviderMock.Object));
}

[Fact]
public void EFDebugLogger_EnablesLogging_WhenCacheSettingsEnableLoggingIsTrue()
{
// Arrange
var cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();
var loggerMock = new Mock<ILogger<EFDebugLogger>>();
var serviceProviderMock = new Mock<IServiceProvider>();

cacheSettingsMock.Setup(c => c.Value).Returns(new EFCoreSecondLevelCacheSettings { EnableLogging = true });
loggerMock.Setup(l => l.IsEnabled(LogLevel.Debug)).Returns(true);

// Act
var logger = new EFDebugLogger(cacheSettingsMock.Object, loggerMock.Object, serviceProviderMock.Object);

// Assert
Assert.True(logger.IsLoggerEnabled);
}

[Fact]
public void EFDebugLogger_DisablesLogging_WhenCacheSettingsEnableLoggingIsFalse()
{
// Arrange
var cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();
var loggerMock = new Mock<ILogger<EFDebugLogger>>();
var serviceProviderMock = new Mock<IServiceProvider>();

cacheSettingsMock.Setup(c => c.Value).Returns(new EFCoreSecondLevelCacheSettings { EnableLogging = false });

// Act
var logger = new EFDebugLogger(cacheSettingsMock.Object, loggerMock.Object, serviceProviderMock.Object);

// Assert
Assert.False(logger.IsLoggerEnabled);
}

[Fact]
public void NotifyCacheableEvent_InvokesCacheableEvent_WhenLoggerIsEnabled()
{
// Arrange && Act
_debugLogger.NotifyCacheableEvent(CacheableLogEventId.CachingSystemStarted, "TestMessage", "TestCommand");

// Assert
_cacheableEventMock.Verify(e => e.Invoke(It.Is<EFCacheableLogEvent>(
x =>
x.EventId == CacheableLogEventId.CachingSystemStarted
&& x.Message == "TestMessage"
&& x.CommandText == "TestCommand"
&& x.ServiceProvider == _serviceProviderMock.Object)),

Check warning

Code scanning / CodeQL

Reference equality test on System.Object Warning

Reference equality for System.Object comparisons (
this
argument has type IServiceProvider).
Times.Once);
}

[Fact]
public void NotifyCacheableEvent_DoesNotInvokeCacheableEvent_WhenLoggerIsDisabled()
{
// Arrange
var cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();
var loggerMock = new Mock<ILogger<EFDebugLogger>>();
var serviceProviderMock = new Mock<IServiceProvider>();
var cacheableEventMock = new Mock<Action<EFCacheableLogEvent>>();

cacheSettingsMock.Setup(c => c.Value).Returns(new EFCoreSecondLevelCacheSettings { EnableLogging = false });
cacheSettingsMock.Object.Value.CacheableEvent = cacheableEventMock.Object;

var logger = new EFDebugLogger(cacheSettingsMock.Object, loggerMock.Object, serviceProviderMock.Object);

// Act
logger.NotifyCacheableEvent(CacheableLogEventId.CachingSystemStarted, "TestMessage", "TestCommand");

// Assert
cacheableEventMock.Verify(e => e.Invoke(It.IsAny<EFCacheableLogEvent>()), Times.Never);
}

[Fact]
public void NotifyCacheableEvent_DoesNotInvokeCacheableEvent_WhenCacheableEventIsNull()
{
// Arrange
var cacheSettingsMock = new Mock<IOptions<EFCoreSecondLevelCacheSettings>>();
var serviceProviderMock = new Mock<IServiceProvider>();
var loggerMock = new Mock<ILogger<EFDebugLogger>>();
var cacheableEventMock = new Mock<Action<EFCacheableLogEvent>>();

cacheSettingsMock.Setup(c => c.Value).Returns(new EFCoreSecondLevelCacheSettings { EnableLogging = true });
loggerMock.Setup(l => l.IsEnabled(LogLevel.Debug)).Returns(true);

var logger = new EFDebugLogger(cacheSettingsMock.Object, loggerMock.Object, serviceProviderMock.Object);

// Act
logger.NotifyCacheableEvent(CacheableLogEventId.CachingSystemStarted, "TestMessage", "TestCommand");

// Assert
cacheableEventMock.Verify(e => e.Invoke(It.IsAny<EFCacheableLogEvent>()), Times.Never);
}

[Fact]
public void NotifyCacheInvalidation_InvokesEFCacheInvalidationInfo()
{
// Arrange
var cacheDependencies = new HashSet<string>();

// Act
_debugLogger.NotifyCacheInvalidation(true, cacheDependencies);

// Assert
_cacheInvalidationEventMock.Verify(e => e.Invoke(It.Is<EFCacheInvalidationInfo>(
x =>
x.CacheDependencies == cacheDependencies

Check warning

Code scanning / CodeQL

Reference equality test on System.Object Warning

Reference equality for System.Object comparisons (
this
argument has type ISet).
&& x.ClearAllCachedEntries == true

Check notice

Code scanning / CodeQL

Unnecessarily complex Boolean expression Note

The expression 'A == true' can be simplified to 'A'.
&& x.ServiceProvider == _serviceProviderMock.Object)),

Check warning

Code scanning / CodeQL

Reference equality test on System.Object Warning

Reference equality for System.Object comparisons (
this
argument has type IServiceProvider).
Times.Once);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;

namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

// ReSharper disable once InconsistentNaming
public class EFServiceCollectionExtensionsTests
{
[Fact]
public void AddEFSecondLevelCache_ThrowsArgumentNullException_WhenOptionsIsNull()
{
// Arrange
var services = new ServiceCollection();

// Act && Assert
Assert.Throws<ArgumentNullException>(() => services.AddEFSecondLevelCache(null!));
}

[Fact]
public void AddEFSecondLevelCache_RegistersRequiredServices()
{
// Arrange
var services = new ServiceCollection();

// Act
services.TryAddSingleton(typeof(ILogger<>), typeof(Logger<>));
services.AddEFSecondLevelCache(_ => { });

// Assert
var serviceProvider = services.BuildServiceProvider();

Assert.NotNull(serviceProvider.GetService<IEFDebugLogger>());
Assert.NotNull(serviceProvider.GetService<IEFCacheServiceCheck>());
Assert.NotNull(serviceProvider.GetService<IEFCacheKeyPrefixProvider>());
Assert.NotNull(serviceProvider.GetService<IEFCacheKeyProvider>());
Assert.NotNull(serviceProvider.GetService<IEFCachePolicyParser>());
Assert.NotNull(serviceProvider.GetService<IEFSqlCommandsProcessor>());
Assert.NotNull(serviceProvider.GetService<IEFCacheDependenciesProcessor>());
Assert.NotNull(serviceProvider.GetService<ILockProvider>());
Assert.NotNull(serviceProvider.GetService<IMemoryCacheChangeTokenProvider>());
Assert.NotNull(serviceProvider.GetService<IDbCommandInterceptorProcessor>());
Assert.NotNull(serviceProvider.GetService<SecondLevelCacheInterceptor>());
}

[Fact]
public void AddEFSecondLevelCache_RegistersDefaultHashProvider_WhenHashProviderIsNull()
{
// Arrange
var services = new ServiceCollection();

// Act
services.AddEFSecondLevelCache(options => { options.Settings.HashProvider = null; });

// Assert
var serviceProvider = services.BuildServiceProvider();

Assert.IsType<XxHash64Unsafe>(serviceProvider.GetService<IEFHashProvider>());
}

[Fact]
public void AddEFSecondLevelCache_RegistersCustomHashProvider_WhenHashProviderIsNotNull()
{
// Arrange
var services = new ServiceCollection();

// Act
services.AddEFSecondLevelCache(options => { options.Settings.HashProvider = typeof(CustomHashProvider); });

// Assert
var serviceProvider = services.BuildServiceProvider();

Assert.IsType<CustomHashProvider>(serviceProvider.GetService<IEFHashProvider>());
}

[Fact]
public void AddEFSecondLevelCache_RegistersDefaultCacheProvider_WhenCacheProviderIsNull()
{
// Arrange
var services = new ServiceCollection();

// Act
services.AddEFSecondLevelCache(options => { options.Settings.CacheProvider = null; });

// Assert
var serviceProvider = services.BuildServiceProvider();

Assert.IsType<EFMemoryCacheServiceProvider>(serviceProvider.GetService<IEFCacheServiceProvider>());
}

[Fact]
public void AddEFSecondLevelCache_RegistersCustomCacheProvider_WhenCacheProviderIsNotNull()
{
// Arrange
var services = new ServiceCollection();

// Act
services.AddEFSecondLevelCache(options => { options.Settings.CacheProvider = typeof(CustomCacheProvider); });

// Assert
var serviceProvider = services.BuildServiceProvider();

Assert.IsType<CustomCacheProvider>(serviceProvider.GetService<IEFCacheServiceProvider>());
}

private class Logger<TCategoryName> : ILogger<TCategoryName>
{
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
Func<TState, Exception, string> formatter) => throw new NotImplementedException();

public bool IsEnabled(LogLevel logLevel) => throw new NotImplementedException();

public IDisposable BeginScope<TState>(TState state) where TState : notnull =>
throw new NotImplementedException();
}

private class CustomHashProvider : IEFHashProvider
{
public ulong ComputeHash(string data) => throw new NotImplementedException();

public ulong ComputeHash(byte[] data) => throw new NotImplementedException();

public ulong ComputeHash(byte[] data, int offset, int len, uint seed) => throw new NotImplementedException();
}

private class CustomCacheProvider : IEFCacheServiceProvider
{
public void ClearAllCachedEntries() => throw new NotImplementedException();

public EFCachedData GetValue(EFCacheKey cacheKey, EFCachePolicy cachePolicy) =>
throw new NotImplementedException();

public void InsertValue(EFCacheKey cacheKey, EFCachedData value, EFCachePolicy cachePolicy) =>
throw new NotImplementedException();

public void InvalidateCacheDependencies(EFCacheKey cacheKey) => throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

// ReSharper disable once InconsistentNaming
public class EFTableColumnInfoTests
{
[Fact]
@@ -15,10 +16,10 @@ public void ToString_ReturnsFormattedString_WithValidProperties()
};

// Act
var result = columnInfo.ToString();
var actual = columnInfo.ToString();

// Assert
Assert.Equal("Ordinal: 1, Name: ColumnName, DbTypeName: DbType, TypeName= Type.", result);
Assert.Equal("Ordinal: 1, Name: ColumnName, DbTypeName: DbType, TypeName= Type.", actual);
}

[Fact]
@@ -28,9 +29,9 @@ public void ToString_ReturnsFormattedString_WithDefaultProperties()
var columnInfo = new EFTableColumnInfo();

// Act
var result = columnInfo.ToString();
var actual = columnInfo.ToString();

// Assert
Assert.Equal("Ordinal: 0, Name: , DbTypeName: , TypeName= .", result);
Assert.Equal("Ordinal: 0, Name: , DbTypeName: , TypeName= .", actual);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

// ReSharper disable once InconsistentNaming
public class EFTableRowTests
{
[Fact]
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

// ReSharper disable once InconsistentNaming
public class EFTableRowsDataReaderTests
{
[Fact]
@@ -310,7 +311,7 @@ public void GetDataTypeName_ReturnsEmptyString_WhenTypeNameIsEmpty()
}

[Fact]
public void GetFieldType_ReturnsCorrectType_WhenOrdinalIsValid()
public void GetFieldType_ReturnsExpectedType_WhenOrdinalIsValid()
{
// Arrange
var tableRows = new EFTableRows
@@ -1487,10 +1488,10 @@ public void GetFieldValue_ShouldNotThrowInvalidCastExceptionWhenConvertToDecimal
dataReader.Read();

// Act
var exception = Record.Exception(() => dataReader.GetFieldValue<decimal>(0));
var actual = Record.Exception(() => dataReader.GetFieldValue<decimal>(0));

// Assert
Assert.Null(exception);
Assert.Null(actual);
}

public static IEnumerable<object[]> InvalidNumberData =>
@@ -1680,10 +1681,10 @@ public void GetFieldValue_ShouldNotThrowInvalidCastExceptionWhenValueConversionF
dataReader.Read();

// Act
var exception = Record.Exception(() => dataReader.GetFieldValue<DateOnly>(0));
var actual = Record.Exception(() => dataReader.GetFieldValue<DateOnly>(0));

// Assert
Assert.Null(exception);
Assert.Null(actual);
}

[Fact]
@@ -1782,10 +1783,10 @@ public void GetFieldValue_ShouldNotThrowInvalidCastExceptionWhenValueConversionF
dataReader.Read();

// Act
var exception = Record.Exception(() => dataReader.GetFieldValue<TimeOnly>(0));
var actual = Record.Exception(() => dataReader.GetFieldValue<TimeOnly>(0));

// Assert
Assert.Null(exception);
Assert.Null(actual);
}

[Fact]
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

// ReSharper disable once InconsistentNaming
public class EFTableRowsTests
{
[Fact]
@@ -12,6 +13,7 @@ public void Constructor_ShouldThrowArgumentNullException_WhenReaderIsNull()
DbDataReader reader = null;

// Act & Assert
// ReSharper disable once AssignNullToNotNullAttribute
Assert.Throws<ArgumentNullException>("reader", () => new EFTableRows(reader));
}

@@ -75,7 +77,7 @@ public void Constructor_ShouldInitializeColumnsInfo_WhenReaderIsValid()
}

[Fact]
public void Indexer_ReturnsCorrectRow_WhenIndexIsValid()
public void Indexer_ReturnsExpectedRow_WhenIndexIsValid()
{
// Arrange
var rows = new List<EFTableRow>
@@ -87,14 +89,14 @@ public void Indexer_ReturnsCorrectRow_WhenIndexIsValid()
var tableRows = new EFTableRows { Rows = rows };

// Act
var result = tableRows[1];
var actual = tableRows[1];

// Assert
Assert.Equal(2, result.Values[0]);
Assert.Equal(2, actual.Values[0]);
}

[Fact]
public void Indexer_SetsCorrectRow_WhenIndexIsValid()
public void Indexer_SetsExpectedRow_WhenIndexIsValid()
{
// Arrange
var rows = new List<EFTableRow>
@@ -160,7 +162,7 @@ public void Add_ShouldAddItemToRows()
}

[Fact]
public void Get_ShouldReturnCorrectRow()
public void Get_ShouldReturnExpectedRow()
{
// Arrange
var tableRows = new EFTableRows();
@@ -169,14 +171,14 @@ public void Get_ShouldReturnCorrectRow()
tableRows.Add(row);

// Act
var result = tableRows.Get(0);
var actual = tableRows.Get(0);

// Assert
Assert.Equal(row, result);
Assert.Equal(row, actual);
}

[Fact]
public void GetOrdinal_ShouldReturnCorrectOrdinal()
public void GetOrdinal_ShouldReturnExpectedOrdinal()
{
// Arrange
var tableRows = new EFTableRows()
@@ -195,7 +197,7 @@ public void GetOrdinal_ShouldReturnCorrectOrdinal()
}

[Fact]
public void GetName_ShouldReturnCorrectName()
public void GetName_ShouldReturnExpectedName()
{
// Arrange
var tableRows = new EFTableRows()
@@ -240,7 +242,7 @@ public void RecordsAffected_ReturnsMinusOne()
}

[Fact]
public void VisibleFieldCount_ReturnsCorrectValue_WhenSet()
public void VisibleFieldCount_ReturnsExpectedValue_WhenSet()
{
// Arrange
var tableRows = new EFTableRows { VisibleFieldCount = 5 };
@@ -269,9 +271,9 @@ public void GetFieldTypeName_ReturnsTypeName_WhenOrdinalIsValid()
var tableRows = new EFTableRows { ColumnsInfo = columnsInfo };

// Act
var result = tableRows.GetFieldTypeName(0);
var actual = tableRows.GetFieldTypeName(0);

Assert.Equal("System.String", result);
Assert.Equal("System.String", actual);
}

[Fact]
@@ -286,9 +288,9 @@ public void GetFieldTypeName_ThrowsArgumentOutOfRangeException_WhenOrdinalIsInva
var tableRows = new EFTableRows { ColumnsInfo = columnsInfo };

// Act
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => tableRows.GetFieldTypeName(0));
var actual = Assert.Throws<ArgumentOutOfRangeException>(() => tableRows.GetFieldTypeName(0));

// Assert
Assert.Equal("Index[0] was outside of array's bounds. (Parameter 'ordinal')", exception.Message);
Assert.Equal("Index[0] was outside of array's bounds. (Parameter 'ordinal')", actual.Message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using AsyncKeyedLock;

namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

public class LockProviderTests
{
[Fact]
public void Lock_ReturnsNonNullReleaser()
{
// Arrange
var lockProvider = new LockProvider();

// Act
var releaser = lockProvider.Lock();

// Assert
Assert.IsType<AsyncNonKeyedLockReleaser>(releaser);
}

[Fact]
public async Task LockAsync_ReturnsNonNullReleaser()
{
// Arrange
var lockProvider = new LockProvider();

// Act
var releaser = await lockProvider.LockAsync();

// Assert
Assert.IsType<AsyncNonKeyedLockReleaser>(releaser);
}

[Fact]
public void Lock_CanBeDisposed()
{
// Arrange
var lockProvider = new LockProvider();
var releaser = lockProvider.Lock();

// Act
releaser.Dispose();

// Assert
Assert.True(true); // If no exception is thrown, the test passes
}

[Fact]
public async Task LockAsync_CanBeDisposed()
{
// Arrange
var lockProvider = new LockProvider();
var releaser = await lockProvider.LockAsync();

// Act
releaser.Dispose();

// Assert
Assert.True(true); // If no exception is thrown, the test passes
}

[Fact]
public void Dispose_DisposesLockProvider()
{
// Arrange
var lockProvider = new LockProvider();

// Act
lockProvider.Dispose();

// Assert
Assert.True(true); // If no exception is thrown, the test passes
}
}
Original file line number Diff line number Diff line change
@@ -115,7 +115,7 @@ public void NonQueryExecuted_ReturnsProcessedResult()
}

[Fact]
public void NonQueryExecuted_ShouldNotArgumentNullException_WhenAnyParameterIsNull()
public void NonQueryExecuted_ShouldNotThrowArgumentNullException_WhenAnyParameterIsNull()
{
// Arrange
const int expected = int.MaxValue;
@@ -235,7 +235,7 @@ public async Task NonQueryExecutedAsync_ReturnsProcessedResult()
}

[Fact]
public async Task NonQueryExecutedAsync_ShouldNotArgumentNullException_WhenAnyParameterIsNull()
public async Task NonQueryExecutedAsync_ShouldNotThrowArgumentNullException_WhenAnyParameterIsNull()
{
// Arrange
const int expected = 1;
@@ -353,7 +353,7 @@ public void NonQueryExecuting_ReturnsExpectedInterceptionResult()
}

[Fact]
public void NonQueryExecuting_ShouldNotArgumentNullException_WhenAnyParameterIsNull()
public void NonQueryExecuting_ShouldNotThrowArgumentNullException_WhenAnyParameterIsNull()
{
// Arrange
var expected = InterceptionResult<int>.SuppressWithResult(int.MaxValue);
@@ -472,7 +472,7 @@ public async Task NonQueryExecutingAsync_ReturnsExpectedInterceptionResult()
}

[Fact]
public async Task NonQueryExecutingAsync_ShouldNotArgumentNullException_WhenAnyParameterIsNull()
public async Task NonQueryExecutingAsync_ShouldNotThrowArgumentNullException_WhenAnyParameterIsNull()
{
// Arrange
var expected = InterceptionResult<int>.SuppressWithResult(int.MaxValue);
@@ -586,7 +586,7 @@ public void ReaderExecuted_ReturnsExpectedDbDataReader()
}

[Fact]
public void ReaderExecuted_ShouldNotArgumentNullException_WhenAnyParameterIsNull()
public void ReaderExecuted_ShouldNotThrowArgumentNullException_WhenAnyParameterIsNull()
{
// Arrange
var expected = Mock.Of<DbDataReader>();
@@ -699,7 +699,7 @@ public async Task ReaderExecutedAsync_ReturnsExpectedDbDataReader()
}

[Fact]
public async Task ReaderExecutedAsync_ShouldNotArgumentNullException_WhenAnyParameterIsNull()
public async Task ReaderExecutedAsync_ShouldNotThrowArgumentNullException_WhenAnyParameterIsNull()
{
// Arrange
var expected = Mock.Of<DbDataReader>();
@@ -817,7 +817,7 @@ public void ReaderExecuting_ReturnsExpectedInterceptionResult()
}

[Fact]
public void ReaderExecuting_ShouldNotArgumentNullException_WhenAnyParameterIsNull()
public void ReaderExecuting_ShouldNotThrowArgumentNullException_WhenAnyParameterIsNull()
{
// Arrange
var dataReader = Mock.Of<DbDataReader>();
@@ -939,7 +939,7 @@ public async Task ReaderExecutingAsync_ReturnsExpectedInterceptionResult()
}

[Fact]
public async Task ReaderExecutingAsync_ShouldNotArgumentNullException_WhenAnyParameterIsNull()
public async Task ReaderExecutingAsync_ShouldNotThrowArgumentNullException_WhenAnyParameterIsNull()
{
// Arrange
var dataReader = Mock.Of<DbDataReader>();
@@ -1058,7 +1058,7 @@ public void ScalarExecuted_ReturnsProcessedResult()
}

[Fact]
public void ScalarExecuted_ShouldNotArgumentNullException_WhenAnyParameterIsNull()
public void ScalarExecuted_ShouldNotThrowArgumentNullException_WhenAnyParameterIsNull()
{
// Arrange
var expected = new object();
@@ -1178,7 +1178,7 @@ public async Task ScalarExecutedAsync_ReturnsProcessedResult()
}

[Fact]
public async Task ScalarExecutedAsync_ShouldNotArgumentNullException_WhenAnyParameterIsNull()
public async Task ScalarExecutedAsync_ShouldNotThrowArgumentNullException_WhenAnyParameterIsNull()
{
// Arrange
var expected = new object();
@@ -1296,7 +1296,7 @@ public void ScalarExecuting_ReturnsExpectedInterceptionResult()
}

[Fact]
public void ScalarExecuting_ShouldNotArgumentNullException_WhenAnyParameterIsNull()
public void ScalarExecuting_ShouldNotThrowArgumentNullException_WhenAnyParameterIsNull()
{
// Arrange
var expected = InterceptionResult<object>.SuppressWithResult(new object());
@@ -1415,7 +1415,7 @@ public async Task ScalarExecutingAsync_ReturnsExpectedInterceptionResult()
}

[Fact]
public async Task ScalarExecutingAsync_ShouldNotArgumentNullException_WhenAnyParameterIsNull()
public async Task ScalarExecutingAsync_ShouldNotThrowArgumentNullException_WhenAnyParameterIsNull()
{
// Arrange
var expected = InterceptionResult<object>.SuppressWithResult(new object());
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

public class StringExtensionsTests
{
[Fact]
public void EndsWith_ReturnsFalse_WhenValueIsNull()
{
// Arrange
var collection = new List<string> { "testValue", "anotherValue" };

// Act
var actual = collection.EndsWith(null, StringComparison.OrdinalIgnoreCase);

// Assert
Assert.False(actual);
}

[Fact]
public void EndsWith_ReturnsTrue_WhenCollectionContainsItemEndingWithValue()
{
// Arrange
var collection = new List<string> { "testValue", "anotherValue" };

// Act
var actual = collection.EndsWith("Value", StringComparison.OrdinalIgnoreCase);

// Assert
Assert.True(actual);
}

[Fact]
public void EndsWith_ReturnsFalse_WhenCollectionDoesNotContainItemEndingWithValue()
{
// Arrange
var collection = new List<string> { "testValue", "anotherValue" };

// Act
var actual = collection.EndsWith("NotExist", StringComparison.OrdinalIgnoreCase);

// Assert
Assert.False(actual);
}

[Fact]
public void StartsWith_ReturnsFalse_WhenValueIsNull()
{
// Arrange
var collection = new List<string> { "ValueTest", "ValueAnother" };

// Act
var actual = collection.StartsWith(null, StringComparison.OrdinalIgnoreCase);

// Assert
Assert.False(actual);
}

[Fact]
public void StartsWith_ReturnsTrue_WhenCollectionContainsItemStartingWithValue()
{
// Arrange
var collection = new List<string> { "ValueTest", "ValueAnother" };

// Act
var actual = collection.StartsWith("Value", StringComparison.OrdinalIgnoreCase);

// Assert
Assert.True(actual);
}

[Fact]
public void StartsWith_ReturnsFalse_WhenCollectionDoesNotContainItemStartingWithValue()
{
// Arrange
var collection = new List<string> { "ValueTest", "ValueAnother" };

// Act
var actual = collection.StartsWith("NotExist", StringComparison.OrdinalIgnoreCase);

// Assert
Assert.False(actual);
}

[Fact]
public void ContainsEvery_ReturnsFalse_WhenCollectionIsNull()
{
// Arrange
var source = new List<string> { "item1", "item2", "item3" };

// Act
var actual = source.ContainsEvery(null, StringComparer.OrdinalIgnoreCase);

// Assert
Assert.False(actual);
}

[Fact]
public void ContainsEvery_ReturnsTrue_WhenSourceContainsEveryItemInCollection()
{
// Arrange
var source = new List<string> { "item1", "item2", "item3" };
var collection = new List<string> { "item1", "item2", "item3" };

// Act
var actual = source.ContainsEvery(collection, StringComparer.OrdinalIgnoreCase);

// Assert
Assert.True(actual);
}

[Fact]
public void ContainsEvery_ReturnsFalse_WhenSourceDoesNotContainEveryItemInCollection()
{
// Arrange
var source = new List<string> { "item1", "item2" };
var collection = new List<string> { "item1", "item2", "item3" };

// Act
var actual = source.ContainsEvery(collection, StringComparer.OrdinalIgnoreCase);

// Assert
Assert.False(actual);
}

[Fact]
public void ContainsOnly_ReturnsFalse_WhenCollectionIsNull()
{
// Arrange
var source = new List<string> { "item1", "item2" };

// Act
var actual = source.ContainsOnly(null, StringComparer.OrdinalIgnoreCase);

// Assert
Assert.False(actual);
}

[Fact]
public void ContainsOnly_ReturnsTrue_WhenSourceContainsOnlyItemsInCollection()
{
// Arrange
var source = new List<string> { "item1", "item2" };
var collection = new List<string> { "item1", "item2", "item3" };

// Act
var actual = source.ContainsOnly(collection, StringComparer.OrdinalIgnoreCase);

// Assert
Assert.True(actual);
}

[Fact]
public void ContainsOnly_ReturnsFalse_WhenSourceContainsItemsNotInCollection()
{
// Arrange
var source = new List<string> { "item1", "item4" };
var collection = new List<string> { "item1", "item2", "item3" };

// Act
var actual = source.ContainsOnly(collection, StringComparer.OrdinalIgnoreCase);

// Assert
Assert.False(actual);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

public class TableEntityInfoTests
{
[Fact]
public void ToString_ReturnsExpectedFormat_WhenClrTypeAndTableNameAreSet()
{
// Arrange
var entityInfo = new TableEntityInfo
{
ClrType = typeof(string),
TableName = "TestTable"
};

// Act
var actual = entityInfo.ToString();

// Assert
Assert.Equal("System.String::TestTable", actual);
}

[Fact]
public void ToString_ReturnsExpectedFormat_WhenClrTypeIsNull()
{
// Arrange
var entityInfo = new TableEntityInfo
{
ClrType = null!,
TableName = "TestTable"
};

// Act
var actual = entityInfo.ToString();

// Assert
Assert.Equal("::TestTable", actual);
}

[Fact]
public void ToString_ReturnsExpectedFormat_WhenTableNameIsNull()
{
// Arrange
var entityInfo = new TableEntityInfo
{
ClrType = typeof(string),
TableName = null!
};

// Act
var actual = entityInfo.ToString();

// Assert
Assert.Equal("System.String::", actual);
}

[Fact]
public void ToString_ReturnsExpectedFormat_WhenBothClrTypeAndTableNameAreNull()
{
// Arrange
var entityInfo = new TableEntityInfo
{
ClrType = null!,
TableName = null!
};

// Act
var actual = entityInfo.ToString();

// Assert
Assert.Equal("::", actual);
}
}
Original file line number Diff line number Diff line change
@@ -8,10 +8,10 @@
public void IsNull_ShouldReturnTrue_WhenValueIsNull()
{
// Arrange && Act
var result = ((object)null).IsNull();
var actual = ((object)null).IsNull();

Check warning

Code scanning / CodeQL

Useless upcast Warning

There is no need to upcast from
null
to
Object
- the conversion can be done implicitly.

// Assert
Assert.True(result);
Assert.True(actual);
}

[Fact]
@@ -21,10 +21,10 @@
object value = DBNull.Value;

// Act
var result = value.IsNull();
var actual = value.IsNull();

// Assert
Assert.True(result);
Assert.True(actual);
}

[Fact]
@@ -34,10 +34,10 @@
object value = new object();

// Act
var result = value.IsNull();
var actual = value.IsNull();

// Assert
Assert.False(result);
Assert.False(actual);
}

[Theory]
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
namespace EFCoreSecondLevelCacheInterceptor.UnitTests;

public class XxHash64UnsafeTests
{
[Fact]
public void ComputeHash_ThrowsArgumentNullException_WhenStringDataIsNull()
{
// Arrange
var hashProvider = new XxHash64Unsafe();

// Act && Assert
Assert.Throws<ArgumentNullException>(() => hashProvider.ComputeHash(((string)null)!));

Check warning

Code scanning / CodeQL

Useless upcast Warning

There is no need to upcast from
null
to
String
- the conversion can be done implicitly.
}

[Fact]
public void ComputeHash_ThrowsArgumentNullException_WhenByteArrayDataIsNull()
{
// Arrange
var hashProvider = new XxHash64Unsafe();

// Act && Assert
Assert.Throws<ArgumentNullException>(() => hashProvider.ComputeHash(((byte[])null)!));

Check warning

Code scanning / CodeQL

Useless upcast Warning

There is no need to upcast from
null
to
Byte[]
- the conversion can be done implicitly.
}

[Fact]
public void ComputeHash_ThrowsArgumentNullException_WhenByteArrayDataWithOffsetIsNull()
{
// Arrange
var hashProvider = new XxHash64Unsafe();

// Act && Assert
Assert.Throws<ArgumentNullException>(() => hashProvider.ComputeHash(null!, 0, 0, 0));
}

[Fact]
public void ComputeHash_ReturnsSameHash_ForSameStringData()
{
// Arrange
var hashProvider = new XxHash64Unsafe();
var data = "test";

// Act
var hash1 = hashProvider.ComputeHash(data);
var hash2 = hashProvider.ComputeHash(data);

// Act && Assert
Assert.Equal(hash1, hash2);
}

[Fact]
public void ComputeHash_ReturnsSameHash_ForSameByteArrayData()
{
// Arrange
var hashProvider = new XxHash64Unsafe();
var data = "test"u8.ToArray();

// Act
var hash1 = hashProvider.ComputeHash(data);
var hash2 = hashProvider.ComputeHash(data);

// Act && Assert
Assert.Equal(hash1, hash2);
}

[Fact]
public void ComputeHash_ReturnsSameHash_ForSameByteArrayDataWithOffset()
{
// Arrange
var hashProvider = new XxHash64Unsafe();
var data = "test"u8.ToArray();

// Act
var hash1 = hashProvider.ComputeHash(data, 0, data.Length, 0);
var hash2 = hashProvider.ComputeHash(data, 0, data.Length, 0);

// Act && Assert
Assert.Equal(hash1, hash2);
}

[Fact]
public void ComputeHash_ReturnsDifferentHash_ForDifferentStringData()
{
// Arrange
var hashProvider = new XxHash64Unsafe();

// Act
var hash1 = hashProvider.ComputeHash("test1");
var hash2 = hashProvider.ComputeHash("test2");

// Act && Assert
Assert.NotEqual(hash1, hash2);
}

[Fact]
public void ComputeHash_ReturnsDifferentHash_ForDifferentByteArrayData()
{
// Arrange
var hashProvider = new XxHash64Unsafe();
var data1 = "test1"u8.ToArray();
var data2 = "test2"u8.ToArray();

// Act
var hash1 = hashProvider.ComputeHash(data1);
var hash2 = hashProvider.ComputeHash(data2);

// Act && Assert
Assert.NotEqual(hash1, hash2);
}

[Fact]
public void ComputeHash_ReturnsDifferentHash_ForDifferentByteArrayDataWithOffset()
{
// Arrange
var hashProvider = new XxHash64Unsafe();
var data1 = "test1"u8.ToArray();
var data2 = "test2"u8.ToArray();

// Act
var hash1 = hashProvider.ComputeHash(data1, 0, data1.Length, 0);
var hash2 = hashProvider.ComputeHash(data2, 0, data2.Length, 0);

// Act && Assert
Assert.NotEqual(hash1, hash2);
}
}

Unchanged files with check annotations Beta

EFServiceProvider.RunInContext(context =>
{
context.People.Where(a => a.Id > 500).BatchDelete();

Check warning on line 19 in src/Tests/Issues/Issue192/Program.cs

GitHub Actions / Analyze (csharp)

'IQueryableBatchExtensions.BatchDelete(IQueryable)' is obsolete: 'As of EF 7 there are native method ExcuteDelete.'
context.People.Where(a => a.Id <= 500)

Check warning on line 21 in src/Tests/Issues/Issue192/Program.cs

GitHub Actions / Analyze (csharp)

'IQueryableBatchExtensions.BatchUpdate<T>(IQueryable<T>, Expression<Func<T, T>>, Type?)' is obsolete: 'As of EF 7 there are native method ExcuteUpdate.'
.BatchUpdate(a => new Person { Name = a.Name + "-Test" });
//`EFCore.BulkExtensions` doesn't pass all of its executed SQL commands such as `BulkInsert` through the interceptors.
private static void runBenchmarks()
{
var config = ManualConfig.Create(DefaultConfig.Instance)

Check warning on line 22 in src/Tests/EFCoreSecondLevelCacheInterceptor.PerformanceTests/Program.cs

GitHub Actions / Analyze (csharp)

'ConfigExtensions.With(IConfig, params IAnalyser[])' is obsolete: 'This method will soon be removed, please start using .AddAnalyser() instead.'

Check warning on line 22 in src/Tests/EFCoreSecondLevelCacheInterceptor.PerformanceTests/Program.cs

GitHub Actions / Analyze (csharp)

'ConfigExtensions.With(IConfig, params IExporter[])' is obsolete: 'This method will soon be removed, please start using .AddExporter() instead.'

Check warning on line 22 in src/Tests/EFCoreSecondLevelCacheInterceptor.PerformanceTests/Program.cs

GitHub Actions / Analyze (csharp)

'ConfigExtensions.With(IConfig, params IDiagnoser[])' is obsolete: 'This method will soon be removed, please start using .AddDiagnoser() instead.'

Check warning on line 22 in src/Tests/EFCoreSecondLevelCacheInterceptor.PerformanceTests/Program.cs

GitHub Actions / Analyze (csharp)

'ConfigExtensions.With(IConfig, params IColumn[])' is obsolete: 'This method will soon be removed, please start using .AddColumn() instead.'

Check warning on line 22 in src/Tests/EFCoreSecondLevelCacheInterceptor.PerformanceTests/Program.cs

GitHub Actions / Analyze (csharp)

'ConfigExtensions.With(IConfig, params IColumn[])' is obsolete: 'This method will soon be removed, please start using .AddColumn() instead.'

Check warning on line 22 in src/Tests/EFCoreSecondLevelCacheInterceptor.PerformanceTests/Program.cs

GitHub Actions / Analyze (csharp)

'ConfigExtensions.With(IConfig, params IColumn[])' is obsolete: 'This method will soon be removed, please start using .AddColumn() instead.'

Check warning on line 22 in src/Tests/EFCoreSecondLevelCacheInterceptor.PerformanceTests/Program.cs

GitHub Actions / Analyze (csharp)

'ConfigExtensions.With(IConfig, params IColumn[])' is obsolete: 'This method will soon be removed, please start using .AddColumn() instead.'

Check warning on line 22 in src/Tests/EFCoreSecondLevelCacheInterceptor.PerformanceTests/Program.cs

GitHub Actions / Analyze (csharp)

'ConfigExtensions.With(IConfig, params IColumn[])' is obsolete: 'This method will soon be removed, please start using .AddColumn() instead.'
.With(BenchmarkDotNet.Analysers.EnvironmentAnalyser.Default)
.With(BenchmarkDotNet.Exporters.MarkdownExporter.GitHub)
.With(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default)