Skip to content

Commit 2e4498a

Browse files
Increase the test coverage
1 parent 90745a7 commit 2e4498a

File tree

4 files changed

+277
-17
lines changed

4 files changed

+277
-17
lines changed

unittests/nexus_engine/nexus_engine_extensions_unittests/ExtensionScriptsTests.cs

Lines changed: 274 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44

55
using Nexus.Config;
66
using Nexus.Config.Models;
7-
#pragma warning disable IDE0005 // Using directive is unnecessary - ExtensionScripts is in this namespace
8-
using Nexus.Engine.Extensions;
9-
#pragma warning restore IDE0005
107
using Nexus.Engine.Share;
8+
using Nexus.Engine.Share.Models;
119
using Nexus.External.Apis.FileSystem;
1210
using Nexus.External.Apis.ProcessManagement;
1311

@@ -274,4 +272,277 @@ public void CloseSession_WhenSessionHasCommands_CancelsThem()
274272
// Assert - Should not throw
275273
_ = m_ExtensionScripts.Should().NotBeNull();
276274
}
275+
276+
/// <summary>
277+
/// Verifies that GetCommandStatus increments read count on multiple calls.
278+
/// </summary>
279+
[Fact]
280+
public void GetCommandStatus_MultipleCalls_IncrementsReadCount()
281+
{
282+
// Arrange
283+
m_ExtensionScripts = new ExtensionScripts(
284+
m_MockEngine.Object,
285+
m_MockFileSystem.Object,
286+
m_MockProcessManager.Object,
287+
m_MockSettings.Object);
288+
289+
// Manually add a command to cache for testing
290+
var commandInfo = CommandInfo.Enqueued("session-123", "cmd-test", "Extension: Test", DateTime.Now, null);
291+
var cacheField = typeof(ExtensionScripts).GetField("m_CommandCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
292+
var cache = (System.Collections.Concurrent.ConcurrentDictionary<string, CommandInfo>)cacheField!.GetValue(m_ExtensionScripts)!;
293+
cache["cmd-test"] = commandInfo;
294+
295+
var initialReadCount = commandInfo.ReadCount;
296+
297+
// Act
298+
_ = m_ExtensionScripts.GetCommandStatus("cmd-test");
299+
_ = m_ExtensionScripts.GetCommandStatus("cmd-test");
300+
301+
// Assert
302+
_ = commandInfo.ReadCount.Should().Be(initialReadCount + 2);
303+
}
304+
305+
/// <summary>
306+
/// Verifies that CancelCommand returns true when extension is running.
307+
/// </summary>
308+
[Fact]
309+
public void CancelCommand_WhenExtensionRunning_ReturnsTrue()
310+
{
311+
// Arrange
312+
m_MockProcessManager.Setup(pm => pm.KillProcess(It.IsAny<int>())).Verifiable();
313+
314+
m_ExtensionScripts = new ExtensionScripts(
315+
m_MockEngine.Object,
316+
m_MockFileSystem.Object,
317+
m_MockProcessManager.Object,
318+
m_MockSettings.Object);
319+
320+
// Manually add running extension for testing
321+
var runningExtensionsField = typeof(ExtensionScripts).GetField("m_RunningExtensions", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
322+
var extensionStatusType = typeof(ExtensionScripts).Assembly.GetType("Nexus.Engine.Extensions.Models.ExtensionStatus")!;
323+
var runningExtensions = runningExtensionsField!.GetValue(m_ExtensionScripts)!;
324+
325+
var commandInfo = CommandInfo.Enqueued("session-123", "cmd-test", "Extension: Test", DateTime.Now, 12345);
326+
var cacheField = typeof(ExtensionScripts).GetField("m_CommandCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
327+
var cache = (System.Collections.Concurrent.ConcurrentDictionary<string, CommandInfo>)cacheField!.GetValue(m_ExtensionScripts)!;
328+
cache["cmd-test"] = commandInfo;
329+
330+
var cts = new CancellationTokenSource();
331+
var status = Activator.CreateInstance(extensionStatusType)!;
332+
extensionStatusType.GetProperty("CommandId")!.SetValue(status, "cmd-test");
333+
extensionStatusType.GetProperty("SessionId")!.SetValue(status, "session-123");
334+
extensionStatusType.GetProperty("ExtensionName")!.SetValue(status, "Test");
335+
extensionStatusType.GetProperty("ProcessId")!.SetValue(status, 12345);
336+
extensionStatusType.GetProperty("StartTime")!.SetValue(status, DateTime.Now);
337+
extensionStatusType.GetProperty("CancellationTokenSource")!.SetValue(status, cts);
338+
extensionStatusType.GetProperty("Parameters")!.SetValue(status, null);
339+
var indexerMethod = runningExtensions.GetType().GetProperty("Item")!.SetMethod!;
340+
_ = indexerMethod.Invoke(runningExtensions, new object[] { "cmd-test", status });
341+
342+
// Act
343+
var result = m_ExtensionScripts.CancelCommand("session-123", "cmd-test");
344+
345+
// Assert
346+
_ = result.Should().BeTrue();
347+
m_MockProcessManager.Verify(pm => pm.KillProcess(12345), Times.Once);
348+
_ = cts.Token.IsCancellationRequested.Should().BeTrue();
349+
}
350+
351+
/// <summary>
352+
/// Verifies that CancelCommand handles exception when killing process gracefully.
353+
/// </summary>
354+
[Fact]
355+
public void CancelCommand_WhenKillProcessThrowsException_StillCancels()
356+
{
357+
// Arrange
358+
_ = m_MockProcessManager.Setup(pm => pm.KillProcess(It.IsAny<int>()))
359+
.Throws(new InvalidOperationException("Process not found"));
360+
361+
m_ExtensionScripts = new ExtensionScripts(
362+
m_MockEngine.Object,
363+
m_MockFileSystem.Object,
364+
m_MockProcessManager.Object,
365+
m_MockSettings.Object);
366+
367+
// Manually add running extension for testing
368+
var runningExtensionsField = typeof(ExtensionScripts).GetField("m_RunningExtensions", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
369+
var extensionStatusType = typeof(ExtensionScripts).Assembly.GetType("Nexus.Engine.Extensions.Models.ExtensionStatus")!;
370+
var runningExtensions = runningExtensionsField!.GetValue(m_ExtensionScripts)!;
371+
372+
var commandInfo = CommandInfo.Enqueued("session-123", "cmd-test", "Extension: Test", DateTime.Now, 12345);
373+
var cacheField = typeof(ExtensionScripts).GetField("m_CommandCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
374+
var cache = (System.Collections.Concurrent.ConcurrentDictionary<string, CommandInfo>)cacheField!.GetValue(m_ExtensionScripts)!;
375+
cache["cmd-test"] = commandInfo;
376+
377+
var cts = new CancellationTokenSource();
378+
var status = Activator.CreateInstance(extensionStatusType)!;
379+
extensionStatusType.GetProperty("CommandId")!.SetValue(status, "cmd-test");
380+
extensionStatusType.GetProperty("SessionId")!.SetValue(status, "session-123");
381+
extensionStatusType.GetProperty("ExtensionName")!.SetValue(status, "Test");
382+
extensionStatusType.GetProperty("ProcessId")!.SetValue(status, 12345);
383+
extensionStatusType.GetProperty("StartTime")!.SetValue(status, DateTime.Now);
384+
extensionStatusType.GetProperty("CancellationTokenSource")!.SetValue(status, cts);
385+
extensionStatusType.GetProperty("Parameters")!.SetValue(status, null);
386+
var indexerMethod = runningExtensions.GetType().GetProperty("Item")!.SetMethod!;
387+
_ = indexerMethod.Invoke(runningExtensions, new object[] { "cmd-test", status });
388+
389+
// Act
390+
var result = m_ExtensionScripts.CancelCommand("session-123", "cmd-test");
391+
392+
// Assert - Should still return true because exception is caught and cancellation still happens
393+
_ = result.Should().BeTrue();
394+
_ = cts.Token.IsCancellationRequested.Should().BeTrue();
395+
}
396+
397+
/// <summary>
398+
/// Verifies that CloseSession with commands cancels them.
399+
/// </summary>
400+
[Fact]
401+
public void CloseSession_WithCommands_CancelsThem()
402+
{
403+
// Arrange
404+
m_MockProcessManager.Setup(pm => pm.KillProcess(It.IsAny<int>())).Verifiable();
405+
406+
m_ExtensionScripts = new ExtensionScripts(
407+
m_MockEngine.Object,
408+
m_MockFileSystem.Object,
409+
m_MockProcessManager.Object,
410+
m_MockSettings.Object);
411+
412+
// Manually set up session with commands for testing
413+
var sessionCommandsField = typeof(ExtensionScripts).GetField("m_SessionCommands", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
414+
var sessionCommands = (System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentBag<string>>)sessionCommandsField!.GetValue(m_ExtensionScripts)!;
415+
var commandIds = new System.Collections.Concurrent.ConcurrentBag<string> { "cmd-1", "cmd-2" };
416+
sessionCommands["session-123"] = commandIds;
417+
418+
var runningExtensionsField = typeof(ExtensionScripts).GetField("m_RunningExtensions", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
419+
var extensionStatusType = typeof(ExtensionScripts).Assembly.GetType("Nexus.Engine.Extensions.Models.ExtensionStatus")!;
420+
var runningExtensions = runningExtensionsField!.GetValue(m_ExtensionScripts)!;
421+
422+
var cts1 = new CancellationTokenSource();
423+
var status1 = Activator.CreateInstance(extensionStatusType)!;
424+
extensionStatusType.GetProperty("CommandId")!.SetValue(status1, "cmd-1");
425+
extensionStatusType.GetProperty("SessionId")!.SetValue(status1, "session-123");
426+
extensionStatusType.GetProperty("ExtensionName")!.SetValue(status1, "Test1");
427+
extensionStatusType.GetProperty("ProcessId")!.SetValue(status1, 12345);
428+
extensionStatusType.GetProperty("StartTime")!.SetValue(status1, DateTime.Now);
429+
extensionStatusType.GetProperty("CancellationTokenSource")!.SetValue(status1, cts1);
430+
extensionStatusType.GetProperty("Parameters")!.SetValue(status1, null);
431+
var indexerMethod1 = runningExtensions.GetType().GetProperty("Item")!.SetMethod!;
432+
_ = indexerMethod1.Invoke(runningExtensions, new object[] { "cmd-1", status1 });
433+
434+
// Act
435+
m_ExtensionScripts.CloseSession("session-123");
436+
437+
// Assert
438+
_ = m_ExtensionScripts.Should().NotBeNull();
439+
m_MockProcessManager.Verify(pm => pm.KillProcess(It.IsAny<int>()), Times.Once);
440+
}
441+
442+
/// <summary>
443+
/// Verifies that GetSessionCommands returns commands for existing session.
444+
/// </summary>
445+
[Fact]
446+
public void GetSessionCommands_WithExistingSession_ReturnsCommands()
447+
{
448+
// Arrange
449+
m_ExtensionScripts = new ExtensionScripts(
450+
m_MockEngine.Object,
451+
m_MockFileSystem.Object,
452+
m_MockProcessManager.Object,
453+
m_MockSettings.Object);
454+
455+
// Manually add commands to session for testing
456+
var commandInfo1 = CommandInfo.Enqueued("session-123", "cmd-1", "Extension: Test1", DateTime.Now, null);
457+
var commandInfo2 = CommandInfo.Enqueued("session-123", "cmd-2", "Extension: Test2", DateTime.Now, null);
458+
459+
var cacheField = typeof(ExtensionScripts).GetField("m_CommandCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
460+
var cache = (System.Collections.Concurrent.ConcurrentDictionary<string, CommandInfo>)cacheField!.GetValue(m_ExtensionScripts)!;
461+
cache["cmd-1"] = commandInfo1;
462+
cache["cmd-2"] = commandInfo2;
463+
464+
var sessionCommandsField = typeof(ExtensionScripts).GetField("m_SessionCommands", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
465+
var sessionCommands = (System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentBag<string>>)sessionCommandsField!.GetValue(m_ExtensionScripts)!;
466+
var commandIds = new System.Collections.Concurrent.ConcurrentBag<string> { "cmd-1", "cmd-2" };
467+
sessionCommands["session-123"] = commandIds;
468+
469+
// Act
470+
var result = m_ExtensionScripts.GetSessionCommands("session-123");
471+
472+
// Assert
473+
_ = result.Should().NotBeNull();
474+
_ = result.Should().HaveCount(2);
475+
_ = result.Should().Contain(c => c.CommandId == "cmd-1");
476+
_ = result.Should().Contain(c => c.CommandId == "cmd-2");
477+
}
478+
479+
/// <summary>
480+
/// Verifies that GetSessionCommands handles missing command IDs gracefully.
481+
/// </summary>
482+
[Fact]
483+
public void GetSessionCommands_WithMissingCommandIds_HandlesGracefully()
484+
{
485+
// Arrange
486+
m_ExtensionScripts = new ExtensionScripts(
487+
m_MockEngine.Object,
488+
m_MockFileSystem.Object,
489+
m_MockProcessManager.Object,
490+
m_MockSettings.Object);
491+
492+
// Manually add session with command ID that doesn't exist in cache
493+
var sessionCommandsField = typeof(ExtensionScripts).GetField("m_SessionCommands", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
494+
var sessionCommands = (System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentBag<string>>)sessionCommandsField!.GetValue(m_ExtensionScripts)!;
495+
var commandIds = new System.Collections.Concurrent.ConcurrentBag<string> { "cmd-missing" };
496+
sessionCommands["session-123"] = commandIds;
497+
498+
// Act
499+
var result = m_ExtensionScripts.GetSessionCommands("session-123");
500+
501+
// Assert
502+
_ = result.Should().NotBeNull();
503+
_ = result.Should().BeEmpty();
504+
}
505+
506+
/// <summary>
507+
/// Verifies that CancelCommand updates cache to cancelled state.
508+
/// </summary>
509+
[Fact]
510+
public void CancelCommand_UpdatesCacheToCancelledState()
511+
{
512+
// Arrange
513+
m_ExtensionScripts = new ExtensionScripts(
514+
m_MockEngine.Object,
515+
m_MockFileSystem.Object,
516+
m_MockProcessManager.Object,
517+
m_MockSettings.Object);
518+
519+
var commandInfo = CommandInfo.Executing("session-123", "cmd-test", "Extension: Test", DateTime.Now, DateTime.Now, 12345);
520+
var cacheField = typeof(ExtensionScripts).GetField("m_CommandCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
521+
var cache = (System.Collections.Concurrent.ConcurrentDictionary<string, CommandInfo>)cacheField!.GetValue(m_ExtensionScripts)!;
522+
cache["cmd-test"] = commandInfo;
523+
524+
var runningExtensionsField = typeof(ExtensionScripts).GetField("m_RunningExtensions", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
525+
var extensionStatusType = typeof(ExtensionScripts).Assembly.GetType("Nexus.Engine.Extensions.Models.ExtensionStatus")!;
526+
var runningExtensions = runningExtensionsField!.GetValue(m_ExtensionScripts)!;
527+
528+
var cts = new CancellationTokenSource();
529+
var status = Activator.CreateInstance(extensionStatusType)!;
530+
extensionStatusType.GetProperty("CommandId")!.SetValue(status, "cmd-test");
531+
extensionStatusType.GetProperty("SessionId")!.SetValue(status, "session-123");
532+
extensionStatusType.GetProperty("ExtensionName")!.SetValue(status, "Test");
533+
extensionStatusType.GetProperty("ProcessId")!.SetValue(status, 12345);
534+
extensionStatusType.GetProperty("StartTime")!.SetValue(status, DateTime.Now);
535+
extensionStatusType.GetProperty("CancellationTokenSource")!.SetValue(status, cts);
536+
extensionStatusType.GetProperty("Parameters")!.SetValue(status, null);
537+
var indexerMethod = runningExtensions.GetType().GetProperty("Item")!.SetMethod!;
538+
_ = indexerMethod.Invoke(runningExtensions, new object[] { "cmd-test", status });
539+
540+
// Act
541+
_ = m_ExtensionScripts.CancelCommand("session-123", "cmd-test");
542+
543+
// Assert
544+
var updatedInfo = m_ExtensionScripts.GetCommandStatus("cmd-test");
545+
_ = updatedInfo.Should().NotBeNull();
546+
_ = updatedInfo!.State.Should().Be(CommandState.Cancelled);
547+
}
277548
}

unittests/nexus_engine/nexus_engine_unittests/Internal/ProcessOutputAggregatorTests.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,7 @@ public void Attach_WithNullProcess_ThrowsArgumentNullException()
7171
using var aggregator = new ProcessOutputAggregator();
7272

7373
// Act & Assert
74-
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type - intentional for testing
75-
var ex = Assert.Throws<ArgumentNullException>(() => aggregator.Attach(null));
76-
#pragma warning restore CS8625
74+
var ex = Assert.Throws<ArgumentNullException>(() => aggregator.Attach(null!));
7775
Assert.Equal("process", ex.ParamName);
7876
}
7977

unittests/nexus_engine/nexus_engine_unittests/Internal/ProcessOutputLineTests.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ public void Constructor_WithNullText_SetsTextToEmptyString()
5656
const bool isError = false;
5757

5858
// Act
59-
#pragma warning disable CS8604 // Possible null reference argument - intentional for testing
60-
var line = new ProcessOutputLine(text, isError);
61-
#pragma warning restore CS8604
59+
var line = new ProcessOutputLine(text!, isError);
6260

6361
// Assert
6462
Assert.Equal(string.Empty, line.Text);

unittests/nexus_protocol_unittests/Middleware/ResponseFormattingMiddlewareTests.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -220,14 +220,7 @@ public override void Write(byte[] buffer, int offset, int count)
220220
/// <returns>The write task.</returns>
221221
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
222222
{
223-
#pragma warning disable IDE0046 // Convert to conditional expression - keeping explicit throw for clarity
224-
if (Position > 0)
225-
{
226-
throw new IOException("Write failed");
227-
}
228-
#pragma warning restore IDE0046
229-
230-
return base.WriteAsync(buffer, offset, count, cancellationToken);
223+
return Position > 0 ? throw new IOException("Write failed") : base.WriteAsync(buffer, offset, count, cancellationToken);
231224
}
232225
}
233226
}

0 commit comments

Comments
 (0)