Testing und interfaces

This commit is contained in:
Meik
2026-02-13 09:06:36 +01:00
parent abbce22aa9
commit 352dc42ae7
8 changed files with 846 additions and 166 deletions

View File

@@ -0,0 +1,150 @@
using FasdDesktopUi.Basics.Services.Models;
namespace F4SD.Cockpit.Client.Test.Basics.Sevices.TicketOverview;
public class TicketOverviewCountProcessorTest
{
private static readonly string[] OverviewKeys = ["TicketsNew", "IncidentNew"];
[Fact]
public void Calculate_PersonalScope_UpdatesOnlyPersonalAndReturnsDeltaChanges()
{
// Arrange
Dictionary<string, TileCounts> current = new(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = new TileCounts(1, 7),
["IncidentNew"] = new TileCounts(2, 8)
};
Dictionary<string, int> incoming = new(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = 5,
["IncidentNew"] = 2
};
// Act
ScopeCountProcessingResult result = TicketOverviewCountProcessor.Calculate(
current,
OverviewKeys,
TileScope.Personal,
incoming,
hasInitializedScope: true);
// Assert
Assert.False(result.IsInitialization);
Assert.Equal(5, result.UpdatedCounts["TicketsNew"].Personal);
Assert.Equal(7, result.UpdatedCounts["TicketsNew"].Role);
Assert.Equal(2, result.UpdatedCounts["IncidentNew"].Personal);
Assert.Equal(8, result.UpdatedCounts["IncidentNew"].Role);
Assert.Single(result.Changes);
TileCountChange change = result.Changes[0];
Assert.Equal("TicketsNew", change.Key);
Assert.Equal(TileScope.Personal, change.Scope);
Assert.Equal(1, change.OldCount);
Assert.Equal(5, change.NewCount);
Assert.Equal(4, change.Delta);
}
[Fact]
public void Calculate_RoleScope_UpdatesOnlyRoleAndReturnsDeltaChanges()
{
// Arrange
Dictionary<string, TileCounts> current = new(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = new TileCounts(10, 3),
["IncidentNew"] = new TileCounts(4, 6)
};
Dictionary<string, int> incoming = new(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = 3,
["IncidentNew"] = 9
};
// Act
ScopeCountProcessingResult result = TicketOverviewCountProcessor.Calculate(
current,
OverviewKeys,
TileScope.Role,
incoming,
hasInitializedScope: true);
// Assert
Assert.False(result.IsInitialization);
Assert.Equal(10, result.UpdatedCounts["TicketsNew"].Personal);
Assert.Equal(3, result.UpdatedCounts["TicketsNew"].Role);
Assert.Equal(4, result.UpdatedCounts["IncidentNew"].Personal);
Assert.Equal(9, result.UpdatedCounts["IncidentNew"].Role);
Assert.Single(result.Changes);
TileCountChange change = result.Changes[0];
Assert.Equal("IncidentNew", change.Key);
Assert.Equal(TileScope.Role, change.Scope);
Assert.Equal(6, change.OldCount);
Assert.Equal(9, change.NewCount);
Assert.Equal(3, change.Delta);
}
[Fact]
public void Calculate_FirstInitialization_DoesNotReturnDeltaChanges()
{
// Arrange
Dictionary<string, TileCounts> current = new(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = new TileCounts(0, 0),
["IncidentNew"] = new TileCounts(0, 0)
};
Dictionary<string, int> incoming = new(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = 4,
["IncidentNew"] = 1
};
// Act
ScopeCountProcessingResult result = TicketOverviewCountProcessor.Calculate(
current,
OverviewKeys,
TileScope.Personal,
incoming,
hasInitializedScope: false);
// Assert
Assert.True(result.IsInitialization);
Assert.Empty(result.Changes);
Assert.Equal(4, result.UpdatedCounts["TicketsNew"].Personal);
Assert.Equal(1, result.UpdatedCounts["IncidentNew"].Personal);
}
[Fact]
public void Calculate_MissingIncomingValues_DefaultsToZero()
{
// Arrange
Dictionary<string, TileCounts> current = new(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = new TileCounts(5, 1),
["IncidentNew"] = new TileCounts(2, 4)
};
Dictionary<string, int> incoming = new(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = 5
};
// Act
ScopeCountProcessingResult result = TicketOverviewCountProcessor.Calculate(
current,
OverviewKeys,
TileScope.Personal,
incoming,
hasInitializedScope: true);
// Assert
Assert.Equal(5, result.UpdatedCounts["TicketsNew"].Personal);
Assert.Equal(1, result.UpdatedCounts["TicketsNew"].Role);
Assert.Equal(0, result.UpdatedCounts["IncidentNew"].Personal);
Assert.Equal(4, result.UpdatedCounts["IncidentNew"].Role);
Assert.Single(result.Changes);
Assert.Equal("IncidentNew", result.Changes[0].Key);
Assert.Equal(2, result.Changes[0].OldCount);
Assert.Equal(0, result.Changes[0].NewCount);
}
}

View File

