Skip to content

Commit 4bd4ba8

Browse files
committed
SEBWIN-686: Extended unit test coverage for runtime component.
1 parent 8ab4a74 commit 4bd4ba8

16 files changed

Lines changed: 211 additions & 45 deletions
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright (c) 2025 ETH Zürich, IT Services
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
*/
8+
9+
using Microsoft.VisualStudio.TestTools.UnitTesting;
10+
using Moq;
11+
using SafeExamBrowser.Configuration.Contracts.Integrity;
12+
using SafeExamBrowser.Core.Contracts.OperationModel;
13+
using SafeExamBrowser.Logging.Contracts;
14+
using SafeExamBrowser.Runtime.Operations.Bootstrap;
15+
16+
namespace SafeExamBrowser.Runtime.UnitTests.Operations.Bootstrap
17+
{
18+
[TestClass]
19+
public class ApplicationIntegrityOperationTests
20+
{
21+
private Mock<IIntegrityModule> integrityModule;
22+
private Mock<ILogger> logger;
23+
24+
private ApplicationIntegrityOperation sut;
25+
26+
[TestInitialize]
27+
public void Initialize()
28+
{
29+
integrityModule = new Mock<IIntegrityModule>();
30+
logger = new Mock<ILogger>();
31+
32+
sut = new ApplicationIntegrityOperation(integrityModule.Object, logger.Object);
33+
}
34+
35+
[TestMethod]
36+
public void Perform_MustVerifyCodeSignature()
37+
{
38+
var isValid = true;
39+
40+
integrityModule.Setup(m => m.TryVerifyCodeSignature(out isValid)).Returns(true);
41+
42+
var result = sut.Perform();
43+
44+
integrityModule.Verify(m => m.TryVerifyCodeSignature(out isValid), Times.Once);
45+
logger.Verify(l => l.Info(It.IsAny<string>()), Times.AtLeastOnce);
46+
47+
Assert.AreEqual(OperationResult.Success, result);
48+
}
49+
50+
[TestMethod]
51+
public void Perform_MustLogCompromise()
52+
{
53+
var isValid = false;
54+
55+
integrityModule.Setup(m => m.TryVerifyCodeSignature(out isValid)).Returns(true);
56+
57+
var result = sut.Perform();
58+
59+
integrityModule.Verify(m => m.TryVerifyCodeSignature(out isValid), Times.Once);
60+
logger.Verify(l => l.Warn(It.IsAny<string>()), Times.Once);
61+
62+
Assert.AreEqual(OperationResult.Success, result);
63+
}
64+
65+
[TestMethod]
66+
public void Perform_MustLogFailure()
67+
{
68+
var isValid = false;
69+
70+
integrityModule.Setup(m => m.TryVerifyCodeSignature(out isValid)).Returns(false);
71+
72+
var result = sut.Perform();
73+
74+
integrityModule.Verify(m => m.TryVerifyCodeSignature(out isValid), Times.Once);
75+
logger.Verify(l => l.Warn(It.IsAny<string>()), Times.Once);
76+
77+
Assert.AreEqual(OperationResult.Success, result);
78+
}
79+
80+
[TestMethod]
81+
public void Revert_MustDoNothing()
82+
{
83+
var result = sut.Revert();
84+
85+
integrityModule.VerifyNoOtherCalls();
86+
logger.VerifyNoOtherCalls();
87+
88+
Assert.AreEqual(OperationResult.Success, result);
89+
}
90+
}
91+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright (c) 2025 ETH Zürich, IT Services
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
*/
8+
9+
using Microsoft.VisualStudio.TestTools.UnitTesting;
10+
using Moq;
11+
using SafeExamBrowser.Core.Contracts.OperationModel;
12+
using SafeExamBrowser.I18n.Contracts;
13+
using SafeExamBrowser.Logging.Contracts;
14+
using SafeExamBrowser.Runtime.Operations.Bootstrap;
15+
using SafeExamBrowser.UserInterface.Contracts.Windows;
16+
17+
namespace SafeExamBrowser.Runtime.UnitTests.Operations.Bootstrap
18+
{
19+
[TestClass]
20+
public class BootstrapOperationSequenceTests
21+
{
22+
private Mock<ILogger> logger;
23+
private Mock<IOperation> operationA;
24+
private Mock<IOperation> operationB;
25+
private IOperation[] operations;
26+
private Mock<ISplashScreen> splashScreen;
27+
28+
private BootstrapOperationSequence sut;
29+
30+
[TestInitialize]
31+
public void Initialize()
32+
{
33+
logger = new Mock<ILogger>();
34+
operationA = new Mock<IOperation>();
35+
operationB = new Mock<IOperation>();
36+
operations = new[] { operationA.Object, operationB.Object };
37+
splashScreen = new Mock<ISplashScreen>();
38+
39+
sut = new BootstrapOperationSequence(logger.Object, operations, splashScreen.Object);
40+
}
41+
42+
[TestMethod]
43+
public void MustUpdateProgress()
44+
{
45+
operationA.Setup(o => o.Perform()).Returns(OperationResult.Success);
46+
operationA.Setup(o => o.Revert()).Returns(OperationResult.Success);
47+
operationB.Setup(o => o.Perform()).Returns(OperationResult.Success);
48+
operationB.Setup(o => o.Revert()).Returns(OperationResult.Success);
49+
50+
sut.TryPerform();
51+
sut.TryRevert();
52+
53+
splashScreen.Verify(s => s.SetValue(It.Is<int>(v => v == 0)), Times.Once);
54+
splashScreen.Verify(s => s.SetIndeterminate(), Times.Once);
55+
splashScreen.Verify(s => s.SetMaxValue(It.Is<int>(v => v == 2)), Times.Once);
56+
splashScreen.Verify(s => s.Progress(), Times.Exactly(2));
57+
58+
operationA.Setup(o => o.Perform()).Returns(OperationResult.Failed);
59+
60+
sut.TryPerform();
61+
62+
splashScreen.Verify(s => s.Regress(), Times.Once);
63+
}
64+
65+
[TestMethod]
66+
public void MustUpdateStatus()
67+
{
68+
operationA.Raise(o => o.StatusChanged += default, TextKey.Build);
69+
operationB.Raise(o => o.StatusChanged += default, TextKey.Version);
70+
71+
splashScreen.Verify(s => s.UpdateStatus(It.Is<TextKey>(t => t == TextKey.Build), true), Times.Once);
72+
splashScreen.Verify(s => s.UpdateStatus(It.Is<TextKey>(t => t == TextKey.Version), true), Times.Once);
73+
}
74+
}
75+
}

SafeExamBrowser.Runtime.UnitTests/Operations/ClientOperationTests.cs renamed to SafeExamBrowser.Runtime.UnitTests/Operations/Session/ClientOperationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
using SafeExamBrowser.UserInterface.Contracts.Windows;
2626
using SafeExamBrowser.WindowsApi.Contracts;
2727

28-
namespace SafeExamBrowser.Runtime.UnitTests.Operations
28+
namespace SafeExamBrowser.Runtime.UnitTests.Operations.Session
2929
{
3030
[TestClass]
3131
public class ClientOperationTests

SafeExamBrowser.Runtime.UnitTests/Operations/ClientTerminationOperationTests.cs renamed to SafeExamBrowser.Runtime.UnitTests/Operations/Session/ClientTerminationOperationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
using SafeExamBrowser.UserInterface.Contracts.Windows;
2323
using SafeExamBrowser.WindowsApi.Contracts;
2424

25-
namespace SafeExamBrowser.Runtime.UnitTests.Operations
25+
namespace SafeExamBrowser.Runtime.UnitTests.Operations.Session
2626
{
2727
[TestClass]
2828
public class ClientTerminationOperationTests

SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs renamed to SafeExamBrowser.Runtime.UnitTests/Operations/Session/ConfigurationOperationTests.cs

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@
2828
using SafeExamBrowser.UserInterface.Contracts.Windows;
2929
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
3030

31-
namespace SafeExamBrowser.Runtime.UnitTests.Operations
31+
namespace SafeExamBrowser.Runtime.UnitTests.Operations.Session
3232
{
3333
[TestClass]
3434
public class ConfigurationOperationTests
3535
{
3636
private const string FILE_NAME = "SebClientSettings.seb";
37+
private static readonly string FILE_PATH = Path.Combine(Path.GetDirectoryName(typeof(ConfigurationOperationTests).Assembly.Location), nameof(Operations), nameof(Session), "Testdata", FILE_NAME);
3738

3839
private AppConfig appConfig;
3940
private RuntimeContext context;
@@ -84,10 +85,9 @@ public void Perform_MustUseCommandLineArgumentAs1stPrio()
8485
{
8586
var settings = new AppSettings { ConfigurationMode = ConfigurationMode.Exam };
8687
var url = @"http://www.safeexambrowser.org/whatever.seb";
87-
var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), "Testdata", FILE_NAME);
8888

89-
appConfig.AppDataFilePath = location;
90-
appConfig.ProgramDataFilePath = location;
89+
appConfig.AppDataFilePath = FILE_PATH;
90+
appConfig.ProgramDataFilePath = FILE_PATH;
9191

9292
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.Success);
9393

@@ -102,33 +102,31 @@ public void Perform_MustUseCommandLineArgumentAs1stPrio()
102102
[TestMethod]
103103
public void Perform_MustUseProgramDataAs2ndPrio()
104104
{
105-
var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), "Testdata", FILE_NAME);
106105
var settings = default(AppSettings);
107106

108-
appConfig.ProgramDataFilePath = location;
107+
appConfig.ProgramDataFilePath = FILE_PATH;
109108

110109
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.Success);
111110

112111
var sut = new ConfigurationOperation(null, dependencies, fileSystem.Object, hashAlgorithm.Object, repository.Object, uiFactory.Object);
113112
var result = sut.Perform();
114113

115-
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(location)), out settings, It.IsAny<PasswordParameters>()), Times.Once);
114+
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(FILE_PATH)), out settings, It.IsAny<PasswordParameters>()), Times.Once);
116115
Assert.AreEqual(OperationResult.Success, result);
117116
}
118117

