microsoft/TypeAgent

Public

mirrored fromhttps://github.com/microsoft/TypeAgentAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/review-tests-to-test-live

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

dotnet/autoShell.Tests/AppCommandHandlerTests.cs

192lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Diagnostics;
5using autoShell.Handlers;
6using autoShell.Logging;
7using autoShell.Services;
8using Moq;
9using Newtonsoft.Json.Linq;
10
11namespace autoShell.Tests;
12
13public class AppCommandHandlerTests
14{
15 private readonly Mock<IAppRegistry> _appRegistryMock = new();
16 private readonly Mock<IProcessService> _processMock = new();
17 private readonly Mock<IWindowService> _windowMock = new();
18 private readonly Mock<ILogger> _loggerMock = new();
19 private readonly AppCommandHandler _handler;
20
21 public AppCommandHandlerTests()
22 {
23 _handler = new AppCommandHandler(_appRegistryMock.Object, _processMock.Object, _windowMock.Object, _loggerMock.Object);
24 }
25
26 /// <summary>
27 /// Verifies that launching a non-running app starts it using its executable path.
28 /// </summary>
29 [Fact]
30 public void LaunchProgram_AppNotRunning_StartsViaPath()
31 {
32 _appRegistryMock.Setup(a => a.ResolveProcessName("chrome")).Returns("chrome");
33 _processMock.Setup(p => p.GetProcessesByName("chrome")).Returns([]);
34 _appRegistryMock.Setup(a => a.GetExecutablePath("chrome")).Returns("chrome.exe");
35
36 Handle("LaunchProgram", "chrome");
37
38 _processMock.Verify(p => p.Start(It.Is<ProcessStartInfo>(
39 psi => psi.FileName == "chrome.exe" && psi.UseShellExecute == true)), Times.Once);
40 }
41
42 /// <summary>
43 /// Verifies that launching an app with a configured working directory env var sets the working directory.
44 /// </summary>
45 [Fact]
46 public void LaunchProgram_WithWorkingDir_SetsWorkingDirectory()
47 {
48 _appRegistryMock.Setup(a => a.ResolveProcessName("github copilot")).Returns("github copilot");
49 _processMock.Setup(p => p.GetProcessesByName("github copilot")).Returns([]);
50 _appRegistryMock.Setup(a => a.GetExecutablePath("github copilot")).Returns("copilot.exe");
51 _appRegistryMock.Setup(a => a.GetWorkingDirectoryEnvVar("github copilot")).Returns("GITHUB_COPILOT_ROOT_DIR");
52
53 Handle("LaunchProgram", "github copilot");
54
55 _processMock.Verify(p => p.Start(It.Is<ProcessStartInfo>(
56 psi => psi.WorkingDirectory != "")), Times.Once);
57 }
58
59 /// <summary>
60 /// Verifies that launching an app with configured arguments passes them to the process start info.
61 /// </summary>
62 [Fact]
63 public void LaunchProgram_WithArguments_SetsArguments()
64 {
65 _appRegistryMock.Setup(a => a.ResolveProcessName("github copilot")).Returns("github copilot");
66 _processMock.Setup(p => p.GetProcessesByName("github copilot")).Returns([]);
67 _appRegistryMock.Setup(a => a.GetExecutablePath("github copilot")).Returns("copilot.exe");
68 _appRegistryMock.Setup(a => a.GetArguments("github copilot")).Returns("--allow-all-tools");
69
70 Handle("LaunchProgram", "github copilot");
71
72 _processMock.Verify(p => p.Start(It.Is<ProcessStartInfo>(
73 psi => psi.Arguments == "--allow-all-tools")), Times.Once);
74 }
75
76 /// <summary>
77 /// Verifies that when no executable path is available, the app is launched via its AppUserModelId through explorer.exe.
78 /// </summary>
79 [Fact]
80 public void LaunchProgram_NoPath_UsesAppUserModelId()
81 {
82 _appRegistryMock.Setup(a => a.ResolveProcessName("calculator")).Returns("calculator");
83 _processMock.Setup(p => p.GetProcessesByName("calculator")).Returns([]);
84 _appRegistryMock.Setup(a => a.GetExecutablePath("calculator")).Returns((string)null!);
85 _appRegistryMock.Setup(a => a.GetAppUserModelId("calculator")).Returns("Microsoft.WindowsCalculator");
86
87 Handle("LaunchProgram", "calculator");
88
89 _processMock.Verify(p => p.Start(It.Is<ProcessStartInfo>(
90 psi => psi.FileName == "explorer.exe")), Times.Once);
91 }
92
93 /// <summary>
94 /// Verifies that closing a program resolves its process name and looks up running processes.
95 /// Note: the actual <see cref="System.Diagnostics.Process.CloseMainWindow"/> call path cannot be unit-tested because
96 /// <see cref="System.Diagnostics.Process.MainWindowHandle"/> is not virtual and cannot be mocked.
97 /// </summary>
98 [Fact]
99 public void CloseProgram_ResolvesProcessNameAndLooksUpProcesses()
100 {
101 _appRegistryMock.Setup(a => a.ResolveProcessName("notepad")).Returns("notepad");
102 // Return a real (albeit useless in test) empty array to avoid null ref;
103 // We cannot easily mock Process objects, so we verify the lookup was attempted.
104 _processMock.Setup(p => p.GetProcessesByName("notepad")).Returns([]);
105
106 Handle("CloseProgram", "notepad");
107
108 _processMock.Verify(p => p.GetProcessesByName("notepad"), Times.Once);
109 }
110
111 /// <summary>
112 /// Verifies that closing a program that is not running does not throw an exception.
113 /// </summary>
114 [Fact]
115 public void CloseProgram_NotRunning_DoesNothing()
116 {
117 _appRegistryMock.Setup(a => a.ResolveProcessName("notepad")).Returns("notepad");
118 _processMock.Setup(p => p.GetProcessesByName("notepad")).Returns([]);
119
120 var ex = Record.Exception(() => Handle("CloseProgram", "notepad"));
121
122 Assert.Null(ex);
123 }
124
125 /// <summary>
126 /// Verifies that the ListAppNames command invokes <see cref="IAppRegistry.GetAllAppNames"/> on the app registry.
127 /// </summary>
128 [Fact]
129 public void ListAppNames_CallsGetAllAppNames()
130 {
131 _appRegistryMock.Setup(a => a.GetAllAppNames()).Returns(["notepad", "chrome"]);
132
133 Handle("ListAppNames", "");
134
135 _appRegistryMock.Verify(a => a.GetAllAppNames(), Times.Once);
136 }
137
138 /// <summary>
139 /// Verifies that launching an already-running app raises its window instead of starting a new process.
140 /// </summary>
141 [Fact]
142 public void LaunchProgram_AlreadyRunning_RaisesWindow()
143 {
144 _appRegistryMock.Setup(a => a.ResolveProcessName("notepad")).Returns("notepad");
145 _processMock.Setup(p => p.GetProcessesByName("notepad")).Returns([Process.GetCurrentProcess()]);
146 _appRegistryMock.Setup(a => a.GetExecutablePath("notepad")).Returns("notepad.exe");
147
148 Handle("LaunchProgram", "notepad");
149
150 _windowMock.Verify(w => w.RaiseWindow("notepad", "notepad.exe"), Times.Once);
151 _processMock.Verify(p => p.Start(It.IsAny<ProcessStartInfo>()), Times.Never);
152 }
153
154 /// <summary>
155 /// Verifies that a <see cref="System.ComponentModel.Win32Exception"/> on first launch attempt triggers a fallback retry using the friendly name.
156 /// </summary>
157 [Fact]
158 public void LaunchProgram_Win32Exception_FallsBackToFriendlyName()
159 {
160 _appRegistryMock.Setup(a => a.ResolveProcessName("myapp")).Returns("myapp");
161 _processMock.Setup(p => p.GetProcessesByName("myapp")).Returns([]);
162 _appRegistryMock.Setup(a => a.GetExecutablePath("myapp")).Returns("myapp.exe");
163 _processMock.SetupSequence(p => p.Start(It.IsAny<ProcessStartInfo>()))
164 .Throws(new System.ComponentModel.Win32Exception("not found"))
165 .Returns(Process.GetCurrentProcess());
166
167 Handle("LaunchProgram", "myapp");
168
169 _processMock.Verify(p => p.Start(It.IsAny<ProcessStartInfo>()), Times.Exactly(2));
170 }
171
172 /// <summary>
173 /// Verifies that launching an app with no path and no AppUserModelId does not start any process.
174 /// </summary>
175 [Fact]
176 public void LaunchProgram_NoPathNoAppModelId_DoesNothing()
177 {
178 _appRegistryMock.Setup(a => a.ResolveProcessName("unknown")).Returns("unknown");
179 _processMock.Setup(p => p.GetProcessesByName("unknown")).Returns([]);
180 _appRegistryMock.Setup(a => a.GetExecutablePath("unknown")).Returns((string)null!);
181 _appRegistryMock.Setup(a => a.GetAppUserModelId("unknown")).Returns((string)null!);
182
183 Handle("LaunchProgram", "unknown");
184
185 _processMock.Verify(p => p.Start(It.IsAny<ProcessStartInfo>()), Times.Never);
186 }
187
188 private void Handle(string key, string value)
189 {
190 _handler.Handle(key, value, JToken.FromObject(value));
191 }
192}
193