@@ -0,0 +1,243 @@
using FasdDesktopUi.Basics.Services;
using FasdDesktopUi.Basics.Services.Models;
namespace F4SD.Cockpit.Client.Test.Basics.Sevices.TicketOverview;
public class TicketOverviewUpdateServiceTest
{
[Fact]
public async Task UpdateAvailability_FirstFetch_EmitsInitializationEventsForBothScopes()
{
// Arrange
var communication = new FakeCommunication();
communication.SetCounts(TileScope.Personal, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = 2
});
communication.SetCounts(TileScope.Role, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = 5
});
var service = CreateService(communication, out _);
var events = new List<TicketOverviewCountsChangedEventArgs>();
service.OverviewCountsChanged += (_, args) => events.Add(args);
// Act
service.UpdateAvailability(true);
await WaitUntilAsync(() => service.AreAllScopesInitialized);
// Assert
Assert.Equal(2, events.Count);
Assert.All(events, e => Assert.Empty(e.Changes));
Assert.Contains(events, e => e.InitializedScope == TileScope.Personal);
Assert.Contains(events, e => e.InitializedScope == TileScope.Role);
Assert.Equal(2, service.CurrentCounts["TicketsNew"].Personal);
Assert.Equal(5, service.CurrentCounts["TicketsNew"].Role);
}
[Fact]
public async Task FetchAsync_ChangedAndUnchangedCounts_EmitsOnlyRealDeltaEvent()
{
// Arrange
var communication = new FakeCommunication();
communication.SetCounts(TileScope.Personal, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = 1
});
communication.SetCounts(TileScope.Role, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = 3
});
var service = CreateService(communication, out _);
var events = new List<TicketOverviewCountsChangedEventArgs>();
service.OverviewCountsChanged += (_, args) => events.Add(args);
service.UpdateAvailability(true);
await WaitUntilAsync(() => service.AreAllScopesInitialized);
events.Clear();
// Act + Assert (changed)
communication.SetCounts(TileScope.Personal, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = 4
});
await service.FetchAsync(TileScope.Personal);
Assert.Single(events);
Assert.Single(events[0].Changes);
Assert.Equal("TicketsNew", events[0].Changes[0].Key);
Assert.Equal(1, events[0].Changes[0].OldCount);
Assert.Equal(4, events[0].Changes[0].NewCount);
Assert.Equal(3, events[0].Changes[0].Delta);
// Act + Assert (unchanged)
events.Clear();
await service.FetchAsync(TileScope.Personal);
Assert.Empty(events);
}
[Fact]
public async Task Stop_ResetsInitializationAndCounts()
{
// Arrange
var communication = new FakeCommunication();
communication.SetCounts(TileScope.Personal, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
["TicketsNew"] = 7
});
communication.SetCounts(TileScope.Role, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
["IncidentNew"] = 9
});
var service = CreateService(communication, out _);
service.UpdateAvailability(true);
await WaitUntilAsync(() => service.AreAllScopesInitialized);
// Act
service.Stop();
// Assert
Assert.False(service.IsScopeInitialized(TileScope.Personal));
Assert.False(service.IsScopeInitialized(TileScope.Role));
Assert.All(service.CurrentCounts.Values, value =>
{
Assert.Equal(0, value.Personal);
Assert.Equal(0, value.Role);
});
}
private static TicketOverviewUpdateService CreateService(FakeCommunication communication, out FakeDispatcher dispatcher)
{
dispatcher = new FakeDispatcher();
var source = new FakeCommunicationSource(communication);
var settings = new FakeSettingsProvider();
return new TicketOverviewUpdateService(source, dispatcher, settings);
}
private static async Task WaitUntilAsync(Func<bool> condition, int timeoutMs = 2000)
{
var startedAt = DateTime.UtcNow;
while (!condition())
{
if ((DateTime.UtcNow - startedAt).TotalMilliseconds > timeoutMs)
{
throw new TimeoutException("Condition was not reached within timeout.");
}
await Task.Delay(10);
}
}
private sealed class FakeCommunicationSource : ITicketOverviewCommunicationSource
{
private readonly ITicketOverviewCommunication _communication;
public FakeCommunicationSource(ITicketOverviewCommunication communication)
{
_communication = communication;
}
public ITicketOverviewCommunication Resolve()
{
return _communication;
}
}
private sealed class FakeCommunication : ITicketOverviewCommunication
{
private Dictionary<string, int> _personalCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
private Dictionary<string, int> _roleCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
public bool IsDemo()
{
return false;
}
public Task<Dictionary<string, int>> GetTicketOverviewCounts(string[] overviewKeys, bool useRoleScope)
{
var source = useRoleScope ? _roleCounts : _personalCounts;
return Task.FromResult(new Dictionary<string, int>(source, StringComparer.OrdinalIgnoreCase));
}
public void RegisterGeneratedTicket(FasdCockpitCommunicationDemo.DemoTicketRecord record)
{
}
public void SetCounts(TileScope scope, Dictionary<string, int> counts)
{
var copy = counts == null
? new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
: new Dictionary<string, int>(counts, StringComparer.OrdinalIgnoreCase);
if (scope == TileScope.Role)
{
_roleCounts = copy;
}
else
{
_personalCounts = copy;
}
}
}
private sealed class FakeDispatcher : ITicketOverviewDispatcher
{
public Task InvokeAsync(Action action)
{
action();
return Task.CompletedTask;
}
public Task InvokeAsync(Func<Task> action)
{
return action();
}
public ITicketOverviewTimer CreateTimer(TimeSpan interval, Action tick)
{
return new FakeTimer(interval, tick);
}
}
private sealed class FakeSettingsProvider : ITicketOverviewSettingsProvider
{
public int GetPollingMinutes(TileScope scope)
{
return 1;
}
}
private sealed class FakeTimer : ITicketOverviewTimer
{
private readonly Action _tick;
public FakeTimer(TimeSpan interval, Action tick)
{
Interval = interval;
_tick = tick;
}
public TimeSpan Interval { get; set; }
public bool IsEnabled { get; private set; }
public void Start()
{
IsEnabled = true;
}
public void Stop()
{
IsEnabled = false;
}
public void Fire()
{
_tick?.Invoke();
}
}
}