using System.Reflection;
using System.Runtime.CompilerServices;
using Admin.NET.Plugin.AiDOP.Entity.S8;
using Admin.NET.Plugin.AiDOP.Infrastructure;
using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
using Admin.NET.Plugin.AiDOP.Service.S8;
using Admin.NET.Plugin.AiDOP.Service.S8.Rules;
using Xunit;
namespace Admin.NET.Plugin.AiDOP.Tests.S8;
///
/// S8-STEP6E-CFG-WATCH-FIX-AND-SAFE-CERT-1:钉住 CFG_WATCH 的
/// 「整实体 PUT 退役 + 新建 canonical 词表」契约。
///
/// 不触任何数据库。 退役守卫在**任何 DB 访问之前**抛出,词表校验是纯静态方法,
/// 所以都不需要可用的仓储。用 绕开构造函数
/// (其 SqlSugarRepository<T> 参数的无参构造会触发 Furion.App 静态初始化,
/// 在无宿主的 xunit 进程内必抛 TypeInitializationException)。
/// 若将来有人把守卫挪到 DB 访问之后,本测试会因 NullReferenceException 失败——
/// 这正是我们要的信号:守卫必须前置。
///
public class S8WatchRuleWriteGuardTests
{
private static S8WatchRuleService Svc() =>
(S8WatchRuleService)RuntimeHelpers.GetUninitializedObject(typeof(S8WatchRuleService));
private static readonly S8TrustedScope Scope = new(838257186181189L);
private static AdoS8WatchRule Body() => new()
{
RuleCode = "TEST-WATCH-X",
SceneCode = "S1",
RuleType = S8TimeoutRuleEvaluator.RuleTypeCode,
Severity = S8SeverityCode.Follow,
DatasetCode = "PURCHASE_DELIVERY",
WatchObjectType = "ORDER",
PollIntervalSeconds = 300,
};
// ---------------- P0:整实体 PUT 退役 ----------------
[Fact]
public void UpdateAsync_IsRetired_WithWatchSpecificMessage()
{
var ex = Assert.Throws(
() => { _ = Svc().UpdateAsync(1L, Body(), Scope); });
Assert.Equal(S8WriteRetiredException.WatchRuleUpdateMessage, ex.Message);
// 不能沿用 CFG_ALERT 的文案:那会让用户误以为「监控规则整体只读了」。
Assert.NotEqual(S8WriteRetiredException.UserMessage, ex.Message);
Assert.Contains("专用配置接口", ex.Message);
}
/// 越权 / 不存在的 id 同样 410,不得因 id 不同回落 404 分支(避免存在性探测)。
[Theory]
[InlineData(0L)]
[InlineData(187L)] // UAT 真实存在的 stale 行
[InlineData(1329909460001L)] // Golden watch rule
[InlineData(999999999L)] // 不存在
public void UpdateAsync_IsRetired_RegardlessOfId(long id)
{
Assert.Throws(() => { _ = Svc().UpdateAsync(id, Body(), Scope); });
}
/// 退役异常必须继承 S8BizException,使只捕获基类的旧调用方安全降级为 400 而非 500。
[Fact]
public void RetiredException_DerivesFromBizException_AndIsNotNotFound()
{
Assert.True(typeof(S8BizException).IsAssignableFrom(typeof(S8WriteRetiredException)));
Assert.False(typeof(S8NotFoundException).IsAssignableFrom(typeof(S8WriteRetiredException)));
}
/// 窄写入口与读路径必须保留——退役的只是整实体 PUT。
[Fact]
public void NarrowWritePathsAndReadPath_ArePreserved()
{
var t = typeof(S8WatchRuleService);
// S8-TENANT-ONLY-BATCH5:列表按租户取,不再按 (租户, 工厂)。
Assert.NotNull(t.GetMethod("ListAsync", new[] { typeof(long) }));
Assert.Null(t.GetMethod("ListAsync", new[] { typeof(long), typeof(long) }));
// S8-RULE-GOVERNANCE-BATCH1:整块 params 入口已被强类型白名单入口取代(G1 修复)。
// 断言旧签名**不存在**,否则那条"缺字段即置 NULL"的路径就还活着。
Assert.Null(t.GetMethods().FirstOrDefault(m => m.Name == "UpdateParamsAsync"));
Assert.NotNull(t.GetMethod("UpdateParametersAsync", new[] { typeof(long), typeof(S8RuleParametersPayload), typeof(S8TrustedScope) }));
Assert.NotNull(t.GetMethod("EnableAsync", new[] { typeof(long), typeof(S8TrustedScope) }));
Assert.NotNull(t.GetMethod("DisableAsync", new[] { typeof(long), typeof(S8TrustedScope) }));
Assert.NotNull(t.GetMethod("UpdateScheduleAsync", new[] { typeof(long), typeof(S8WatchRuleSchedulePayload), typeof(S8TrustedScope) }));
Assert.NotNull(t.GetMethod("PauseAsync", new[] { typeof(long), typeof(S8TrustedScope) }));
Assert.NotNull(t.GetMethod("ResumeAsync", new[] { typeof(long), typeof(S8TrustedScope) }));
Assert.NotNull(t.GetMethod("RunNowAsync", new[] { typeof(long), typeof(S8TrustedScope) }));
}
// ================================================================================
// S8-RULE-GOVERNANCE-BATCH3:原「P1:新建 canonical 词表」整段已删除。
//
// 那一段守护的是 ValidateVocabularyForCreate —— 即"调用方提交的 rule_type / scene_code /
// severity 是否在词表内"。业务侧建规则能力已退役,该私有方法随之删除,
// 词表校验的职责整体上移到 S8RuleCatalog 的构造期校验(且更严格:rule_type 改为 ordinal
// 精确匹配,"timeout" 这类大小写错误在那里会直接让服务起不来,而不是存下来再运行期报错)。
//
// 对应覆盖去向:
// · 词表 / 类型合法性 → S8RuleDefinitionContractTests.T2_*
// · severity 不得静默降级 → S8RuleParameterContractTests.T6_NonWhitelistedSeverity_IsRejected
// + S8TimeoutEvaluatorTests.RuntimeParameters_InvalidSeverity_*
// · 业务不能建 / 删规则 → S8RuleCreationRetiredTests
// ================================================================================
}