| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- 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;
- /// <summary>
- /// 规则取数配置校验。
- ///
- /// S8-DATASET-FOUNDATION-HARDENING-2 起分两级:
- /// · ValidateForSave —— 结构 + 目录归属,不要求 Provider 已上线(允许存草稿);
- /// · ValidateForEnable —— 完整运行条件(委托 Enable Gate)。
- /// </summary>
- 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<S8DatasetDefinition> 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<S8MonitoringRowSet> 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 mode = null, string datasetCode = null, string expression = null) => new()
- {
- RuleCode = "UT_RULE",
- RuleType = ruleType,
- DataAccessMode = mode,
- DatasetCode = datasetCode,
- Expression = expression
- };
- // ---------- Legacy ----------
- [Fact]
- public void LegacyRule_WithNullMode_AndExpression_IsAccepted()
- {
- // 132 条历史规则的形态:mode=NULL、dataset_code=NULL、expression 有值。
- S8WatchRuleDataAccessValidator.ValidateForSave(
- Rule("TIMEOUT", mode: null, datasetCode: null, expression: "SELECT 1"), Catalog());
- }
- [Fact]
- public void LegacyRule_WithoutExpression_IsRejected()
- {
- var ex = Assert.Throws<S8BizException>(() =>
- S8WatchRuleDataAccessValidator.ValidateForSave(Rule("TIMEOUT", expression: null), Catalog()));
- Assert.Contains("expression", ex.Message);
- }
- [Fact]
- public void LegacyRule_MustNotCarryDatasetCode()
- {
- // 两种模式的字段混填会让实际生效路径不可预测,必须显式拒绝。
- var rule = Rule("TIMEOUT", mode: S8DataAccessMode.LegacySql, datasetCode: ReadyDataset, expression: "SELECT 1");
- Assert.Throws<S8BizException>(() => S8WatchRuleDataAccessValidator.ValidateForSave(rule, Catalog()));
- }
- [Fact]
- public void LegacyRule_IsRejectedByEnableGate_AfterRuntimeRetirement()
- {
- // 语义变更(S8-LEGACY-RUNTIME-RETIREMENT-1):本用例原名
- // LegacyRule_PassesEnableGate_WithoutDatasetChecks,断言 Legacy 规则可以被启用。
- // 该行为已被产品裁决取代 —— STANDARD_DATASET 是唯一允许运行的取数模式。
- // 这不是实现回归,而是断言的业务前提本身已经改变,故就地反转而非删除,
- // 以保留「Legacy 曾可启用」这段历史在 git blame 中可追。
- //
- // 用 mode: null 而非显式 LEGACY_SQL 是刻意的:132 条历史规则正是 NULL,
- // 必须证明退役门禁覆盖的是**解析后的模式**,而不只是字面值。
- var rule = Rule("TIMEOUT", mode: null, expression: "SELECT 1");
- var ex = Assert.Throws<S8BizException>(() =>
- S8WatchRuleDataAccessValidator.ValidateForEnable(rule, Gate(), 1, 1));
- Assert.Contains(S8RuleCreationPolicy.LegacySqlRetiredReason, ex.Message);
- }
- // ---------- Standard dataset · Save ----------
- [Fact]
- public void StandardDatasetRule_WithoutDatasetCode_IsRejectedOnSave()
- {
- var ex = Assert.Throws<S8BizException>(() =>
- S8WatchRuleDataAccessValidator.ValidateForSave(
- Rule("TIMEOUT", mode: S8DataAccessMode.StandardDataset), Catalog()));
- Assert.Contains(S8DatasetReasonCode.CodeMissing, ex.Message);
- }
- [Fact]
- public void StandardDatasetRule_WithUndefinedDataset_IsRejectedOnSave()
- {
- var ex = Assert.Throws<S8BizException>(() =>
- S8WatchRuleDataAccessValidator.ValidateForSave(
- Rule("TIMEOUT", mode: S8DataAccessMode.StandardDataset, datasetCode: "NO_SUCH_DATASET"), Catalog()));
- Assert.Contains(S8DatasetReasonCode.NotDefined, ex.Message);
- }
- [Fact]
- public void StandardDatasetRule_DefinedButProviderMissing_CanBeSavedAsDraft()
- {
- // 关键行为:Provider 未上线时仍可保存配置。
- S8WatchRuleDataAccessValidator.ValidateForSave(
- Rule("TIMEOUT", mode: S8DataAccessMode.StandardDataset, datasetCode: DefinedNoProvider), Catalog());
- }
- // ---------- Standard dataset · Enable ----------
- [Fact]
- public void StandardDatasetRule_DefinedButProviderMissing_CannotBeEnabled()
- {
- var ex = Assert.Throws<S8BizException>(() =>
- S8WatchRuleDataAccessValidator.ValidateForEnable(
- Rule("TIMEOUT", mode: S8DataAccessMode.StandardDataset, datasetCode: DefinedNoProvider),
- Gate(), 1, 1));
- Assert.Contains(S8DatasetReasonCode.ProviderNotRegistered, ex.Message);
- }
- [Fact]
- public void StandardDatasetRule_WithCapabilityMismatch_CannotBeEnabled()
- {
- // TIMEOUT-only 数据集挂 OUT_OF_RANGE 规则。
- var ex = Assert.Throws<S8BizException>(() =>
- S8WatchRuleDataAccessValidator.ValidateForEnable(
- Rule("OUT_OF_RANGE", mode: S8DataAccessMode.StandardDataset, datasetCode: ReadyDataset),
- Gate(), 1, 1));
- Assert.Contains(S8DatasetReasonCode.CapabilityMismatch, ex.Message);
- }
- [Fact]
- public void StandardDatasetRule_Matching_IsEnabled_WithoutAnySql()
- {
- // 标准数据集规则不需要用户填写任何 SQL。
- var rule = Rule("TIMEOUT", mode: S8DataAccessMode.StandardDataset, datasetCode: ReadyDataset, expression: null);
- S8WatchRuleDataAccessValidator.ValidateForSave(rule, Catalog());
- S8WatchRuleDataAccessValidator.ValidateForEnable(rule, Gate(), 1, 1);
- }
- // ---------- 其他 ----------
- [Fact]
- public void UnknownDataAccessMode_IsRejected()
- {
- Assert.Throws<S8BizException>(() =>
- S8WatchRuleDataAccessValidator.ValidateForSave(
- Rule("TIMEOUT", mode: "SOMETHING_ELSE", expression: "SELECT 1"), Catalog()));
- }
- [Fact]
- public void RequiresDataSource_TrueForLegacy_FalseForStandardDataset()
- {
- // LEGACY_SQL 仍必须绑定可信作用域内的数据源;STANDARD_DATASET 无物理数据源概念。
- Assert.True(S8WatchRuleDataAccessValidator.RequiresDataSource(Rule("TIMEOUT", mode: null)));
- Assert.True(S8WatchRuleDataAccessValidator.RequiresDataSource(Rule("TIMEOUT", mode: S8DataAccessMode.LegacySql)));
- Assert.False(S8WatchRuleDataAccessValidator.RequiresDataSource(
- Rule("TIMEOUT", mode: S8DataAccessMode.StandardDataset, datasetCode: ReadyDataset)));
- }
- }
|