microsoft/TypeAgent

Public

mirrored from https://github.com/microsoft/TypeAgentAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
f6da8916bd40d461b5d29c4f7fbda09d178d99fc

Branches

Tags

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

Clone

HTTPS

Download ZIP

dotnet/autoShell.Tests/ActionDispatcherIntegrationTests.cs

235lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Text.Json;
5using autoShell.Logging;
6using autoShell.Services;
7using Moq;
8
9namespace autoShell.Tests;
10
11/// <summary>
12/// Integration tests that exercise the full <see cref="ActionDispatcher.Create"/> → <see cref="ActionDispatcher.Dispatch"/> → handler → service pipeline
13/// using mock services. These verify that <see cref="ActionDispatcher"/> wiring is correct.
14/// </summary>
15public class ActionDispatcherIntegrationTests
16{
17 private readonly Mock<IRegistryService> _registryMock = new();
18 private readonly Mock<ISystemParametersService> _systemParamsMock = new();
19 private readonly Mock<IProcessService> _processMock = new();
20 private readonly Mock<IAudioService> _audioMock = new();
21 private readonly Mock<IAppRegistry> _appRegistryMock = new();
22 private readonly Mock<IDebuggerService> _debuggerMock = new();
23 private readonly Mock<IBrightnessService> _brightnessMock = new();
24 private readonly Mock<IDisplayService> _displayMock = new();
25 private readonly Mock<IWindowService> _windowMock = new();
26 private readonly Mock<INetworkService> _networkMock = new();
27 private readonly Mock<IVirtualDesktopService> _virtualDesktopMock = new();
28 private readonly Mock<ILogger> _loggerMock = new();
29 private readonly ActionDispatcher _dispatcher;
30
31 public ActionDispatcherIntegrationTests()
32 {
33 _dispatcher = ActionDispatcher.Create(
34 _loggerMock.Object,
35 _registryMock.Object,
36 _systemParamsMock.Object,
37 _processMock.Object,
38 _audioMock.Object,
39 _appRegistryMock.Object,
40 _debuggerMock.Object,
41 _brightnessMock.Object,
42 _displayMock.Object,
43 _windowMock.Object,
44 _networkMock.Object,
45 _virtualDesktopMock.Object);
46 }
47
48 /// <summary>
49 /// Verifies that a Volume command dispatched through <see cref="ActionDispatcher.Create"/> reaches the audio service.
50 /// </summary>
51 [Fact]
52 public void Dispatch_Volume_ReachesAudioService()
53 {
54 _audioMock.Setup(a => a.GetVolume()).Returns(50);
55
56 Dispatch("""{"actionName":"Volume","parameters":{"targetVolume":75}}""");
57
58 _audioMock.Verify(a => a.SetVolume(75), Times.Once);
59 }
60
61 /// <summary>
62 /// Verifies that a Mute command dispatched through <see cref="ActionDispatcher.Create"/> reaches the audio service.
63 /// </summary>
64 [Fact]
65 public void Dispatch_Mute_ReachesAudioService()
66 {
67 Dispatch("""{"actionName":"Mute","parameters":{"on":true}}""");
68
69 _audioMock.Verify(a => a.SetMute(true), Times.Once);
70 }
71
72 /// <summary>
73 /// Verifies that a LaunchProgram command dispatched through <see cref="ActionDispatcher.Create"/> reaches the process service.
74 /// </summary>
75 [Fact]
76 public void Dispatch_LaunchProgram_ReachesProcessService()
77 {
78 _appRegistryMock.Setup(a => a.ResolveProcessName("notepad")).Returns("notepad");
79 _processMock.Setup(p => p.GetProcessesByName("notepad")).Returns([]);
80 _appRegistryMock.Setup(a => a.GetExecutablePath("notepad")).Returns("notepad.exe");
81
82 Dispatch("""{"actionName":"LaunchProgram","parameters":{"name":"notepad"}}""");
83
84 _processMock.Verify(p => p.Start(It.IsAny<System.Diagnostics.ProcessStartInfo>()), Times.Once);
85 }
86
87 /// <summary>
88 /// Verifies that a SetWallpaper command dispatched through <see cref="ActionDispatcher.Create"/> reaches the system parameters service.
89 /// </summary>
90 [Fact]
91 public void Dispatch_SetWallpaper_ReachesSystemParamsService()
92 {
93 Dispatch("""{"actionName":"SetWallpaper","parameters":{"filePath":"C:\\wallpaper.jpg"}}""");
94
95 _systemParamsMock.Verify(s => s.SetParameter(0x0014, 0, @"C:\wallpaper.jpg", 3), Times.Once);
96 }
97
98 /// <summary>
99 /// Verifies that a ConnectWifi command dispatched through <see cref="ActionDispatcher.Create"/> reaches the network service.
100 /// </summary>
101 [Fact]
102 public void Dispatch_ConnectWifi_ReachesNetworkService()
103 {
104 Dispatch("""{"actionName":"ConnectWifi","parameters":{"ssid":"MyNetwork","password":"pass123"}}""");
105
106 _networkMock.Verify(n => n.ConnectToWifi(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
107 }
108
109 /// <summary>
110 /// Verifies that a NextDesktop command dispatched through <see cref="ActionDispatcher.Create"/> reaches the virtual desktop service.
111 /// </summary>
112 [Fact]
113 public void Dispatch_NextDesktop_ReachesVirtualDesktopService()
114 {
115 Dispatch("""{"actionName":"NextDesktop","parameters":{}}""");
116
117 _virtualDesktopMock.Verify(v => v.NextDesktop(), Times.Once);
118 }
119
120 /// <summary>
121 /// Verifies that a SetThemeMode command dispatched through <see cref="ActionDispatcher.Create"/> reaches the registry service.
122 /// </summary>
123 [Fact]
124 public void Dispatch_SetThemeMode_ReachesRegistryService()
125 {
126 Dispatch("""{"actionName":"SetThemeMode","parameters":{"mode":"dark"}}""");
127
128 _registryMock.Verify(r => r.SetValue(
129 It.IsAny<string>(), "AppsUseLightTheme", 0, It.IsAny<Microsoft.Win32.RegistryValueKind>()), Times.Once);
130 }
131
132 /// <summary>
133 /// Verifies that an unknown command does not throw and logs a debug message.
134 /// </summary>
135 [Fact]
136 public void Dispatch_UnknownCommand_DoesNotThrow()
137 {
138 var ex = Record.Exception(() => Dispatch("""{"actionName":"NonExistentCommand","parameters":{}}"""));
139
140 Assert.Null(ex);
141 }
142
143 /// <summary>
144 /// Verifies that multiple commands dispatched separately all reach their services.
145 /// </summary>
146 [Fact]
147 public void Dispatch_MultipleCommands_AllReachServices()
148 {
149 _audioMock.Setup(a => a.GetVolume()).Returns(50);
150
151 Dispatch("""{"actionName":"Volume","parameters":{"targetVolume":80}}""");
152 Dispatch("""{"actionName":"Mute","parameters":{"on":false}}""");
153
154 _audioMock.Verify(a => a.SetVolume(80), Times.Once);
155 _audioMock.Verify(a => a.SetMute(false), Times.Once);
156 }
157
158 /// <summary>
159 /// Verifies that quit stops processing and returns null.
160 /// </summary>
161 [Fact]
162 public void Dispatch_Quit_ReturnsQuitResult()
163 {
164 ActionResult result = _dispatcher.Dispatch(JsonDocument.Parse("""{"actionName":"quit","parameters":{}}""").RootElement);
165
166 Assert.NotNull(result);
167 Assert.True(result.Success);
168 Assert.True(result.IsQuit);
169 }
170
171 private void Dispatch(string json)
172 {
173 _dispatcher.Dispatch(JsonDocument.Parse(json).RootElement);
174 }
175
176 // --- Schema wiring validation ---
177
178 /// <summary>
179 /// Verifies that every action defined in the .pas.json schemas has a registered C# handler.
180 /// This test fails when a new action is added to a TypeScript schema but not wired in C#.
181 /// </summary>
182 [Fact]
183 public void AllSchemaActions_HaveRegisteredHandlers()
184 {
185 var schemaActions = LoadRealSchemaActions();
186 Assert.True(schemaActions.Count > 0, "No schema actions loaded — .pas.json files must be present after build");
187
188 var (missingHandlers, _) = SchemaValidator.FindMismatches(schemaActions, _dispatcher.RegisteredActions);
189
190 Assert.True(
191 missingHandlers.Count == 0,
192 $"Schema actions without C# handlers: {string.Join(", ", missingHandlers)}");
193 }
194
195 /// <summary>
196 /// Actions registered in C# handlers that intentionally have no .pas.json schema definition
197 /// (query/utility actions that take no parameters from the LLM).
198 /// </summary>
199 private static readonly System.Collections.Generic.HashSet<string> SchemalessActions = new()
200 {
201 "ListAppNames",
202 "ListThemes",
203 "ListWifiNetworks",
204 "ListResolutions",
205 "DisplayResolutionAndAspectRatio",
206 };
207
208 /// <summary>
209 /// Verifies that every registered C# handler action has a matching .pas.json schema definition.
210 /// Known schemaless actions (query/utility) are excluded from the check.
211 /// </summary>
212 [Fact]
213 public void AllRegisteredHandlers_HaveSchemaDefinitions()
214 {
215 var schemaActions = LoadRealSchemaActions();
216 Assert.True(schemaActions.Count > 0, "No schema actions loaded — .pas.json files must be present after build");
217
218 var (_, missingSchemas) = SchemaValidator.FindMismatches(schemaActions, _dispatcher.RegisteredActions);
219 missingSchemas.RemoveAll(a => SchemalessActions.Contains(a));
220
221 Assert.True(
222 missingSchemas.Count == 0,
223 $"Handler actions without schema definitions: {string.Join(", ", missingSchemas)}");
224 }
225
226 private static System.Collections.Generic.HashSet<string> LoadRealSchemaActions()
227 {
228 var validator = new SchemaValidator(new Logging.NullLogger());
229 // From test output (autoShell.Tests/bin/Debug/net8.0-windows/) we need 5 levels up to repo root
230 var schemaDir = System.IO.Path.Combine(
231 AppContext.BaseDirectory, "..", "..", "..", "..", "..",
232 "ts", "packages", "agents", "desktop", "dist");
233 return validator.LoadActionNames(schemaDir);
234 }
235}
236