Skip to content

Commit a836126

Browse files
committed
Move tests. Expand DotnetExtractDirectoryCleanupHostedServiceTests.
1 parent c53620d commit a836126

File tree

10 files changed

+163
-15
lines changed

10 files changed

+163
-15
lines changed

ControlR.Agent.Common/Services/DotnetExtractDirectoryCleanupHostedService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private void TryClearDotnetExtractDir(string agentTempDirBase)
7070

7171
var subdirs = _fileSystem
7272
.GetDirectories(agentTempDirBase)
73-
.Select(x => new DirectoryInfo(x))
73+
.Select(_fileSystem.GetDirectoryInfo)
7474
.OrderByDescending(x => x.CreationTime)
7575
.Skip(Math.Max(1, agentProcs))
7676
.ToArray();
@@ -80,7 +80,7 @@ private void TryClearDotnetExtractDir(string agentTempDirBase)
8080
try
8181
{
8282
_logger.LogInformation("Deleting .NET extract subdirectory {SubDir}.", subdir.FullName);
83-
subdir.Delete(true);
83+
_fileSystem.DeleteDirectory(subdir.FullName, recursive: true);
8484
}
8585
catch (Exception ex)
8686
{

Libraries/ControlR.Libraries.Shared/Services/FileSystem/FileSystemDirectoryInfo.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace ControlR.Libraries.Shared.Services.FileSystem;
33
public interface IFileSystemDirectory
44
{
55
FileAttributes Attributes { get; }
6+
DateTime CreationTime { get; }
67
bool Exists { get; }
78
string FullName { get; }
89
DateTime LastWriteTime { get; }
@@ -15,6 +16,8 @@ internal sealed class FileSystemDirectoryInfo(DirectoryInfo directoryInfo) : IFi
1516
{
1617
public FileAttributes Attributes => directoryInfo.Attributes;
1718

19+
public DateTime CreationTime => directoryInfo.CreationTime;
20+
1821
public bool Exists => directoryInfo.Exists;
1922

2023
public string FullName => directoryInfo.FullName;

Libraries/ControlR.Libraries.TestingUtilities/FileSystem/FakeFileSystem.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ public class FakeFileSystem(char directorySeparator = '/', bool isCaseSensitive
1717
private readonly Lock _syncRoot = new();
1818
private readonly Dictionary<string, string> _versionInfoSources = new(GetComparer(isCaseSensitive));
1919

20-
public void AddDirectory(string directoryPath, FileAttributes attributes = FileAttributes.Directory, DateTime? lastWriteTime = null)
20+
public void AddDirectory(
21+
string directoryPath,
22+
FileAttributes attributes = FileAttributes.Directory,
23+
DateTime? creationTime = null,
24+
DateTime? lastWriteTime = null)
2125
{
2226
lock (_syncRoot)
2327
{
2428
var normalizedPath = NormalizePath(directoryPath);
25-
EnsureDirectoryHierarchy(normalizedPath, attributes, lastWriteTime ?? DateTime.UtcNow);
29+
EnsureDirectoryHierarchy(normalizedPath, attributes, creationTime ?? DateTime.UtcNow, lastWriteTime ?? DateTime.UtcNow);
2630
}
2731
}
2832

@@ -755,13 +759,14 @@ private void EnsureDirectoryExists(string normalizedPath, string originalPath)
755759
}
756760
}
757761

758-
private void EnsureDirectoryHierarchy(string normalizedPath, FileAttributes? attributes = null, DateTime? lastWriteTime = null)
762+
private void EnsureDirectoryHierarchy(string normalizedPath, FileAttributes? attributes = null, DateTime? creationTime = null, DateTime? lastWriteTime = null)
759763
{
760764
var root = NormalizeRoot(normalizedPath);
761765
if (!string.IsNullOrEmpty(root) && !_directories.ContainsKey(root))
762766
{
763767
_directories[root] = new FakeDirectoryEntry(root)
764768
{
769+
CreationTime = creationTime ?? DateTime.UtcNow,
765770
LastWriteTime = lastWriteTime ?? DateTime.UtcNow
766771
};
767772
}
@@ -773,6 +778,7 @@ private void EnsureDirectoryHierarchy(string normalizedPath, FileAttributes? att
773778
if (_directories.TryGetValue(normalizedPath, out var rootDirectory))
774779
{
775780
rootDirectory.Attributes = attributes ?? rootDirectory.Attributes;
781+
rootDirectory.CreationTime = creationTime ?? rootDirectory.CreationTime;
776782
rootDirectory.LastWriteTime = lastWriteTime ?? rootDirectory.LastWriteTime;
777783
}
778784

@@ -789,6 +795,7 @@ private void EnsureDirectoryHierarchy(string normalizedPath, FileAttributes? att
789795
{
790796
_directories[current] = new FakeDirectoryEntry(current)
791797
{
798+
CreationTime = creationTime ?? DateTime.UtcNow,
792799
LastWriteTime = lastWriteTime ?? DateTime.UtcNow
793800
};
794801
continue;
@@ -800,6 +807,7 @@ private void EnsureDirectoryHierarchy(string normalizedPath, FileAttributes? att
800807
if (_directories.TryGetValue(normalizedPath, out var existingDirectory))
801808
{
802809
existingDirectory.Attributes = attributes ?? existingDirectory.Attributes;
810+
existingDirectory.CreationTime = creationTime ?? existingDirectory.CreationTime;
803811
existingDirectory.LastWriteTime = lastWriteTime ?? existingDirectory.LastWriteTime;
804812
}
805813
}
@@ -1021,6 +1029,7 @@ private IFileSystemDirectory ToDirectoryInfo(string normalizedPath, FakeDirector
10211029
return new FakeFileSystemDirectoryInfo(normalizedPath)
10221030
{
10231031
Attributes = directory?.Attributes ?? FileAttributes.Directory,
1032+
CreationTime = directory?.CreationTime ?? DateTime.MinValue,
10241033
Exists = directory is not null,
10251034
LastWriteTime = directory?.LastWriteTime ?? DateTime.MinValue,
10261035
Name = GetName(normalizedPath),
@@ -1084,6 +1093,8 @@ private sealed class FakeDirectoryEntry(string fullName)
10841093
{
10851094
public FileAttributes Attributes { get; set; } = FileAttributes.Directory;
10861095

1096+
public DateTime CreationTime { get; set; } = DateTime.UtcNow;
1097+
10871098
public string FullName { get; } = fullName;
10881099

10891100
public DateTime LastWriteTime { get; set; } = DateTime.UtcNow;

Libraries/ControlR.Libraries.TestingUtilities/FileSystem/FakeFileSystemInfo.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ public sealed class FakeFileSystemDirectoryInfo(string fullName) : IFileSystemDi
66
{
77
public FileAttributes Attributes { get; init; } = FileAttributes.Directory;
88

9+
public DateTime CreationTime { get; init; } = DateTime.UtcNow;
10+
911
public bool Exists { get; init; } = true;
1012

1113
public string FullName { get; init; } = fullName;

Tests/ControlR.Agent.Common.Tests/Services/AgentUpdaterTests.cs renamed to Tests/ControlR.Agent.Common.Tests/AgentUpdaterTests.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,16 @@
77
using ControlR.Libraries.Api.Contracts.Enums;
88
using ControlR.Libraries.Shared.Primitives;
99
using ControlR.Libraries.Shared.Services;
10-
using ControlR.Libraries.Shared.Services.FileSystem;
1110
using ControlR.Libraries.Shared.Services.Http;
1211
using ControlR.Libraries.Shared.Services.Processes;
1312
using ControlR.Libraries.TestingUtilities.FileSystem;
1413
using Microsoft.Extensions.Hosting;
1514
using Microsoft.Extensions.Logging.Abstractions;
1615
using Microsoft.Extensions.Options;
1716
using Moq;
18-
using System.Net;
1917
using System.Security.Cryptography;
2018

21-
namespace ControlR.Agent.Common.Tests.Services;
19+
namespace ControlR.Agent.Common.Tests;
2220

2321
public class AgentUpdaterTests
2422
{

Tests/ControlR.Agent.Common.Tests/Startup/DependencyResolutionTests.cs renamed to Tests/ControlR.Agent.Common.Tests/DependencyResolutionTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using ControlR.Agent.Common.Startup;
33
using Microsoft.Extensions.Hosting;
44

5-
namespace ControlR.Agent.Common.Tests.Startup;
5+
namespace ControlR.Agent.Common.Tests;
66

77
public class DependencyResolutionTests
88
{

Tests/ControlR.Agent.Common.Tests/Services/DesktopClientWatcherWinTests.cs renamed to Tests/ControlR.Agent.Common.Tests/DesktopClientWatcherWinTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
using Microsoft.Extensions.Time.Testing;
1515
using Moq;
1616

17-
namespace ControlR.Agent.Common.Tests.Services;
17+
namespace ControlR.Agent.Common.Tests;
1818

1919
[SupportedOSPlatform("windows8.0")]
2020
public class DesktopClientWatcherWinTests
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
using ControlR.Agent.Common.Services;
2+
using ControlR.Agent.Shared.Interfaces;
3+
using ControlR.Libraries.Api.Contracts.Enums;
4+
using ControlR.Libraries.Shared.Services;
5+
using ControlR.Libraries.Shared.Services.FileSystem;
6+
using ControlR.Libraries.Shared.Services.Processes;
7+
using ControlR.Libraries.TestingUtilities.FileSystem;
8+
using Microsoft.Extensions.Logging.Abstractions;
9+
using Moq;
10+
11+
namespace ControlR.Agent.Common.Tests;
12+
13+
public class DotnetExtractDirectoryCleanupHostedServiceTests
14+
{
15+
private const string WindowsExtractDirectory = @"C:\Windows\SystemTemp\.net\ControlR.Agent";
16+
17+
[Fact]
18+
public async Task StartAsync_Elevated_ContinuesWhenDirectoryDeleteFails()
19+
{
20+
var newest = $@"{WindowsExtractDirectory}\newest";
21+
var second = $@"{WindowsExtractDirectory}\second";
22+
var oldest = $@"{WindowsExtractDirectory}\oldest";
23+
var now = DateTime.UtcNow;
24+
25+
var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict);
26+
fileSystem.Setup(x => x.DirectoryExists(WindowsExtractDirectory)).Returns(true);
27+
fileSystem.Setup(x => x.GetDirectories(WindowsExtractDirectory)).Returns([oldest, second, newest]);
28+
fileSystem.Setup(x => x.GetDirectoryInfo(newest)).Returns(new FakeFileSystemDirectoryInfo(newest) { CreationTime = now.AddMinutes(-1) });
29+
fileSystem.Setup(x => x.GetDirectoryInfo(second)).Returns(new FakeFileSystemDirectoryInfo(second) { CreationTime = now.AddMinutes(-2) });
30+
fileSystem.Setup(x => x.GetDirectoryInfo(oldest)).Returns(new FakeFileSystemDirectoryInfo(oldest) { CreationTime = now.AddMinutes(-3) });
31+
fileSystem.Setup(x => x.DeleteDirectory(second, true)).Throws(new IOException("locked"));
32+
fileSystem.Setup(x => x.DeleteDirectory(oldest, true));
33+
34+
var processManager = new Mock<IProcessManager>(MockBehavior.Strict);
35+
processManager.Setup(x => x.GetProcessesByName("ControlR.Agent")).Returns([]);
36+
37+
var service = CreateService(
38+
fileSystem.Object,
39+
processManager.Object,
40+
CreateElevationChecker(true).Object,
41+
CreateSystemEnvironment(SystemPlatform.Windows).Object);
42+
43+
await service.StartAsync(CancellationToken.None);
44+
45+
fileSystem.Verify(x => x.DeleteDirectory(second, true), Times.Once);
46+
fileSystem.Verify(x => x.DeleteDirectory(oldest, true), Times.Once);
47+
fileSystem.Verify(x => x.DeleteDirectory(newest, true), Times.Never);
48+
}
49+
50+
[Fact]
51+
public async Task StartAsync_Elevated_DeletesOldestSubdirectoriesUsingFakeFileSystem()
52+
{
53+
var fileSystem = new FakeFileSystem('\\');
54+
var now = DateTime.UtcNow;
55+
fileSystem.AddDirectory(WindowsExtractDirectory);
56+
fileSystem.AddDirectory($@"{WindowsExtractDirectory}\oldest", creationTime: now.AddMinutes(-3), lastWriteTime: now.AddMinutes(-3));
57+
fileSystem.AddDirectory($@"{WindowsExtractDirectory}\second", creationTime: now.AddMinutes(-2), lastWriteTime: now.AddMinutes(-2));
58+
fileSystem.AddDirectory($@"{WindowsExtractDirectory}\newest", creationTime: now.AddMinutes(-1), lastWriteTime: now.AddMinutes(-1));
59+
60+
var processManager = new Mock<IProcessManager>(MockBehavior.Strict);
61+
processManager
62+
.Setup(x => x.GetProcessesByName("ControlR.Agent"))
63+
.Returns([Mock.Of<IProcess>()]);
64+
65+
var service = CreateService(
66+
fileSystem,
67+
processManager.Object,
68+
CreateElevationChecker(true).Object,
69+
CreateSystemEnvironment(SystemPlatform.Windows).Object);
70+
71+
await service.StartAsync(CancellationToken.None);
72+
73+
Assert.False(fileSystem.DirectoryExists($@"{WindowsExtractDirectory}\oldest"));
74+
Assert.True(fileSystem.DirectoryExists($@"{WindowsExtractDirectory}\second"));
75+
Assert.True(fileSystem.DirectoryExists($@"{WindowsExtractDirectory}\newest"));
76+
}
77+
78+
[Fact]
79+
public async Task StartAsync_NotElevated_DoesNotAccessProcessManager()
80+
{
81+
var fileSystem = new FakeFileSystem('\\');
82+
var processManager = new Mock<IProcessManager>(MockBehavior.Strict);
83+
var service = CreateService(
84+
fileSystem,
85+
processManager.Object,
86+
CreateElevationChecker(false).Object,
87+
CreateSystemEnvironment(SystemPlatform.Windows).Object);
88+
89+
await service.StartAsync(CancellationToken.None);
90+
91+
processManager.Verify(x => x.GetProcessesByName(It.IsAny<string>()), Times.Never);
92+
}
93+
94+
[Fact]
95+
public async Task StartAsync_UnknownPlatform_DoesNotTryCleanup()
96+
{
97+
var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict);
98+
var processManager = new Mock<IProcessManager>(MockBehavior.Strict);
99+
var service = CreateService(
100+
fileSystem.Object,
101+
processManager.Object,
102+
CreateElevationChecker(true).Object,
103+
CreateSystemEnvironment(SystemPlatform.Unknown).Object);
104+
105+
await service.StartAsync(CancellationToken.None);
106+
107+
processManager.Verify(x => x.GetProcessesByName(It.IsAny<string>()), Times.Never);
108+
}
109+
110+
private static Mock<IElevationChecker> CreateElevationChecker(bool isElevated)
111+
{
112+
var elevationChecker = new Mock<IElevationChecker>(MockBehavior.Strict);
113+
elevationChecker.Setup(x => x.IsElevated()).Returns(isElevated);
114+
return elevationChecker;
115+
}
116+
117+
private static DotnetExtractDirectoryCleanupHostedService CreateService(
118+
IFileSystem fileSystem,
119+
IProcessManager processManager,
120+
IElevationChecker elevationChecker,
121+
ISystemEnvironment systemEnvironment)
122+
{
123+
return new DotnetExtractDirectoryCleanupHostedService(
124+
fileSystem,
125+
processManager,
126+
elevationChecker,
127+
systemEnvironment,
128+
NullLogger<DotnetExtractDirectoryCleanupHostedService>.Instance);
129+
}
130+
131+
private static Mock<ISystemEnvironment> CreateSystemEnvironment(SystemPlatform platform)
132+
{
133+
var systemEnvironment = new Mock<ISystemEnvironment>(MockBehavior.Strict);
134+
systemEnvironment.Setup(x => x.Platform).Returns(platform);
135+
return systemEnvironment;
136+
}
137+
}

Tests/ControlR.Agent.Common.Tests/Services/FileSystemPathProviderTests.cs renamed to Tests/ControlR.Agent.Common.Tests/FileSystemPathProviderTests.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44
using ControlR.Libraries.Api.Contracts.Enums;
55
using ControlR.Libraries.Shared.Helpers;
66
using ControlR.Libraries.Shared.Services;
7-
using ControlR.Libraries.TestingUtilities;
87
using ControlR.Libraries.TestingUtilities.FileSystem;
98
using Moq;
10-
using Xunit;
119

12-
namespace ControlR.Agent.Common.Tests.Services;
10+
namespace ControlR.Agent.Common.Tests;
1311

1412
public class FileSystemPathProviderTests(ITestOutputHelper testOutputHelper)
1513
{

Tests/ControlR.Agent.Common.Tests/Services/IpcClientAuthenticatorTests.cs renamed to Tests/ControlR.Agent.Common.Tests/IpcClientAuthenticatorTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using ControlR.Agent.Common.Interfaces;
22
using ControlR.Agent.Common.Services;
33
using ControlR.Libraries.Ipc;
4-
using ControlR.Libraries.Api.Contracts.Constants;
54
using ControlR.Libraries.Shared.Constants;
65
using ControlR.Libraries.Shared.Primitives;
76
using ControlR.Libraries.Shared.Services;
@@ -11,7 +10,7 @@
1110
using ControlR.Libraries.Api.Contracts.Dtos.IpcDtos;
1211
using ControlR.Agent.Shared.Services;
1312

14-
namespace ControlR.Agent.Common.Tests.Services;
13+
namespace ControlR.Agent.Common.Tests;
1514

1615
public class IpcClientAuthenticatorTests
1716
{

0 commit comments

Comments
 (0)