diff --git a/.gitignore b/.gitignore index 55e5f0a25..d12b4d991 100644 --- a/.gitignore +++ b/.gitignore @@ -285,3 +285,6 @@ install.sh # Confidential temporary files — must not reach public repository tmp/ +test/Generated/GeneratedCode/*.generated.cs +test/MSBuild/Generated/**/*.generated.cs +test/MSBuild/GeneratedOutput/**/*.cs \ No newline at end of file diff --git a/src/Refitter.MSBuild/RefitterGenerateTask.cs b/src/Refitter.MSBuild/RefitterGenerateTask.cs index 307f47baf..6bb370596 100644 --- a/src/Refitter.MSBuild/RefitterGenerateTask.cs +++ b/src/Refitter.MSBuild/RefitterGenerateTask.cs @@ -209,17 +209,12 @@ private List GetExpectedGeneratedFiles(string refitterFilePath) else { // Single file mode - string outputPath; - if (!string.IsNullOrWhiteSpace(outputFolder)) - { - // outputFolder is specified - outputPath = Path.GetFullPath(Path.Combine(refitterFileDirectory, outputFolder, outputFilename)); - } - else - { - // No outputFolder specified - generate in current directory (default behavior) - outputPath = Path.GetFullPath(Path.Combine(refitterFileDirectory, outputFilename)); - } + // If outputFolder is not specified in .refitter, CLI uses default "./Generated" + var effectiveOutputFolder = !string.IsNullOrWhiteSpace(outputFolder) + ? outputFolder + : "./Generated"; + + var outputPath = Path.GetFullPath(Path.Combine(refitterFileDirectory, effectiveOutputFolder, outputFilename)); generatedFiles.Add(outputPath); } diff --git a/src/Refitter.Tests/Examples/PropertyNamingPolicyTests.cs b/src/Refitter.Tests/Examples/PropertyNamingPolicyTests.cs index 348604e1d..1929e1257 100644 --- a/src/Refitter.Tests/Examples/PropertyNamingPolicyTests.cs +++ b/src/Refitter.Tests/Examples/PropertyNamingPolicyTests.cs @@ -319,4 +319,4 @@ private static async Task GenerateCodeFromSpec( } } } -} \ No newline at end of file +} diff --git a/src/Refitter.Tests/Examples/SettingsFileOutputPathTests.cs b/src/Refitter.Tests/Examples/SettingsFileOutputPathTests.cs new file mode 100644 index 000000000..323352199 --- /dev/null +++ b/src/Refitter.Tests/Examples/SettingsFileOutputPathTests.cs @@ -0,0 +1,186 @@ +using System.Reflection; +using FluentAssertions; +using Refitter.Core; +using Refitter.Tests.Build; + +namespace Refitter.Tests.Examples; + +/// +/// Regression tests for issue #998: MSBuild + .NET 10 ignores outputFolder and filename-from-.refitter behavior +/// Tests exercise the actual GenerateCommand helper logic via reflection +/// +public class SettingsFileOutputPathTests +{ + private const string MinimalOpenApiSpec = """ + { + "openapi": "3.0.1", + "info": { "title": "Test API", "version": "1.0.0" }, + "paths": { + "/test": { + "get": { + "operationId": "getTest", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { "id": { "type": "integer" } } + } + } + } + } + } + } + } + } + } + """; + + [Test] + public void SettingsFile_WithoutOutputFilename_UsesRefitterFilenameFallback() + { + var tempDir = CreateTempDirectory(); + + try + { + var settingsFile = Path.Combine(tempDir, "myapi.refitter"); + File.WriteAllText(settingsFile, "{}"); + + var settingsJson = """ + { + "openApiPath": "spec.json", + "namespace": "TestNamespace" + } + """; + + var refitGeneratorSettings = Serializer.Deserialize(settingsJson); + var settings = new Settings { SettingsFilePath = settingsFile }; + + ApplySettingsFileDefaults(settingsFile, refitGeneratorSettings); + + refitGeneratorSettings.OutputFilename.Should().Be("myapi.cs", + "because OutputFilename should default to the .refitter filename"); + + var outputPath = GetOutputPath(settings, refitGeneratorSettings); + Path.GetFullPath(outputPath).Should().Be(Path.GetFullPath(Path.Combine(tempDir, "Generated", "myapi.cs")), + "because default output folder is ./Generated rooted at the settings file directory"); + } + finally + { + CleanupDirectory(tempDir); + } + } + + [Test] + public void SettingsFile_WithExplicitDefaultOutputFolder_RootsToSettingsFileDirectory() + { + var tempDir = CreateTempDirectory(); + + try + { + var settingsFile = Path.Combine(tempDir, "api.refitter"); + File.WriteAllText(settingsFile, "{}"); + + var settingsJson = """ + { + "openApiPath": "spec.json", + "namespace": "TestNamespace", + "outputFolder": "./Generated", + "outputFilename": "Api.cs" + } + """; + + var refitGeneratorSettings = Serializer.Deserialize(settingsJson); + var settings = new Settings { SettingsFilePath = settingsFile }; + + ApplySettingsFileDefaults(settingsFile, refitGeneratorSettings); + + var outputPath = GetOutputPath(settings, refitGeneratorSettings); + Path.GetFullPath(outputPath).Should().Be(Path.GetFullPath(Path.Combine(tempDir, "Generated", "Api.cs")), + "because outputFolder is rooted relative to the .refitter file directory"); + } + finally + { + CleanupDirectory(tempDir); + } + } + + [Test] + public async Task SettingsFile_PreservesNamingInterfaceName() + { + // Arrange: .refitter with custom naming.interfaceName + var tempDir = CreateTempDirectory(); + + try + { + var openApiFile = Path.Combine(tempDir, "spec.json"); + await File.WriteAllTextAsync(openApiFile, MinimalOpenApiSpec); + + var settingsJson = """ + { + "openApiPath": "spec.json", + "namespace": "TestNamespace", + "naming": { + "useOpenApiTitle": false, + "interfaceName": "CustomApi" + } + } + """; + + var refitGeneratorSettings = Serializer.Deserialize(settingsJson); + refitGeneratorSettings.OpenApiPath = openApiFile; + + var generator = await RefitGenerator.CreateAsync(refitGeneratorSettings); + var code = generator.Generate(); + + // Assert: Naming settings should be preserved and used in generation + refitGeneratorSettings.Naming.Should().NotBeNull(); + refitGeneratorSettings.Naming.InterfaceName.Should().Be("CustomApi"); + code.Should().Contain("interface ICustomApi", "because custom interface name should be used"); + BuildHelper.BuildCSharp(code).Should().BeTrue(); + } + finally + { + CleanupDirectory(tempDir); + } + } + + private static string GetOutputPath(Settings settings, RefitGeneratorSettings refitGeneratorSettings) + { + var method = typeof(GenerateCommand).GetMethod( + "GetOutputPath", + BindingFlags.NonPublic | BindingFlags.Static, + null, + [typeof(Settings), typeof(RefitGeneratorSettings)], + null); + + method.Should().NotBeNull(); + + return (string)method!.Invoke(null, [settings, refitGeneratorSettings])!; + } + + private static void ApplySettingsFileDefaults(string settingsFilePath, RefitGeneratorSettings refitGeneratorSettings) + { + var method = typeof(GenerateCommand).GetMethod( + "ApplySettingsFileDefaults", + BindingFlags.NonPublic | BindingFlags.Static); + + method.Should().NotBeNull(); + method!.Invoke(null, [settingsFilePath, refitGeneratorSettings]); + } + + private static string CreateTempDirectory() + { + var tempDir = Path.Combine(Path.GetTempPath(), $"refitter-test-{Guid.NewGuid()}"); + Directory.CreateDirectory(tempDir); + return tempDir; + } + + private static void CleanupDirectory(string path) + { + if (Directory.Exists(path)) + Directory.Delete(path, recursive: true); + } +} diff --git a/src/Refitter.Tests/Examples/TrimUnusedSchemaAliasWithLeadingDigitPropertyTests.cs b/src/Refitter.Tests/Examples/TrimUnusedSchemaAliasWithLeadingDigitPropertyTests.cs index a5b4f4fb7..dcb024a49 100644 --- a/src/Refitter.Tests/Examples/TrimUnusedSchemaAliasWithLeadingDigitPropertyTests.cs +++ b/src/Refitter.Tests/Examples/TrimUnusedSchemaAliasWithLeadingDigitPropertyTests.cs @@ -233,4 +233,4 @@ private static async Task GenerateCodeFromSpec(string openApiSpec) } } } -} \ No newline at end of file +} diff --git a/src/Refitter.Tests/GenerateCommandTests.cs b/src/Refitter.Tests/GenerateCommandTests.cs index eaf039f86..b149d398e 100644 --- a/src/Refitter.Tests/GenerateCommandTests.cs +++ b/src/Refitter.Tests/GenerateCommandTests.cs @@ -147,4 +147,228 @@ public void Command_Should_Have_Protected_ExecuteAsync_Method() method.Should().NotBeNull(); method!.IsFamily.Should().BeTrue(); } + + [Test] + public void GetOutputPath_Should_Root_Relative_To_SettingsFile_When_OutputFolder_Is_Default() + { + var settingsFilePath = Path.Combine(Path.GetTempPath(), "Projects", "MyApi", "petstore.refitter"); + var settings = new Settings + { + SettingsFilePath = settingsFilePath, + OutputPath = Settings.DefaultOutputPath + }; + + var refitSettings = new RefitGeneratorSettings + { + OutputFolder = "./Generated", + OutputFilename = "Output.cs" + }; + + var method = typeof(GenerateCommand).GetMethod( + "GetOutputPath", + BindingFlags.NonPublic | BindingFlags.Static, + [typeof(Settings), typeof(RefitGeneratorSettings)]); + + method.Should().NotBeNull(); + var result = method!.Invoke(null, [settings, refitSettings]) as string; + + var expectedPath = Path.Combine(Path.GetDirectoryName(settingsFilePath)!, "./Generated", "Output.cs"); + result.Should().Be(expectedPath); + } + + [Test] + public void GetOutputPath_Should_Use_Explicit_Cli_Output_File_Path() + { + var settings = new Settings + { + OutputPath = Path.Combine("GeneratedCode", "Petstore.cs") + }; + + var refitSettings = new RefitGeneratorSettings + { + OutputFolder = "./Generated", + OutputFilename = "Output.cs" + }; + + var method = typeof(GenerateCommand).GetMethod( + "GetOutputPath", + BindingFlags.NonPublic | BindingFlags.Static, + [typeof(Settings), typeof(RefitGeneratorSettings)]); + + method.Should().NotBeNull(); + var result = method!.Invoke(null, [settings, refitSettings]) as string; + + result.Should().Be(Path.Combine("GeneratedCode", "Petstore.cs")); + } + + [Test] + public void GetOutputPath_Should_Use_Default_Cli_Output_File_In_Current_Directory() + { + var settings = new Settings + { + OutputPath = Settings.DefaultOutputPath + }; + + var refitSettings = new RefitGeneratorSettings + { + OutputFolder = "./Generated", + OutputFilename = "Output.cs" + }; + + var method = typeof(GenerateCommand).GetMethod( + "GetOutputPath", + BindingFlags.NonPublic | BindingFlags.Static, + [typeof(Settings), typeof(RefitGeneratorSettings)]); + + method.Should().NotBeNull(); + var result = method!.Invoke(null, [settings, refitSettings]) as string; + + result.Should().Be(Settings.DefaultOutputPath); + } + + [Test] + public void GetOutputPath_Should_Root_Relative_To_SettingsFile_When_OutputFolder_Is_Custom() + { + var settingsFilePath = Path.Combine(Path.GetTempPath(), "Projects", "MyApi", "petstore.refitter"); + var settings = new Settings + { + SettingsFilePath = settingsFilePath, + OutputPath = Settings.DefaultOutputPath + }; + + var refitSettings = new RefitGeneratorSettings + { + OutputFolder = "./CustomOutput", + OutputFilename = "PetStore.cs" + }; + + var method = typeof(GenerateCommand).GetMethod( + "GetOutputPath", + BindingFlags.NonPublic | BindingFlags.Static, + [typeof(Settings), typeof(RefitGeneratorSettings)]); + + var result = method!.Invoke(null, [settings, refitSettings]) as string; + + var expectedPath = Path.Combine(Path.GetDirectoryName(settingsFilePath)!, "./CustomOutput", "PetStore.cs"); + result.Should().Be(expectedPath); + } + + [Test] + public void GetOutputPath_Should_Use_SettingsFileName_When_OutputFilename_Is_Missing() + { + // OutputFilename will be defaulted by ApplySettingsFileDefaults + var settingsFilePath = Path.Combine(Path.GetTempPath(), "Projects", "MyApi", "petstore.refitter"); + var settings = new Settings + { + SettingsFilePath = settingsFilePath, + OutputPath = Settings.DefaultOutputPath + }; + + var refitSettings = new RefitGeneratorSettings + { + OutputFolder = "./Generated", + OutputFilename = null + }; + + // Apply defaults first (simulates what GenerateCommand does) + var applyMethod = typeof(GenerateCommand).GetMethod( + "ApplySettingsFileDefaults", + BindingFlags.NonPublic | BindingFlags.Static); + applyMethod!.Invoke(null, [settingsFilePath, refitSettings]); + + var method = typeof(GenerateCommand).GetMethod( + "GetOutputPath", + BindingFlags.NonPublic | BindingFlags.Static, + [typeof(Settings), typeof(RefitGeneratorSettings)]); + + var result = method!.Invoke(null, [settings, refitSettings]) as string; + + // Should use settings file base name + var expectedFilename = Path.GetFileNameWithoutExtension(settingsFilePath) + ".cs"; + var expectedPath = Path.Combine(Path.GetDirectoryName(settingsFilePath)!, "./Generated", expectedFilename); + result.Should().Be(expectedPath); + } + + [Test] + public void GetOutputPath_For_MultipleFiles_Should_Use_Explicit_Cli_Output_Directory() + { + var settings = new Settings + { + OutputPath = Path.Combine("GeneratedCode", "MultipleFiles"), + GenerateMultipleFiles = true + }; + + var refitSettings = new RefitGeneratorSettings + { + GenerateMultipleFiles = true, + OutputFolder = "./Generated" + }; + + var command = new GenerateCommand(); + var method = typeof(GenerateCommand).GetMethod( + "GetOutputPath", + BindingFlags.NonPublic | BindingFlags.Instance, + [typeof(Settings), typeof(RefitGeneratorSettings), typeof(GeneratedCode)]); + + method.Should().NotBeNull(); + + var result = method!.Invoke(command, [settings, refitSettings, new GeneratedCode("RefitInterfaces", "// code")]) as string; + + result.Should().Be(Path.Combine("GeneratedCode", "MultipleFiles", "RefitInterfaces.cs")); + } + + [Test] + public void ApplySettingsFileDefaults_Should_Set_Default_OutputFolder() + { + var settingsFilePath = Path.Combine(Path.GetTempPath(), "Projects", "MyApi", "petstore.refitter"); + var refitSettings = new RefitGeneratorSettings + { + OutputFolder = null + }; + + var method = typeof(GenerateCommand).GetMethod( + "ApplySettingsFileDefaults", + BindingFlags.NonPublic | BindingFlags.Static); + + method.Should().NotBeNull(); + method!.Invoke(null, [settingsFilePath, refitSettings]); + + refitSettings.OutputFolder.Should().Be("./Generated"); + } + + [Test] + public void ApplySettingsFileDefaults_Should_Preserve_Explicit_OutputFolder() + { + var settingsFilePath = Path.Combine(Path.GetTempPath(), "Projects", "MyApi", "petstore.refitter"); + var refitSettings = new RefitGeneratorSettings + { + OutputFolder = "./CustomFolder" + }; + + var method = typeof(GenerateCommand).GetMethod( + "ApplySettingsFileDefaults", + BindingFlags.NonPublic | BindingFlags.Static); + + method!.Invoke(null, [settingsFilePath, refitSettings]); + + refitSettings.OutputFolder.Should().Be("./CustomFolder"); + } + + [Test] + public void ApplySettingsFileDefaults_Should_Set_Default_When_Empty_OutputFolder() + { + var settingsFilePath = Path.Combine(Path.GetTempPath(), "Projects", "MyApi", "petstore.refitter"); + var refitSettings = new RefitGeneratorSettings + { + OutputFolder = string.Empty + }; + + var method = typeof(GenerateCommand).GetMethod( + "ApplySettingsFileDefaults", + BindingFlags.NonPublic | BindingFlags.Static); + + method!.Invoke(null, [settingsFilePath, refitSettings]); + + refitSettings.OutputFolder.Should().Be("./Generated"); + } } diff --git a/src/Refitter/GenerateCommand.cs b/src/Refitter/GenerateCommand.cs index 80ff55361..83aa55b07 100644 --- a/src/Refitter/GenerateCommand.cs +++ b/src/Refitter/GenerateCommand.cs @@ -37,9 +37,27 @@ protected override ValidationResult Validate(CommandContext context, Settings se protected override async Task ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken) { - var refitGeneratorSettings = CreateRefitGeneratorSettings(settings); + RefitGeneratorSettings refitGeneratorSettings; + try { + // When settings file is provided, deserialize it first and use as source of truth + if (!string.IsNullOrWhiteSpace(settings.SettingsFilePath)) + { + var json = await File.ReadAllTextAsync(settings.SettingsFilePath); + refitGeneratorSettings = Serializer.Deserialize(json); + + // Allow CLI to override OpenApiPath if explicitly provided + if (!string.IsNullOrWhiteSpace(settings.OpenApiPath)) + refitGeneratorSettings.OpenApiPath = settings.OpenApiPath; + + ApplySettingsFileDefaults(settings.SettingsFilePath, refitGeneratorSettings); + } + else + { + // No settings file - build from CLI arguments + refitGeneratorSettings = CreateRefitGeneratorSettings(settings); + } var stopwatch = Stopwatch.StartNew(); var version = GetType().Assembly.GetName().Version!.ToString(); if (version == "1.0.0.0") @@ -86,16 +104,6 @@ protected override async Task ExecuteAsync(CommandContext context, Settings return 0; } - if (!string.IsNullOrWhiteSpace(settings.SettingsFilePath)) - { - var json = await File.ReadAllTextAsync(settings.SettingsFilePath); - refitGeneratorSettings = Serializer.Deserialize(json); - refitGeneratorSettings.OpenApiPath = settings.OpenApiPath!; - - if (!string.IsNullOrWhiteSpace(refitGeneratorSettings.ContractsOutputFolder)) - refitGeneratorSettings.GenerateMultipleFiles = true; - } - var generator = await RefitGenerator.CreateAsync(refitGeneratorSettings); if (!settings.SkipValidation) await ValidateOpenApiSpec(refitGeneratorSettings.OpenApiPath, settings); @@ -639,39 +647,99 @@ private static void SimpleDonationBanner() private static string GetOutputPath(Settings settings, RefitGeneratorSettings refitGeneratorSettings) { + if (UsesDirectCliOutput(settings)) + { + return settings.OutputPath!; + } + + if (UsesDirectCliDefaults(settings)) + { + return Settings.DefaultOutputPath; + } + + // Determine root directory based on settings file location + var root = string.IsNullOrWhiteSpace(settings.SettingsFilePath) + ? string.Empty + : Path.GetDirectoryName(settings.SettingsFilePath) ?? string.Empty; + var outputPath = settings.OutputPath != Settings.DefaultOutputPath && !string.IsNullOrWhiteSpace(settings.OutputPath) ? settings.OutputPath : refitGeneratorSettings.OutputFilename ?? "Output.cs"; - if (!string.IsNullOrWhiteSpace(refitGeneratorSettings.OutputFolder) && - refitGeneratorSettings.OutputFolder != RefitGeneratorSettings.DefaultOutputFolder) + if (!string.IsNullOrWhiteSpace(refitGeneratorSettings.OutputFolder)) { outputPath = Path.Combine(refitGeneratorSettings.OutputFolder, outputPath); } + // Root the output path relative to settings file location if applicable + if (!string.IsNullOrWhiteSpace(root)) + { + outputPath = Path.Combine(root, outputPath); + } + return outputPath; } + private static void ApplySettingsFileDefaults(string settingsFilePath, RefitGeneratorSettings refitGeneratorSettings) + { + // Re-apply multi-file trigger logic + if (!string.IsNullOrWhiteSpace(refitGeneratorSettings.ContractsOutputFolder)) + refitGeneratorSettings.GenerateMultipleFiles = true; + + // Default outputFolder to ./Generated if not specified (property initializer not invoked by JSON deserialization) + if (string.IsNullOrWhiteSpace(refitGeneratorSettings.OutputFolder)) + { + refitGeneratorSettings.OutputFolder = RefitGeneratorSettings.DefaultOutputFolder; + } + + // Default outputFilename to .refitter filename if not specified + if (string.IsNullOrWhiteSpace(refitGeneratorSettings.OutputFilename)) + { + var refitterFileName = Path.GetFileNameWithoutExtension(settingsFilePath); + refitGeneratorSettings.OutputFilename = $"{refitterFileName}.cs"; + } + } + private string GetOutputPath( Settings settings, RefitGeneratorSettings refitGeneratorSettings, GeneratedCode outputFile) { + if (IsDirectCliGeneration(settings)) + { + var outputDirectory = UsesDirectCliOutput(settings) + ? settings.OutputPath! + : "."; + + return Path.Combine(outputDirectory, outputFile.Filename); + } + var root = string.IsNullOrWhiteSpace(settings.SettingsFilePath) ? string.Empty : Path.GetDirectoryName(settings.SettingsFilePath) ?? string.Empty; - if (!string.IsNullOrWhiteSpace(refitGeneratorSettings.OutputFolder) && - refitGeneratorSettings.OutputFolder != RefitGeneratorSettings.DefaultOutputFolder) + if (!string.IsNullOrWhiteSpace(refitGeneratorSettings.OutputFolder)) { return Path.Combine(root, refitGeneratorSettings.OutputFolder, outputFile.Filename); } return !string.IsNullOrWhiteSpace(settings.OutputPath) && settings.OutputPath != Settings.DefaultOutputPath ? Path.Combine(root, settings.OutputPath, outputFile.Filename) - : outputFile.Filename; + : Path.Combine(root, outputFile.Filename); } + + private static bool IsDirectCliGeneration(Settings settings) => + string.IsNullOrWhiteSpace(settings.SettingsFilePath); + + private static bool UsesDirectCliOutput(Settings settings) => + IsDirectCliGeneration(settings) && + !string.IsNullOrWhiteSpace(settings.OutputPath) && + settings.OutputPath != Settings.DefaultOutputPath; + + private static bool UsesDirectCliDefaults(Settings settings) => + IsDirectCliGeneration(settings) && + (string.IsNullOrWhiteSpace(settings.OutputPath) || settings.OutputPath == Settings.DefaultOutputPath); private static async Task ValidateOpenApiSpec(string openApiPath, Settings settings) { OpenApiValidationResult validationResult; diff --git a/test/MSBuild/build.ps1 b/test/MSBuild/build.ps1 index 0addce726..9ddb792e8 100644 --- a/test/MSBuild/build.ps1 +++ b/test/MSBuild/build.ps1 @@ -1,9 +1,13 @@ -Remove-Item bin -Force -Recurse -Remove-Item obj -Force -Recurse +Remove-Item bin -Force -Recurse -ErrorAction SilentlyContinue +Remove-Item obj -Force -Recurse -ErrorAction SilentlyContinue +Remove-Item GeneratedOutput -Force -Recurse -ErrorAction SilentlyContinue +Remove-Item Generated -Force -Recurse -ErrorAction SilentlyContinue dotnet build-server shutdown -dotnet nuget locals global-packages --clear -Remove-Item Refitter.MSBuild.*.nupkg -Force -Remove-Item Petstore.cs +Remove-Item Refitter.MSBuild.*.nupkg -Force -ErrorAction SilentlyContinue +Remove-Item Petstore.cs -ErrorAction SilentlyContinue +Remove-Item PetstorePreserveOriginal.cs -ErrorAction SilentlyContinue +Remove-Item Output.cs -ErrorAction SilentlyContinue # Issue #998: Should not exist + dotnet restore ../../src/Refitter.slnx dotnet clean -c release ../../src/Refitter.slnx dotnet build -c release ../../src/Refitter/Refitter.csproj @@ -13,5 +17,71 @@ dotnet add package .\Refitter.MSBuild.1.0.0.nupkg --source . dotnet restore dotnet add package Refitter.MSBuild --source . dotnet run -v d -filelogger -c Release + +# Issue #998 regression assertions +Write-Host "" +Write-Host "=== Issue #998 Regression Checks ===" -ForegroundColor Cyan + +# Check that expected files exist under Generated (default output folder) +if (!(Test-Path "Generated\Petstore.cs")) { + Write-Host "ERROR: Expected Generated\Petstore.cs not found (from petstore.refitter with output filename)" -ForegroundColor Red + exit 1 +} +Write-Host "PASS: Generated\Petstore.cs exists (respects output filename + default outputFolder)" -ForegroundColor Green + +if (!(Test-Path "Generated\PetstorePreserveOriginal.cs")) { + Write-Host "ERROR: Expected Generated\PetstorePreserveOriginal.cs not found (from petstore-preserve-original.refitter)" -ForegroundColor Red + exit 1 +} +Write-Host "PASS: Generated\PetstorePreserveOriginal.cs exists (respects output filename + default outputFolder)" -ForegroundColor Green + +# Ensure files are not placed in root +if (Test-Path "Petstore.cs") { + Write-Host "ERROR: Unexpected Petstore.cs found in root (outputFolder default ignored)" -ForegroundColor Red + exit 1 +} +if (Test-Path "PetstorePreserveOriginal.cs") { + Write-Host "ERROR: Unexpected PetstorePreserveOriginal.cs found in root (outputFolder default ignored)" -ForegroundColor Red + exit 1 +} +Write-Host "PASS: No stray Petstore*.cs in root directory" -ForegroundColor Green + +# Check that stray Output.cs does NOT exist (the bug symptom) +if (Test-Path "Output.cs") { + Write-Host "ERROR: Unexpected Output.cs found in root (Issue #998 symptom)" -ForegroundColor Red + Write-Host " This indicates outputFolder or outputFilename settings were ignored" -ForegroundColor Red + exit 1 +} +Write-Host "PASS: No stray Output.cs in root directory" -ForegroundColor Green + +# Check interface naming (smoke test for naming.interfaceName) +$petstoreContent = Get-Content "Generated\Petstore.cs" -Raw +if ($petstoreContent -notmatch "interface ISwaggerPetstore") { + Write-Host "ERROR: Expected interface ISwaggerPetstore not found in Petstore.cs" -ForegroundColor Red + Write-Host " naming.interfaceName setting may have been ignored" -ForegroundColor Red + exit 1 +} else { + Write-Host "PASS: Interface ISwaggerPetstore found (respects naming.interfaceName)" -ForegroundColor Green +} + +# Check outputFolder when explicitly specified +if (!(Test-Path "GeneratedOutput\PetstoreWithFolder.cs")) { + Write-Host "ERROR: Expected GeneratedOutput\PetstoreWithFolder.cs not found (from petstore-with-outputfolder.refitter)" -ForegroundColor Red + exit 1 +} +Write-Host "PASS: GeneratedOutput\PetstoreWithFolder.cs exists (respects explicit outputFolder setting)" -ForegroundColor Green + +$petstoreWithFolderContent = Get-Content "GeneratedOutput\PetstoreWithFolder.cs" -Raw +if ($petstoreWithFolderContent -notmatch "interface ISwaggerPetstoreWithFolder") { + Write-Host "ERROR: Expected interface ISwaggerPetstoreWithFolder not found in GeneratedOutput\PetstoreWithFolder.cs" -ForegroundColor Red + Write-Host " naming.interfaceName setting may have been ignored" -ForegroundColor Red + exit 1 +} else { + Write-Host "PASS: Interface ISwaggerPetstoreWithFolder found (respects naming.interfaceName with outputFolder)" -ForegroundColor Green +} + +Write-Host "=== All Issue #998 Checks Complete ===" -ForegroundColor Cyan +Write-Host "" + dotnet remove package Refitter.MSBuild -Remove-Item Refitter.MSBuild.*.nupkg -Force +Remove-Item Refitter.MSBuild.*.nupkg -Force \ No newline at end of file diff --git a/test/MSBuild/petstore-with-outputfolder.refitter b/test/MSBuild/petstore-with-outputfolder.refitter new file mode 100644 index 000000000..4eaa545da --- /dev/null +++ b/test/MSBuild/petstore-with-outputfolder.refitter @@ -0,0 +1,10 @@ +{ + "openApiPath": "https://petstore3.swagger.io/api/v3/openapi.json", + "outputFolder": "./GeneratedOutput", + "outputFilename": "PetstoreWithFolder.cs", + "namespace": "Petstore.Client", + "naming": { + "useOpenApiTitle": false, + "interfaceName": "SwaggerPetstoreWithFolder" + } +}