microsoft/TypeAgent
Publicmirrored fromhttps://github.com/microsoft/TypeAgentAvailable
dotnet/autoShell.Tests/EndToEndTests.cs
273lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | using System.Text.Json; |
| 5 | |
| 6 | namespace autoShell.Tests; |
| 7 | |
| 8 | /// <summary> |
| 9 | /// End-to-end tests that launch autoShell.exe as a child process and communicate |
| 10 | /// via the stdin/stdout JSON protocol. Tests the full pipeline including process |
| 11 | /// startup, JSON parsing, command dispatch, and response serialization. |
| 12 | /// </summary> |
| 13 | [Trait("Category", "E2E")] |
| 14 | public sealed class EndToEndTests : IDisposable |
| 15 | { |
| 16 | private readonly AutoShellProcess _process; |
| 17 | |
| 18 | public EndToEndTests() |
| 19 | { |
| 20 | _process = AutoShellProcess.StartInteractive(); |
| 21 | } |
| 22 | |
| 23 | public void Dispose() |
| 24 | { |
| 25 | _process.Dispose(); |
| 26 | } |
| 27 | |
| 28 | // --- Query commands (assert JSON stdout) --- |
| 29 | |
| 30 | /// <summary> |
| 31 | /// Verifies that ListAppNames returns a valid JSON array of app names via stdout. |
| 32 | /// </summary> |
| 33 | [Fact] |
| 34 | public async Task ListAppNames_ReturnsJsonArray() |
| 35 | { |
| 36 | _process.SendCommand("""{"actionName":"ListAppNames","parameters":{}}"""); |
| 37 | |
| 38 | string? response = await _process.ReadLineAsync(); |
| 39 | |
| 40 | Assert.NotNull(response); |
| 41 | var result = JsonDocument.Parse(response).RootElement; |
| 42 | Assert.True(result.GetProperty("success").GetBoolean()); |
| 43 | var data = result.GetProperty("data"); |
| 44 | Assert.True(data.GetArrayLength() > 0); |
| 45 | } |
| 46 | |
| 47 | /// <summary> |
| 48 | /// Verifies that ListThemes returns a valid JSON array of theme file paths via stdout. |
| 49 | /// </summary> |
| 50 | [Fact] |
| 51 | public async Task ListThemes_ReturnsJsonArray() |
| 52 | { |
| 53 | _process.SendCommand("""{"actionName":"ListThemes","parameters":{}}"""); |
| 54 | |
| 55 | // Theme scanning involves disk I/O; allow extra time |
| 56 | string? response = await _process.ReadLineAsync(10000); |
| 57 | |
| 58 | Assert.NotNull(response); |
| 59 | var result = JsonDocument.Parse(response).RootElement; |
| 60 | Assert.True(result.GetProperty("success").GetBoolean()); |
| 61 | var data = result.GetProperty("data"); |
| 62 | Assert.True(data.GetArrayLength() > 0); |
| 63 | } |
| 64 | |
| 65 | /// <summary> |
| 66 | /// Verifies that multiple sequential query commands each return a separate response. |
| 67 | /// </summary> |
| 68 | [Fact] |
| 69 | public async Task MultipleQueries_EachReturnsResponse() |
| 70 | { |
| 71 | _process.SendCommand("""{"actionName":"ListAppNames","parameters":{}}"""); |
| 72 | string? response1 = await _process.ReadLineAsync(); |
| 73 | |
| 74 | _process.SendCommand("""{"actionName":"ListThemes","parameters":{}}"""); |
| 75 | string? response2 = await _process.ReadLineAsync(); |
| 76 | |
| 77 | Assert.NotNull(response1); |
| 78 | Assert.NotNull(response2); |
| 79 | _ = JsonDocument.Parse(response1); |
| 80 | _ = JsonDocument.Parse(response2); |
| 81 | } |
| 82 | |
| 83 | /// </summary> |
| 84 | [Fact] |
| 85 | public async Task ListResolutions_ReturnsResponse() |
| 86 | { |
| 87 | _process.SendCommand("""{"actionName":"ListResolutions","parameters":{}}"""); |
| 88 | |
| 89 | string? response = await _process.ReadLineAsync(10000); |
| 90 | |
| 91 | Assert.NotNull(response); |
| 92 | Assert.NotEmpty(response); |
| 93 | } |
| 94 | |
| 95 | /// <summary> |
| 96 | /// Verifies that ListWifiNetworks returns a response via stdout. |
| 97 | /// </summary> |
| 98 | [Fact] |
| 99 | public async Task ListWifiNetworks_ReturnsResponse() |
| 100 | { |
| 101 | _process.SendCommand("""{"actionName":"ListWifiNetworks","parameters":{}}"""); |
| 102 | |
| 103 | string? response = await _process.ReadLineAsync(10000); |
| 104 | |
| 105 | Assert.NotNull(response); |
| 106 | Assert.NotEmpty(response); |
| 107 | } |
| 108 | |
| 109 | /// <summary> |
| 110 | /// Verifies that SetScreenResolution with an invalid value produces a response without crashing. |
| 111 | /// Uses an intentionally invalid resolution to avoid changing the actual display. |
| 112 | /// </summary> |
| 113 | [Fact] |
| 114 | public async Task SetScreenResolution_InvalidValue_ReturnsResponse() |
| 115 | { |
| 116 | _process.SendCommand("""{"actionName":"SetScreenResolution","parameters":{"width":99999,"height":99999}}"""); |
| 117 | |
| 118 | // May produce an error message or status — just verify the process survives |
| 119 | // and we can still send commands |
| 120 | await _process.ReadLineAsync(); |
| 121 | |
| 122 | _process.SendCommand("""{"actionName":"ListAppNames","parameters":{}}"""); |
| 123 | string? response = await _process.ReadLineAsync(); |
| 124 | |
| 125 | Assert.False(_process.HasExited); |
| 126 | Assert.NotNull(response); |
| 127 | } |
| 128 | |
| 129 | // --- Protocol edge cases --- |
| 130 | |
| 131 | /// <summary> |
| 132 | /// Verifies that two separate commands each produce a response. |
| 133 | /// </summary> |
| 134 | [Fact] |
| 135 | public async Task MultiCommandObject_ProducesMultipleResponses() |
| 136 | { |
| 137 | _process.SendCommand("""{"actionName":"ListAppNames","parameters":{}}"""); |
| 138 | _process.SendCommand("""{"actionName":"ListThemes","parameters":{}}"""); |
| 139 | |
| 140 | string? response1 = await _process.ReadLineAsync(); |
| 141 | string? response2 = await _process.ReadLineAsync(10000); |
| 142 | |
| 143 | Assert.NotNull(response1); |
| 144 | Assert.NotNull(response2); |
| 145 | _ = JsonDocument.Parse(response1); |
| 146 | _ = JsonDocument.Parse(response2); |
| 147 | } |
| 148 | |
| 149 | /// <summary> |
| 150 | /// Verifies that sending a quit command causes the process to exit. |
| 151 | /// </summary> |
| 152 | [Fact] |
| 153 | public async Task Quit_StopsMidBatch() |
| 154 | { |
| 155 | _process.SendCommand("""{"actionName":"ListAppNames","parameters":{}}"""); |
| 156 | string? response1 = await _process.ReadLineAsync(); |
| 157 | Assert.NotNull(response1); |
| 158 | _ = JsonDocument.Parse(response1); |
| 159 | |
| 160 | _process.SendCommand("""{"actionName":"quit","parameters":{}}"""); |
| 161 | _process.WaitForExit(10000); |
| 162 | Assert.True(_process.HasExited); |
| 163 | } |
| 164 | |
| 165 | /// <summary> |
| 166 | /// Verifies that sending {"quit":""} causes the process to exit cleanly. |
| 167 | /// </summary> |
| 168 | [Fact] |
| 169 | public void Quit_ProcessExits() |
| 170 | { |
| 171 | _process.SendQuit(); |
| 172 | |
| 173 | Assert.True(_process.HasExited); |
| 174 | } |
| 175 | |
| 176 | /// <summary> |
| 177 | /// Verifies that malformed JSON does not crash the process. |
| 178 | /// Sends invalid JSON followed by a valid query to confirm the process is still alive. |
| 179 | /// </summary> |
| 180 | [Fact] |
| 181 | public async Task MalformedJson_ProcessSurvives() |
| 182 | { |
| 183 | _process.SendCommand("this is not json"); |
| 184 | |
| 185 | // Process should still be alive — send a valid command to verify |
| 186 | _process.SendCommand("""{"actionName":"ListAppNames","parameters":{}}"""); |
| 187 | string? response = await _process.ReadLineAsync(); |
| 188 | |
| 189 | Assert.False(_process.HasExited); |
| 190 | Assert.NotNull(response); |
| 191 | } |
| 192 | |
| 193 | /// <summary> |
| 194 | /// Verifies that an empty line does not crash the process. |
| 195 | /// The error message goes to stdout (known protocol limitation), so we |
| 196 | /// consume it before verifying the process is still responsive. |
| 197 | /// </summary> |
| 198 | [Fact] |
| 199 | public async Task EmptyLine_ProcessSurvives() |
| 200 | { |
| 201 | _process.SendCommand(""); |
| 202 | // Consume the error/status message that goes to stdout. |
| 203 | // Allow extra time since empty-line handling may be slow. |
| 204 | string? errorLine = await _process.ReadLineAsync(); |
| 205 | |
| 206 | // Ensure the first read completed before starting a second one |
| 207 | Assert.False(_process.HasExited); |
| 208 | |
| 209 | _process.SendCommand("""{"actionName":"ListAppNames","parameters":{}}"""); |
| 210 | string? response = await _process.ReadLineAsync(); |
| 211 | |
| 212 | Assert.False(_process.HasExited); |
| 213 | Assert.NotNull(response); |
| 214 | } |
| 215 | |
| 216 | /// <summary> |
| 217 | /// Verifies that an unknown command does not crash the process. |
| 218 | /// </summary> |
| 219 | [Fact] |
| 220 | public async Task UnknownCommand_ProcessSurvives() |
| 221 | { |
| 222 | _process.SendCommand("""{"actionName":"NonExistentCommand","parameters":{}}"""); |
| 223 | |
| 224 | _process.SendCommand("""{"actionName":"ListAppNames","parameters":{}}"""); |
| 225 | string? response = await _process.ReadLineAsync(); |
| 226 | |
| 227 | Assert.False(_process.HasExited); |
| 228 | Assert.NotNull(response); |
| 229 | } |
| 230 | |
| 231 | /// <summary> |
| 232 | /// Verifies that closing stdin (EOF) causes the process to exit cleanly. |
| 233 | /// </summary> |
| 234 | [Fact] |
| 235 | public void StdinClosed_ProcessExits() |
| 236 | { |
| 237 | _process.CloseStdin(); |
| 238 | _process.WaitForExit(5000); |
| 239 | |
| 240 | Assert.True(_process.HasExited); |
| 241 | } |
| 242 | |
| 243 | // --- Command-line mode --- |
| 244 | |
| 245 | /// <summary> |
| 246 | /// Verifies that passing a single JSON command as a command-line argument |
| 247 | /// executes it and exits (non-interactive mode). |
| 248 | /// </summary> |
| 249 | [Fact] |
| 250 | public void CommandLineMode_SingleObject_ExecutesAndExits() |
| 251 | { |
| 252 | var (output, exitCode) = AutoShellProcess.RunWithArgs( |
| 253 | """{"actionName":"ListAppNames","parameters":{}}"""); |
| 254 | |
| 255 | Assert.Equal(0, exitCode); |
| 256 | Assert.NotEmpty(output); |
| 257 | _ = JsonDocument.Parse(output.Trim()); |
| 258 | } |
| 259 | |
| 260 | /// <summary> |
| 261 | /// Verifies that passing a JSON array of commands as command-line arguments |
| 262 | /// executes all of them and exits. |
| 263 | /// </summary> |
| 264 | [Fact] |
| 265 | public void CommandLineMode_JsonArray_ExecutesAllAndExits() |
| 266 | { |
| 267 | var (output, exitCode) = AutoShellProcess.RunWithArgs( |
| 268 | """[{"actionName":"ListAppNames","parameters":{}},{"actionName":"ListThemes","parameters":{}}]"""); |
| 269 | |
| 270 | Assert.Equal(0, exitCode); |
| 271 | Assert.NotEmpty(output); |
| 272 | } |
| 273 | } |
| 274 | |