diff --git a/test/ConsoleApp/Directory.Build.props b/test/ConsoleApp/Directory.Build.props
index 06de0c508..cd0f563cc 100644
--- a/test/ConsoleApp/Directory.Build.props
+++ b/test/ConsoleApp/Directory.Build.props
@@ -5,6 +5,12 @@
true
true
+
+
+ true
+ false
+
Program.cs
diff --git a/test/multiple-sources.refitter b/test/multiple-sources.refitter
index 82e85c206..d013fc23d 100644
--- a/test/multiple-sources.refitter
+++ b/test/multiple-sources.refitter
@@ -4,5 +4,7 @@
"OpenAPI/v3.0/petstore.json",
"OpenAPI/v3.0/api-with-examples.json"
],
+ "namespace": "MultipleSources",
+ "outputFolder": "GeneratedCode",
"multipleInterfaces": "bytag"
}
diff --git a/test/smoke-tests.bat b/test/smoke-tests.bat
index c65009102..6b8e26375 100644
--- a/test/smoke-tests.bat
+++ b/test/smoke-tests.bat
@@ -2,14 +2,20 @@
setlocal enabledelayedexpansion
:: Parse command line arguments
-set "PARALLEL=true"
+set "THROTTLE_LIMIT=4"
set "USE_PRODUCTION=false"
set "USE_DOCKER=false"
:parse_args
if "%~1"=="" goto :main
+if /i "%~1"=="-throttlelimit" (
+ set "THROTTLE_LIMIT=%~2"
+ shift
+ shift
+ goto :parse_args
+)
if /i "%~1"=="-parallel" (
- set "PARALLEL=%~2"
+ :: Kept for backward compatibility
shift
shift
goto :parse_args
@@ -28,11 +34,11 @@ shift
goto :parse_args
:main
-echo Parallel: %PARALLEL%
+echo ThrottleLimit: %THROTTLE_LIMIT%
echo UseProduction: %USE_PRODUCTION%
echo UseDocker: %USE_DOCKER%
-goto :run_tests
+goto :start_tests
:throw_on_native_failure
if not %errorlevel%==0 (
@@ -41,87 +47,81 @@ if not %errorlevel%==0 (
)
goto :eof
-:generate_and_build
-set "format=%~1"
-set "namespace=%~2"
-set "output_path=%~3"
-set "args=%~4"
-set "net_core=%~5"
-set "csproj=%~6"
-set "build_from_source=%~7"
-set "use_docker=%~8"
-
-if "%build_from_source%"=="" set "build_from_source=true"
-if "%use_docker%"=="" set "use_docker=false"
-
-:: Clean up generated files
-del /q /s ".\GeneratedCode\*.cs" 2>nul
-
-set "process_path=.\bin\refitter"
-if /i "%build_from_source%"=="false" set "process_path=refitter"
-if /i "%use_docker%"=="true" set "process_path=docker"
+:generate
+:: %1=specPath %2=namespace %3=outputPath %4=extraArgs %5=processPath %6=useDocker
+set "spec_path=%~1"
+set "gen_namespace=%~2"
+set "gen_output=%~3"
+set "gen_args=%~4"
+set "gen_process=%~5"
+set "gen_docker=%~6"
-:: Check if using docker
-if /i "%use_docker%"=="true" (
+if /i "%gen_docker%"=="true" (
set "current_dir=%CD%"
set "current_dir=!current_dir:\=/!"
-
- :: Check if using settings file
- echo %args% | findstr "settings-file" >nul
- if %errorlevel%==0 (
- echo docker run --rm -v "!current_dir!:/src" -w /src christianhelle/refitter --no-logging %args%
- docker run --rm -v "!current_dir!:/src" -w /src christianhelle/refitter --no-logging %args%
- call :throw_on_native_failure
- ) else (
- echo docker run --rm -v "!current_dir!:/src" -w /src christianhelle/refitter .\openapi.%format% --namespace %namespace% --output .\GeneratedCode\%output_path% --no-logging %args%
- docker run --rm -v "!current_dir!:/src" -w /src christianhelle/refitter .\openapi.%format% --namespace %namespace% --output .\GeneratedCode\%output_path% --no-logging %args%
- call :throw_on_native_failure
- )
- goto :build_project
-)
-
-:: Check if using settings file
-echo %args% | findstr "settings-file" >nul
-if %errorlevel%==0 (
- echo %process_path% --no-logging %args%
- %process_path% --no-logging %args%
+ echo docker run --rm -v "!current_dir!:/src" -w /src christianhelle/refitter !spec_path! --namespace !gen_namespace! --output !gen_output! --no-logging !gen_args!
+ docker run --rm -v "!current_dir!:/src" -w /src christianhelle/refitter !spec_path! --namespace !gen_namespace! --output !gen_output! --no-logging !gen_args!
call :throw_on_native_failure
) else (
- echo %process_path% .\openapi.%format% --namespace %namespace% --output .\GeneratedCode\%output_path% --no-logging %args%
- %process_path% .\openapi.%format% --namespace %namespace% --output .\GeneratedCode\%output_path% --no-logging %args%
+ echo !gen_process! !spec_path! --namespace !gen_namespace! --output !gen_output! --no-logging !gen_args!
+ !gen_process! !spec_path! --namespace !gen_namespace! --output !gen_output! --no-logging !gen_args!
call :throw_on_native_failure
)
+goto :eof
-:build_project
+:generate_from_settings
+:: %1=settingsFile %2=processPath %3=useDocker
+set "settings_file=%~1"
+set "gfs_process=%~2"
+set "gfs_docker=%~3"
-:: Build the project
-if not "%csproj%"=="" (
- echo.
- echo Building %csproj% file
- echo.
- set "solution=%csproj%"
+if /i "%gfs_docker%"=="true" (
+ set "current_dir=%CD%"
+ set "current_dir=!current_dir:\=/!"
+ echo docker run --rm -v "!current_dir!:/src" -w /src christianhelle/refitter --no-logging --settings-file !settings_file!
+ docker run --rm -v "!current_dir!:/src" -w /src christianhelle/refitter --no-logging --settings-file !settings_file!
+ call :throw_on_native_failure
) else (
- echo.
- echo Building ConsoleApp
- echo.
- set "solution=.\ConsoleApp\ConsoleApp.slnx"
- if /i "%net_core%"=="true" set "solution=.\ConsoleApp\ConsoleApp.Core.slnx"
+ echo !gfs_process! --no-logging --settings-file !settings_file!
+ !gfs_process! --no-logging --settings-file !settings_file!
+ call :throw_on_native_failure
)
+goto :eof
+
+:build_solution
+:: %1=solution %2=noRestore (true/false) %3=smokeTest (true/false)
+set "build_sln=%~1"
+set "build_norestore=%~2"
+set "build_smoketest=%~3"
+
+set "build_extra="
+if /i "%build_norestore%"=="true" set "build_extra=!build_extra! --no-restore"
+if /i "%build_smoketest%"=="true" set "build_extra=!build_extra! --property:SmokeTest=true"
-dotnet build %solution%
+echo.
+echo Building %build_sln%
+echo.
+dotnet build %build_sln% --nologo -v q --property WarningLevel=0 /clp:ErrorsOnly!build_extra!
call :throw_on_native_failure
goto :eof
+:clean_generated_code
+del /q /s ".\GeneratedCode\*.cs" 2>nul
+for /d %%d in (".\GeneratedCode\*") do rd /s /q "%%d" 2>nul
+goto :eof
+
:run_tests
-set "method=%~1"
-set "parallel_param=%~2"
-set "build_from_source_param=%~3"
-set "use_docker_param=%~4"
+set "build_from_source=%~1"
+set "use_docker_param=%~2"
-if "%build_from_source_param%"=="" set "build_from_source_param=true"
+if "%build_from_source%"=="" set "build_from_source=true"
if "%use_docker_param%"=="" set "use_docker_param=false"
-:: Array of filenames (simulated using variables)
+set "process_path=.\bin\refitter"
+if /i "%build_from_source%"=="false" set "process_path=refitter"
+if /i "%use_docker_param%"=="true" set "process_path=docker"
+
+:: Array of filenames
set "filename0=weather"
set "filename1=bot.paths"
set "filename2=petstore"
@@ -138,10 +138,17 @@ set "filename12=hubspot-events"
set "filename13=hubspot-webhooks"
set "filename_count=14"
-if /i "%build_from_source_param%"=="true" (
+:: V3.1 filenames
+set "v31filename0=webhook-example"
+set "v31filename_count=1"
+
+:: ==========================================
+:: Phase 0: Build refitter from source
+:: ==========================================
+if /i "%build_from_source%"=="true" (
if /i "%use_docker_param%"=="false" (
- echo dotnet publish ..\src\Refitter\Refitter.csproj -p:PublishReadyToRun=true -o bin -f net8.0
- dotnet publish ..\src\Refitter\Refitter.csproj -p:PublishReadyToRun=true -o bin -f net8.0
+ echo dotnet publish ..\src\Refitter\Refitter.csproj -c Release -o bin -f net9.0
+ dotnet publish ..\src\Refitter\Refitter.csproj -c Release -o bin -f net9.0
call :throw_on_native_failure
echo refitter --version
@@ -150,62 +157,234 @@ if /i "%build_from_source_param%"=="true" (
)
)
-:: Generate with settings files
-call :generate_and_build " " " " "SwaggerPetstoreDirect.generated.cs" "--settings-file .\petstore.refitter" "" "" "%build_from_source_param%" "%use_docker_param%"
-call :generate_and_build " " " " "" "--settings-file .\Apizr\petstore.apizr.refitter" "" ".\Apizr\Sample.csproj" "%build_from_source_param%" "%use_docker_param%"
-call :generate_and_build " " " " "" "--settings-file .\MultipleFiles\petstore.refitter" "" "MultipleFiles\Client\Client.csproj" "%build_from_source_param%" "%use_docker_param%"
+:: ==========================================
+:: Phase 1: Pre-restore packages
+:: ==========================================
+echo.
+echo === Pre-restoring packages ===
+echo.
+dotnet restore .\ConsoleApp\ConsoleApp.slnx --nologo -v q
+dotnet restore .\ConsoleApp\ConsoleApp.Core.slnx --nologo -v q
+dotnet restore .\Apizr\Sample.csproj --nologo -v q
+
+:: ==========================================
+:: Phase 2: Settings-file tests
+:: ==========================================
+echo.
+echo === Settings-file tests ===
+echo.
+
+call :clean_generated_code
+call :generate_from_settings ".\petstore.refitter" "%process_path%" "%use_docker_param%"
+call :build_solution ".\ConsoleApp\ConsoleApp.slnx" "true"
+
+call :clean_generated_code
+call :generate_from_settings ".\Apizr\petstore.apizr.refitter" "%process_path%" "%use_docker_param%"
+call :build_solution ".\Apizr\Sample.csproj" "true"
+
+call :generate_from_settings ".\MultipleFiles\petstore.refitter" "%process_path%" "%use_docker_param%"
+call :build_solution "MultipleFiles\Client\Client.csproj" "false"
+
+call :clean_generated_code
+call :generate_from_settings ".\multiple-sources.refitter" "%process_path%" "%use_docker_param%"
+call :build_solution ".\ConsoleApp\ConsoleApp.Core.slnx" "true"
+
+:: ==========================================
+:: Phase 3: Generate all STANDARD variants
+:: ==========================================
+echo.
+echo === Generating standard variants ===
+echo.
+call :clean_generated_code
+
+:: Standard variant definitions (suffix, prefix, args)
+:: We process versions/formats/filenames and generate ALL variants, then build once
+
+for %%v in (v3.0 v2.0) do (
+ set "version_tag=%%v"
+ set "version_tag=!version_tag:.=!"
+ for %%f in (json yaml) do (
+ for /l %%i in (0,1,13) do (
+ set "cur_filename=!filename%%i!"
+ set "file_path=.\OpenAPI\%%v\!cur_filename!.%%f"
+
+ if exist "!file_path!" (
+ :: Create output base and namespace
+ set "output_base=!cur_filename!"
+ set "output_base=!output_base:-=!"
+ set "output_base=!output_base:.=!"
+ call :capitalize output_base
+ set "ns=!output_base!_!version_tag!_%%f"
+ set "file_tag=!version_tag!_%%f_!output_base!"
+
+ :: Standard variants
+ call :generate "!file_path!" "!ns!.Cancellation" ".\GeneratedCode\WithCancellation!file_tag!.generated.cs" "--cancellation-tokens" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.Internal" ".\GeneratedCode\Internal!file_tag!.generated.cs" "--internal" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.UsingApiResponse" ".\GeneratedCode\IApi!file_tag!.generated.cs" "--use-api-response" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.UsingIObservable" ".\GeneratedCode\IObservable!file_tag!.generated.cs" "--use-observable-response" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.UsingIsoDateFormat" ".\GeneratedCode\UsingIsoDateFormat!file_tag!.generated.cs" "--use-iso-date-format" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.MultipleInterfaces" ".\GeneratedCode\MultipleInterfaces!file_tag!.generated.cs" "--multiple-interfaces ByEndpoint" "%process_path%" "%use_docker_param%"
+ :: NOTE: --multiple-interfaces ByEndpoint --operation-name-template generates duplicate types (known limitation)
+ :: Tested as generate-only after the batch build
+ call :generate "!file_path!" "!ns!.ContractOnly" ".\GeneratedCode\ContractOnly!file_tag!.generated.cs" "--contract-only" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.DynamicQuerystring" ".\GeneratedCode\DynamicQuerystring!file_tag!.generated.cs" "--use-dynamic-querystring-parameters" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.IntegerTypeInt64" ".\GeneratedCode\IntegerTypeInt64!file_tag!.generated.cs" "--integer-type Int64" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.TrimUnusedSchema" ".\GeneratedCode\TrimUnusedSchema!file_tag!.generated.cs" "--trim-unused-schema" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.OptionalNullable" ".\GeneratedCode\OptionalNullable!file_tag!.generated.cs" "--optional-nullable-parameters" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.NoDeprecated" ".\GeneratedCode\NoDeprecated!file_tag!.generated.cs" "--no-deprecated-operations" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.NoAutoGeneratedHeader" ".\GeneratedCode\NoAutoGenHeader!file_tag!.generated.cs" "--no-auto-generated-header" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.NoAcceptHeaders" ".\GeneratedCode\NoAcceptHeaders!file_tag!.generated.cs" "--no-accept-headers" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.SkipDefaultAdditionalProps" ".\GeneratedCode\SkipDefaultAddlProps!file_tag!.generated.cs" "--skip-default-additional-properties" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.NoInlineJsonConverters" ".\GeneratedCode\NoInlineJsonConv!file_tag!.generated.cs" "--no-inline-json-converters" "%process_path%" "%use_docker_param%"
+ :: Petstore-only variants (tag/path filters require petstore-specific tags)
+ echo !cur_filename! | findstr /b "petstore" >nul && (
+ call :generate "!file_path!" "!ns!.TagFiltered" ".\GeneratedCode\TagFiltered!file_tag!.generated.cs" "--tag pet --tag user --tag store" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.MatchPathFiltered" ".\GeneratedCode\MatchPathFiltered!file_tag!.generated.cs" "--match-path ^^/pet/.*" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.MultipleInterfacesByTag" ".\GeneratedCode\MultipleInterfacesByTag!file_tag!.generated.cs" "--multiple-interfaces ByTag" "%process_path%" "%use_docker_param%"
+ )
+ :: Multiple files variant (unique subdirectory)
+ call :generate "!file_path!" "!ns!.MultipleFiles" ".\GeneratedCode\MultipleFiles\!file_tag!\" "--multiple-files" "%process_path%" "%use_docker_param%"
+ :: Separate contracts variant (unique subdirectories)
+ call :generate "!file_path!" "!ns!.SeparateContractsFile" ".\GeneratedCode\SeparateContracts\!file_tag!\" "--contracts-output GeneratedCode\Contracts\!file_tag! --contracts-namespace !ns!.SeparateContractsFile.Contracts" "%process_path%" "%use_docker_param%"
+ )
+ )
+ )
+)
+
+:: V3.1 specs (skip MultipleInterfaces variants - webhook specs may lack regular API paths)
+for %%f in (json yaml) do (
+ for /l %%i in (0,1,0) do (
+ set "cur_filename=!v31filename%%i!"
+ set "file_path=.\OpenAPI\v3.1\!cur_filename!.%%f"
+
+ if exist "!file_path!" (
+ set "output_base=!cur_filename!"
+ set "output_base=!output_base:-=!"
+ set "output_base=!output_base:.=!"
+ call :capitalize output_base
+ set "ns=!output_base!_v31_%%f"
+ set "file_tag=v31_%%f_!output_base!"
+
+ call :generate "!file_path!" "!ns!.Cancellation" ".\GeneratedCode\WithCancellation!file_tag!.generated.cs" "--cancellation-tokens" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.Internal" ".\GeneratedCode\Internal!file_tag!.generated.cs" "--internal" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.UsingApiResponse" ".\GeneratedCode\IApi!file_tag!.generated.cs" "--use-api-response" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.UsingIObservable" ".\GeneratedCode\IObservable!file_tag!.generated.cs" "--use-observable-response" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.UsingIsoDateFormat" ".\GeneratedCode\UsingIsoDateFormat!file_tag!.generated.cs" "--use-iso-date-format" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.ContractOnly" ".\GeneratedCode\ContractOnly!file_tag!.generated.cs" "--contract-only" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.DynamicQuerystring" ".\GeneratedCode\DynamicQuerystring!file_tag!.generated.cs" "--use-dynamic-querystring-parameters" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.IntegerTypeInt64" ".\GeneratedCode\IntegerTypeInt64!file_tag!.generated.cs" "--integer-type Int64" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.TrimUnusedSchema" ".\GeneratedCode\TrimUnusedSchema!file_tag!.generated.cs" "--trim-unused-schema" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.OptionalNullable" ".\GeneratedCode\OptionalNullable!file_tag!.generated.cs" "--optional-nullable-parameters" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.NoDeprecated" ".\GeneratedCode\NoDeprecated!file_tag!.generated.cs" "--no-deprecated-operations" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.NoAutoGeneratedHeader" ".\GeneratedCode\NoAutoGenHeader!file_tag!.generated.cs" "--no-auto-generated-header" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.NoAcceptHeaders" ".\GeneratedCode\NoAcceptHeaders!file_tag!.generated.cs" "--no-accept-headers" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.SkipDefaultAdditionalProps" ".\GeneratedCode\SkipDefaultAddlProps!file_tag!.generated.cs" "--skip-default-additional-properties" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.NoInlineJsonConverters" ".\GeneratedCode\NoInlineJsonConv!file_tag!.generated.cs" "--no-inline-json-converters" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.MultipleFiles" ".\GeneratedCode\MultipleFiles\!file_tag!\" "--multiple-files" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.SeparateContractsFile" ".\GeneratedCode\SeparateContracts\!file_tag!\" "--contracts-output GeneratedCode\Contracts\!file_tag! --contracts-namespace !ns!.SeparateContractsFile.Contracts" "%process_path%" "%use_docker_param%"
+ )
+ )
+)
+
+:: ==========================================
+:: Phase 4: Build standard variants (one build validates all)
+:: ==========================================
+echo.
+echo === Building standard variants ===
+echo.
+call :build_solution ".\ConsoleApp\ConsoleApp.slnx" "true" "true"
+
+:: ==========================================
+:: Phase 4b: Generate-only test for MultipleInterfacesWithCustomName
+:: This variant uses --multiple-interfaces ByEndpoint --operation-name-template which
+:: generates duplicate types per-endpoint (known limitation). We verify generation succeeds.
+:: ==========================================
+echo.
+echo === Generate-only: MultipleInterfacesWithCustomName (petstore) ===
+echo.
+call :generate ".\OpenAPI\v3.0\petstore.json" "GenerateOnly.MultipleInterfacesWithCustomName" ".\GeneratedCode\MultipleInterfacesWithCustomName_generateonly.cs" "--multiple-interfaces ByEndpoint --operation-name-template ExecuteAsync" "%process_path%" "%use_docker_param%"
+if not exist ".\GeneratedCode\MultipleInterfacesWithCustomName_generateonly.cs" (
+ echo Generate-only test failed: MultipleInterfacesWithCustomName
+ exit /b 1
+)
+del /q ".\GeneratedCode\MultipleInterfacesWithCustomName_generateonly.cs"
+echo Generate-only test passed: MultipleInterfacesWithCustomName
+
+:: ==========================================
+:: Phase 5: Generate netCore variants (accumulate on top of standard code)
+:: ==========================================
+echo.
+echo === Generating netCore variants ===
+echo.
-:: Process versions and formats
for %%v in (v3.0 v2.0) do (
+ set "version_tag=%%v"
+ set "version_tag=!version_tag:.=!"
for %%f in (json yaml) do (
for /l %%i in (0,1,13) do (
- set "filename=!filename%%i!"
- set "file_path=.\OpenAPI\%%v\!filename!.%%f"
+ set "cur_filename=!filename%%i!"
+ set "file_path=.\OpenAPI\%%v\!cur_filename!.%%f"
if exist "!file_path!" (
- copy "!file_path!" ".\openapi.%%f" >nul
-
- :: Create output path and namespace
- set "output_path=!filename!.generated.cs"
- set "output_path=!output_path:~0,1!!output_path:~1!"
- call :capitalize output_path
-
- set "namespace=!filename!"
- set "namespace=!namespace:-=!"
- call :capitalize namespace
-
- :: Generate different variants
- call :generate_and_build "%%f" "!namespace!.Disposable" "Disposable!output_path!" "--disposable" "true" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.MultipleFiles" "" "--multiple-files" "" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.SeparateContractsFile" "" "--contracts-output GeneratedCode/Contracts --contracts-namespace !namespace!.SeparateContractsFile.Contracts" "" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.Cancellation" "WithCancellation!output_path!" "--cancellation-tokens" "" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.Internal" "Internal!output_path!" "--internal" "" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.UsingApiResponse" "IApi!output_path!" "--use-api-response" "" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.UsingIObservable" "IObservable!output_path!" "--use-observable-response" "" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.UsingIsoDateFormat" "UsingIsoDateFormat!output_path!" "--use-iso-date-format" "" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.MultipleInterfaces" "MultipleInterfaces!output_path!" "--multiple-interfaces ByEndpoint" "" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.MultipleInterfaces" "MultipleInterfacesWithCustomName!output_path!" "--multiple-interfaces ByEndpoint --operation-name-template ExecuteAsync" "" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.TagFiltered" "TagFiltered!output_path!" "--tag pet --tag user --tag store" "" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.MatchPathFiltered" "MatchPathFiltered!output_path!" "--match-path ^^/pet/.*" "" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.ContractOnly" "ContractOnly!output_path!" "--contract-only" "" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.ImmutableRecords" "ImmutableRecords!output_path!" "--immutable-records" "true" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.PolymorphicSerialization" "PolymorphicSerialization!output_path!" "--use-polymorphic-serialization" "true" "" "%build_from_source_param%" "%use_docker_param%"
- call :generate_and_build "%%f" "!namespace!.CollectionFormatCsv" "CollectionFormatCsv!output_path!" "--collection-format csv" "true" "" "%build_from_source_param%" "%use_docker_param%"
+ set "output_base=!cur_filename!"
+ set "output_base=!output_base:-=!"
+ set "output_base=!output_base:.=!"
+ call :capitalize output_base
+ set "ns=!output_base!_!version_tag!_%%f"
+ set "file_tag=!version_tag!_%%f_!output_base!"
+
+ call :generate "!file_path!" "!ns!.Disposable" ".\GeneratedCode\Disposable!file_tag!.generated.cs" "--disposable" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.ImmutableRecords" ".\GeneratedCode\ImmutableRecords!file_tag!.generated.cs" "--immutable-records" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.PolymorphicSerialization" ".\GeneratedCode\PolymorphicSerialization!file_tag!.generated.cs" "--use-polymorphic-serialization" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.CollectionFormatCsv" ".\GeneratedCode\CollectionFormatCsv!file_tag!.generated.cs" "--collection-format csv" "%process_path%" "%use_docker_param%"
)
)
)
)
-:: Process URLs
+:: V3.1 netCore variants
+for %%f in (json yaml) do (
+ for /l %%i in (0,1,0) do (
+ set "cur_filename=!v31filename%%i!"
+ set "file_path=.\OpenAPI\v3.1\!cur_filename!.%%f"
+
+ if exist "!file_path!" (
+ set "output_base=!cur_filename!"
+ set "output_base=!output_base:-=!"
+ set "output_base=!output_base:.=!"
+ call :capitalize output_base
+ set "ns=!output_base!_v31_%%f"
+ set "file_tag=v31_%%f_!output_base!"
+
+ call :generate "!file_path!" "!ns!.Disposable" ".\GeneratedCode\Disposable!file_tag!.generated.cs" "--disposable" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.ImmutableRecords" ".\GeneratedCode\ImmutableRecords!file_tag!.generated.cs" "--immutable-records" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.PolymorphicSerialization" ".\GeneratedCode\PolymorphicSerialization!file_tag!.generated.cs" "--use-polymorphic-serialization" "%process_path%" "%use_docker_param%"
+ call :generate "!file_path!" "!ns!.CollectionFormatCsv" ".\GeneratedCode\CollectionFormatCsv!file_tag!.generated.cs" "--collection-format csv" "%process_path%" "%use_docker_param%"
+ )
+ )
+)
+
+:: ==========================================
+:: Phase 6: Build netCore variants
+:: ==========================================
+echo.
+echo === Building netCore variants ===
+echo.
+call :build_solution ".\ConsoleApp\ConsoleApp.Core.slnx" "true" "true"
+
+:: ==========================================
+:: Phase 7: URL-based tests (network-dependent)
+:: ==========================================
+echo.
+echo === URL-based tests ===
+echo.
+call :clean_generated_code
+
for %%u in ("https://petstore3.swagger.io/api/v3/openapi.json" "https://petstore3.swagger.io/api/v3/openapi.yaml") do (
set "namespace=PetstoreFromUri"
set "output_path=PetstoreFromUri.generated.cs"
- del /q "*.generated.cs" 2>nul
-
- set "process_path=.\bin\refitter"
- if /i "%build_from_source_param%"=="false" set "process_path=refitter"
- if /i "%use_docker_param%"=="true" set "process_path=docker"
+ del /q ".\GeneratedCode\*.cs" 2>nul
if /i "%use_docker_param%"=="true" (
set "current_dir=%CD%"
@@ -219,11 +398,7 @@ for %%u in ("https://petstore3.swagger.io/api/v3/openapi.json" "https://petstore
call :throw_on_native_failure
)
- echo.
- echo Building ConsoleApp
- echo.
- dotnet build .\ConsoleApp\ConsoleApp.slnx
- call :throw_on_native_failure
+ call :build_solution ".\ConsoleApp\ConsoleApp.slnx" "true"
)
goto :eof
@@ -240,6 +415,8 @@ for %%a in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) do (
set "%1=!first_char!!rest_chars!"
goto :eof
+:start_tests
+
:: Main execution
if /i "%USE_PRODUCTION%"=="true" (
echo Running smoke tests in production mode
@@ -261,11 +438,11 @@ echo Starting smoke tests...
set "start_time=%time%"
if /i "%USE_DOCKER%"=="true" (
- call :run_tests "dotnet-run" "%PARALLEL%" "false" "true"
+ call :run_tests "false" "true"
) else if /i "%USE_PRODUCTION%"=="true" (
- call :run_tests "dotnet-run" "%PARALLEL%" "false" "false"
+ call :run_tests "false" "false"
) else (
- call :run_tests "dotnet-run" "%PARALLEL%" "true" "false"
+ call :run_tests "true" "false"
)
set "end_time=%time%"
diff --git a/test/smoke-tests.ps1 b/test/smoke-tests.ps1
index aa434e121..e4d48c27d 100644
--- a/test/smoke-tests.ps1
+++ b/test/smoke-tests.ps1
@@ -1,15 +1,16 @@
param (
- [Parameter(Mandatory=$false)]
- [bool]
- $Parallel = $true,
-
[Parameter(Mandatory=$false)]
[switch]
$UseProduction = $false,
[Parameter(Mandatory=$false)]
[switch]
- $UseDocker = $false
+ $UseDocker = $false,
+
+ # Kept for backward compatibility
+ [Parameter(Mandatory=$false)]
+ [bool]
+ $Parallel = $true
)
function ThrowOnNativeFailure
@@ -20,171 +21,124 @@ function ThrowOnNativeFailure
}
}
-function GenerateAndBuild
+function GetProcessPath([bool]$buildFromSource, [bool]$useDocker)
{
- param (
- [Parameter(Mandatory=$true)]
- [string]
- $format,
-
- [Parameter(Mandatory=$true)]
- [string]
- $namespace,
-
- [Parameter(Mandatory=$false)]
- [string]
- $outputPath,
-
- [Parameter(Mandatory=$false)]
- [string]
- $args,
-
- [Parameter(Mandatory=$false)]
- [switch]
- $netCore = $false,
-
- [Parameter(Mandatory=$false)]
- [string]
- $csproj,
-
- [Parameter(Mandatory=$false)]
- [bool]
- $buildFromSource = $true,
-
- [Parameter(Mandatory=$false)]
- [bool]
- $useDocker = $false
- )
+ if ($useDocker) { return "docker" }
+ if (-not $buildFromSource) { return "refitter" }
+ return "./bin/refitter"
+}
- try
- {
- Get-ChildItem './GeneratedCode/*.cs' -Recurse | ForEach-Object { Remove-Item -Path $_.FullName -Force }
- } catch
- {
- # Do nothing
+function BuildDockerPrefix()
+{
+ $currentDir = (Get-Location).Path.Replace('\', '/')
+ $userParam = ""
+ if ($IsLinux -or $IsMacOS) {
+ $uid = sh -c 'id -u'
+ $gid = sh -c 'id -g'
+ $userParam = "--user ${uid}:${gid}"
}
+ $prefix = "run --rm -v ""${currentDir}:/src"" -w /src"
+ if ($userParam) { $prefix += " $userParam" }
+ $prefix += " christianhelle/refitter"
+ return $prefix
+}
- $processPath = "./bin/refitter"
- if ($buildFromSource -eq $false)
- {
- $processPath = "refitter"
- }
- if ($useDocker)
- {
- $processPath = "docker"
- }
+function StartRefitter
+{
+ param (
+ [string]$arguments,
+ [string]$processPath,
+ [bool]$useDocker = $false
+ )
if ($useDocker)
{
- $currentDir = (Get-Location).Path.Replace('\', '/')
- # Get current user ID and group ID for Linux/macOS
- $userParam = ""
- if ($IsLinux -or $IsMacOS) {
- $uid = sh -c 'id -u'
- $gid = sh -c 'id -g'
- $userParam = "--user ${uid}:${gid}"
- }
-
- if ($args.Contains("settings-file"))
- {
- if ($userParam) {
- Write-Host "docker run --rm -v ""${currentDir}:/src"" -w /src $userParam christianhelle/refitter --no-logging $args"
- $process = Start-Process $processPath `
- -Args "run --rm -v ""${currentDir}:/src"" -w /src $userParam christianhelle/refitter --no-logging $args" `
- -NoNewWindow `
- -PassThru
- } else {
- Write-Host "docker run --rm -v ""${currentDir}:/src"" -w /src christianhelle/refitter --no-logging $args"
- $process = Start-Process $processPath `
- -Args "run --rm -v ""${currentDir}:/src"" -w /src christianhelle/refitter --no-logging $args" `
- -NoNewWindow `
- -PassThru
- }
- } else
- {
- if ($userParam) {
- Write-Host "docker run --rm -v ""${currentDir}:/src"" -w /src $userParam christianhelle/refitter ./openapi.$format --namespace $namespace --output ./GeneratedCode/$outputPath --no-logging $args"
- $process = Start-Process $processPath `
- -Args "run --rm -v ""${currentDir}:/src"" -w /src $userParam christianhelle/refitter ./openapi.$format --namespace $namespace --output ./GeneratedCode/$outputPath --no-logging $args" `
- -NoNewWindow `
- -PassThru
- } else {
- Write-Host "docker run --rm -v ""${currentDir}:/src"" -w /src christianhelle/refitter ./openapi.$format --namespace $namespace --output ./GeneratedCode/$outputPath --no-logging $args"
- $process = Start-Process $processPath `
- -Args "run --rm -v ""${currentDir}:/src"" -w /src christianhelle/refitter ./openapi.$format --namespace $namespace --output ./GeneratedCode/$outputPath --no-logging $args" `
- -NoNewWindow `
- -PassThru
- }
- }
+ $dockerPrefix = BuildDockerPrefix
+ $fullArgs = "$dockerPrefix $arguments"
+ Write-Host "docker $fullArgs"
+ return Start-Process "docker" -Args $fullArgs -NoNewWindow -PassThru
}
- elseif ($args.Contains("settings-file"))
- {
- Write-Host "refitter --no-logging $args"
- $process = Start-Process $processPath `
- -Args "--no-logging $args" `
- -NoNewWindow `
- -PassThru
- } else
+ else
{
- Write-Host "refitter ./openapi.$format --namespace $namespace --output ./GeneratedCode/$outputPath --no-logging $args"
- $process = Start-Process $processPath `
- -Args "./openapi.$format --namespace $namespace --output ./GeneratedCode/$outputPath --no-logging $args" `
- -NoNewWindow `
- -PassThru
+ Write-Host "$processPath $arguments"
+ return Start-Process $processPath -Args $arguments -NoNewWindow -PassThru
}
+}
- $process | Wait-Process
- if ($process.ExitCode -ne 0)
- {
- throw "Refitter failed"
- }
+function GenerateFromSettingsFile
+{
+ param (
+ [string]$settingsFile,
+ [string]$processPath,
+ [bool]$useDocker = $false
+ )
- if ($csproj -ne '')
- {
- Write-Host "`r`nBuilding $csproj file`r`n"
- $solution = $csproj
- } else
- {
- Write-Host "`r`nBuilding ConsoleApp`r`n"
- $solution = "./ConsoleApp/ConsoleApp.slnx"
- if ($netCore)
- {
- $solution = "./ConsoleApp/ConsoleApp.Core.slnx"
+ $p = StartRefitter `
+ -arguments "--no-logging --settings-file $settingsFile" `
+ -processPath $processPath `
+ -useDocker $useDocker
+ $p | Wait-Process
+ if ($p.ExitCode -ne 0) { throw "Refitter failed for settings file: $settingsFile" }
+}
+
+function BuildSolution
+{
+ param (
+ [string]$solution,
+ [switch]$noRestore,
+ [switch]$smokeTest
+ )
+
+ $buildArgs = "build $solution --nologo -v q --property WarningLevel=0 /clp:ErrorsOnly"
+ if ($noRestore) { $buildArgs += " --no-restore" }
+ if ($smokeTest) { $buildArgs += " --property:SmokeTest=true" }
+
+ Write-Host "`r`nBuilding $solution`r`n"
+ $p = Start-Process "dotnet" -Args $buildArgs -NoNewWindow -PassThru
+ $p | Wait-Process
+ if ($p.ExitCode -ne 0) { throw "Build Failed: $solution" }
+}
+
+function CleanGeneratedCode
+{
+ try {
+ if (Test-Path './GeneratedCode') {
+ Get-ChildItem './GeneratedCode' -Recurse -Include '*.cs' -ErrorAction SilentlyContinue |
+ ForEach-Object { Remove-Item -Path $_.FullName -Force }
+ Get-ChildItem './GeneratedCode' -Directory -ErrorAction SilentlyContinue |
+ ForEach-Object { Remove-Item -Path $_.FullName -Recurse -Force }
}
- }
+ } catch { }
+}
+
+function RunGenerationTasks
+{
+ param (
+ [array]$tasks,
+ [string]$processPath,
+ [bool]$useDocker
+ )
- $process = Start-Process "dotnet" `
- -Args "build $solution --nologo -v q --property WarningLevel=0 /clp:ErrorsOnly" `
- -NoNewWindow `
- -PassThru
- $process | Wait-Process
- if ($process.ExitCode -ne 0)
+ for ($i = 0; $i -lt $tasks.Count; $i++)
{
- throw "Build Failed!"
+ $task = $tasks[$i]
+ $arguments = "$($task.SpecPath) --namespace $($task.Namespace) --output $($task.OutputPath) --no-logging"
+ if ($task.Args) { $arguments += " $($task.Args)" }
+ $p = StartRefitter -arguments $arguments -processPath $processPath -useDocker $useDocker
+ $p | Wait-Process
+ if ($p.ExitCode -ne 0) { throw "Refitter generation failed for: $($task.SpecPath) ($($task.Namespace))" }
}
}
function RunTests
{
param (
- [Parameter(Mandatory=$true)]
- [ValidateSet("dotnet-run", "refitter")]
- [string]
- $Method,
-
- [Parameter(Mandatory=$false)]
- [bool]
- $Parallel = $false,
-
- [Parameter(Mandatory=$false)]
- [bool]
- $BuildFromSource = $true,
-
- [Parameter(Mandatory=$false)]
- [bool]
- $UseDocker = $false
+ [bool]$BuildFromSource = $true,
+ [bool]$UseDocker = $false
)
+ $processPath = GetProcessPath -buildFromSource $BuildFromSource -useDocker $UseDocker
+
$filenames = @(
"weather",
"bot.paths",
@@ -202,129 +156,293 @@ function RunTests
"hubspot-webhooks"
)
+ $v31Filenames = @(
+ "webhook-example"
+ )
+
+ # Standard variants: compile on all frameworks (net462-net10)
+ $standardVariants = @(
+ @{ Suffix="Cancellation"; Prefix="WithCancellation"; Args="--cancellation-tokens" },
+ @{ Suffix="Internal"; Prefix="Internal"; Args="--internal" },
+ @{ Suffix="UsingApiResponse"; Prefix="IApi"; Args="--use-api-response" },
+ @{ Suffix="UsingIObservable"; Prefix="IObservable"; Args="--use-observable-response" },
+ @{ Suffix="UsingIsoDateFormat"; Prefix="UsingIsoDateFormat"; Args="--use-iso-date-format" },
+ @{ Suffix="MultipleInterfaces"; Prefix="MultipleInterfaces"; Args="--multiple-interfaces ByEndpoint" },
+ # NOTE: --multiple-interfaces ByEndpoint --operation-name-template produces duplicate types per-endpoint.
+ # This is a known Refitter limitation. We test generation works but skip compilation.
+ # @{ Suffix="MultipleInterfaces"; Prefix="MultipleInterfacesWithCustomName"; Args="--multiple-interfaces ByEndpoint --operation-name-template ExecuteAsync" },
+ @{ Suffix="ContractOnly"; Prefix="ContractOnly"; Args="--contract-only" },
+ @{ Suffix="DynamicQuerystring"; Prefix="DynamicQuerystring"; Args="--use-dynamic-querystring-parameters" },
+ @{ Suffix="IntegerTypeInt64"; Prefix="IntegerTypeInt64"; Args="--integer-type Int64" },
+ @{ Suffix="TrimUnusedSchema"; Prefix="TrimUnusedSchema"; Args="--trim-unused-schema" },
+ @{ Suffix="OptionalNullable"; Prefix="OptionalNullable"; Args="--optional-nullable-parameters" },
+ @{ Suffix="NoDeprecated"; Prefix="NoDeprecated"; Args="--no-deprecated-operations" },
+ @{ Suffix="NoAutoGeneratedHeader"; Prefix="NoAutoGenHeader"; Args="--no-auto-generated-header" },
+ @{ Suffix="NoAcceptHeaders"; Prefix="NoAcceptHeaders"; Args="--no-accept-headers" },
+ @{ Suffix="SkipDefaultAdditionalProps"; Prefix="SkipDefaultAddlProps"; Args="--skip-default-additional-properties" },
+ @{ Suffix="NoInlineJsonConverters"; Prefix="NoInlineJsonConv"; Args="--no-inline-json-converters" }
+ )
+
+ # Petstore-only variants: require specs with specific tags/paths (petstore has "pet", "user", "store" tags)
+ $petstoreOnlyVariants = @(
+ @{ Suffix="TagFiltered"; Prefix="TagFiltered"; Args="--tag pet --tag user --tag store" },
+ @{ Suffix="MatchPathFiltered"; Prefix="MatchPathFiltered"; Args="--match-path ^/pet/.*" },
+ @{ Suffix="MultipleInterfacesByTag"; Prefix="MultipleInterfacesByTag"; Args="--multiple-interfaces ByTag" }
+ )
+
+ # NetCore variants: require net8.0+ features
+ $netCoreVariants = @(
+ @{ Suffix="Disposable"; Prefix="Disposable"; Args="--disposable" },
+ @{ Suffix="ImmutableRecords"; Prefix="ImmutableRecords"; Args="--immutable-records" },
+ @{ Suffix="PolymorphicSerialization"; Prefix="PolymorphicSerialization"; Args="--use-polymorphic-serialization" },
+ @{ Suffix="CollectionFormatCsv"; Prefix="CollectionFormatCsv"; Args="--collection-format csv" }
+ )
+
+ # ==========================================
+ # Phase 0: Build refitter from source
+ # ==========================================
if ($BuildFromSource -and -not $UseDocker)
{
- Write-Host "dotnet publish ../src/Refitter/Refitter.csproj -c Release -o bin -f net10.0"
- Start-Process "dotnet" -Args "publish ../src/Refitter/Refitter.csproj -c Release -o bin -f net10.0" -NoNewWindow -PassThru | Wait-Process
+ Write-Host "dotnet publish ../src/Refitter/Refitter.csproj -c Release -o bin -f net9.0"
+ Start-Process "dotnet" -Args "publish ../src/Refitter/Refitter.csproj -c Release -o bin -f net9.0" -NoNewWindow -PassThru | Wait-Process
Write-Host "refitter --version"
- $process = Start-Process "./bin/refitter" `
- -Args " --version" `
- -NoNewWindow `
- -PassThru
- $process | Wait-Process
- if ($process.ExitCode -ne 0)
- {
- throw "Show version failed!"
- }
+ $p = Start-Process "./bin/refitter" -Args "--version" -NoNewWindow -PassThru
+ $p | Wait-Process
+ if ($p.ExitCode -ne 0) { throw "Show version failed!" }
}
- GenerateAndBuild -format " " -namespace " " -outputPath "SwaggerPetstoreDirect.generated.cs" -args "--settings-file ./petstore.refitter" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format " " -namespace " " -args "--settings-file ./Apizr/petstore.apizr.refitter" -csproj "./Apizr/Sample.csproj" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format " " -namespace " " -args "--settings-file ./MultipleFiles/petstore.refitter" -csproj "MultipleFiles/Client/Client.csproj" -buildFromSource $buildFromSource -useDocker $UseDocker
-
- "v3.0", "v2.0" | ForEach-Object {
- $version = $_
- "json", "yaml" | ForEach-Object {
- $format = $_
- $filenames | ForEach-Object {
- $filename = "./OpenAPI/$version/$_.$format"
- $exists = Test-Path -Path $filename -PathType Leaf
- if ($exists -eq $true)
+ # ==========================================
+ # Phase 1: Pre-restore packages
+ # ==========================================
+ Write-Host "`r`n=== Pre-restoring packages ===`r`n"
+ Start-Process "dotnet" -Args "restore ./ConsoleApp/ConsoleApp.slnx --nologo -v q" -NoNewWindow -PassThru | Wait-Process
+ Start-Process "dotnet" -Args "restore ./ConsoleApp/ConsoleApp.Core.slnx --nologo -v q" -NoNewWindow -PassThru | Wait-Process
+ Start-Process "dotnet" -Args "restore ./Apizr/Sample.csproj --nologo -v q" -NoNewWindow -PassThru | Wait-Process
+
+ # ==========================================
+ # Phase 2: Settings-file tests (individual generate + build)
+ # ==========================================
+ Write-Host "`r`n=== Settings-file tests ===`r`n"
+
+ CleanGeneratedCode
+ GenerateFromSettingsFile -settingsFile "./petstore.refitter" -processPath $processPath -useDocker $UseDocker
+ BuildSolution -solution "./ConsoleApp/ConsoleApp.slnx" -noRestore
+
+ CleanGeneratedCode
+ GenerateFromSettingsFile -settingsFile "./Apizr/petstore.apizr.refitter" -processPath $processPath -useDocker $UseDocker
+ BuildSolution -solution "./Apizr/Sample.csproj" -noRestore
+
+ GenerateFromSettingsFile -settingsFile "./MultipleFiles/petstore.refitter" -processPath $processPath -useDocker $UseDocker
+ BuildSolution -solution "MultipleFiles/Client/Client.csproj"
+
+ CleanGeneratedCode
+ GenerateFromSettingsFile -settingsFile "./multiple-sources.refitter" -processPath $processPath -useDocker $UseDocker
+ BuildSolution -solution "./ConsoleApp/ConsoleApp.Core.slnx" -noRestore
+
+ # ==========================================
+ # Phase 3: Generate all STANDARD variants (no build until all are generated)
+ # ==========================================
+ Write-Host "`r`n=== Generating standard variants ===`r`n"
+ CleanGeneratedCode
+
+ $standardTasks = @()
+ $netCoreTasks = @()
+
+ # Helper to create unique file tag from version/format/filename
+ function MakeFileTag([string]$version, [string]$format, [string]$filename)
+ {
+ $vTag = $version.Replace(".", "")
+ $base = $filename.Replace("-", "").Replace(".", "")
+ $base = $base.Substring(0, 1).ToUpperInvariant() + $base.Substring(1)
+ $nsBase = "${base}_${vTag}_${format}"
+ return @{ Tag = "${vTag}_${format}_${base}"; Namespace = $nsBase }
+ }
+
+ # Collect generation tasks for v2.0 and v3.0
+ foreach ($version in @("v3.0", "v2.0"))
+ {
+ foreach ($format in @("json", "yaml"))
+ {
+ foreach ($filename in $filenames)
+ {
+ $specPath = "./OpenAPI/$version/$filename.$format"
+ if (-not (Test-Path -Path $specPath -PathType Leaf)) { continue }
+
+ $info = MakeFileTag $version $format $filename
+ $fileTag = $info.Tag
+ $ns = $info.Namespace
+
+ foreach ($v in $standardVariants)
{
- Copy-Item $filename ./openapi.$format
- $outputPath = "$_.generated.cs"
- $outputPath = $outputPath.Substring(0, 1).ToUpperInvariant() + $outputPath.Substring(1, $outputPath.Length - 1)
- $namespace = $_.Replace("-", "")
- $namespace = $namespace.Substring(0, 1).ToUpperInvariant() + $namespace.Substring(1, $namespace.Length - 1)
-
- GenerateAndBuild -format $format -namespace "$namespace.Disposable" -outputPath "Disposable$outputPath" -args "--disposable" -buildFromSource $buildFromSource -netCore -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.MultipleFiles" -args "--multiple-files" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.SeparateContractsFile" -args "--contracts-output GeneratedCode/Contracts --contracts-namespace $namespace.SeparateContractsFile.Contracts" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.Cancellation" -outputPath "WithCancellation$outputPath" "--cancellation-tokens" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.Internal" -outputPath "Internal$outputPath" -args "--internal" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.UsingApiResponse" -outputPath "IApi$outputPath" -args "--use-api-response" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.UsingIObservable" -outputPath "IObservable$outputPath" -args "--use-observable-response" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.UsingIsoDateFormat" -outputPath "UsingIsoDateFormat$outputPath" -args "--use-iso-date-format" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.MultipleInterfaces" -outputPath "MultipleInterfaces$outputPath" -args "--multiple-interfaces ByEndpoint" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.MultipleInterfaces" -outputPath "MultipleInterfacesWithCustomName$outputPath" -args "--multiple-interfaces ByEndpoint --operation-name-template ExecuteAsync" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.TagFiltered" -outputPath "TagFiltered$outputPath" -args "--tag pet --tag user --tag store" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.MatchPathFiltered" -outputPath "MatchPathFiltered$outputPath" -args "--match-path ^/pet/.*" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.ContractOnly" -outputPath "ContractOnly$outputPath" -args "--contract-only" -buildFromSource $buildFromSource -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.ImmutableRecords" -outputPath "ImmutableRecords$outputPath" -args "--immutable-records" -buildFromSource $buildFromSource -netCore -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.PolymorphicSerialization" -outputPath "PolymorphicSerialization$outputPath" -args "--use-polymorphic-serialization" -buildFromSource $buildFromSource -netCore -useDocker $UseDocker
- GenerateAndBuild -format $format -namespace "$namespace.CollectionFormatCsv" -outputPath "CollectionFormatCsv$outputPath" -args "--collection-format csv" -buildFromSource $buildFromSource -netCore -useDocker $UseDocker
+ $standardTasks += @{
+ SpecPath = $specPath
+ Namespace = "$ns.$($v.Suffix)"
+ OutputPath = "./GeneratedCode/$($v.Prefix)${fileTag}.generated.cs"
+ Args = $v.Args
+ }
}
- }
- }
- }
- "https://petstore3.swagger.io/api/v3/openapi.json", "https://petstore3.swagger.io/api/v3/openapi.yaml" | ForEach-Object {
- $namespace = "PetstoreFromUri"
- $outputPath = "PetstoreFromUri.generated.cs"
+ # Petstore-only variants (tag/path filters require petstore-specific tags)
+ if ($filename -like "petstore*")
+ {
+ foreach ($v in $petstoreOnlyVariants)
+ {
+ $standardTasks += @{
+ SpecPath = $specPath
+ Namespace = "$ns.$($v.Suffix)"
+ OutputPath = "./GeneratedCode/$($v.Prefix)${fileTag}.generated.cs"
+ Args = $v.Args
+ }
+ }
+ }
- Get-ChildItem '*.generated.cs' -Recurse | foreach { Remove-Item -Path $_.FullName }
+ # Multiple files variant (unique subdirectory)
+ $standardTasks += @{
+ SpecPath = $specPath
+ Namespace = "$ns.MultipleFiles"
+ OutputPath = "./GeneratedCode/MultipleFiles/$fileTag/"
+ Args = "--multiple-files"
+ }
- $processPath = "./bin/refitter"
- if ($buildFromSource -eq $false)
- {
- $processPath = "refitter"
- }
- if ($UseDocker)
- {
- $processPath = "docker"
+ # Separate contracts variant (unique subdirectories for both interface and contracts)
+ $standardTasks += @{
+ SpecPath = $specPath
+ Namespace = "$ns.SeparateContractsFile"
+ OutputPath = "./GeneratedCode/SeparateContracts/$fileTag/"
+ Args = "--contracts-output GeneratedCode/Contracts/$fileTag --contracts-namespace $ns.SeparateContractsFile.Contracts"
+ }
+
+ foreach ($v in $netCoreVariants)
+ {
+ $netCoreTasks += @{
+ SpecPath = $specPath
+ Namespace = "$ns.$($v.Suffix)"
+ OutputPath = "./GeneratedCode/$($v.Prefix)${fileTag}.generated.cs"
+ Args = $v.Args
+ }
+ }
+ }
}
+ }
- if ($UseDocker)
+ # Collect generation tasks for v3.1
+ # Note: v3.1 webhook specs may not have regular API paths, so skip MultipleInterfaces variants
+ foreach ($format in @("json", "yaml"))
+ {
+ foreach ($filename in $v31Filenames)
{
- $currentDir = (Get-Location).Path.Replace('\', '/')
- # Get current user ID and group ID for Linux/macOS
- $userParam = ""
- if ($IsLinux -or $IsMacOS) {
- $uid = sh -c 'id -u'
- $gid = sh -c 'id -g'
- $userParam = "--user ${uid}:${gid}"
+ $specPath = "./OpenAPI/v3.1/$filename.$format"
+ if (-not (Test-Path -Path $specPath -PathType Leaf)) { continue }
+
+ $info = MakeFileTag "v3.1" $format $filename
+ $fileTag = $info.Tag
+ $ns = $info.Namespace
+
+ foreach ($v in $standardVariants)
+ {
+ if ($v.Args -like "*--multiple-interfaces*") { continue }
+ $standardTasks += @{
+ SpecPath = $specPath
+ Namespace = "$ns.$($v.Suffix)"
+ OutputPath = "./GeneratedCode/$($v.Prefix)${fileTag}.generated.cs"
+ Args = $v.Args
+ }
}
-
- if ($userParam) {
- Write-Host "docker run --rm -v ""${currentDir}:/src"" -w /src $userParam christianhelle/refitter ""$_"" --namespace $namespace --output ./GeneratedCode/$outputPath"
- $process = Start-Process $processPath `
- -Args "run --rm -v ""${currentDir}:/src"" -w /src $userParam christianhelle/refitter ""$_"" --namespace $namespace --output ./GeneratedCode/$outputPath" `
- -NoNewWindow `
- -PassThru
- } else {
- Write-Host "docker run --rm -v ""${currentDir}:/src"" -w /src christianhelle/refitter ""$_"" --namespace $namespace --output ./GeneratedCode/$outputPath"
- $process = Start-Process $processPath `
- -Args "run --rm -v ""${currentDir}:/src"" -w /src christianhelle/refitter ""$_"" --namespace $namespace --output ./GeneratedCode/$outputPath" `
- -NoNewWindow `
- -PassThru
+
+ $standardTasks += @{
+ SpecPath = $specPath
+ Namespace = "$ns.MultipleFiles"
+ OutputPath = "./GeneratedCode/MultipleFiles/$fileTag/"
+ Args = "--multiple-files"
}
- }
- else
- {
- Write-Host "refitter ./openapi.$format --namespace $namespace --output ./GeneratedCode/$outputPath --no-logging $args"
- $process = Start-Process $processPath `
- -Args """$_"" --namespace $namespace --output ./GeneratedCode/$outputPath" `
- -NoNewWindow `
- -PassThru
- }
- $process | Wait-Process
- if ($process.ExitCode -ne 0)
- {
- throw "Refitter failed"
- }
- Write-Host "`r`nBuilding ConsoleApp`r`n"
- $process = Start-Process "dotnet" `
- -Args "build ./ConsoleApp/ConsoleApp.slnx" `
- -NoNewWindow `
- -PassThru
- $process | Wait-Process
- if ($process.ExitCode -ne 0)
- {
- throw "Build Failed!"
+ $standardTasks += @{
+ SpecPath = $specPath
+ Namespace = "$ns.SeparateContractsFile"
+ OutputPath = "./GeneratedCode/SeparateContracts/$fileTag/"
+ Args = "--contracts-output GeneratedCode/Contracts/$fileTag --contracts-namespace $ns.SeparateContractsFile.Contracts"
+ }
+
+ foreach ($v in $netCoreVariants)
+ {
+ $netCoreTasks += @{
+ SpecPath = $specPath
+ Namespace = "$ns.$($v.Suffix)"
+ OutputPath = "./GeneratedCode/$($v.Prefix)${fileTag}.generated.cs"
+ Args = $v.Args
+ }
+ }
}
}
+
+ Write-Host "Standard generation tasks: $($standardTasks.Count)"
+ Write-Host "NetCore generation tasks: $($netCoreTasks.Count)"
+
+ # Execute standard generation in parallel batches
+ RunGenerationTasks -tasks $standardTasks -processPath $processPath -useDocker $UseDocker
+
+ # ==========================================
+ # Phase 4: Build standard variants (one build validates all)
+ # ==========================================
+ Write-Host "`r`n=== Building standard variants ===`r`n"
+ BuildSolution -solution "./ConsoleApp/ConsoleApp.slnx" -noRestore -smokeTest
+
+ # ==========================================
+ # Phase 4b: Generate-only test for MultipleInterfacesWithCustomName
+ # This variant uses --multiple-interfaces ByEndpoint --operation-name-template which
+ # generates duplicate types per-endpoint (known limitation). We verify generation succeeds.
+ # ==========================================
+ Write-Host "`r`n=== Generate-only: MultipleInterfacesWithCustomName (petstore) ===`r`n"
+ $customNameSpec = "./OpenAPI/v3.0/petstore.json"
+ $customNameArgs = "--multiple-interfaces ByEndpoint --operation-name-template ExecuteAsync"
+ $customNameOutput = "./GeneratedCode/MultipleInterfacesWithCustomName_generateonly.cs"
+ $customNameCmd = "$processPath $customNameSpec --namespace GenerateOnly.MultipleInterfacesWithCustomName --output $customNameOutput --no-logging $customNameArgs"
+ Write-Host $customNameCmd
+ Invoke-Expression $customNameCmd
+ if (-not (Test-Path $customNameOutput)) { throw "Generate-only test failed: MultipleInterfacesWithCustomName" }
+ Remove-Item $customNameOutput -Force
+ Write-Host "Generate-only test passed: MultipleInterfacesWithCustomName"
+
+ # ==========================================
+ # Phase 5: Generate netCore variants (accumulate on top of standard code)
+ # Net8/Net9/Net10 can compile both standard and netCore code
+ # ==========================================
+ Write-Host "`r`n=== Generating netCore variants ===`r`n"
+ RunGenerationTasks -tasks $netCoreTasks -processPath $processPath -useDocker $UseDocker
+
+ # ==========================================
+ # Phase 6: Build netCore variants
+ # ==========================================
+ Write-Host "`r`n=== Building netCore variants ===`r`n"
+ BuildSolution -solution "./ConsoleApp/ConsoleApp.Core.slnx" -noRestore -smokeTest
+
+ # ==========================================
+ # Phase 7: URL-based tests (network-dependent)
+ # ==========================================
+ Write-Host "`r`n=== URL-based tests ===`r`n"
+ CleanGeneratedCode
+
+ @("https://petstore3.swagger.io/api/v3/openapi.json", "https://petstore3.swagger.io/api/v3/openapi.yaml") | ForEach-Object {
+ $url = $_
+ $urlFormat = if ($url.EndsWith(".json")) { "json" } else { "yaml" }
+ $namespace = "PetstoreFromUri"
+ $outputPath = "PetstoreFromUri.generated.cs"
+
+ try {
+ Get-ChildItem './GeneratedCode/*.cs' -Recurse -ErrorAction SilentlyContinue |
+ ForEach-Object { Remove-Item -Path $_.FullName -Force }
+ } catch { }
+
+ $p = StartRefitter `
+ -arguments """$url"" --namespace $namespace --output ./GeneratedCode/$outputPath --no-logging" `
+ -processPath $processPath `
+ -useDocker $UseDocker
+ $p | Wait-Process
+ if ($p.ExitCode -ne 0) { throw "Refitter failed for URL: $url" }
+
+ BuildSolution -solution "./ConsoleApp/ConsoleApp.slnx" -noRestore
+ }
}
if ($UseProduction)
@@ -345,6 +463,10 @@ if ($UseDocker)
Write-Host "`r`n"
}
-Measure-Command { RunTests -Method "dotnet-run" -Parallel $Parallel -BuildFromSource (!$UseProduction -and !$UseDocker) -UseDocker $UseDocker }
+Measure-Command {
+ RunTests `
+ -BuildFromSource (!$UseProduction -and !$UseDocker) `
+ -UseDocker $UseDocker
+}
Write-Host "`r`n"
Write-Host "`r`n"