using Admin.NET.Plugin.AiDOP.Entity.S8; using Admin.NET.Plugin.AiDOP.Service.S8; using Admin.NET.Plugin.AiDOP.Service.S8.Rules; using Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess; using Xunit; namespace Admin.NET.Test.S8; /// /// 规则取数配置校验(T1–T4)。 /// /// 分两级: /// · ValidateForSave —— dataset_code 结构 + 目录归属,不要求 Provider 已上线(允许存草稿); /// · ValidateForEnable —— 完整运行条件(委托 Enable Gate)。 /// /// S8-STANDARD-DATASET-HARD-CUTOVER-1:原有的 Legacy 分组用例 /// (mode=NULL 可保存 / 缺 expression 被拒 / 不得混填 dataset_code / /// RequiresDataSource / UnknownDataAccessMode)已全部删除 —— /// 它们校验的是"两种取数模式之间的边界",而现在只剩一种模式。 /// 那些字段(DataAccessMode / Expression / DataSourceId)连同校验分支一并物理消失, /// 用例既写不出来、也无对应风险可守。 /// public class S8WatchRuleDataAccessValidatorTests { private const string ReadyDataset = "FAKE_VALIDATOR_DATASET"; private const string DefinedNoProvider = "FAKE_VALIDATOR_DATASET_NO_PROVIDER"; private static S8DatasetCapabilities TimeoutCapable() => new() { SupportsTimeout = true, HasSourceObjectId = true, HasRelatedObjectCode = true, HasDueAt = true, HasStatus = true }; private sealed class FakeSource : IS8DatasetDefinitionSource { public IEnumerable GetDefinitions() => new[] { new S8DatasetDefinition { DatasetCode = ReadyDataset, DisplayName = "校验测试数据集", Kind = S8DatasetKind.Object, Capabilities = TimeoutCapable() }, new S8DatasetDefinition { DatasetCode = DefinedNoProvider, DisplayName = "已定义未实现数据集", Kind = S8DatasetKind.Object, Capabilities = TimeoutCapable() } }; } private sealed class TimeoutOnlyProvider : IS8MonitoringDataProvider { public string DatasetCode => ReadyDataset; public S8DatasetCapabilities Capabilities => TimeoutCapable(); public Task LoadAsync(S8MonitoringDataRequest request, CancellationToken cancellationToken = default) => Task.FromResult(S8MonitoringRowSet.Empty); } private static S8DatasetCatalog Catalog() => new(new[] { new FakeSource() }); private static S8DatasetEnableGate Gate() => new(Catalog(), new S8MonitoringDataProviderRegistry(new[] { new TimeoutOnlyProvider() })); private static AdoS8WatchRule Rule(string ruleType, string datasetCode = null) => new() { RuleCode = "UT_RULE", RuleType = ruleType, DatasetCode = datasetCode }; // ---------- T1 / T2 / T3:保存级 ---------- [Fact] public void DatasetRule_WithoutDatasetCode_IsRejectedOnSave() { var ex = Assert.Throws(() => S8WatchRuleDataAccessValidator.ValidateForSave( Rule("TIMEOUT"), Catalog())); Assert.Contains(S8DatasetReasonCode.CodeMissing, ex.Message); } [Fact] public void DatasetRule_WithUndefinedDataset_IsRejectedOnSave() { var ex = Assert.Throws(() => S8WatchRuleDataAccessValidator.ValidateForSave( Rule("TIMEOUT", "NO_SUCH_DATASET"), Catalog())); Assert.Contains(S8DatasetReasonCode.NotDefined, ex.Message); } [Fact] public void DatasetRule_DefinedButProviderMissing_CanBeSavedAsDraft() { // 关键行为:Provider 未上线时仍可保存配置。 S8WatchRuleDataAccessValidator.ValidateForSave( Rule("TIMEOUT", DefinedNoProvider), Catalog()); } // ---------- T3 / T4:启用级 ---------- [Fact] public void DatasetRule_DefinedButProviderMissing_CannotBeEnabled() { var ex = Assert.Throws(() => S8WatchRuleDataAccessValidator.ValidateForEnable( Rule("TIMEOUT", DefinedNoProvider), Gate(), 1, 1)); Assert.Contains(S8DatasetReasonCode.ProviderNotRegistered, ex.Message); } [Fact] public void DatasetRule_WithCapabilityMismatch_CannotBeEnabled() { // TIMEOUT-only 数据集挂 OUT_OF_RANGE 规则。 var ex = Assert.Throws(() => S8WatchRuleDataAccessValidator.ValidateForEnable( Rule("OUT_OF_RANGE", ReadyDataset), Gate(), 1, 1)); Assert.Contains(S8DatasetReasonCode.CapabilityMismatch, ex.Message); } [Fact] public void DatasetRule_Matching_IsEnabled_WithoutAnySql() { // 标准数据集规则不需要用户填写任何 SQL。 var rule = Rule("TIMEOUT", ReadyDataset); S8WatchRuleDataAccessValidator.ValidateForSave(rule, Catalog()); S8WatchRuleDataAccessValidator.ValidateForEnable(rule, Gate(), 1, 1); } }