Skip to content

Commit 0bc68fd

Browse files
CopilotRubenCerna2079Aniruddh25Copilot
authored
Add dab auto-config-simulate CLI command (#3189)
## Why make this change? Developers using autoentities needed a way to preview which database objects would be matched by their filter definitions before committing changes to the config. ## What is this change? New `auto-config-simulate` verb that connects to the MSSQL database, executes the autoentities pattern-match query for each filter definition, and outputs results. **New files:** - `src/Cli/Commands/AutoConfigSimulateOptions.cs` — defines the `auto-config-simulate` verb with optional `--output`/`-o` flag - `src/Cli.Tests/AutoConfigSimulateTests.cs` — unit and integration tests for the simulate command **`ConfigGenerator.cs` additions:** - `TrySimulateAutoentities` — loads config (with env-var resolution), validates MSSQL + autoentities present, opens a `SqlConnection`, and runs `MsSqlQueryBuilder.BuildGetAutoentitiesQuery()` for each defined filter using explicit `SqlParameter` instances with `SqlDbType.NVarChar` - `WriteSimulationResultsToConsole` — prints grouped output with match counts and aligned `entity → schema.object` lines - `WriteSimulationResultsToCsvFile` — writes RFC 4180-compliant CSV (values are properly quoted/escaped) - `QuoteCsvValue` — helper that wraps and escapes commas, quotes, and newlines in CSV fields **`Program.cs`:** registers `AutoConfigSimulateOptions` in the parser. ## How was this tested? - [x] Integration Tests — `AutoConfigSimulateTests.cs` covers: filter matching a known table with console output, filter matching a known table with CSV file output, and filter matching no tables (all marked `[TestCategory("MsSql")]`, skip gracefully when `MSSQL_SA_PASSWORD` is not set). - [x] Unit Tests — `AutoConfigSimulateTests.cs` covers: no autoentities defined. ## Sample Request(s) **Console output (default):** ```sh dab auto-config-simulate ``` ``` AutoEntities Simulation Results Filter: ProductTables Matches: 3 Products -> dbo.Products Inventory -> dbo.Inventory Pricing -> dbo.Pricing Filter: AnotherFilter Matches: 0 (no matches) ``` **CSV file output:** ```sh dab auto-config-simulate --output results.csv ``` ```csv filter_name,entity_name,database_object ProductTables,Products,dbo.Products ProductTables,Inventory,dbo.Inventory ProductTables,Pricing,dbo.Pricing ``` <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey). --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: RubenCerna2079 <[email protected]> Co-authored-by: Ruben Cerna <[email protected]> Co-authored-by: Aniruddh Munde <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Aniruddh25 <[email protected]>
1 parent bf18263 commit 0bc68fd

4 files changed

Lines changed: 498 additions & 1 deletion

File tree

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace Cli.Tests;
5+
6+
/// <summary>
7+
/// Tests for the auto-config-simulate CLI command.
8+
/// </summary>
9+
[TestClass]
10+
public class AutoConfigSimulateTests
11+
{
12+
/// <summary>
13+
/// MSSQL test category constant, matching the value used by Service.Tests to filter integration tests.
14+
/// Run with: dotnet test --filter "TestCategory=MsSql"
15+
/// </summary>
16+
private const string MSSQL_CATEGORY = "MsSql";
17+
18+
/// <summary>
19+
/// Connection string template for integration tests.
20+
/// The @env('MSSQL_SA_PASSWORD') reference is resolved at config load time when
21+
/// TrySimulateAutoentities calls TryLoadConfig with doReplaceEnvVar: true.
22+
/// </summary>
23+
private const string MSSQL_CONNECTION_STRING_TEMPLATE =
24+
"Server=tcp:127.0.0.1,1433;Persist Security Info=False;User ID=sa;" +
25+
"Password=@env('MSSQL_SA_PASSWORD');MultipleActiveResultSets=False;Connection Timeout=30;";
26+
27+
private IFileSystem? _fileSystem;
28+
private FileSystemRuntimeConfigLoader? _runtimeConfigLoader;
29+
30+
[TestInitialize]
31+
public void TestInitialize()
32+
{
33+
_fileSystem = FileSystemUtils.ProvisionMockFileSystem();
34+
_runtimeConfigLoader = new FileSystemRuntimeConfigLoader(_fileSystem);
35+
36+
ILoggerFactory loggerFactory = TestLoggerSupport.ProvisionLoggerFactory();
37+
ConfigGenerator.SetLoggerForCliConfigGenerator(loggerFactory.CreateLogger<ConfigGenerator>());
38+
SetCliUtilsLogger(loggerFactory.CreateLogger<Utils>());
39+
}
40+
41+
[TestCleanup]
42+
public void TestCleanup()
43+
{
44+
_fileSystem = null;
45+
_runtimeConfigLoader = null;
46+
}
47+
48+
/// <summary>
49+
/// Tests that the simulate command fails when no autoentities are defined in the config.
50+
/// </summary>
51+
[TestMethod]
52+
public void TestSimulateAutoentities_NoAutoentitiesDefined()
53+
{
54+
// Arrange: create an MSSQL config without autoentities
55+
InitOptions initOptions = CreateBasicInitOptionsForMsSqlWithConfig(config: TEST_RUNTIME_CONFIG_FILE);
56+
Assert.IsTrue(TryGenerateConfig(initOptions, _runtimeConfigLoader!, _fileSystem!));
57+
58+
AutoConfigSimulateOptions options = new(config: TEST_RUNTIME_CONFIG_FILE);
59+
60+
// Act
61+
bool success = TrySimulateAutoentities(options, _runtimeConfigLoader!, _fileSystem!);
62+
63+
// Assert
64+
Assert.IsFalse(success);
65+
}
66+
67+
/// <summary>
68+
/// Integration test: verifies that an autoentities filter matching a known table (dbo.books)
69+
/// produces correct console output containing the filter name, entity name, and database object.
70+
/// Requires a running MSSQL instance with MSSQL_SA_PASSWORD environment variable set.
71+
/// </summary>
72+
[TestMethod]
73+
[TestCategory(MSSQL_CATEGORY)]
74+
public void TestSimulateAutoentities_WithMatchingFilter_OutputsToConsole()
75+
{
76+
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("MSSQL_SA_PASSWORD")))
77+
{
78+
Assert.Inconclusive("MSSQL_SA_PASSWORD environment variable not set. Skipping integration test.");
79+
return;
80+
}
81+
82+
// Arrange: create MSSQL config with autoentities filter for dbo.books
83+
InitOptions initOptions = new(
84+
databaseType: DatabaseType.MSSQL,
85+
connectionString: MSSQL_CONNECTION_STRING_TEMPLATE,
86+
cosmosNoSqlDatabase: null,
87+
cosmosNoSqlContainer: null,
88+
graphQLSchemaPath: null,
89+
setSessionContext: false,
90+
hostMode: HostMode.Development,
91+
corsOrigin: new List<string>(),
92+
authenticationProvider: EasyAuthType.AppService.ToString(),
93+
config: TEST_RUNTIME_CONFIG_FILE);
94+
Assert.IsTrue(TryGenerateConfig(initOptions, _runtimeConfigLoader!, _fileSystem!));
95+
96+
AutoConfigOptions autoConfigOptions = new(
97+
definitionName: "books-filter",
98+
patternsInclude: new[] { "dbo.books" },
99+
config: TEST_RUNTIME_CONFIG_FILE);
100+
Assert.IsTrue(ConfigGenerator.TryConfigureAutoentities(autoConfigOptions, _runtimeConfigLoader!, _fileSystem!));
101+
102+
AutoConfigSimulateOptions options = new(config: TEST_RUNTIME_CONFIG_FILE);
103+
104+
// Capture console output
105+
TextWriter originalOut = Console.Out;
106+
using StringWriter consoleOutput = new();
107+
Console.SetOut(consoleOutput);
108+
bool success;
109+
try
110+
{
111+
success = TrySimulateAutoentities(options, _runtimeConfigLoader!, _fileSystem!);
112+
}
113+
finally
114+
{
115+
Console.SetOut(originalOut);
116+
}
117+
118+
string output = consoleOutput.ToString();
119+
120+
// Assert
121+
Assert.IsTrue(success, "Simulation should succeed when the filter matches tables.");
122+
StringAssert.Contains(output, "books-filter", "Output should contain the filter name.");
123+
StringAssert.Contains(output, "books", "Output should contain the entity name.");
124+
StringAssert.Contains(output, "dbo.books", "Output should contain the database object.");
125+
}
126+
127+
/// <summary>
128+
/// Integration test: verifies that an autoentities filter matching a known table (dbo.books)
129+
/// produces a well-formed CSV file containing the filter name, entity name, and database object.
130+
/// Requires a running MSSQL instance with MSSQL_SA_PASSWORD environment variable set.
131+
/// </summary>
132+
[TestMethod]
133+
[TestCategory(MSSQL_CATEGORY)]
134+
public void TestSimulateAutoentities_WithMatchingFilter_WritesToCsvFile()
135+
{
136+
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("MSSQL_SA_PASSWORD")))
137+
{
138+
Assert.Inconclusive("MSSQL_SA_PASSWORD environment variable not set. Skipping integration test.");
139+
return;
140+
}
141+
142+
// Arrange: create MSSQL config with autoentities filter for dbo.books
143+
InitOptions initOptions = new(
144+
databaseType: DatabaseType.MSSQL,
145+
connectionString: MSSQL_CONNECTION_STRING_TEMPLATE,
146+
cosmosNoSqlDatabase: null,
147+
cosmosNoSqlContainer: null,
148+
graphQLSchemaPath: null,
149+
setSessionContext: false,
150+
hostMode: HostMode.Development,
151+
corsOrigin: new List<string>(),
152+
authenticationProvider: EasyAuthType.AppService.ToString(),
153+
config: TEST_RUNTIME_CONFIG_FILE);
154+
Assert.IsTrue(TryGenerateConfig(initOptions, _runtimeConfigLoader!, _fileSystem!));
155+
156+
AutoConfigOptions autoConfigOptions = new(
157+
definitionName: "books-filter",
158+
patternsInclude: new[] { "dbo.books" },
159+
config: TEST_RUNTIME_CONFIG_FILE);
160+
Assert.IsTrue(ConfigGenerator.TryConfigureAutoentities(autoConfigOptions, _runtimeConfigLoader!, _fileSystem!));
161+
162+
string outputCsvPath = "simulation-output.csv";
163+
AutoConfigSimulateOptions options = new(output: outputCsvPath, config: TEST_RUNTIME_CONFIG_FILE);
164+
165+
// Act
166+
bool success = TrySimulateAutoentities(options, _runtimeConfigLoader!, _fileSystem!);
167+
168+
// Assert
169+
Assert.IsTrue(success, "Simulation should succeed when the filter matches tables.");
170+
Assert.IsTrue(_fileSystem!.File.Exists(outputCsvPath), "CSV output file should be created.");
171+
string csvContent = _fileSystem.File.ReadAllText(outputCsvPath);
172+
StringAssert.Contains(csvContent, "filter_name,entity_name,database_object", "CSV should have a header row.");
173+
StringAssert.Contains(csvContent, "books-filter", "CSV should contain the filter name.");
174+
StringAssert.Contains(csvContent, "books", "CSV should contain the entity name.");
175+
StringAssert.Contains(csvContent, "dbo.books", "CSV should contain the database object.");
176+
}
177+
178+
/// <summary>
179+
/// Integration test: verifies that an autoentities filter matching no tables returns success
180+
/// and prints a "(no matches)" message to the console.
181+
/// Requires a running MSSQL instance with MSSQL_SA_PASSWORD environment variable set.
182+
/// </summary>
183+
[TestMethod]
184+
[TestCategory(MSSQL_CATEGORY)]
185+
public void TestSimulateAutoentities_WithNonMatchingFilter_OutputsNoMatches()
186+
{
187+
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("MSSQL_SA_PASSWORD")))
188+
{
189+
Assert.Inconclusive("MSSQL_SA_PASSWORD environment variable not set. Skipping integration test.");
190+
return;
191+
}
192+
193+
// Arrange: create MSSQL config with autoentities filter that matches no tables
194+
InitOptions initOptions = new(
195+
databaseType: DatabaseType.MSSQL,
196+
connectionString: MSSQL_CONNECTION_STRING_TEMPLATE,
197+
cosmosNoSqlDatabase: null,
198+
cosmosNoSqlContainer: null,
199+
graphQLSchemaPath: null,
200+
setSessionContext: false,
201+
hostMode: HostMode.Development,
202+
corsOrigin: new List<string>(),
203+
authenticationProvider: EasyAuthType.AppService.ToString(),
204+
config: TEST_RUNTIME_CONFIG_FILE);
205+
Assert.IsTrue(TryGenerateConfig(initOptions, _runtimeConfigLoader!, _fileSystem!));
206+
207+
AutoConfigOptions autoConfigOptions = new(
208+
definitionName: "empty-filter",
209+
patternsInclude: new[] { "dbo.NonExistentTable99999" },
210+
config: TEST_RUNTIME_CONFIG_FILE);
211+
Assert.IsTrue(ConfigGenerator.TryConfigureAutoentities(autoConfigOptions, _runtimeConfigLoader!, _fileSystem!));
212+
213+
AutoConfigSimulateOptions options = new(config: TEST_RUNTIME_CONFIG_FILE);
214+
215+
// Capture console output
216+
TextWriter originalOut = Console.Out;
217+
using StringWriter consoleOutput = new();
218+
Console.SetOut(consoleOutput);
219+
bool success;
220+
try
221+
{
222+
success = TrySimulateAutoentities(options, _runtimeConfigLoader!, _fileSystem!);
223+
}
224+
finally
225+
{
226+
Console.SetOut(originalOut);
227+
}
228+
229+
string output = consoleOutput.ToString();
230+
231+
// Assert
232+
// Output format is produced by WriteSimulationResultsToConsole:
233+
// "Filter: <name>", "Matches: <count>", and "(no matches)" when count is 0.
234+
Assert.IsTrue(success, "Simulation should succeed even when no tables match.");
235+
StringAssert.Contains(output, "empty-filter", "Output should contain the filter name.");
236+
StringAssert.Contains(output, "Matches: 0", "Output should show zero matches.");
237+
StringAssert.Contains(output, "(no matches)", "Output should show the 'no matches' message.");
238+
}
239+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.IO.Abstractions;
5+
using Azure.DataApiBuilder.Config;
6+
using Azure.DataApiBuilder.Product;
7+
using Cli.Constants;
8+
using CommandLine;
9+
using Microsoft.Extensions.Logging;
10+
using static Cli.Utils;
11+
using ILogger = Microsoft.Extensions.Logging.ILogger;
12+
13+
namespace Cli.Commands
14+
{
15+
/// <summary>
16+
/// Command options for the auto-config-simulate verb.
17+
/// Simulates autoentities generation by querying the database and displaying
18+
/// which entities would be created for each filter definition.
19+
/// </summary>
20+
[Verb("auto-config-simulate", isDefault: false, HelpText = "Simulate autoentities generation by querying the database and displaying the results.", Hidden = false)]
21+
public class AutoConfigSimulateOptions : Options
22+
{
23+
public AutoConfigSimulateOptions(
24+
string? output = null,
25+
string? config = null)
26+
: base(config)
27+
{
28+
Output = output;
29+
}
30+
31+
[Option('o', "output", Required = false, HelpText = "Path to output CSV file. If not specified, results are printed to the console.")]
32+
public string? Output { get; }
33+
34+
public int Handler(ILogger logger, FileSystemRuntimeConfigLoader loader, IFileSystem fileSystem)
35+
{
36+
logger.LogInformation("{productName} {version}", PRODUCT_NAME, ProductInfo.GetProductVersion());
37+
bool isSuccess = ConfigGenerator.TrySimulateAutoentities(this, loader, fileSystem);
38+
if (isSuccess)
39+
{
40+
return CliReturnCode.SUCCESS;
41+
}
42+
else
43+
{
44+
logger.LogError("Failed to simulate autoentities.");
45+
return CliReturnCode.GENERAL_ERROR;
46+
}
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)