microsoft/TypeAgent
Publicmirrored fromhttps://github.com/microsoft/TypeAgentAvailable
dotnet/autoShell.Tests/EndToEndTests.cs
269lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | using Newtonsoft.Json.Linq; |
| 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("""{"ListAppNames":""}"""); |
| 37 | |
| 38 | string? response = await _process.ReadLineAsync(); |
| 39 | |
| 40 | Assert.NotNull(response); |
| 41 | var array = JArray.Parse(response); |
| 42 | Assert.NotEmpty(array); |
| 43 | } |
| 44 | |
| 45 | /// <summary> |
| 46 | /// Verifies that ListThemes returns a valid JSON array of theme file paths via stdout. |
| 47 | /// </summary> |
| 48 | [Fact] |
| 49 | public async Task ListThemes_ReturnsJsonArray() |
| 50 | { |
| 51 | _process.SendCommand("""{"ListThemes":""}"""); |
| 52 | |
| 53 | // Theme scanning involves disk I/O; allow extra time |
| 54 | string? response = await _process.ReadLineAsync(10000); |
| 55 | |
| 56 | Assert.NotNull(response); |
| 57 | var array = JArray.Parse(response); |
| 58 | Assert.NotEmpty(array); |
| 59 | } |
| 60 | |
| 61 | /// <summary> |
| 62 | /// Verifies that multiple sequential query commands each return a separate response. |
| 63 | /// </summary> |
| 64 | [Fact] |
| 65 | public async Task MultipleQueries_EachReturnsResponse() |
| 66 | { |
| 67 | _process.SendCommand("""{"ListAppNames":""}"""); |
| 68 | string? response1 = await _process.ReadLineAsync(); |
| 69 | |
| 70 | _process.SendCommand("""{"ListThemes":""}"""); |
| 71 | string? response2 = await _process.ReadLineAsync(); |
| 72 | |
| 73 | Assert.NotNull(response1); |
| 74 | Assert.NotNull(response2); |
| 75 | JArray.Parse(response1); |
| 76 | JArray.Parse(response2); |
| 77 | } |
| 78 | |
| 79 | /// <summary> |
| 80 | /// Verifies that ListResolutions returns a valid JSON string via stdout. |
| 81 | /// </summary> |
| 82 | [Fact] |
| 83 | public async Task ListResolutions_ReturnsResponse() |
| 84 | { |
| 85 | _process.SendCommand("""{"ListResolutions":""}"""); |
| 86 | |
| 87 | string? response = await _process.ReadLineAsync(10000); |
| 88 | |
| 89 | Assert.NotNull(response); |
| 90 | Assert.NotEmpty(response); |
| 91 | } |
| 92 | |
| 93 | /// <summary> |
| 94 | /// Verifies that ListWifiNetworks returns a response via stdout. |
| 95 | /// </summary> |
| 96 | [Fact] |
| 97 | public async Task ListWifiNetworks_ReturnsResponse() |
| 98 | { |
| 99 | _process.SendCommand("""{"ListWifiNetworks":""}"""); |
| 100 | |
| 101 | string? response = await _process.ReadLineAsync(10000); |
| 102 | |
| 103 | Assert.NotNull(response); |
| 104 | Assert.NotEmpty(response); |
| 105 | } |
| 106 | |
| 107 | /// <summary> |
| 108 | /// Verifies that SetScreenResolution with an invalid value produces a response without crashing. |
| 109 | /// Uses an intentionally invalid resolution to avoid changing the actual display. |
| 110 | /// </summary> |
| 111 | [Fact] |
| 112 | public async Task SetScreenResolution_InvalidValue_ReturnsResponse() |
| 113 | { |
| 114 | _process.SendCommand("""{"SetScreenResolution":"99999x99999"}"""); |
| 115 | |
| 116 | // May produce an error message or status — just verify the process survives |
| 117 | // and we can still send commands |
| 118 | await _process.ReadLineAsync(5000); |
| 119 | |
| 120 | _process.SendCommand("""{"ListAppNames":""}"""); |
| 121 | string? response = await _process.ReadLineAsync(); |
| 122 | |
| 123 | Assert.False(_process.HasExited); |
| 124 | Assert.NotNull(response); |
| 125 | } |
| 126 | |
| 127 | // --- Protocol edge cases --- |
| 128 | |
| 129 | /// <summary> |
| 130 | /// Verifies that multiple commands in a single JSON object each produce stdout output. |
| 131 | /// </summary> |
| 132 | [Fact] |
| 133 | public async Task MultiCommandObject_ProducesMultipleResponses() |
| 134 | { |
| 135 | _process.SendCommand("""{"ListAppNames":"", "ListThemes":""}"""); |
| 136 | |
| 137 | string? response1 = await _process.ReadLineAsync(); |
| 138 | string? response2 = await _process.ReadLineAsync(); |
| 139 | |
| 140 | Assert.NotNull(response1); |
| 141 | Assert.NotNull(response2); |
| 142 | // One should be app names, the other themes — both valid JSON arrays |
| 143 | JArray.Parse(response1); |
| 144 | JArray.Parse(response2); |
| 145 | } |
| 146 | |
| 147 | /// <summary> |
| 148 | /// Verifies that quit stops processing mid-batch. Commands after quit should not execute. |
| 149 | /// </summary> |
| 150 | [Fact] |
| 151 | public async Task Quit_StopsMidBatch() |
| 152 | { |
| 153 | // ListAppNames produces output, quit should stop before ListThemes runs |
| 154 | _process.SendCommand("""{"ListAppNames":"", "quit":"", "ListThemes":""}"""); |
| 155 | |
| 156 | string? response1 = await _process.ReadLineAsync(); |
| 157 | Assert.NotNull(response1); |
| 158 | JArray.Parse(response1); |
| 159 | |
| 160 | // Process should have exited — no second response |
| 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("""{"ListAppNames":""}"""); |
| 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 message that goes to stdout |
| 203 | await _process.ReadLineAsync(); |
| 204 | |
| 205 | _process.SendCommand("""{"ListAppNames":""}"""); |
| 206 | string? response = await _process.ReadLineAsync(); |
| 207 | |
| 208 | Assert.False(_process.HasExited); |
| 209 | Assert.NotNull(response); |
| 210 | } |
| 211 | |
| 212 | /// <summary> |
| 213 | /// Verifies that an unknown command does not crash the process. |
| 214 | /// </summary> |
| 215 | [Fact] |
| 216 | public async Task UnknownCommand_ProcessSurvives() |
| 217 | { |
| 218 | _process.SendCommand("""{"NonExistentCommand":"value"}"""); |
| 219 | |
| 220 | _process.SendCommand("""{"ListAppNames":""}"""); |
| 221 | string? response = await _process.ReadLineAsync(); |
| 222 | |
| 223 | Assert.False(_process.HasExited); |
| 224 | Assert.NotNull(response); |
| 225 | } |
| 226 | |
| 227 | /// <summary> |
| 228 | /// Verifies that closing stdin (EOF) causes the process to exit cleanly. |
| 229 | /// </summary> |
| 230 | [Fact] |
| 231 | public void StdinClosed_ProcessExits() |
| 232 | { |
| 233 | _process.CloseStdin(); |
| 234 | _process.WaitForExit(5000); |
| 235 | |
| 236 | Assert.True(_process.HasExited); |
| 237 | } |
| 238 | |
| 239 | // --- Command-line mode --- |
| 240 | |
| 241 | /// <summary> |
| 242 | /// Verifies that passing a single JSON command as a command-line argument |
| 243 | /// executes it and exits (non-interactive mode). |
| 244 | /// </summary> |
| 245 | [Fact] |
| 246 | public void CommandLineMode_SingleObject_ExecutesAndExits() |
| 247 | { |
| 248 | var (output, exitCode) = AutoShellProcess.RunWithArgs( |
| 249 | """{"ListAppNames":""}"""); |
| 250 | |
| 251 | Assert.Equal(0, exitCode); |
| 252 | Assert.NotEmpty(output); |
| 253 | JArray.Parse(output.Trim()); |
| 254 | } |
| 255 | |
| 256 | /// <summary> |
| 257 | /// Verifies that passing a JSON array of commands as command-line arguments |
| 258 | /// executes all of them and exits. |
| 259 | /// </summary> |
| 260 | [Fact] |
| 261 | public void CommandLineMode_JsonArray_ExecutesAllAndExits() |
| 262 | { |
| 263 | var (output, exitCode) = AutoShellProcess.RunWithArgs( |
| 264 | """[{"ListAppNames":""},{"ListThemes":""}]"""); |
| 265 | |
| 266 | Assert.Equal(0, exitCode); |
| 267 | Assert.NotEmpty(output); |
| 268 | } |
| 269 | } |
| 270 | |