| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116 |
- using System.Runtime.CompilerServices;
- using Admin.NET.Plugin.AiDOP.Controllers.S8;
- using Admin.NET.Plugin.AiDOP.Entity.S8;
- using Admin.NET.Plugin.AiDOP.Infrastructure;
- using Admin.NET.Plugin.AiDOP.Service.S8;
- using Microsoft.AspNetCore.Http;
- using Microsoft.AspNetCore.Mvc;
- using Xunit;
- namespace Admin.NET.Plugin.AiDOP.Tests.S8;
- /// <summary>
- /// S8-STEP6D-CFG-ALERT-LEGACY-WRITE-GUARD-CERT-1:钉住 CFG_ALERT 的
- /// 「历史兼容只读 + 写入口退役」契约。
- ///
- /// <para><b>不触任何数据库。</b> 这不是靠 mock 硬凑出来的——写方法在**任何 DB 访问之前**
- /// 就抛出,所以根本不需要可用的仓储。用 <see cref="RuntimeHelpers.GetUninitializedObject"/>
- /// 绕开构造函数(其参数 <c>SqlSugarRepository<T></c> 的无参构造会触发 Furion.App
- /// 静态初始化,在无宿主的 xunit 进程内必抛 TypeInitializationException)。
- /// 若将来有人把守卫挪到 DB 访问之后,本测试会因 NullReferenceException 而失败——
- /// 这正是我们想要的信号:**守卫必须前置**。</para>
- /// </summary>
- public class S8AlertRuleWriteRetiredTests
- {
- private static S8AlertRuleService Svc() =>
- (S8AlertRuleService)RuntimeHelpers.GetUninitializedObject(typeof(S8AlertRuleService));
- private static readonly S8TrustedScope Scope = new(838257186181189L, 838257186320453L);
- private static AdoS8ConfigAlertRulesController Controller() => new(Svc(), null!);
- private static void AssertGone(IActionResult result)
- {
- var objectResult = Assert.IsType<ObjectResult>(result);
- Assert.Equal(StatusCodes.Status410Gone, objectResult.StatusCode);
- var message = objectResult.Value!.GetType().GetProperty("message")!.GetValue(objectResult.Value) as string;
- Assert.Equal(S8WriteRetiredException.UserMessage, message);
- }
- [Fact]
- public void CreateAsync_IsRetired()
- {
- var ex = Assert.Throws<S8WriteRetiredException>(
- () => { _ = Svc().CreateAsync(new AdoS8AlertRule { RuleCode = "X", SceneCode = "S1" }, Scope); });
- Assert.Equal(S8WriteRetiredException.UserMessage, ex.Message);
- }
- [Fact]
- public void UpdateAsync_IsRetired()
- {
- Assert.Throws<S8WriteRetiredException>(
- () => { _ = Svc().UpdateAsync(1L, new AdoS8AlertRule { RuleCode = "X", SceneCode = "S1" }, Scope); });
- }
- [Fact]
- public void DeleteAsync_IsRetired()
- {
- Assert.Throws<S8WriteRetiredException>(() => { _ = Svc().DeleteAsync(1L, Scope); });
- }
- [Fact]
- public async Task Controller_Create_Returns410_WithoutScopeOrRepositoryAccess()
- {
- AssertGone(await Controller().CreateAsync(new AdoS8AlertRule { RuleCode = "X", SceneCode = "S1" }));
- }
- [Theory]
- [InlineData(1L)]
- [InlineData(999999999L)]
- public async Task Controller_UpdateAndDelete_Return410_RegardlessOfId(long id)
- {
- var controller = Controller();
- AssertGone(await controller.UpdateAsync(id, new AdoS8AlertRule { RuleCode = "X", SceneCode = "S1" }));
- AssertGone(await controller.DeleteAsync(id));
- }
- /// <summary>越权 / 不存在的 Id 同样返回「已退役」,不得因 Id 不同而走回 404 分支。</summary>
- [Theory]
- [InlineData(0L)]
- [InlineData(1L)] // Demo 租户实际存在的 G01_TEST_ALERT
- [InlineData(999999999L)] // 不存在
- public void Writes_AreRetired_RegardlessOfId(long id)
- {
- Assert.Throws<S8WriteRetiredException>(() => { _ = Svc().DeleteAsync(id, Scope); });
- Assert.Throws<S8WriteRetiredException>(
- () => { _ = Svc().UpdateAsync(id, new AdoS8AlertRule { RuleCode = "X", SceneCode = "S1" }, Scope); });
- }
- /// <summary>读路径必须保留:历史兼容数据仍要能查。</summary>
- [Fact]
- public void ReadPath_IsPreserved()
- {
- Assert.NotNull(typeof(S8AlertRuleService).GetMethod("ListAsync", new[] { typeof(long), typeof(long) }));
- }
- /// <summary>
- /// 继承关系是防御性设计:只捕获 S8BizException 的旧调用方会安全降级为 400,而不是 500。
- /// </summary>
- [Fact]
- public void RetiredException_DerivesFromBizException()
- {
- Assert.True(typeof(S8BizException).IsAssignableFrom(typeof(S8WriteRetiredException)));
- Assert.False(typeof(S8NotFoundException).IsAssignableFrom(typeof(S8WriteRetiredException)));
- }
- /// <summary>用户文案必须同时说明「只读」与「当前权威是监控规则」,且不含内部实现细节。</summary>
- [Fact]
- public void UserMessage_StatesReadOnlyAndWatchRuleAuthority()
- {
- var m = S8WriteRetiredException.UserMessage;
- Assert.Contains("只读", m);
- Assert.Contains("监控规则", m);
- foreach (var leak in new[] { "Exception", "SqlSugar", "ado_s8_", "Service", "null" })
- Assert.DoesNotContain(leak, m, StringComparison.OrdinalIgnoreCase);
- }
- }
|