Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
66 changes: 66 additions & 0 deletions src/Cli.Tests/ValidateConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,72 @@ public void TestChildWithEntitiesAndAutoentitiesResolvingZeroLogsNamedWarning()
VerifyAutoentityZeroDiscoveredWarning(loggerMock, expectedFileNameInMessage: "child-db.json");
}

/// <summary>
/// Child config with only autoentities that resolve to >0 entities is valid.
/// Simulates the production code path where the metadata provider stores resolution
/// counts on the root (merged) config rather than on the child config directly.
/// This is the regression test for the bug where child-config autoentities caused
/// "No entities found" even when they were expanded successfully.
/// Covers child truth-table row C5 (DS=1, E=0, AE=1, resolved>0, counts on root).
/// </summary>
[TestMethod]
public void TestChildWithDataSourceAndAutoentitiesResolvingEntitiesIsValid()
{
// Child has no explicit entities; only autoentities.
RuntimeConfig childConfig = BuildTestConfig(
hasDataSource: true,
entities: new(),
autoentities: new() { { "ae1", BuildSimpleAutoentity() } });
childConfig.IsChildConfig = true;

RuntimeConfig rootConfig = BuildTestConfig(
hasDataSource: false, entities: new(),
dataSourceFiles: new DataSourceFiles(new[] { "child-db.json" }));
rootConfig.ChildConfigs.Add(("child-db.json", childConfig));

// Simulate production: metadata provider stores counts in the root config,
// NOT directly on the child config.
rootConfig.AutoentityResolutionCounts["ae1"] = 3;

RuntimeConfigValidator validator = BuildValidator(rootConfig);
validator.ValidateDataSourceAndEntityPresence(rootConfig);

Assert.AreEqual(0, validator.ConfigValidationExceptions.Count,
"Child config with autoentities that resolved entities should pass validation.");
}

/// <summary>
/// Both root and child configs have autoentities that resolve to >0 entities.
/// Resolution counts for both are stored only on the root config (as the metadata
/// provider does at runtime). Validation must pass for both configs.
/// </summary>
[TestMethod]
public void TestRootAndChildBothWithAutoentitiesResolvingEntitiesIsValid()
{
RuntimeConfig childConfig = BuildTestConfig(
hasDataSource: true,
entities: new(),
autoentities: new() { { "child-ae", BuildSimpleAutoentity() } });
childConfig.IsChildConfig = true;

RuntimeConfig rootConfig = BuildTestConfig(
hasDataSource: true,
entities: new(),
dataSourceFiles: new DataSourceFiles(new[] { "child-db.json" }),
autoentities: new() { { "root-ae", BuildSimpleAutoentity() } });
rootConfig.ChildConfigs.Add(("child-db.json", childConfig));

// Simulate production: metadata provider stores ALL counts in the root config.
rootConfig.AutoentityResolutionCounts["root-ae"] = 2;
rootConfig.AutoentityResolutionCounts["child-ae"] = 4;

RuntimeConfigValidator validator = BuildValidator(rootConfig);
validator.ValidateDataSourceAndEntityPresence(rootConfig);

Assert.AreEqual(0, validator.ConfigValidationExceptions.Count,
"Root and child configs both with autoentities that resolved entities should pass validation.");
}

/// <summary>
/// Helper: verifies that the autoentity-discovered-zero warning was logged at least once,
/// optionally also checking that the formatted message contains a child config file name.
Expand Down
12 changes: 12 additions & 0 deletions src/Core/Configurations/RuntimeConfigValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,18 @@ private void ValidateRootConfig(RuntimeConfig runtimeConfig)
// Validate each child config independently.
foreach ((string fileName, RuntimeConfig childConfig) in runtimeConfig.ChildConfigs)
{
// The metadata provider stores autoentity resolution counts on the root (merged)
// config. Copy those counts to the child config so per-child validation can find
// them. Only copy if the child hasn't already been populated (e.g. in unit tests).
foreach (KeyValuePair<string, Autoentity> ae in childConfig.Autoentities)
{
if (!childConfig.AutoentityResolutionCounts.ContainsKey(ae.Key)
&& runtimeConfig.AutoentityResolutionCounts.TryGetValue(ae.Key, out int count))
{
childConfig.AutoentityResolutionCounts[ae.Key] = count;
}
}

ValidateNonRootConfig(childConfig, configName: fileName);
}
}
Expand Down