StocktakeLabelConfirmAuthTests.cs 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. using System.Reflection;
  2. using Microsoft.AspNetCore.Authorization;
  3. using Admin.NET.Plugin.AiDOP.MaterialWarehouse;
  4. using Xunit;
  5. namespace Admin.NET.Plugin.AiDOP.Tests.S5.MaterialWarehouse;
  6. /// <summary>
  7. /// FUNC-S5-012 盘点标签确认:匿名访问收口守卫。
  8. /// <para>
  9. /// 纯反射断言,不连库、不起服务,普通 CI 也会执行(无 AIDOP_IT 门禁)。
  10. /// 背景:该服务原先类级带 [AllowAnonymous],实测匿名 GET(无 Authorization 头、
  11. /// credentials:'omit')返回 HTTP 200。本组用例把「不得再标注匿名」固化下来。
  12. /// </para>
  13. /// <para>
  14. /// 注意:本组只覆盖 <b>authentication</b>。该服务的租户边界仍是 WHERE IsTag=1,
  15. /// 属 DATA-MODEL DEBT,未在此断言,也不得据此认为 tenant isolation 已收口。
  16. /// </para>
  17. /// </summary>
  18. public class StocktakeLabelConfirmAuthTests
  19. {
  20. private static readonly Type Svc = typeof(StocktakeLabelConfirmService);
  21. [Fact]
  22. public void Class_MustNotBeMarkedAllowAnonymous()
  23. {
  24. var attr = Svc.GetCustomAttribute<AllowAnonymousAttribute>(inherit: true);
  25. Assert.Null(attr);
  26. }
  27. [Fact]
  28. public void NoEndpoint_MayBeMarkedAllowAnonymous()
  29. {
  30. var offenders = Svc
  31. .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
  32. .Where(m => m.GetCustomAttribute<AllowAnonymousAttribute>(inherit: true) != null)
  33. .Select(m => m.Name)
  34. .ToList();
  35. Assert.True(offenders.Count == 0, $"以下端点仍标注了 AllowAnonymous:{string.Join(", ", offenders)}");
  36. }
  37. /// <summary>
  38. /// 同目录其余 MissedPrint 只读服务此前已收口,一并守住,防止有人再加回来。
  39. /// </summary>
  40. [Theory]
  41. [InlineData(typeof(LabelQueryService))]
  42. [InlineData(typeof(StocktakeResultService))]
  43. [InlineData(typeof(PendingInspectionService))]
  44. [InlineData(typeof(StockQueryService))]
  45. public void SiblingReadOnlyServices_MustNotBeAnonymous(Type serviceType)
  46. {
  47. Assert.Null(serviceType.GetCustomAttribute<AllowAnonymousAttribute>(inherit: true));
  48. }
  49. /// <summary>
  50. /// 本服务必须保持只读:出现任何写端点都应让本用例失败,
  51. /// 因为「匿名已关」的风险论证建立在「无写能力」之上。
  52. /// </summary>
  53. [Fact]
  54. public void Service_MustRemainReadOnly()
  55. {
  56. var writeVerbs = new[] { "HttpPostAttribute", "HttpPutAttribute", "HttpDeleteAttribute", "HttpPatchAttribute" };
  57. var offenders = Svc
  58. .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
  59. .Where(m => m.GetCustomAttributes(inherit: true)
  60. .Any(a => writeVerbs.Contains(a.GetType().Name)))
  61. .Select(m => m.Name)
  62. .ToList();
  63. Assert.True(offenders.Count == 0, $"出现写端点:{string.Join(", ", offenders)}");
  64. }
  65. }