KpiTargetResolverTests.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. using Admin.NET.Plugin.AiDOP.Entity;
  2. using Admin.NET.Plugin.AiDOP.SmartOps;
  3. using Microsoft.Extensions.Logging.Abstractions;
  4. using Xunit;
  5. namespace Admin.NET.Plugin.AiDOP.Tests.SmartOps;
  6. public class KpiTargetResolverTests
  7. {
  8. private static KpiTargetResolver Resolver(FakeKpiTargetStore store, FakeDailyReader daily) =>
  9. new(store, new LegacyKpiTargetProvider(daily, NullLogger<LegacyKpiTargetProvider>.Instance), NullLogger<KpiTargetResolver>.Instance);
  10. [Fact]
  11. public async Task FactoryBeatsTenantDefault()
  12. {
  13. var store = new FakeKpiTargetStore();
  14. store.Configs.Add(Cfg(1, 0, 10, new DateTime(2026, 1, 1)));
  15. store.Configs.Add(Cfg(2, 1, 3, new DateTime(2026, 1, 1)));
  16. var snap = await Resolver(store, new FakeDailyReader()).ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 8, 1));
  17. Assert.Equal(3m, snap.TargetValue);
  18. Assert.Equal(KpiTargetSnapshotSource.FormalFactory, snap.TargetSource);
  19. Assert.Equal(2, snap.TargetConfigId);
  20. }
  21. [Fact]
  22. public async Task InclusiveBounds_AndFutureNotActive()
  23. {
  24. var store = new FakeKpiTargetStore();
  25. store.Configs.Add(Cfg(1, 1, 3, new DateTime(2026, 9, 1), new DateTime(2026, 9, 30)));
  26. var r = Resolver(store, new FakeDailyReader());
  27. var onStart = await r.ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 9, 1));
  28. var onEnd = await r.ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 9, 30));
  29. var before = await r.ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 8, 31));
  30. Assert.Equal(3m, onStart.TargetValue);
  31. Assert.Equal(3m, onEnd.TargetValue);
  32. Assert.NotEqual(KpiTargetSnapshotSource.FormalFactory, before.TargetSource);
  33. }
  34. [Fact]
  35. public async Task SwitchDay_UsesNewConfig()
  36. {
  37. var store = new FakeKpiTargetStore();
  38. store.Configs.Add(Cfg(1, 1, 3, new DateTime(2026, 1, 1), new DateTime(2026, 8, 31)));
  39. store.Configs.Add(Cfg(2, 1, 5, new DateTime(2026, 9, 1)));
  40. var r = Resolver(store, new FakeDailyReader());
  41. var d1 = await r.ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 8, 31));
  42. var d2 = await r.ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 9, 1));
  43. Assert.Equal(3m, d1.TargetValue);
  44. Assert.Equal(5m, d2.TargetValue);
  45. }
  46. [Fact]
  47. public async Task DuplicateScope_Throws()
  48. {
  49. var store = new FakeKpiTargetStore();
  50. store.Configs.Add(Cfg(1, 1, 3, new DateTime(2026, 1, 1)));
  51. store.Configs.Add(Cfg(2, 1, 4, new DateTime(2026, 1, 1)));
  52. await Assert.ThrowsAsync<KpiTargetDuplicateScopeException>(() =>
  53. Resolver(store, new FakeDailyReader()).ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 8, 1)));
  54. }
  55. [Fact]
  56. public async Task Missing_ReturnsNull_NotZero()
  57. {
  58. var snap = await Resolver(new FakeKpiTargetStore(), new FakeDailyReader())
  59. .ResolveAsync(9, 1, "S5_L1_001", "S5", DateTime.Today);
  60. Assert.Null(snap.TargetValue);
  61. Assert.Equal(KpiTargetSnapshotSource.Missing, snap.TargetSource);
  62. }
  63. [Fact]
  64. public async Task LegacyOnlyWhenNoFormal_S1()
  65. {
  66. var daily = new FakeDailyReader { Current = 8m };
  67. var snap = await Resolver(new FakeKpiTargetStore(), daily)
  68. .ResolveAsync(9, 1, "S1_L1_001", "S1", DateTime.Today);
  69. Assert.Equal(8m, snap.TargetValue);
  70. Assert.Equal(KpiTargetSnapshotSource.LegacyDailyCurrent, snap.TargetSource);
  71. }
  72. [Fact]
  73. public async Task FormalBeatsLegacy()
  74. {
  75. var store = new FakeKpiTargetStore();
  76. store.Configs.Add(Cfg(1, 1, 3, new DateTime(2026, 1, 1)));
  77. var daily = new FakeDailyReader { Current = 8m };
  78. var snap = await Resolver(store, daily).ResolveAsync(9, 1, "S1_L1_001", "S1", DateTime.Today);
  79. Assert.Equal(3m, snap.TargetValue);
  80. Assert.Equal(KpiTargetSnapshotSource.FormalFactory, snap.TargetSource);
  81. }
  82. [Fact]
  83. public async Task S5_DoesNotUseLegacy()
  84. {
  85. var daily = new FakeDailyReader { Current = 8m, Prior = 7m };
  86. var snap = await Resolver(new FakeKpiTargetStore(), daily)
  87. .ResolveAsync(9, 1, "S5_L1_001", "S5", DateTime.Today);
  88. Assert.Null(snap.TargetValue);
  89. }
  90. [Fact]
  91. public async Task CodeDefault_WhenNoDaily()
  92. {
  93. var snap = await Resolver(new FakeKpiTargetStore(), new FakeDailyReader())
  94. .ResolveAsync(9, 1, "S1_L1_001", "S1", DateTime.Today);
  95. Assert.Equal(3m, snap.TargetValue);
  96. Assert.Equal(KpiTargetSnapshotSource.LegacyCode, snap.TargetSource);
  97. }
  98. [Fact]
  99. public void HistoricalDaily_NotMutatedByConfigInsert()
  100. {
  101. var store = new FakeKpiTargetStore();
  102. var dailyValue = 9m;
  103. store.Configs.Add(Cfg(1, 1, 3, DateTime.Today));
  104. Assert.Equal(9m, dailyValue);
  105. Assert.Single(store.Configs);
  106. }
  107. private static AdoSmartOpsKpiTargetConfig Cfg(long id, long factory, decimal value, DateTime from, DateTime? to = null) => new()
  108. {
  109. Id = id,
  110. TenantId = 9,
  111. FactoryId = factory,
  112. MetricCode = "S1_L1_001",
  113. TargetValue = value,
  114. EffectiveFrom = from,
  115. EffectiveTo = to,
  116. SourceType = KpiTargetConfigSource.Manual,
  117. Status = 1,
  118. CreateTime = DateTime.Now,
  119. UpdateTime = DateTime.Now
  120. };
  121. }