Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3d2b37d
Initial plan
Copilot Jan 30, 2026
c9d7425
Add autoentities-configure CLI command with all required options
Copilot Jan 30, 2026
3ec334a
Fix MCP options serialization in autoentities template converter
Copilot Jan 30, 2026
a3f5ab0
Add comprehensive tests for autoentities-configure command
Copilot Jan 30, 2026
f2cdf40
Fix GraphQL type serialization and improve error messages
Copilot Feb 5, 2026
504641d
Add permissions validation for new autoentity definitions
Copilot Feb 6, 2026
7d6684c
Fix permission issues
RubenCerna2079 Feb 13, 2026
a047f02
Fix cli tests
RubenCerna2079 Feb 13, 2026
015db1d
Merge branch 'main' into copilot/add-autoentities-configure-command
RubenCerna2079 Feb 13, 2026
27657f2
Fix unit tests
RubenCerna2079 Feb 13, 2026
06ff923
Fix unit tests
RubenCerna2079 Feb 13, 2026
4248231
Fix formatting errors
RubenCerna2079 Feb 13, 2026
1d1a7cb
Merge branch 'main' into copilot/add-autoentities-configure-command
RubenCerna2079 Feb 17, 2026
217909b
Change name to auto-config
RubenCerna2079 Feb 17, 2026
63a1bae
Merge branch 'main' into copilot/add-autoentities-configure-command
JerryNixon Feb 23, 2026
60c305f
Merge branch 'main' into copilot/add-autoentities-configure-command
Aniruddh25 Feb 25, 2026
0993304
Changes based on comments
RubenCerna2079 Feb 27, 2026
c205992
Merge branch 'main' into copilot/add-autoentities-configure-command
RubenCerna2079 Feb 27, 2026
3fb29d7
Fix variable
RubenCerna2079 Feb 27, 2026
92ef1fe
Fix variable syntax
RubenCerna2079 Feb 27, 2026
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
291 changes: 291 additions & 0 deletions src/Cli.Tests/AutoConfigTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Cli.Tests;

/// <summary>
/// Tests for the auto-config CLI command.
/// </summary>
[TestClass]
public class AutoConfigTests
{
private IFileSystem? _fileSystem;
private FileSystemRuntimeConfigLoader? _runtimeConfigLoader;

[TestInitialize]
public void TestInitialize()
{
_fileSystem = FileSystemUtils.ProvisionMockFileSystem();
_runtimeConfigLoader = new FileSystemRuntimeConfigLoader(_fileSystem);

ILoggerFactory loggerFactory = TestLoggerSupport.ProvisionLoggerFactory();
ConfigGenerator.SetLoggerForCliConfigGenerator(loggerFactory.CreateLogger<ConfigGenerator>());
SetCliUtilsLogger(loggerFactory.CreateLogger<Utils>());
}

[TestCleanup]
public void TestCleanup()
{
_fileSystem = null;
_runtimeConfigLoader = null;
}

/// <summary>
/// Tests that a new autoentities definition is successfully created with patterns.
/// </summary>
[TestMethod]
public void TestCreateAutoentitiesDefinition_WithPatterns()
{
// Arrange
InitOptions initOptions = CreateBasicInitOptionsForMsSqlWithConfig(config: TEST_RUNTIME_CONFIG_FILE);
Assert.IsTrue(ConfigGenerator.TryGenerateConfig(initOptions, _runtimeConfigLoader!, _fileSystem!));

AutoConfigOptions options = new(
definitionName: "test-def",
patternsInclude: new[] { "dbo.%", "sys.%" },
patternsExclude: new[] { "dbo.internal%" },
patternsName: "{schema}_{table}",
config: TEST_RUNTIME_CONFIG_FILE
);

// Act
bool success = ConfigGenerator.TryConfigureAutoentities(options, _runtimeConfigLoader!, _fileSystem!);

// Assert
Assert.IsTrue(success);
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? config));
Assert.IsNotNull(config.Autoentities);
Assert.IsTrue(config.Autoentities.Autoentities.ContainsKey("test-def"));

Autoentity autoentity = config.Autoentities.Autoentities["test-def"];
Assert.AreEqual(2, autoentity.Patterns.Include.Length);
Assert.AreEqual("dbo.%", autoentity.Patterns.Include[0]);
Assert.AreEqual("sys.%", autoentity.Patterns.Include[1]);
Assert.AreEqual(1, autoentity.Patterns.Exclude.Length);
Assert.AreEqual("dbo.internal%", autoentity.Patterns.Exclude[0]);
Assert.AreEqual("{schema}_{table}", autoentity.Patterns.Name);
}

