diff --git a/src/Cli.Tests/ValidateConfigTests.cs b/src/Cli.Tests/ValidateConfigTests.cs index fe985f1a34..6e3f0fb1d5 100644 --- a/src/Cli.Tests/ValidateConfigTests.cs +++ b/src/Cli.Tests/ValidateConfigTests.cs @@ -883,6 +883,72 @@ public void TestChildWithEntitiesAndAutoentitiesResolvingZeroLogsNamedWarning() VerifyAutoentityZeroDiscoveredWarning(loggerMock, expectedFileNameInMessage: "child-db.json"); } + /// + /// 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). + /// + [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."); + } + + /// + /// 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. + /// + [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."); + } + /// /// 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. diff --git a/src/Core/Configurations/RuntimeConfigValidator.cs b/src/Core/Configurations/RuntimeConfigValidator.cs index 1c9f8b9ecd..c8a6c6e2c9 100644 --- a/src/Core/Configurations/RuntimeConfigValidator.cs +++ b/src/Core/Configurations/RuntimeConfigValidator.cs @@ -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 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); } }