From 9da8fb319245e3cc2039ff28818758f00c5fbe28 Mon Sep 17 00:00:00 2001 From: Sean Leonard Date: Tue, 23 Jul 2024 14:46:25 -0700 Subject: [PATCH 1/5] Update engine code: SqlQueryEngine ExecuteQuery(StoredProcedure) support for using cache. Updated method signature formatting in DabCacheService to have params on new lines. --- src/Core/Resolvers/SqlQueryEngine.cs | 32 ++++++++++++++++++++++ src/Core/Services/Cache/DabCacheService.cs | 10 +++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Core/Resolvers/SqlQueryEngine.cs b/src/Core/Resolvers/SqlQueryEngine.cs index 853d52711b..548d334681 100644 --- a/src/Core/Resolvers/SqlQueryEngine.cs +++ b/src/Core/Resolvers/SqlQueryEngine.cs @@ -353,11 +353,43 @@ public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMeta // private async Task ExecuteAsync(SqlExecuteStructure structure, string dataSourceName) { + RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig(); DatabaseType databaseType = _runtimeConfigProvider.GetConfig().GetDataSourceFromDataSourceName(dataSourceName).DatabaseType; IQueryBuilder queryBuilder = _queryFactory.GetQueryBuilder(databaseType); IQueryExecutor queryExecutor = _queryFactory.GetQueryExecutor(databaseType); string queryString = queryBuilder.Build(structure); + // Global Cache enablement check + if (runtimeConfig.CanUseCache()) + { + // Entity level cache behavior checks + bool entityCacheEnabled = runtimeConfig.Entities[structure.EntityName].IsCachingEnabled; + + // Database policies not considered for stored procedure execution. + if (entityCacheEnabled) + { + DatabaseQueryMetadata queryMetadata = new( + queryText: queryString, + dataSource: dataSourceName, + queryParameters: structure.Parameters); + + JsonArray? result = await _cache.GetOrSetAsync( + async () => await queryExecutor.ExecuteQueryAsync( + sqltext: queryString, + parameters: structure.Parameters, + dataReaderHandler: queryExecutor.GetJsonArrayAsync, + httpContext: _httpContextAccessor.HttpContext!, + args: null, + dataSourceName: dataSourceName), + queryMetadata, + runtimeConfig.GetEntityCacheEntryTtl(entityName: structure.EntityName)); + + byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(result); + JsonDocument cacheServiceResponse = JsonDocument.Parse(jsonBytes); + return cacheServiceResponse; + } + } + JsonArray? resultArray = await queryExecutor.ExecuteQueryAsync( sqltext: queryString, diff --git a/src/Core/Services/Cache/DabCacheService.cs b/src/Core/Services/Cache/DabCacheService.cs index 3499a36612..20ab2905d4 100644 --- a/src/Core/Services/Cache/DabCacheService.cs +++ b/src/Core/Services/Cache/DabCacheService.cs @@ -53,7 +53,10 @@ public DabCacheService(IFusionCache cache, ILogger? logger, IHt /// Number of seconds the cache entry should be valid before eviction. /// JSON Response /// Throws when the cache-miss factory method execution fails. - public async ValueTask GetOrSetAsync(IQueryExecutor queryExecutor, DatabaseQueryMetadata queryMetadata, int cacheEntryTtl) + public async ValueTask GetOrSetAsync( + IQueryExecutor queryExecutor, + DatabaseQueryMetadata queryMetadata, + int cacheEntryTtl) { string cacheKey = CreateCacheKey(queryMetadata); JsonElement? result = await _cache.GetOrSetAsync( @@ -87,7 +90,10 @@ public DabCacheService(IFusionCache cache, ILogger? logger, IHt /// Number of seconds the cache entry should be valid before eviction. /// JSON Response /// Throws when the cache-miss factory method execution fails. - public async ValueTask GetOrSetAsync(Func> executeQueryAsync, DatabaseQueryMetadata queryMetadata, int cacheEntryTtl) + public async ValueTask GetOrSetAsync( + Func> executeQueryAsync, + DatabaseQueryMetadata queryMetadata, + int cacheEntryTtl) { string cacheKey = CreateCacheKey(queryMetadata); From 75d58acba346431bfa262a5a6313125e656557a5 Mon Sep 17 00:00:00 2001 From: Sean Leonard Date: Thu, 1 Aug 2024 14:43:26 -0700 Subject: [PATCH 2/5] add tests and more comments for using the cache with stored procedures. --- src/Core/Resolvers/SqlQueryEngine.cs | 9 +- .../DabCacheServiceIntegrationTests.cs | 85 +++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/Core/Resolvers/SqlQueryEngine.cs b/src/Core/Resolvers/SqlQueryEngine.cs index 548d334681..8eb087beca 100644 --- a/src/Core/Resolvers/SqlQueryEngine.cs +++ b/src/Core/Resolvers/SqlQueryEngine.cs @@ -359,13 +359,18 @@ public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMeta IQueryExecutor queryExecutor = _queryFactory.GetQueryExecutor(databaseType); string queryString = queryBuilder.Build(structure); - // Global Cache enablement check + // Only proceed to use caching code when + // RuntimeConfig.Cache.Enabled is true + // RuntimeConfig.DataSource.Options.SetSessionContext is false if (runtimeConfig.CanUseCache()) { // Entity level cache behavior checks bool entityCacheEnabled = runtimeConfig.Entities[structure.EntityName].IsCachingEnabled; - // Database policies not considered for stored procedure execution. + // Stored procedures do not support nor honor runtime config defined + // authorization policies. Here, DAB only checks that the entity has + // caching enabled and doesn't check for database policies. This explicitly + // differs from how the cache works for non-stored procedure requests. if (entityCacheEnabled) { DatabaseQueryMetadata queryMetadata = new( diff --git a/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs b/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs index 785c8a572b..ce6e2758c3 100644 --- a/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs +++ b/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs @@ -7,6 +7,7 @@ using System.Data.Common; using System.Net; using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using Azure.DataApiBuilder.Core.Models; @@ -252,6 +253,90 @@ await Assert.ThrowsExceptionAsync( message: "Expected an exception to be thrown."); } + /// + /// Tests DAB's cache service invocation when the type is JsonArray. + /// JsonArray aligns with the type used for executing stored procedures against + /// MSSQL databases. + /// This test validates that the cache service returns the expected a database response + /// because the cache is empty and the factory method is expected to be called. + /// + [TestMethod] + public async Task JsonArray_CacheServiceInvokation_CacheEmpty_ReturnsFactoryResult() + { + // Arrange + using FusionCache cache = CreateFusionCache(sizeLimit: 1000, defaultEntryTtlSeconds: 1); + JsonArray? expectedDatabaseResponse = new() + { + JsonNode.Parse(@"{""key"": ""value""}"), + JsonNode.Parse(@"{""key"": ""value2""}") + }; + + Mock>> mockExecuteQuery = new(); + mockExecuteQuery.Setup(e => e.Invoke()).Returns(Task.FromResult(expectedDatabaseResponse)); + + Dictionary parameters = new() + { + {"param1", new DbConnectionParam(value: "param1Value") } + }; + + DatabaseQueryMetadata queryMetadata = new(queryText: "select c.name from c", dataSource: "dataSource1", queryParameters: parameters); + DabCacheService dabCache = CreateDabCacheService(cache); + + // Act + int cacheEntryTtl = 1; + JsonArray? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + + // Assert + Assert.AreEqual(expected: true, actual: mockExecuteQuery.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); + + // Validates that the expected database response is returned by the cache service. + Assert.AreEqual(expected: expectedDatabaseResponse, actual: result, message: ERROR_UNEXPECTED_RESULT); + } + + /// + /// Tests DAB's cache service invocation when the type is JsonArray. + /// JsonArray aligns with the type used for executing stored procedures against + /// MSSQL databases. + /// This test validates that a cache hit occurs when the same request + /// is submitted before the cache entry expires. Validates that + /// DabCacheService.CreateCacheKey(..) outputs the same key given constant input. + /// + [TestMethod] + public async Task JsonArray_CacheServiceInvocation_CacheHit_NoFactoryInvocation() + { + // Arrange + using FusionCache cache = CreateFusionCache(sizeLimit: 1000, defaultEntryTtlSeconds: 1); + JsonArray? expectedDatabaseResponse = new() + { + JsonNode.Parse(@"{""key"": ""value""}"), + JsonNode.Parse(@"{""key"": ""value2""}") + }; + + Mock>> mockExecuteQuery = new(); + mockExecuteQuery.Setup(e => e.Invoke()).Returns(Task.FromResult(expectedDatabaseResponse)); + + Dictionary parameters = new() + { + {"param1", new DbConnectionParam(value: "param1Value") } + }; + + DatabaseQueryMetadata queryMetadata = new(queryText: "select c.name from c", dataSource: "dataSource1", queryParameters: parameters); + DabCacheService dabCache = CreateDabCacheService(cache); + + int cacheEntryTtl = 1; + // First call. Cache miss + _ = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + + // Act + JsonArray? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + + // Assert + Assert.AreEqual(expected: true, actual: mockExecuteQuery.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); + Assert.IsFalse(mockExecuteQuery.Invocations.Count > 1, message: "Expected a cache hit, but observed cache misses."); + Assert.AreEqual(expected: true, actual: mockExecuteQuery.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); + Assert.AreEqual(expected: expectedDatabaseResponse, actual: result, message: ERROR_UNEXPECTED_RESULT); + } + /// /// Validates that the first invocation of the cache service results in a cache miss because /// the cache is expected to be empty. From e37f4fb570a00a80fece56e6cda954e611b7f5b7 Mon Sep 17 00:00:00 2001 From: Sean Leonard Date: Mon, 9 Sep 2024 09:22:32 -0700 Subject: [PATCH 3/5] add propert param spacing and handle null result from executor by not serializing, just returning null jsondoc. -> addresses pr feedback. --- src/Core/Resolvers/SqlQueryEngine.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Core/Resolvers/SqlQueryEngine.cs b/src/Core/Resolvers/SqlQueryEngine.cs index 8eb087beca..00f313cd1d 100644 --- a/src/Core/Resolvers/SqlQueryEngine.cs +++ b/src/Core/Resolvers/SqlQueryEngine.cs @@ -375,8 +375,8 @@ public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMeta { DatabaseQueryMetadata queryMetadata = new( queryText: queryString, - dataSource: dataSourceName, - queryParameters: structure.Parameters); + dataSource: dataSourceName, + queryParameters: structure.Parameters); JsonArray? result = await _cache.GetOrSetAsync( async () => await queryExecutor.ExecuteQueryAsync( @@ -389,8 +389,14 @@ public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMeta queryMetadata, runtimeConfig.GetEntityCacheEntryTtl(entityName: structure.EntityName)); - byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(result); - JsonDocument cacheServiceResponse = JsonDocument.Parse(jsonBytes); + JsonDocument? cacheServiceResponse = null; + + if (result is not null) + { + byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(result); + cacheServiceResponse = JsonDocument.Parse(jsonBytes); + } + return cacheServiceResponse; } } From 65e00296733471189e0ddfaec75805939ddacfdc Mon Sep 17 00:00:00 2001 From: Sean Leonard Date: Mon, 9 Sep 2024 12:50:11 -0700 Subject: [PATCH 4/5] Per pr feedback, updated grammar issue in comment and updated all instances of "cacheEntryTtl" variable name to "cacheEntryTtlInSeconds" --- .../DabCacheServiceIntegrationTests.cs | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs b/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs index ce6e2758c3..d05b8ef2d9 100644 --- a/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs +++ b/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs @@ -62,8 +62,8 @@ public async Task FirstCacheServiceInvocationCallsFactory() DabCacheService dabCache = CreateDabCacheService(cache); // Act - int cacheEntryTtl = 1; - JsonElement? result = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + int cacheEntryTtlInSeconds = 1; + JsonElement? result = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Assert Assert.AreEqual(expected: true, actual: mockQueryExecutor.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); @@ -99,11 +99,11 @@ public async Task SecondCacheServiceInvocation_CacheHit_NoSecondFactoryCall() DabCacheService dabCache = CreateDabCacheService(cache); // Prime the cache with a single entry - int cacheEntryTtl = 1; - _ = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + int cacheEntryTtlInSeconds = 1; + _ = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Act - JsonElement? result = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + JsonElement? result = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Assert Assert.IsFalse(mockQueryExecutor.Invocations.Count is 2, message: "Expected a cache hit, but observed two cache misses."); @@ -134,15 +134,15 @@ public async Task ThirdCacheServiceInvocation_CacheHit_NoSecondFactoryCall() DabCacheService dabCache = CreateDabCacheService(cache); // Prime the cache with a single entry - int cacheEntryTtl = 1; - _ = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); - _ = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + int cacheEntryTtlInSeconds = 1; + _ = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); + _ = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Sleep for the amount of time the cache entry is valid to trigger eviction. - Thread.Sleep(millisecondsTimeout: cacheEntryTtl * 1000); + Thread.Sleep(millisecondsTimeout: cacheEntryTtlInSeconds * 1000); // Act - JsonElement? result = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + JsonElement? result = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Assert Assert.IsFalse(mockQueryExecutor.Invocations.Count is 1, message: "QueryExecutor invocation count too low. A cache hit shouldn't have occurred since the entry should have expired."); @@ -174,11 +174,11 @@ public async Task LargeCacheKey_BadBehavior() DabCacheService dabCache = CreateDabCacheService(cache); // Prime the cache. - int cacheEntryTtl = 1; - _ = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + int cacheEntryTtlInSeconds = 1; + _ = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Act - JsonElement? result = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + JsonElement? result = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Assert Assert.IsFalse(mockQueryExecutor.Invocations.Count is 1, message: "Unexpected cache hit when cache entry size exceeded cache capacity."); @@ -208,8 +208,8 @@ public async Task CacheServiceFactoryInvocationReturnsNull() DabCacheService dabCache = CreateDabCacheService(cache); // Act - int cacheEntryTtl = 1; - JsonElement? result = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + int cacheEntryTtlInSeconds = 1; + JsonElement? result = await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Assert Assert.AreEqual(expected: true, actual: mockQueryExecutor.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); @@ -247,9 +247,9 @@ public async Task CacheServiceFactoryInvocationThrowsException() DabCacheService dabCache = CreateDabCacheService(cache); // Act and Assert - int cacheEntryTtl = 1; + int cacheEntryTtlInSeconds = 1; await Assert.ThrowsExceptionAsync( - async () => await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl), + async () => await dabCache.GetOrSetAsync(queryExecutor: mockQueryExecutor.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds), message: "Expected an exception to be thrown."); } @@ -257,7 +257,7 @@ await Assert.ThrowsExceptionAsync( /// Tests DAB's cache service invocation when the type is JsonArray. /// JsonArray aligns with the type used for executing stored procedures against /// MSSQL databases. - /// This test validates that the cache service returns the expected a database response + /// This test validates that the cache service returns the expected database response /// because the cache is empty and the factory method is expected to be called. /// [TestMethod] @@ -283,8 +283,8 @@ public async Task JsonArray_CacheServiceInvokation_CacheEmpty_ReturnsFactoryResu DabCacheService dabCache = CreateDabCacheService(cache); // Act - int cacheEntryTtl = 1; - JsonArray? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + int cacheEntryTtlInSeconds = 1; + JsonArray? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Assert Assert.AreEqual(expected: true, actual: mockExecuteQuery.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); @@ -323,12 +323,12 @@ public async Task JsonArray_CacheServiceInvocation_CacheHit_NoFactoryInvocation( DatabaseQueryMetadata queryMetadata = new(queryText: "select c.name from c", dataSource: "dataSource1", queryParameters: parameters); DabCacheService dabCache = CreateDabCacheService(cache); - int cacheEntryTtl = 1; + int cacheEntryTtlInSeconds = 1; // First call. Cache miss - _ = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + _ = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Act - JsonArray? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + JsonArray? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Assert Assert.AreEqual(expected: true, actual: mockExecuteQuery.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); @@ -362,8 +362,8 @@ public async Task FirstCacheServiceInvocationCallsFuncAndReturnResult() DabCacheService dabCache = CreateDabCacheService(cache); // Act - int cacheEntryTtl = 1; - JObject? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + int cacheEntryTtlInSeconds = 1; + JObject? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Assert Assert.AreEqual(expected: true, actual: mockExecuteQuery.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); @@ -394,12 +394,12 @@ public async Task SecondCacheServiceInvocation_CacheHit_NoFuncInvocation() DatabaseQueryMetadata queryMetadata = new(queryText: "select c.name from c", dataSource: "dataSource1", queryParameters: parameters); DabCacheService dabCache = CreateDabCacheService(cache); - int cacheEntryTtl = 1; + int cacheEntryTtlInSeconds = 1; // First call. Cache miss - _ = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + _ = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Act - JObject? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + JObject? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Assert Assert.AreEqual(expected: true, actual: mockExecuteQuery.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); @@ -432,17 +432,17 @@ public async Task ThirdCacheServiceInvocation_CacheHit_NoFuncInvocation() DatabaseQueryMetadata queryMetadata = new(queryText: "select c.name from c", dataSource: "dataSource1", queryParameters: parameters); DabCacheService dabCache = CreateDabCacheService(cache); - int cacheEntryTtl = 1; + int cacheEntryTtlInSeconds = 1; // First call. Cache miss - _ = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); - _ = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + _ = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); + _ = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Sleep for the amount of time the cache entry is valid to trigger eviction. - Thread.Sleep(millisecondsTimeout: cacheEntryTtl * 1000); + Thread.Sleep(millisecondsTimeout: cacheEntryTtlInSeconds * 1000); // Act - JObject? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtl); + JObject? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Assert Assert.IsFalse(mockExecuteQuery.Invocations.Count < 2, message: "QueryExecutor invocation count too low. A cache hit shouldn't have occurred since the entry should have expired."); From 08ed0c53c048f14c78b7e31193b13d6b82dfc9bc Mon Sep 17 00:00:00 2001 From: Sean Leonard Date: Mon, 9 Sep 2024 12:55:24 -0700 Subject: [PATCH 5/5] updated grammar , no more "invokation" and also reordered assert which checks for invocation > 1 and invocation = 1 to give separate error messages to clearly indicate whether the cache wasn't hit --- src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs b/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs index d05b8ef2d9..1185726764 100644 --- a/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs +++ b/src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs @@ -261,7 +261,7 @@ await Assert.ThrowsExceptionAsync( /// because the cache is empty and the factory method is expected to be called. /// [TestMethod] - public async Task JsonArray_CacheServiceInvokation_CacheEmpty_ReturnsFactoryResult() + public async Task JsonArray_CacheServiceInvocation_CacheEmpty_ReturnsFactoryResult() { // Arrange using FusionCache cache = CreateFusionCache(sizeLimit: 1000, defaultEntryTtlSeconds: 1); @@ -331,7 +331,6 @@ public async Task JsonArray_CacheServiceInvocation_CacheHit_NoFactoryInvocation( JsonArray? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Assert - Assert.AreEqual(expected: true, actual: mockExecuteQuery.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); Assert.IsFalse(mockExecuteQuery.Invocations.Count > 1, message: "Expected a cache hit, but observed cache misses."); Assert.AreEqual(expected: true, actual: mockExecuteQuery.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); Assert.AreEqual(expected: expectedDatabaseResponse, actual: result, message: ERROR_UNEXPECTED_RESULT); @@ -402,7 +401,6 @@ public async Task SecondCacheServiceInvocation_CacheHit_NoFuncInvocation() JObject? result = await dabCache.GetOrSetAsync(executeQueryAsync: mockExecuteQuery.Object, queryMetadata: queryMetadata, cacheEntryTtl: cacheEntryTtlInSeconds); // Assert - Assert.AreEqual(expected: true, actual: mockExecuteQuery.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); Assert.IsFalse(mockExecuteQuery.Invocations.Count > 1, message: "Expected a cache hit, but observed cache misses."); Assert.AreEqual(expected: true, actual: mockExecuteQuery.Invocations.Count is 1, message: ERROR_UNEXPECTED_INVOCATIONS); Assert.AreEqual(expected: expectedDatabaseResponse, actual: result, message: ERROR_UNEXPECTED_RESULT);