KpiAchievementEvaluationTests.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. using Admin.NET.Plugin.AiDOP.Infrastructure;
  2. using Xunit;
  3. namespace Admin.NET.Plugin.AiDOP.Tests.SmartOps;
  4. public class KpiAchievementEvaluationTests
  5. {
  6. private const string Higher = "higher_is_better";
  7. private const string Lower = "lower_is_better";
  8. [Theory]
  9. [InlineData(100, 100, "green", false)]
  10. [InlineData(99, 100, "yellow", false)]
  11. [InlineData(95, 100, "yellow", false)]
  12. [InlineData(94.99, 100, "red", false)]
  13. [InlineData(80, 100, "red", false)]
  14. [InlineData(79.99, 100, "red", true)]
  15. public void Higher_DefaultBoundaries(decimal current, decimal target, string status, bool blink)
  16. {
  17. AssertEval(current, target, Higher, status, blink);
  18. }
  19. [Theory]
  20. [InlineData(100, 100, "green", false)]
  21. [InlineData(100.01, 100, "yellow", false)]
  22. [InlineData(110, 100, "yellow", false)]
  23. [InlineData(110.01, 100, "red", false)]
  24. [InlineData(120, 100, "red", false)]
  25. [InlineData(120.01, 100, "red", true)]
  26. public void Lower_DefaultBoundaries(decimal current, decimal target, string status, bool blink)
  27. {
  28. AssertEval(current, target, Lower, status, blink);
  29. }
  30. [Theory]
  31. [InlineData(100, 100, "green", false)]
  32. [InlineData(90, 100, "yellow", false)]
  33. [InlineData(89.99, 100, "red", false)]
  34. [InlineData(70, 100, "red", false)]
  35. [InlineData(69.99, 100, "red", true)]
  36. [InlineData(80, 100, "red", false)]
  37. public void Higher_CustomYellow90Red70(decimal current, decimal target, string status, bool blink)
  38. {
  39. AssertEval(current, target, Higher, status, blink, 90m, 70m);
  40. }
  41. [Theory]
  42. [InlineData(100, 100, "green", false)]
  43. [InlineData(115, 100, "yellow", false)]
  44. [InlineData(115.01, 100, "red", false)]
  45. [InlineData(130, 100, "red", false)]
  46. [InlineData(130.01, 100, "red", true)]
  47. [InlineData(120, 100, "red", false)]
  48. public void Lower_CustomYellow115Red130(decimal current, decimal target, string status, bool blink)
  49. {
  50. AssertEval(current, target, Lower, status, blink, 115m, 130m);
  51. }
  52. [Fact]
  53. public void NullCurrent_SafeYellow() => AssertSafeYellow(null, 100m);
  54. [Fact]
  55. public void NullTarget_SafeYellow() => AssertSafeYellow(80m, null);
  56. [Fact]
  57. public void ZeroTarget_SafeYellow() => AssertSafeYellow(80m, 0m);
  58. private static void AssertSafeYellow(decimal? current, decimal? target)
  59. {
  60. var e = AidopS4KpiMerge.Evaluate(current, target, Higher);
  61. Assert.Equal("yellow", e.Status);
  62. Assert.Null(e.RatioPercent);
  63. Assert.False(e.IsBeyondRedThreshold);
  64. Assert.Equal("yellow", AidopS4KpiMerge.AchievementLevel(current, target, Higher));
  65. }
  66. [Fact]
  67. public void IllegalHigher_RedGreaterThanYellow_FallsBackToDefaults()
  68. {
  69. var e = AidopS4KpiMerge.Evaluate(94.99m, 100m, Higher, 90m, 95m);
  70. Assert.Equal("red", e.Status);
  71. Assert.False(e.IsBeyondRedThreshold);
  72. Assert.Equal(95m, e.EffectiveYellowThreshold);
  73. Assert.Equal(80m, e.EffectiveRedThreshold);
  74. }
  75. [Fact]
  76. public void IllegalHigher_YellowAbove100_FallsBackToDefaults()
  77. {
  78. var e = AidopS4KpiMerge.Evaluate(96m, 100m, Higher, 101m, 80m);
  79. Assert.Equal("yellow", e.Status);
  80. Assert.Equal(95m, e.EffectiveYellowThreshold);
  81. Assert.Equal(80m, e.EffectiveRedThreshold);
  82. }
  83. [Fact]
  84. public void IllegalLower_YellowBelow100_FallsBackToDefaults()
  85. {
  86. var e = AidopS4KpiMerge.Evaluate(105m, 100m, Lower, 90m, 120m);
  87. Assert.Equal("yellow", e.Status);
  88. Assert.Equal(110m, e.EffectiveYellowThreshold);
  89. Assert.Equal(120m, e.EffectiveRedThreshold);
  90. }
  91. [Fact]
  92. public void IllegalLower_RedLessThanYellow_FallsBackToDefaults()
  93. {
  94. var e = AidopS4KpiMerge.Evaluate(112m, 100m, Lower, 115m, 110m);
  95. Assert.Equal("red", e.Status);
  96. Assert.False(e.IsBeyondRedThreshold);
  97. Assert.Equal(110m, e.EffectiveYellowThreshold);
  98. Assert.Equal(120m, e.EffectiveRedThreshold);
  99. }
  100. [Fact]
  101. public void SaveRejects_IllegalHigher_RedGreaterThanYellow()
  102. {
  103. Assert.False(AidopS4KpiMerge.TryValidateThresholds(Higher, 90m, 95m, out var error));
  104. Assert.Equal("higher_is_better 阈值不合法,应满足 0 < RedThreshold <= YellowThreshold <= 100。", error);
  105. }
  106. [Fact]
  107. public void SaveRejects_IllegalHigher_YellowAbove100()
  108. {
  109. Assert.False(AidopS4KpiMerge.TryValidateThresholds(Higher, 101m, 80m, out var error));
  110. Assert.Contains("higher_is_better", error);
  111. }
  112. [Fact]
  113. public void SaveRejects_IllegalLower_YellowBelow100()
  114. {
  115. Assert.False(AidopS4KpiMerge.TryValidateThresholds(Lower, 90m, 120m, out var error));
  116. Assert.Equal("lower_is_better 阈值不合法,应满足 100 <= YellowThreshold <= RedThreshold。", error);
  117. }
  118. [Fact]
  119. public void SaveRejects_IllegalLower_RedLessThanYellow()
  120. {
  121. Assert.False(AidopS4KpiMerge.TryValidateThresholds(Lower, 115m, 110m, out var error));
  122. Assert.Contains("lower_is_better", error);
  123. }
  124. [Fact]
  125. public void SaveAllows_BothThresholdsNull()
  126. {
  127. Assert.True(AidopS4KpiMerge.TryValidateThresholds(Higher, null, null, out var error));
  128. Assert.Null(error);
  129. }
  130. [Fact]
  131. public void SaveAllows_SingleThresholdCombinedWithDefault()
  132. {
  133. Assert.True(AidopS4KpiMerge.TryValidateThresholds(Higher, 90m, null, out _));
  134. Assert.True(AidopS4KpiMerge.TryValidateThresholds(Lower, null, 130m, out _));
  135. }
  136. [Fact]
  137. public void SaveDoesNotSwap_IllegalPair()
  138. {
  139. Assert.False(AidopS4KpiMerge.TryValidateThresholds(Higher, 70m, 90m, out _));
  140. var e = AidopS4KpiMerge.Evaluate(75m, 100m, Higher, 70m, 90m);
  141. Assert.Equal(95m, e.EffectiveYellowThreshold);
  142. Assert.Equal(80m, e.EffectiveRedThreshold);
  143. }
  144. [Fact]
  145. public void AchievementLevel_MatchesEvaluateStatus()
  146. {
  147. foreach (var (current, target, dir, y, r) in new (decimal, decimal, string, decimal?, decimal?)[]
  148. {
  149. (100m, 100m, Higher, null, null),
  150. (79.99m, 100m, Higher, null, null),
  151. (120.01m, 100m, Lower, null, null),
  152. (69.99m, 100m, Higher, 90m, 70m),
  153. (94.99m, 100m, Higher, 90m, 95m)
  154. })
  155. {
  156. var eval = AidopS4KpiMerge.Evaluate(current, target, dir, y, r);
  157. Assert.Equal(eval.Status, AidopS4KpiMerge.AchievementLevel(current, target, dir, y, r));
  158. }
  159. }
  160. private static void AssertEval(
  161. decimal current,
  162. decimal target,
  163. string direction,
  164. string status,
  165. bool blink,
  166. decimal? yellow = null,
  167. decimal? red = null)
  168. {
  169. var e = AidopS4KpiMerge.Evaluate(current, target, direction, yellow, red);
  170. Assert.Equal(status, e.Status);
  171. Assert.Equal(blink, e.IsBeyondRedThreshold);
  172. Assert.Equal(status == "red" && blink, e.Status == "red" && e.IsBeyondRedThreshold);
  173. Assert.Equal(e.Status, AidopS4KpiMerge.AchievementLevel(current, target, direction, yellow, red));
  174. Assert.Equal(blink, AidopS4KpiMerge.IsBeyondRedThreshold(current, target, direction, yellow, red));
  175. }
  176. }