| 1 | using System.Threading.Channels; |
| 2 | using CascadeIDE.Features.Workspace.Application; |
| 3 | using CascadeIDE.Services; |
| 4 | using Xunit; |
| 5 | |
| 6 | namespace CascadeIDE.Tests; |
| 7 | |
| 8 | public sealed class BlankSolutionCreatorTests |
| 9 | { |
| 10 | private sealed class CapturingDotnetRunner : IDotnetCommandRunner |
| 11 | { |
| 12 | public List<(IReadOnlyList<string> Args, string WorkingDir)> Calls { get; } = []; |
| 13 | |
| 14 | public Task<(bool Success, int ExitCode, string Output)> RunAsync( |
| 15 | IReadOnlyList<string> args, |
| 16 | string workingDirectory, |
| 17 | CancellationToken cancellationToken = default) |
| 18 | { |
| 19 | Calls.Add((args, workingDirectory)); |
| 20 | return Task.FromResult((true, 0, "")); |
| 21 | } |
| 22 | |
| 23 | public Task<(bool Success, int ExitCode)> RunWithChunkWriterAsync( |
| 24 | IReadOnlyList<string> args, |
| 25 | string workingDirectory, |
| 26 | ChannelWriter<string> chunkWriter, |
| 27 | CancellationToken cancellationToken = default) => |
| 28 | throw new NotSupportedException(); |
| 29 | } |
| 30 | |
| 31 | /// <summary>Имитирует <c>dotnet new sln</c>: создаёт <c>{name}.sln</c> в каталоге <c>-o</c>.</summary> |
| 32 | private sealed class DotnetNewSlnSimRunner : IDotnetCommandRunner |
| 33 | { |
| 34 | public List<(IReadOnlyList<string> Args, string WorkingDir)> Calls { get; } = []; |
| 35 | |
| 36 | public Task<(bool Success, int ExitCode, string Output)> RunAsync( |
| 37 | IReadOnlyList<string> args, |
| 38 | string workingDirectory, |
| 39 | CancellationToken cancellationToken = default) |
| 40 | { |
| 41 | Calls.Add((args, workingDirectory)); |
| 42 | // new sln -n NAME -o OUT |
| 43 | var name = args.Count > 3 ? args[3] : ""; |
| 44 | var outDir = args.Count > 5 ? args[5] : ""; |
| 45 | Directory.CreateDirectory(outDir); |
| 46 | var slnPath = Path.Combine(outDir, name + ".sln"); |
| 47 | File.WriteAllText(slnPath, "Microsoft Visual Studio Solution File, Format Version 12.00\r\n"); |
| 48 | return Task.FromResult((true, 0, "")); |
| 49 | } |
| 50 | |
| 51 | public Task<(bool Success, int ExitCode)> RunWithChunkWriterAsync( |
| 52 | IReadOnlyList<string> args, |
| 53 | string workingDirectory, |
| 54 | ChannelWriter<string> chunkWriter, |
| 55 | CancellationToken cancellationToken = default) => |
| 56 | throw new NotSupportedException(); |
| 57 | } |
| 58 | |
| 59 | [Fact] |
| 60 | public async Task TryCreateAsync_Rejects_ExistingFile() |
| 61 | { |
| 62 | var tmp = Path.Combine(Path.GetTempPath(), "cide-blank-sln-test-" + Guid.NewGuid().ToString("n")); |
| 63 | Directory.CreateDirectory(tmp); |
| 64 | var sln = Path.Combine(tmp, "Already.sln"); |
| 65 | await File.WriteAllTextAsync(sln, "", CancellationToken.None); |
| 66 | |
| 67 | try |
| 68 | { |
| 69 | var r = await BlankSolutionCreator.TryCreateAsync(sln, new CapturingDotnetRunner(), CancellationToken.None); |
| 70 | Assert.False(r.Ok); |
| 71 | Assert.Contains("уже существует", r.ErrorMessage ?? "", StringComparison.Ordinal); |
| 72 | } |
| 73 | finally |
| 74 | { |
| 75 | try |
| 76 | { |
| 77 | File.Delete(sln); |
| 78 | Directory.Delete(tmp, recursive: true); |
| 79 | } |
| 80 | catch |
| 81 | { |
| 82 | // ignore |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | [Fact] |
| 88 | public async Task TryCreateAsync_Invokes_DotnetNewSln_WithNameAndOutputDir() |
| 89 | { |
| 90 | var tmp = Path.Combine(Path.GetTempPath(), "cide-blank-sln-new-" + Guid.NewGuid().ToString("n")); |
| 91 | var sln = Path.Combine(tmp, "MyApp.sln"); |
| 92 | var runner = new DotnetNewSlnSimRunner(); |
| 93 | |
| 94 | try |
| 95 | { |
| 96 | var r = await BlankSolutionCreator.TryCreateAsync(sln, runner, CancellationToken.None); |
| 97 | Assert.True(r.Ok, r.ErrorMessage); |
| 98 | Assert.Equal(sln, r.SolutionPath, StringComparer.OrdinalIgnoreCase); |
| 99 | Assert.True(File.Exists(sln)); |
| 100 | |
| 101 | var call = Assert.Single(runner.Calls); |
| 102 | Assert.Equal(tmp, call.WorkingDir, StringComparer.OrdinalIgnoreCase); |
| 103 | Assert.Equal(["new", "sln", "-n", "MyApp", "-o", tmp], call.Args); |
| 104 | } |
| 105 | finally |
| 106 | { |
| 107 | try |
| 108 | { |
| 109 | if (File.Exists(sln)) |
| 110 | File.Delete(sln); |
| 111 | if (Directory.Exists(tmp)) |
| 112 | Directory.Delete(tmp, recursive: true); |
| 113 | } |
| 114 | catch |
| 115 | { |
| 116 | // ignore |
| 117 | } |
| 118 | } |
| 119 | } |
| 120 | } |
| 121 | |