119118
[TestMethod]
120119
public void Perform_MustUseAppDataAs3rdPrio()
121120
{
122-
var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), "Testdata", FILE_NAME);
123121
var settings = default(AppSettings);
124122

125-
appConfig.AppDataFilePath = location;
123+
appConfig.AppDataFilePath = FILE_PATH;
126124
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.Success);
127125

128126
var sut = new ConfigurationOperation(null, dependencies, fileSystem.Object, hashAlgorithm.Object, repository.Object, uiFactory.Object);
129127
var result = sut.Perform();
130128

131-
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(location)), out settings, It.IsAny<PasswordParameters>()), Times.Once);
129+
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(FILE_PATH)), out settings, It.IsAny<PasswordParameters>()), Times.Once);
132130
Assert.AreEqual(OperationResult.Success, result);
133131
}
134132

@@ -290,13 +288,13 @@ public void Perform_MustOnlyAllowToEnterAdminPasswordFiveTimes()
290288
var settings = new AppSettings { ConfigurationMode = ConfigurationMode.ConfigureClient };
291289
var url = @"http://www.safeexambrowser.org/whatever.seb";
292290

293-
appConfig.AppDataFilePath = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), "Testdata", FILE_NAME);
291+
appConfig.AppDataFilePath =
294292
localSettings.Security.AdminPasswordHash = "1234";
295293
settings.Security.AdminPasswordHash = "9876";
296294
nextSession.Settings = settings;
297295

