microsoft/TypeAgent

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dev/georgeng/autogen-cicd

Branches

Tags

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

Clone

HTTPS

Download ZIP

dotnet/autoShell.Tests/ActionDispatcherIntegrationTests.cs

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