Testing und interfaces
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user