/// <summary>
/// Tests that template options are correctly configured for an autoentities definition.
/// </summary>
[TestMethod]
public void TestConfigureAutoentitiesDefinition_WithTemplateOptions()
{
// Arrange
InitOptions initOptions = CreateBasicInitOptionsForMsSqlWithConfig(config: TEST_RUNTIME_CONFIG_FILE);
Assert.IsTrue(ConfigGenerator.TryGenerateConfig(initOptions, _runtimeConfigLoader!, _fileSystem!));

AutoConfigOptions options = new(
definitionName: "test-def",
templateRestEnabled: true,
templateGraphqlEnabled: false,
templateMcpDmlTool: "true",
templateCacheEnabled: true,
templateCacheTtlSeconds: 30,
templateCacheLevel: "L1",
templateHealthEnabled: true,
config: TEST_RUNTIME_CONFIG_FILE
);

// Act
bool success = ConfigGenerator.TryConfigureAutoentities(options, _runtimeConfigLoader!, _fileSystem!);

// Assert
Assert.IsTrue(success);
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? config));

Autoentity autoentity = config.Autoentities!.Autoentities["test-def"];
Assert.IsTrue(autoentity.Template.Rest.Enabled);
Assert.IsFalse(autoentity.Template.GraphQL.Enabled);
Assert.IsTrue(autoentity.Template.Mcp!.DmlToolEnabled);
Assert.AreEqual(true, autoentity.Template.Cache.Enabled);
Assert.AreEqual(30, autoentity.Template.Cache.TtlSeconds);
Assert.AreEqual(EntityCacheLevel.L1, autoentity.Template.Cache.Level);
Assert.IsTrue(autoentity.Template.Health.Enabled);
}

/// <summary>
/// Tests that an existing autoentities definition is successfully updated.
/// </summary>
[TestMethod]
public void TestUpdateExistingAutoentitiesDefinition()
{
// Arrange
InitOptions initOptions = CreateBasicInitOptionsForMsSqlWithConfig(config: TEST_RUNTIME_CONFIG_FILE);
Assert.IsTrue(ConfigGenerator.TryGenerateConfig(initOptions, _runtimeConfigLoader!, _fileSystem!));

// Create initial definition
AutoConfigOptions initialOptions = new(
definitionName: "test-def",
patternsInclude: new[] { "dbo.%" },
templateCacheTtlSeconds: 10,
permissions: new[] { "anonymous", "read" },
config: TEST_RUNTIME_CONFIG_FILE
);
Assert.IsTrue(ConfigGenerator.TryConfigureAutoentities(initialOptions, _runtimeConfigLoader!, _fileSystem!));

// Update definition
AutoConfigOptions updateOptions = new(
definitionName: "test-def",
patternsExclude: new[] { "dbo.internal%" },
templateCacheTtlSeconds: 60,
permissions: new[] { "authenticated", "create,read,update,delete" },
config: TEST_RUNTIME_CONFIG_FILE
);

// Act
bool success = ConfigGenerator.TryConfigureAutoentities(updateOptions, _runtimeConfigLoader!, _fileSystem!);

// Assert
Assert.IsTrue(success);
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? config));

Autoentity autoentity = config.Autoentities!.Autoentities["test-def"];
// Include should remain from initial setup
Assert.AreEqual(1, autoentity.Patterns.Include.Length);
Assert.AreEqual("dbo.%", autoentity.Patterns.Include[0]);
// Exclude should be added
Assert.AreEqual(1, autoentity.Patterns.Exclude.Length);
Assert.AreEqual("dbo.internal%", autoentity.Patterns.Exclude[0]);
// Cache TTL should be updated
Assert.AreEqual(60, autoentity.Template.Cache.TtlSeconds);
// Permissions should be replaced
Assert.AreEqual(1, autoentity.Permissions.Length);
Assert.AreEqual("authenticated", autoentity.Permissions[0].Role);
}

/// <summary>
/// Tests that permissions are correctly parsed and applied.
/// </summary>
[TestMethod]
public void TestConfigureAutoentitiesDefinition_WithMultipleActions()
{
// Arrange
InitOptions initOptions = CreateBasicInitOptionsForMsSqlWithConfig(config: TEST_RUNTIME_CONFIG_FILE);
Assert.IsTrue(ConfigGenerator.TryGenerateConfig(initOptions, _runtimeConfigLoader!, _fileSystem!));

AutoConfigOptions options = new(
definitionName: "test-def",
permissions: new[] { "authenticated", "create,read,update,delete" },
config: TEST_RUNTIME_CONFIG_FILE
);

// Act
bool success = ConfigGenerator.TryConfigureAutoentities(options, _runtimeConfigLoader!, _fileSystem!);

// Assert
Assert.IsTrue(success);
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? config));

