Skip to content

Commit 439f7a5

Browse files
authored
Merge branch 'main' into dev/rubencerna/update-validation-autoentities
2 parents c4fc9d6 + 77b44a0 commit 439f7a5

4 files changed

Lines changed: 71 additions & 35 deletions

File tree

src/Config/ObjectModel/RuntimeConfig.cs

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,6 @@ public RuntimeConfig(
373373
}
374374

375375
SetupDataSourcesUsed();
376-
377376
}
378377

379378
/// <summary>
@@ -529,12 +528,13 @@ Runtime is not null && Runtime.Host is not null
529528

530529
/// <summary>
531530
/// Returns the ttl-seconds value for a given entity.
532-
/// If the property is not set, returns the global default value set in the runtime config.
533-
/// If the global default value is not set, the default value is used (5 seconds).
531+
/// If the entity explicitly sets ttl-seconds, that value is used.
532+
/// Otherwise, falls back to the global cache TTL setting.
533+
/// Callers are responsible for checking whether caching is enabled before using the result.
534534
/// </summary>
535535
/// <param name="entityName">Name of the entity to check cache configuration.</param>
536536
/// <returns>Number of seconds (ttl) that a cache entry should be valid before cache eviction.</returns>
537-
/// <exception cref="DataApiBuilderException">Raised when an invalid entity name is provided or if the entity has caching disabled.</exception>
537+
/// <exception cref="DataApiBuilderException">Raised when an invalid entity name is provided.</exception>
538538
public virtual int GetEntityCacheEntryTtl(string entityName)
539539
{
540540
if (!Entities.TryGetValue(entityName, out Entity? entityConfig))
@@ -545,31 +545,23 @@ public virtual int GetEntityCacheEntryTtl(string entityName)
545545
subStatusCode: DataApiBuilderException.SubStatusCodes.EntityNotFound);
546546
}
547547

548-
if (!entityConfig.IsCachingEnabled)
549-
{
550-
throw new DataApiBuilderException(
551-
message: $"{entityName} does not have caching enabled.",
552-
statusCode: HttpStatusCode.BadRequest,
553-
subStatusCode: DataApiBuilderException.SubStatusCodes.NotSupported);
554-
}
555-
556-
if (entityConfig.Cache.UserProvidedTtlOptions)
548+
if (entityConfig.Cache is not null && entityConfig.Cache.UserProvidedTtlOptions)
557549
{
558550
return entityConfig.Cache.TtlSeconds.Value;
559551
}
560-
else
561-
{
562-
return GlobalCacheEntryTtl();
563-
}
552+
553+
return GlobalCacheEntryTtl();
564554
}
565555

566556
/// <summary>
567557
/// Returns the cache level value for a given entity.
568-
/// If the property is not set, returns the default (L1L2) for a given entity.
558+
/// If the entity explicitly sets level, that value is used.
559+
/// Otherwise, falls back to the global cache level or the default.
560+
/// Callers are responsible for checking whether caching is enabled before using the result.
569561
/// </summary>
570562
/// <param name="entityName">Name of the entity to check cache configuration.</param>
571563
/// <returns>Cache level that a cache entry should be stored in.</returns>
572-
/// <exception cref="DataApiBuilderException">Raised when an invalid entity name is provided or if the entity has caching disabled.</exception>
564+
/// <exception cref="DataApiBuilderException">Raised when an invalid entity name is provided.</exception>
573565
public virtual EntityCacheLevel GetEntityCacheEntryLevel(string entityName)
574566
{
575567
if (!Entities.TryGetValue(entityName, out Entity? entityConfig))
@@ -580,22 +572,12 @@ public virtual EntityCacheLevel GetEntityCacheEntryLevel(string entityName)
580572
subStatusCode: DataApiBuilderException.SubStatusCodes.EntityNotFound);
581573
}
582574

