From 7b22ce094be266ffa9c4b793f90948616803c824 Mon Sep 17 00:00:00 2001 From: Senn Geerts Date: Sat, 13 Jul 2024 20:11:04 +0200 Subject: [PATCH 1/9] #196 Add example project with top level statement, because that failed to generate specs, fixed now. Added an external nuget dependancy to the example project (nlog) because that made spec generation fail, fixed now. And now also testing both .NET6 & .NET8 --- Saunter.sln | 18 ++ examples/.gitignore | 4 + .../Program.cs | 76 +++++++ .../StreetlightsAPI.TopLevelStatement.csproj | 46 +++++ .../appsettings.json | 19 ++ examples/StreetlightsAPI/Program.cs | 7 +- .../StreetlightsAPI/StreetlightsAPI.csproj | 15 ++ examples/StreetlightsAPI/nlog.config | 23 +++ .../AsyncAPI.Saunter.Generator.Cli.csproj | 3 +- .../ToFile/DependencyResolver.cs | 22 ++ .../ToFile/ServiceProviderBuilder.cs | 21 +- ...syncAPI.Saunter.Generator.Cli.Tests.csproj | 9 + .../IntegrationTests.cs | 190 ++---------------- .../Specs/ExpectedSpecFiles.cs | 12 ++ .../Specs/streetlights_v2.6.json | 94 +++++++++ .../Specs/streetlights_v2.6.yml | 61 ++++++ 16 files changed, 442 insertions(+), 178 deletions(-) create mode 100644 examples/.gitignore create mode 100644 examples/StreetlightsAPI.TopLevelStatement/Program.cs create mode 100644 examples/StreetlightsAPI.TopLevelStatement/StreetlightsAPI.TopLevelStatement.csproj create mode 100644 examples/StreetlightsAPI.TopLevelStatement/appsettings.json create mode 100644 examples/StreetlightsAPI/nlog.config create mode 100644 src/AsyncAPI.Saunter.Generator.Cli/ToFile/DependencyResolver.cs create mode 100644 test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/ExpectedSpecFiles.cs create mode 100644 test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/streetlights_v2.6.json create mode 100644 test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/streetlights_v2.6.yml diff --git a/Saunter.sln b/Saunter.sln index 85c435a4..60ff6a88 100644 --- a/Saunter.sln +++ b/Saunter.sln @@ -12,6 +12,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saunter.Tests", "test\Saunter.Tests\Saunter.Tests.csproj", "{3ADB27EF-7C80-40EB-AFC6-5D06D415FFAB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{6ABD4842-47AF-49A5-B057-0EBA64416789}" + ProjectSection(SolutionItems) = preProject + examples\.gitignore = examples\.gitignore + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StreetlightsAPI", "examples\StreetlightsAPI\StreetlightsAPI.csproj", "{F188D4A7-BBCB-464F-A370-2BD84D18EA79}" EndProject @@ -48,6 +51,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncAPI.Saunter.Generator. EndProject 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 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StreetlightsAPI.TopLevelStatement", "examples\StreetlightsAPI.TopLevelStatement\StreetlightsAPI.TopLevelStatement.csproj", "{6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -142,6 +147,18 @@ Global {18AD0249-0436-4A26-9972-B97BA6905A54}.Release|x64.Build.0 = Release|Any CPU {18AD0249-0436-4A26-9972-B97BA6905A54}.Release|x86.ActiveCfg = Release|Any CPU {18AD0249-0436-4A26-9972-B97BA6905A54}.Release|x86.Build.0 = Release|Any CPU + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}.Debug|x64.ActiveCfg = Debug|Any CPU + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}.Debug|x64.Build.0 = Debug|Any CPU + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}.Debug|x86.ActiveCfg = Debug|Any CPU + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}.Debug|x86.Build.0 = Debug|Any CPU + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}.Release|Any CPU.Build.0 = Release|Any CPU + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}.Release|x64.ActiveCfg = Release|Any CPU + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}.Release|x64.Build.0 = Release|Any CPU + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}.Release|x86.ActiveCfg = Release|Any CPU + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -156,6 +173,7 @@ Global {E8FACA22-CFED-4710-89E4-D55F31BF96B3} = {D8CB9C0D-9605-457B-979F-C8994B20A926} {6C102D4D-3DA4-4763-B75E-C15E33E7E94A} = {28D4C365-FDED-49AE-A97D-36202E24A55A} {18AD0249-0436-4A26-9972-B97BA6905A54} = {6491E321-2D02-44AB-9116-D722FE169595} + {6F6B8B03-9045-46EC-AE12-E7ADA492F9FA} = {6ABD4842-47AF-49A5-B057-0EBA64416789} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2F85D9DA-DBCF-4F13-8C42-5719F1469B2E} diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 00000000..f91a1165 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,4 @@ +specs/ +streetlights.json +streetlights.yml +streetlights.yaml \ No newline at end of file diff --git a/examples/StreetlightsAPI.TopLevelStatement/Program.cs b/examples/StreetlightsAPI.TopLevelStatement/Program.cs new file mode 100644 index 00000000..79aa9229 --- /dev/null +++ b/examples/StreetlightsAPI.TopLevelStatement/Program.cs @@ -0,0 +1,76 @@ +using System.Linq; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NLog; +using NLog.Web; +using Saunter; +using Saunter.AsyncApiSchema.v2; +using StreetlightsAPI; + +LogManager.Setup().LoadConfigurationFromAppSettings(); + +var builder = WebApplication.CreateBuilder(args); +builder.Host.ConfigureLogging(logging => logging.AddSimpleConsole(console => console.SingleLine = true)); +builder.Host.UseNLog(); + +// Add Saunter to the application services. +builder.Services.AddAsyncApiSchemaGeneration(options => +{ + options.AssemblyMarkerTypes = [typeof(StreetlightMessageBus)]; + + options.Middleware.UiTitle = "Streetlights API"; + + options.AsyncApi = new AsyncApiDocument + { + Info = new Info("Streetlights API", "1.0.0") + { + Description = "The Smartylighting Streetlights API allows you to remotely manage the city lights.", + License = new License("Apache 2.0") + { + Url = "https://www.apache.org/licenses/LICENSE-2.0" + } + }, + Servers = + { + ["mosquitto"] = new Server("test.mosquitto.org", "mqtt"), + ["webapi"] = new Server("localhost:5000", "http"), + }, + }; +}); + +builder.Services.AddScoped(); +builder.Services.AddControllers(); + +var app = builder.Build(); + +app.UseDeveloperExceptionPage(); + +app.UseRouting(); +app.UseCors(configure => configure.AllowAnyOrigin().AllowAnyMethod()); + +app.UseEndpoints(endpoints => +{ + endpoints.MapAsyncApiDocuments(); + endpoints.MapAsyncApiUi(); + + endpoints.MapControllers(); +}); + +await app.StartAsync(); + +// Print the AsyncAPI doc location +var logger = app.Services.GetService().CreateLogger(); +var options = app.Services.GetService>(); +var addresses = app.Urls; +logger.LogInformation("AsyncAPI doc available at: {URL}", $"{addresses.FirstOrDefault()}{options.Value.Middleware.Route}"); +logger.LogInformation("AsyncAPI UI available at: {URL}", $"{addresses.FirstOrDefault()}{options.Value.Middleware.UiBaseRoute}"); + +// Redirect base url to AsyncAPI UI +app.Map("/", () => Results.Redirect("index.html")); +app.Map("/index.html", () => Results.Redirect(options.Value.Middleware.UiBaseRoute)); + +await app.WaitForShutdownAsync(); diff --git a/examples/StreetlightsAPI.TopLevelStatement/StreetlightsAPI.TopLevelStatement.csproj b/examples/StreetlightsAPI.TopLevelStatement/StreetlightsAPI.TopLevelStatement.csproj new file mode 100644 index 00000000..4e4dfcc0 --- /dev/null +++ b/examples/StreetlightsAPI.TopLevelStatement/StreetlightsAPI.TopLevelStatement.csproj @@ -0,0 +1,46 @@ + + + + + net8.0 + false + + + true + json,yml + streetlights.{extension} + specs + + + + bin\Debug\StreetlightsAPI.TopLevelStatement.xml + 1701;1702;1591 + + + + bin\Release\StreetlightsAPI.TopLevelStatement.xml + 1701;1702;1591 + + + + + + + + + + + + + + PreserveNewest + + + + + + + + + diff --git a/examples/StreetlightsAPI.TopLevelStatement/appsettings.json b/examples/StreetlightsAPI.TopLevelStatement/appsettings.json new file mode 100644 index 00000000..b4f6487b --- /dev/null +++ b/examples/StreetlightsAPI.TopLevelStatement/appsettings.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + + "AllowedHosts": "*", + + "Kestrel": { + "EndPoints": { + "Http": { + "Url": "http://localhost:5001" + } + } + } +} diff --git a/examples/StreetlightsAPI/Program.cs b/examples/StreetlightsAPI/Program.cs index aa3f77ad..9afd805e 100644 --- a/examples/StreetlightsAPI/Program.cs +++ b/examples/StreetlightsAPI/Program.cs @@ -6,6 +6,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using NLog; +using NLog.Web; using Saunter; using Saunter.AsyncApiSchema.v2; @@ -15,6 +17,8 @@ public class Program { public static void Main(string[] args) { + LogManager.Setup().LoadConfigurationFromAppSettings(); + CreateHostBuilder(args).Build().Run(); } @@ -22,10 +26,11 @@ public static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args) .ConfigureLogging(logging => logging.AddSimpleConsole(console => console.SingleLine = true)) + .UseNLog() .ConfigureWebHostDefaults(web => { web.UseStartup(); - web.UseUrls("http://localhost:5000"); + web.UseUrls("http://localhost:5001"); }); } } diff --git a/examples/StreetlightsAPI/StreetlightsAPI.csproj b/examples/StreetlightsAPI/StreetlightsAPI.csproj index 43f0b54f..ab72e944 100644 --- a/examples/StreetlightsAPI/StreetlightsAPI.csproj +++ b/examples/StreetlightsAPI/StreetlightsAPI.csproj @@ -1,6 +1,8 @@  + net6.0 false @@ -19,4 +21,17 @@ + + + + + PreserveNewest + + + + + + + + diff --git a/examples/StreetlightsAPI/nlog.config b/examples/StreetlightsAPI/nlog.config new file mode 100644 index 00000000..dd855146 --- /dev/null +++ b/examples/StreetlightsAPI/nlog.config @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AsyncAPI.Saunter.Generator.Cli/AsyncAPI.Saunter.Generator.Cli.csproj b/src/AsyncAPI.Saunter.Generator.Cli/AsyncAPI.Saunter.Generator.Cli.csproj index e510838e..6ab8e1ae 100644 --- a/src/AsyncAPI.Saunter.Generator.Cli/AsyncAPI.Saunter.Generator.Cli.csproj +++ b/src/AsyncAPI.Saunter.Generator.Cli/AsyncAPI.Saunter.Generator.Cli.csproj @@ -6,6 +6,7 @@ enable 12 AsyncAPI.Saunter.Generator.Cli + $(NoWarn);EF1001 AsyncAPI Command Line Tools: Dotnet tool to generate AsyncAPI spec file from dotnet startup assembly. AsyncAPI Initiative @@ -40,8 +41,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + - diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/DependencyResolver.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/DependencyResolver.cs new file mode 100644 index 00000000..3d6d010a --- /dev/null +++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/DependencyResolver.cs @@ -0,0 +1,22 @@ +using System.Reflection; + +namespace AsyncAPI.Saunter.Generator.Cli.ToFile; + +internal static class DependencyResolver +{ + public static void Init(string startupAssemblyBasePath) + { + AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => + { + var requestedAssembly = new AssemblyName(args.Name); + var fullPath = Path.Combine(startupAssemblyBasePath, $"{requestedAssembly.Name}.dll"); + if (File.Exists(fullPath)) + { + var assembly = Assembly.LoadFile(fullPath); + return assembly; + } + Console.WriteLine($"Could not resolve assembly: {args.Name}, requested by {args.RequestingAssembly?.FullName}"); + return default; + }; + } +} diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ServiceProviderBuilder.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ServiceProviderBuilder.cs index 69bde4f2..eed4c58b 100644 --- a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ServiceProviderBuilder.cs +++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ServiceProviderBuilder.cs @@ -1,5 +1,6 @@ -using System.Reflection; -using System.Runtime.Loader; +using System.Runtime.Loader; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.Extensions.Logging; namespace AsyncAPI.Saunter.Generator.Cli.ToFile; @@ -13,12 +14,20 @@ internal class ServiceProviderBuilder(ILogger logger) : { public IServiceProvider BuildServiceProvider(string startupAssembly) { - var fullPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), startupAssembly)); + var fullPath = Path.GetFullPath(startupAssembly); + var basePath = Path.GetDirectoryName(fullPath); + DependencyResolver.Init(basePath); + logger.LogInformation($"Loading startup assembly: {fullPath}"); var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(fullPath); - var nswagCommandsAssembly = Assembly.LoadFrom("NSwag.Commands.dll"); - var nswagServiceProvider = nswagCommandsAssembly.GetType("NSwag.Commands.ServiceProviderResolver"); - var serviceProvider = (IServiceProvider)nswagServiceProvider.InvokeMember("GetServiceProvider", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, null, [assembly]); + var reporter = new OperationReporter(new OperationReportHandler( + m => logger.LogError(m), + m => logger.LogWarning(m), + m => logger.LogInformation(m), + m => logger.LogDebug(m))); + var appServiceProvider = new AppServiceProviderFactory(assembly, reporter); + var serviceProvider = appServiceProvider.Create([]); + return serviceProvider; } } 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 6704dac9..ad8e79fc 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 @@ -31,4 +31,13 @@ + + + PreserveNewest + + + PreserveNewest + + + diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs index e7751ac3..52cb62e7 100644 --- a/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs +++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs @@ -50,186 +50,36 @@ Retrieves AsyncAPI spec from a startup assembly and writes to file. """, StringCompareShould.IgnoreLineEndings); } - [Fact] - public void StreetlightsAPIExportSpecTest() + /// + /// Both example projects are used to check whether AsyncAPI spec generation is working because they are targeting different .NET versions and are using different hosting strategies. + /// - StreetlightsAPI project is targeting NET6 using the 'old school' Startup-class hosting mechanism. + /// - StreetlightsAPI.TopLevelStatement project is targeting NET8 using the new Top Level Statement hosting mechanism. + /// + [Theory] + [InlineData("StreetlightsAPI", "net6.0")] + [InlineData("StreetlightsAPI.TopLevelStatement", "net8.0")] + public void Streetlights_ExportSpecTest(string csprojName, string targetFramework) { - var path = Directory.GetCurrentDirectory(); + var path = Path.Combine(Directory.GetCurrentDirectory(), csprojName); output.WriteLine($"Output path: {path}"); - var stdOut = RunTool($"tofile ../../../../../examples/StreetlightsAPI/bin/Debug/net6.0/StreetlightsAPI.dll --output {path} --format json,yml,yaml"); + var stdOut = RunTool($"tofile ../../../../../examples/{csprojName}/bin/Debug/{targetFramework}/{csprojName}.dll --output {path} --format json,yml,yaml"); stdOut.ShouldNotBeEmpty(); stdOut.ShouldContain($"AsyncAPI yaml successfully written to {Path.Combine(path, "asyncapi.yaml")}"); stdOut.ShouldContain($"AsyncAPI yml successfully written to {Path.Combine(path, "asyncapi.yml")}"); stdOut.ShouldContain($"AsyncAPI json successfully written to {Path.Combine(path, "asyncapi.json")}"); - File.Exists("asyncapi.yml").ShouldBeTrue("asyncapi.yml"); - File.Exists("asyncapi.yaml").ShouldBeTrue("asyncapi.yaml"); - File.Exists("asyncapi.json").ShouldBeTrue("asyncapi.json"); + File.Exists(Path.Combine(csprojName, "asyncapi.yml")).ShouldBeTrue("asyncapi.yml"); + File.Exists(Path.Combine(csprojName, "asyncapi.yaml")).ShouldBeTrue("asyncapi.yaml"); + File.Exists(Path.Combine(csprojName, "asyncapi.json")).ShouldBeTrue("asyncapi.json"); - var yml = File.ReadAllText("asyncapi.yml"); - yml.ShouldBe(""" - asyncapi: 2.6.0 - info: - title: Streetlights API - version: 1.0.0 - description: The Smartylighting Streetlights API allows you to remotely manage the city lights. - license: - name: Apache 2.0 - url: https://www.apache.org/licenses/LICENSE-2.0 - servers: - mosquitto: - url: test.mosquitto.org - protocol: mqtt - webapi: - url: localhost:5000 - protocol: http - defaultContentType: application/json - 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' - subscribe/light/measured: - servers: - - mosquitto - subscribe: - operationId: PublishLightMeasurement - summary: Subscribe to environmental lighting conditions for a particular streetlight. - tags: - - name: Light - message: - payload: - $ref: '#/components/schemas/lightMeasuredEvent' - components: - schemas: - lightMeasuredEvent: - type: object - properties: - id: - type: integer - format: int32 - description: Id of the streetlight. - lumens: - type: integer - format: int32 - description: Light intensity measured in lumens. - sentAt: - type: string - format: date-time - description: Light intensity measured in lumens. - additionalProperties: false - messages: - lightMeasuredEvent: - payload: - $ref: '#/components/schemas/lightMeasuredEvent' - name: lightMeasuredEvent - """, "yaml"); + var yml = File.ReadAllText(Path.Combine(csprojName, "asyncapi.yml")); + yml.ShouldBe(ExpectedSpecFiles.Yml_v2_6, "yml"); - var yaml = File.ReadAllText("asyncapi.yaml"); - yaml.ShouldBe(yml, "yml"); + var yaml = File.ReadAllText(Path.Combine(csprojName, "asyncapi.yaml")); + yaml.ShouldBe(yml, "yaml"); - var json = File.ReadAllText("asyncapi.json"); - json.ShouldBe(""" - { - "asyncapi": "2.6.0", - "info": { - "title": "Streetlights API", - "version": "1.0.0", - "description": "The Smartylighting Streetlights API allows you to remotely manage the city lights.", - "license": { - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0" - } - }, - "servers": { - "mosquitto": { - "url": "test.mosquitto.org", - "protocol": "mqtt" - }, - "webapi": { - "url": "localhost:5000", - "protocol": "http" - } - }, - "defaultContentType": "application/json", - "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" - } - } - }, - "subscribe/light/measured": { - "servers": [ - "mosquitto" - ], - "subscribe": { - "operationId": "PublishLightMeasurement", - "summary": "Subscribe to environmental lighting conditions for a particular streetlight.", - "tags": [ - { - "name": "Light" - } - ], - "message": { - "payload": { - "$ref": "#/components/schemas/lightMeasuredEvent" - } - } - } - } - }, - "components": { - "schemas": { - "lightMeasuredEvent": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int32", - "description": "Id of the streetlight." - }, - "lumens": { - "type": "integer", - "format": "int32", - "description": "Light intensity measured in lumens." - }, - "sentAt": { - "type": "string", - "format": "date-time", - "description": "Light intensity measured in lumens." - } - }, - "additionalProperties": false - } - }, - "messages": { - "lightMeasuredEvent": { - "payload": { - "$ref": "#/components/schemas/lightMeasuredEvent" - }, - "name": "lightMeasuredEvent" - } - } - } - } - """, "json"); + var json = File.ReadAllText(Path.Combine(csprojName, "asyncapi.json")); + json.ShouldBe(ExpectedSpecFiles.Json_v2_6, "json"); } } diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/ExpectedSpecFiles.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/ExpectedSpecFiles.cs new file mode 100644 index 00000000..aa6ed033 --- /dev/null +++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/ExpectedSpecFiles.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace AsyncAPI.Saunter.Generator.Cli.Tests; + +public static class ExpectedSpecFiles +{ + public static string Json_v2_6 => File.ReadAllText("Specs/streetlights_v2.6.json"); + + public static string Yml_v2_6 => File.ReadAllText("Specs/streetlights_v2.6.yml"); +} diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/streetlights_v2.6.json b/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/streetlights_v2.6.json new file mode 100644 index 00000000..8a429cbb --- /dev/null +++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/streetlights_v2.6.json @@ -0,0 +1,94 @@ +{ + "asyncapi": "2.6.0", + "info": { + "title": "Streetlights API", + "version": "1.0.0", + "description": "The Smartylighting Streetlights API allows you to remotely manage the city lights.", + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0" + } + }, + "servers": { + "mosquitto": { + "url": "test.mosquitto.org", + "protocol": "mqtt" + }, + "webapi": { + "url": "localhost:5000", + "protocol": "http" + } + }, + "defaultContentType": "application/json", + "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" + } + } + }, + "subscribe/light/measured": { + "servers": [ + "mosquitto" + ], + "subscribe": { + "operationId": "PublishLightMeasurement", + "summary": "Subscribe to environmental lighting conditions for a particular streetlight.", + "tags": [ + { + "name": "Light" + } + ], + "message": { + "payload": { + "$ref": "#/components/schemas/lightMeasuredEvent" + } + } + } + } + }, + "components": { + "schemas": { + "lightMeasuredEvent": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32", + "description": "Id of the streetlight." + }, + "lumens": { + "type": "integer", + "format": "int32", + "description": "Light intensity measured in lumens." + }, + "sentAt": { + "type": "string", + "format": "date-time", + "description": "Light intensity measured in lumens." + } + }, + "additionalProperties": false + } + }, + "messages": { + "lightMeasuredEvent": { + "payload": { + "$ref": "#/components/schemas/lightMeasuredEvent" + }, + "name": "lightMeasuredEvent" + } + } + } +} \ No newline at end of file diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/streetlights_v2.6.yml b/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/streetlights_v2.6.yml new file mode 100644 index 00000000..efccd7ed --- /dev/null +++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/streetlights_v2.6.yml @@ -0,0 +1,61 @@ +asyncapi: 2.6.0 +info: + title: Streetlights API + version: 1.0.0 + description: The Smartylighting Streetlights API allows you to remotely manage the city lights. + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0 +servers: + mosquitto: + url: test.mosquitto.org + protocol: mqtt + webapi: + url: localhost:5000 + protocol: http +defaultContentType: application/json +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' + subscribe/light/measured: + servers: + - mosquitto + subscribe: + operationId: PublishLightMeasurement + summary: Subscribe to environmental lighting conditions for a particular streetlight. + tags: + - name: Light + message: + payload: + $ref: '#/components/schemas/lightMeasuredEvent' +components: + schemas: + lightMeasuredEvent: + type: object + properties: + id: + type: integer + format: int32 + description: Id of the streetlight. + lumens: + type: integer + format: int32 + description: Light intensity measured in lumens. + sentAt: + type: string + format: date-time + description: Light intensity measured in lumens. + additionalProperties: false + messages: + lightMeasuredEvent: + payload: + $ref: '#/components/schemas/lightMeasuredEvent' + name: lightMeasuredEvent \ No newline at end of file From ba8dd8e5d1e3a1a20e9b3a3f22c329dc5363bcc5 Mon Sep 17 00:00:00 2001 From: Senn Geerts Date: Sat, 13 Jul 2024 20:23:37 +0200 Subject: [PATCH 2/9] #196 Ignore expected warning (ASP0014 needs #173) --- examples/StreetlightsAPI.TopLevelStatement/Program.cs | 2 +- .../StreetlightsAPI.TopLevelStatement.csproj | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/StreetlightsAPI.TopLevelStatement/Program.cs b/examples/StreetlightsAPI.TopLevelStatement/Program.cs index 79aa9229..164eb0ce 100644 --- a/examples/StreetlightsAPI.TopLevelStatement/Program.cs +++ b/examples/StreetlightsAPI.TopLevelStatement/Program.cs @@ -14,7 +14,7 @@ LogManager.Setup().LoadConfigurationFromAppSettings(); var builder = WebApplication.CreateBuilder(args); -builder.Host.ConfigureLogging(logging => logging.AddSimpleConsole(console => console.SingleLine = true)); +builder.Logging.AddSimpleConsole(console => console.SingleLine = true); builder.Host.UseNLog(); // Add Saunter to the application services. diff --git a/examples/StreetlightsAPI.TopLevelStatement/StreetlightsAPI.TopLevelStatement.csproj b/examples/StreetlightsAPI.TopLevelStatement/StreetlightsAPI.TopLevelStatement.csproj index 4e4dfcc0..64639d4f 100644 --- a/examples/StreetlightsAPI.TopLevelStatement/StreetlightsAPI.TopLevelStatement.csproj +++ b/examples/StreetlightsAPI.TopLevelStatement/StreetlightsAPI.TopLevelStatement.csproj @@ -5,6 +5,9 @@ the AsyncAPI.Saunter.Generator.Cli tool can generate specs for projects targetting .NET6 and .NET8. --> net8.0 false + + + $(NoWarn);ASP0014 true From 3d6c0ea1b5c323bd928de0c4bbff93b82f60d999 Mon Sep 17 00:00:00 2001 From: Senn Geerts Date: Sat, 13 Jul 2024 20:37:16 +0200 Subject: [PATCH 3/9] #196 formatting and readme wording --- .../ToFile/ToFileCommand.cs | 12 ++++++------ src/AsyncAPI.Saunter.Generator.Cli/readme.md | 12 ++++++------ .../IntegrationTests.cs | 14 +++++++------- .../Specs/ExpectedSpecFiles.cs | 6 +----- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ToFileCommand.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ToFileCommand.cs index 0a62c43d..91055b47 100644 --- a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ToFileCommand.cs +++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/ToFileCommand.cs @@ -13,11 +13,11 @@ internal class ToFileCommand(ILogger logger, IEnvironmentBuilder /// Retrieves AsyncAPI spec from a startup assembly and writes to file. /// /// relative path to the application's startup assembly - /// -o,relative path where the AsyncAPI will be output [defaults to stdout] - /// -d,name(s) of the AsyncAPI documents you want to retrieve, as configured in your startup class [defaults to all documents] - /// exports AsyncAPI in json and/or yml format [defaults to json] - /// defines the file name template, {document} and {extension} template variables can be used [defaults to "{document}_asyncapi.{extension}\"] - /// define environment variable(s) for the application. Formatted as a comma separated list of key=value pairs or just key for flags + /// -o,relative path where the AsyncAPI documents will be exported to + /// -d,name(s) of the AsyncAPI documents you want to export as configured in your startup class. To export all documents using null. + /// exports AsyncAPI in json and/or yml format + /// defines the file name template, {document} and {extension} template variables can be used + /// define environment variable(s) for the application. Formatted as a comma separated list of _key=value_ pairs [Command("tofile")] public int ToFile([Argument] string startupassembly, string output = "./", string doc = null, string format = "json", string filename = DEFAULT_FILENAME, string env = "") { @@ -68,6 +68,6 @@ public int ToFile([Argument] string startupassembly, string output = "./", strin } } - return 1; + return 0; } } diff --git a/src/AsyncAPI.Saunter.Generator.Cli/readme.md b/src/AsyncAPI.Saunter.Generator.Cli/readme.md index b5ee1273..01030989 100644 --- a/src/AsyncAPI.Saunter.Generator.Cli/readme.md +++ b/src/AsyncAPI.Saunter.Generator.Cli/readme.md @@ -1,17 +1,17 @@ # AsyncApi Generator.Cli Tool -A dotnet tool to generate AsyncAPI specification files based of a dotnet DLL (The application itself). +A dotnet tool to generate AsyncAPI specification files based of a dotnet assembly (The application itself). ## Tool usage ``` dotnet asyncapi tofile [startup-assembly] --output [output-path] --format [json,yml,yaml] --doc [asyncapi-document-name] ``` -- _startup-assembly_: the file path to the entrypoint dotnet DLL that hosts AsyncAPI document(s). +- _startup-assembly_: the file path to the dotnet startup assembly (DLL) that hosts AsyncAPI document(s). ## Tool options - _--doc_: The name of the AsyncAPI document as defined in the startup class by the ```.ConfigureNamedAsyncApi()```-method. If only ```.AddAsyncApiSchemaGeneration()``` is used, the document is unnamed and will always be exported. If not specified, all documents will be exported. -- _--output_: relative path where the AsyncAPI will be output [defaults to stdout] -- _--filename_: the template for the outputted file names. Default: "{document}_asyncapi.{extension}" -- _--format_: the output formats to generate, can be a combination of json, yml and/or yaml. +- _--output_: relative path where the AsyncAPI documents will be exported to (Default: the csproj root "./"). +- _--filename_: the template for the outputted file names (Default: "{document}_asyncapi.{extension}"). +- _--format_: the output formats to generate, can be a combination of json, yml and/or yaml (Default: "json"). - _--env_: define environment variable(s) for the application. Formatted as a comma separated list of _key=value_ pairs, example: ```ASPNETCORE_ENVIRONMENT=AsyncAPI,CONNECT_TO_DATABASE=false```. ## Install the Generator.Cli dotnet Tool @@ -24,4 +24,4 @@ Want to learn more about .NET tools? Or want to install it local using a manifes [Check out this Microsoft page on how to manage .NET tools](https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools) ## Internals -How does the tool work internally? It tries to exact an ```IServiceProvider``` from the provided _startup-assembly_ and exports AsyncApiDocument(s) as registered in the services provider. \ No newline at end of file +How does the tool work internally? It tries to exact an ```IServiceProvider``` from the provided _startup-assembly_ and exports AsyncApiDocument(s) as registered with the services provider. \ No newline at end of file diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs index 52cb62e7..7b5c6fb6 100644 --- a/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs +++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs @@ -5,7 +5,7 @@ namespace AsyncAPI.Saunter.Generator.Cli.Tests; public class IntegrationTests(ITestOutputHelper output) { - private string RunTool(string args, int expectedExitCode = 1) + private string RunTool(string args, int expectedExitCode = 0) { using var outWriter = new StringWriter(); using var errorWriter = new StringWriter(); @@ -31,7 +31,7 @@ private string RunTool(string args, int expectedExitCode = 1) [Fact] public void DefaultCallPrintsCommandInfo() { - var stdOut = RunTool("tofile", 0).Trim(); + var stdOut = RunTool("tofile").Trim(); stdOut.ShouldBe(""" Usage: tofile [arguments...] [options...] [-h|--help] [--version] @@ -42,11 +42,11 @@ Retrieves AsyncAPI spec from a startup assembly and writes to file. [0] relative path to the application's startup assembly Options: - -o|--output relative path where the AsyncAPI will be output [defaults to stdout] (Default: "./") - -d|--doc name(s) of the AsyncAPI documents you want to retrieve as configured in your startup class [defaults to all documents] (Default: null) - --format exports AsyncAPI in json and/or yml format [defaults to json] (Default: "json") - --filename defines the file name template, {document} and {extension} template variables can be used [defaults to "{document}_asyncapi.{extension}\"] (Default: "{document}_asyncapi.{extension}") - --env define environment variable(s) for the application. Formatted as a comma separated list of key=value pairs or just key for flags (Default: "") + -o|--output relative path where the AsyncAPI documents will be exported to (Default: "./") + -d|--doc name(s) of the AsyncAPI documents you want to export as configured in your startup class. To export all documents using null. (Default: null) + --format exports AsyncAPI in json and/or yml format (Default: "json") + --filename defines the file name template, {document} and {extension} template variables can be used (Default: "{document}_asyncapi.{extension}") + --env define environment variable(s) for the application. Formatted as a comma separated list of _key=value_ pairs (Default: "") """, StringCompareShould.IgnoreLineEndings); } diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/ExpectedSpecFiles.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/ExpectedSpecFiles.cs index aa6ed033..0d569e3a 100644 --- a/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/ExpectedSpecFiles.cs +++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/Specs/ExpectedSpecFiles.cs @@ -1,8 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace AsyncAPI.Saunter.Generator.Cli.Tests; +namespace AsyncAPI.Saunter.Generator.Cli.Tests; public static class ExpectedSpecFiles { From 8a4f5c6b6c80abc614de6137adf20782d8388d01 Mon Sep 17 00:00:00 2001 From: Senn Geerts Date: Sat, 13 Jul 2024 20:43:36 +0200 Subject: [PATCH 4/9] #196 ExitCode test problems --- examples/StreetlightsAPI.TopLevelStatement/Program.cs | 3 +++ .../StreetlightsAPI.TopLevelStatement.csproj | 3 --- src/AsyncAPI.Saunter.Generator.Cli/Program.cs | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/StreetlightsAPI.TopLevelStatement/Program.cs b/examples/StreetlightsAPI.TopLevelStatement/Program.cs index 164eb0ce..2e59f1cb 100644 --- a/examples/StreetlightsAPI.TopLevelStatement/Program.cs +++ b/examples/StreetlightsAPI.TopLevelStatement/Program.cs @@ -52,6 +52,8 @@ app.UseRouting(); app.UseCors(configure => configure.AllowAnyOrigin().AllowAnyMethod()); +// to be fixed with issue #173 +#pragma warning disable ASP0014 // Suggest using top level route registrations instead of UseEndpoints app.UseEndpoints(endpoints => { endpoints.MapAsyncApiDocuments(); @@ -59,6 +61,7 @@ endpoints.MapControllers(); }); +#pragma warning restore ASP0014 // Suggest using top level route registrations instead of UseEndpoints await app.StartAsync(); diff --git a/examples/StreetlightsAPI.TopLevelStatement/StreetlightsAPI.TopLevelStatement.csproj b/examples/StreetlightsAPI.TopLevelStatement/StreetlightsAPI.TopLevelStatement.csproj index 64639d4f..4e4dfcc0 100644 --- a/examples/StreetlightsAPI.TopLevelStatement/StreetlightsAPI.TopLevelStatement.csproj +++ b/examples/StreetlightsAPI.TopLevelStatement/StreetlightsAPI.TopLevelStatement.csproj @@ -5,9 +5,6 @@ the AsyncAPI.Saunter.Generator.Cli tool can generate specs for projects targetting .NET6 and .NET8. --> net8.0 false - - - $(NoWarn);ASP0014 true diff --git a/src/AsyncAPI.Saunter.Generator.Cli/Program.cs b/src/AsyncAPI.Saunter.Generator.Cli/Program.cs index 3502ea7d..58bfef22 100644 --- a/src/AsyncAPI.Saunter.Generator.Cli/Program.cs +++ b/src/AsyncAPI.Saunter.Generator.Cli/Program.cs @@ -15,3 +15,5 @@ var app = ConsoleApp.Create(); app.Add(); app.Run(args); + +Environment.ExitCode = 0; From 07b4273b9e1ffaae0fea1208621c5190131a5258 Mon Sep 17 00:00:00 2001 From: Senn Geerts Date: Sat, 13 Jul 2024 20:48:09 +0200 Subject: [PATCH 5/9] #196 ExitCode test problems -- the issue is a missing reference --- .../AsyncAPI.Saunter.Generator.Cli.Tests.csproj | 1 + 1 file changed, 1 insertion(+) 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 ad8e79fc..edd24802 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 @@ -23,6 +23,7 @@ + From e846dd9ac829ca654b09b2abf4e09afff2f1326e Mon Sep 17 00:00:00 2001 From: Senn Geerts Date: Sat, 13 Jul 2024 20:57:02 +0200 Subject: [PATCH 6/9] #196 GetStreamFor logging --- .../ToFile/StreamProvider.cs | 8 ++++++-- .../IntegrationTests.cs | 14 +++++++------- .../ToFile/StreamProviderTests.cs | 15 ++++++++++++++- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs index 8af5dbd4..09f56617 100644 --- a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs +++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs @@ -1,14 +1,18 @@ -namespace AsyncAPI.Saunter.Generator.Cli.ToFile; +using Microsoft.Extensions.Logging; + +namespace AsyncAPI.Saunter.Generator.Cli.ToFile; internal interface IStreamProvider { Stream GetStreamFor(string path); } -internal class StreamProvider : IStreamProvider +internal class StreamProvider(ILogger logger) : IStreamProvider { public Stream GetStreamFor(string path) { + logger.LogDebug($"GetStreamFor(path: {path})"); + if (!string.IsNullOrEmpty(path)) { Directory.CreateDirectory(Path.GetDirectoryName(path)); diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs index 7b5c6fb6..0f352801 100644 --- a/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs +++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs @@ -60,7 +60,7 @@ Retrieves AsyncAPI spec from a startup assembly and writes to file. [InlineData("StreetlightsAPI.TopLevelStatement", "net8.0")] public void Streetlights_ExportSpecTest(string csprojName, string targetFramework) { - var path = Path.Combine(Directory.GetCurrentDirectory(), csprojName); + var path = Path.Combine(Directory.GetCurrentDirectory(), csprojName, "specs"); output.WriteLine($"Output path: {path}"); var stdOut = RunTool($"tofile ../../../../../examples/{csprojName}/bin/Debug/{targetFramework}/{csprojName}.dll --output {path} --format json,yml,yaml"); @@ -69,17 +69,17 @@ public void Streetlights_ExportSpecTest(string csprojName, string targetFramewor stdOut.ShouldContain($"AsyncAPI yml successfully written to {Path.Combine(path, "asyncapi.yml")}"); stdOut.ShouldContain($"AsyncAPI json successfully written to {Path.Combine(path, "asyncapi.json")}"); - File.Exists(Path.Combine(csprojName, "asyncapi.yml")).ShouldBeTrue("asyncapi.yml"); - File.Exists(Path.Combine(csprojName, "asyncapi.yaml")).ShouldBeTrue("asyncapi.yaml"); - File.Exists(Path.Combine(csprojName, "asyncapi.json")).ShouldBeTrue("asyncapi.json"); + File.Exists(Path.Combine(path, "asyncapi.yml")).ShouldBeTrue("asyncapi.yml"); + File.Exists(Path.Combine(path, "asyncapi.yaml")).ShouldBeTrue("asyncapi.yaml"); + File.Exists(Path.Combine(path, "asyncapi.json")).ShouldBeTrue("asyncapi.json"); - var yml = File.ReadAllText(Path.Combine(csprojName, "asyncapi.yml")); + var yml = File.ReadAllText(Path.Combine(path, "asyncapi.yml")); yml.ShouldBe(ExpectedSpecFiles.Yml_v2_6, "yml"); - var yaml = File.ReadAllText(Path.Combine(csprojName, "asyncapi.yaml")); + var yaml = File.ReadAllText(Path.Combine(path, "asyncapi.yaml")); yaml.ShouldBe(yml, "yaml"); - var json = File.ReadAllText(Path.Combine(csprojName, "asyncapi.json")); + var json = File.ReadAllText(Path.Combine(path, "asyncapi.json")); json.ShouldBe(ExpectedSpecFiles.Json_v2_6, "json"); } } diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/StreamProviderTests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/StreamProviderTests.cs index a340b6d4..27051eb1 100644 --- a/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/StreamProviderTests.cs +++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/ToFile/StreamProviderTests.cs @@ -1,11 +1,21 @@ 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 StreamProviderTests { - private readonly IStreamProvider _streamProvider = new StreamProvider(); + private readonly IStreamProvider _streamProvider; + private readonly ILogger _logger; + + public StreamProviderTests() + { + this._logger = Substitute.For>(); + this._streamProvider = new StreamProvider(this._logger); + } [Fact] public void NullPathIsStdOut() @@ -14,6 +24,7 @@ public void NullPathIsStdOut() stream.ShouldNotBeNull(); Assert.False(stream is FileStream); + this._logger.Received(1).CallToLog(LogLevel.Debug); } [Fact] @@ -33,5 +44,7 @@ public void StringPathIsFileStream() { File.Delete(path); } + + this._logger.Received(1).CallToLog(LogLevel.Debug); } } From fc7cccd26ad3765a1bb2583d6594b1cb00cd7c8e Mon Sep 17 00:00:00 2001 From: Senn Geerts Date: Sat, 13 Jul 2024 21:28:48 +0200 Subject: [PATCH 7/9] #196 GetStreamFor try recreate directory? --- .../ToFile/StreamProvider.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs index 09f56617..2e285438 100644 --- a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs +++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using System.Diagnostics; +using Microsoft.Extensions.Logging; namespace AsyncAPI.Saunter.Generator.Cli.ToFile; @@ -15,7 +16,21 @@ public Stream GetStreamFor(string path) if (!string.IsNullOrEmpty(path)) { - Directory.CreateDirectory(Path.GetDirectoryName(path)); + var directory = Path.GetDirectoryName(path); + var sw = Stopwatch.StartNew(); + do + { + try + { + Directory.CreateDirectory(directory); + } + catch (Exception e) when (sw.Elapsed < TimeSpan.FromMilliseconds(250)) + { + logger.LogDebug(e, "Retry..."); + Thread.Sleep(100); + } + } + while (!Directory.Exists(directory)); } return path != null ? File.Create(path) : Console.OpenStandardOutput(); From 4f1064e412d07c4206cb2f135c61d4302cb84d6f Mon Sep 17 00:00:00 2001 From: Senn Geerts Date: Sat, 13 Jul 2024 21:33:08 +0200 Subject: [PATCH 8/9] #196 GetStreamFor try recreate directory?? --- .../ToFile/StreamProvider.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs index 2e285438..19583e6d 100644 --- a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs +++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs @@ -16,21 +16,21 @@ public Stream GetStreamFor(string path) if (!string.IsNullOrEmpty(path)) { - var directory = Path.GetDirectoryName(path); + var directory = new DirectoryInfo(Path.GetDirectoryName(path)); var sw = Stopwatch.StartNew(); do { try { - Directory.CreateDirectory(directory); + directory.Create(); } catch (Exception e) when (sw.Elapsed < TimeSpan.FromMilliseconds(250)) { - logger.LogDebug(e, "Retry..."); + logger.LogDebug(e, $"Retry... {directory.Parent.Exists}, {directory.Parent.Parent.Exists}, {directory.Parent.Parent.Parent.Exists}"); Thread.Sleep(100); } } - while (!Directory.Exists(directory)); + while (!directory.Exists); } return path != null ? File.Create(path) : Console.OpenStandardOutput(); From 74260a8fb877dcb86dec50ead7afcfb2d5b798f0 Mon Sep 17 00:00:00 2001 From: Senn Geerts Date: Sat, 13 Jul 2024 21:41:02 +0200 Subject: [PATCH 9/9] #196 permission issue? --- .../ToFile/StreamProvider.cs | 16 +------------- .../IntegrationTests.cs | 22 +++++++++---------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs index 19583e6d..7ada6199 100644 --- a/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs +++ b/src/AsyncAPI.Saunter.Generator.Cli/ToFile/StreamProvider.cs @@ -16,21 +16,7 @@ public Stream GetStreamFor(string path) if (!string.IsNullOrEmpty(path)) { - var directory = new DirectoryInfo(Path.GetDirectoryName(path)); - var sw = Stopwatch.StartNew(); - do - { - try - { - directory.Create(); - } - catch (Exception e) when (sw.Elapsed < TimeSpan.FromMilliseconds(250)) - { - logger.LogDebug(e, $"Retry... {directory.Parent.Exists}, {directory.Parent.Parent.Exists}, {directory.Parent.Parent.Parent.Exists}"); - Thread.Sleep(100); - } - } - while (!directory.Exists); + Directory.CreateDirectory(Path.GetDirectoryName(path)); } return path != null ? File.Create(path) : Console.OpenStandardOutput(); diff --git a/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs b/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs index 0f352801..c6079917 100644 --- a/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs +++ b/test/AsyncAPI.Saunter.Generator.Cli.Tests/IntegrationTests.cs @@ -60,26 +60,26 @@ Retrieves AsyncAPI spec from a startup assembly and writes to file. [InlineData("StreetlightsAPI.TopLevelStatement", "net8.0")] public void Streetlights_ExportSpecTest(string csprojName, string targetFramework) { - var path = Path.Combine(Directory.GetCurrentDirectory(), csprojName, "specs"); + var path = Path.Combine(Directory.GetCurrentDirectory()); output.WriteLine($"Output path: {path}"); - var stdOut = RunTool($"tofile ../../../../../examples/{csprojName}/bin/Debug/{targetFramework}/{csprojName}.dll --output {path} --format json,yml,yaml"); + var stdOut = RunTool($"tofile ../../../../../examples/{csprojName}/bin/Debug/{targetFramework}/{csprojName}.dll --output {path} --filename {csprojName}.{{extension}} --format json,yml,yaml"); stdOut.ShouldNotBeEmpty(); - stdOut.ShouldContain($"AsyncAPI yaml successfully written to {Path.Combine(path, "asyncapi.yaml")}"); - stdOut.ShouldContain($"AsyncAPI yml successfully written to {Path.Combine(path, "asyncapi.yml")}"); - stdOut.ShouldContain($"AsyncAPI json successfully written to {Path.Combine(path, "asyncapi.json")}"); + stdOut.ShouldContain($"AsyncAPI yaml successfully written to {Path.Combine(path, $"{csprojName}.yaml")}"); + stdOut.ShouldContain($"AsyncAPI yml successfully written to {Path.Combine(path, $"{csprojName}.yml")}"); + stdOut.ShouldContain($"AsyncAPI json successfully written to {Path.Combine(path, $"{csprojName}.json")}"); - File.Exists(Path.Combine(path, "asyncapi.yml")).ShouldBeTrue("asyncapi.yml"); - File.Exists(Path.Combine(path, "asyncapi.yaml")).ShouldBeTrue("asyncapi.yaml"); - File.Exists(Path.Combine(path, "asyncapi.json")).ShouldBeTrue("asyncapi.json"); + File.Exists(Path.Combine(path, $"{csprojName}.yml")).ShouldBeTrue(); + File.Exists(Path.Combine(path, $"{csprojName}.yaml")).ShouldBeTrue(); + File.Exists(Path.Combine(path, $"{csprojName}.json")).ShouldBeTrue(); - var yml = File.ReadAllText(Path.Combine(path, "asyncapi.yml")); + var yml = File.ReadAllText(Path.Combine(path, $"{csprojName}.yml")); yml.ShouldBe(ExpectedSpecFiles.Yml_v2_6, "yml"); - var yaml = File.ReadAllText(Path.Combine(path, "asyncapi.yaml")); + var yaml = File.ReadAllText(Path.Combine(path, $"{csprojName}.yaml")); yaml.ShouldBe(yml, "yaml"); - var json = File.ReadAllText(Path.Combine(path, "asyncapi.json")); + var json = File.ReadAllText(Path.Combine(path, $"{csprojName}.json")); json.ShouldBe(ExpectedSpecFiles.Json_v2_6, "json"); } }