Autoentity autoentity = config.Autoentities!.Autoentities["test-def"];
Assert.AreEqual(1, autoentity.Permissions.Length);
Assert.AreEqual("authenticated", autoentity.Permissions[0].Role);
Assert.AreEqual(4, autoentity.Permissions[0].Actions.Length);
}

/// <summary>
/// Tests that invalid MCP dml-tool value is handled correctly.
/// </summary>
[TestMethod]
public void TestConfigureAutoentitiesDefinition_InvalidMcpDmlTool()
{
// Arrange
InitOptions initOptions = CreateBasicInitOptionsForMsSqlWithConfig(config: TEST_RUNTIME_CONFIG_FILE);
Assert.IsTrue(ConfigGenerator.TryGenerateConfig(initOptions, _runtimeConfigLoader!, _fileSystem!));

AutoConfigOptions options = new(
definitionName: "test-def",
templateMcpDmlTool: "invalid-value",
permissions: new[] { "anonymous", "read" },
config: TEST_RUNTIME_CONFIG_FILE
);

// Act
bool success = ConfigGenerator.TryConfigureAutoentities(options, _runtimeConfigLoader!, _fileSystem!);

// Assert - Should fail due to invalid MCP value
Assert.IsFalse(success);
}

/// <summary>
/// Tests that invalid cache level value is handled correctly.
/// </summary>
[TestMethod]
public void TestConfigureAutoentitiesDefinition_InvalidCacheLevel()
{
// Arrange
InitOptions initOptions = CreateBasicInitOptionsForMsSqlWithConfig(config: TEST_RUNTIME_CONFIG_FILE);
Assert.IsTrue(ConfigGenerator.TryGenerateConfig(initOptions, _runtimeConfigLoader!, _fileSystem!));

AutoConfigOptions options = new(
definitionName: "test-def",
templateCacheLevel: "InvalidLevel",
permissions: new[] { "anonymous", "read" },
config: TEST_RUNTIME_CONFIG_FILE
);

// Act
bool success = ConfigGenerator.TryConfigureAutoentities(options, _runtimeConfigLoader!, _fileSystem!);

// Assert - Should fail due to invalid cache level
Assert.IsFalse(success);
}

/// <summary>
/// Tests that multiple autoentities definitions can coexist.
/// </summary>
[TestMethod]
public void TestMultipleAutoentitiesDefinitions()
{
// Arrange
InitOptions initOptions = CreateBasicInitOptionsForMsSqlWithConfig(config: TEST_RUNTIME_CONFIG_FILE);
Assert.IsTrue(ConfigGenerator.TryGenerateConfig(initOptions, _runtimeConfigLoader!, _fileSystem!));

// Create first definition
AutoConfigOptions options1 = new(
definitionName: "def-1",
patternsInclude: new[] { "dbo.%" },
permissions: new[] { "anonymous", "read" },
config: TEST_RUNTIME_CONFIG_FILE
);
Assert.IsTrue(ConfigGenerator.TryConfigureAutoentities(options1, _runtimeConfigLoader!, _fileSystem!));

// Create second definition
AutoConfigOptions options2 = new(
definitionName: "def-2",
patternsInclude: new[] { "sys.%" },
permissions: new[] { "authenticated", "*" },
config: TEST_RUNTIME_CONFIG_FILE
);

// Act
bool success = ConfigGenerator.TryConfigureAutoentities(options2, _runtimeConfigLoader!, _fileSystem!);

// Assert
Assert.IsTrue(success);
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? config));
Assert.AreEqual(2, config.Autoentities!.Autoentities.Count);
Assert.IsTrue(config.Autoentities.Autoentities.ContainsKey("def-1"));
Assert.IsTrue(config.Autoentities.Autoentities.ContainsKey("def-2"));
}