298296
dialog.Setup(d => d.Show(It.IsAny<IWindow>())).Callback(() => count++).Returns(new PasswordDialogResult { Success = true });
299-
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.Success);
297+
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.PasswordNeeded);
300298
repository.Setup(r => r.TryLoadSettings(It.Is<Uri>(u => u.LocalPath.Contains(FILE_NAME)), out localSettings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.Success);
301299
uiFactory.Setup(f => f.CreatePasswordDialog(It.IsAny<string>(), It.IsAny<string>())).Returns(dialog.Object);
302300

@@ -404,17 +402,16 @@ public void Perform_MustSucceedIfSettingsPasswordCorrect()
404402
public void Perform_MustUseCurrentPasswordIfAvailable()
405403
{
406404
var url = @"http://www.safeexambrowser.org/whatever.seb";
407-
var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), "Testdata", FILE_NAME);
408405
var settings = new AppSettings { ConfigurationMode = ConfigurationMode.Exam };
409406

410-
appConfig.AppDataFilePath = location;
407+
appConfig.AppDataFilePath = FILE_PATH;
411408
settings.Security.AdminPasswordHash = "1234";
412409

413410
repository
414411
.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.IsAny<PasswordParameters>()))
415412
.Returns(LoadStatus.PasswordNeeded);
416413
repository
417-
.Setup(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(new Uri(location))), out settings, It.IsAny<PasswordParameters>()))
414+
.Setup(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(new Uri(FILE_PATH))), out settings, It.IsAny<PasswordParameters>()))
418415
.Returns(LoadStatus.Success);
419416
repository
420417
.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.Is<PasswordParameters>(p => p.IsHash == true && p.Password == settings.Security.AdminPasswordHash)))
@@ -437,7 +434,7 @@ public void Perform_MustAbortAskingForAdminPasswordIfDecidedByUser()
437434
var nextSettings = new AppSettings { ConfigurationMode = ConfigurationMode.ConfigureClient };
438435
var url = @"http://www.safeexambrowser.org/whatever.seb";
439436

