Skip to content
Open
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
63 changes: 63 additions & 0 deletions src/Cli.Tests/ValidateConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,69 @@ public void TestChildWithEntitiesAndAutoentitiesResolvingZeroLogsNamedWarning()
VerifyAutoentityZeroDiscoveredWarning(loggerMock, expectedFileNameInMessage: "child-db.json");
}

/// <summary>
/// Child config with only autoentities that resolve to greater than 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.
/// </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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests exercise the propagation loop but do not reproduce the production data-source-files path.

Manually calling rootConfig.ChildConfigs.Add(...) does not perform the other work done by the real loader: child entities and autoentities are merged into the root collections, ownership maps are combined, and metadata providers are created per data source. The missing merge is significant because it hides both the root-without-data-source failure and the case where child discoveries incorrectly satisfy the root.

Please add a regression test that creates actual root and child JSON files, loads them through FileSystemRuntimeConfigLoader, runs metadata initialization and validation, and asserts the per-file result. Since the linked issue also reports missing child-generated entities through MCP, please either add runtime/MCP coverage proving those entities are present or track that runtime behavior separately rather than closing the whole issue with validation-only tests.

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
11 changes: 11 additions & 0 deletions src/Core/Configurations/RuntimeConfigValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,17 @@ 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 config.
// Copy those counts to the child config so per-child validation can find them
foreach (KeyValuePair<string, Autoentity> ae in childConfig.Autoentities)
{
if (!childConfig.AutoentityResolutionCounts.ContainsKey(ae.Key)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest metadata result should be authoritative instead of preserving an existing child value.

The ContainsKey guard makes validation stateful. After one validation, the child contains the copied count. If metadata initialization later updates the root count, a subsequent validation will retain the old child value. For example, a child copied with count 0 will continue failing even if the current root count is 3; the reverse can incorrectly accept an autoentity that now resolves 0.

Please overwrite the child value from the current root result, including removing a stale child value when the root has no result. Preferably, avoid mutating the child entirely and have per-child validation read the authoritative root count dictionary using the child's autoentity definition names.

&& runtimeConfig.AutoentityResolutionCounts.TryGetValue(ae.Key, out int count))
{
childConfig.AutoentityResolutionCounts[ae.Key] = count;
}
}

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