Finish implementing Serilog, LogManager, and full-screen loader.

This commit is contained in:
Jared Goodwin 2023-05-22 15:43:45 -07:00
parent d1c432efd9
commit db19e92823
22 changed files with 465 additions and 210 deletions

View File

@ -14,7 +14,6 @@ namespace Remotely.Server.API
[ApiController]
public class ServerLogsController : ControllerBase
{
private readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true };
private readonly ILogsManager _logsManager;
private readonly ILogger<ServerLogsController> _logger;

View File

@ -0,0 +1,29 @@
@using Nihs.SimpleMessenger;
@using Remotely.Server.Models.Messages;
@inject IMessenger Messenger
@if (_loaderShown)
{
<LoadingSignal StatusMessage="@_statusMessage" />
}
@code {
private bool _loaderShown;
private string _statusMessage = string.Empty;
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Messenger.Register<ShowLoaderMessage>(this, HandleShowLoaderMessage);
}
return base.OnAfterRenderAsync(firstRender);
}
private async Task HandleShowLoaderMessage(ShowLoaderMessage message)
{
_loaderShown = message.IsShown;
_statusMessage = message.StatusMessage;
await InvokeAsync(StateHasChanged);
}
}

View File

@ -1,21 +1,31 @@
@inject IClientAppState AppState
@if (_theme == Theme.Dark)
{
<div class="signal" style="border-color: whitesmoke"></div>
}
else if (_theme == Theme.Light)
{
<div class="signal" style="border-color: #333"></div>
}
<div class="signal-background">
<div class="signal-wrapper text-center">
@if (!string.IsNullOrEmpty(StatusMessage))
{
<h4>
@StatusMessage
</h4>
}
<div class="signal @(GetSignalClass())"></div>
</div>
</div>
@code {
private Theme _theme;
[Parameter]
public string StatusMessage { get; set; } = string.Empty;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
_theme = await AppState.GetEffectiveTheme();
await base.OnInitializedAsync();
}
private string GetSignalClass()
{
return _theme == Theme.Dark ? "signal-dark" : "signal-light";
}
}

View File

