Skip to content

Commit 636660a

Browse files
committed
Added tests for the --dry-run command line option
Added test and input validation for the mutually exclusive --backup and --dry-run command line options
1 parent feb9365 commit 636660a

13 files changed

Lines changed: 359 additions & 112 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change Log
22

3+
## TBD
4+
5+
- Added input validation for the mutually exclusive --backup and --dry-run command line options
6+
37
## v1.0.1103.27 (November 3<sup>rd</sup>, 2024)
48
- Fixed [#5](https://github.com/icnocop/PackageReferenceVersionToAttribute/issues/5) - Fixed assembly binding redirection error which may occur when loading the extension.
59

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// <copyright file="ProjectConverterOptionsValidator.cs" company="Rami Abughazaleh">
2+
// Copyright (c) Rami Abughazaleh. All rights reserved.
3+
// </copyright>
4+
5+
namespace PackageReferenceVersionToAttribute
6+
{
7+
using Microsoft.Extensions.Options;
8+
9+
/// <summary>
10+
/// Validates options for the ProjectConverter, ensuring that configuration values
11+
/// in <see cref="ProjectConverterOptions"/> are set correctly.
12+
/// </summary>
13+
public class ProjectConverterOptionsValidator : IValidateOptions<ProjectConverterOptions>
14+
{
15+
/// <inheritdoc/>
16+
public ValidateOptionsResult Validate(string name, ProjectConverterOptions options)
17+
{
18+
if (options.DryRun && options.Backup)
19+
{
20+
return ValidateOptionsResult.Fail("Backup cannot be enabled when Dry Run is active.");
21+
}
22+
23+
return ValidateOptionsResult.Success;
24+
}
25+
}
26+
}

src/PackageReferenceVersionToAttributeTool/FilePatternMatcher.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace PackageReferenceVersionToAttributeTool
66
{
77
using System.Collections.Generic;
88
using System.IO;
9+
using System.Linq;
910
using Microsoft.Extensions.FileSystemGlobbing;
1011
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
1112

src/PackageReferenceVersionToAttributeTool/NullSourceControlService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PackageReferenceVersionToAttributeTool
66
{
7+
using System.Threading.Tasks;
78
using PackageReferenceVersionToAttribute;
89

910
/// <summary>

src/PackageReferenceVersionToAttributeTool/PackageReferenceVersionToAttributeTool.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
55
<TargetFramework>net8.0</TargetFramework>
6-
<ImplicitUsings>enable</ImplicitUsings>
7-
<Nullable>enable</Nullable>
86
<GenerateDocumentationFile>true</GenerateDocumentationFile>
97
<PackAsTool>true</PackAsTool>
108
<ToolCommandName>PackageReferenceVersionToAttribute.Tool</ToolCommandName>

src/PackageReferenceVersionToAttributeTool/Program.cs

Lines changed: 3 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,8 @@
44

55
namespace PackageReferenceVersionToAttributeTool
66
{
7-
using System;
87
using System.CommandLine;
9-
using System.CommandLine.NamingConventionBinder;
10-
using Microsoft.Extensions.DependencyInjection;
11-
using Microsoft.Extensions.Logging;
12-
using Microsoft.Extensions.Options;
13-
using PackageReferenceVersionToAttribute;
8+
using System.Threading.Tasks;
149

1510
/// <summary>
1611
/// The entry point for the application.
@@ -27,92 +22,9 @@ internal class Program
2722
/// </returns>
2823
internal static async Task<int> Main(string[] args)
2924
{
30-
var pathArgument = new Argument<string[]>(
31-
name: "inputs",
32-
description: "The project files or wildcard patterns to convert.")
33-
{
34-
Arity = ArgumentArity.OneOrMore,
35-
};
25+
ProgramCommand command = new ProgramCommand();
3626

37-
var backupOption = new Option<bool>(
38-
aliases: ["--backup", "-b"],
39-
description: "Create a backup of the project files.");
40-
41-
var forceOption = new Option<bool>(
42-
aliases: ["--force", "-f"],
43-
description: "Force conversion even if the project files are read-only.");
44-
45-
var dryRunOption = new Option<bool>(
46-
aliases: ["--dry-run", "-d"],
47-
description: "Preview changes without making any modifications.");
48-
49-
var rootCommand = new RootCommand
50-
{
51-
pathArgument,
52-
backupOption,
53-
forceOption,
54-
dryRunOption,
55-
};
56-
57-
// Set the handler for the command
58-
rootCommand.Handler = CommandHandler.Create(
59-
async (string[] inputs, bool backup, bool force, bool dryRun) =>
60-
{
61-
// Bind command line arguments
62-
var options = new ProjectConverterOptions
63-
{
64-
Backup = backup,
65-
Force = force,
66-
DryRun = dryRun,
67-
};
68-
69-
foreach (var input in inputs)
70-
{
71-
await ConvertPackageReferencesAsync(input, options);
72-
}
73-
});
74-
75-
// Invoke the command line parser
76-
return await rootCommand.InvokeAsync(args);
77-
}
78-
79-
private static async Task ConvertPackageReferencesAsync(
80-
string input, ProjectConverterOptions options)
81-
{
82-
FilePatternMatcher filePatternMatcher = new();
83-
List<string> matchingFiles = filePatternMatcher.GetMatchingFiles(input);
84-
if (matchingFiles.Count == 0)
85-
{
86-
Console.WriteLine($"No matching files found for pattern: {input}");
87-
return;
88-
}
89-
90-
if (options.Backup)
91-
{
92-
Console.WriteLine("Backup option is enabled.");
93-
}
94-
95-
if (options.Force)
96-
{
97-
Console.WriteLine("Force option is enabled.");
98-
}
99-
100-
if (options.DryRun)
101-
{
102-
Console.WriteLine("Dry run mode is enabled. No changes will be made.");
103-
}
104-
105-
using var serviceProvider = new ServiceCollection()
106-
.AddSingleton(Options.Create(options))
107-
.AddLogging(configure => configure.AddConsole())
108-
.AddSingleton<ProjectConverter>()
109-
.AddSingleton<IFileService, FileService>()
110-
.AddSingleton<ISourceControlService, NullSourceControlService>()
111-
.BuildServiceProvider();
112-
113-
var projectConverter = serviceProvider.GetRequiredService<ProjectConverter>();
114-
115-
await projectConverter.ConvertAsync(matchingFiles);
27+
return await command.InvokeAsync(args);
11628
}
11729
}
11830
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// <copyright file="ProgramCommand.cs" company="Rami Abughazaleh">
2+
// Copyright (c) Rami Abughazaleh. All rights reserved.
3+
// </copyright>
4+
5+
namespace PackageReferenceVersionToAttributeTool
6+
{
7+
using System;
8+
using System.Collections.Generic;
9+
using System.CommandLine;
10+
using System.CommandLine.NamingConventionBinder;
11+
using System.Reflection;
12+
using System.Threading.Tasks;
13+
using Microsoft.Extensions.DependencyInjection;
14+
using Microsoft.Extensions.Logging;
15+
using PackageReferenceVersionToAttribute;
16+
17+
/// <summary>
18+
/// Program command.
19+
/// </summary>
20+
internal class ProgramCommand : RootCommand
21+
{
22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="ProgramCommand"/> class.
24+
/// </summary>
25+
public ProgramCommand()
26+
: base("Converts PackageReference Version child elements to attributes in C# projects.")
27+
{
28+
var inputsArgument = new Argument<string[]>(
29+
name: "inputs",
30+
description: "The project files or wildcard patterns to convert.")
31+
{
32+
Arity = ArgumentArity.OneOrMore,
33+
};
34+
35+
var backupOption = new Option<bool>(
36+
aliases: ["--backup", "-b"],
37+
description: "Create a backup of the project files.");
38+
39+
var forceOption = new Option<bool>(
40+
aliases: ["--force", "-f"],
41+
description: "Force conversion even if the project files are read-only.");
42+
43+
var dryRunOption = new Option<bool>(
44+
aliases: ["--dry-run", "-d"],
45+
description: "Preview changes without making any modifications.");
46+
47+
var versionOption = new Option<bool>("--version", "Display the version information.");
48+
49+
this.Add(inputsArgument);
50+
this.Add(backupOption);
51+
this.Add(forceOption);
52+
this.Add(dryRunOption);
53+
54+
// Add validation
55+
this.AddValidator(result =>
56+
{
57+
bool backup = result.GetValueForOption(backupOption);
58+
bool force = result.GetValueForOption(forceOption);
59+
bool dryRun = result.GetValueForOption(dryRunOption);
60+
61+
var options = new ProjectConverterOptions
62+
{
63+
Backup = backup,
64+
Force = force,
65+
DryRun = dryRun,
66+
};
67+
68+
var validator = new ProjectConverterOptionsValidator();
69+
var validationResult = validator.Validate(nameof(ProjectConverterOptions), options);
70+
if (validationResult.Failed)
71+
{
72+
result.ErrorMessage = validationResult.FailureMessage;
73+
}
74+
});
75+
76+
// Set the handler for the command
77+
this.Handler = CommandHandler.Create<ProgramCommandLineOptions>(async (options) =>
78+
{
79+
if (options.Version)
80+
{
81+
var version = Assembly.GetExecutingAssembly().GetName().Version;
82+
Console.WriteLine($"Version: {version}");
83+
return;
84+
}
85+
86+
foreach (var input in options.Inputs)
87+
{
88+
await ConvertPackageReferencesAsync(input, options);
89+
}
90+
});
91+
}
92+
93+
private static async Task ConvertPackageReferencesAsync(
94+
string input, ProjectConverterOptions options)
95+
{
96+
FilePatternMatcher filePatternMatcher = new();
97+
List<string> matchingFiles = filePatternMatcher.GetMatchingFiles(input);
98+
if (matchingFiles.Count == 0)
99+
{
100+
Console.WriteLine($"No matching files found for pattern: {input}");
101+
return;
102+
}
103+
104+
if (options.Backup)
105+
{
106+
Console.WriteLine("Backup option is enabled.");
107+
}
108+
109+
if (options.Force)
110+
{
111+
Console.WriteLine("Force option is enabled.");
112+
}
113+
114+
if (options.DryRun)
115+
{
116+
Console.WriteLine("Dry run mode is enabled. No changes will be made.");
117+
}
118+
119+
using var serviceProvider = new ServiceCollection()
120+
.AddSingleton(Microsoft.Extensions.Options.Options.Create(options))
121+
.AddLogging(configure => configure.AddConsole())
122+
.AddSingleton<ProjectConverter>()
123+
.AddSingleton<IFileService, FileService>()
124+
.AddSingleton<ISourceControlService, NullSourceControlService>()
125+
.BuildServiceProvider();
126+
127+
var projectConverter = serviceProvider.GetRequiredService<ProjectConverter>();
128+
129+
await projectConverter.ConvertAsync(matchingFiles);
130+
}
131+
}
132+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// <copyright file="ProgramCommandLineOptions.cs" company="Rami Abughazaleh">
2+
// Copyright (c) Rami Abughazaleh. All rights reserved.
3+
// </copyright>
4+
5+
namespace PackageReferenceVersionToAttributeTool
6+
{
7+
using System.Collections.Generic;
8+
using PackageReferenceVersionToAttribute;
9+
10+
/// <summary>
11+
/// Program command line options.
12+
/// </summary>
13+
internal class ProgramCommandLineOptions : ProjectConverterOptions
14+
{
15+
/// <summary>
16+
/// Gets or sets the project files or wildcard patterns to convert.
17+
/// </summary>
18+
public IEnumerable<string> Inputs { get; set; }
19+
20+
/// <summary>
21+
/// Gets or sets a value indicating whether to display the version information of the application.
22+
/// </summary>
23+
public bool Version { get; set; }
24+
}
25+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// <copyright file="DryRunAndBackupTests.cs" company="Rami Abughazaleh">
2+
// Copyright (c) Rami Abughazaleh. All rights reserved.
3+
// </copyright>
4+
5+
namespace PackageReferenceVersionToAttributeToolTests
6+
{
7+
using Microsoft.VisualStudio.TestTools.UnitTesting;
8+
using PackageReferenceVersionToAttributeTool;
9+
using PackageReferenceVersionToAttributeToolTests.FileSystem;
10+
using static PackageReferenceVersionToAttributeToolTests.ToolRunner.ToolRunner;
11+
12+
/// <summary>
13+
/// Contains unit tests for verifying the expected behavior
14+
/// for the `--dry-run` and `--backup` parameters of the tool,
15+
/// when used together.
16+
/// </summary>
17+
[TestClass]
18+
public class DryRunAndBackupTests
19+
{
20+
/// <summary>
21+
/// Verifies that a call to <see cref="Program.Main"/>
22+
/// with the `--dry-run` and `--backup` parameters,
23+
/// fails with a validation error.
24+
/// </summary>
25+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
26+
[TestMethod]
27+
public async Task Run_WithDryRunAndBackupParameters_FailsWithValidationError()
28+
{
29+
// Arrange
30+
const string Contents = """
31+
<Project Sdk="Microsoft.NET.Sdk">
32+
<PropertyGroup>
33+
<TargetFramework>net8.0</TargetFramework>
34+
</PropertyGroup>
35+
<ItemGroup>
36+
<PackageReference Include="PackageA">
37+
<Version>1.2.3</Version>
38+
</PackageReference>
39+
</ItemGroup>
40+
</Project>
41+
""";
42+
using var projectA = new TempFile(
43+
"ProjectA.csproj",
44+
Contents);
45+
46+
using var testDirectory = new TempDir
47+
{
48+
projectA,
49+
};
50+
51+
var usage = await File.ReadAllTextAsync("Usage.txt");
52+
53+
// Act
54+
var result = await RunToolAsync($"\"{projectA.Path}\" --dry-run --backup");
55+
56+
// Assert
57+
Assert.AreEqual(1, result.ExitCode, result.OutputAndError);
58+
Assert.AreEqual(usage, result.Output.Trim(), result.OutputAndError);
59+
Assert.AreEqual(
60+
"Backup cannot be enabled when Dry Run is active.",
61+
result.Error.Trim());
62+
63+
Assert.IsFalse(File.Exists($"{projectA.Path}.bak"));
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)