440-
appConfig.AppDataFilePath = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), "Testdata", FILE_NAME);
437+
appConfig.AppDataFilePath = FILE_PATH;
441438
currentSession.Settings = currentSettings;
442439
currentSettings.Security.AdminPasswordHash = "1234";
443440
nextSession.Settings = nextSettings;
@@ -479,7 +476,7 @@ public void Repeat_MustPerformForExamWithCorrectUri()
479476
{
480477
var currentSettings = new AppSettings();
481478
var location = Path.GetDirectoryName(GetType().Assembly.Location);
482-
var resource = new Uri(Path.Combine(location, nameof(Operations), "Testdata", FILE_NAME));
479+
var resource = new Uri(Path.Combine(location, nameof(Operations), nameof(Session), "Testdata", FILE_NAME));
483480
var settings = new AppSettings { ConfigurationMode = ConfigurationMode.Exam };
484481

485482
currentSession.Settings = currentSettings;
@@ -501,7 +498,7 @@ public void Repeat_MustPerformForClientConfigurationWithCorrectUri()
501498
{
502499
var currentSettings = new AppSettings();
503500
var location = Path.GetDirectoryName(GetType().Assembly.Location);
504-
var resource = new Uri(Path.Combine(location, nameof(Operations), "Testdata", FILE_NAME));
501+
var resource = new Uri(Path.Combine(location, nameof(Operations), nameof(Session), "Testdata", FILE_NAME));
505502
var settings = new AppSettings { ConfigurationMode = ConfigurationMode.ConfigureClient };
506503

507504
currentSession.Settings = currentSettings;
@@ -524,7 +521,7 @@ public void Repeat_MustDeleteTemporaryFileAfterClientConfiguration()
524521
{
525522
var currentSettings = new AppSettings();
526523
var location = Path.GetDirectoryName(GetType().Assembly.Location);
527-
var resource = new Uri(Path.Combine(location, nameof(Operations), "Testdata", FILE_NAME));
524+
var resource = new Uri(Path.Combine(location, nameof(Operations), nameof(Session), "Testdata", FILE_NAME));
528525
var settings = new AppSettings { ConfigurationMode = ConfigurationMode.ConfigureClient };
529526
var delete = 0;
530527
var configure = 0;
@@ -578,7 +575,7 @@ public void Repeat_MustAbortForSettingsPasswordIfWishedByUser()
578575
var currentSettings = new AppSettings();
579576
var dialog = new Mock<IPasswordDialog>();
580577
var location = Path.GetDirectoryName(GetType().Assembly.Location);
581-
var resource = new Uri(Path.Combine(location, nameof(Operations), "Testdata", FILE_NAME));
578+
var resource = new Uri(Path.Combine(location, nameof(Operations), nameof(Session), "Testdata", FILE_NAME));
582579
var settings = new AppSettings { ConfigurationMode = ConfigurationMode.ConfigureClient };
583580

584581
currentSession.Settings = currentSettings;
@@ -601,7 +598,7 @@ public void Repeat_MustNotWaitForPasswordViaClientIfCommunicationHasFailed()
601598
var clientProxy = new Mock<IClientProxy>();
602599
var currentSettings = new AppSettings();
603600
var location = Path.GetDirectoryName(GetType().Assembly.Location);
604-
var resource = new Uri(Path.Combine(location, nameof(Operations), "Testdata", FILE_NAME));
601+
var resource = new Uri(Path.Combine(location, nameof(Operations), nameof(Session), "Testdata", FILE_NAME));
605602
var settings = new AppSettings { ConfigurationMode = ConfigurationMode.ConfigureClient };
606603

607604
context.ClientProxy = clientProxy.Object;

SafeExamBrowser.Runtime.UnitTests/Operations/DisclaimerOperationTests.cs renamed to SafeExamBrowser.Runtime.UnitTests/Operations/Session/DisclaimerOperationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
2020
using SafeExamBrowser.UserInterface.Contracts.Windows;
2121

22-
namespace SafeExamBrowser.Runtime.UnitTests.Operations
22+
namespace SafeExamBrowser.Runtime.UnitTests.Operations.Session
2323
{
2424
[TestClass]
2525
public class DisclaimerOperationTests

SafeExamBrowser.Runtime.UnitTests/Operations/DisplayMonitorOperationTests.cs renamed to SafeExamBrowser.Runtime.UnitTests/Operations/Session/DisplayMonitorOperationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
2222
using SafeExamBrowser.UserInterface.Contracts.Windows;
2323

24-
namespace SafeExamBrowser.Runtime.UnitTests.Operations
24+
namespace SafeExamBrowser.Runtime.UnitTests.Operations.Session
2525
{
2626
[TestClass]
2727
public class DisplayMonitorOperationTests

SafeExamBrowser.Runtime.UnitTests/Operations/KioskModeOperationTests.cs renamed to SafeExamBrowser.Runtime.UnitTests/Operations/Session/KioskModeOperationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
using SafeExamBrowser.UserInterface.Contracts.Windows;
2222
using SafeExamBrowser.WindowsApi.Contracts;
2323

24-
namespace SafeExamBrowser.Runtime.UnitTests.Operations
24+
namespace SafeExamBrowser.Runtime.UnitTests.Operations.Session
2525
{
2626
[TestClass]
2727
public class KioskModeOperationTests

SafeExamBrowser.Runtime.UnitTests/Operations/RemoteSessionOperationTests.cs renamed to SafeExamBrowser.Runtime.UnitTests/Operations/Session/RemoteSessionOperationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
2121
using SafeExamBrowser.UserInterface.Contracts.Windows;
2222

23-
namespace SafeExamBrowser.Runtime.UnitTests.Operations
23+
namespace SafeExamBrowser.Runtime.UnitTests.Operations.Session
2424
{
2525
[TestClass]
2626
public class RemoteSessionOperationTests

SafeExamBrowser.Runtime.UnitTests/Operations/ServerOperationTests.cs renamed to SafeExamBrowser.Runtime.UnitTests/Operations/Session/ServerOperationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
using SafeExamBrowser.UserInterface.Contracts.Windows;
2929
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
3030

31-
namespace SafeExamBrowser.Runtime.UnitTests.Operations
31+
namespace SafeExamBrowser.Runtime.UnitTests.Operations.Session
3232
{
3333
[TestClass]
3434
public class ServerOperationTests

0 commit comments

Comments
 (0)