Skip to content

Commit f3ec11c

Browse files
Inject IConsole into ValidatorRunner for testable console output (#75)
* Initial plan * Inject IConsole into ValidatorRunner and add console output tests Agent-Logs-Url: https://github.com/304NotModified/SLNX-validator/sessions/2ef89553-2469-46d8-8587-2c62c427fa11 Co-authored-by: 304NotModified <5808377+304NotModified@users.noreply.github.com> * Address PR feedback: simplify IConsole, rename to FakeConsole, make console required, remove SizeCapturingStream Agent-Logs-Url: https://github.com/304NotModified/SLNX-validator/sessions/d2fc4b14-e446-4e41-8704-97bcd27ae058 Co-authored-by: 304NotModified <5808377+304NotModified@users.noreply.github.com> * Rename to WriteLineAsync/WriteErrorLineAsync, rename FakeConsole lists, use instance _console field in tests Agent-Logs-Url: https://github.com/304NotModified/SLNX-validator/sessions/72e734b1-9ad7-4cb3-91ee-5cddb2c4570e Co-authored-by: 304NotModified <5808377+304NotModified@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: 304NotModified <5808377+304NotModified@users.noreply.github.com>
1 parent 65e66b2 commit f3ec11c

7 files changed

Lines changed: 87 additions & 11 deletions

File tree

src/SLNX-validator/IConsole.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace JulianVerdurmen.SlnxValidator;
2+
3+
internal interface IConsole
4+
{
5+
Task WriteLineAsync(string value);
6+
Task WriteErrorLineAsync(string value);
7+
}

src/SLNX-validator/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public static async Task<int> Main(string[] args)
8181
var services = new ServiceCollection()
8282
.AddSlnxValidator()
8383
.AddSingleton<SlnxCollector>()
84+
.AddSingleton<IConsole>(new SystemConsole())
8485
.AddSingleton<ValidatorRunner>()
8586
.BuildServiceProvider();
8687

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace JulianVerdurmen.SlnxValidator;
2+
3+
internal sealed class SystemConsole : IConsole
4+
{
5+
public Task WriteLineAsync(string value) => Console.Out.WriteLineAsync(value);
6+
public Task WriteErrorLineAsync(string value) => Console.Error.WriteLineAsync(value);
7+
}

src/SLNX-validator/ValidatorRunner.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace JulianVerdurmen.SlnxValidator;
99

10-
internal sealed class ValidatorRunner(SlnxCollector collector, ISonarReporter sonarReporter, ISarifReporter sarifReporter, IRequiredFilesChecker requiredFilesChecker, IFileSystem fileSystem)
10+
internal sealed class ValidatorRunner(SlnxCollector collector, ISonarReporter sonarReporter, ISarifReporter sarifReporter, IRequiredFilesChecker requiredFilesChecker, IFileSystem fileSystem, IConsole console)
1111
{
1212
public async Task<int> RunAsync(ValidatorRunnerOptions options, CancellationToken cancellationToken)
1313
{
@@ -23,7 +23,7 @@ public async Task<int> RunAsync(ValidatorRunnerOptions options, CancellationToke
2323

2424
if (results.Count == 0)
2525
{
26-
await Console.Error.WriteLineAsync($"No .slnx files found for input: {options.Input}");
26+
await console.WriteErrorLineAsync($"No .slnx files found for input: {options.Input}");
2727
return options.ContinueOnError ? 0 : 1;
2828
}
2929

@@ -34,14 +34,14 @@ public async Task<int> RunAsync(ValidatorRunnerOptions options, CancellationToke
3434
{
3535
await sonarReporter.WriteReportAsync(reportResults, options.SonarqubeReportPath);
3636
var size = fileSystem.GetFileSize(options.SonarqubeReportPath);
37-
Console.WriteLine($"SonarQube report written to: {options.SonarqubeReportPath} ({size} bytes)");
37+
await console.WriteLineAsync($"SonarQube report written to: {options.SonarqubeReportPath} ({size} bytes)");
3838
}
3939

4040
if (options.SarifReportPath is not null)
4141
{
4242
await sarifReporter.WriteReportAsync(reportResults, options.SarifReportPath);
4343
var size = fileSystem.GetFileSize(options.SarifReportPath);
44-
Console.WriteLine($"SARIF report written to: {options.SarifReportPath} ({size} bytes)");
44+
await console.WriteLineAsync($"SARIF report written to: {options.SarifReportPath} ({size} bytes)");
4545
}
4646

4747
var hasErrors = results.Any(r => r.Errors.Any(e => options.SeverityOverrides.IsFailingError(e.Code)));
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace JulianVerdurmen.SlnxValidator.Tests;
2+
3+
internal sealed class FakeConsole : IConsole
4+
{
5+
public List<string> OutputLines { get; } = [];
6+
public List<string> ErrorLines { get; } = [];
7+
8+
public Task WriteLineAsync(string value) { OutputLines.Add(value); return Task.CompletedTask; }
9+
public Task WriteErrorLineAsync(string value) { ErrorLines.Add(value); return Task.CompletedTask; }
10+
}

tests/SLNX-validator.Tests/MockFileSystem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ public Stream OpenRead(string path) =>
4040
new MemoryStream(Encoding.UTF8.GetBytes(_fileContents.GetValueOrDefault(path, "")));
4141
public Task<string> ReadAllTextAsync(string path, CancellationToken cancellationToken = default) =>
4242
Task.FromResult(_fileContents.GetValueOrDefault(path, ""));
43-
public long GetFileSize(string path) =>
44-
CreatedFiles.TryGetValue(path, out var ms) ? ms.Length : 0;
43+
public long GetFileSize(string path) => 0;
4544
}
45+

tests/SLNX-validator.Tests/ValidatorRunnerTests.cs

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,23 @@ namespace JulianVerdurmen.SlnxValidator.Tests;
99

1010
public class ValidatorRunnerTests
1111
{
12-
private static ValidatorRunner CreateRunner(IFileSystem fileSystem, IRequiredFilesChecker? checker = null)
12+
private readonly FakeConsole _console = new();
13+
14+
private ValidatorRunner CreateRunner(IFileSystem fileSystem, IRequiredFilesChecker? checker = null)
1315
{
1416
checker ??= Substitute.For<IRequiredFilesChecker>();
1517
var resolver = Substitute.For<ISlnxFileResolver>();
1618
var collector = new SlnxCollector(fileSystem, resolver, Substitute.For<ISlnxValidator>(), checker);
1719
var sonarReporter = new SonarReporter(fileSystem);
1820
var sarifReporter = new SarifReporter(fileSystem);
19-
return new ValidatorRunner(collector, sonarReporter, sarifReporter, checker, fileSystem);
21+
return new ValidatorRunner(collector, sonarReporter, sarifReporter, checker, fileSystem, _console);
2022
}
2123

2224
private static ValidatorRunnerOptions Options(string input = "test.slnx",
2325
bool continueOnError = false, string? requiredFilesPattern = null) =>
2426
new(input, SonarqubeReportPath: null, continueOnError, requiredFilesPattern, WorkingDirectory: ".");
2527

26-
private static ValidatorRunner CreateRunnerWithSlnx(
28+
private ValidatorRunner CreateRunnerWithSlnx(
2729
string slnxPath, string slnxContent, IRequiredFilesChecker? checker = null)
2830
{
2931
checker ??= Substitute.For<IRequiredFilesChecker>();
@@ -39,7 +41,7 @@ private static ValidatorRunner CreateRunnerWithSlnx(
3941
var collector = new SlnxCollector(fileSystem, resolver, validator, checker);
4042
var sonarReporter = new SonarReporter(fileSystem);
4143
var sarifReporter = new SarifReporter(fileSystem);
42-
return new ValidatorRunner(collector, sonarReporter, sarifReporter, checker, fileSystem);
44+
return new ValidatorRunner(collector, sonarReporter, sarifReporter, checker, fileSystem, _console);
4345
}
4446

4547
#region RunAsync – file resolution
@@ -248,5 +250,54 @@ public async Task RunAsync_IgnoreAllCodesMajorSpecificCode_SpecificCodeCausesExi
248250
}
249251

250252
#endregion
251-
}
252253

254+
#region RunAsync – console output
255+
256+
[Test]
257+
public async Task RunAsync_NoFilesFound_WritesErrorToConsole()
258+
{
259+
// Arrange
260+
var runner = CreateRunner(new MockFileSystem());
261+
262+
// Act
263+
await runner.RunAsync(Options("nonexistent.slnx"), CancellationToken.None);
264+
265+
// Assert
266+
_console.ErrorLines.Should().ContainMatch("*No .slnx files found for input: nonexistent.slnx*");
267+
}
268+
269+
[Test]
270+
public async Task RunAsync_SonarqubeReportPath_WritesConfirmationToConsole()
271+
{
272+
// Arrange
273+
var slnxPath = Path.GetFullPath("test.slnx");
274+
var runner = CreateRunnerWithSlnx(slnxPath, "<Solution />");
275+
var options = new ValidatorRunnerOptions(slnxPath, SonarqubeReportPath: "report.xml",
276+
ContinueOnError: false, RequiredFilesPattern: null, WorkingDirectory: ".");
277+
278+
// Act
279+
await runner.RunAsync(options, CancellationToken.None);
280+
281+
// Assert
282+
_console.OutputLines.Should().ContainMatch("*SonarQube report written to: report.xml*");
283+
}
284+
285+
[Test]
286+
public async Task RunAsync_SarifReportPath_WritesConfirmationToConsole()
287+
{
288+
// Arrange
289+
var slnxPath = Path.GetFullPath("test.slnx");
290+
var runner = CreateRunnerWithSlnx(slnxPath, "<Solution />");
291+
var options = new ValidatorRunnerOptions(slnxPath, SonarqubeReportPath: null,
292+
ContinueOnError: false, RequiredFilesPattern: null, WorkingDirectory: ".",
293+
SarifReportPath: "report.sarif");
294+
295+
// Act
296+
await runner.RunAsync(options, CancellationToken.None);
297+
298+
// Assert
299+
_console.OutputLines.Should().ContainMatch("*SARIF report written to: report.sarif*");
300+
}
301+
302+
#endregion
303+
}

0 commit comments

Comments
 (0)