/// <summary>
/// Tests that attempting to configure autoentities without a config file fails.
/// </summary>
[TestMethod]
public void TestConfigureAutoentitiesDefinition_NoConfigFile()
{
// Arrange
AutoConfigOptions options = new(
definitionName: "test-def",
permissions: new[] { "anonymous", "read" }
);

// Act
bool success = ConfigGenerator.TryConfigureAutoentities(options, _runtimeConfigLoader!, _fileSystem!);

// Assert
Assert.IsFalse(success);
}
}
104 changes: 104 additions & 0 deletions src/Cli/Commands/AutoConfigOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.IO.Abstractions;
using Azure.DataApiBuilder.Config;
using Azure.DataApiBuilder.Product;
using Cli.Constants;
using CommandLine;
using Microsoft.Extensions.Logging;
using static Cli.Utils;
using ILogger = Microsoft.Extensions.Logging.ILogger;

namespace Cli.Commands
{
/// <summary>
/// AutoConfigOptions command options
/// This command will be used to configure autoentities definitions in the config file.
/// </summary>
[Verb("auto-config", isDefault: false, HelpText = "Configure autoentities definitions", Hidden = false)]
public class AutoConfigOptions : Options
{
public AutoConfigOptions(
string definitionName,
IEnumerable<string>? patternsInclude = null,
IEnumerable<string>? patternsExclude = null,
string? patternsName = null,
string? templateMcpDmlTool = null,
bool? templateRestEnabled = null,
bool? templateGraphqlEnabled = null,
bool? templateCacheEnabled = null,
int? templateCacheTtlSeconds = null,
string? templateCacheLevel = null,
bool? templateHealthEnabled = null,
IEnumerable<string>? permissions = null,
string? config = null)
: base(config)
{
DefinitionName = definitionName;
PatternsInclude = patternsInclude;
PatternsExclude = patternsExclude;
PatternsName = patternsName;
TemplateMcpDmlTool = templateMcpDmlTool;
TemplateRestEnabled = templateRestEnabled;
TemplateGraphqlEnabled = templateGraphqlEnabled;
TemplateCacheEnabled = templateCacheEnabled;
TemplateCacheTtlSeconds = templateCacheTtlSeconds;
TemplateCacheLevel = templateCacheLevel;
TemplateHealthEnabled = templateHealthEnabled;
Permissions = permissions;
}

[Value(0, Required = true, HelpText = "Name of the autoentities definition to configure.")]
public string DefinitionName { get; }

[Option("patterns.include", Required = false, HelpText = "T-SQL LIKE pattern(s) to include database objects. Space-separated array of patterns. Default: '%.%'.")]
public IEnumerable<string>? PatternsInclude { get; }

[Option("patterns.exclude", Required = false, HelpText = "T-SQL LIKE pattern(s) to exclude database objects. Space-separated array of patterns. Default: null")]
public IEnumerable<string>? PatternsExclude { get; }

[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.rest.enabled", Required = false, HelpText = "Enable/disable REST endpoint for generated entities. Allowed values: true, false. Default: true")]
public bool? TemplateRestEnabled { get; }

[Option("template.graphql.enabled", Required = false, HelpText = "Enable/disable GraphQL endpoint for generated entities. Allowed values: true, false. Default: true")]
public bool? TemplateGraphqlEnabled { get; }

[Option("template.cache.enabled", Required = false, HelpText = "Enable/disable cache for generated entities. Allowed values: true, false. Default: false")]
public bool? TemplateCacheEnabled { get; }

[Option("template.cache.ttl-seconds", Required = false, HelpText = "Cache time-to-live in seconds for generated entities. Default: null")]
public int? TemplateCacheTtlSeconds { get; }

[Option("template.cache.level", Required = false, HelpText = "Cache level for generated entities. Allowed values: L1, L1L2. Default: L1L2")]
public string? TemplateCacheLevel { get; }

[Option("template.health.enabled", Required = false, HelpText = "Enable/disable health check for generated entities. Allowed values: true, false. Default: true")]
public bool? TemplateHealthEnabled { get; }

[Option("permissions", Required = false, Separator = ':', HelpText = "Permissions for generated entities in the format role:actions (e.g., anonymous:read). Default: null")]
public IEnumerable<string>? Permissions { get; }

public int Handler(ILogger logger, FileSystemRuntimeConfigLoader loader, IFileSystem fileSystem)
{
logger.LogInformation("{productName} {version}", PRODUCT_NAME, ProductInfo.GetProductVersion());
bool isSuccess = ConfigGenerator.TryConfigureAutoentities(this, loader, fileSystem);
if (isSuccess)
{
logger.LogInformation("Successfully configured autoentities definition: {DefinitionName}.", DefinitionName);
return CliReturnCode.SUCCESS;
}
else
{
logger.LogError("Failed to configure autoentities definition: {DefinitionName}.", DefinitionName);
return CliReturnCode.GENERAL_ERROR;
}
}
}
}
Loading