diff --git a/schemas/dab.draft.schema.json b/schemas/dab.draft.schema.json
index 70d5c4be29..84f8e5cfbd 100644
--- a/schemas/dab.draft.schema.json
+++ b/schemas/dab.draft.schema.json
@@ -819,15 +819,23 @@
"additionalProperties": false,
"properties": {
"mcp": {
- "type": "object",
- "description": "MCP endpoint configuration",
- "additionalProperties": false,
- "properties": {
- "dml-tools": {
+ "oneOf": [
+ {
"$ref": "#/$defs/boolean-or-string",
- "description": "Enable/disable all DML tools with default settings."
+ "description": "Boolean shorthand: true enables dml-tools only (custom-tool remains false), false disables all MCP functionality."
+ },
+ {
+ "type": "object",
+ "description": "MCP endpoint configuration",
+ "additionalProperties": false,
+ "properties": {
+ "dml-tools": {
+ "$ref": "#/$defs/boolean-or-string",
+ "description": "Enable/disable all DML tools with default settings."
+ }
+ }
}
- }
+ ]
},
"rest": {
"type": "object",
diff --git a/src/Cli.Tests/AutoConfigTests.cs b/src/Cli.Tests/AutoConfigTests.cs
index 40e3a461f7..7bd5487305 100644
--- a/src/Cli.Tests/AutoConfigTests.cs
+++ b/src/Cli.Tests/AutoConfigTests.cs
@@ -80,7 +80,7 @@ public void TestConfigureAutoentitiesDefinition_WithTemplateOptions()
definitionName: "test-def",
templateRestEnabled: true,
templateGraphqlEnabled: false,
- templateMcpDmlTool: "true",
+ templateMcpDmlTools: "true",
templateCacheEnabled: true,
templateCacheTtlSeconds: 30,
templateCacheLevel: "L1",
@@ -196,7 +196,7 @@ public void TestConfigureAutoentitiesDefinition_InvalidMcpDmlTool()
AutoConfigOptions options = new(
definitionName: "test-def",
- templateMcpDmlTool: "invalid-value",
+ templateMcpDmlTools: "invalid-value",
permissions: new[] { "anonymous", "read" },
config: TEST_RUNTIME_CONFIG_FILE
);
diff --git a/src/Cli.Tests/EndToEndTests.cs b/src/Cli.Tests/EndToEndTests.cs
index 4ae27f790f..d5bd937fe9 100644
--- a/src/Cli.Tests/EndToEndTests.cs
+++ b/src/Cli.Tests/EndToEndTests.cs
@@ -3,6 +3,7 @@
using Azure.DataApiBuilder.Config.Converters;
using Azure.DataApiBuilder.Product;
+using Azure.DataApiBuilder.Service;
using Cli.Constants;
using Microsoft.Data.SqlClient;
@@ -857,6 +858,83 @@ public void TestEngineStartUpWithVerboseAndLogLevelOptions(string logLevelOption
StringAssert.Contains(output, $"User provided config file: {TEST_RUNTIME_CONFIG_FILE}", StringComparison.Ordinal);
}
+ ///
+ /// Validates that `dab start` correctly sets
+ /// based on whether the --LogLevel CLI flag is provided.
+ ///
+ /// When the --LogLevel flag is provided, IsLogLevelOverriddenByCli should be true.
+ /// When the --LogLevel flag is omitted (log level comes from the config file), IsLogLevelOverriddenByCli should be false.
+ ///
+ /// The --LogLevel CLI flag value, or null to omit the flag.
+ /// Expected value of Startup.IsLogLevelOverriddenByCli.
+ [DataTestMethod]
+ [DataRow(null, false, DisplayName = "IsLogLevelOverriddenByCli is false")]
+ [DataRow(LogLevel.Error, true, DisplayName = "IsLogLevelOverriddenByCli is true")]
+ public async Task TestStartCommandResolvesLogLevelFromConfigOrFlag(
+ LogLevel? cliLogLevel,
+ bool expectedIsOverridden)
+ {
+ string baseConfig = @"
+ {
+ ""$schema"": """ + DAB_DRAFT_SCHEMA_TEST_PATH + @""",
+ ""data-source"": {
+ ""database-type"": ""mssql"",
+ ""connection-string"": """ + SAMPLE_TEST_CONN_STRING + @"""
+ },
+ ""runtime"": {
+ ""rest"": {
+ ""path"": ""/api"",
+ ""enabled"": true
+ },
+ ""graphql"": {
+ ""path"": ""/graphql"",
+ ""enabled"": true,
+ ""allow-introspection"": true
+ },
+ ""host"": {
+ ""mode"": ""development"",
+ ""cors"": {
+ ""origins"": [],
+ ""allow-credentials"": false
+ },
+ ""authentication"": {
+ ""provider"": ""Unauthenticated""
+ }
+ },
+ ""telemetry"": {
+ ""log-level"": {
+ ""Azure.DataApiBuilder.Core.Services.ISqlMetadataProvider"": ""Information"",
+ ""Azure.DataApiBuilder.Core"": ""Debug"",
+ ""Azure.DataApiBuilder.Service.Controllers.RestController"": ""Error"",
+ ""default"": ""Warning""
+ }
+ }
+ },
+ ""entities"": {}
+ }";
+
+ // Merge in an entity so the config is not rejected for having an empty entities section.
+ string configWithLogLevel = AddPropertiesToJson(baseConfig, BASIC_ENTITY_WITH_ANONYMOUS_ROLE);
+ _fileSystem!.File.WriteAllText(TEST_RUNTIME_CONFIG_FILE, configWithLogLevel);
+
+ StartOptions options = new(
+ verbose: false,
+ logLevel: cliLogLevel,
+ isHttpsRedirectionDisabled: false,
+ mcpStdio: false,
+ mcpRole: null,
+ config: TEST_RUNTIME_CONFIG_FILE);
+
+ // Run TryStartEngineWithOptions on a background task because StartEngine blocks until the host shuts down.
+ Task engineTask = Task.Run(() =>
+ TryStartEngineWithOptions(options, _runtimeConfigLoader!, _fileSystem!));
+
+ // Wait for the engine to finish loading the config.
+ await Task.Delay(TimeSpan.FromSeconds(5));
+
+ Assert.AreEqual(expectedIsOverridden, Startup.IsLogLevelOverriddenByCli);
+ }
+
///
/// Validates that valid usage of verbs and associated options produce exit code 0 (CliReturnCode.SUCCESS).
/// Verifies that explicitly implemented verbs (add, update, init, start) and appropriately
diff --git a/src/Cli.Tests/ModuleInitializer.cs b/src/Cli.Tests/ModuleInitializer.cs
index 0bbb5b3eab..3a10eeffde 100644
--- a/src/Cli.Tests/ModuleInitializer.cs
+++ b/src/Cli.Tests/ModuleInitializer.cs
@@ -65,6 +65,8 @@ public static void Init()
VerifierSettings.IgnoreMember(entity => entity.EntityFirst);
// Ignore the entity IsLinkingEntity as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(entity => entity.IsLinkingEntity);
+ // Ignore the entity IsAutoentity as that's unimportant from a test standpoint.
+ VerifierSettings.IgnoreMember(entity => entity.IsAutoentity);
// Ignore the UserProvidedTtlOptions. They aren't serialized to our config file, enforced by EntityCacheOptionsConverter.
VerifierSettings.IgnoreMember(cacheOptions => cacheOptions.UserProvidedTtlOptions);
// Ignore the UserProvidedEnabledOptions. They aren't serialized to our config file, enforced by EntityCacheOptionsConverter.
diff --git a/src/Cli/Commands/AutoConfigOptions.cs b/src/Cli/Commands/AutoConfigOptions.cs
index 41227cd03a..41be943303 100644
--- a/src/Cli/Commands/AutoConfigOptions.cs
+++ b/src/Cli/Commands/AutoConfigOptions.cs
@@ -24,7 +24,7 @@ public AutoConfigOptions(
IEnumerable? patternsInclude = null,
IEnumerable? patternsExclude = null,
string? patternsName = null,
- string? templateMcpDmlTool = null,
+ string? templateMcpDmlTools = null,
bool? templateRestEnabled = null,
bool? templateGraphqlEnabled = null,
bool? templateCacheEnabled = null,
@@ -39,7 +39,7 @@ public AutoConfigOptions(
PatternsInclude = patternsInclude;
PatternsExclude = patternsExclude;
PatternsName = patternsName;
- TemplateMcpDmlTool = templateMcpDmlTool;
+ TemplateMcpDmlTools = templateMcpDmlTools;
TemplateRestEnabled = templateRestEnabled;
TemplateGraphqlEnabled = templateGraphqlEnabled;
TemplateCacheEnabled = templateCacheEnabled;
@@ -61,8 +61,8 @@ public AutoConfigOptions(
[Option("patterns.name", Required = false, HelpText = "Interpolation syntax for entity naming (must be unique for each generated entity). Default: '{object}'")]
public string? PatternsName { get; }
- [Option("template.mcp.dml-tool", Required = false, HelpText = "Enable/disable DML tools for generated entities. Allowed values: true, false. Default: true")]
- public string? TemplateMcpDmlTool { get; }
+ [Option("template.mcp.dml-tools", Required = false, HelpText = "Enable/disable DML tools for generated entities. Allowed values: true, false. Default: true")]
+ public string? TemplateMcpDmlTools { get; }
[Option("template.rest.enabled", Required = false, HelpText = "Enable/disable REST endpoint for generated entities. Allowed values: true, false. Default: true")]
public bool? TemplateRestEnabled { get; }
diff --git a/src/Cli/ConfigGenerator.cs b/src/Cli/ConfigGenerator.cs
index 8cec9dd239..af98be05ff 100644
--- a/src/Cli/ConfigGenerator.cs
+++ b/src/Cli/ConfigGenerator.cs
@@ -2596,18 +2596,10 @@ public static bool TryStartEngineWithOptions(StartOptions options, FileSystemRun
}
minimumLogLevel = (LogLevel)options.LogLevel;
+ args.Add("--LogLevel");
+ args.Add(minimumLogLevel.ToString());
_logger.LogInformation("Setting minimum LogLevel: {minimumLogLevel}.", minimumLogLevel);
}
- else
- {
- minimumLogLevel = deserializedRuntimeConfig.GetConfiguredLogLevel();
- HostMode hostModeType = deserializedRuntimeConfig.IsDevelopmentMode() ? HostMode.Development : HostMode.Production;
-
- _logger.LogInformation($"Setting default minimum LogLevel: {minimumLogLevel} for {hostModeType} mode.", minimumLogLevel, hostModeType);
- }
-
- args.Add("--LogLevel");
- args.Add(minimumLogLevel.ToString());
// This will add args to disable automatic redirects to https if specified by user
if (options.IsHttpsRedirectionDisabled)
@@ -3109,11 +3101,11 @@ private static AutoentityPatterns BuildAutoentityPatterns(AutoConfigOptions opti
bool userProvidedCache = existingAutoentity?.Template.UserProvidedCacheOptions ?? false;
// Update MCP options
- if (!string.IsNullOrWhiteSpace(options.TemplateMcpDmlTool))
+ if (!string.IsNullOrWhiteSpace(options.TemplateMcpDmlTools))
{
- if (!bool.TryParse(options.TemplateMcpDmlTool, out bool mcpDmlToolValue))
+ if (!bool.TryParse(options.TemplateMcpDmlTools, out bool mcpDmlToolValue))
{
- _logger.LogError("Invalid value for template.mcp.dml-tool: {value}. Valid values are: true, false", options.TemplateMcpDmlTool);
+ _logger.LogError("Invalid value for template.mcp.dml-tools: {value}. Valid values are: true, false", options.TemplateMcpDmlTools);
return null;
}
@@ -3122,7 +3114,7 @@ private static AutoentityPatterns BuildAutoentityPatterns(AutoConfigOptions opti
bool? dmlToolValue = mcpDmlToolValue;
mcp = new EntityMcpOptions(customToolEnabled: customToolEnabled, dmlToolsEnabled: dmlToolValue);
userProvidedMcp = true;
- _logger.LogInformation("Updated template.mcp.dml-tool for definition '{DefinitionName}'", options.DefinitionName);
+ _logger.LogInformation("Updated template.mcp.dml-tools for definition '{DefinitionName}'", options.DefinitionName);
}
// Update REST options
@@ -3268,7 +3260,7 @@ public static bool TrySimulateAutoentities(AutoConfigSimulateOptions options, Fi
if (runtimeConfig.DataSource.DatabaseType != DatabaseType.MSSQL)
{
- _logger.LogError("Autoentities simulation is only supported for MSSQL databases. Current database type: {DatabaseType}.", runtimeConfig.DataSource.DatabaseType);
+ _logger.LogError("The autoentities simulation is only supported for MSSQL databases. Current database type: {DatabaseType}.", runtimeConfig.DataSource.DatabaseType);
return false;
}
@@ -3360,7 +3352,7 @@ public static bool TrySimulateAutoentities(AutoConfigSimulateOptions options, Fi
/// The simulation results keyed by filter (definition) name.
private static void WriteSimulationResultsToConsole(Dictionary> results)
{
- Console.WriteLine("AutoEntities Simulation Results");
+ Console.WriteLine("Autoentities Simulation Results");
Console.WriteLine();
foreach ((string filterName, List<(string EntityName, string SchemaName, string ObjectName)> matches) in results)
diff --git a/src/Config/ObjectModel/Entity.cs b/src/Config/ObjectModel/Entity.cs
index d67a1f9f28..9c9ba17f2c 100644
--- a/src/Config/ObjectModel/Entity.cs
+++ b/src/Config/ObjectModel/Entity.cs
@@ -45,6 +45,9 @@ public record Entity
[JsonIgnore]
public bool IsLinkingEntity { get; init; }
+ [JsonIgnore]
+ public bool IsAutoentity { get; init; }
+
[JsonConstructor]
public Entity(
EntitySource Source,
@@ -58,7 +61,8 @@ public Entity(
bool IsLinkingEntity = false,
EntityHealthCheckConfig? Health = null,
string? Description = null,
- EntityMcpOptions? Mcp = null)
+ EntityMcpOptions? Mcp = null,
+ bool IsAutoentity = false)
{
this.Health = Health;
this.Source = Source;
@@ -72,6 +76,7 @@ public Entity(
this.IsLinkingEntity = IsLinkingEntity;
this.Description = Description;
this.Mcp = Mcp;
+ this.IsAutoentity = IsAutoentity;
}
///
diff --git a/src/Config/ObjectModel/RuntimeConfig.cs b/src/Config/ObjectModel/RuntimeConfig.cs
index 89cc7413d1..54c3e77556 100644
--- a/src/Config/ObjectModel/RuntimeConfig.cs
+++ b/src/Config/ObjectModel/RuntimeConfig.cs
@@ -269,6 +269,11 @@ public bool TryAddGeneratedAutoentityNameToDataSourceName(string entityName, str
return false;
}
+ public bool RemoveGeneratedAutoentityNameFromDataSourceName(string entityName)
+ {
+ return _entityNameToDataSourceName.Remove(entityName);
+ }
+
///
/// Constructor for runtimeConfig.
/// To be used when setting up from cli json scenario.
@@ -502,7 +507,7 @@ public string GetDataSourceNameFromAutoentityName(string autoentityName)
if (!_autoentityNameToDataSourceName.TryGetValue(autoentityName, out string? autoentityDataSource))
{
throw new DataApiBuilderException(
- message: $"{autoentityName} is not a valid autoentity.",
+ message: $"'{autoentityName}' is not a valid autoentities definition.",
statusCode: HttpStatusCode.NotFound,
subStatusCode: DataApiBuilderException.SubStatusCodes.EntityNotFound);
}
diff --git a/src/Core/Configurations/RuntimeConfigProvider.cs b/src/Core/Configurations/RuntimeConfigProvider.cs
index 644782e2cc..70327326ba 100644
--- a/src/Core/Configurations/RuntimeConfigProvider.cs
+++ b/src/Core/Configurations/RuntimeConfigProvider.cs
@@ -426,4 +426,32 @@ public void AddMergedEntitiesToConfig(Dictionary newEntities)
};
_configLoader.EditRuntimeConfig(newRuntimeConfig);
}
+
+ public void RemoveGeneratedAutoentitiesFromConfig()
+ {
+ Dictionary entities = new(_configLoader.RuntimeConfig!.Entities);
+ List removingEntities = new();
+
+ // Add entities that will be removed to a list first to avoid modifying the collection while iterating over it.
+ foreach ((string name, Entity entity) in entities)
+ {
+ if (entity.IsAutoentity)
+ {
+ removingEntities.Add(name);
+ }
+ }
+
+ // Remove all autoentities from the config.
+ foreach (string name in removingEntities)
+ {
+ entities.Remove(name);
+ _configLoader.RuntimeConfig!.RemoveGeneratedAutoentityNameFromDataSourceName(name);
+ }
+
+ RuntimeConfig newRuntimeConfig = _configLoader.RuntimeConfig! with
+ {
+ Entities = new(entities)
+ };
+ _configLoader.EditRuntimeConfig(newRuntimeConfig);
+ }
}
diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs
index 96fa47dcfd..23d12ec31f 100644
--- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs
+++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs
@@ -305,7 +305,7 @@ protected override async Task GenerateAutoentitiesIntoEntities(IReadOnlyDictiona
foreach ((string autoentityName, Autoentity autoentity) in autoentities)
{
int addedEntities = 0;
- JsonArray? resultArray = await QueryAutoentitiesAsync(autoentity);
+ JsonArray? resultArray = await QueryAutoentitiesAsync(autoentityName, autoentity);
if (resultArray is null)
{
continue;
@@ -316,7 +316,7 @@ protected override async Task GenerateAutoentitiesIntoEntities(IReadOnlyDictiona
if (resultObject is null)
{
throw new DataApiBuilderException(
- message: $"Cannot create new entity from autoentity pattern due to an internal error.",
+ message: $"Cannot create new entity from autoentities definition '{autoentityName}' due to an internal error.",
statusCode: HttpStatusCode.InternalServerError,
subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization);
}
@@ -329,7 +329,7 @@ protected override async Task GenerateAutoentitiesIntoEntities(IReadOnlyDictiona
if (string.IsNullOrWhiteSpace(entityName) || string.IsNullOrWhiteSpace(objectName) || string.IsNullOrWhiteSpace(schemaName))
{
- _logger.LogError("Skipping autoentity generation: entity_name or object is null or empty for autoentity pattern '{AutoentityName}'.", autoentityName);
+ _logger.LogError("Skipping autoentity generation: 'entity_name', 'object', or 'schema' is null or empty for autoentities definition '{autoentityName}'.", autoentityName);
continue;
}
@@ -349,14 +349,15 @@ protected override async Task GenerateAutoentitiesIntoEntities(IReadOnlyDictiona
Health: autoentity.Template.Health,
Fields: null,
Relationships: null,
- Mappings: new());
+ Mappings: new(),
+ IsAutoentity: true);
// Add the generated entity to the linking entities dictionary.
// This allows the entity to be processed later during metadata population.
if (!entities.TryAdd(entityName, generatedEntity) || !runtimeConfig.TryAddGeneratedAutoentityNameToDataSourceName(entityName, autoentityName))
{
throw new DataApiBuilderException(
- message: $"Entity with name '{entityName}' already exists. Cannot create new entity from autoentity pattern with definition-name '{autoentityName}'.",
+ message: $"Entity '{entityName}' conflicts with autoentity pattern '{autoentityName}'. Use --patterns.exclude to skip it.",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization);
}
@@ -375,14 +376,14 @@ protected override async Task GenerateAutoentitiesIntoEntities(IReadOnlyDictiona
if (addedEntities == 0)
{
- _logger.LogWarning("No new entities were generated from the autoentity {autoentityName} defined in the configuration.", autoentityName);
+ _logger.LogWarning("No new entities were generated from the autoentities definition '{autoentityName}'.", autoentityName);
}
}
_runtimeConfigProvider.AddMergedEntitiesToConfig(entities);
}
- public async Task QueryAutoentitiesAsync(Autoentity autoentity)
+ public async Task QueryAutoentitiesAsync(string autoentityName, Autoentity autoentity)
{
string include = string.Join(",", autoentity.Patterns.Include);
string exclude = string.Join(",", autoentity.Patterns.Exclude);
@@ -395,10 +396,10 @@ protected override async Task GenerateAutoentitiesIntoEntities(IReadOnlyDictiona
{ $"{BaseQueryStructure.PARAM_NAME_PREFIX}name_pattern", new(namePattern, null, SqlDbType.NVarChar) }
};
- _logger.LogInformation("Query for Autoentities is being executed with the following parameters.");
- _logger.LogInformation($"Autoentities include pattern: {include}");
- _logger.LogInformation($"Autoentities exclude pattern: {exclude}");
- _logger.LogInformation($"Autoentities name pattern: {namePattern}");
+ _logger.LogDebug("Query for autoentities is being executed with the following parameters.");
+ _logger.LogDebug("The autoentities definition '{autoentityName}' include pattern: {include}", autoentityName, include);
+ _logger.LogDebug("The autoentities definition '{autoentityName}' exclude pattern: {exclude}", autoentityName, exclude);
+ _logger.LogDebug("The autoentities definition '{autoentityName}' name pattern: {namePattern}", autoentityName, namePattern);
JsonArray? resultArray = await QueryExecutor.ExecuteQueryAsync(
sqltext: getAutoentitiesQuery,
diff --git a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs
index 6aa2712468..951b5984e4 100644
--- a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs
+++ b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs
@@ -353,6 +353,12 @@ public async Task InitializeAsync()
GenerateRestPathToEntityMap();
InitODataParser();
+
+ if (_isValidateOnly)
+ {
+ RemoveGeneratedAutoentities();
+ }
+
timer.Stop();
_logger.LogTrace($"Done inferring Sql database schema in {timer.ElapsedMilliseconds}ms.");
}
@@ -711,7 +717,16 @@ private void GenerateDatabaseObjectForEntities()
///
protected virtual Task GenerateAutoentitiesIntoEntities(IReadOnlyDictionary? autoentities)
{
- throw new NotSupportedException($"{GetType().Name} does not support Autoentities yet.");
+ throw new NotSupportedException($"{GetType().Name} does not support autoentities yet.");
+ }
+
+ ///
+ /// Removes the entities that were generated from the autoentities property.
+ /// This should only be done when we only want to validate the entities.
+ ///
+ private void RemoveGeneratedAutoentities()
+ {
+ _runtimeConfigProvider.RemoveGeneratedAutoentitiesFromConfig();
}
protected void PopulateDatabaseObjectForEntity(
diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs
index 53a97ae722..e37b0920e2 100644
--- a/src/Service.Tests/Configuration/ConfigurationTests.cs
+++ b/src/Service.Tests/Configuration/ConfigurationTests.cs
@@ -5590,7 +5590,7 @@ public async Task TestAutoentitiesAreGeneratedIntoEntities(bool useEntities, int
///
[TestCategory(TestCategory.MSSQL)]
[DataTestMethod]
- [DataRow("publishers", "uniqueSingularPublisher", "uniquePluralPublishers", "/unique/publisher", "Entity with name 'publishers' already exists. Cannot create new entity from autoentity pattern with definition-name 'PublisherAutoEntity'.", DisplayName = "Autoentities fail due to entity name")]
+ [DataRow("publishers", "uniqueSingularPublisher", "uniquePluralPublishers", "/unique/publisher", "Entity 'publishers' conflicts with autoentity pattern 'PublisherAutoEntity'. Use --patterns.exclude to skip it.", DisplayName = "Autoentities fail due to entity name")]
[DataRow("UniquePublisher", "publishers", "uniquePluralPublishers", "/unique/publisher", "Entity publishers generates queries/mutation that already exist", DisplayName = "Autoentities fail due to graphql singular type")]
[DataRow("UniquePublisher", "uniqueSingularPublisher", "publishers", "/unique/publisher", "Entity publishers generates queries/mutation that already exist", DisplayName = "Autoentities fail due to graphql plural type")]
[DataRow("UniquePublisher", "uniqueSingularPublisher", "uniquePluralPublishers", "/publishers", "The rest path: publishers specified for entity: publishers is already used by another entity.", DisplayName = "Autoentities fail due to rest path")]
diff --git a/src/Service.Tests/Configuration/HotReload/ConfigurationHotReloadTests.cs b/src/Service.Tests/Configuration/HotReload/ConfigurationHotReloadTests.cs
index 0f9ff6c1b8..b1770f504d 100644
--- a/src/Service.Tests/Configuration/HotReload/ConfigurationHotReloadTests.cs
+++ b/src/Service.Tests/Configuration/HotReload/ConfigurationHotReloadTests.cs
@@ -64,6 +64,7 @@ private static void GenerateConfigFile(
string entityBackingColumn = "title",
string entityExposedName = "title",
string mcpEnabled = "true",
+ string autoentityName = "autoentity_{object}",
string configFileName = CONFIG_FILE_NAME)
{
File.WriteAllText(configFileName, @"
@@ -180,6 +181,29 @@ private static void GenerateConfigFile(
}
]
}
+ },
+ ""autoentities"": {
+ ""BooksAutoentities"": {
+ ""patterns"": {
+ ""include"": [ ""%book%"" ],
+ ""name"": """ + autoentityName + @"""
+ },
+ ""template"": {
+ ""rest"": {
+ ""enabled"": true
+ }
+ },
+ ""permissions"": [
+ {
+ ""role"": ""anonymous"",
+ ""actions"": [
+ {
+ ""action"": ""*""
+ }
+ ]
+ }
+ ]
+ }
}
}");
}
@@ -768,6 +792,41 @@ await WaitForConditionAsync(
Assert.AreEqual(HttpStatusCode.OK, restResult.StatusCode);
}
+ ///
+ /// Hot reload the configuration file so that it changes the name of the autoentity properties.
+ /// Then we assert that the hot reload is successful by sending a request to the newly created autoentity.
+ ///
+ [TestCategory(MSSQL_ENVIRONMENT)]
+ [TestMethod]
+ public async Task HotReloadAutoentities()
+ {
+ // Arrange
+ _writer = new StringWriter();
+ Console.SetOut(_writer);
+
+ // Act
+ HttpResponseMessage restResult = await _testClient.GetAsync($"rest/autoentity_books");
+
+ GenerateConfigFile(
+ connectionString: $"{ConfigurationTests.GetConnectionStringFromEnvironmentConfig(TestCategory.MSSQL).Replace("\\", "\\\\")}",
+ autoentityName: "HotReload_{object}");
+ await WaitForConditionAsync(
+ () => WriterContains(HOT_RELOAD_SUCCESS_MESSAGE),
+ TimeSpan.FromSeconds(HOT_RELOAD_TIMEOUT_SECONDS),
+ TimeSpan.FromMilliseconds(500));
+
+ HttpResponseMessage failRestResult = await _testClient.GetAsync($"rest/autoentity_books");
+ HttpResponseMessage hotReloadRestResult = await _testClient.GetAsync($"rest/HotReload_books");
+
+ // Assert
+ Assert.AreEqual(HttpStatusCode.OK, restResult.StatusCode,
+ $"REST request before hot-reload failed when it was expected to succeed. Response: {await restResult.Content.ReadAsStringAsync()}");
+ Assert.AreEqual(HttpStatusCode.NotFound, failRestResult.StatusCode,
+ $"REST request after hot-reload succeeded when it was expected to fail. Response: {await failRestResult.Content.ReadAsStringAsync()}");
+ Assert.AreEqual(HttpStatusCode.OK, hotReloadRestResult.StatusCode,
+ $"REST request after hot-reload failed when it was expected to succeed. Response: {await hotReloadRestResult.Content.ReadAsStringAsync()}");
+ }
+
///
/// /// (Warning: This test only currently works in the pipeline due to constrains of not
/// being able to change from one database type to another, under normal circumstances
diff --git a/src/Service.Tests/ModuleInitializer.cs b/src/Service.Tests/ModuleInitializer.cs
index 637280b45d..9d8c213b7c 100644
--- a/src/Service.Tests/ModuleInitializer.cs
+++ b/src/Service.Tests/ModuleInitializer.cs
@@ -69,6 +69,8 @@ public static void Init()
VerifierSettings.IgnoreMember(entity => entity.EntityFirst);
// Ignore the entity IsLinkingEntity as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(entity => entity.IsLinkingEntity);
+ // Ignore the entity IsAutoentity as that's unimportant from a test standpoint.
+ VerifierSettings.IgnoreMember(entity => entity.IsAutoentity);
// Ignore the UserProvidedTtlOptions. They aren't serialized to our config file, enforced by EntityCacheOptionsConverter.
VerifierSettings.IgnoreMember(cacheOptions => cacheOptions.UserProvidedTtlOptions);
// Ignore the UserProvidedEnabledOptions. They aren't serialized to our config file, enforced by EntityCacheOptionsConverter.
diff --git a/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs b/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs
index 4c5782b4ca..7242716847 100644
--- a/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs
+++ b/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs
@@ -634,7 +634,7 @@ public async Task CheckAutoentitiesQuery(string[] include, string[] exclude, str
// Act
MsSqlMetadataProvider metadataProvider = (MsSqlMetadataProvider)_sqlMetadataProvider;
- JsonArray resultArray = await metadataProvider.QueryAutoentitiesAsync(autoentity);
+ JsonArray resultArray = await metadataProvider.QueryAutoentitiesAsync("autoentity", autoentity);
// Assert
Assert.IsNotNull(resultArray);