583-
if (!entityConfig.IsCachingEnabled)
584-
{
585-
throw new DataApiBuilderException(
586-
message: $"{entityName} does not have caching enabled.",
587-
statusCode: HttpStatusCode.BadRequest,
588-
subStatusCode: DataApiBuilderException.SubStatusCodes.NotSupported);
589-
}
590-
591-
if (entityConfig.Cache.UserProvidedLevelOptions)
575+
if (entityConfig.Cache is not null && entityConfig.Cache.UserProvidedLevelOptions)
592576
{
593577
return entityConfig.Cache.Level.Value;
594578
}
595-
else
596-
{
597-
return EntityCacheLevel.L1L2;
598-
}
579+
580+
return EntityCacheOptions.DEFAULT_LEVEL;
599581
}
600582

601583
/// <summary>

src/Service.Tests/Caching/CachingConfigProcessingTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,4 +416,48 @@ private static string GetRawConfigJson(string globalCacheConfig, string entityCa
416416

417417
return expectedRuntimeConfigJson.ToString();
418418
}
419+
420+
/// <summary>
421+
/// Regression test: Validates that when global runtime cache is enabled but entity cache is disabled,
422+
/// GetEntityCacheEntryTtl and GetEntityCacheEntryLevel do not throw and return sensible defaults.
423+
/// Previously, these methods threw a DataApiBuilderException (BadRequest/NotSupported) when the entity
424+
/// had caching disabled, which caused 400 errors for valid requests when the global cache was enabled.
425+
/// These methods are now pure accessors that always return a value regardless of cache enablement.
426+
/// </summary>
427+
/// <param name="globalCacheConfig">Global cache configuration JSON fragment.</param>
428+
/// <param name="entityCacheConfig">Entity cache configuration JSON fragment.</param>
429+
/// <param name="expectedTtl">Expected TTL returned by GetEntityCacheEntryTtl.</param>
430+
/// <param name="expectedLevel">Expected cache level returned by GetEntityCacheEntryLevel.</param>
431+
[DataRow(@",""cache"": { ""enabled"": true, ""ttl-seconds"": 10 }", @",""cache"": { ""enabled"": false }", 10, EntityCacheLevel.L1L2, DisplayName = "Global cache enabled with custom TTL, entity cache disabled: entity returns global TTL and default level.")]
432+
[DataRow(@",""cache"": { ""enabled"": true }", @",""cache"": { ""enabled"": false }", 5, EntityCacheLevel.L1L2, DisplayName = "Global cache enabled with default TTL, entity cache disabled: entity returns default TTL and default level.")]
433+
[DataRow(@",""cache"": { ""enabled"": true, ""ttl-seconds"": 10 }", @"", 10, EntityCacheLevel.L1L2, DisplayName = "Global cache enabled with custom TTL, entity cache omitted: entity returns global TTL and default level.")]
434+
[DataTestMethod]
435+
public void GetEntityCacheEntryTtlAndLevel_DoesNotThrow_WhenRuntimeCacheEnabledAndEntityCacheDisabled(
436+
string globalCacheConfig,
437+
string entityCacheConfig,
438+
int expectedTtl,
439+
EntityCacheLevel expectedLevel)
440+
{
441+
// Arrange
442+
string fullConfig = GetRawConfigJson(globalCacheConfig: globalCacheConfig, entityCacheConfig: entityCacheConfig);
443+
RuntimeConfigLoader.TryParseConfig(
444+
json: fullConfig,
445+
out RuntimeConfig? config,
446+
replacementSettings: null);
447+
448+
Assert.IsNotNull(config, message: "Config must not be null, runtime config JSON deserialization failed.");
449+
Assert.IsTrue(config.IsCachingEnabled, message: "Global caching should be enabled for this test scenario.");
450+
451+
Entity entity = config.Entities.First().Value;
452+
Assert.IsFalse(entity.IsCachingEnabled, message: "Entity caching should be disabled for this test scenario.");
453+
454+
string entityName = config.Entities.First().Key;
455+
456+
// Act & Assert - These calls must not throw.
457+
int actualTtl = config.GetEntityCacheEntryTtl(entityName);
458+
EntityCacheLevel actualLevel = config.GetEntityCacheEntryLevel(entityName);
459+
460+
Assert.AreEqual(expected: expectedTtl, actual: actualTtl, message: "GetEntityCacheEntryTtl should return the global/default TTL when entity cache is disabled.");
461+
Assert.AreEqual(expected: expectedLevel, actual: actualLevel, message: "GetEntityCacheEntryLevel should return the default level when entity cache is disabled.");
462+
}
419463
}