@ -1,18 +1,42 @@
.signal {
.signal-background {
position: fixed;
top: 45vh;
left: calc(50% - 25px);
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background-color: rgba(0,0,0,0.5);
z-index: 1000;
}
.signal-wrapper {
position: absolute;
display: inline-block;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.signal {
display: inline-block;
border-width: 8px;
border-style: solid;
margin-top: 10px;
border-radius: 100%;
height: 50px;
width: 50px;
opacity: 0;
position: absolute;
transform-origin: center;
animation: pulsate .85s ease-out;
animation-iteration-count: infinite;
}
.signal.signal-dark {
border-color: whitesmoke;
}
.signal.signal-light {
border-color: #333;
}
@keyframes pulsate {
0% {
transform: scale(.1);

View File

@ -49,8 +49,8 @@
{
_displayStyle = "block";
await InvokeAsync(StateHasChanged);
// The fade animation won't work without a delay here.
await Task.Delay(100);
// The fade animation won't work without a delay here.
await Task.Delay(100);
_showClass = "show";
await InvokeAsync(StateHasChanged);
};

View File

@ -57,9 +57,6 @@ namespace Remotely.Server.Data
builder.Entity<Organization>()
.HasMany(x => x.RemotelyUsers)
.WithOne(x => x.Organization);
builder.Entity<Organization>()
.HasMany(x => x.EventLogs)
.WithOne(x => x.Organization);
builder.Entity<Organization>()
.HasMany(x => x.DeviceGroups)
.WithOne(x => x.Organization);

View File

@ -0,0 +1,13 @@
namespace Remotely.Server.Models.Messages;
public class ShowLoaderMessage
{
public ShowLoaderMessage(bool isShown, string statusMessage)
{
IsShown = isShown;
StatusMessage = statusMessage;
}
public bool IsShown { get; }
public string StatusMessage { get; }
}

View File

@ -1,4 +1,5 @@
@page "/server-logs"
@using System.Collections.ObjectModel;
@attribute [Authorize]
@inherits AuthComponentBase
@ -6,6 +7,7 @@
@inject IToastService ToastService
@inject IJsInterop JsInterop
@inject ILogsManager LogsManager
@inject ILoaderService LoaderService
<h3 class="mb-3">Server Logs</h3>
@ -35,9 +37,9 @@
<div style="display:inline-block">
<strong>Type:</strong>
<br />
<select class="form-control-sm" @bind="_eventType">
<select class="form-control-sm" @bind="LogLevel">
<option value="">All</option>
@foreach (var eventType in Enum.GetValues(typeof(EventType)))
@foreach (var eventType in Enum.GetValues(typeof(LogLevel)))
{
<option @key="eventType" value="@eventType">@eventType</option>
}
@ -46,51 +48,23 @@
<div style="display:inline-block">
<strong>Filter:</strong>
<br />
<input type="text" @bind="_messageFilter" />
<input type="text" @bind="MessageFilter" @bind:event="oninput" />
</div>
<div style="display:inline-block">
<strong>From:</strong>
<br />
<input type="date" @bind="_fromDate" />
<input type="date" @bind="FromDate" />
</div>
<div style="display:inline-block">
<strong>To:</strong>
<br />
<input type="date" @bind-value="_toDate" />
<input type="date" @bind="ToDate" />
</div>
</div>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Type</th>
<th>Timestamp</th>
<th>Message</th>
<th>Source</th>
<th>Stack Trace</th>
</tr>
</thead>
<tbody>
@foreach (var eventLog in FilteredLogs)
{
<tr @key="eventLog">
<td>@eventLog.EventType</td>
<td>@eventLog.TimeStamp</td>
<td>
@if (!string.IsNullOrWhiteSpace(eventLog.Message))
{
foreach (var line in eventLog.Message.Split("\n", StringSplitOptions.RemoveEmptyEntries))
{
<div>@line</div>
}
}
</td>
<td>@eventLog.Source</td>
<td>@eventLog.StackTrace</td>
</tr>
}
</tbody>
</table>
<textarea readonly class="logs-content bg-dark text-white">
@(string.Join(Environment.NewLine, _filteredLogs))
</textarea>
}
else
{
@ -98,35 +72,104 @@ else
}
@code {
private readonly List<EventLog> _filteredLogs = new();
private EventType? _eventType;
private string _messageFilter;
private readonly string _messageDebounceKey = Guid.NewGuid().ToString();
private readonly List<string> _filteredLogs = new();
private LogLevel? _logLevel;
private string _messageFilter = string.Empty;
private DateTimeOffset _fromDate = DateTimeOffset.Now.AddDays(-7);
private DateTimeOffset _toDate = DateTimeOffset.Now;
private IEnumerable<EventLog> FilteredLogs
private DateTimeOffset FromDate
{
get
get => _fromDate;
set
{
return Enumerable.Empty<EventLog>();
//return DataService.GetEventLogs(User.UserName,
// _fromDate,
// _toDate,
// _eventType,
// _messageFilter);
if (value > _toDate)
{
ToastService.ShowToast("Invalid date range.", classString: "bg-warning");
return;
}
_fromDate = value;
_ = RefreshLogs();
}
}
private LogLevel? LogLevel
{
get => _logLevel;
set
{
_logLevel = value;
_ = RefreshLogs();
}
}
private string MessageFilter
{
get => _messageFilter;
set
{
_messageFilter = value;
Debouncer.Debounce(
TimeSpan.FromSeconds(1),
() => _ = RefreshLogs(),
_messageDebounceKey);
}
}
private DateTimeOffset ToDate
{
get => _toDate;
set
{
if (value < _fromDate)
{
ToastService.ShowToast("Invalid date range.", classString: "bg-warning");
return;
}
_toDate = value;
_ = RefreshLogs();
}
}
protected override async Task OnInitializedAsync()
{
await RefreshLogs();
await base.OnInitializedAsync();
}
private async Task ClearAllLogs()
{
var result = await JsInterop.Confirm("Are you sure you want to delete all logs?");
if (result)
{
await LogsManager.DeleteLogs();
using (var _ = LoaderService.ShowLoader("Deleting logs"))
{
await LogsManager.DeleteLogs();
}
await RefreshLogs();
ToastService.ShowToast("Logs deleted.");
}
}
private async Task RefreshLogs()
{
using var _ = await LoaderService.ShowLoader("Refreshing logs");
_filteredLogs.Clear();
var logsAsync = LogsManager.GetLogs(
_fromDate,
_toDate,
_messageFilter,
_logLevel);
await foreach (var line in logsAsync)
{
_filteredLogs.Add(line);
}
await InvokeAsync(StateHasChanged);
}
}

View File

@ -4,13 +4,18 @@
grid-column-gap: 2em;
}
.filters-row {
display: grid;
grid-template-columns: auto auto auto 1fr;
grid-column-gap: 1em;
}
.logs-content {
width: 100%;
white-space: pre;
height: 500px;
}
@media (max-width: 641px) {
.buttons-row {

View File

@ -36,6 +36,7 @@ using Remotely.Shared.Services;
using System;
using Immense.RemoteControl.Server.Services;
using Serilog;
using Nihs.SimpleMessenger;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
@ -43,8 +44,6 @@ var services = builder.Services;
ConfigureSerilog(builder);
builder.Host.UseSerilog();
builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging"));
if (OperatingSystem.IsWindows() &&
@ -190,13 +189,14 @@ services.AddSingleton<IApplicationConfig, ApplicationConfig>();
services.AddScoped<ApiAuthorizationFilter>();
services.AddScoped<LocalOnlyFilter>();
services.AddScoped<ExpiringTokenFilter>();
services.AddHostedService<DbCleanupService>();
services.AddHostedService<DataCleanupService>();
services.AddHostedService<ScriptScheduler>();
services.AddSingleton<IUpgradeService, UpgradeService>();
services.AddScoped<IToastService, ToastService>();
services.AddScoped<IModalService, ModalService>();
services.AddScoped<IJsInterop, JsInterop>();
services.AddScoped<ICircuitConnection, CircuitConnection>();
services.AddScoped<ILoaderService, LoaderService>();
services.AddScoped(x => (CircuitHandler)x.GetRequiredService<ICircuitConnection>());
services.AddSingleton<ICircuitManager, CircuitManager>();
services.AddScoped<IAuthService, AuthService>();
@ -206,6 +206,7 @@ services.AddScoped<IScriptScheduleDispatcher, ScriptScheduleDispatcher>();
services.AddSingleton<IOtpProvider, OtpProvider>();
services.AddSingleton<IEmbeddedServerDataSearcher, EmbeddedServerDataSearcher>();
services.AddSingleton<ILogsManager>(LogsManager.Default);
services.AddSingleton(WeakReferenceMessenger.Default);
services.AddRemoteControlServer(config =>
{
@ -336,9 +337,11 @@ void ConfigureSerilog(WebApplicationBuilder webAppBuilder)
{
loggerConfiguration
.Enrich.FromLogContext()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File($"{logPath}/Remotely_Server.log", rollingInterval: RollingInterval.Day, retainedFileTimeLimit: TimeSpan.FromDays(dataRetentionDays));
.WriteTo.File($"{logPath}/Remotely_Server.log",
rollingInterval: RollingInterval.Day,
retainedFileTimeLimit: TimeSpan.FromDays(dataRetentionDays),
shared: true);
}
var loggerConfig = new LoggerConfiguration();

View File

@ -31,6 +31,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="7.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.6" />
<PackageReference Include="Nihs.SimpleMessenger" Version="1.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />

View File

@ -0,0 +1,62 @@
using Microsoft.Build.Framework;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Remotely.Server.Services
{
public class DataCleanupService : IHostedService, IDisposable
{
private readonly ILogger<DataCleanupService> _logger;
private readonly IServiceProvider _services;
private System.Timers.Timer _cleanupTimer = new(TimeSpan.FromDays(1));
public DataCleanupService(
IServiceProvider serviceProvider,
ILogger<DataCleanupService> logger)
{
_services = serviceProvider;
_logger = logger;
_cleanupTimer.Elapsed += CleanupTimer_Elapsed;
}
public void Dispose()
{
_cleanupTimer?.Dispose();
GC.SuppressFinalize(this);
}
public Task StartAsync(CancellationToken cancellationToken)
{
_cleanupTimer.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_cleanupTimer.Stop();
_cleanupTimer.Dispose();
return Task.CompletedTask;
}
private void CleanupTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
try
{
using var scope = _services.CreateScope();
var dataService = scope.ServiceProvider.GetRequiredService<IDataService>();
dataService.CleanupOldRecords();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during data cleanup.");
}
}
}
}

View File

@ -1,48 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Remotely.Server.Services
{
public class DbCleanupService : IHostedService, IDisposable
{
public DbCleanupService(IServiceProvider serviceProvider)
{
Services = serviceProvider;
}
private IServiceProvider Services { get; }
private System.Timers.Timer CleanupTimer { get; set; }
public void Dispose()
{
CleanupTimer?.Dispose();
GC.SuppressFinalize(this);
}
public Task StartAsync(CancellationToken cancellationToken)
{
CleanupTimer?.Dispose();
CleanupTimer = new System.Timers.Timer(TimeSpan.FromDays(1).TotalMilliseconds);
CleanupTimer.Elapsed += CleanupTimer_Elapsed;
CleanupTimer.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
CleanupTimer?.Dispose();
return Task.CompletedTask;
}
private void CleanupTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
using var scope = Services.CreateScope();
var dataService = scope.ServiceProvider.GetRequiredService<IDataService>();
dataService.CleanupOldRecords();
}
}
}

View File

@ -0,0 +1,34 @@
using Nihs.SimpleMessenger;
using Remotely.Server.Models.Messages;
using Remotely.Shared.Primitives;
using System;
using System.Threading.Tasks;
namespace Remotely.Server.Services;
public interface ILoaderService
{
Task<IDisposable> ShowLoader(string statusMessage);
void HideLoader();
}
public class LoaderService : ILoaderService
{
private readonly IMessenger _messenger;
public LoaderService(IMessenger messenger)
{
_messenger = messenger;
}
public async Task<IDisposable> ShowLoader(string statusMessage)
{
await _messenger.Send(new ShowLoaderMessage(true, statusMessage));
return new CallbackDisposable(HideLoader);
}
public void HideLoader()
{
_messenger.Send(new ShowLoaderMessage(false, string.Empty));
}
}

View File

@ -1,9 +1,16 @@
using Remotely.Shared.Extensions;
using Microsoft.Extensions.Logging;
using Remotely.Shared.Extensions;
using Serilog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely.Server.Services
@ -13,6 +20,11 @@ namespace Remotely.Server.Services
string GetLogsDirectory();
Task<FileInfo> ZipAllLogs();
Task DeleteLogs();
IAsyncEnumerable<string> GetLogs(
DateTimeOffset startDate,
DateTimeOffset endDate,
string messageFilter,
LogLevel? logLevelFilter);
}
public class LogsManager : ILogsManager
@ -72,9 +84,123 @@ namespace Remotely.Server.Services
}
catch (Exception ex)
{
Console.WriteLine("Failed to delete log file: {filename}. Message: {exMessage}", file, ex.Message);
Console.WriteLine($"Failed to delete log file: {file}. Message: {ex.Message}");
try
{
Console.WriteLine("Attempting to zero out log contents.");
using var fs = File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
fs.SetLength(0);
}
catch (Exception ex2)
{
Console.WriteLine($"Failed to clear log contents: {file}. Message: {ex2.Message}");
}
}
}
}
public async IAsyncEnumerable<string> GetLogs(
DateTimeOffset startDate,
DateTimeOffset endDate,
string messageFilter,
LogLevel? logLevelFilter)
{
var fromDate = startDate.UtcDateTime.Date;
var toDate = endDate.UtcDateTime.Date.AddDays(1);
var result = new StringBuilder();
var logsDir = GetLogsDirectory();
var files = Directory
.GetFiles(logsDir)
.Select(x => new FileInfo(x))
.Where(x =>
x.LastWriteTimeUtc >= fromDate &&
x.LastWriteTimeUtc <= toDate);
foreach (var file in files)
{
var linesAsync = GetLines(file, messageFilter, logLevelFilter);
await foreach (var line in linesAsync)
{
yield return line;
}
}
}
private async IAsyncEnumerable<string> GetLines(
FileInfo file,
string messageFilter,
LogLevel? logLevelFilter)
{
LogLevel? currentLogLevel = null;
using var fs = File.Open(file.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var sr = new StreamReader(fs);
while (true)
{
var currentLine = await sr.ReadLineAsync();
if (currentLine is null)
{
break;
}
if (logLevelFilter is not null)
{
if (TryGetLogLevel(currentLine, out var parsedLevel))
{
currentLogLevel = parsedLevel;
}
if (currentLogLevel != logLevelFilter)
{
continue;
}
}
if (!string.IsNullOrWhiteSpace(messageFilter) &&
!currentLine.Contains(messageFilter, StringComparison.OrdinalIgnoreCase))
{
continue;
}
yield return currentLine;
}
}
private bool TryGetLogLevel(
string lineContent,
[NotNullWhen(true)] out LogLevel? logLevel)
{
try
{
var logLevelTag = lineContent[31..36];
if (_logLevelMap.TryGetValue(logLevelTag, out var result))
{
logLevel = result;
return true;
}
}
catch
{
// Ignored.
}
logLevel = default;
return false;
}
private static readonly ReadOnlyDictionary<string, LogLevel> _logLevelMap = new(new Dictionary<string, LogLevel>()
{
["[VRB]"] = LogLevel.Trace,
["[DBG]"] = LogLevel.Debug,
["[INF]"] = LogLevel.Information,
["[WRN]"] = LogLevel.Warning,
["[ERR]"] = LogLevel.Error,
["[FTL]"] = LogLevel.Critical
});
}
}

