diff --git a/.gitignore b/.gitignore
index 758b4e3..dd3e691 100644
--- a/.gitignore
+++ b/.gitignore
@@ -206,6 +206,7 @@ PublishScripts/
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
+dotnet-tools.json
# Microsoft Azure Build Output
csx/
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..2451c08
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Saunter.sln b/Saunter.sln
index 10d433b..85c435a 100644
--- a/Saunter.sln
+++ b/Saunter.sln
@@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{E0D34C77-9
.editorconfig = .editorconfig
.gitattributes = .gitattributes
CHANGELOG.md = CHANGELOG.md
+ Directory.Build.props = Directory.Build.props
README.md = README.md
EndProjectSection
EndProject
@@ -45,7 +46,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "npm", "npm", "{E8FACA22-CFE
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncAPI.Saunter.Generator.Cli", "src\AsyncAPI.Saunter.Generator.Cli\AsyncAPI.Saunter.Generator.Cli.csproj", "{6C102D4D-3DA4-4763-B75E-C15E33E7E94A}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncAPI.Saunter.Generator.Cli.Tests", "test\AsyncAPI.Saunter.Generator.Cli.Tests\AsyncAPI.Saunter.Generator.Cli.Tests.csproj", "{18AD0249-0436-4A26-9972-B97BA6905A54}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncAPI.Saunter.Generator.Cli.Tests", "test\AsyncAPI.Saunter.Generator.Cli.Tests\AsyncAPI.Saunter.Generator.Cli.Tests.csproj", "{18AD0249-0436-4A26-9972-B97BA6905A54}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/AsyncApiDocumentExtractor.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/AsyncApiDocumentExtractor.cs
index aa9852b..6a1709f 100644
--- a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/AsyncApiDocumentExtractor.cs
+++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/AsyncApiDocumentExtractor.cs
@@ -3,12 +3,17 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-using Saunter.Serialization;
using Saunter;
+using Saunter.Serialization;
namespace AsyncAPI.Saunter.Generator.Cli.ToFile;
-internal class AsyncApiDocumentExtractor(ILogger logger)
+internal interface IAsyncApiDocumentExtractor
+{
+ IEnumerable<(string name, AsyncApiDocument document)> GetAsyncApiDocument(IServiceProvider serviceProvider, string[] requestedDocuments);
+}
+
+internal class AsyncApiDocumentExtractor(ILogger logger) : IAsyncApiDocumentExtractor
{
private IEnumerable GetDocumentNames(string[] requestedDocuments, AsyncApiOptions asyncApiOptions)
{
diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/Environment.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/Environment.cs
index 040637a..2ae66e7 100644
--- a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/Environment.cs
+++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/Environment.cs
@@ -2,26 +2,27 @@
namespace AsyncAPI.Saunter.Generator.Cli.ToFile;
-internal class EnvironmentBuilder(ILogger logger)
+internal interface IEnvironmentBuilder
+{
+ void SetEnvironmentVariables(string env);
+}
+
+internal class EnvironmentBuilder(ILogger logger) : IEnvironmentBuilder
{
public void SetEnvironmentVariables(string env)
{
var envVars = !string.IsNullOrWhiteSpace(env) ? env.Split(',').Select(x => x.Trim()) : Array.Empty();
- foreach (var envVar in envVars.Select(x => x.Split('=').Select(x => x.Trim()).ToList()))
+ var keyValues = envVars.Select(x => x.Split('=').Select(x => x.Trim()).ToList());
+ foreach (var envVar in keyValues)
{
- if (envVar.Count is 1)
- {
- Environment.SetEnvironmentVariable(envVar[0], null, EnvironmentVariableTarget.Process);
- logger.LogDebug($"Set environment flag: {envVar[0]}");
- }
- if (envVar.Count is 2)
+ if (envVar.Count == 2 && !string.IsNullOrWhiteSpace(envVar[0]))
{
- Environment.SetEnvironmentVariable(envVar[0], envVar.ElementAtOrDefault(1), EnvironmentVariableTarget.Process);
+ Environment.SetEnvironmentVariable(envVar[0], envVar[1], EnvironmentVariableTarget.Process);
logger.LogDebug($"Set environment variable: {envVar[0]} = {envVar[1]}");
}
else
{
- logger.LogCritical("Environment variables should be in the format: env1=value1,env2=value2,env3");
+ logger.LogCritical("Environment variables should be in the format: env1=value1,env2=value2");
}
}
}
diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/FileWriter.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/FileWriter.cs
index 62e1bc4..d58060e 100644
--- a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/FileWriter.cs
+++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/FileWriter.cs
@@ -2,7 +2,12 @@
namespace AsyncAPI.Saunter.Generator.Cli.ToFile;
-internal class FileWriter(ILogger logger)
+internal interface IFileWriter
+{
+ void Write(string outputPath, string fileTemplate, string documentName, string format, Action streamWriter);
+}
+
+internal class FileWriter(IStreamProvider streamProvider, ILogger logger) : IFileWriter
{
public void Write(string outputPath, string fileTemplate, string documentName, string format, Action streamWriter)
{
@@ -12,12 +17,12 @@ public void Write(string outputPath, string fileTemplate, string documentName, s
private void WriteFile(string outputPath, Action writeAction)
{
- using var stream = outputPath != null ? File.Create(outputPath) : Console.OpenStandardOutput();
+ using var stream = streamProvider.GetStreamFor(outputPath);
writeAction(stream);
if (outputPath != null)
{
- logger.LogInformation($"AsyncAPI {Path.GetExtension(outputPath)[1..]} successfully written to {outputPath}");
+ logger.LogInformation($"AsyncAPI {Path.GetExtension(outputPath).TrimStart('.')} successfully written to {outputPath}");
}
}
diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ServiceExtensions.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ServiceExtensions.cs
index 041e496..33201f1 100644
--- a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ServiceExtensions.cs
+++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ServiceExtensions.cs
@@ -6,10 +6,11 @@ internal static class ServiceExtensions
{
public static IServiceCollection AddToFileCommand(this IServiceCollection services)
{
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
return services;
}
}
diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ServiceProviderBuilder.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ServiceProviderBuilder.cs
index 5c3c6a6..69bde4f 100644
--- a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ServiceProviderBuilder.cs
+++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ServiceProviderBuilder.cs
@@ -4,7 +4,12 @@
namespace AsyncAPI.Saunter.Generator.Cli.ToFile;
-internal class ServiceProviderBuilder(ILogger logger)
+internal interface IServiceProviderBuilder
+{
+ IServiceProvider BuildServiceProvider(string startupAssembly);
+}
+
+internal class ServiceProviderBuilder(ILogger logger) : IServiceProviderBuilder
{
public IServiceProvider BuildServiceProvider(string startupAssembly)
{
@@ -17,4 +22,3 @@ public IServiceProvider BuildServiceProvider(string startupAssembly)
return serviceProvider;
}
}
-
diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs
new file mode 100644
index 0000000..8af5dbd
--- /dev/null
+++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs
@@ -0,0 +1,19 @@
+namespace AsyncAPI.Saunter.Generator.Cli.ToFile;
+
+internal interface IStreamProvider
+{
+ Stream GetStreamFor(string path);
+}
+
+internal class StreamProvider : IStreamProvider
+{
+ public Stream GetStreamFor(string path)
+ {
+ if (!string.IsNullOrEmpty(path))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+ }
+
+ return path != null ? File.Create(path) : Console.OpenStandardOutput();
+ }
+}
diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ToFileCommand.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ToFileCommand.cs
index 98bebc5..0a62c43 100644
--- a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ToFileCommand.cs
+++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ToFileCommand.cs
@@ -5,7 +5,7 @@
namespace AsyncAPI.Saunter.Generator.Cli.ToFile;
-internal class ToFileCommand(ILogger logger, EnvironmentBuilder environment, ServiceProviderBuilder builder, AsyncApiDocumentExtractor docExtractor, FileWriter fileWriter)
+internal class ToFileCommand(ILogger logger, IEnvironmentBuilder environment, IServiceProviderBuilder builder, IAsyncApiDocumentExtractor docExtractor, IFileWriter fileWriter)
{
private const string DEFAULT_FILENAME = "{document}_asyncapi.{extension}";
@@ -37,11 +37,7 @@ public int ToFile([Argument] string startupassembly, string output = "./", strin
foreach (var (documentName, asyncApiDocument) in documents)
{
// Serialize to specified output location or stdout
- var outputPath = !string.IsNullOrWhiteSpace(output) ? Path.Combine(Directory.GetCurrentDirectory(), output) : null;
- if (!string.IsNullOrEmpty(outputPath))
- {
- Directory.CreateDirectory(outputPath);
- }
+ var outputPath = !string.IsNullOrWhiteSpace(output) ? Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), output)) : null;
var exportJson = true;
var exportYml = false;
diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/AsyncAPI.Saunter.Generator.Cli.Tests.csproj b/test/AsyncAPI.Saunter.Generator.Cli.Tests/AsyncAPI.Saunter.Generator.Cli.Tests.csproj
index 355fdd5..6b89cdb 100644
--- a/test/AsyncAPI.Saunter.Generator.Cli.Tests/AsyncAPI.Saunter.Generator.Cli.Tests.csproj
+++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/AsyncAPI.Saunter.Generator.Cli.Tests.csproj
@@ -1,4 +1,4 @@
-
+
net8.0
@@ -9,11 +9,17 @@
+
-
-
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/E2ETests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/E2ETests.cs
new file mode 100644
index 0000000..0134ce6
--- /dev/null
+++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/E2ETests.cs
@@ -0,0 +1,59 @@
+using System.Diagnostics;
+using Shouldly;
+using Xunit.Abstractions;
+
+namespace AsyncAPI.Saunter.Generator.Cli.Tests;
+
+public class E2ETests(ITestOutputHelper output)
+{
+ private string Run(string file, string args, string workingDirectory, int expectedExitCode = 0)
+ {
+ var process = Process.Start(new ProcessStartInfo(file)
+ {
+ Arguments = args,
+ WorkingDirectory = workingDirectory,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ });
+ process.WaitForExit(TimeSpan.FromSeconds(20));
+ var stdOut = process.StandardOutput.ReadToEnd().Trim();
+ var stdError = process.StandardError.ReadToEnd().Trim();
+ output.WriteLine($"### Output of \"{file} {args}\"");
+ output.WriteLine(stdOut);
+ output.WriteLine(stdError);
+
+ process.ExitCode.ShouldBe(expectedExitCode);
+ return stdOut;
+ }
+
+ [Fact(Skip = "Manual verification only")]
+ public void Pack_Install_Run_Uninstall_Test()
+ {
+ var workingDirectory = "../../../../../src/AsyncAPI.Saunter.Generator.Cli";
+ var stdOut = this.Run("dotnet", "pack", workingDirectory);
+ stdOut.ShouldContain("Successfully created package");
+
+ // use --force flag to ensure the test starts clean every run
+ stdOut = this.Run("dotnet", "new tool-manifest --force", workingDirectory);
+ stdOut.ShouldContain("The template \"Dotnet local tool manifest file\" was created successfully");
+
+ stdOut = this.Run("dotnet", "tool install --local --add-source ./bin/Release AsyncAPI.Saunter.Generator.Cli", workingDirectory);
+ stdOut = stdOut.Replace("Skipping NuGet package signature verification.", "").Trim();
+ stdOut.ShouldContain("You can invoke the tool from this directory using the following commands: 'dotnet tool run dotnet-asyncapi");
+ stdOut.ShouldContain("was successfully installed.");
+
+ stdOut = this.Run("dotnet", "tool list --local asyncapi.saunter.generator.cli", workingDirectory);
+ stdOut.ShouldContain("dotnet-asyncapi");
+
+ stdOut = this.Run("dotnet", "tool run dotnet-asyncapi", workingDirectory, 1);
+ stdOut.ShouldContain("tofile: retrieves AsyncAPI from a startup assembly, and writes to file");
+
+ stdOut = this.Run("dotnet", "tool uninstall --local asyncapi.saunter.generator.cli", workingDirectory);
+ stdOut.ShouldContain(" was successfully uninstalled");
+ stdOut.ShouldContain("removed from manifest file");
+
+ stdOut = this.Run("dotnet", "tool list --local asyncapi.saunter.generator.cli", workingDirectory, 1);
+ stdOut.ShouldNotContain("dotnet-asyncapi");
+ }
+}
diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/DotnetCliToolTests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs
similarity index 92%
rename from test/AsyncAPI.Saunter.Generator.Cli.Tests/DotnetCliToolTests.cs
rename to test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs
index 8d9e12d..105916d 100644
--- a/test/AsyncAPI.Saunter.Generator.Cli.Tests/DotnetCliToolTests.cs
+++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs
@@ -1,26 +1,29 @@
-using System.Diagnostics;
-using Shouldly;
+using Shouldly;
using Xunit.Abstractions;
namespace AsyncAPI.Saunter.Generator.Cli.Tests;
-public class DotnetCliToolTests(ITestOutputHelper output)
+public class IntegrationTests(ITestOutputHelper output)
{
private string RunTool(string args, int expectedExitCode = 1)
{
- var process = Process.Start(new ProcessStartInfo("dotnet")
- {
- Arguments = $"../../../../../src/AsyncAPI.Saunter.Generator.Cli/bin/Debug/net8.0/AsyncAPI.Saunter.Generator.Cli.dll tofile {args}",
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- });
- process.WaitForExit();
- var stdOut = process.StandardOutput.ReadToEnd().Trim();
- var stdError = process.StandardError.ReadToEnd().Trim();
+ using var outWriter = new StringWriter();
+ using var errorWriter = new StringWriter();
+ Console.SetOut(outWriter);
+ Console.SetError(errorWriter);
+
+ var entryPoint = typeof(Program).Assembly.EntryPoint!;
+ entryPoint.Invoke(null, new object[] { args.Split(' ') });
+
+ var stdOut = outWriter.ToString();
+ var stdError = errorWriter.ToString();
+ output.WriteLine($"RUN: {args}");
+ output.WriteLine("### STD OUT");
output.WriteLine(stdOut);
+ output.WriteLine("### STD ERROR");
output.WriteLine(stdError);
- process.ExitCode.ShouldBe(expectedExitCode);
+ Environment.ExitCode.ShouldBe(expectedExitCode);
//stdError.ShouldBeEmpty(); LEGO lib doesn't like id: "id is not a valid property at #/components/schemas/lightMeasuredEvent""
return stdOut;
}
@@ -28,7 +31,7 @@ private string RunTool(string args, int expectedExitCode = 1)
[Fact]
public void DefaultCallPrintsCommandInfo()
{
- var stdOut = RunTool("", 0).Trim();
+ var stdOut = RunTool("tofile", 0).Trim();
stdOut.ShouldBe("""
Usage: tofile [arguments...] [options...] [-h|--help] [--version]
@@ -52,7 +55,7 @@ public void StreetlightsAPIExportSpecTest()
{
var path = Directory.GetCurrentDirectory();
output.WriteLine($"Output path: {path}");
- var stdOut = RunTool($"../../../../../examples/StreetlightsAPI/bin/Debug/net8.0/StreetlightsAPI.dll --output {path} --format json,yml,yaml");
+ var stdOut = RunTool($"tofile ../../../../../examples/StreetlightsAPI/bin/Debug/net8.0/StreetlightsAPI.dll --output {path} --format json,yml,yaml");
stdOut.ShouldNotBeEmpty();
stdOut.ShouldContain($"AsyncAPI yaml successfully written to {Path.Combine(path, "asyncapi.yaml")}");
diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/PackAndInstallLocalTests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/PackAndInstallLocalTests.cs
deleted file mode 100644
index 2bf87ba..0000000
--- a/test/AsyncAPI.Saunter.Generator.Cli.Tests/PackAndInstallLocalTests.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System.Diagnostics;
-using Shouldly;
-using Xunit.Abstractions;
-
-namespace AsyncAPI.Saunter.Generator.Cli.Tests;
-
-public class PackAndInstallLocalTests(ITestOutputHelper output)
-{
- private string Run(string file, string args, string workingDirectory, int expectedExitCode = 0)
- {
- var process = Process.Start(new ProcessStartInfo(file)
- {
- Arguments = args,
- WorkingDirectory = workingDirectory,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- UseShellExecute = false,
- });
- process.WaitForExit(TimeSpan.FromSeconds(20));
- var stdOut = process.StandardOutput.ReadToEnd().Trim();
- var stdError = process.StandardError.ReadToEnd().Trim();
- output.WriteLine($"### Output of \"{file} {args}\"");
- output.WriteLine(stdOut);
- output.WriteLine(stdError);
-
- process.ExitCode.ShouldBe(expectedExitCode);
- return stdOut;
- }
-
- [Fact]
- public void Pack_Install_Run_Uninstall_Test()
- {
- var stdOut = this.Run("dotnet", "pack", "../../../../../src/AsyncAPI.Saunter.Generator.Cli");
- stdOut.ShouldContain("Successfully created package");
-
- stdOut = this.Run("dotnet", "tool install --global --add-source ./bin/Release AsyncAPI.Saunter.Generator.Cli", "../../../../../src/AsyncAPI.Saunter.Generator.Cli");
- stdOut.ShouldBeOneOf("You can invoke the tool using the following command: dotnet-asyncapi\r\nTool 'asyncapi.saunter.generator.cli' (version '1.0.1') was successfully installed.",
- "Tool 'asyncapi.saunter.generator.cli' was reinstalled with the stable version (version '1.0.1').");
-
- stdOut = this.Run("dotnet", "tool list -g asyncapi.saunter.generator.cli", "");
- stdOut.ShouldContain("dotnet-asyncapi");
-
- stdOut = this.Run("dotnet", "asyncapi", "", 1);
- stdOut.ShouldContain("tofile: retrieves AsyncAPI from a startup assembly, and writes to file");
-
- stdOut = this.Run("dotnet", "tool uninstall -g asyncapi.saunter.generator.cli", "");
- stdOut.ShouldContain(" was successfully uninstalled.");
-
- stdOut = this.Run("dotnet", "tool list -g asyncapi.saunter.generator.cli", "", 1);
- stdOut.ShouldNotContain("dotnet-asyncapi");
- }
-}
diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/AsyncApiDocumentExtractorTests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/AsyncApiDocumentExtractorTests.cs
new file mode 100644
index 0000000..bf09183
--- /dev/null
+++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/AsyncApiDocumentExtractorTests.cs
@@ -0,0 +1,152 @@
+using AsyncAPI.Saunter.Generator.Cli.ToFile;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using NSubstitute;
+using NSubstitute.Community.Logging;
+using Saunter;
+using Saunter.AsyncApiSchema.v2;
+using Saunter.Serialization;
+using Shouldly;
+
+namespace AsyncAPI.Saunter.Generator.Cli.Tests.ToFile;
+
+public class AsyncApiDocumentExtractorTests
+{
+ private readonly AsyncApiDocumentExtractor _extractor;
+ private readonly ILogger _logger;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IAsyncApiDocumentProvider _documentProvider;
+ private readonly IOptions _asyncApiOptions;
+ private readonly IAsyncApiDocumentSerializer _documentSerializer;
+
+ public AsyncApiDocumentExtractorTests()
+ {
+ var services = new ServiceCollection();
+ this._documentProvider = Substitute.For();
+ this._asyncApiOptions = Substitute.For>();
+ var options = new AsyncApiOptions();
+ this._asyncApiOptions.Value.Returns(options);
+ this._documentSerializer = Substitute.For();
+ services.AddSingleton(this._documentProvider);
+ services.AddSingleton(this._asyncApiOptions);
+ services.AddSingleton(this._documentSerializer);
+ this._serviceProvider = services.BuildServiceProvider();
+
+ this._logger = Substitute.For>();
+ this._extractor = new AsyncApiDocumentExtractor(this._logger);
+ }
+
+ [Fact]
+ public void GetAsyncApiDocument_Null_NoMarkerAssemblies()
+ {
+ var documents = this._extractor.GetAsyncApiDocument(this._serviceProvider, null).ToList();
+
+ this._logger.Received(1).CallToLog(LogLevel.Critical);
+ }
+
+ [Fact]
+ public void GetAsyncApiDocument_Default_WithMarkerAssembly()
+ {
+ this._asyncApiOptions.Value.AssemblyMarkerTypes = [typeof(AsyncApiDocumentExtractorTests)];
+ var doc = new AsyncApiDocument();
+ this._documentProvider.GetDocument(default, default).ReturnsForAnyArgs(doc);
+ this._documentSerializer.Serialize(doc).ReturnsForAnyArgs("""
+ asyncapi: 2.6.0
+ info:
+ title: Streetlights API
+ """);
+
+ var documents = this._extractor.GetAsyncApiDocument(this._serviceProvider, null).ToList();
+
+ this._logger.Received(0).CallToLog(LogLevel.Critical);
+ documents.Count.ShouldBe(1);
+ documents[0].name.ShouldBeNull();
+ documents[0].document.Info.Title.ShouldBe("Streetlights API");
+ }
+
+ [Fact]
+ public void GetAsyncApiDocument_1NamedDocument()
+ {
+ this._asyncApiOptions.Value.AssemblyMarkerTypes = [typeof(AsyncApiDocumentExtractorTests)];
+ var doc = new AsyncApiDocument();
+ this._asyncApiOptions.Value.NamedApis["service 1"] = doc;
+ this._documentProvider.GetDocument(default, default).ReturnsForAnyArgs(doc);
+ this._documentSerializer.Serialize(doc).ReturnsForAnyArgs("""
+ asyncapi: 2.6.0
+ info:
+ title: Streetlights API
+ """);
+
+ var documents = this._extractor.GetAsyncApiDocument(this._serviceProvider, null).ToList();
+
+ this._logger.Received(0).CallToLog(LogLevel.Critical);
+ documents.Count.ShouldBe(1);
+ documents[0].name.ShouldBe("service 1");
+ documents[0].document.Info.Title.ShouldBe("Streetlights API");
+ }
+
+ [Fact]
+ public void GetAsyncApiDocument_2NamedDocument()
+ {
+ this._asyncApiOptions.Value.AssemblyMarkerTypes = [typeof(AsyncApiDocumentExtractorTests)];
+ var doc1 = new AsyncApiDocument { Id = "1" };
+ var doc2 = new AsyncApiDocument { Id = "2" };
+ this._asyncApiOptions.Value.NamedApis["service 1"] = doc1;
+ this._asyncApiOptions.Value.NamedApis["service 2"] = doc2;
+ this._documentProvider.GetDocument(Arg.Any(), Arg.Is(doc1)).Returns(doc1);
+ this._documentProvider.GetDocument(Arg.Any(), Arg.Is(doc2)).Returns(doc2);
+ this._documentSerializer.Serialize(doc1).Returns("""
+ asyncapi: 2.6.0
+ info:
+ title: Streetlights API 1
+ """);
+ this._documentSerializer.Serialize(doc2).Returns("""
+ asyncapi: 2.6.0
+ info:
+ title: Streetlights API 2
+ """);
+
+ var documents = this._extractor.GetAsyncApiDocument(this._serviceProvider, null).OrderBy(x => x.name).ToList();
+
+ this._logger.Received(0).CallToLog(LogLevel.Critical);
+ documents.Count.ShouldBe(2);
+ documents[0].name.ShouldBe("service 1");
+ documents[0].document.Info.Title.ShouldBe("Streetlights API 1");
+ documents[1].name.ShouldBe("service 2");
+ documents[1].document.Info.Title.ShouldBe("Streetlights API 2");
+ }
+
+ [Fact]
+ public void GetAsyncApiDocument_LogErrors()
+ {
+ this._asyncApiOptions.Value.AssemblyMarkerTypes = [typeof(AsyncApiDocumentExtractorTests)];
+ var doc = new AsyncApiDocument();
+ this._documentProvider.GetDocument(default, default).ReturnsForAnyArgs(doc);
+ this._documentSerializer.Serialize(doc).ReturnsForAnyArgs("""
+ asyncapi: 2.6.0
+ info:
+ title: Streetlights API
+ channels:
+ publish/light/measured:
+ servers:
+ - webapi
+ publish:
+ operationId: MeasureLight
+ summary: Inform about environmental lighting conditions for a particular streetlight.
+ tags:
+ - name: Light
+ message:
+ $ref: '#/components/messages/lightMeasuredEvent'
+ """);
+
+ var documents = this._extractor.GetAsyncApiDocument(this._serviceProvider, null).ToList();
+
+ this._logger.Received(0).CallToLog(LogLevel.Critical);
+ this._logger.Received(3).CallToLog(LogLevel.Error);
+ this._logger.Received(0).CallToLog(LogLevel.Warning);
+ documents.Count.ShouldBe(1);
+ documents[0].name.ShouldBeNull();
+ documents[0].document.Info.Title.ShouldBe("Streetlights API");
+ }
+}
diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/EnvironmentBuilderTests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/EnvironmentBuilderTests.cs
new file mode 100644
index 0000000..de26811
--- /dev/null
+++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/EnvironmentBuilderTests.cs
@@ -0,0 +1,94 @@
+using System.Collections;
+using AsyncAPI.Saunter.Generator.Cli.ToFile;
+using Microsoft.Extensions.Logging;
+using NSubstitute;
+using NSubstitute.Community.Logging;
+using Shouldly;
+
+namespace AsyncAPI.Saunter.Generator.Cli.Tests.ToFile;
+
+public class EnvironmentBuilderTests : IDisposable
+{
+ private readonly IDictionary _variablesBefore = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Process);
+ private readonly EnvironmentBuilder _environment;
+ private readonly ILogger _logger;
+
+ public EnvironmentBuilderTests()
+ {
+ this._logger = Substitute.For>();
+ this._environment = new EnvironmentBuilder(this._logger);
+ }
+
+ private Dictionary GetAddedEnvironmentVariables()
+ {
+ var after = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Process);
+ return after.Cast().ExceptBy(this._variablesBefore.Keys.Cast(), x => x.Key).ToDictionary(x => x.Key.ToString(), x => x.Value?.ToString());
+ }
+
+ public void Dispose()
+ {
+ foreach (var variable in this.GetAddedEnvironmentVariables())
+ {
+ Environment.SetEnvironmentVariable(variable.Key, null, EnvironmentVariableTarget.Process);
+ }
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void EmptyEnvStringProvided(string env)
+ {
+ this._environment.SetEnvironmentVariables(env);
+
+ this._logger.ReceivedCalls().Count().ShouldBe(0);
+ this.GetAddedEnvironmentVariables().ShouldBeEmpty();
+ }
+
+ [Theory]
+ [InlineData("env1=val1", 1)]
+ [InlineData("a=b,b=c", 2)]
+ public void ValidEnvStringProvided(string env, int expectedSets)
+ {
+ this._environment.SetEnvironmentVariables(env);
+
+ this._logger.Received(expectedSets).CallToLog(LogLevel.Debug);
+ this.GetAddedEnvironmentVariables().ShouldNotBeEmpty();
+ }
+
+ [Theory]
+ [InlineData(",", 2)]
+ [InlineData(",,,,", 5)]
+ [InlineData("=a", 1)]
+ [InlineData("b", 1)]
+ [InlineData("=", 1)]
+ [InlineData("====", 1)]
+ public void InvalidEnvStringProvided(string env, int expectedSets)
+ {
+ this._environment.SetEnvironmentVariables(env);
+
+ this._logger.Received(expectedSets).CallToLog(LogLevel.Critical);
+ this.GetAddedEnvironmentVariables().ShouldBeEmpty();
+ }
+
+ [Fact]
+ public void ValidateEnvValues()
+ {
+ this._environment.SetEnvironmentVariables("ENV=1,,Test=two");
+
+ Environment.GetEnvironmentVariable("ENV").ShouldBe("1");
+ Environment.GetEnvironmentVariable("Test").ShouldBe("two");
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData(" ")]
+ public void EmptyValueDeletesEnvValue(string value)
+ {
+ this._environment.SetEnvironmentVariables($"ENV=1,,ENV={value}");
+
+ Environment.GetEnvironmentVariable("ENV").ShouldBe(null);
+ }
+}
diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/FileWriterTests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/FileWriterTests.cs
new file mode 100644
index 0000000..129959b
--- /dev/null
+++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/FileWriterTests.cs
@@ -0,0 +1,107 @@
+using System.Text;
+using AsyncAPI.Saunter.Generator.Cli.ToFile;
+using Microsoft.Extensions.Logging;
+using NSubstitute;
+using Shouldly;
+using Xunit.Abstractions;
+
+namespace AsyncAPI.Saunter.Generator.Cli.Tests.ToFile;
+
+public class FileWriterTests
+{
+ private readonly Action _testContextWriter = stream => stream.Write(Encoding.Default.GetBytes("ananas"));
+
+ private readonly FileWriter _writer;
+ private readonly IStreamProvider _streamProvider;
+ private readonly ILogger _logger;
+ private readonly MemoryStream _stream = new();
+
+ public FileWriterTests(ITestOutputHelper output)
+ {
+ this._logger = Substitute.For>();
+ this._streamProvider = Substitute.For();
+ this._streamProvider.GetStreamFor(default).ReturnsForAnyArgs(x =>
+ {
+ output.WriteLine($"GetStreamFor({x.Args()[0]})");
+ return this._stream;
+ });
+ this._writer = new FileWriter(this._streamProvider, this._logger);
+ }
+
+ [Fact]
+ public void CheckStreamContents()
+ {
+ this._writer.Write("/", "", "", "", _testContextWriter);
+
+ this._streamProvider.Received(1).GetStreamFor(Path.GetFullPath("/"));
+ Encoding.Default.GetString(this._stream.GetBuffer().Take(6).ToArray()).ShouldBe("ananas");
+ }
+
+ [Fact]
+ public void CheckName_NoVariablesInTemplate()
+ {
+ this._writer.Write("/some/path", "fixed_name", "doc", "json", _testContextWriter);
+
+ this._streamProvider.Received(1).GetStreamFor(Path.GetFullPath("/some/path/fixed_name"));
+ }
+
+ [Theory]
+ [InlineData("./")]
+ [InlineData("/")]
+ [InlineData("/test/")]
+ [InlineData("/test/1/2/3/4/")]
+ public void CheckOutputPath_BaseOutputPath_Absolute(string path)
+ {
+ this._writer.Write(path, "document.something", "", "", _testContextWriter);
+
+ this._streamProvider.Received(1).GetStreamFor(Path.GetFullPath($"{path}document.something"));
+ }
+
+ [Theory]
+ [InlineData(".")]
+ [InlineData("")]
+ [InlineData("asyncApi/")]
+ [InlineData("service-1/")]
+ [InlineData("service 1/")]
+ [InlineData("service 1/spec")]
+ public void CheckOutputPath_BaseOutputPath_Relative(string path)
+ {
+ this._writer.Write(path, "document.something", "", "", _testContextWriter);
+
+ this._streamProvider.Received(1).GetStreamFor(Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), path, "document.something")));
+ }
+
+ [Theory]
+ [InlineData("json")]
+ [InlineData("yml")]
+ [InlineData("txt")]
+ public void CheckOutputPath_FormatTemplate(string format)
+ {
+ this._writer.Write("/some/path", "{extension}_name.{extension}", "doc", format, _testContextWriter);
+
+ this._streamProvider.Received(1).GetStreamFor(Path.GetFullPath($"/some/path/{format}_name.{format}"));
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(null)]
+ public void CheckOutputPath_FormatTemplate_trimmed(string format)
+ {
+ this._writer.Write("/some/path", "{extension}_name.{extension}", "doc", format, _testContextWriter);
+
+ this._streamProvider.Received(1).GetStreamFor(Path.GetFullPath("/some/path/name."));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("asyncApi")]
+ [InlineData("service-1")]
+ [InlineData("service 1")]
+ public void CheckOutputPath_DocumentNameTemplate(string documentName)
+ {
+ this._writer.Write("/some/path", "{document}.something", documentName, "", _testContextWriter);
+
+ this._streamProvider.Received(1).GetStreamFor(Path.GetFullPath($"/some/path/{documentName}.something"));
+ }
+}
diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/StreamProviderTests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/StreamProviderTests.cs
new file mode 100644
index 0000000..a340b6d
--- /dev/null
+++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/StreamProviderTests.cs
@@ -0,0 +1,37 @@
+using AsyncAPI.Saunter.Generator.Cli.ToFile;
+using Shouldly;
+
+namespace AsyncAPI.Saunter.Generator.Cli.Tests.ToFile;
+
+public class StreamProviderTests
+{
+ private readonly IStreamProvider _streamProvider = new StreamProvider();
+
+ [Fact]
+ public void NullPathIsStdOut()
+ {
+ using var stream = this._streamProvider.GetStreamFor(null);
+
+ stream.ShouldNotBeNull();
+ Assert.False(stream is FileStream);
+ }
+
+ [Fact]
+ public void StringPathIsFileStream()
+ {
+ var path = Path.GetFullPath("./test");
+ File.Delete(path);
+ try
+ {
+ using var stream = this._streamProvider.GetStreamFor(path);
+
+ stream.ShouldNotBeNull();
+ Assert.True(stream is FileStream);
+ File.Exists(path);
+ }
+ finally
+ {
+ File.Delete(path);
+ }
+ }
+}
diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/ToFileCommandTests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/ToFileCommandTests.cs
new file mode 100644
index 0000000..d879cc4
--- /dev/null
+++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/ToFileCommandTests.cs
@@ -0,0 +1,205 @@
+using AsyncAPI.Saunter.Generator.Cli.ToFile;
+using LEGO.AsyncAPI.Models;
+using Microsoft.Extensions.Logging;
+using NSubstitute;
+using Shouldly;
+using Xunit.Abstractions;
+
+namespace AsyncAPI.Saunter.Generator.Cli.Tests.ToFile;
+
+public class ToFileCommandTests
+{
+ private readonly ToFileCommand _command;
+ private readonly IEnvironmentBuilder _environment;
+ private readonly IServiceProviderBuilder _builder;
+ private readonly IAsyncApiDocumentExtractor _docExtractor;
+ private readonly IFileWriter _fileWriter;
+ private readonly ILogger _logger;
+ private readonly ITestOutputHelper _output;
+
+ public ToFileCommandTests(ITestOutputHelper output)
+ {
+ this._output = output;
+ this._logger = Substitute.For>();
+ this._environment = Substitute.For();
+ this._builder = Substitute.For();
+ this._docExtractor = Substitute.For();
+ this._fileWriter = Substitute.For();
+ this._command = new ToFileCommand(this._logger, _environment, _builder, _docExtractor, _fileWriter);
+ }
+
+ [Fact]
+ public void StartupAssembly_FileNotFoundException()
+ {
+ Assert.Throws(() => this._command.ToFile(""));
+ }
+
+ [Fact]
+ public void SetEnvironmentVariables()
+ {
+ var me = typeof(ToFileCommandTests).Assembly.Location;
+
+ this._command.ToFile(me, env: "env=value");
+
+ this._environment.Received(1).SetEnvironmentVariables("env=value");
+ }
+
+ [Fact]
+ public void BuildServiceProvider()
+ {
+ var me = typeof(ToFileCommandTests).Assembly.Location;
+ this._output.WriteLine($"Assembly: {me}");
+
+ this._command.ToFile(me);
+
+ this._builder.Received(1).BuildServiceProvider(me);
+ }
+
+ [Fact]
+ public void GetAsyncApiDocument_DefaultDocParam()
+ {
+ var me = typeof(ToFileCommandTests).Assembly.Location;
+ this._output.WriteLine($"Assembly: {me}");
+ var sp = Substitute.For();
+ this._builder.BuildServiceProvider(default).ReturnsForAnyArgs(sp);
+
+ this._command.ToFile(me);
+
+ this._docExtractor.Received(1).GetAsyncApiDocument(sp, null);
+ }
+
+ [Fact]
+ public void GetAsyncApiDocument_DocParam()
+ {
+ var me = typeof(ToFileCommandTests).Assembly.Location;
+ this._output.WriteLine($"Assembly: {me}");
+ var sp = Substitute.For();
+ this._builder.BuildServiceProvider(default).ReturnsForAnyArgs(sp);
+
+ this._command.ToFile(me, doc: "a");
+
+ this._docExtractor.Received(1).GetAsyncApiDocument(sp, Arg.Is(x => x.SequenceEqual(new[] { "a" }))); ;
+ }
+
+ [Fact]
+ public void GetAsyncApiDocument_DocParamMultiple()
+ {
+ var me = typeof(ToFileCommandTests).Assembly.Location;
+ this._output.WriteLine($"Assembly: {me}");
+ var sp = Substitute.For();
+ this._builder.BuildServiceProvider(default).ReturnsForAnyArgs(sp);
+
+ this._command.ToFile(me, doc: "a,b, c ,,");
+
+ this._docExtractor.Received(1).GetAsyncApiDocument(sp, Arg.Is(x => x.SequenceEqual(new[] { "a", "b", " c " })));
+ }
+
+ [Fact]
+ public void WriteFile_DefaultParams()
+ {
+ var me = typeof(ToFileCommandTests).Assembly.Location;
+ this._output.WriteLine($"Assembly: {me}");
+ this._docExtractor.GetAsyncApiDocument(default, default).ReturnsForAnyArgs([(null, new AsyncApiDocument { Info = new AsyncApiInfo { Title = "a" } } )]);
+
+ this._command.ToFile(me);
+
+ this._fileWriter.ReceivedCalls().Count().ShouldBe(1);
+ this._fileWriter.Received(1).Write(Path.GetFullPath("./"), "{document}_asyncapi.{extension}", null, "json", Arg.Any>());
+ }
+
+ [Theory]
+ [InlineData("json")]
+ [InlineData("yml")]
+ [InlineData("yaml")]
+ public void WriteFile_FormatParam(string format)
+ {
+ var me = typeof(ToFileCommandTests).Assembly.Location;
+ this._output.WriteLine($"Assembly: {me}");
+ this._docExtractor.GetAsyncApiDocument(default, default).ReturnsForAnyArgs([(null, new AsyncApiDocument { Info = new AsyncApiInfo { Title = "a" } })]);
+
+ this._command.ToFile(me, format: format);
+
+ this._fileWriter.ReceivedCalls().Count().ShouldBe(1);
+ this._fileWriter.Received(1).Write(Path.GetFullPath("./"), "{document}_asyncapi.{extension}", null, format, Arg.Any>());
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData(null)]
+ public void WriteFile_EmptyFormatParamVariants_FallbackToJson(string format)
+ {
+ var me = typeof(ToFileCommandTests).Assembly.Location;
+ this._output.WriteLine($"Assembly: {me}");
+ this._docExtractor.GetAsyncApiDocument(default, default).ReturnsForAnyArgs([(null, new AsyncApiDocument { Info = new AsyncApiInfo { Title = "a" } })]);
+
+ this._command.ToFile(me, format: format);
+
+ this._fileWriter.ReceivedCalls().Count().ShouldBe(1);
+ this._fileWriter.Received(1).Write(Path.GetFullPath("./"), "{document}_asyncapi.{extension}", null, "json", Arg.Any>());
+ }
+
+ [Theory]
+ [InlineData("a")]
+ [InlineData("json1")]
+ [InlineData(".json")]
+ public void WriteFile_InvalidFormatParam_FallbackToJson(string format)
+ {
+ var me = typeof(ToFileCommandTests).Assembly.Location;
+ this._output.WriteLine($"Assembly: {me}");
+ this._docExtractor.GetAsyncApiDocument(default, default).ReturnsForAnyArgs([(null, new AsyncApiDocument { Info = new AsyncApiInfo { Title = "a" } })]);
+
+ this._command.ToFile(me, format: format);
+
+ this._fileWriter.ReceivedCalls().Count().ShouldBe(0);
+ }
+
+ [Fact]
+ public void WriteFile_FormatParamMultiple()
+ {
+ var me = typeof(ToFileCommandTests).Assembly.Location;
+ this._output.WriteLine($"Assembly: {me}");
+ this._docExtractor.GetAsyncApiDocument(default, default).ReturnsForAnyArgs([(null, new AsyncApiDocument { Info = new AsyncApiInfo { Title = "a" } })]);
+
+ this._command.ToFile(me, format: " json , yaml,yml ,,a, ");
+
+ this._fileWriter.ReceivedCalls().Count().ShouldBe(3);
+ this._fileWriter.Received(1).Write(Path.GetFullPath("./"), "{document}_asyncapi.{extension}", null, "json", Arg.Any>());
+ this._fileWriter.Received(1).Write(Path.GetFullPath("./"), "{document}_asyncapi.{extension}", null, "yml", Arg.Any>());
+ this._fileWriter.Received(1).Write(Path.GetFullPath("./"), "{document}_asyncapi.{extension}", null, "yaml", Arg.Any>());
+ }
+
+ [Theory]
+ [InlineData("doc")]
+ [InlineData("{document}")]
+ [InlineData("{extension}")]
+ [InlineData("{document}.{extension}")]
+ public void WriteFile_FileTemplateParam(string template)
+ {
+ var me = typeof(ToFileCommandTests).Assembly.Location;
+ this._output.WriteLine($"Assembly: {me}");
+ this._docExtractor.GetAsyncApiDocument(default, default).ReturnsForAnyArgs([(null, new AsyncApiDocument { Info = new AsyncApiInfo { Title = "a" } })]);
+
+ this._command.ToFile(me, filename: template);
+
+ this._fileWriter.ReceivedCalls().Count().ShouldBe(1);
+ this._fileWriter.Received(1).Write(Path.GetFullPath("./"), template, null, "json", Arg.Any>());
+ }
+
+ [Theory]
+ [InlineData("./")]
+ [InlineData("/")]
+ [InlineData("a/")]
+ [InlineData("/a/b")]
+ public void WriteFile_OutputPathParam(string path)
+ {
+ var me = typeof(ToFileCommandTests).Assembly.Location;
+ this._output.WriteLine($"Assembly: {me}");
+ this._docExtractor.GetAsyncApiDocument(default, default).ReturnsForAnyArgs([(null, new AsyncApiDocument { Info = new AsyncApiInfo { Title = "a" } })]);
+
+ this._command.ToFile(me, output: path);
+
+ this._fileWriter.ReceivedCalls().Count().ShouldBe(1);
+ this._fileWriter.Received(1).Write(Path.GetFullPath(path), "{document}_asyncapi.{extension}", null, "json", Arg.Any>());
+ }
+}
diff --git a/test/Saunter.Tests/Saunter.Tests.csproj b/test/Saunter.Tests/Saunter.Tests.csproj
index 8f47b9f..98b0356 100644
--- a/test/Saunter.Tests/Saunter.Tests.csproj
+++ b/test/Saunter.Tests/Saunter.Tests.csproj
@@ -17,11 +17,11 @@
-
+
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive