| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- using Admin.NET.Plugin.AiDOP.Entity;
- using Admin.NET.Plugin.AiDOP.SmartOps;
- using Microsoft.Extensions.Logging.Abstractions;
- using Xunit;
- namespace Admin.NET.Plugin.AiDOP.Tests.SmartOps;
- public class KpiTargetResolverTests
- {
- private static KpiTargetResolver Resolver(FakeKpiTargetStore store, FakeDailyReader daily) =>
- new(store, new LegacyKpiTargetProvider(daily, NullLogger<LegacyKpiTargetProvider>.Instance), NullLogger<KpiTargetResolver>.Instance);
- [Fact]
- public async Task FactoryBeatsTenantDefault()
- {
- var store = new FakeKpiTargetStore();
- store.Configs.Add(Cfg(1, 0, 10, new DateTime(2026, 1, 1)));
- store.Configs.Add(Cfg(2, 1, 3, new DateTime(2026, 1, 1)));
- var snap = await Resolver(store, new FakeDailyReader()).ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 8, 1));
- Assert.Equal(3m, snap.TargetValue);
- Assert.Equal(KpiTargetSnapshotSource.FormalFactory, snap.TargetSource);
- Assert.Equal(2, snap.TargetConfigId);
- }
- [Fact]
- public async Task InclusiveBounds_AndFutureNotActive()
- {
- var store = new FakeKpiTargetStore();
- store.Configs.Add(Cfg(1, 1, 3, new DateTime(2026, 9, 1), new DateTime(2026, 9, 30)));
- var r = Resolver(store, new FakeDailyReader());
- var onStart = await r.ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 9, 1));
- var onEnd = await r.ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 9, 30));
- var before = await r.ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 8, 31));
- Assert.Equal(3m, onStart.TargetValue);
- Assert.Equal(3m, onEnd.TargetValue);
- Assert.NotEqual(KpiTargetSnapshotSource.FormalFactory, before.TargetSource);
- }
- [Fact]
- public async Task SwitchDay_UsesNewConfig()
- {
- var store = new FakeKpiTargetStore();
- store.Configs.Add(Cfg(1, 1, 3, new DateTime(2026, 1, 1), new DateTime(2026, 8, 31)));
- store.Configs.Add(Cfg(2, 1, 5, new DateTime(2026, 9, 1)));
- var r = Resolver(store, new FakeDailyReader());
- var d1 = await r.ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 8, 31));
- var d2 = await r.ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 9, 1));
- Assert.Equal(3m, d1.TargetValue);
- Assert.Equal(5m, d2.TargetValue);
- }
- [Fact]
- public async Task DuplicateScope_Throws()
- {
- var store = new FakeKpiTargetStore();
- store.Configs.Add(Cfg(1, 1, 3, new DateTime(2026, 1, 1)));
- store.Configs.Add(Cfg(2, 1, 4, new DateTime(2026, 1, 1)));
- await Assert.ThrowsAsync<KpiTargetDuplicateScopeException>(() =>
- Resolver(store, new FakeDailyReader()).ResolveAsync(9, 1, "S1_L1_001", "S1", new DateTime(2026, 8, 1)));
- }
- [Fact]
- public async Task Missing_ReturnsNull_NotZero()
- {
- var snap = await Resolver(new FakeKpiTargetStore(), new FakeDailyReader())
- .ResolveAsync(9, 1, "S5_L1_001", "S5", DateTime.Today);
- Assert.Null(snap.TargetValue);
- Assert.Equal(KpiTargetSnapshotSource.Missing, snap.TargetSource);
- }
- [Fact]
- public async Task LegacyOnlyWhenNoFormal_S1()
- {
- var daily = new FakeDailyReader { Current = 8m };
- var snap = await Resolver(new FakeKpiTargetStore(), daily)
- .ResolveAsync(9, 1, "S1_L1_001", "S1", DateTime.Today);
- Assert.Equal(8m, snap.TargetValue);
- Assert.Equal(KpiTargetSnapshotSource.LegacyDailyCurrent, snap.TargetSource);
- }
- [Fact]
- public async Task FormalBeatsLegacy()
- {
- var store = new FakeKpiTargetStore();
- store.Configs.Add(Cfg(1, 1, 3, new DateTime(2026, 1, 1)));
- var daily = new FakeDailyReader { Current = 8m };
- var snap = await Resolver(store, daily).ResolveAsync(9, 1, "S1_L1_001", "S1", DateTime.Today);
- Assert.Equal(3m, snap.TargetValue);
- Assert.Equal(KpiTargetSnapshotSource.FormalFactory, snap.TargetSource);
- }
- [Fact]
- public async Task S5_DoesNotUseLegacy()
- {
- var daily = new FakeDailyReader { Current = 8m, Prior = 7m };
- var snap = await Resolver(new FakeKpiTargetStore(), daily)
- .ResolveAsync(9, 1, "S5_L1_001", "S5", DateTime.Today);
- Assert.Null(snap.TargetValue);
- }
- [Fact]
- public async Task CodeDefault_WhenNoDaily()
- {
- var snap = await Resolver(new FakeKpiTargetStore(), new FakeDailyReader())
- .ResolveAsync(9, 1, "S1_L1_001", "S1", DateTime.Today);
- Assert.Equal(3m, snap.TargetValue);
- Assert.Equal(KpiTargetSnapshotSource.LegacyCode, snap.TargetSource);
- }
- [Fact]
- public void HistoricalDaily_NotMutatedByConfigInsert()
- {
- var store = new FakeKpiTargetStore();
- var dailyValue = 9m;
- store.Configs.Add(Cfg(1, 1, 3, DateTime.Today));
- Assert.Equal(9m, dailyValue);
- Assert.Single(store.Configs);
- }
- private static AdoSmartOpsKpiTargetConfig Cfg(long id, long factory, decimal value, DateTime from, DateTime? to = null) => new()
- {
- Id = id,
- TenantId = 9,
- FactoryId = factory,
- MetricCode = "S1_L1_001",
- TargetValue = value,
- EffectiveFrom = from,
- EffectiveTo = to,
- SourceType = KpiTargetConfigSource.Manual,
- Status = 1,
- CreateTime = DateTime.Now,
- UpdateTime = DateTime.Now
- };
- }
|