HomeGridLastKnownValueContractTests.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. using Admin.NET.Plugin.AiDOP.Infrastructure;
  2. using Admin.NET.Plugin.AiDOP.SmartOps;
  3. using Xunit;
  4. namespace Admin.NET.Plugin.AiDOP.Tests.DataPlatform;
  5. public class HomeGridLastKnownValueContractTests
  6. {
  7. private static readonly DateTime Today = new(2026, 9, 29);
  8. [Fact]
  9. public void LatestNoData_ShowsPreviousOk_AsLastKnown()
  10. {
  11. var card = HomeGridDisplayPolicy.Decide(
  12. Row(1, 3m, 10m, "OK", new DateTime(2026, 9, 23)),
  13. Row(2, null, null, "NO_DATA", new DateTime(2026, 9, 28)),
  14. 7, Today);
  15. Assert.Equal(3m, card.CurrentValue);
  16. Assert.Equal(10m, card.TargetValue);
  17. Assert.Equal("LAST_KNOWN_VALUE", card.DisplayState);
  18. Assert.Equal(new DateTime(2026, 9, 23), card.BizDate);
  19. Assert.Equal("NO_DATA", card.LatestResultStatus);
  20. Assert.Equal(new DateTime(2026, 9, 28), card.LatestBizDate);
  21. }
  22. [Fact]
  23. public void LatestOk_IsCurrentValue()
  24. {
  25. var card = HomeGridDisplayPolicy.Decide(
  26. Row(5, 1m, 2m, "OK", Today),
  27. Row(5, 1m, 2m, "OK", Today),
  28. 7, Today);
  29. Assert.Equal("CURRENT_VALUE", card.DisplayState);
  30. Assert.Equal(0, card.ValueAgeDays);
  31. Assert.Equal("FRESH", card.FreshnessState);
  32. }
  33. [Fact]
  34. public void NeverHadValue_IsNoData()
  35. {
  36. var card = HomeGridDisplayPolicy.Decide(
  37. null, Row(3, null, null, "NO_DATA", Today), 7, Today);
  38. Assert.Null(card.CurrentValue);
  39. Assert.Equal("NO_DATA", card.DisplayState);
  40. Assert.Equal("NO_VALUE", card.FreshnessState);
  41. }
  42. [Fact]
  43. public void SourceUnsupported_WithoutHistory_StaysUnsupported()
  44. {
  45. var card = HomeGridDisplayPolicy.Decide(
  46. null, Row(4, null, null, "SOURCE_UNSUPPORTED", Today), 7, Today);
  47. Assert.Equal("SOURCE_UNSUPPORTED", card.DisplayState);
  48. }
  49. [Fact]
  50. public void ValueAndTarget_ComeFromTheSameUsableRow()
  51. {
  52. var card = HomeGridDisplayPolicy.Decide(
  53. Row(8, 100m, 80m, "OK", new DateTime(2026, 9, 23)),
  54. Row(9, null, 1m, "NO_DATA", Today),
  55. 7, Today);
  56. Assert.Equal(100m, card.CurrentValue);
  57. Assert.Equal(80m, card.TargetValue);
  58. Assert.NotEqual(1m, card.TargetValue);
  59. }
  60. [Fact]
  61. public void BlankHistoricalStatus_IsNormalizedToOk()
  62. {
  63. var card = HomeGridDisplayPolicy.Decide(
  64. Row(6, 4m, null, "", new DateTime(2026, 9, 1)),
  65. Row(7, null, null, "NO_DATA", Today),
  66. 7, Today);
  67. Assert.Equal("OK", card.ResultStatus);
  68. Assert.Equal("NO_DATA", card.LatestResultStatus);
  69. }
  70. [Fact]
  71. public void DailySla_EightDaysIsStale_SevenDaysIsFresh()
  72. {
  73. var fresh = HomeGridDisplayPolicy.Decide(
  74. Row(1, 1m, null, "OK", Today.AddDays(-7)),
  75. Row(1, 1m, null, "OK", Today.AddDays(-7)),
  76. 7, Today);
  77. var stale = HomeGridDisplayPolicy.Decide(
  78. Row(1, 1m, null, "OK", Today.AddDays(-8)),
  79. Row(1, 1m, null, "OK", Today.AddDays(-8)),
  80. 7, Today);
  81. Assert.Equal("FRESH", fresh.FreshnessState);
  82. Assert.Equal("STALE", stale.FreshnessState);
  83. }
  84. [Fact]
  85. public void MonthlySla_ThirtyDaysFresh_FortySixDaysStale()
  86. {
  87. var fresh = HomeGridDisplayPolicy.Decide(
  88. Row(1, 1m, null, "OK", Today.AddDays(-30)),
  89. Row(1, 1m, null, "OK", Today.AddDays(-30)),
  90. 45, Today);
  91. var stale = HomeGridDisplayPolicy.Decide(
  92. Row(1, 1m, null, "OK", Today.AddDays(-46)),
  93. Row(1, 1m, null, "OK", Today.AddDays(-46)),
  94. 45, Today);
  95. Assert.Equal("FRESH", fresh.FreshnessState);
  96. Assert.Equal("STALE", stale.FreshnessState);
  97. }
  98. [Fact]
  99. public void MissingSla_IsPolicyMissing_NotFresh()
  100. {
  101. var card = HomeGridDisplayPolicy.Decide(
  102. Row(1, 1m, null, "OK", Today),
  103. Row(1, 1m, null, "OK", Today),
  104. null, Today);
  105. Assert.Equal("POLICY_MISSING", card.FreshnessState);
  106. }
  107. [Fact]
  108. public void FutureBizDate_ClampsAgeToZero()
  109. {
  110. var card = HomeGridDisplayPolicy.Decide(
  111. Row(1, 1m, null, "OK", Today.AddDays(2)),
  112. Row(1, 1m, null, "OK", Today.AddDays(2)),
  113. 7, Today);
  114. Assert.True(card.FutureBizDate);
  115. Assert.Equal(0, card.ValueAgeDays);
  116. Assert.Equal("FRESH", card.FreshnessState);
  117. }
  118. [Fact]
  119. public void Sql_UsesSameScope_AndDoesNotFallBackOutsideTheRange()
  120. {
  121. var source = File.ReadAllText(Path.Combine(RepoRoot(),
  122. "server", "Plugins", "Admin.NET.Plugin.AiDOP", "Controllers", "AidopKanbanController.Generic.cs"));
  123. Assert.Contains("ORDER BY v.biz_date DESC, v.calc_time DESC, v.id DESC", source);
  124. Assert.Equal(2, Count(source, "AND (@ds IS NULL OR v.biz_date>=@ds)"));
  125. Assert.Equal(2, Count(source, "AND (@de IS NULL OR v.biz_date<=@de)"));
  126. Assert.Contains("v.metric_value IS NOT NULL", source);
  127. Assert.Contains("v.result_status='OK' OR v.result_status IS NULL OR v.result_status=''", source);
  128. Assert.Contains("v.tenant_id=@t AND v.factory_id=@f AND v.module_code=@m", source);
  129. Assert.Contains("禁止拿未筛选的最后真实值补上", source);
  130. Assert.DoesNotContain("ToDictionary", Slice(source, "latestAttemptSql", "public async Task<IActionResult> GetDetailKpisGeneric"));
  131. }
  132. [Fact]
  133. public void FutureBizDateWarning_NamesOnlyTenantFactoryMetricAndDate()
  134. {
  135. var source = File.ReadAllText(Path.Combine(RepoRoot(),
  136. "server", "Plugins", "Admin.NET.Plugin.AiDOP", "Controllers", "AidopKanbanController.Generic.cs"));
  137. var i = source.IndexOf("home grid future biz date", StringComparison.Ordinal);
  138. Assert.True(i > 0);
  139. var window = source[i..(i + 220)];
  140. Assert.Contains("tenant={TenantId}", window);
  141. Assert.Contains("factory={FactoryId}", window);
  142. Assert.Contains("metric={MetricCode}", window);
  143. Assert.Contains("bizDate={BizDate}", window);
  144. Assert.DoesNotContain("resultReason", window, StringComparison.OrdinalIgnoreCase);
  145. }
  146. [Theory]
  147. [InlineData("S5_L1_003", "天", 45)]
  148. [InlineData("S5_L1_004", "天", 45)]
  149. [InlineData("S6_L1_003", "天", 45)]
  150. [InlineData("S7_L1_003", "天", 45)]
  151. [InlineData("S9_L1_001", "天", 45)]
  152. [InlineData("S8_L1_001", "实时", 1)]
  153. [InlineData("S1_L1_001", "天", 7)]
  154. public void SeedHelper_UsesMonthlyCodeThenRealtimeThenSeven(string code, string frequency, int expected)
  155. {
  156. Assert.Equal(expected, AidopKpiMasterSeed.ResolveFreshnessSlaDays(code, frequency));
  157. }
  158. private static HomeGridDisplayPolicy.Row Row(long id, decimal? value, decimal? target, string? status, DateTime bizDate) =>
  159. new()
  160. {
  161. Id = id,
  162. MetricValue = value,
  163. TargetValue = target,
  164. ResultStatus = status,
  165. BizDate = bizDate,
  166. };
  167. private static string RepoRoot()
  168. {
  169. var dir = new DirectoryInfo(AppContext.BaseDirectory);
  170. while (dir != null && !File.Exists(Path.Combine(dir.FullName, "AGENTS.md")))
  171. dir = dir.Parent;
  172. return dir?.FullName ?? throw new InvalidOperationException("repo root not found");
  173. }
  174. private static string Slice(string src, string start, string end)
  175. {
  176. var i = src.IndexOf(start, StringComparison.Ordinal);
  177. var j = src.IndexOf(end, i, StringComparison.Ordinal);
  178. Assert.True(i >= 0 && j > i);
  179. return src[i..j];
  180. }
  181. private static int Count(string src, string token)
  182. {
  183. var n = 0;
  184. var i = 0;
  185. while ((i = src.IndexOf(token, i, StringComparison.Ordinal)) >= 0)
  186. {
  187. n++;
  188. i += token.Length;
  189. }
  190. return n;
  191. }
  192. }