src/Service.Tests/Configuration/ConfigurationTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2903,7 +2903,7 @@ public async Task ValidateErrorMessageForMutationWithoutReadPermission()
29032903
}";
29042904
string queryName = "stock_by_pk";
29052905

2906-
ValidateMutationSucceededAtDbLayer(server, client, graphQLQuery, queryName, authToken, AuthorizationResolver.ROLE_AUTHENTICATED);
2906+
await ValidateMutationSucceededAtDbLayer(server, client, graphQLQuery, queryName, authToken, AuthorizationResolver.ROLE_AUTHENTICATED);
29072907
}
29082908
finally
29092909
{
@@ -3225,7 +3225,7 @@ public async Task ValidateInheritanceOfReadPermissionFromAnonymous()
32253225
/// <param name="query">GraphQL query/mutation text</param>
32263226
/// <param name="queryName">GraphQL query/mutation name</param>
32273227
/// <param name="authToken">Auth token for the graphQL request</param>
3228-
private static async void ValidateMutationSucceededAtDbLayer(TestServer server, HttpClient client, string query, string queryName, string authToken, string clientRoleHeader)
3228+
private static async Task ValidateMutationSucceededAtDbLayer(TestServer server, HttpClient client, string query, string queryName, string authToken, string clientRoleHeader)
32293229
{
32303230
JsonElement queryResponse = await GraphQLRequestExecutor.PostGraphQLRequestAsync(
32313231
client,
@@ -3237,6 +3237,7 @@ private static async void ValidateMutationSucceededAtDbLayer(TestServer server,
32373237
clientRoleHeader: clientRoleHeader);
32383238

32393239
Assert.IsNotNull(queryResponse);
3240+
Assert.AreNotEqual(JsonValueKind.Null, queryResponse.ValueKind, "Expected a JSON object response but received null.");
32403241
Assert.IsFalse(queryResponse.TryGetProperty("errors", out _));
32413242
}
32423243

src/Service.Tests/Configuration/Telemetry/AzureLogAnalyticsTests.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,18 @@ public async Task TestAzureLogAnalyticsFlushServiceSucceed(string message, LogLe
120120

121121
_ = Task.Run(() => flusherService.StartAsync(tokenSource.Token));
122122

123-
await Task.Delay(2000);
123+
// Poll until the log appears (the flusher service needs time to dequeue and upload)
124+
int maxWaitMs = 10000;
125+
int pollIntervalMs = 100;
126+
int elapsed = 0;
127+
while (customClient.LogAnalyticsLogs.Count == 0 && elapsed < maxWaitMs)
128+
{
129+
await Task.Delay(pollIntervalMs);
130+
elapsed += pollIntervalMs;
131+
}
124132

125133
// Assert
134+
Assert.IsTrue(customClient.LogAnalyticsLogs.Count > 0, $"Expected at least one log entry after waiting {elapsed}ms, but found none.");
126135
AzureLogAnalyticsLogs actualLog = customClient.LogAnalyticsLogs[0];
127136
Assert.AreEqual(logLevel.ToString(), actualLog.LogLevel);
128137
Assert.AreEqual(message, actualLog.Message);

0 commit comments

Comments
 (0)