View File

@ -38,13 +38,13 @@ namespace Remotely.Server.Services
{
try
{
_logger.LogInformation("Script Schedule Dispatcher started.");
_logger.LogDebug("Script Schedule Dispatcher started.");
var schedules = await _dataService.GetScriptSchedulesDue();
if (schedules?.Any() != true)
{
_logger.LogInformation("No schedules are due.");
_logger.LogDebug("No schedules are due.");
return;
}
@ -52,18 +52,18 @@ namespace Remotely.Server.Services
{
try
{
_logger.LogInformation("Considering {scheduleName}. Interval: {interval}. Next Run: {nextRun}.",
_logger.LogDebug("Considering {scheduleName}. Interval: {interval}. Next Run: {nextRun}.",
schedule.Name,
schedule.Interval,
schedule.NextRun);
if (!AdvanceSchedule(schedule))
{
_logger.LogInformation("Schedule is not due.");
_logger.LogDebug("Schedule is not due.");
continue;
}
_logger.LogInformation("Creating script run for schedule {scheduleName}.", schedule.Name);
_logger.LogDebug("Creating script run for schedule {scheduleName}.", schedule.Name);
var scriptRun = new ScriptRun()
{
@ -100,7 +100,7 @@ namespace Remotely.Server.Services
await _circuitConnection.RunScript(onlineDevices, schedule.SavedScriptId, scriptRun.Id, ScriptInputType.ScheduledScript, true);
_logger.LogInformation("Created script run for schedule {scheduleName}.", schedule.Name);
_logger.LogDebug("Created script run for schedule {scheduleName}.", schedule.Name);
schedule.LastRun = Time.Now;
await _dataService.AddOrUpdateScriptSchedule(schedule);

View File

@ -16,7 +16,7 @@
</div>
<ToastHarness />
<LoaderHarness />
<ModalHarness />
</div>
</Authorized>

View File

@ -83,14 +83,14 @@
<span class="oi oi-key" aria-hidden="true"></span> API Keys
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="server-logs">
<span class="oi oi-document" aria-hidden="true"></span> Server Logs
</NavLink>
</li>
@if (_user?.IsServerAdmin == true)
{
<li class="nav-item px-3">
<NavLink class="nav-link" href="server-logs">
<span class="oi oi-document" aria-hidden="true"></span> Server Logs
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="server-config">
<span class="oi oi-wrench" aria-hidden="true"></span> Server Config

View File

@ -10,6 +10,15 @@
"Default": "Information"
}
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
}
},
"ApplicationOptions": {
"AllowApiLogin": false,
"BannedDevices": [],

View File

@ -1,23 +0,0 @@
using Remotely.Shared.Enums;
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
namespace Remotely.Shared.Models
{
public class EventLog
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string ID { get; set; }
public EventType EventType { get; set; }
public string Message { get; set; }
public string Source { get; set; }
public string StackTrace { get; set; }
public string OrganizationID { get; set; }
public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.Now;
[JsonIgnore]
public Organization Organization { get; set; }
}
}

View File

@ -26,8 +26,6 @@ namespace Remotely.Shared.Models
public ICollection<Device> Devices { get; set; }
public ICollection<EventLog> EventLogs { get; set; }
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string ID { get; set; }

View File

@ -39,10 +39,18 @@ namespace Remotely.Tests
var viewerHub = new Mock<IHubContext<ViewerHub>>();
var expiringTokenService = new Mock<IExpiringTokenService>();
var serviceSessionCache = new Mock<IServiceHubSessionCache>();
var logger = new Mock<ILogger<AgentHub>>();
appConfig.Setup(x => x.BannedDevices).Returns(new string[] { _testData.Device1.DeviceName });
var hub = new AgentHub(DataService, appConfig.Object, serviceSessionCache.Object, viewerHub.Object, circuitManager.Object, expiringTokenService.Object);
var hub = new AgentHub(
DataService,
appConfig.Object,
serviceSessionCache.Object,
viewerHub.Object,
circuitManager.Object,
expiringTokenService.Object,
logger.Object);
var hubClients = new Mock<IHubCallerClients>();
var caller = new Mock<ISingleClientProxy>();
@ -54,6 +62,8 @@ namespace Remotely.Tests
caller.Verify(x => x.SendCoreAsync("UninstallAgent", It.IsAny<object[]>(), It.IsAny<CancellationToken>()), Times.Once);
}
// TODO: Checking of device ban should be pulled out into
// a separate service that's better testable.
[TestMethod]
[DoNotParallelize]
public async Task DeviceCameOnline_BannedById()
@ -66,10 +76,18 @@ namespace Remotely.Tests
var viewerHub = new Mock<IHubContext<ViewerHub>>();
var expiringTokenService = new Mock<IExpiringTokenService>();
var serviceSessionCache = new Mock<IServiceHubSessionCache>();
var logger = new Mock<ILogger<AgentHub>>();
appConfig.Setup(x => x.BannedDevices).Returns(new string[] { _testData.Device1.ID });
var hub = new AgentHub(DataService, appConfig.Object, serviceSessionCache.Object, viewerHub.Object, circuitManager.Object, expiringTokenService.Object);
var hub = new AgentHub(
DataService,
appConfig.Object,
serviceSessionCache.Object,
viewerHub.Object,
circuitManager.Object,
expiringTokenService.Object,
logger.Object);
var hubClients = new Mock<IHubCallerClients>();
var caller = new Mock<ISingleClientProxy>();
@ -81,51 +99,6 @@ namespace Remotely.Tests
caller.Verify(x => x.SendCoreAsync("UninstallAgent", It.IsAny<object[]>(), It.IsAny<CancellationToken>()), Times.Once);
}
[TestMethod]
[DoNotParallelize]
public async Task DeviceCameOnline_NotBanned()
{
var appConfig = new Mock<IApplicationConfig>();
var circuitManager = new Mock<ICircuitManager>();
var circuitConnection = new Mock<ICircuitConnection>();
circuitManager.Setup(x => x.Connections).Returns(new[] { circuitConnection.Object });
circuitConnection.Setup(x => x.User).Returns(_testData.Admin1);
var browserHubClients = new Mock<IHubClients>();
var expiringTokenService = new Mock<IExpiringTokenService>();
var serviceSessionCache = new Mock<IServiceHubSessionCache>();
var viewerHub = new Mock<IHubContext<ViewerHub>>();
appConfig.Setup(x => x.BannedDevices).Returns(Array.Empty<string>());
var hub = new AgentHub(DataService, appConfig.Object, serviceSessionCache.Object, viewerHub.Object, circuitManager.Object, expiringTokenService.Object)
{
Context = new CallerContext()
};
var agentHubClients = new Mock<IHubCallerClients>();
var agentHubCaller = new Mock<ISingleClientProxy>();
var agentClientsProxy = new Mock<IClientProxy>();
agentHubClients.Setup(x => x.Caller).Returns(agentHubCaller.Object);
agentHubClients.Setup(x => x.Clients(It.IsAny<IReadOnlyList<string>>())).Returns(agentClientsProxy.Object);
hub.Clients = agentHubClients.Object;
var result = await hub.DeviceCameOnline(_testData.Device1);
Assert.IsTrue(result);
agentHubClients.Verify(x => x.Caller, Times.Never);
agentHubCaller.Verify(x => x.SendCoreAsync("UninstallAgent", It.IsAny<object[]>(), It.IsAny<CancellationToken>()), Times.Never);
circuitConnection.Verify(x => x.InvokeCircuitEvent(
CircuitEventName.DeviceUpdate,
It.Is<Device>(x => x.ID == _testData.Device1.ID)),
Times.Once);
}
[TestCleanup]
public void TestCleanup()
{