From a3e7a53e0022986f560f937832955e0d20204da1 Mon Sep 17 00:00:00 2001 From: "Bryan Wang (PSHCT)" Date: Tue, 8 Aug 2017 11:34:41 -0700 Subject: [PATCH 01/40] Increment PSSwagger version --- PSSwagger/PSSwagger.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PSSwagger/PSSwagger.psd1 b/PSSwagger/PSSwagger.psd1 index 8f3b77c..4e5e537 100644 --- a/PSSwagger/PSSwagger.psd1 +++ b/PSSwagger/PSSwagger.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'PSSwagger.psm1' -ModuleVersion = '0.2.0' +ModuleVersion = '0.3.0' PowerShellVersion = '5.1' GUID = '6c925abf-49bc-49f4-8a47-12b95c9a8b37' Author = 'Microsoft Corporation' From 3c20fd34ea39da70db7bcb95352bf997a79eb5aa Mon Sep 17 00:00:00 2001 From: brywang-msft Date: Wed, 9 Aug 2017 16:26:19 -0700 Subject: [PATCH 02/40] readme update (#292) * readme update * Review comments * review --- README.md | 72 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 0bb9167..09f5492 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,11 @@ A PowerShell module with commands to generate the PowerShell commands for a give ## Supported Platforms | Usage | Platforms | | ----------------| ------------------------------------- | -| Developer | Windows, Any full PowerShell version, PowerShell Core (NOTE: AzureRM.Profile is currently broken on beta.1, but a new module should be released soon) | -| Module Publisher| Any full PowerShell version | -| Module Consumer | Any full PowerShell version, PowerShell Core Alpha11 or older | +| Developer | Windows, PowerShell 5.1+, Latest PowerShell Core | +| Module Publisher| PowerShell 5.1+ | +| Module Consumer | PowerShell 5.1+, Latest PowerShell Core | +**Future note**: We plan on supporting PowerShell 4+ for module consumers in the future; that scenario is untested currently. **Testing note**: While any full PowerShell version is fine for development, we recommend using PowerShell 5.1+ to enable testing our implementation of Get-FileHash. ## Dependencies @@ -40,11 +41,16 @@ A PowerShell module with commands to generate the PowerShell commands for a give | AzureRM.Profile.NetCore.Preview | * | Module containing authentication helpers, required for Microsoft Azure modules on PowerShell Core | ## Usage - -1. Git clone this repository. - ```code - git clone https://github.com/PowerShell/PSSwagger.git - ``` +NOTE: In the short term, for best performance, the operation IDs in your Open API specifications should be of the form "_". For example, the operation ID "Resource_GetByName" gets a resource named Resource by name. +1. Get PSSwagger! + * Install from PowerShellGallery.com: + ```powershell + Install-Module -Name PSSwagger + ``` + * Clone the repository + ```powershell + git clone https://github.com/PowerShell/PSSwagger.git + ``` 2. Ensure you AutoRest version 0.17.3 installed ```powershell @@ -53,40 +59,38 @@ A PowerShell module with commands to generate the PowerShell commands for a give 3. Ensure AutoRest.exe is in $env:Path ```powershell - $env:path += ";$env:localappdata\PackageManagement\NuGet\Packages\AutoRest.0.17.3\tools" + $package = Get-Package -Name AutoRest -RequiredVersion 0.17.3 + $env:path += ";$(Split-Path $package.Source -Parent)\tools" ``` 4. If you plan on precompiling the generated assembly, ensure you have the module AzureRM.Profile or AzureRM.NetCore.Preview available to PackageManagement if you are on PowerShell or PowerShell Core, respectively. 5. Run the following in a PowerShell console from the directory where you cloned PSSwagger in - ```powershell - Import-Module .\PSSwagger\PSSwagger.psd1 - $param = @{ - SpecificationUri = 'https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/arm-batch/2015-12-01/swagger/BatchManagement.json' - Path = 'C:\GeneratedModules\' - Name = 'AzBatchManagement' - UseAzureCsharpGenerator = $true - } - New-PSSwaggerModule @param - ``` -After step 5, the module will be in `C:\Temp\GeneratedModule\Generated.AzureRM.BatchManagement ($param.Path)` folder. - -Before importing that module and using it, you need to import `PSSwaggerUtility` module which is under PSSwagger folder. - -```powershell -Import-Module .\PSSwagger\PSSwaggerUtility -Import-Module "$($param.Path)\$($param.Name)" -Get-Command -Module $param.Name -``` + ```powershell + Import-Module .\PSSwagger\PSSwagger.psd1 + $param = @{ + # Download the Open API v2 Specification from this location + SpecificationUri = 'https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/arm-batch/2015-12-01/swagger/BatchManagement.json' + # Output the generated module to this path + Path = 'C:\GeneratedModules\' + # Name of the generated module + Name = 'AzBatchManagement' + # This specification is for a Microsoft Azure service, so use Azure-specific functionality + UseAzureCsharpGenerator = $true + } + # You may be prompted to download missing dependencies + New-PSSwaggerModule @param + ``` + The generated module will be in the `C:\Temp\GeneratedModule\Generated.AzureRM.BatchManagement` folder. + For more New-PSSwaggerModule options, check out the [documentation](/docs/commands/New-PSSwaggerModule.md). +6. Your generated module is now ready! For production modules (i.e. modules you will publish to PowerShellGallery.com), we recommend using the -IncludeCoreFxAssembly option to generate the Core CLR assembly, strong name signing assemblies in the generated module, and authenticode signing the module. Optionally, you can remove the generated C# code (under the Generated.CSharp folder) for even more security. + +## Metadata Generation +There are many cases where module generation doesn't result in the most optimal names for things like parameters or commands. To enable this and many other customization options, we've introduced additional PowerShell-specific Open API extensions that can be specified separately from your main specification. For more information, check out [PowerShell Extensions](/docs/extensions/readme.md) and [New-PSSwaggerMetadataFile](/docs/commands/New-PSSwaggerMetadataFile.md). ## Dynamic generation of C# assembly -When importing the module for the first time, the packaged C# files will be automatically compiled if the expected assembly doesn't exist. -If the module's script files are signed, regardless of your script execution policy, the catalog file's signing will be checked for validity. -If the generated module is not signed, the catalog file's signing will not be checked. However, the catalog file's hashed contents will always be checked. - -## Distribution of module -Because of the dynamic compilation feature, it is highly recommended that publishers of a generated module Authenticode sign the module and strong name sign both precompiled assemblies (full CLR and core CLR). +When importing the module for the first time, the packaged C# files will be automatically compiled if the expected assembly doesn't exist. ## Microsoft.Rest.ServiceClientTracing support To enable Microsoft.Rest.ServiceClientTracing support, pass -Debug into any generated command. From 2d646f91f3283bd04ffd9c1dd5160621d0017408 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Fri, 11 Aug 2017 14:19:22 -0700 Subject: [PATCH 03/40] Support custom x-ms-pageable\NextLinkName field name (#294) --- PSSwagger/PSSwagger.Constants.ps1 | 20 ++++++++++---------- PSSwagger/Paths.psm1 | 5 +++++ Tests/run-tests.ps1 | 7 ------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/PSSwagger/PSSwagger.Constants.ps1 b/PSSwagger/PSSwagger.Constants.ps1 index 0b4b9a3..9e84de7 100644 --- a/PSSwagger/PSSwagger.Constants.ps1 +++ b/PSSwagger/PSSwagger.Constants.ps1 @@ -371,9 +371,9 @@ $PagingBlockStrFunctionCallWithTop = @' Write-Verbose -Message 'Flattening paged results.' # Get the next page iff 1) there is a next page and 2) any result in the next page would be returned - while (`$result -and `$result.NextPageLink -and ((`$Top -eq -1) -or (`$returnedCount -lt `$Top))) { - Write-Debug -Message "Retrieving next page: `$(`$result.NextPageLink)" - `$taskResult = $clientName$pagingOperations.$pagingOperationName(`$result.NextPageLink) + while (`$result -and (Get-Member -InputObject `$result -Name $NextLinkName) -and `$result.$NextLinkName -and ((`$Top -eq -1) -or (`$returnedCount -lt `$Top))) { + Write-Debug -Message "Retrieving next page: `$(`$result.$NextLinkName)" + `$taskResult = $clientName$pagingOperations.$pagingOperationName(`$result.$NextLinkName) $getTaskResult } '@ @@ -381,9 +381,9 @@ $PagingBlockStrFunctionCallWithTop = @' $PagingBlockStrFunctionCall = @' Write-Verbose -Message 'Flattening paged results.' - while (`$result -and `$result.NextPageLink) { - Write-Debug -Message "Retrieving next page: `$(`$result.NextPageLink)" - `$taskResult = $clientName$pagingOperations.$pagingOperationName(`$result.NextPageLink) + while (`$result -and (Get-Member -InputObject `$result -Name $NextLinkName) -and `$result.$NextLinkName) { + Write-Debug -Message "Retrieving next page: `$(`$result.$NextLinkName)" + `$taskResult = $clientName$pagingOperations.$pagingOperationName(`$result.$NextLinkName) $getTaskResult } '@ @@ -393,8 +393,8 @@ $PagingBlockStrCmdletCallWithTop = @' Write-Verbose -Message 'Flattening paged results.' # Get the next page iff 1) there is a next page and 2) any result in the next page would be returned - while (`$result -and `$result.NextPageLink -and ((`$Top -eq -1) -or (`$returnedCount -lt `$Top))) { - Write-Debug -Message "Retrieving next page: `$(`$result.NextPageLink)" + while (`$result -and (Get-Member -InputObject `$result -Name $NextLinkName) -and `$result.$NextLinkName -and ((`$Top -eq -1) -or (`$returnedCount -lt `$Top))) { + Write-Debug -Message "Retrieving next page: `$(`$result.$NextLinkName)" $Cmdlet $CmdletArgs } '@ @@ -402,8 +402,8 @@ $PagingBlockStrCmdletCallWithTop = @' $PagingBlockStrCmdletCall = @' Write-Verbose -Message 'Flattening paged results.' - while (`$result -and `$result.NextPageLink) { - Write-Debug -Message "Retrieving next page: `$(`$result.NextPageLink)" + while (`$result -and (Get-Member -InputObject `$result -Name $NextLinkName) -and `$result.$NextLinkName) { + Write-Debug -Message "Retrieving next page: `$(`$result.$NextLinkName)" $Cmdlet $CmdletArgs } '@ diff --git a/PSSwagger/Paths.psm1 b/PSSwagger/Paths.psm1 index e40763f..00853fd 100644 --- a/PSSwagger/Paths.psm1 +++ b/PSSwagger/Paths.psm1 @@ -539,6 +539,7 @@ function New-SwaggerPath $skipParameterToAdd = $null $pagingBlock = '' $pagingOperationName = '' + $NextLinkName = 'NextLink' $pagingOperations = '' $Cmdlet = '' $CmdletParameter = '' @@ -558,6 +559,10 @@ function New-SwaggerPath $CmdletArgs = $x_ms_pageableObject.CmdletArgsPaging } + if ($x_ms_pageableObject.ContainsKey('NextLinkName') -and $x_ms_pageableObject.NextLinkName) { + $NextLinkName = $x_ms_pageableObject.NextLinkName + } + $topParameterToAdd = @{ Details = @{ Name = 'Top' diff --git a/Tests/run-tests.ps1 b/Tests/run-tests.ps1 index 8601f65..76a2cc2 100644 --- a/Tests/run-tests.ps1 +++ b/Tests/run-tests.ps1 @@ -140,13 +140,6 @@ if ($TestSuite.Contains("All")) { $executeTestsCommand += " -Tag $TestSuite" } - - -# Clean up generated test assemblies -Write-Verbose "Cleaning old test assemblies, if any." -Get-ChildItem -Path (Join-Path "$PSScriptRoot" "PSSwagger.TestUtilities") -Filter *.dll | Remove-Item -Force -Get-ChildItem -Path (Join-Path "$PSScriptRoot" "PSSwagger.TestUtilities") -Filter *.pdb | Remove-Item -Force - Write-Verbose "Dependency versions:" Write-Verbose " -- AzureRM.Profile: $($azureRmProfile.Version)" Write-Verbose " -- Pester: $((get-command invoke-pester).Version)" From 3d2520e96a719e3dd753cb9080fa89fbf8fad894 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Mon, 14 Aug 2017 11:27:00 -0700 Subject: [PATCH 04/40] Updated Readme and fixed an error related to importing the PSSwaggerUtility module. (#300) --- PSSwagger/PSSwagger.psm1 | 3 +-- README.md | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index f758695..0c6e3c2 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -663,8 +663,7 @@ function ConvertTo-CsharpCode Export-CliXml -InputObject $PathFunctionDetails -Path $cliXmlTmpPath $dependencies = Get-PSSwaggerExternalDependencies -Azure:$codeCreatedByAzureGenerator -Framework 'net4' $microsoftRestClientRuntimeAzureRequiredVersion = if ($dependencies.ContainsKey('Microsoft.Rest.ClientRuntime.Azure')) { $dependencies['Microsoft.Rest.ClientRuntime.Azure'].RequiredVersion } else { '' } - $command = "Import-Module '$PSScriptRoot\PSSwaggerUtility'; - Add-PSSwaggerClientType -OutputAssemblyName '$outAssembly' `` + $command = "PSSwaggerUtility\Add-PSSwaggerClientType -OutputAssemblyName '$outAssembly' `` -ClrPath '$clrPath' `` -CSharpFiles $allCSharpFilesArrayString `` -CodeCreatedByAzureGenerator:`$$codeCreatedByAzureGenerator `` diff --git a/README.md b/README.md index 09f5492..6d4897f 100644 --- a/README.md +++ b/README.md @@ -61,15 +61,29 @@ NOTE: In the short term, for best performance, the operation IDs in your Open AP ```powershell $package = Get-Package -Name AutoRest -RequiredVersion 0.17.3 $env:path += ";$(Split-Path $package.Source -Parent)\tools" + Get-Command -Name AutoRest ``` -4. If you plan on precompiling the generated assembly, ensure you have the module AzureRM.Profile or AzureRM.NetCore.Preview available to PackageManagement if you are on PowerShell or PowerShell Core, respectively. +4. If you plan on pre-compiling the generated assembly, ensure you have the module AzureRM.Profile or AzureRM.NetCore.Preview available to PackageManagement if you are on PowerShell or PowerShell Core, respectively. 5. Run the following in a PowerShell console from the directory where you cloned PSSwagger in ```powershell - Import-Module .\PSSwagger\PSSwagger.psd1 - $param = @{ + # Import PSSwagger module + Import-Module PSSwagger + + # If you are trying from a clone of this repository, follow below steps to import the PSSwagger module. + # Ensure PSSwaggerUtility module is available in $env:PSModulePath + $PSSwaggerFolderPath = Resolve-Path '.\PSSwagger' + $env:PSModulePath = "$PSSwaggerFolderPath;$env:PSModulePath" + Import-Module .\PSSwagger + + # Ensure PSSwagger module is loaded into the current session + Get-Module PSSwagger + + # Prepare input parameters for cmdlet generation + $null = New-Item -ItemType Directory -Path C:\GeneratedModules -Force + $params = @{ # Download the Open API v2 Specification from this location SpecificationUri = 'https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/arm-batch/2015-12-01/swagger/BatchManagement.json' # Output the generated module to this path @@ -79,9 +93,11 @@ NOTE: In the short term, for best performance, the operation IDs in your Open AP # This specification is for a Microsoft Azure service, so use Azure-specific functionality UseAzureCsharpGenerator = $true } + # You may be prompted to download missing dependencies - New-PSSwaggerModule @param + New-PSSwaggerModule @params ``` + The generated module will be in the `C:\Temp\GeneratedModule\Generated.AzureRM.BatchManagement` folder. For more New-PSSwaggerModule options, check out the [documentation](/docs/commands/New-PSSwaggerModule.md). 6. Your generated module is now ready! For production modules (i.e. modules you will publish to PowerShellGallery.com), we recommend using the -IncludeCoreFxAssembly option to generate the Core CLR assembly, strong name signing assemblies in the generated module, and authenticode signing the module. Optionally, you can remove the generated C# code (under the Generated.CSharp folder) for even more security. From 092bcac56956b81b75389de9899c80218e720744 Mon Sep 17 00:00:00 2001 From: Sebastian N Date: Tue, 15 Aug 2017 21:41:16 +0200 Subject: [PATCH 05/40] Fix localization error in SwaggerUtil (#303) Pluralization is only supported for the English language. --- PSSwagger/SwaggerUtils.psm1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PSSwagger/SwaggerUtils.psm1 b/PSSwagger/SwaggerUtils.psm1 index e58af78..aa6d041 100644 --- a/PSSwagger/SwaggerUtils.psm1 +++ b/PSSwagger/SwaggerUtils.psm1 @@ -28,7 +28,7 @@ if(-not (Get-OperatingSystemInfo).IsCore) Add-Type -AssemblyName System.Data.Entity.Design } - $script:PluralizationService = [System.Data.Entity.Design.PluralizationServices.PluralizationService]::CreateService([System.Globalization.CultureInfo]::CurrentCulture) + $script:PluralizationService = [System.Data.Entity.Design.PluralizationServices.PluralizationService]::CreateService([System.Globalization.CultureInfo]::GetCultureInfo('en-US')) $PluralToSingularMapPath = Join-Path -Path $PSScriptRoot -ChildPath 'PluralToSingularMap.json' if(Test-Path -Path $PluralToSingularMapPath -PathType Leaf) @@ -1771,4 +1771,4 @@ function Get-PowerShellCodeGenSettings { } } } -} \ No newline at end of file +} From 0f700d3e4840b6bc7e45c48b4e8f93747520f0ea Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Thu, 17 Aug 2017 11:59:22 -0700 Subject: [PATCH 06/40] Ensure $oDataQuery expression is generated properly when a global parameter is referenced in more than one swagger operations with or without x-ms-odata extension. (#307) --- PSSwagger/SwaggerUtils.psm1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PSSwagger/SwaggerUtils.psm1 b/PSSwagger/SwaggerUtils.psm1 index aa6d041..78b9cba 100644 --- a/PSSwagger/SwaggerUtils.psm1 +++ b/PSSwagger/SwaggerUtils.psm1 @@ -941,7 +941,8 @@ function Get-ParamType { # #<...>/parameters/ $GlobalParameters = $SwaggerDict['Parameters'] - $GlobalParamDetails = $GlobalParameters[$ReferenceParts[-1]] + # Cloning the common parameters object so that some values can be updated without impacting other operations. + $GlobalParamDetails = $GlobalParameters[$ReferenceParts[-1]].Clone() # Get the definition name of the global parameter so that 'New-Object' can be generated. if($GlobalParamDetails.Type -and $GlobalParamDetails.Type -match '[.]') { From 090b30423f4fb11a838a6ea7dd66533d8f1970b2 Mon Sep 17 00:00:00 2001 From: brywang-msft Date: Tue, 22 Aug 2017 11:09:30 -0700 Subject: [PATCH 07/40] Alpha 1 of test server (#309) * Alpha 1 of test server --- .../Build-PowerShellModule.ps1 | 166 ----- ...PSSwagger.LiveTestFramework.Resources.psd1 | 15 - .../PSSwagger.LiveTestFramework.psm1 | 569 ------------------ .../PSSwagger.LiveTestFramework.Build.psd1} | 23 +- .../PSSwagger.LiveTestFramework.Build.psm1 | 342 +++++++++++ PSSwagger.LiveTestFramework/readme.md | 12 - .../src/ConvertFrom-CSharpFiles.ps1 | 7 - .../src/ConvertTo-CSharpFiles.ps1 | 8 - .../PSSwagger.LTF.ConsoleServer.csproj | 10 +- .../PSSwagger.LTF.ConsoleServer/Program.cs | 133 +++- .../PSSwagger.LTF.ConsoleServer.csproj | 10 + .../Interfaces/IInputPipe.cs | 15 +- .../Interfaces/IOutputPipe.cs | 9 +- .../src/PSSwagger.LTF.IO.Lib/JsonRpcPipe.cs | 215 +++++++ .../NamedPipeClient.cs | 78 ++- .../NamedPipeServer.cs | 89 ++- .../src/PSSwagger.LTF.IO.Lib/NullPipe.cs | 51 ++ .../PSSwagger.LTF.IO.Lib.csproj | 11 + .../PSSwagger.LTF.IO.Lib/StandardInputPipe.cs | 72 +++ .../StandardOutputPipe.cs | 20 +- .../src/PSSwagger.LTF.IO.Lib/nuget.config | 13 + .../vs-csproj/PSSwagger.LTF.IO.Lib.csproj | 67 +++ .../vs-csproj/PSSwagger.LTF.IO.Lib.sln | 22 + .../vs-csproj/packages.config | 4 + .../Converters/DynamicTypedObjectConverter.cs | 133 ++++ .../Converters/LiveTestRequestConverter.cs | 185 ++++++ .../Credentials/AzureCredentialProvider.cs | 86 +++ .../Credentials/LiveTestCredentialFactory.cs | 128 ++++ .../Exceptions/CommandFailedException.cs | 46 ++ .../InvalidTestCredentialsException.cs | 17 + .../ModulePathNotRootedException.cs | 29 + .../src/PSSwagger.LTF.Lib/GeneratedModule.cs | 53 -- .../src/PSSwagger.LTF.Lib/IO/JsonBlockPipe.cs | 76 --- .../src/PSSwagger.LTF.Lib/IO/NullPipe.cs | 42 -- .../PSSwagger.LTF.Lib/IO/StandardInputPipe.cs | 38 -- .../Interfaces/ICommandBuilder.cs | 17 +- .../Interfaces/ICredentialProvider.cs | 23 + .../Interfaces/IRunspaceManager.cs | 20 +- .../Interfaces/IServiceTracer.cs | 19 + .../PSSwagger.LTF.Lib/Json/JsonPathFinder.cs | 93 +++ .../Json/JsonQueryBuilder.cs | 80 +++ .../src/PSSwagger.LTF.Lib/LiveTestServer.cs | 154 ++++- .../src/PSSwagger.LTF.Lib/Logging/Logger.cs | 27 +- .../PSSwagger.LTF.Lib/Messages/JsonRpcBase.cs | 3 + .../Messages/LiveTestCredentials.cs | 21 + .../Messages/LiveTestError.cs | 8 + .../Messages/LiveTestRequest.cs | 107 ++++ .../Messages/LiveTestResponse.cs | 3 + .../Messages/LiveTestResult.cs | 3 + .../Models/CommandExecutionResult.cs | 66 ++ .../Models/GeneratedModule.cs | 358 +++++++++++ .../PSSwagger.LTF.Lib/Models/OperationData.cs | 35 ++ .../PSSwagger.LTF.Lib/Models/ParameterData.cs | 26 + .../Models/RuntimeTypeData.cs | 34 ++ .../PSSwagger.LTF.Lib.csproj | 18 +- .../PowerShell/PowerShellCommand.cs | 52 +- .../PowerShell/PowerShellRunspace.cs | 179 +++++- .../ClientRuntimeServiceTracer.cs | 77 +++ .../ServiceTracing/ServiceTracingManager.cs | 51 ++ .../vs-csproj/PSSwagger.LTF.Lib.csproj | 83 ++- .../vs-csproj/PSSwagger.LTF.Lib.sln | 6 + .../PSSwagger.LTF.Lib/vs-csproj/app.config | 11 + .../vs-csproj/packages.config | 2 + .../AzureCredentialProviderTests.cs | 89 +++ .../DynamicTypedObjectConverterTests.cs | 110 ++++ .../GeneratedModuleTests.cs | 429 ++++++++++++- .../JsonBlockPipeTests.cs | 131 ---- .../JsonPathFinderTests.cs | 104 ++++ .../JsonQueryBuilderTests.cs | 63 ++ .../JsonRpcPipeTests.cs | 191 ++++++ .../LiveTestCredentialFactoryTests.cs | 323 ++++++++++ .../LiveTestRequestConverterTests.cs | 266 ++++++++ .../LiveTestRequestTests.cs | 321 ++++++++++ .../LiveTestServerTests.cs | 43 +- .../Mocks/CommandBasedCredentialProvider.cs | 24 + .../Mocks/MockCommandBuilder.cs | 53 +- .../Mocks/MockGeneratedModule.cs | 11 +- .../Mocks/MockJsonPathFinder.cs | 65 ++ .../Mocks/MockRunspaceManager.cs | 37 +- .../Mocks/MockServiceTracer.cs | 25 + .../Mocks/MockServiceTracingManager.cs | 47 ++ .../Mocks/MockTestCredentialFactory.cs | 30 + .../Mocks/ParameterBasedCredentialProvider.cs | 22 + .../Mocks/StringPipe.cs | 65 +- .../Mocks/TestBlockPipe.cs | 27 +- .../Mocks/XUnitOutputPipe.cs | 70 +++ .../PSSwagger.LTF.Lib.UnitTests.csproj | 19 +- .../PowerShellCommandTests.cs | 93 +++ .../PowerShellRunspaceTests.cs | 223 +++++++ .../BasicHappyPath/BasicHappyPath.psd1 | 21 + .../BasicHappyPath/BasicHappyPath.psm1 | 29 + .../InvalidManifest/InvalidManifest.psd1 | 20 + .../MissingParameterSet.psd1 | 20 + .../MissingParameterSet.psm1 | 28 + .../PSSwagger.LTF.Lib.UnitTests/nuget.config | 8 +- .../PSSwagger.LTF.Lib.UnitTests.csproj | 60 +- .../vs-csproj/PSSwagger.LTF.Lib.UnitTests.sln | 12 + .../vs-csproj/app.config | 11 + .../vs-csproj/packages.config | 2 + .../PSSwagger.LiveTestFramework.Tests.psd1 | 7 +- .../PSSwagger.LiveTestFramework.Tests.psm1 | 114 +--- .../test/Pester/Unit.Tests.ps1 | 7 - 102 files changed, 6068 insertions(+), 1522 deletions(-) delete mode 100644 PSSwagger.LiveTestFramework/Build-PowerShellModule.ps1 delete mode 100644 PSSwagger.LiveTestFramework/PSSwagger.LiveTestFramework.Resources.psd1 delete mode 100644 PSSwagger.LiveTestFramework/PSSwagger.LiveTestFramework.psm1 rename PSSwagger.LiveTestFramework/{PSSwagger.LiveTestFramework.psd1 => build/PSSwagger.LiveTestFramework.Build.psd1} (53%) create mode 100644 PSSwagger.LiveTestFramework/build/PSSwagger.LiveTestFramework.Build.psm1 delete mode 100644 PSSwagger.LiveTestFramework/src/ConvertFrom-CSharpFiles.ps1 delete mode 100644 PSSwagger.LiveTestFramework/src/ConvertTo-CSharpFiles.ps1 rename PSSwagger.LiveTestFramework/src/{PSSwagger.LTF.Lib => PSSwagger.LTF.IO.Lib}/Interfaces/IInputPipe.cs (68%) rename PSSwagger.LiveTestFramework/src/{PSSwagger.LTF.Lib => PSSwagger.LTF.IO.Lib}/Interfaces/IOutputPipe.cs (78%) create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/JsonRpcPipe.cs rename PSSwagger.LiveTestFramework/src/{PSSwagger.LTF.Lib/IO => PSSwagger.LTF.IO.Lib}/NamedPipeClient.cs (67%) rename PSSwagger.LiveTestFramework/src/{PSSwagger.LTF.Lib/IO => PSSwagger.LTF.IO.Lib}/NamedPipeServer.cs (59%) create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/NullPipe.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/PSSwagger.LTF.IO.Lib.csproj create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/StandardInputPipe.cs rename PSSwagger.LiveTestFramework/src/{PSSwagger.LTF.Lib/IO => PSSwagger.LTF.IO.Lib}/StandardOutputPipe.cs (56%) create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/nuget.config create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/vs-csproj/PSSwagger.LTF.IO.Lib.csproj create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/vs-csproj/PSSwagger.LTF.IO.Lib.sln create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/vs-csproj/packages.config create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Converters/DynamicTypedObjectConverter.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Converters/LiveTestRequestConverter.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Credentials/AzureCredentialProvider.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Credentials/LiveTestCredentialFactory.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Exceptions/CommandFailedException.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Exceptions/InvalidTestCredentialsException.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Exceptions/ModulePathNotRootedException.cs delete mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/GeneratedModule.cs delete mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/IO/JsonBlockPipe.cs delete mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/IO/NullPipe.cs delete mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/IO/StandardInputPipe.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/ICredentialProvider.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/IServiceTracer.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Json/JsonPathFinder.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Json/JsonQueryBuilder.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestCredentials.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/CommandExecutionResult.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/GeneratedModule.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/OperationData.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/ParameterData.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/RuntimeTypeData.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/ServiceTracing/ClientRuntimeServiceTracer.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/ServiceTracing/ServiceTracingManager.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/app.config create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/AzureCredentialProviderTests.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/DynamicTypedObjectConverterTests.cs delete mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonBlockPipeTests.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonPathFinderTests.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonQueryBuilderTests.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonRpcPipeTests.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestCredentialFactoryTests.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestRequestConverterTests.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestRequestTests.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/CommandBasedCredentialProvider.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockJsonPathFinder.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockServiceTracer.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockServiceTracingManager.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockTestCredentialFactory.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/ParameterBasedCredentialProvider.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/XUnitOutputPipe.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/PowerShellCommandTests.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/PowerShellRunspaceTests.cs create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/BasicHappyPath/BasicHappyPath.psd1 create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/BasicHappyPath/BasicHappyPath.psm1 create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/InvalidManifest/InvalidManifest.psd1 create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/MissingParameterSet/MissingParameterSet.psd1 create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/MissingParameterSet/MissingParameterSet.psm1 create mode 100644 PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/app.config delete mode 100644 PSSwagger.LiveTestFramework/test/Pester/Unit.Tests.ps1 diff --git a/PSSwagger.LiveTestFramework/Build-PowerShellModule.ps1 b/PSSwagger.LiveTestFramework/Build-PowerShellModule.ps1 deleted file mode 100644 index ec1fe21..0000000 --- a/PSSwagger.LiveTestFramework/Build-PowerShellModule.ps1 +++ /dev/null @@ -1,166 +0,0 @@ -<# -.DESCRIPTION - Builds the PSSwagger.LiveTestFramework module. -#> -[CmdletBinding()] -param( - [Parameter(Mandatory=$true)] - [string] - $OutputDirectory -) - -<# -.DESCRIPTION - Remove the output directory if it exists then create it. -#> -function Prepare-OutputDirectory { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string] - $OutputDirectory - ) - - if (Test-Path -Path $OutputDirectory) { - Write-Verbose -Message "Cleaning module directory" - Remove-Item -Path $OutputDirectory -ErrorAction SilentlyContinue -Recurse -Force - } - - Write-Verbose -Message "Creating module directory" - $null = New-Item -Path $OutputDirectory -ItemType Container -Force -} - -<# -.DESCRIPTION - Converts all source C# files (.cs) to code files (.Code.ps1). -#> -function Convert-CSharpFiles { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string] - $SrcPath - ) - - Write-Verbose -Message "Converting all C# files to code files" - - & $SrcPath\ConvertFrom-CSharpFiles.ps1 -} - -<# -.DESCRIPTION - Copies PowerShell module files to the output directory (.psd1 and .psm1) -#> -function Copy-PowerShellModuleFiles { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string] - $OutputDirectory, - - [Parameter(Mandatory=$true)] - [string] - $RepoPath - ) - - Copy-Item -Path (Join-Path -Path $repoPath -ChildPath 'PSSwagger.LiveTestFramework.psd1') -Destination (Join-Path -Path $OutputDirectory -ChildPath 'PSSwagger.LiveTestFramework.psd1') -Verbose - Copy-Item -Path (Join-Path -Path $repoPath -ChildPath 'PSSwagger.LiveTestFramework.psm1') -Destination (Join-Path -Path $OutputDirectory -ChildPath 'PSSwagger.LiveTestFramework.psm1') -Verbose -} - -<# -.DESCRIPTION - Copies all code files in the correct directory structure to the output directory. -#> -function Copy-CodeProject { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string] - $OutputDirectory, - - [Parameter(Mandatory=$true)] - [string] - $SrcPath, - - [Parameter(Mandatory=$true)] - [string] - $Project - ) - - Get-ChildItem -Path (Join-Path -Path $srcPath -ChildPath $Project | Join-Path -ChildPath '*.Code.ps1') -File | ForEach-Object { - $dir = $_.Directory - $subPath = "\" - while ($dir -and $dir.FullName -ne $srcPath) { - $subPath = Join-Path $dir.Name -ChildPath $dirName - $dir = $dir.Parent - } - $outputDir = Join-Path -Path $OutputDirectory -ChildPath 'src' | Join-Path -ChildPath $subPath - if (-not (Test-Path -Path $outputDir -PathType Container)) { - $null = New-Item -Path $outputDir -ItemType Container -Force - } - $outputFilePath = Join-Path -Path $outputDir -ChildPath $_.Name - - Copy-Item -Path $_.FullName -Destination $outputFilePath -Verbose - } -} - -<# -.DESCRIPTION - Retrieves the release info file. -#> -function Get-ReleaseInfo { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string] - $RepoPath - ) - - Get-Content -Path (Join-Path -Path $RepoPath -ChildPath 'release.json') | ConvertFrom-Json -} - -<# -.DESCRIPTION - Replaces dynamic info in the module manifest, like ModuleVersion. -#> -function Set-ModuleManifestInfo { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [object] - $Release, - - [Parameter(Mandatory=$true)] - [string] - $OutputDirectory - ) - - $psd1Path = Join-Path -Path $OutputDirectory -ChildPath 'PSSwagger.LiveTestFramework.psd1' - $manifestContent = Get-Content -Path $psd1Path - $manifestContent = $manifestContent.Replace("ModuleVersion = '9.9.9'", "ModuleVersion = '$($Release.version)'") - $manifestContent | Out-File -FilePath $psd1Path -} - -$srcPath = Join-Path -Path $PSScriptRoot -ChildPath 'src' -$release = Get-ReleaseInfo -RepoPath $PSScriptRoot -$moduleOutputDirectory = Join-Path -Path $OutputDirectory -ChildPath 'PSSwagger.LiveTestFramework' | Join-Path -ChildPath $release.version -Prepare-OutputDirectory -OutputDirectory $moduleOutputDirectory -Convert-CSharpFiles -SrcPath $srcPath - -# Copy PowerShell files -Copy-PowerShellModuleFiles -OutputDirectory $moduleOutputDirectory -RepoPath $PSScriptRoot - -# Copy code files -# This should ignore csproj, intermediate cs files -# Currently this is a non-recursive search for *.Code.ps1 files -Copy-CodeProject -OutputDirectory $moduleOutputDirectory -SrcPath $srcPath -Project 'PSSwagger.LTF.Lib' -Copy-CodeProject -OutputDirectory $moduleOutputDirectory -SrcPath $srcPath -Project 'PSSwagger.LTF.ConsoleServer' - -Set-ModuleManifestInfo -Release $release -OutputDirectory $moduleOutputDirectory - -# This currently only works in some cases. -if (-not $?) { - Write-Host "Module packaging completed with errors." -BackgroundColor DarkRed -} else { - Write-Host "Module packaged successfully." -BackgroundColor DarkGreen -} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/PSSwagger.LiveTestFramework.Resources.psd1 b/PSSwagger.LiveTestFramework/PSSwagger.LiveTestFramework.Resources.psd1 deleted file mode 100644 index 2147913..0000000 --- a/PSSwagger.LiveTestFramework/PSSwagger.LiveTestFramework.Resources.psd1 +++ /dev/null @@ -1,15 +0,0 @@ -######################################################################################### -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# Localized PSSwagger.LiveTestFramework.Resources.psd1 -# -######################################################################################### - -ConvertFrom-StringData @' -###PSLOC - - StartingConsoleServerFromPath=Starting console server from path: {0} - CodeFileSignatureValidationFailed=Failed to validate the signature of file '{0}'. -###PSLOC -'@ \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/PSSwagger.LiveTestFramework.psm1 b/PSSwagger.LiveTestFramework/PSSwagger.LiveTestFramework.psm1 deleted file mode 100644 index 69e18fb..0000000 --- a/PSSwagger.LiveTestFramework/PSSwagger.LiveTestFramework.psm1 +++ /dev/null @@ -1,569 +0,0 @@ -Microsoft.PowerShell.Core\Set-StrictMode -Version Latest -Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename PSSwagger.LiveTestFramework.Resources.psd1 - -<# -.DESCRIPTION - Compile the PSSwagger.LiveTestFramework assemblies if required, then start the server and return the process. The compiled assemblies can be found in the bin folder. - -.PARAMETER BootstrapConsent - User has consented to automatically download package dependencies. - -.PARAMETER NoNewWindow - Don't create a new window for the server. Only available on full PowerShell. -#> -function Start-PSSwaggerLiveTestServer { - [CmdletBinding()] - param( - [Parameter(Mandatory=$false)] - [switch] - $BootstrapConsent, - - [Parameter(Mandatory=$false)] - [switch] - $NoNewWindow - ) - - $osInfo = PSSwaggerUtility\Get-OperatingSystemInfo - if ($osInfo.IsCore) { - $clr = 'coreclr' - } else { - $clr = 'fullclr' - } - - $outputDirectory = Join-Path -Path $PSScriptRoot -ChildPath "bin" | Join-Path -ChildPath $clr - - # Use a friendlier but longer name - $exeName = "PSSwagger.LiveTestFramework.ConsoleServer.exe" - $exePath = Join-Path -Path $outputDirectory -ChildPath $exeName - $addTypeParams = @{ - BootstrapConsent = $BootstrapConsent - OutputDirectory = $outputDirectory - } - - # Check if we have access to write the compiled binaries - try { - "" | out-file $exePath -append - $null = Remove-Item -Path $exePath -Force - $addTypeParams['SaveAssembly'] = $true - } catch { } - - if (-not (Test-Path -Path (Join-Path -Path $outputDirectory -ChildPath 'PSSwagger.LTF.Lib.dll'))) { - Add-PSSwaggerLiveTestLibType @addTypeParams - } - - if (-not (Test-Path -Path $exePath)) { - $addTypeParams['OutputFileName'] = $exeName - Add-PSSwaggerLiveTestServerType @addTypeParams - } - - # Start server.exe - Write-Verbose -Message ($LocalizedData.StartingConsoleServerFromPath -f ($exePath)) - $startProcessParams = @{ - FilePath = $exePath - PassThru = $true - } - - if (-not $osInfo.IsCore -and $NoNewWindow) { - $startProcessParams['NoNewWindow'] = $true - } - - Start-Process @startProcessParams -} - -<# -.DESCRIPTION - Compile the PSSwagger LiveTestFramework library assembly for the current CLR. - -.PARAMETER OutputDirectory - Output directory to place compiled assembly. - -.PARAMETER OutputFileName - Name of output assembly. - -.PARAMETER DebugSymbolDirectory - If the assembly is saved to disk, the code used to generate it will be saved here. If the PDB exists, it will also be saved here. - -.PARAMETER AllUsers - Download package dependencies to PSSwagger global location. - -.PARAMETER BootstrapConsent - User has consented to automatically download package dependencies. - -.PARAMETER SaveAssembly - Save output assembly. By default, generates in-memory. -#> -function Add-PSSwaggerLiveTestLibType { - [CmdletBinding()] - param( - [Parameter(Mandatory=$false)] - [string] - $OutputDirectory, - - [Parameter(Mandatory=$false)] - [string] - $OutputFileName = "PSSwagger.LTF.Lib.dll", - - [Parameter(Mandatory=$false)] - [string] - $DebugSymbolDirectory, - - [Parameter(Mandatory=$false)] - [switch] - $AllUsers, - - [Parameter(Mandatory=$false)] - [switch] - $BootstrapConsent, - - [Parameter(Mandatory=$false)] - [switch] - $SaveAssembly - ) - - $osInfo = PSSwaggerUtility\Get-OperatingSystemInfo - $codeFilePaths = @() - $systemRefs = @() - $packageDependencies = @{} - # TODO: Fill in system and package refs for both CLR - if ($osInfo.IsCore) { - $clr = 'coreclr' - # TODO: Fill in system and package refs for core CLR - $systemRefs += 'System.dll' - } else { - $clr = 'fullclr' - # TODO: Fill in system and package refs for full CLR - $systemRefs += 'System.dll' - } - - if (-not $OutputDirectory) { - $OutputDirectory = Join-Path -Path $PSScriptRoot -ChildPath "bin" | Join-Path -ChildPath $clr - } - - foreach ($item in (Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath "src" | Join-Path -ChildPath "PSSwagger.LTF.Lib" | Join-Path -ChildPath "*.Code.ps1") -Recurse -File)) { - if ($osInfo.IsWindows) { - $sig = Get-AuthenticodeSignature -FilePath $item.FullName - if (('Valid' -ne $sig.Status) -and ('NotSigned' -ne $sig.Status) -and ('UnknownError' -ne $sig.Status)) { - throw ($LocalizedData.CodeFileSignatureValidationFailed -f ($item.FullName)) - } - } - - $codeFilePaths += $item.FullName - } - - Add-PSSwaggerLiveTestTypeGeneric -CodeFilePaths $codeFilePaths -OutputDirectory $OutputDirectory -OutputFileName $OutputFileName ` - -DebugSymbolDirectory $DebugSymbolDirectory -SystemReferences $systemRefs ` - -PackageDependencies $PackageDependencies -AllUsers:$AllUsers ` - -BootstrapConsent:$BootstrapConsent -SaveAssembly:$SaveAssembly -} - -<# -.DESCRIPTION - Compile the PSSwagger LiveTestFramework Console Server assembly for the current CLR. - -.PARAMETER OutputDirectory - Output directory to place compiled assembly. - -.PARAMETER OutputFileName - Name of output assembly. - -.PARAMETER DebugSymbolDirectory - If the assembly is saved to disk, the code used to generate it will be saved here. If the PDB exists, it will also be saved here. - -.PARAMETER AllUsers - Download package dependencies to PSSwagger global location. - -.PARAMETER BootstrapConsent - User has consented to automatically download package dependencies. - -.PARAMETER SaveAssembly - Save output assembly. By default, generates in-memory. -#> -function Add-PSSwaggerLiveTestServerType { - [CmdletBinding()] - param( - [Parameter(Mandatory=$false)] - [string] - $OutputDirectory, - - [Parameter(Mandatory=$false)] - [string] - $OutputFileName = "PSSwagger.LTF.ConsoleServer.exe", - - [Parameter(Mandatory=$false)] - [string] - $DebugSymbolDirectory, - - [Parameter(Mandatory=$false)] - [switch] - $AllUsers, - - [Parameter(Mandatory=$false)] - [switch] - $BootstrapConsent, - - [Parameter(Mandatory=$false)] - [switch] - $SaveAssembly - ) - - $osInfo = PSSwaggerUtility\Get-OperatingSystemInfo - $codeFilePaths = @() - $systemRefs = @() - $packageDependencies = @{} - # TODO: Fill in system and package refs for both CLR - if ($osInfo.IsCore) { - $clr = 'coreclr' - # TODO: Fill in system and package refs for core CLR - $systemRefs += 'System.dll' - } else { - $clr = 'fullclr' - # TODO: Fill in system and package refs for full CLR - $systemRefs += 'System.dll' - } - - if (-not $OutputDirectory) { - $OutputDirectory = Join-Path -Path $PSScriptRoot -ChildPath "bin" | Join-Path -ChildPath $clr - } - - foreach ($item in (Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath "src" | Join-Path -ChildPath "PSSwagger.LTF.ConsoleServer" | Join-Path -ChildPath "*.Code.ps1") -Recurse -File)) { - if ($osInfo.IsWindows) { - $sig = Get-AuthenticodeSignature -FilePath $item.FullName - if (('Valid' -ne $sig.Status) -and ('NotSigned' -ne $sig.Status) -and ('UnknownError' -ne $sig.Status)) { - throw ($LocalizedData.CodeFileSignatureValidationFailed -f ($item.FullName)) - } - } - - $codeFilePaths += $item.FullName - } - - Add-PSSwaggerLiveTestTypeGeneric -CodeFilePaths $codeFilePaths -OutputDirectory $OutputDirectory -OutputFileName $OutputFileName ` - -DebugSymbolDirectory $DebugSymbolDirectory -SystemReferences $systemRefs ` - -PackageDependencies $PackageDependencies -AllUsers:$AllUsers ` - -BootstrapConsent:$BootstrapConsent -SaveAssembly:$SaveAssembly -OutputType 'ConsoleApplication' -} - -<# -.DESCRIPTION - Compile a generic PSSwagger LiveTestFramework assembly. - -.PARAMETER CodeFilePaths - Full paths to the code files to compile. - -.PARAMETER OutputDirectory - Output directory to place compiled assembly. - -.PARAMETER OutputFileName - Name of output assembly. - -.PARAMETER DebugSymbolDirectory - If the assembly is saved to disk, the code used to generate it will be saved here. If the PDB exists, it will also be saved here. - -.PARAMETER SystemReferences - Framework references to add. - -.PARAMETER PackageDependencies - Package dependencies required. - -.PARAMETER AllUsers - Download package dependencies to PSSwagger global location. - -.PARAMETER BootstrapConsent - User has consented to automatically download package dependencies. - -.PARAMETER SaveAssembly - Save output assembly. By default, generates in-memory. -#> -function Add-PSSwaggerLiveTestTypeGeneric { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string[]] - $CodeFilePaths, - - [Parameter(Mandatory=$true)] - [string] - $OutputDirectory, - - [Parameter(Mandatory=$false)] - [string] - $OutputFileName, - - [Parameter(Mandatory=$false)] - [ValidateSet("ConsoleApplication","Library")] - [string] - $OutputType = 'Library', - - [Parameter(Mandatory=$false)] - [string] - $DebugSymbolDirectory, - - [Parameter(Mandatory=$true)] - [string[]] - $SystemReferences, - - [Parameter(Mandatory=$true)] - [hashtable] - $PackageDependencies, - - [Parameter(Mandatory=$false)] - [switch] - $AllUsers, - - [Parameter(Mandatory=$false)] - [switch] - $BootstrapConsent, - - [Parameter(Mandatory=$false)] - [switch] - $SaveAssembly - ) - - if ($OutputDirectory -and -not (Test-Path -Path $OutputDirectory -PathType Container)) { - $null = New-Item -Path $OutputDirectory -ItemType Directory -Force - } - - if ($DebugSymbolDirectory -and -not (Test-Path -Path $DebugSymbolDirectory -PathType Container)) { - $null = New-Item -Path $DebugSymbolDirectory -ItemType Directory -Force - } - - $GetPSSwaggerAddTypeParametersParams = @{ - Path = $CodeFilePaths - OutputDirectory = $OutputDirectory - AllUsers = $AllUsers - BootstrapConsent = $BootstrapConsent - TestBuild = $true - SymbolPath = $DebugSymbolDirectory - PackageDependencies = $PackageDependencies - FileReferences = $SystemReferences - OutputType = $OutputType - } - - if ($SaveAssembly) { - $GetPSSwaggerAddTypeParametersParams['OutputAssemblyName'] = $OutputFileName - } - - $addTypeParamsResult = Get-PSSwaggerAddTypeParameters @GetPSSwaggerAddTypeParametersParams - - if ($addTypeParamsResult['ResolvedPackageReferences']) { - foreach ($extraRef in $addTypeParamsResult['ResolvedPackageReferences']) { - if ($SaveAssembly) { - $null = Copy-Item -Path $extraRef -Destination (Join-Path -Path $OutputDirectory -ChildPath (Split-Path -Path $extraRef -Leaf)) -Force - } - - Add-Type -Path $extraRef -ErrorAction Ignore - } - } - - if ($addTypeParamsResult['SourceFileName']) { - # A source code file is expected to exist - # Emit the created source code - $addTypeParamsResult['SourceCode'] | Out-File -FilePath $addTypeParamsResult['SourceFileName'] - } - - $addTypeParams = $addTypeParamsResult['Params'] - Add-Type @addTypeParams - - # Copy the PDB to the symbol path if specified - if ($addTypeParamsResult['OutputAssemblyPath']) { - # Verify result of Add-Type - if ((-not (Test-Path -Path $addTypeParamsResult['OutputAssemblyPath'])) -or ((Get-Item -Path $addTypeParamsResult['OutputAssemblyPath']).Length -eq 0kb)) { - return $false - } - - $outputAssemblyItem = Get-Item -Path $addTypeParamsResult['OutputAssemblyPath'] - $OutputPdbName = "$($outputAssemblyItem.BaseName).pdb" - if ($DebugSymbolDirectory -and (Test-Path -Path (Join-Path -Path $OutputDirectory -ChildPath $OutputPdbName))) { - $null = Copy-Item -Path (Join-Path -Path $OutputDirectory -ChildPath $OutputPdbName) -Destination (Join-Path -Path $DebugSymbolDirectory -ChildPath $OutputPdbName) - } - } -} - -<# -.DESCRIPTION - Compiles AutoRest generated C# code using the framework of the current PowerShell process. - -.PARAMETER Path - All *.Code.ps1 C# files to compile. - -.PARAMETER OutputDirectory - Full Path to output directory. - -.PARAMETER OutputAssemblyName - Optional assembly file name. - -.PARAMETER AllUsers - User has specified to install package dependencies to global location. - -.PARAMETER BootstrapConsent - User has consented to bootstrap dependencies. - -.PARAMETER TestBuild - Build binaries for testing (disable compiler optimizations, enable full debug information). - -.PARAMETER SymbolPath - Path to store PDB file and matching source file. - -.PARAMETER PackageDependencies - Map of package dependencies to add as referenced assemblies but don't exist on disk. - -.PARAMETER FileReferences - Compilation references that exist on disk. - -.PARAMETER PreprocessorDirectives - Preprocessor directives to add to the top of the combined source code file. -#> -function Get-PSSwaggerAddTypeParameters { - [CmdletBinding()] - param( - [Parameter(Mandatory=$false)] - [string[]] - $Path, - - [Parameter(Mandatory=$false)] - [AllowEmptyString()] - [string] - $OutputDirectory, - - [Parameter(Mandatory=$false)] - [AllowEmptyString()] - [string] - $OutputAssemblyName, - - [Parameter(Mandatory=$false)] - [switch] - $AllUsers, - - [Parameter(Mandatory=$false)] - [switch] - $BootstrapConsent, - - [Parameter(Mandatory=$false)] - [switch] - $TestBuild, - - [Parameter(Mandatory=$false)] - [string] - $SymbolPath, - - [Parameter(Mandatory=$false)] - [ValidateSet("ConsoleApplication","Library")] - [string] - $OutputType = 'Library', - - [Parameter(Mandatory=$false)] - [hashtable] - $PackageDependencies, - - [Parameter(Mandatory=$false)] - [string[]] - $FileReferences, - - [Parameter(Mandatory=$false)] - [string[]] - $PreprocessorDirectives - ) - - $resultObj = @{ - # The add type parameters to use - Params = $null - # Full path to resolved package reference assemblies - ResolvedPackageReferences = @() - # The expected output assembly full path - OutputAssemblyPath = $null - # The actual source to be emitted - SourceCode = $null - # The file name the returned params expect to exist, if required - SourceFileName = $null - } - - # Resolve package dependencies - $extraRefs = @() - if ($PackageDependencies) { - foreach ($entry in ($PackageDependencies.GetEnumerator() | Sort-Object { $_.Value.LoadOrder })) { - $reference = $entry.Value - $resolvedRef = Get-PSSwaggerDependency -PackageName $reference.PackageName ` - -RequiredVersion $reference.RequiredVersion ` - -References $reference.References ` - -Framework $reference.Framework ` - -AllUsers:$AllUsers -Install -BootstrapConsent:$BootstrapConsent - $extraRefs += $resolvedRef - $resultObj['ResolvedPackageReferences'] += $resolvedRef - } - } - - # Combine the possibly authenticode-signed *.Code.ps1 files into a single file, adding preprocessor directives to the beginning if specified - $srcContent = @() - $srcContent += $Path | ForEach-Object { "// File $_"; Remove-AuthenticodeSignatureBlock -Path $_ } - if ($PreprocessorDirectives) { - foreach ($preprocessorDirective in $PreprocessorDirectives) { - $srcContent = ,$preprocessorDirective + $srcContent - } - } - - $oneSrc = $srcContent -join "`n" - $resultObj['SourceCode'] = $oneSrc - if ($SymbolPath) { - if ($OutputAssemblyName) { - $OutputAssemblyBaseName = [System.IO.Path]::GetFileNameWithoutExtension("$OutputAssemblyName") - $resultObj['SourceFileName'] = Join-Path -Path $SymbolPath -ChildPath "Generated.$OutputAssemblyBaseName.cs" - } else { - $resultObj['SourceFileName'] = Join-Path -Path $SymbolPath -ChildPath "Generated.cs" - } - - $addTypeParams = @{ - Path = $resultObj['SourceFileName'] - WarningAction = 'Ignore' - } - } else { - $addTypeParams = @{ - TypeDefinition = $oneSrc - Language = "CSharp" - WarningAction = 'Ignore' - } - } - - if (-not (Get-OperatingSystemInfo).IsCore) { - $compilerParameters = New-Object -TypeName System.CodeDom.Compiler.CompilerParameters - $compilerParameters.CompilerOptions = '/debug:full' - if ($TestBuild) { - $compilerParameters.IncludeDebugInformation = $true - } else { - $compilerParameters.CompilerOptions += ' /optimize+' - } - - if ($OutputType -eq 'ConsoleApplication') { - $compilerParameters.GenerateExecutable = $true - } - - $compilerParameters.WarningLevel = 3 - foreach ($ref in ($FileReferences + $extraRefs)) { - $null = $compilerParameters.ReferencedAssemblies.Add($ref) - } - $addTypeParams['CompilerParameters'] = $compilerParameters - } else { - $addTypeParams['ReferencedAssemblies'] = ($FileReferences + $extraRefs) - if ($OutputType -eq 'ConsoleApplication') { - $addTypeParams['ReferencedAssemblies'] = $OutputType - } - } - - $OutputPdbName = '' - if ($OutputAssemblyName) { - $OutputAssembly = Join-Path -Path $OutputDirectory -ChildPath $OutputAssemblyName - $resultObj['OutputAssemblyPath'] = $OutputAssembly - if ($addTypeParams.ContainsKey('CompilerParameters')) { - $addTypeParams['CompilerParameters'].OutputAssembly = $OutputAssembly - } else { - $addTypeParams['OutputAssembly'] = $OutputAssembly - } - } else { - if ($addTypeParams.ContainsKey('CompilerParameters')) { - $addTypeParams['CompilerParameters'].GenerateInMemory = $true - } - } - - $resultObj['Params'] = $addTypeParams - return $resultObj -} - -Export-ModuleMember -Function Start-PSSwaggerLiveTestServer,Add-PSSwaggerLiveTestLibType,Add-PSSwaggerLiveTestServerType \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/PSSwagger.LiveTestFramework.psd1 b/PSSwagger.LiveTestFramework/build/PSSwagger.LiveTestFramework.Build.psd1 similarity index 53% rename from PSSwagger.LiveTestFramework/PSSwagger.LiveTestFramework.psd1 rename to PSSwagger.LiveTestFramework/build/PSSwagger.LiveTestFramework.Build.psd1 index 485ba1e..c33dba3 100644 --- a/PSSwagger.LiveTestFramework/PSSwagger.LiveTestFramework.psd1 +++ b/PSSwagger.LiveTestFramework/build/PSSwagger.LiveTestFramework.Build.psd1 @@ -1,28 +1,27 @@ @{ - RootModule = 'PSSwagger.LiveTestFramework.psm1' - ModuleVersion = '9.9.9' - GUID = '026fa119-121b-4816-9556-5a306bebb963' + RootModule = 'PSSwagger.LiveTestFramework.Build.psm1' + ModuleVersion = '0.0.1' + GUID = '03459d8d-5fcc-48e1-9a88-94f7971e0335' Author = 'Microsoft Corporation' CompanyName = 'Microsoft Corporation' Copyright = '(c) Microsoft Corporation. All rights reserved.' - Description = 'PowerShell module with commands for generating or manipulating PSSwagger.LiveTestFramework binaries.' - PowerShellVersion = '5.0' - FunctionsToExport = @('Start-PSSwaggerLiveTestServer','Add-PSSwaggerLiveTestLibType','Add-PSSwaggerLiveTestServerType') + Description = 'PowerShell module with commands for building the PSSwagger.LiveTestFramework package.' + PowerShellVersion = '5.1' + FunctionsToExport = @('Initialize-BuildDependency','Invoke-Build','Start-BuildDotNetProject') CmdletsToExport = '' VariablesToExport = '' AliasesToExport = '' - RequiredModules = @('PSSwaggerUtility') + RequiredModules = @() NestedModules = @() - + DefaultCommandPrefix = 'LTF' FileList = @( - 'PSSwagger.LiveTestFramework.psd1', - 'PSSwagger.LiveTestFramework.psm1' + 'PSSwagger.LiveTestFramework.Build.psd1', + 'PSSwagger.LiveTestFramework.Build.psm1' ) PrivateData = @{ PSData = @{ - Tags = @('Azure', - 'Swagger', + Tags = @('Swagger', 'PSEdition_Desktop', 'PSEdition_Core', 'Linux', diff --git a/PSSwagger.LiveTestFramework/build/PSSwagger.LiveTestFramework.Build.psm1 b/PSSwagger.LiveTestFramework/build/PSSwagger.LiveTestFramework.Build.psm1 new file mode 100644 index 0000000..a72c8fd --- /dev/null +++ b/PSSwagger.LiveTestFramework/build/PSSwagger.LiveTestFramework.Build.psm1 @@ -0,0 +1,342 @@ +Microsoft.PowerShell.Core\Set-StrictMode -Version Latest + +<# +.DESCRIPTION + Ensures all dependencies required for running tests are present on the current machine. +#> +function Initialize-BuildDependency { + [CmdletBinding()] + param() + + $expectedDotNetVersion = "2.1.0-preview1-006547" + Write-Host "Setting up PSSwagger.LiveTestFramework.Build dependencies:" + Write-Host " dotnet: $expectedDotnetVersion" + Write-Host " PSSwaggerUtility: *" + Write-Host "" + $failed = Setup-PSSwaggerUtility + if (-not $failed) { + Initialize-PSSwaggerDependencies + Import-Module -Name PSSwaggerUtility + $r = Setup-DotNet | Select-Object -Last 1 + $failed = $failed -or $r + } + + if ($failed) { + Write-Error -Message 'One or more dependencies failed to intialize.' + } else { + Write-Host "Completed setting up PSSwagger.LiveTestFramework.Build dependencies." -BackgroundColor DarkGreen + } + + $failed +} + +<# +.DESCRIPTION + Build PSSwagger LiveTestFramework Console Server and libraries using dotnet CLI. + Optionally copies binaries to OutputDirectory parameter value if available. +#> +function Invoke-Build { + [CmdletBinding()] + param( + [Parameter(Mandatory=$false)] + [string] + [ValidateSet('net452')] + $Framework = 'net452', + + [Parameter(Mandatory=$false)] + [string] + [ValidateSet('Debug','Release')] + $Configuration = 'Debug', + + [Parameter(Mandatory=$false)] + [string] + $OutputDirectory, + + [Parameter(Mandatory=$false)] + [switch] + $CleanOutputDirectory + ) + + Initialize-BuildDependency + if ($OutputDirectory -and $CleanOutputDirectory -and (Test-Path -Path $OutputDirectory -PathType Container)) { + Remove-Item -Path $OutputDirectory -Recurse + } + + if ($OutputDirectory -and (-not (Test-Path -Path $OutputDirectory -PathType Container))) { + $null = New-Item -Path $OutputDirectory -ItemType Directory + } + + $cache = @{} + Get-ChildItem -Path (Split-Path -Path $PSScriptRoot -Parent | Join-Path -ChildPath "src" | Join-Path -ChildPath "*.csproj") -File -Recurse | ForEach-Object { + # Execute only if it's a Microsoft.NET.SDK project + if ((Get-Content -Path $_.FullName | Out-String).Contains(" +function Install-Dotnet { + [CmdletBinding()] + param( + [Parameter(Mandatory=$false)] + [string]$Channel = "preview", + [Parameter(Mandatory=$false)] + [string]$Version = "2.0.0-preview1-005952" + ) + + $osInfo = PSSwaggerUtility\Get-OperatingSystemInfo + $obtainUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain" + + # Install for Linux and OS X + if ($osInfo.IsLinux -or $osInfo.IsOSX) { + $LinuxInfo = Get-Content /etc/os-release -Raw | ConvertFrom-StringData + $IsUbuntu = $LinuxInfo.ID -match 'ubuntu' + + # Uninstall all previous dotnet packages + $uninstallScript = if ($IsUbuntu) { + "dotnet-uninstall-debian-packages.sh" + } elseif ($osInfo.IsOSX) { + "dotnet-uninstall-pkgs.sh" + } + + if ($uninstallScript) { + Start-NativeExecution { + curl -sO $obtainUrl/uninstall/$uninstallScript + bash ./$uninstallScript + } + } else { + Write-Warning "This script only removes prior versions of dotnet for Ubuntu 14.04 and OS X" + } + + $installScript = "dotnet-install.sh" + Start-NativeExecution { + curl -sO $obtainUrl/$installScript + bash ./$installScript -c $Channel -v $Version + } + } elseif ($osInfo.IsWindows) { + Remove-Item -ErrorAction SilentlyContinue -Recurse -Force ~\AppData\Local\Microsoft\dotnet + $installScript = "dotnet-install.ps1" + Invoke-WebRequest -Uri $obtainUrl/$installScript -OutFile $installScript + & ./$installScript -Channel $Channel -Version $Version + } + + $originalPath = $env:PATH + $dotnetPath = Get-DotNetPath + + if (-not (Get-Command -Name 'dotnet' -ErrorAction Ignore)) { + Write-Verbose -Message "dotnet not found in path. Adding downloaded dotnet to path." + $env:PATH = $dotnetPath + [IO.Path]::PathSeparator + $env:PATH + } + + if (-not (Get-Command -Name 'dotnet' -ErrorAction Ignore)) { + Write-Error -Message "dotnet failed to be added to path. Restoring original." + $env:PATH = $originalPath + } +} + +<# +.DESCRIPTION + Gets the expected dotnet CLI location. +#> +function Get-DotNetPath +{ + [CmdletBinding()] + param() + + $osInfo = PSSwaggerUtility\Get-OperatingSystemInfo + if ($osInfo.IsWindows) { + $path = "$env:LocalAppData\Microsoft\dotnet" + } else { + $path = "$env:HOME/.dotnet" + } + + Write-Verbose -Message "dotnet CLI path: $path" + $path +} + +<# +.DESCRIPTION + Executes a script block. +#> +function script:Start-NativeExecution([scriptblock]$sb, [switch]$IgnoreExitcode) +{ + $backupEAP = $script:ErrorActionPreference + $script:ErrorActionPreference = "Continue" + try { + & $sb + # note, if $sb doesn't have a native invocation, $LASTEXITCODE will + # point to the obsolete value + if ($LASTEXITCODE -ne 0 -and -not $IgnoreExitcode) { + throw "Execution of {$sb} failed with exit code $LASTEXITCODE" + } + } finally { + $script:ErrorActionPreference = $backupEAP + } +} + +Export-ModuleMember -Function Initialize-BuildDependency,Invoke-Build,Start-BuildDotNetProject \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/readme.md b/PSSwagger.LiveTestFramework/readme.md index 7bc75cf..0940306 100644 --- a/PSSwagger.LiveTestFramework/readme.md +++ b/PSSwagger.LiveTestFramework/readme.md @@ -10,17 +10,5 @@ When you, as the service owner, have multiple language SDKs you need to test, yo Live service <-> Language-specific SDK <-> Language-specific test server <-> Test code -## Usage -After you've imported ```PSSwaggerUtility```, you can import ```PSSwagger.LiveTestFramework```. Use this module to compile the library and console server types or start the server directly. - -### Start-PSSwaggerLiveTestServer -TODO - -### Add-PSSwaggerLiveTestLibType -TODO - -### Add-PSSwaggerLiveTestServerType -TODO - # Other Documentation TODO \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/ConvertFrom-CSharpFiles.ps1 b/PSSwagger.LiveTestFramework/src/ConvertFrom-CSharpFiles.ps1 deleted file mode 100644 index 1c3248a..0000000 --- a/PSSwagger.LiveTestFramework/src/ConvertFrom-CSharpFiles.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath '*.cs') -Recurse -File | ForEach-Object { - $dir = $_.DirectoryName - # TODO: Ignore using .gitignore when we move PSSwagger.LiveTestFramework to a new repo - if ((-not $dir.Contains('vs-csproj')) -and (-not $dir.Contains('obj')) -and (-not $dir.Contains('bin'))) { - $null = Move-Item -Path $_.FullName -Destination (Join-Path -Path $dir -ChildPath "$($_.BaseName).Code.ps1") -Force - } -} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/ConvertTo-CSharpFiles.ps1 b/PSSwagger.LiveTestFramework/src/ConvertTo-CSharpFiles.ps1 deleted file mode 100644 index 104fca0..0000000 --- a/PSSwagger.LiveTestFramework/src/ConvertTo-CSharpFiles.ps1 +++ /dev/null @@ -1,8 +0,0 @@ -Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath '*.Code.ps1') -Recurse -File | ForEach-Object { - $dir = $_.DirectoryName - # TODO: Ignore using .gitignore when we move PSSwagger.LiveTestFramework to a new repo - if ((-not $dir.Contains('vs-csproj')) -and (-not $dir.Contains('obj')) -and (-not $dir.Contains('bin'))) { - $filename = $_.BaseName.Substring(0, ($_.BaseName.Length)-5) - $null = Move-Item -Path $_.FullName -Destination (Join-Path -Path $dir -ChildPath "$filename.cs") -Force - } -} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj index 7c60f56..de5da85 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj @@ -1,6 +1,14 @@ + net452 Exe - net452;netcoreapp2.0 + + + + + + + + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs index e82c252..68fa4ee 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs @@ -1,11 +1,140 @@ -namespace PSSwagger.LTF.ConsoleServer +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.ConsoleServer { + using Lib; + using Lib.Credentials; + using Lib.IO; + using Lib.Logging; + using Lib.Models; + using Lib.PowerShell; + using Lib.ServiceTracing; using System; - + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Threading; + class Program { static void Main(string[] args) { + ServerArgs serverArgs = new ServerArgs(args); + NamedPipeServer namedPipe = new NamedPipeServer(serverArgs.LogPipeName); + Logger logger = new Logger(namedPipe, namedPipe); + if (serverArgs.Errors.Count > 0) + { + logger.LogError("Server arguments had errors."); + foreach (string error in serverArgs.Errors) + { + logger.LogError(error); + } + return; + } + + // Load external modules + PowerShellRunspace runspace = new PowerShellRunspace(logger); + foreach (string externalModule in serverArgs.ExternalModules) + { + GeneratedModule module = new GeneratedModule(runspace); + module.ModulePath = externalModule; + CommandExecutionResult result = module.Load(); + if (result != null && result.HadErrors) + { + logger.LogError(String.Format(CultureInfo.CurrentCulture, "Failed to load extra module: {0}", externalModule)); + result.LogErrors(logger); + return; + } + } + + // Start test server + JsonRpcPipe jsonRpcPipe = new JsonRpcPipe(new StandardInputPipe(), new StandardOutputPipe()); + LiveTestServer server = new LiveTestServer(new LiveTestServerStartParams() + { + Logger = logger, + Input = jsonRpcPipe, + Output = jsonRpcPipe, + CredentialFactory = new LiveTestCredentialFactory(), + RunspaceManager = runspace, + ModulePath = serverArgs.ModulePath, + TracingManager = new ServiceTracingManager(), + SpecificationPaths = serverArgs.SpecificationPaths + }); + + try + { + server.RunAsync().Wait(); + } catch (Exception ex) + { + logger.LogError("Failed to start server: " + ex.ToString()); + } + + // Wait until server exits (usually means the server ran into an internal error) + while (server.IsRunning) + { + Thread.Sleep(1); + } + } + } + + class ServerArgs + { + public List SpecificationPaths { get; set; } + public List ExternalModules { get; set; } + public string ModulePath { get; set; } + public string LogPipeName { get; set; } + public List Errors { get; set; } + + public ServerArgs(string[] args) + { + this.LogPipeName = "psswagger-ltf-consoleserver"; + string lastArg = String.Empty; + this.SpecificationPaths = new List(); + this.ExternalModules = new List(); + this.Errors = new List(); + foreach (string arg in args) + { + if (!arg.StartsWith("/")) + { + switch (lastArg.ToLowerInvariant()) + { + case "spec": + if (!arg.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) + { + this.Errors.Add("Specification file is not a .json file."); + } else if (!File.Exists(arg)) + { + this.Errors.Add(String.Format(CultureInfo.CurrentCulture, "Specification file does not exist: {0}", arg)); + } else + { + this.SpecificationPaths.Add(arg); + } + break; + case "extmodule": + this.ExternalModules.Add(arg); + break; + case "testmodule": + this.ModulePath = arg; + break; + case "logpipename": + this.LogPipeName = arg; + break; + default: + this.Errors.Add(String.Format(CultureInfo.CurrentCulture, "Unknown argument: {0}", lastArg)); + break; + } + lastArg = String.Empty; + } else + { + lastArg = arg.Substring(1); + } + } + + if (String.IsNullOrEmpty(this.ModulePath)) + { + this.Errors.Add("No test module path specified."); + } } } } diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/vs-csproj/PSSwagger.LTF.ConsoleServer.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/vs-csproj/PSSwagger.LTF.ConsoleServer.csproj index e645ca9..e07b498 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/vs-csproj/PSSwagger.LTF.ConsoleServer.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/vs-csproj/PSSwagger.LTF.ConsoleServer.csproj @@ -44,6 +44,16 @@ + + + {6C53850A-7758-452C-8C02-CBAA894B814E} + PSSwagger.LTF.IO.Lib + + + {D2E4CBA1-BA97-4C60-9645-317666DF6C9F} + PSSwagger.LTF.Lib + + + + + + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/vs-csproj/PSSwagger.LTF.IO.Lib.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/vs-csproj/PSSwagger.LTF.IO.Lib.csproj new file mode 100644 index 0000000..91205a8 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/vs-csproj/PSSwagger.LTF.IO.Lib.csproj @@ -0,0 +1,67 @@ + + + + + Debug + AnyCPU + {6C53850A-7758-452C-8C02-CBAA894B814E} + Library + PSSwagger.LTF.IO.Lib + PSSwagger.LTF.IO.Lib + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + Interfaces\%(FileName).cs + + + + + + + + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/vs-csproj/PSSwagger.LTF.IO.Lib.sln b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/vs-csproj/PSSwagger.LTF.IO.Lib.sln new file mode 100644 index 0000000..f33c4fe --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/vs-csproj/PSSwagger.LTF.IO.Lib.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSSwagger.LTF.IO.Lib", "PSSwagger.LTF.IO.Lib.csproj", "{D2E4CBA1-BA97-4C60-9645-317666DF6C9F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D2E4CBA1-BA97-4C60-9645-317666DF6C9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D2E4CBA1-BA97-4C60-9645-317666DF6C9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2E4CBA1-BA97-4C60-9645-317666DF6C9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D2E4CBA1-BA97-4C60-9645-317666DF6C9F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/vs-csproj/packages.config b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/vs-csproj/packages.config new file mode 100644 index 0000000..810e559 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/vs-csproj/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Converters/DynamicTypedObjectConverter.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Converters/DynamicTypedObjectConverter.cs new file mode 100644 index 0000000..01b4ac0 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Converters/DynamicTypedObjectConverter.cs @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Converters +{ + using Models; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + /// + /// Converts JSON objects into a strongly-typed object with different property names from the JSON object when the type is not known at compile time. + /// + public class DynamicTypedObjectConverter : JsonConverter + { + private RuntimeTypeData typeData; + + /// + /// Constructor. + /// + /// Metadata for type this converter should convert. + public DynamicTypedObjectConverter(RuntimeTypeData typeData) + { + this.typeData = typeData; + } + + public override bool CanConvert(Type objectType) + { + return objectType.Equals(typeData.Type); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + Dictionary dict = serializer.Deserialize>(reader); + Dictionary objectTypeProperties = new Dictionary(); + foreach (PropertyInfo pi in objectType.GetProperties()) + { + objectTypeProperties[pi.Name.ToLowerInvariant()] = pi; + } + + object obj = Activator.CreateInstance(objectType); + foreach (string prop in dict.Keys) + { + string propertyName = prop.ToLowerInvariant(); + object converted; + if (objectTypeProperties.ContainsKey(propertyName)) + { + // This JSON property directly translates to a .NET property. This should be similar to normal deserialization. + if (ConvertObject(dict[prop], objectTypeProperties[propertyName].PropertyType, serializer, out converted)) + { + objectTypeProperties[propertyName].SetValue(obj, converted); + } + } else + { + // Check if typeData describes a mapping from this JSON property to a .NET property. + ParameterData match = typeData.Properties.Where((kvp) => !String.IsNullOrEmpty(kvp.Value.JsonName) && + kvp.Value.JsonName.Equals(propertyName, StringComparison.OrdinalIgnoreCase)).Select((kvp) => kvp.Value).FirstOrDefault(); + if (match != null) + { + if (ConvertObject(dict[prop], objectTypeProperties[match.Name].PropertyType, serializer, out converted)) + { + objectTypeProperties[match.Name].SetValue(obj, converted); + } + } + } + } + + return obj; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteRawValue("null"); + return; + } + + // This reverses the process of mapping JSON property -> .NET property, going from .NET to JSON instead. + Dictionary dict = new Dictionary(); + foreach (PropertyInfo pi in value.GetType().GetProperties()) + { + if (pi.GetCustomAttribute(typeof(JsonIgnoreAttribute)) == null) + { + object val = pi.GetValue(value); + if ((val != null) || serializer.NullValueHandling == NullValueHandling.Include) + { + string propertyName = pi.Name.ToLowerInvariant(); + if (this.typeData.Properties.ContainsKey(propertyName) && !String.IsNullOrEmpty(this.typeData.Properties[propertyName].JsonName)) + { + propertyName = this.typeData.Properties[propertyName].JsonName; + } + + dict[propertyName] = val; + } + } + } + + serializer.Serialize(writer, dict); + } + + /// + /// Convert a raw Newtonsoft.Json object into the expected type. + /// + /// Raw Newtonsoft.Json object. + /// Type to convert to. + /// Current serializer. + /// Converter object. Can be null whether or not true is returned. + /// True if conversion was recognized by this method; false otherwise. + private bool ConvertObject(object val, Type expectedType, JsonSerializer serializer, out object converted) + { + converted = null; + bool success = true; + if (val == null || val.GetType().Equals(expectedType)) + { + converted = val; + } + else if (val is JToken) + { + converted = serializer.Deserialize(new JTokenReader(val as JToken), expectedType); + } + else + { + success = false; + } + + return success; + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Converters/LiveTestRequestConverter.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Converters/LiveTestRequestConverter.cs new file mode 100644 index 0000000..222f997 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Converters/LiveTestRequestConverter.cs @@ -0,0 +1,185 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Converters +{ + using Messages; + using Models; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + /// + /// Converts JSON objects into a LiveTestRequest. + /// + public class LiveTestRequestConverter : JsonConverter + { + private GeneratedModule module; + + /// + /// Constructor. + /// + /// Module containing all operations and data this converter should handle. + public LiveTestRequestConverter(GeneratedModule module) + { + this.module = module; + } + + /// + /// Register this converter and all type converters underneath. + /// + /// Serializer settings to register all converters to. + public void RegisterSelf(JsonSerializerSettings settings) + { + settings.Converters.Add(this); + foreach (OperationData operation in module.Operations.Values) + { + foreach (ParameterData parameter in operation.Parameters.Values) + { + RegisterTypeConverter(parameter.Type, settings); + } + } + } + + private void RegisterTypeConverter(RuntimeTypeData typeData, JsonSerializerSettings settings) + { + if (typeData != null && typeData.Type != null && typeData.Type.GetConstructor(new Type[] { }) != null) + { + settings.Converters.Add(new DynamicTypedObjectConverter(typeData)); + foreach (ParameterData property in typeData.Properties.Values) + { + RegisterTypeConverter(property.Type, settings); + } + } + } + + public override bool CanConvert(Type objectType) + { + return objectType.Equals(typeof(LiveTestRequest)); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JsonSerializer cleanSerializer = new JsonSerializer(); + LiveTestRequest request = cleanSerializer.Deserialize(reader, objectType) as LiveTestRequest; + // Current test protocol states that the method is given as A.B_C, so ignore anything without a '.'. + int dotIndex = request.Method.IndexOf('.'); + if (dotIndex != -1) + { + request.OperationId = request.Method.Substring(dotIndex+1).ToLowerInvariant(); + if (module.Operations.ContainsKey(request.OperationId)) + { + OperationData op = module.Operations[request.OperationId]; + if (request.Params != null) + { + Dictionary newParams = new Dictionary(); + foreach (string key in request.Params.Keys) + { + // Reserved parameter will be converted later. + if (key.Equals("__reserved", StringComparison.OrdinalIgnoreCase)) + { + newParams[key] = request.Params[key]; + } + else + { + ParameterData match = op.Parameters.Where((kvp) => !String.IsNullOrEmpty(kvp.Value.JsonName) && + kvp.Value.JsonName.Equals(key, StringComparison.OrdinalIgnoreCase)).Select((kvp) => kvp.Value).FirstOrDefault(); + if (match == null && op.Parameters.ContainsKey(key.ToLowerInvariant())) + { + match = op.Parameters[key.ToLowerInvariant()]; + } + if (match != null) + { + // This means that the parameter has been renamed from the spec name. + object converted; + if (ConvertObject(request.Params[key], match.Type.Type, serializer, out converted)) + { + newParams[match.Name.ToLowerInvariant()] = converted; + } + } + } + } + + request.Params = newParams; + } + } + } + + return request; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteRawValue("null"); + return; + } + + LiveTestRequest request = (LiveTestRequest)value; + Dictionary dict = new Dictionary(); + foreach (PropertyInfo pi in value.GetType().GetProperties()) + { + if (!pi.Name.Equals("params", StringComparison.OrdinalIgnoreCase) && pi.GetCustomAttribute(typeof(JsonIgnoreAttribute)) == null) + { + object val = pi.GetValue(value); + if ((val != null) || serializer.NullValueHandling == NullValueHandling.Include) + { + dict[pi.Name.ToLowerInvariant()] = val; + } + } + } + + dict["params"] = request.Params; + if (module.Operations.ContainsKey(request.OperationId)) + { + OperationData op = module.Operations[request.OperationId]; + Dictionary newParams = new Dictionary(); + foreach (string key in request.Params.Keys) + { + if (key.Equals("__reserved", StringComparison.OrdinalIgnoreCase)) + { + newParams[key] = request.Params; + } else if (op.Parameters.ContainsKey(key)) + { + // Decide if the JSON name or the PowerShell name should be used + if (!String.IsNullOrEmpty(op.Parameters[key].JsonName)) + { + newParams[op.Parameters[key].JsonName.ToLowerInvariant()] = request.Params[key]; + } else + { + newParams[op.Parameters[key].Name.ToLowerInvariant()] = request.Params[key]; + } + } + } + + dict["params"] = newParams; + } + + serializer.Serialize(writer, dict); + } + + private bool ConvertObject(object val, Type expectedType, JsonSerializer serializer, out object converted) + { + converted = null; + bool success = true; + if (val == null || val.GetType().Equals(expectedType)) + { + converted = val; + } + else if (val is JToken) + { + converted = serializer.Deserialize(new JTokenReader(val as JToken), expectedType); + } + else + { + success = false; + } + + return success; + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Credentials/AzureCredentialProvider.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Credentials/AzureCredentialProvider.cs new file mode 100644 index 0000000..91eb1d3 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Credentials/AzureCredentialProvider.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Credentials +{ + using Interfaces; + using Logging; + using Models; + using System; + using System.Management.Automation; + using System.Security; + + /// + /// Handle Azure Service Principal credentials. + /// + public class AzureCredentialProvider : ICredentialProvider + { + private Logger logger; + + public string TenantId { get; private set; } + public string ClientId { get; private set; } + public string Secret { get; private set; } + + public AzureCredentialProvider(Logger logger) + { + this.logger = logger; + } + + /// + /// Logs in using Azure ServicePrincipal information. + /// + /// Uses the runspace from this ICommandBuilder. + public void Process(ICommandBuilder command) + { + if (String.IsNullOrEmpty(this.TenantId) || String.IsNullOrEmpty(this.ClientId) || String.IsNullOrEmpty(this.Secret)) + { + throw new InvalidTestCredentialsException(); + } + + // First import the expected module containing Add-AzureRmAccount + ICommandBuilder importAzureModule = command.Runspace.CreateCommand(); + importAzureModule.Command = "Import-Module"; + importAzureModule.AddParameter("Name", "AzureRM.Profile"); + CommandExecutionResult importAzureModuleResult = importAzureModule.Invoke(); + if (importAzureModuleResult.HadErrors) + { + importAzureModuleResult.LogErrors(this.logger); + throw new CommandFailedException(importAzureModule, importAzureModuleResult); + } + + // Now run Add-AzureRmAccount + ICommandBuilder addAzureRmAccount = command.Runspace.CreateCommand(); + addAzureRmAccount.Command = "Add-AzureRmAccount"; + SecureString ss = new SecureString(); + foreach (char c in this.Secret) + { + ss.AppendChar(c); + } + + PSCredential credential = new PSCredential(this.ClientId, ss); + addAzureRmAccount.AddParameter("Credential", credential); + addAzureRmAccount.AddParameter("TenantId", this.TenantId); + addAzureRmAccount.AddParameter("ServicePrincipal", true); + CommandExecutionResult addAzureRmAccountResult = addAzureRmAccount.Invoke(); + if (addAzureRmAccountResult.HadErrors) + { + addAzureRmAccountResult.LogErrors(this.logger); + throw new CommandFailedException(addAzureRmAccount, addAzureRmAccountResult); + } + } + + public void Set(string property, object value) + { + if (property.Equals("tenantid", StringComparison.OrdinalIgnoreCase)) + { + this.TenantId = value.ToString(); + } else if (property.Equals("clientid", StringComparison.OrdinalIgnoreCase)) + { + this.ClientId = value.ToString(); + } else if (property.Equals("secret", StringComparison.OrdinalIgnoreCase)) + { + this.Secret = value.ToString(); + } + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Credentials/LiveTestCredentialFactory.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Credentials/LiveTestCredentialFactory.cs new file mode 100644 index 0000000..3c9d06e --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Credentials/LiveTestCredentialFactory.cs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Credentials +{ + using Interfaces; + using Logging; + using Messages; + using Newtonsoft.Json.Linq; + using System; + using System.Collections.Generic; + + /// + /// Handle creation of credential providers. + /// + public class LiveTestCredentialFactory + { + protected IDictionary> providers = new Dictionary>() + { + { "azure", (logger) => new AzureCredentialProvider(logger) } + }; + + /// + /// Create all credential providers specified by the given request, assuming the __reserved parameter has alreayd been converted by TranslateCredentialsObjects. + /// + /// + /// + /// + public virtual IEnumerable Create(LiveTestRequest request, Logger logger) + { + if (request.Params != null && request.Params.ContainsKey("__reserved")) + { + Dictionary reservedParams = (Dictionary)request.Params["__reserved"]; + LiveTestCredentials[] arr = (LiveTestCredentials[])reservedParams["credentials"]; + foreach (LiveTestCredentials credentials in arr) + { + ICredentialProvider provider = null; + string credType = String.IsNullOrEmpty(credentials.Type) ? "azure" : credentials.Type.ToLowerInvariant(); + if (this.providers.ContainsKey(credType)) + { + provider = this.providers[credType](logger); + } + + if (provider != null) + { + foreach (string property in credentials.Properties.Keys) + { + provider.Set(property, credentials.Properties[property]); + } + + yield return provider; + } + } + } + } + + /// + /// On first read, may contain a __reserved property with a generic JSON object. That object should have a credentials property. + /// This method exists to convert the generic object of the credentials property into a strongly typed array of LiveTestCredentials objects. + /// + /// After processing, the __reserved property will be transformed into a Dictionary<string, object> object. The credentials key will point to an array of LiveTestCredentials objects. + /// + /// Request object to transform. + public virtual void TranslateCredentialsObjects(LiveTestRequest request) + { + // Check if request requires conversion + if (request.Params != null && request.Params.ContainsKey("__reserved") && request.Params["__reserved"] is JObject) + { + JObject reservedParams = (JObject)request.Params["__reserved"]; + Dictionary reservedParamsDict = new Dictionary(); + foreach (JProperty property in reservedParams.Properties()) + { + if (property.Name.Equals("credentials", StringComparison.OrdinalIgnoreCase)) + { + List credsList = new List(); + if (property.Value is JObject) + { + // Single credentials object + credsList.Add(GetCredentials((JObject)property.Value)); + } + else if (property.Value is JArray) + { + // Array of credentials objects + foreach (JToken obj in (JArray)property.Value) + { + credsList.Add(GetCredentials((JObject)obj)); + } + } + + reservedParamsDict[property.Name] = credsList.ToArray(); + } else if (property.Name.Equals("httpResponse", StringComparison.OrdinalIgnoreCase)) + { + request.HttpResponse = property.Value.Value(); + } + else + { + reservedParamsDict[property.Name] = property.Value; + } + } + + request.Params["__reserved"] = reservedParamsDict; + } + } + + private LiveTestCredentials GetCredentials(JObject credsObject) + { + LiveTestCredentials creds = new LiveTestCredentials(); + foreach (JProperty credsProperty in credsObject.Properties()) + { + if (credsProperty.Name.Equals("x-ps-credtype", StringComparison.OrdinalIgnoreCase)) + { + creds.Type = credsProperty.Value.ToString(); + } + else + { + creds.Properties[credsProperty.Name] = credsProperty.Value; + } + } + + if (String.IsNullOrEmpty(creds.Type)) + { + creds.Type = "azure"; + } + + return creds; + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Exceptions/CommandFailedException.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Exceptions/CommandFailedException.cs new file mode 100644 index 0000000..441090e --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Exceptions/CommandFailedException.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib +{ + using Interfaces; + using Models; + using System; + using System.Globalization; + + /// + /// Exception thrown when a command fails to execute. + /// + public class CommandFailedException : Exception + { + public ICommandBuilder Command { get; private set; } + public CommandExecutionResult Result { get; private set; } + public string UserMessage { get; private set; } + + public CommandFailedException(ICommandBuilder command, CommandExecutionResult result) : this(command, result, String.Empty) { } + + public CommandFailedException(ICommandBuilder command, CommandExecutionResult result, string message) + { + this.Command = command; + this.Result = result; + this.UserMessage = message; + } + + public override string Message + { + get + { + string msg; + if (this.Command != null) + { + msg = String.Format(CultureInfo.CurrentCulture, "Failed to execute command '{0}'. Reason: {1}", this.Command.Command, this.UserMessage); + } else + { + msg = this.UserMessage; + } + + return msg; + } + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Exceptions/InvalidTestCredentialsException.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Exceptions/InvalidTestCredentialsException.cs new file mode 100644 index 0000000..afb9f7c --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Exceptions/InvalidTestCredentialsException.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib +{ + using System; + + /// + /// Exception thrown when a command fails to execute. + /// + public class InvalidTestCredentialsException : Exception + { + public InvalidTestCredentialsException() : base("Test received an invalid credentials object.") + { + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Exceptions/ModulePathNotRootedException.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Exceptions/ModulePathNotRootedException.cs new file mode 100644 index 0000000..8d20ca4 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Exceptions/ModulePathNotRootedException.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib +{ + using System; + using System.Globalization; + + /// + /// Exception thrown when a command fails to execute. + /// + public class ModulePathNotRootedException : Exception + { + public string Path { get; private set; } + + public ModulePathNotRootedException(string path) + { + this.Path = path; + } + + public override string Message + { + get + { + return String.Format(CultureInfo.CurrentCulture, "Module path must be an absolute path. Path: {0}", this.Path); + } + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/GeneratedModule.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/GeneratedModule.cs deleted file mode 100644 index 699bd3d..0000000 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/GeneratedModule.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace PSSwagger.LTF.Lib -{ - using Interfaces; - using Messages; - using System; - using System.Collections; - using System.Collections.Generic; - - /// - /// Contains metadata for a generated module being tested. - /// - public class GeneratedModule - { - private IRunspaceManager runspace; - - public string ModulePath { get; set; } - - public IList RequiredModules { get; set; } - - public GeneratedModule(IRunspaceManager runspace) - { - this.runspace = runspace; - this.RequiredModules = new List(); - } - - public virtual IEnumerable ProcessRequest(LiveTestRequest request) - { - return null; - } - - public virtual void Load(bool force = false) - { - foreach (GeneratedModule requiredModule in this.RequiredModules) - { - requiredModule.Load(force: force); - } - - ICommandBuilder command = this.runspace.CreateCommand(); - command.Command = "Import-Module"; - if (!String.IsNullOrEmpty(ModulePath)) - { - command.AddParameter("Name", this.ModulePath, switchParameter: false); - } - - if (force) - { - command.AddParameter("Force", true, switchParameter: true); - } - - command.Invoke(); - } - } -} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/IO/JsonBlockPipe.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/IO/JsonBlockPipe.cs deleted file mode 100644 index d9a92a0..0000000 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/IO/JsonBlockPipe.cs +++ /dev/null @@ -1,76 +0,0 @@ -namespace PSSwagger.LTF.Lib.IO -{ - using Interfaces; - using System; - using System.Threading.Tasks; - - /// - /// Read valid JSON blocks. - /// - public class JsonBlockPipe : IInputPipe - { - private IInputPipe characterReader; - - /// - /// Constructor. - /// - /// InputPipe to use for reading single characters. - public JsonBlockPipe(IInputPipe characterReader) - { - this.characterReader = characterReader; - } - - /// - /// Not implemented for JsonBlockPipe. - /// - public char ReadChar() - { - throw new NotImplementedException(); - } - - /// - /// Read a single valid JSON block. Ignores any characters until the first { is read. Ends when the matching } character is read. - /// - /// Type of block to read and deserialize. - /// Block of type . - public async Task ReadBlockAsync() where T : class - { - string jsonString = String.Empty; - int openBraces = 0; - while (openBraces > 0 || String.IsNullOrEmpty(jsonString)) - { - char c = this.characterReader.ReadChar(); - switch (c) - { - case '{': - openBraces++; - jsonString += c; - break; - case '}': - if (openBraces > 0) - { - openBraces--; - jsonString += c; - } - break; - default: - if (openBraces > 0) - { - jsonString += c; - } - break; - } - } - - return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonString); - } - - /// - /// Not implemented for JsonBlockPipe. - /// - public string ReadLine() - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/IO/NullPipe.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/IO/NullPipe.cs deleted file mode 100644 index 2188185..0000000 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/IO/NullPipe.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace PSSwagger.LTF.Lib.IO -{ - using Interfaces; - using System.Threading.Tasks; - - /// - /// Ignores all IO. Returns defaults on read. - /// - public class NullPipe : IInputPipe, IOutputPipe - { - public void Flush() - { - } - - public char ReadChar() - { - return default(char); - } - - public void Write(char b) - { - } - - public async Task ReadBlockAsync() where T : class - { - return default(T); - } - - public async Task WriteBlockAsync(T msg) where T : class - { - } - - public string ReadLine() - { - return default(string); - } - - public void WriteLine(string line) - { - } - } -} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/IO/StandardInputPipe.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/IO/StandardInputPipe.cs deleted file mode 100644 index d01aa9e..0000000 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/IO/StandardInputPipe.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace PSSwagger.LTF.Lib.IO -{ - using Interfaces; - using System; - using System.Threading.Tasks; - - /// - /// Read from STDIN. - /// - public class StandardInputPipe : IInputPipe - { - /// - /// NotImplemented - /// - public Task ReadBlockAsync() where T : class - { - throw new NotImplementedException(); - } - - /// - /// Read a single character from System.Console. - /// - /// Character read. - public char ReadChar() - { - return (char)Console.In.Read(); - } - - /// - /// Read until the next new line character from System.Console. - /// - /// All text input up to but not including the new line character. - public string ReadLine() - { - return Console.ReadLine(); - } - } -} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/ICommandBuilder.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/ICommandBuilder.cs index 0e5f9cb..5a8f791 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/ICommandBuilder.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/ICommandBuilder.cs @@ -1,6 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.Interfaces { - using System.Collections; + using Models; /// /// Build commands to execute test operations. @@ -12,19 +15,23 @@ public interface ICommandBuilder /// string Command { get; set; } + /// + /// Gets the parent runspace. + /// + IRunspaceManager Runspace { get; } + /// /// Adds the given parameter. /// /// Case insensitive name of parameter. /// Parameter value. - /// True if parameter should be used as a switch (). False otherwise. /// This command builder. - ICommandBuilder AddParameter(string parameterName, object parameterValue, bool switchParameter); - + ICommandBuilder AddParameter(string parameterName, object parameterValue); + /// /// Execute the command in the parent runspace. /// /// Results of command. - IEnumerable Invoke(); + CommandExecutionResult Invoke(); } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/ICredentialProvider.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/ICredentialProvider.cs new file mode 100644 index 0000000..c97c1a3 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/ICredentialProvider.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Interfaces +{ + /// + /// Handle credentials. + /// + public interface ICredentialProvider + { + /// + /// Add the current credential information to the given command or the given command's runspace. + /// + void Process(ICommandBuilder command); + + /// + /// Set an inner property with the given name (case-insensitive) to the given value. + /// + /// Case-insensitive property name. + /// New value of property. + void Set(string property, object value); + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/IRunspaceManager.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/IRunspaceManager.cs index 6aec26a..4bcfb28 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/IRunspaceManager.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/IRunspaceManager.cs @@ -1,6 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.Interfaces { + using Models; using System.Collections; + using System.Collections.Generic; /// /// Manages the execution space for running test commands. @@ -8,11 +13,11 @@ namespace PSSwagger.LTF.Lib.Interfaces public interface IRunspaceManager { /// - /// Execute a given script string in this runspace. + /// Execute a given command in this runspace. /// - /// Script string to execute. - /// Result of script execution. - IEnumerable Invoke(string script); + /// Raw command to execute. The exact type depends on the runspace. + /// Result of command execution. + CommandExecutionResult Invoke(object command); /// /// Create an ICommandBuilder for this runspace. @@ -26,12 +31,5 @@ public interface IRunspaceManager /// Path to module or module name. /// Parsed module information struct. GeneratedModule GetModuleInfo(string modulePath); - - /// - /// Set a variable for this runspace session. - /// - /// Name of variable. - /// Value of variable. - void SetSessionVariable(string variableName, object variableValue); } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/IServiceTracer.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/IServiceTracer.cs new file mode 100644 index 0000000..18059a2 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/IServiceTracer.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Interfaces +{ + using System.Collections.Generic; + using System.Net.Http; + + /// + /// Traces operations at the service layer. + /// + public interface IServiceTracer + { + /// + /// Gets the collection of HttpResponseMessages caught by this tracer. + /// + IList HttpResponses { get; } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Json/JsonPathFinder.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Json/JsonPathFinder.cs new file mode 100644 index 0000000..3adb8d8 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Json/JsonPathFinder.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Json +{ + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using System; + using System.Collections.Generic; + + /// + /// Provides parsing for a JSON file. + /// + public class JsonPathFinder + { + private JToken root; + + public virtual JsonPathFinder Parent + { + get + { + if (this.root.Parent != null) + { + return new JsonPathFinder((JToken)this.root.Parent); + } + + return null; + } + } + + public virtual string Key + { + get + { + string[] dotSplit = this.root.Path.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + if (dotSplit.Length > 1) + { + return dotSplit[dotSplit.Length - 2]; + } + + return null; + } + } + + public virtual T GetValue() + { + return this.root.ToObject(); + } + + public JsonPathFinder() + { + this.root = null; + } + + public JsonPathFinder(string json, JsonSerializerSettings settings = null) + { + JsonSerializerSettings serializationSettings; + if (settings == null) + { + serializationSettings = new JsonSerializerSettings(); + } + else + { + serializationSettings = settings; + } + + this.root = JsonConvert.DeserializeObject(json, serializationSettings); + } + + public JsonPathFinder(JToken jToken) + { + this.root = jToken; + } + + public virtual IEnumerable Find(JsonQueryBuilder queryBuilder) + { + return Find(queryBuilder.ToQuery()); + } + + public virtual IEnumerable Find(string jsonQuery) + { + foreach (JToken token in this.root.SelectTokens(jsonQuery)) + { + yield return new JsonPathFinder(token); + } + } + + public override string ToString() + { + return this.root == null ? null : this.root.ToString(); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Json/JsonQueryBuilder.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Json/JsonQueryBuilder.cs new file mode 100644 index 0000000..d4cf16c --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Json/JsonQueryBuilder.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Json +{ + using System.Text; + + /// + /// Provides JSON Path query building operations. + /// + public class JsonQueryBuilder + { + private StringBuilder queryBuilder = new StringBuilder(); + private bool appendDot = false; + + public JsonQueryBuilder() { } + + public JsonQueryBuilder(string jsonPath) + { + queryBuilder.Append(jsonPath.Replace("#", "$").Replace("/", ".")); + } + + public JsonQueryBuilder RecursiveDescent() + { + appendDot = false; + this.queryBuilder.Append(".."); + return this; + } + + public JsonQueryBuilder Property(string name) + { + if (appendDot) + { + this.queryBuilder.Append("."); + } + this.queryBuilder.Append(name); + appendDot = true; + return this; + } + + public JsonQueryBuilder Any() + { + if (appendDot) + { + this.queryBuilder.Append("."); + } + this.queryBuilder.Append("*"); + appendDot = true; + return this; + } + + public JsonQueryBuilder ArrayIndex(JsonQueryBuilder filter) + { + this.queryBuilder.Append("["); + this.queryBuilder.Append(filter.ToQuery()); + this.queryBuilder.Append("]"); + return this; + } + + public JsonQueryBuilder HasProperty(string property) + { + this.queryBuilder.Append("@."); + this.queryBuilder.Append(property); + return this; + } + + public JsonQueryBuilder Filter(JsonQueryBuilder exp) + { + this.queryBuilder.Append("?("); + this.queryBuilder.Append(exp.ToQuery()); + this.queryBuilder.Append(")"); + return this; + } + + public string ToQuery() + { + return this.queryBuilder.ToString(); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs index ad85fd9..0c3a189 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs @@ -1,12 +1,21 @@ -namespace PSSwagger.LTF.Lib +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib { + using Converters; + using Credentials; + using Interfaces; using IO; - using Messages; using Logging; + using Messages; + using Models; + using ServiceTracing; using System; + using System.Collections.Generic; + using System.IO; using System.Threading; using System.Threading.Tasks; - using Interfaces; public class LiveTestServerStartParams { @@ -15,6 +24,13 @@ public class LiveTestServerStartParams public Logger Logger { get; set; } public IRunspaceManager RunspaceManager { get; set; } public string ModulePath { get; set; } + public IList SpecificationPaths { get; set; } + public LiveTestCredentialFactory CredentialFactory { get; set; } + public ServiceTracingManager TracingManager { get; set; } + public LiveTestServerStartParams() + { + this.SpecificationPaths = new List(); + } } /// @@ -22,6 +38,11 @@ public class LiveTestServerStartParams /// public class LiveTestServer { + #region Language Server Protocol error codes + private const int MethodNotFound = -32601; + private const int InternalError = -32603; + #endregion + private LiveTestServerStartParams parameters; private GeneratedModule currentModule; private Thread messageReadThread; @@ -47,6 +68,14 @@ public LiveTestServer(LiveTestServerStartParams parms) { throw new ArgumentNullException("parms.RunspaceManager"); } + if (parms.CredentialFactory == null) + { + throw new ArgumentNullException("parms.CredentialFactory"); + } + if (parms.TracingManager == null) + { + throw new ArgumentNullException("parms.TracingManager"); + } if (String.IsNullOrEmpty(parms.ModulePath)) { throw new ArgumentNullException("parms.ModulePath"); @@ -69,9 +98,47 @@ public async Task RunAsync() // Retrieve all module information using the current runspace manager this.currentModule = this.parameters.RunspaceManager.GetModuleInfo(this.parameters.ModulePath); + this.currentModule.Logger = this.parameters.Logger; + + // For JSON-RPC pipe input/output, add Newtonsoft.Json converters + if (this.Input is JsonRpcPipe) + { + JsonRpcPipe jsonRpcPipe = (JsonRpcPipe)this.Input; + new LiveTestRequestConverter(this.currentModule).RegisterSelf(jsonRpcPipe.JsonSerializerSettings); + if (this.parameters.Logger != null) + { + this.parameters.Logger.JsonSerializerSettings = jsonRpcPipe.JsonSerializerSettings; + } + } + + if (this.Output is JsonRpcPipe) + { + JsonRpcPipe jsonRpcPipe = (JsonRpcPipe)this.Output; + new LiveTestRequestConverter(this.currentModule).RegisterSelf(jsonRpcPipe.JsonSerializerSettings); + if (this.parameters.Logger != null) + { + this.parameters.Logger.JsonSerializerSettings = jsonRpcPipe.JsonSerializerSettings; + } + } + + // Parse specifications/metadata files for extra information, e.g. parameter renaming + if (this.parameters.SpecificationPaths != null) + { + foreach (string specificationPath in this.parameters.SpecificationPaths) + { + if (this.parameters.Logger != null) + { + this.parameters.Logger.LogAsync("Loading specification file: " + specificationPath); + } + Json.JsonPathFinder jsonFinder = new Json.JsonPathFinder(File.ReadAllText(specificationPath)); + if (this.parameters.Logger != null) + { + this.parameters.Logger.LogAsync("Parsing specification file: " + specificationPath); + } + this.currentModule.LoadMetadataFromSpecification(jsonFinder); + } + } - // Force load the module - this.currentModule.Load(force: true); this.IsRunning = true; // Start message read thread @@ -79,15 +146,82 @@ public async Task RunAsync() { while (this.IsRunning) { - LiveTestRequest msg = await this.Input.ReadBlockAsync(); - if (this.IsRunning) + try { - this.parameters.Logger?.LogAsync("Processing message: {0}", Newtonsoft.Json.JsonConvert.SerializeObject(msg)); - Task.Run(() => this.currentModule.ProcessRequest(msg)); + // Block and wait for the next request + LiveTestRequest msg = await this.Input.ReadBlock(); + if (this.IsRunning) + { + if (this.parameters.Logger != null) + { + this.parameters.Logger.LogAsync("Processing message: {0}", msg); + } + Task.Run(() => + { + LiveTestResponse response = null; + IServiceTracer serviceTracer = null; + try + { + // Enable service tracing so that we can get service layer information required by test protocol + long invocationId = this.parameters.TracingManager.GetNextInvocationId(); + serviceTracer = this.parameters.TracingManager.CreateTracer(invocationId, this.parameters.Logger); + this.parameters.TracingManager.EnableTracing(); + // Process teh request + CommandExecutionResult commandResult = this.currentModule.ProcessRequest(msg, this.parameters.CredentialFactory); + if (commandResult == null) + { + if (this.parameters.Logger != null) + { + this.parameters.Logger.LogAsync("Command not found."); + } + + response = msg.MakeResponse(null, MethodNotFound); + } + else + { + response = msg.MakeResponse(commandResult, serviceTracer, this.parameters.Logger); + } + } + catch (Exception exRequest) + { + if (this.parameters.Logger != null) + { + this.parameters.Logger.LogError("Exception processing request: " + exRequest.ToString()); + } + + response = msg.MakeResponse(exRequest, InternalError); + } finally + { + if (response != null) + { + this.Output.WriteBlock(response); + } + + if (serviceTracer != null) + { + this.parameters.TracingManager.RemoveTracer(serviceTracer); + } + } + }); + } + } + catch (Exception eRead) + { + if (this.parameters.Logger != null) + { + this.parameters.Logger.LogError("Exception during test server message loop: " + eRead.ToString()); + } + + this.IsRunning = false; } } - }) { IsBackground = true }; + }) + { IsBackground = true }; this.messageReadThread.Start(); + if (this.parameters.Logger != null) + { + this.parameters.Logger.LogAsync("PowerShell live test server has started."); + } } public void Stop() diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Logging/Logger.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Logging/Logger.cs index e5e2e89..c26de05 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Logging/Logger.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Logging/Logger.cs @@ -1,6 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.Logging { using Interfaces; + using Newtonsoft.Json; using System; using System.Globalization; using System.Text; @@ -15,12 +19,16 @@ public class Logger private IOutputPipe stderr; private bool timestamp; private bool loggingLevel; + + public JsonSerializerSettings JsonSerializerSettings { get; set; } + public Logger(IOutputPipe stdout, IOutputPipe stderr, bool timestamp = true, bool loggingLevel = true) { this.stdout = stdout; this.stderr = stderr; this.timestamp = timestamp; this.loggingLevel = loggingLevel; + this.JsonSerializerSettings = new JsonSerializerSettings(); } /// @@ -29,7 +37,10 @@ public Logger(IOutputPipe stdout, IOutputPipe stderr, bool timestamp = true, boo /// Message to log. public void Log(string message) { - this.stdout?.WriteLine(FormatMessage(message, "info")); + if (this.stdout != null) + { + this.stdout.WriteLine(FormatMessage(message, "info")); + } } /// @@ -42,9 +53,14 @@ public async Task LogAsync(string message, params object[] objs) string[] objSerialized = new string[objs.Length]; for (int i = 0; i < objs.Length; i++) { - if (objs[i] != null) + object objToSerialize = objs[i]; + if (objToSerialize != null) { - objSerialized[i] = Newtonsoft.Json.JsonConvert.SerializeObject(objs[i]); + if (objToSerialize is System.Management.Automation.PSObject) + { + objToSerialize = ((System.Management.Automation.PSObject)objToSerialize).ImmediateBaseObject; + } + objSerialized[i] = Newtonsoft.Json.JsonConvert.SerializeObject(objToSerialize, this.JsonSerializerSettings); } else { objSerialized[i] = "null"; @@ -60,7 +76,10 @@ public async Task LogAsync(string message, params object[] objs) /// public void LogError(string message) { - this.stderr?.WriteLine(FormatMessage(message, "error")); + if (this.stderr != null) + { + this.stderr.WriteLine(FormatMessage(message, "error")); + } } /// diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/JsonRpcBase.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/JsonRpcBase.cs index 280c0da..010dcbc 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/JsonRpcBase.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/JsonRpcBase.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.Messages { /// diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestCredentials.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestCredentials.cs new file mode 100644 index 0000000..161e640 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestCredentials.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Messages +{ + using System.Collections.Generic; + + /// + /// An Azure Live Test Framework extended credentials object. + /// + public class LiveTestCredentials + { + public string Type { get; set; } + public Dictionary Properties { get; set; } + + public LiveTestCredentials() + { + this.Properties = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestError.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestError.cs index dc128c5..068e9f4 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestError.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestError.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.Messages { /// @@ -8,5 +11,10 @@ public class LiveTestError public long Code { get; set; } public string Message { get; set; } public LiveTestResult Data { get; set; } + + public LiveTestError() + { + this.Data = new LiveTestResult(); + } } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestRequest.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestRequest.cs index 561d2e9..b0fcaa7 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestRequest.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestRequest.cs @@ -1,13 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.Messages { + using Interfaces; + using Logging; + using Models; + using Newtonsoft.Json; + using System; using System.Collections.Generic; + using System.Linq; + using System.Net.Http; /// /// An Azure Live Test Framework JSON-RPC request. /// public class LiveTestRequest : JsonRpcBase { + #region Language Server Protocol error codes + private const int InvalidRequest = -32600; + #endregion + public string Method { get; set; } public Dictionary Params { get; set; } + [JsonIgnore] + public string OperationId { get; set; } + [JsonIgnore] + public bool HttpResponse { get; set; } + + public LiveTestResponse MakeResponse(CommandExecutionResult commandResult, IServiceTracer tracer, Logger logger) + { + LiveTestResponse response = MakeBaseResponse(); + if (commandResult.HadErrors) + { + if (logger != null) + { + logger.LogAsync("Command failed with errors."); + commandResult.LogErrors(logger); + } + + response.Error = new LiveTestError(); + response.Error.Code = InvalidRequest; + response.Error.Data = GetLiveTestResult(commandResult.Errors, tracer); + } + else + { + if (logger != null) + { + logger.LogAsync("Command executed successfully."); + } + + response.Result = GetLiveTestResult(commandResult.Results, tracer); + } + + return response; + } + + public LiveTestResponse MakeResponse(Exception ex, int errorCode) + { + LiveTestResponse response = MakeBaseResponse(); + response.Error = new LiveTestError(); + response.Error.Code = errorCode; + response.Error.Data.Response = ex; + return response; + } + + private LiveTestResult GetLiveTestResult(IEnumerable resultsEnumerable, IServiceTracer tracer) + { + LiveTestResult result = new LiveTestResult(); + object[] resultsArray = resultsEnumerable == null ? null : resultsEnumerable.ToArray(); + if (resultsArray == null || resultsArray.Length == 0) + { + result.Response = null; + } + else if (resultsArray.Length == 1) + { + result.Response = resultsArray[0]; + } + else + { + result.Response = resultsArray; + } + + if (this.HttpResponse) + { + HttpResponseMessage responseMessage = tracer.HttpResponses.LastOrDefault(); + if (responseMessage != null) + { + result.StatusCode = (long)responseMessage.StatusCode; + Dictionary headersDictionary = new Dictionary(); + foreach (KeyValuePair> header in responseMessage.Headers) + { + string[] headerValues = header.Value.ToArray(); + if (headerValues.Length == 1) + { + headersDictionary[header.Key] = headerValues[0]; + } + else if (headerValues.Length > 1) + { + headersDictionary[header.Key] = headerValues; + } + } + + result.Headers = headersDictionary; + } + } + + return result; + } + + private LiveTestResponse MakeBaseResponse() + { + LiveTestResponse response = new LiveTestResponse(); + response.Id = this.Id; + response.JsonRpc = this.JsonRpc; + return response; + } } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestResponse.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestResponse.cs index 9b3e793..7be5873 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestResponse.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestResponse.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.Messages { /// diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestResult.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestResult.cs index 613fe37..e3c35e9 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestResult.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.Messages { /// diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/CommandExecutionResult.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/CommandExecutionResult.cs new file mode 100644 index 0000000..023691b --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/CommandExecutionResult.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Models +{ + using Logging; + using System.Collections.Generic; + using System.Collections.ObjectModel; + + /// + /// Contains command results. + /// + public class CommandExecutionResult + { + /// + /// Gets the results of a single command execution. + /// + public IEnumerable Results { get; private set; } + + /// + /// Gets the error records from a single command execution. + /// + public IEnumerable Errors { get; private set; } + + /// + /// Gets flag indicating if command resulted in one or more errors. + /// + public bool HadErrors { get; private set; } + + public CommandExecutionResult(IEnumerable results, IEnumerable errors, bool hadErrors) + { + this.Results = results; + this.Errors = errors; + this.HadErrors = hadErrors; + } + + public void LogErrors(Logger logger) + { + if (this.Errors != null) + { + foreach (object error in this.Errors) + { + LogError(error, logger); + } + } + } + + private void LogError(object error, Logger logger) + { + if (error is Collection) + { + foreach (object innerError in (Collection)error) + { + LogError(innerError, logger); + } + } + else + { + if (logger != null) + { + logger.LogError(error.ToString()); + } + } + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/GeneratedModule.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/GeneratedModule.cs new file mode 100644 index 0000000..c4971e6 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/GeneratedModule.cs @@ -0,0 +1,358 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Models +{ + using Credentials; + using Interfaces; + using Json; + using Logging; + using Messages; + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + + /// + /// Contains metadata for a generated module being tested. + /// + public class GeneratedModule + { + private IRunspaceManager runspace; + + public string ModulePath { get; set; } + + public IList RequiredModules { get; private set; } + + public IDictionary Operations { get; private set; } + + public Logger Logger { get; set; } + + public GeneratedModule(IRunspaceManager runspace) + { + this.runspace = runspace; + this.RequiredModules = new List(); + this.Operations = new Dictionary(); + } + + public virtual void LoadMetadataFromSpecification(JsonPathFinder jsonPathFinder) + { + // 1. Find x-ms-client-name directly under path + JsonQueryBuilder findNameUnderPath = new JsonQueryBuilder().Property("paths").RecursiveDescent().Property("x-ms-client-name"); + IEnumerable findResults = jsonPathFinder.Find(findNameUnderPath); + if (findResults != null) + { + foreach (JsonPathFinder node in findResults) + { + Log("Found x-ms-client-name under paths: " + node); + JsonPathFinder parameterNode = node.Parent.Parent; + string jsonName = parameterNode.Find("name").Single().GetValue().ToLowerInvariant(); + string parameterName = node.GetValue().ToLowerInvariant(); + JsonPathFinder operationNode = parameterNode.Parent.Parent.Parent; + JsonPathFinder operationIdNode = operationNode.Find("operationId").SingleOrDefault(); + if (operationIdNode != null) + { + string operationId = operationIdNode.GetValue().ToLowerInvariant(); + + if (this.Operations.ContainsKey(operationId) && this.Operations[operationId].Parameters.ContainsKey(parameterName)) + { + Log(String.Format(CultureInfo.CurrentCulture, "Mapping PowerShell parameter '{0}' to JSON property '{1}' for operation '{2}'", parameterName, jsonName, operationId)); + this.Operations[operationId].Parameters[parameterName].JsonName = jsonName; + } + } else + { + // Path Item level parameter + JsonPathFinder pathItemNode = parameterNode.Parent.Parent.Parent; + foreach (JsonPathFinder opIdNodeFromPathItem in pathItemNode.Find(new JsonQueryBuilder().RecursiveDescent().Property("operationId"))) + { + string operationId = opIdNodeFromPathItem.GetValue().ToLowerInvariant(); + if (this.Operations.ContainsKey(operationId) && this.Operations[operationId].Parameters.ContainsKey(parameterName)) + { + Log(String.Format(CultureInfo.CurrentCulture, "Mapping PowerShell parameter '{0}' to JSON property '{1}' for operation '{2}'", parameterName, jsonName, operationId)); + this.Operations[operationId].Parameters[parameterName].JsonName = jsonName; + } + } + } + } + } + + // 2. Find x-ms-client-name directly under x-ms-paths + JsonQueryBuilder findNameUnderExtendedPaths = new JsonQueryBuilder().Property("x-ms-paths").RecursiveDescent().Property("x-ms-client-name"); + findResults = jsonPathFinder.Find(findNameUnderExtendedPaths); + if (findResults != null) + { + foreach (JsonPathFinder node in findResults) + { + Log("Found x-ms-client-name under x-ms-paths: " + node); + JsonPathFinder parameterNode = node.Parent.Parent; + string jsonName = parameterNode.Find("name").Single().GetValue().ToLowerInvariant(); + JsonPathFinder operationNode = parameterNode.Parent.Parent.Parent; + string operationId = operationNode.Find("operationId").Single().GetValue().ToLowerInvariant(); + string parameterName = node.GetValue().ToLowerInvariant(); + if (this.Operations.ContainsKey(operationId) && this.Operations[operationId].Parameters.ContainsKey(parameterName)) + { + Log(String.Format(CultureInfo.CurrentCulture, "Mapping PowerShell parameter '{0}' to JSON property '{1}' for operation '{2}'", parameterName, jsonName, operationId)); + this.Operations[operationId].Parameters[parameterName].JsonName = jsonName; + } + } + } + + // 3. Find x-ms-client-flatten under paths and x-ms-paths + JsonQueryBuilder schemaRef = new JsonQueryBuilder().Property("schema").Property("$ref"); + JsonQueryBuilder findFlattenUnderPaths = new JsonQueryBuilder().Property("paths").RecursiveDescent().Property("x-ms-client-flatten"); + findResults = jsonPathFinder.Find(findFlattenUnderPaths); + if (findResults != null) + { + foreach (JsonPathFinder node in findResults) + { + if (!node.GetValue()) + { + continue; + } + JsonPathFinder parameterNode = node.Parent.Parent; + string definitionPath = parameterNode.Find(schemaRef).Single().GetValue(); + JsonPathFinder operationNode = parameterNode.Parent.Parent.Parent; + JsonPathFinder operationIdNode = operationNode.Find("operationId").SingleOrDefault(); + if (operationIdNode != null) + { + string operationId = operationIdNode.GetValue().ToLowerInvariant(); + + if (this.Operations.ContainsKey(operationId)) + { + JsonQueryBuilder definitionObject = new JsonQueryBuilder(definitionPath); + JsonPathFinder objectNode = jsonPathFinder.Find(definitionObject).Single(); + LoadMetadataFromDefinitionNode(operationId, objectNode, jsonPathFinder); + } + } + else + { + // Path Item level parameter + JsonPathFinder pathItemNode = parameterNode.Parent.Parent.Parent; + foreach (JsonPathFinder opIdNodeFromPathItem in pathItemNode.Find(new JsonQueryBuilder().RecursiveDescent().Property("operationId"))) + { + string operationId = opIdNodeFromPathItem.GetValue().ToLowerInvariant(); + if (this.Operations.ContainsKey(operationId)) + { + JsonQueryBuilder definitionObject = new JsonQueryBuilder(definitionPath); + JsonPathFinder objectNode = jsonPathFinder.Find(definitionObject).Single(); + LoadMetadataFromDefinitionNode(operationId, objectNode, jsonPathFinder); + } + } + } + } + } + + JsonQueryBuilder findFlattenUnderExtendedPaths = new JsonQueryBuilder().Property("x-ms-paths").RecursiveDescent().Property("x-ms-client-flatten"); + findResults = jsonPathFinder.Find(findFlattenUnderExtendedPaths); + if (findResults != null) + { + foreach (JsonPathFinder node in findResults) + { + if (!node.GetValue()) + { + continue; + } + JsonPathFinder parameterNode = node.Parent.Parent; + string definitionPath = parameterNode.Find(schemaRef).Single().GetValue(); + JsonPathFinder operationNode = parameterNode.Parent.Parent.Parent; + JsonPathFinder operationIdNode = operationNode.Find("operationId").SingleOrDefault(); + if (operationIdNode != null) + { + string operationId = operationIdNode.GetValue().ToLowerInvariant(); + + if (this.Operations.ContainsKey(operationId)) + { + JsonQueryBuilder definitionObject = new JsonQueryBuilder(definitionPath); + JsonPathFinder objectNode = jsonPathFinder.Find(definitionObject).Single(); + LoadMetadataFromDefinitionNode(operationId, objectNode, jsonPathFinder); + } + } + else + { + // Path Item level parameter + JsonPathFinder pathItemNode = parameterNode.Parent.Parent.Parent; + foreach (JsonPathFinder opIdNodeFromPathItem in pathItemNode.Find(new JsonQueryBuilder().RecursiveDescent().Property("operationId"))) + { + string operationId = opIdNodeFromPathItem.GetValue().ToLowerInvariant(); + if (this.Operations.ContainsKey(operationId)) + { + JsonQueryBuilder definitionObject = new JsonQueryBuilder(definitionPath); + JsonPathFinder objectNode = jsonPathFinder.Find(definitionObject).Single(); + LoadMetadataFromDefinitionNode(operationId, objectNode, jsonPathFinder); + } + } + } + } + } + + // 4. Find all ref'd parameters + JsonQueryBuilder findRefParameters = new JsonQueryBuilder().Property("paths").Any().Any().Property("parameters").ArrayIndex(new JsonQueryBuilder().Filter(new JsonQueryBuilder().HasProperty("$ref"))); + findResults = jsonPathFinder.Find(findRefParameters); + if (findResults != null) + { + foreach (JsonPathFinder node in findResults) + { + JsonPathFinder operationNode = node.Parent.Parent.Parent; + string operationId = operationNode.Find("operationId").Single().GetValue().ToLowerInvariant(); + JsonPathFinder globalParameter = jsonPathFinder.Find(new JsonQueryBuilder(node.Find("$ref").Single().GetValue())).Single(); + + // Check for x-ms-client-name + JsonPathFinder globalParameterClientName = globalParameter.Find("x-ms-client-name").SingleOrDefault(); + if (globalParameterClientName != null) + { + string parameterName = globalParameterClientName.GetValue().ToLowerInvariant(); + Log("Found x-ms-client-name under global parameter: " + parameterName); + string jsonName = globalParameter.Find("name").Single().GetValue().ToLowerInvariant(); + if (this.Operations.ContainsKey(operationId) && this.Operations[operationId].Parameters.ContainsKey(parameterName)) + { + Log(String.Format(CultureInfo.CurrentCulture, "Mapping PowerShell parameter '{0}' to JSON property '{1}' for operation '{2}'", parameterName, jsonName, operationId)); + this.Operations[operationId].Parameters[parameterName].JsonName = jsonName; + } + } + + // Check for x-ms-client-flatten + JsonPathFinder globalParameterClientFlatten = globalParameter.Find("x-ms-client-flatten").SingleOrDefault(); + if (globalParameterClientFlatten != null) + { + string definitionPath = globalParameter.Find(schemaRef).Single().GetValue(); + if (this.Operations.ContainsKey(operationId)) + { + JsonQueryBuilder definitionObject = new JsonQueryBuilder(definitionPath); + JsonPathFinder objectNode = jsonPathFinder.Find(definitionObject).Single(); + LoadMetadataFromDefinitionNode(operationId, objectNode, jsonPathFinder); + } + } + } + } + } + + public virtual CommandExecutionResult ProcessRequest(LiveTestRequest request, LiveTestCredentialFactory credentialsFactory) + { + if (this.Logger != null) + { + this.Logger.LogAsync("Translating credentials..."); + } + credentialsFactory.TranslateCredentialsObjects(request); + CommandExecutionResult result = null; + string operationId = request.OperationId == null ? null : request.OperationId.ToLowerInvariant(); + if (this.Logger != null) + { + this.Logger.LogAsync("Operation ID of message: " + operationId); + } + if (!String.IsNullOrEmpty(operationId) && this.Operations.ContainsKey(operationId)) + { + if (this.Logger != null) + { + this.Logger.LogAsync("Processing operation..."); + } + OperationData op = this.Operations[operationId]; + // Create the command + ICommandBuilder command = this.runspace.CreateCommand(); + command.Command = op.Command; + foreach (string parameterName in op.Parameters.Keys) + { + if (request.Params != null && request.Params.ContainsKey(parameterName.ToLowerInvariant())) + { + command.AddParameter(parameterName, request.Params[parameterName.ToLowerInvariant()]); + } else + { + this.Logger.LogAsync("Request missing parameter: " + parameterName); + } + } + + // Process credentials + IEnumerable credProviders = credentialsFactory.Create(request, this.Logger); + foreach (ICredentialProvider credProvider in credProviders) + { + credProvider.Process(command); + } + + // Run the command + result = command.Invoke(); + } + else + { + // error + if (this.Logger != null) + { + this.Logger.LogError("Operation ID was not found in module under test."); + } + } + + return result; + } + + public virtual CommandExecutionResult Load(bool force = false) + { + foreach (GeneratedModule requiredModule in this.RequiredModules) + { + CommandExecutionResult requiredModuleResult = requiredModule.Load(force: force); + if (requiredModuleResult != null && requiredModuleResult.HadErrors) + { + return requiredModuleResult; + } + } + + ICommandBuilder command = this.runspace.CreateCommand(); + command.Command = "Import-Module"; + if (!String.IsNullOrEmpty(ModulePath)) + { + command.AddParameter("Name", this.ModulePath); + } + + if (force) + { + command.AddParameter("Force", true); + } + + return command.Invoke(); + } + + private void LoadMetadataFromDefinitionNode(string operationId, JsonPathFinder definitionNode, JsonPathFinder rootFinder) + { + JsonQueryBuilder findName = new JsonQueryBuilder().RecursiveDescent().Property("x-ms-client-name"); + IEnumerable findResults = definitionNode.Find(findName); + if (findResults != null) + { + foreach (JsonPathFinder node in findResults) + { + string jsonName = node.Key.ToLowerInvariant(); + string parameterName = node.GetValue().ToLowerInvariant(); + Log("Found x-ms-client-name under definition property: " + parameterName); + if (this.Operations.ContainsKey(operationId) && this.Operations[operationId].Parameters.ContainsKey(parameterName)) + { + Log(String.Format(CultureInfo.CurrentCulture, "Mapping PowerShell parameter '{0}' to JSON property '{1}' for operation '{2}'", parameterName, jsonName, operationId)); + this.Operations[operationId].Parameters[parameterName].JsonName = jsonName; + } + } + } + + JsonQueryBuilder findFlatten = new JsonQueryBuilder().RecursiveDescent().Property("x-ms-client-flatten"); + JsonQueryBuilder findRef = new JsonQueryBuilder().Property("$ref"); + findResults = definitionNode.Find(findFlatten); + if (findResults != null) + { + foreach (JsonPathFinder node in findResults) + { + if (!node.GetValue()) + { + continue; + } + JsonPathFinder refNode = node.Parent.Parent.Find(findRef).Single(); + string definitionPath = refNode.GetValue(); + JsonQueryBuilder definitionObject = new JsonQueryBuilder(definitionPath); + JsonPathFinder objectNode = rootFinder.Find(definitionObject).Single(); + LoadMetadataFromDefinitionNode(operationId, objectNode, rootFinder); + } + } + } + + private void Log(string msg) + { + if (this.Logger != null) + { + this.Logger.LogAsync(msg); + } + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/OperationData.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/OperationData.cs new file mode 100644 index 0000000..02bf89b --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/OperationData.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Models +{ + using System.Collections.Generic; + + /// + /// Contains metadata for a single operation. + /// + public class OperationData + { + /// + /// Gets the operation ID as specified by the Open API spec. + /// + public string OperationId { get; private set; } + + /// + /// Gets the command corresponding to this operation. + /// + public string Command { get; private set; } + + /// + /// Gets or sets the parameters for this operation. + /// + public Dictionary Parameters { get; set; } + + public OperationData(string operationId, string command) + { + this.Command = command; + this.OperationId = operationId; + this.Parameters = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/ParameterData.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/ParameterData.cs new file mode 100644 index 0000000..f35c0c2 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/ParameterData.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Models +{ + /// + /// Contains metadata for a single parameter. + /// + public class ParameterData + { + /// + /// Name of the parameter when calling a certain operation + /// + public string Name { get; set; } + + /// + /// Name of the parameter when serialized as JSON. + /// + public string JsonName { get; set; } + + /// + /// Type of this parameter. + /// + public RuntimeTypeData Type { get; set; } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/RuntimeTypeData.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/RuntimeTypeData.cs new file mode 100644 index 0000000..0ed9b82 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/RuntimeTypeData.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Models +{ + using System; + using System.Collections.Generic; + + /// + /// Contains metadata for a single Type whose properties differ from its JSON object representation. + /// + public class RuntimeTypeData + { + /// + /// Type represented. + /// + public Type Type { get; set; } + + /// + /// Properties of this type. Keyed on the JSON representation. + /// + public IDictionary Properties { get; private set; } + + public RuntimeTypeData() : this(null) + { + } + + public RuntimeTypeData(Type t) + { + this.Properties = new Dictionary(); + this.Type = t; + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PSSwagger.LTF.Lib.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PSSwagger.LTF.Lib.csproj index 3ffeb7c..9f36504 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PSSwagger.LTF.Lib.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PSSwagger.LTF.Lib.csproj @@ -1,15 +1,17 @@ - net452;netcoreapp2.0 + net452 - - + + + + + + + + + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PowerShell/PowerShellCommand.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PowerShell/PowerShellCommand.cs index 032534d..7f41ff6 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PowerShell/PowerShellCommand.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PowerShell/PowerShellCommand.cs @@ -1,35 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.PowerShell { using Interfaces; + using Models; using System; - using System.Collections; + using System.Management.Automation.Runspaces; /// /// Build a PowerShell command. /// public class PowerShellCommand : ICommandBuilder { + private IRunspaceManager runspace; + private Command powershellCommand; + public string Command { get { - throw new NotImplementedException(); - } + if (powershellCommand != null) + { + return powershellCommand.CommandText; + } + return String.Empty; + } set { - throw new NotImplementedException(); + this.powershellCommand = new Command(value); + } + } + + public IRunspaceManager Runspace + { + get + { + return this.runspace; } } - public ICommandBuilder AddParameter(string parameterName, object parameterValue, bool switchParameter) + public PowerShellCommand(IRunspaceManager runspace) { - throw new NotImplementedException(); + this.runspace = runspace; + this.powershellCommand = null; + } + + public ICommandBuilder AddParameter(string parameterName, object parameterValue) + { + if (String.IsNullOrEmpty(parameterName)) + { + throw new ArgumentNullException("parameterName"); + } + + if (this.powershellCommand == null) + { + throw new InvalidOperationException("Set the Command property before using AddParameter."); + } + + this.powershellCommand.Parameters.Add(parameterName, parameterValue); + return this; } - public IEnumerable Invoke() + public CommandExecutionResult Invoke() { - throw new NotImplementedException(); + return this.runspace.Invoke(this.powershellCommand); } } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PowerShell/PowerShellRunspace.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PowerShell/PowerShellRunspace.cs index 12e8ce4..1eb7c0f 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PowerShell/PowerShellRunspace.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PowerShell/PowerShellRunspace.cs @@ -1,32 +1,197 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.PowerShell { using Interfaces; + using Logging; + using Models; using System; - using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Reflection; /// /// Manage a PowerShell runspace. /// public class PowerShellRunspace : IRunspaceManager { + private Logger logger; + private Runspace runspace; + public PowerShellRunspace(Logger logger) + { + this.runspace = RunspaceFactory.CreateRunspace(); + this.runspace.Open(); + this.logger = logger; + } + public ICommandBuilder CreateCommand() { - throw new NotImplementedException(); + return new PowerShellCommand(this); } public GeneratedModule GetModuleInfo(string modulePath) { - throw new NotImplementedException(); + if (this.logger != null) + { + this.logger.LogAsync(String.Format(CultureInfo.CurrentCulture, "Parsing module: {0}", modulePath)); + } + string moduleName = String.Empty; + GeneratedModule module = new GeneratedModule(this); + module.ModulePath = modulePath; + if (File.Exists(modulePath) || Directory.Exists(modulePath)) + { + // First we have to run Test-ModuleManifest to get RequiredModules + if (!Path.IsPathRooted(modulePath)) + { + throw new ModulePathNotRootedException(modulePath); + } + + ICommandBuilder testModuleManifest = this.CreateCommand(); + testModuleManifest.Command = "Test-ModuleManifest"; + testModuleManifest.AddParameter("Path", modulePath); + // Note that this returns errors, but RequiredModules is still filled out. + CommandExecutionResult result = testModuleManifest.Invoke(); + + PSModuleInfo testResult = result.Results.FirstOrDefault() as PSModuleInfo; + if (testResult == null) + { + if (this.logger != null) + { + this.logger.LogError("No module object was returned by Test-ModuleManifest."); + } + throw new CommandFailedException(testModuleManifest, result, "Can't get module information for an invalid module."); + } + + moduleName = testResult.Name; + foreach (PSModuleInfo requiredModule in testResult.RequiredModules) + { + GeneratedModule requiredModuleInfo = new GeneratedModule(this); + requiredModuleInfo.ModulePath = requiredModule.Name; + module.RequiredModules.Add(requiredModuleInfo); + } + } else + { + // Otherwise assume the input is a module name instead of a path and attempt a direct load + moduleName = modulePath; + } + + if (this.logger != null) + { + this.logger.LogAsync(String.Format(CultureInfo.CurrentCulture, "Loading module: {0}", moduleName)); + } + CommandExecutionResult loadResult = module.Load(); + if (loadResult.HadErrors) + { + loadResult.LogErrors(this.logger); + throw new CommandFailedException(null, loadResult, "Failed to load module."); + } + + ICommandBuilder getModule = this.CreateCommand(); + getModule.Command = "Get-Module"; + getModule.AddParameter("Name", moduleName); + CommandExecutionResult getModuleResult = getModule.Invoke(); + if (getModuleResult.HadErrors) + { + getModuleResult.LogErrors(this.logger); + throw new CommandFailedException(getModule, getModuleResult, "Failed to get module after loading."); + } + + PSModuleInfo moduleInfo = getModuleResult.Results.First() as PSModuleInfo; + foreach (string entry in moduleInfo.ExportedFunctions.Keys) + { + FunctionInfo commandInfo = moduleInfo.ExportedFunctions[entry] as FunctionInfo; + if (this.logger != null) + { + this.logger.LogAsync("Parsing command: " + entry); + } + if (commandInfo != null) + { + foreach (CommandParameterSetInfo parameterSet in commandInfo.ParameterSets) + { + OperationData operationData = new OperationData(parameterSet.Name, entry); + if (this.logger != null) + { + this.logger.LogAsync("Found operation: " + parameterSet.Name); + } + foreach (CommandParameterInfo commandParameterInfo in parameterSet.Parameters) + { + ParameterData parameterData = new ParameterData(); + parameterData.Name = commandParameterInfo.Name.ToLowerInvariant(); + parameterData.Type = new RuntimeTypeData(commandParameterInfo.ParameterType); + operationData.Parameters.Add(parameterData.Name.ToLowerInvariant(), parameterData); + } + + module.Operations[operationData.OperationId.ToLowerInvariant()] = operationData; + } + } + } + + return module; } - public IEnumerable Invoke(string script) + public CommandExecutionResult Invoke(object command) { - throw new NotImplementedException(); + if (command is Command) + { + LogPowerShellCommand(command as Command); + Pipeline pipeline = this.runspace.CreatePipeline(); + pipeline.Commands.Add((Command)command); + IEnumerable results = null; + IEnumerable errors = null; + bool hadErrors = false; + try + { + Collection psResults = pipeline.Invoke(); + results = psResults.Select(psObj => + { + FieldInfo fieldInfo = psResults[0].GetType().GetField("immediateBaseObjectIsEmpty", BindingFlags.Instance | BindingFlags.NonPublic); + if (fieldInfo != null && !(bool)fieldInfo.GetValue(psObj)) + { + return psObj.ImmediateBaseObject; + } else + { + return psObj; + } + }); + hadErrors = pipeline.Error.Count > 0; + if (hadErrors) + { + errors = pipeline.Error.ReadToEnd().AsEnumerable(); + } + } catch (Exception e) + { + if (this.logger != null) + { + this.logger.LogError("Invoke ran into an exception: " + e.ToString()); + } + errors = new object[] { e }; + hadErrors = true; + } + + return new CommandExecutionResult(results, errors, hadErrors); + } + + return null; } - public void SetSessionVariable(string variableName, object variableValue) + private void LogPowerShellCommand(Command command) { - throw new NotImplementedException(); + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + sb.AppendFormat("Executing PowerShell command: {0}\n", command.CommandText); + foreach (CommandParameter parameter in command.Parameters) + { + sb.AppendFormat(CultureInfo.InvariantCulture, " {0} = {1}", parameter.Name, parameter.Value); + } + if (this.logger != null) + { + this.logger.LogAsync(sb.ToString()); + } } } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/ServiceTracing/ClientRuntimeServiceTracer.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/ServiceTracing/ClientRuntimeServiceTracer.cs new file mode 100644 index 0000000..09f2332 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/ServiceTracing/ClientRuntimeServiceTracer.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.ServiceTracing +{ + using Interfaces; + using Logging; + using Microsoft.Rest; + using System; + using System.Collections.Generic; + using System.Net.Http; + + /// + /// Traces operations at the service layer using Microsoft.Rest.ClientRuntime. + /// + public class ClientRuntimeServiceTracer : IServiceTracer, IServiceClientTracingInterceptor + { + private Logger logger; + private string invocationId; + + /// + /// Gets the collection of HttpResponseMessages caught by this tracer. + /// + public IList HttpResponses { get; private set; } + + public ClientRuntimeServiceTracer(long invocationId, Logger logger) + { + this.HttpResponses = new List(); + this.logger = logger; + this.invocationId = invocationId.ToString(); + } + + public void Configuration(string source, string name, string value) + { + } + + public void EnterMethod(string invocationId, object instance, string method, IDictionary parameters) + { + } + + public void ExitMethod(string invocationId, object returnValue) + { + } + + public void Information(string message) + { + } + + public void ReceiveResponse(string invocationId, HttpResponseMessage response) + { + if (this.invocationId.Equals(invocationId)) + { + if (this.logger != null) + { + this.logger.LogAsync(response.ToString()); + } + + this.HttpResponses.Add(response); + } + } + + public void SendRequest(string invocationId, HttpRequestMessage request) + { + if (this.invocationId.Equals(invocationId)) + { + if (this.logger != null) + { + this.logger.LogAsync(request.ToString()); + } + } + } + + public void TraceError(string invocationId, Exception exception) + { + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/ServiceTracing/ServiceTracingManager.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/ServiceTracing/ServiceTracingManager.cs new file mode 100644 index 0000000..6c54129 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/ServiceTracing/ServiceTracingManager.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.ServiceTracing +{ + using Interfaces; + using Logging; + using Microsoft.Rest; + + /// + /// Manages tracing for the service layer. + /// + public class ServiceTracingManager + { + public virtual long GetNextInvocationId() + { + return ServiceClientTracing.NextInvocationId + 1; + } + + /// + /// Create a tracer for the given invocation ID. + /// + /// Invocation ID to create IServiceTracer for + /// Logging source to send trace messages to + /// Service tracer + public virtual IServiceTracer CreateTracer(long invocationId, Logger logger) + { + ClientRuntimeServiceTracer tracer = new ClientRuntimeServiceTracer(invocationId, logger); + ServiceClientTracing.AddTracingInterceptor(tracer); + return tracer; + } + + public virtual void RemoveTracer(IServiceTracer tracer) + { + if (tracer is ClientRuntimeServiceTracer) + { + ServiceClientTracing.RemoveTracingInterceptor((ClientRuntimeServiceTracer)tracer); + } + } + + public virtual void EnableTracing() + { + ServiceClientTracing.IsEnabled = true; + } + + public virtual void DisableTracing() + { + ServiceClientTracing.IsEnabled = false; + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.csproj index b0bf3c4..e722f8a 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.csproj @@ -29,12 +29,23 @@ 4 + + C:\Users\brywang\Documents\Visual Studio 2015\Projects\LiveTestClient\packages\Microsoft.Rest.ClientRuntime.2.3.4\lib\net45\Microsoft.Rest.ClientRuntime.dll + False + packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll True + + packages\System.Management.Automation.6.1.7601.17515\lib\net45\System.Management.Automation.dll + True + + + + @@ -42,65 +53,47 @@ + + + {726C2CFE-2795-45DC-AD28-F211DE79801D} + PSSwagger.LTF.IO.Lib + + - - - Interfaces\IOutputPipe.cs - - - Interfaces\IInputPipe.cs - - - Interfaces\ICommandBuilder.cs - - - Interfaces\IRunspaceManager.cs - - - IO\JsonBlockPipe.cs - - - IO\StandardInputPipe.cs - - - IO\StandardOutputPipe.cs - - - IO\NamedPipeClient.cs - - - IO\NamedPipeServer.cs + + Models\%(FileName).cs - - IO\NullPipe.cs + + Interfaces\%(FileName).cs - - Messages\JsonRpcBase.cs + + Messages\%(FileName).cs - - Messages\LiveTestRequest.cs + + Logging\%(FileName).cs - - Messages\LiveTestResponse.cs + + PowerShell\%(FileName).cs - - Messages\LiveTestResult.cs + + Exceptions\%(FileName).cs - - Messages\LiveTestError.cs + + Credentials\%(FileName).cs - - Logging\Logger.cs + + Converters\%(FileName).cs - - PowerShell\PowerShellCommand.cs + + ServiceTracing\%(FileName).cs - - PowerShell\PowerShellRunspace.cs + + Json\%(FileName).cs + diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.sln b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.sln index e9620ad..f463ad5 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.sln +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSSwagger.LTF.Lib", "PSSwagger.LTF.Lib.csproj", "{D2E4CBA1-BA97-4C60-9645-317666DF6C9F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSSwagger.LTF.IO.Lib", "..\..\PSSwagger.LTF.IO.Lib\vs-csproj\PSSwagger.LTF.IO.Lib.csproj", "{6C53850A-7758-452C-8C02-CBAA894B814E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {D2E4CBA1-BA97-4C60-9645-317666DF6C9F}.Debug|Any CPU.Build.0 = Debug|Any CPU {D2E4CBA1-BA97-4C60-9645-317666DF6C9F}.Release|Any CPU.ActiveCfg = Release|Any CPU {D2E4CBA1-BA97-4C60-9645-317666DF6C9F}.Release|Any CPU.Build.0 = Release|Any CPU + {6C53850A-7758-452C-8C02-CBAA894B814E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C53850A-7758-452C-8C02-CBAA894B814E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C53850A-7758-452C-8C02-CBAA894B814E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C53850A-7758-452C-8C02-CBAA894B814E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/app.config b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/app.config new file mode 100644 index 0000000..dde2c3c --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/packages.config b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/packages.config index 810e559..9b5ec60 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/packages.config +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/packages.config @@ -1,4 +1,6 @@  + + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/AzureCredentialProviderTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/AzureCredentialProviderTests.cs new file mode 100644 index 0000000..f71d824 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/AzureCredentialProviderTests.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests +{ + using Credentials; + using Logging; + using Mocks; + using Models; + using System; + using System.Collections.Generic; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests for AzureCredentialProvider type. + /// + public class AzureCredentialProviderTests + { + private readonly XUnitOutputPipe output; + private readonly XUnitOutputPipe error; + private readonly Logger logger; + public AzureCredentialProviderTests(ITestOutputHelper output) + { + this.output = new XUnitOutputPipe(output); + this.error = new XUnitOutputPipe(output, logAsErrors: true); + this.logger = new Logger(this.output, this.error); + } + + [Fact] + public void HappyPathDefaultCredType() + { + AzureCredentialProvider test = new AzureCredentialProvider(this.logger); + test.Set("tenantId", "testTenantId"); + test.Set("clientId", "testClientId"); + test.Set("secret", "testSecret"); + MockRunspaceManager runspace = new MockRunspaceManager(); + runspace.CommandBuilders.Add(new MockCommandBuilder(runspace) + { + MockResult = new CommandExecutionResult(null, null, false) + }); + runspace.CommandBuilders.Add(new MockCommandBuilder(runspace) + { + MockResult = new CommandExecutionResult(null, null, false) + }); + test.Process(runspace.CommandBuilders[0]); + + Assert.Equal(2, runspace.InvokeHistory.Count); + Assert.Equal("import-module [name azurerm.profile]", ((string)runspace.InvokeHistory[0]).ToLowerInvariant()); + Assert.Equal("add-azurermaccount [credential (testclientid testsecret)] [tenantid testtenantid] [serviceprincipal true]", ((string)runspace.InvokeHistory[1]).ToLowerInvariant()); + } + + [Fact] + public void ExceptionWhenCredentialsPropertiesAreMissing() + { + AzureCredentialProvider test = new AzureCredentialProvider(this.logger); + test.Set("tenantId", "testTenantId"); + test.Set("clientId", "testClientId"); + MockRunspaceManager runspace = new MockRunspaceManager(); + Assert.Throws(() => test.Process(runspace.CreateCommand())); + } + + [Fact] + public void ExceptionWhenCredentialsPropertiesAreEmpty() + { + AzureCredentialProvider test = new AzureCredentialProvider(this.logger); + test.Set("tenantId", "testTenantId"); + test.Set("clientId", "testClientId"); + test.Set("secret", String.Empty); + MockRunspaceManager runspace = new MockRunspaceManager(); + Assert.Throws(() => test.Process(runspace.CreateCommand())); + } + + [Fact] + public void ExceptionWhenCommandFails() + { + AzureCredentialProvider test = new AzureCredentialProvider(this.logger); + test.Set("tenantId", "testTenantId"); + test.Set("clientId", "testClientId"); + test.Set("secret", "testSecret"); + MockRunspaceManager runspace = new MockRunspaceManager(); + runspace.CommandBuilders.Add(new MockCommandBuilder(runspace) + { + MockResult = new CommandExecutionResult(null, new List() { "This is an error" }, true) + }); + Assert.Throws(() => test.Process(runspace.CommandBuilders[0])); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/DynamicTypedObjectConverterTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/DynamicTypedObjectConverterTests.cs new file mode 100644 index 0000000..f9fa8f7 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/DynamicTypedObjectConverterTests.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests +{ + using Converters; + using Models; + using Newtonsoft.Json; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests for DynamicTypedObjectConverter type. + /// + public class DynamicTypedObjectConverterTests + { + private readonly ITestOutputHelper output; + public DynamicTypedObjectConverterTests(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void ConvertObjectWithRenames() + { + string json = "{\"prop\":\"test\",\"booleanproperty\":true}"; + RuntimeTypeData typeData = new RuntimeTypeData(); + typeData.Type = typeof(DynamicTypedObjectConverterTestsObject); + typeData.Properties.Add("property", new ParameterData() + { + JsonName = "prop", + Name = "property", + Type = new RuntimeTypeData() + { + Type = typeof(string) + } + }); + DynamicTypedObjectConverter converter = new DynamicTypedObjectConverter(typeData); + JsonSerializerSettings settings = new JsonSerializerSettings(); + settings.Converters.Add(converter); + + DynamicTypedObjectConverterTestsObject result = JsonConvert.DeserializeObject(json, settings); + Assert.NotNull(result); + Assert.Equal("test", result.Property); + Assert.True(result.BooleanProperty); + + string reserialized = JsonConvert.SerializeObject(result, settings); + Assert.Equal(json, reserialized); + } + + [Fact] + public void ConvertNestedObject() + { + string json = "{ \"obj\": { \"prop\": \"test\", \"booleanproperty\": true }, \"unconvertedobject\": { \"prop\": \"5\" } }"; + string expectedJson = "{\"obj\":{\"prop\":\"test\",\"booleanproperty\":true},\"unconvertedobject\":{\"Property\":null}}"; + RuntimeTypeData childTypeData = new RuntimeTypeData(); + childTypeData.Type = typeof(DynamicTypedObjectConverterTestsObject); + childTypeData.Properties.Add("property", new ParameterData() + { + JsonName = "prop", + Name = "property", + Type = new RuntimeTypeData() + { + Type = typeof(string) + } + }); + RuntimeTypeData parentTypeData = new RuntimeTypeData(); + parentTypeData.Type = typeof(DynamicTypedObjectConverterTestsNestedObject); + parentTypeData.Properties.Add("object", new ParameterData() + { + JsonName = "obj", + Name = "object", + Type = childTypeData + }); + DynamicTypedObjectConverter childConverter = new DynamicTypedObjectConverter(childTypeData); + DynamicTypedObjectConverter parentConverter = new DynamicTypedObjectConverter(parentTypeData); + JsonSerializerSettings settings = new JsonSerializerSettings(); + settings.Converters.Add(childConverter); + settings.Converters.Add(parentConverter); + + DynamicTypedObjectConverterTestsNestedObject result = JsonConvert.DeserializeObject(json, settings); + Assert.NotNull(result); + Assert.NotNull(result.Object); + Assert.Equal("test", result.Object.Property); + Assert.True(result.Object.BooleanProperty); + Assert.NotNull(result.UnconvertedObject); + Assert.Null(result.UnconvertedObject.Property); + + string reserialized = JsonConvert.SerializeObject(result, settings); + Assert.Equal(expectedJson, reserialized); + } + } + + public class DynamicTypedObjectConverterTestsObject + { + public string Property { get; set; } + public bool BooleanProperty { get; set; } + } + + public class DynamicTypedObjectConverterTestsNestedObject + { + public DynamicTypedObjectConverterTestsObject Object { get; set; } + public DynamicTypedObjectConverterTestsUnconvertedObject UnconvertedObject { get; set; } + } + + public class DynamicTypedObjectConverterTestsUnconvertedObject + { + public string Property { get; set; } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/GeneratedModuleTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/GeneratedModuleTests.cs index 76ab7bd..abf8984 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/GeneratedModuleTests.cs +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/GeneratedModuleTests.cs @@ -1,6 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.UnitTests { + using Converters; + using Json; + using Messages; using Mocks; + using Models; + using System.Collections.Generic; using Xunit; using Xunit.Abstractions; @@ -26,10 +34,9 @@ public void BasicLoadTest() module.ModulePath = "test.psd1"; module.Load(); - Assert.Equal("Import-Module", runspace.Builder.Command); - Assert.True(runspace.Builder.Parameters.ContainsKey("Name")); - Assert.Equal("test.psd1", runspace.Builder.Parameters["Name"].Item1); - Assert.False(runspace.Builder.Parameters["Name"].Item2); + Assert.Equal("Import-Module", runspace.CommandBuilders[0].Command); + Assert.True(runspace.CommandBuilders[0].Parameters.ContainsKey("Name")); + Assert.Equal("test.psd1", runspace.CommandBuilders[0].Parameters["Name"]); } /// @@ -43,13 +50,11 @@ public void ForceLoadTest() module.ModulePath = "test.psd1"; module.Load(force: true); - Assert.Equal("Import-Module", runspace.Builder.Command); - Assert.True(runspace.Builder.Parameters.ContainsKey("Name")); - Assert.Equal("test.psd1", runspace.Builder.Parameters["Name"].Item1); - Assert.False(runspace.Builder.Parameters["Name"].Item2); - Assert.True(runspace.Builder.Parameters.ContainsKey("Force")); - Assert.True((bool)runspace.Builder.Parameters["Force"].Item1); - Assert.True(runspace.Builder.Parameters["Force"].Item2); + Assert.Equal("Import-Module", runspace.CommandBuilders[0].Command); + Assert.True(runspace.CommandBuilders[0].Parameters.ContainsKey("Name")); + Assert.Equal("test.psd1", runspace.CommandBuilders[0].Parameters["Name"]); + Assert.True(runspace.CommandBuilders[0].Parameters.ContainsKey("Force")); + Assert.True((bool)runspace.CommandBuilders[0].Parameters["Force"]); } /// @@ -63,10 +68,9 @@ public void LoadWithName() module.ModulePath = "PackageManagement"; module.Load(); - Assert.Equal("Import-Module", runspace.Builder.Command); - Assert.True(runspace.Builder.Parameters.ContainsKey("Name")); - Assert.Equal("PackageManagement", runspace.Builder.Parameters["Name"].Item1); - Assert.False(runspace.Builder.Parameters["Name"].Item2); + Assert.Equal("Import-Module", runspace.CommandBuilders[0].Command); + Assert.True(runspace.CommandBuilders[0].Parameters.ContainsKey("Name")); + Assert.Equal("PackageManagement", runspace.CommandBuilders[0].Parameters["Name"]); } /// @@ -83,8 +87,8 @@ public void LoadWithRequiredModules() module.RequiredModules.Add(requiredModule); module.Load(); - Assert.Equal("import-module [name powershellget false]", runspace.Builder.InvokeHistory[0].ToLowerInvariant()); - Assert.Equal("import-module [name packagemanagement false]", runspace.Builder.InvokeHistory[1].ToLowerInvariant()); + Assert.Equal("import-module [name powershellget]", ((string)runspace.InvokeHistory[0]).ToLowerInvariant()); + Assert.Equal("import-module [name packagemanagement]", ((string)runspace.InvokeHistory[1]).ToLowerInvariant()); } /// @@ -104,9 +108,394 @@ public void ForceIsPropogatedToAllModules() module.RequiredModules.Add(requiredModule); module.Load(force: true); - Assert.Equal("import-module [name psreadline false] [force true true]", runspace.Builder.InvokeHistory[0].ToLowerInvariant()); - Assert.Equal("import-module [name powershellget false] [force true true]", runspace.Builder.InvokeHistory[1].ToLowerInvariant()); - Assert.Equal("import-module [name packagemanagement false] [force true true]", runspace.Builder.InvokeHistory[2].ToLowerInvariant()); + Assert.Equal("import-module [name psreadline] [force true]", ((string)runspace.InvokeHistory[0]).ToLowerInvariant()); + Assert.Equal("import-module [name powershellget] [force true]", ((string)runspace.InvokeHistory[1]).ToLowerInvariant()); + Assert.Equal("import-module [name packagemanagement] [force true]", ((string)runspace.InvokeHistory[2]).ToLowerInvariant()); + } + + [Fact] + public void ProcessOperationSimpleParameters() + { + MockRunspaceManager runspace = new MockRunspaceManager(); + GeneratedModule module = new GeneratedModule(runspace); + module.Operations["thing_get"] = new OperationData("Thing_Get", "Get-Thing") + { + Parameters = new Dictionary() + { + { "Integer", new ParameterData() { Name = "Integer", Type = new RuntimeTypeData(typeof(int)) } }, + { "Boolean", new ParameterData() { Name = "Boolean", Type = new RuntimeTypeData(typeof(bool)) } }, + { "Decimal", new ParameterData() { Name = "Decimal", Type = new RuntimeTypeData(typeof(double)) } }, + { "String", new ParameterData() { Name = "String", Type = new RuntimeTypeData(typeof(string)) } } + } + }; + + MockTestCredentialFactory credentialsFactory = new MockTestCredentialFactory(); + LiveTestRequest request = new LiveTestRequest(); + request.Method = "Things.Thing_Get"; + request.OperationId = "Thing_Get"; + request.Params = new Dictionary(); + request.Params["integer"] = 5; + request.Params["boolean"] = true; + request.Params["decimal"] = 1.2; + request.Params["string"] = "testValue"; + + CommandExecutionResult result = module.ProcessRequest(request, credentialsFactory); + Assert.Equal(1, runspace.InvokeHistory.Count); + Assert.Equal("Get-Thing [Integer 5] [Boolean True] [Decimal 1.2] [String testValue]", ((string)runspace.InvokeHistory[0])); + } + + [Fact] + public void ProcessOperationComplexParameters() + { + MockRunspaceManager runspace = new MockRunspaceManager(); + GeneratedModule module = new GeneratedModule(runspace); + module.Operations["thing_get"] = new OperationData("Thing_Get", "Get-Thing") + { + Parameters = new Dictionary() + { + { "Parameter", new ParameterData() { Name = "Parameter", Type = new RuntimeTypeData(typeof(GeneratedModuleTestsObject)) } } + } + }; + + MockTestCredentialFactory credentialsFactory = new MockTestCredentialFactory(); + LiveTestRequest request = new LiveTestRequest(); + request.Method = "Things.Thing_Get"; + request.OperationId = "Thing_Get"; + request.Params = new Dictionary(); + request.Params["parameter"] = new GeneratedModuleTestsObject() + { + String = "testValue", + Number = 500, + Object = new GeneratedModuleTestsSubObject() + { + Decimal = 1.2, + Boolean = true + } + }; + + CommandExecutionResult result = module.ProcessRequest(request, credentialsFactory); + Assert.Equal(1, runspace.InvokeHistory.Count); + Assert.Equal("Get-Thing [Parameter {[String testValue] [Number 500] [Object [Decimal 1.2] [Boolean True]]}]", (string)runspace.InvokeHistory[0]); + } + + [Fact] + public void ProcessOperationWithCredentials() + { + MockRunspaceManager runspace = new MockRunspaceManager(); + GeneratedModule module = new GeneratedModule(runspace); + module.Operations["thing_get"] = new OperationData("Thing_Get", "Get-Thing"); + + MockTestCredentialFactory credentialsFactory = new MockTestCredentialFactory(); + credentialsFactory.RegisterProvider("commandbased", new CommandBasedCredentialProvider()); + credentialsFactory.RegisterProvider("parameterbased", new ParameterBasedCredentialProvider()); + LiveTestRequest request = new LiveTestRequest(); + request.Method = "Things.Thing_Get"; + request.OperationId = "Thing_Get"; + request.Params = new Dictionary() + { + { "__reserved", new Dictionary() + { + { "credentials", new LiveTestCredentials[] { new LiveTestCredentials() + { + Type = "commandBased", + Properties = new Dictionary() + { + { "tenantId", "testTenantId" }, + { "clientId", "testClientId" }, + { "secret", "testSecret" } + } + }, new LiveTestCredentials() + { + Type = "parameterBased", + Properties = new Dictionary() + { + { "tenantId", "testTenantId" }, + { "clientId", "testClientId" }, + { "secret", "testSecret" } + } + } } + } + } + } + }; + request.Params["parameter"] = new GeneratedModuleTestsObject() + { + String = "testValue", + Number = 500, + Object = new GeneratedModuleTestsSubObject() + { + Decimal = 1.2, + Boolean = true + } + }; + + CommandExecutionResult result = module.ProcessRequest(request, credentialsFactory); + Assert.Equal(2, runspace.InvokeHistory.Count); + Assert.Equal("Login-Account", (string)runspace.InvokeHistory[0]); + Assert.Equal("Login-Account [CredentialKey testCredentials]", (string)runspace.InvokeHistory[1]); + } + + [Fact] + public void ProcessOperationFromJsonFull() + { + MockRunspaceManager runspace = new MockRunspaceManager(); + GeneratedModule module = new GeneratedModule(runspace); + module.Operations["thing_get"] = new OperationData("Thing_Get", "Get-Thing") + { + Parameters = new Dictionary() + { + { "parameter", new ParameterData() { Name = "Parameter", Type = new RuntimeTypeData(typeof(GeneratedModuleTestsObject)) } } + } + }; + + string json = "{\"method\":\"Things.Thing_Get\",\"params\":{\"__reserved\":{\"credentials\":[{\"x-ps-credtype\":\"commandBased\",\"tenantId\":\"testTenantId\",\"clientId\":\"testClientId\",\"secret\":\"testSecret\"},{\"x-ps-credtype\":\"parameterBased\",\"tenantId\":\"testTenantId\",\"clientId\":\"testClientId\",\"secret\":\"testSecret\"}]},\"parameter\":{\"string\":\"testValue\",\"number\":500,\"object\":{\"decimal\":1.2,\"boolean\":true}}}}"; + LiveTestRequestConverter converter = new LiveTestRequestConverter(module); + Newtonsoft.Json.JsonSerializerSettings settings = new Newtonsoft.Json.JsonSerializerSettings(); + converter.RegisterSelf(settings); + + MockTestCredentialFactory credentialsFactory = new MockTestCredentialFactory(); + credentialsFactory.RegisterProvider("commandbased", new CommandBasedCredentialProvider()); + credentialsFactory.RegisterProvider("parameterbased", new ParameterBasedCredentialProvider()); + + LiveTestRequest request = Newtonsoft.Json.JsonConvert.DeserializeObject(json, settings); + CommandExecutionResult result = module.ProcessRequest(request, credentialsFactory); + Assert.Equal("Login-Account [parameter {[String testValue] [Number 500] [Object [Decimal 1.2] [Boolean True]]}]", (string)runspace.InvokeHistory[0]); + Assert.Equal("Login-Account [parameter {[String testValue] [Number 500] [Object [Decimal 1.2] [Boolean True]]}] [CredentialKey testCredentials]", (string)runspace.InvokeHistory[1]); + } + + [Fact] + public void ParameterRenamedUsingAutoRestExt() + { + GeneratedModule expectedModule, testModule; + GetLoadMetadataFromSpecificationTestData(out expectedModule, out testModule); + + // Root document + MockJsonPathFinder rootFinder = new MockJsonPathFinder(); + // Operation's "operationId" property + MockJsonPathFinder operationIdFinder = new MockJsonPathFinder(); + operationIdFinder.ValueMock = "operationid"; + // Operation node + MockJsonPathFinder operationFinder = new MockJsonPathFinder(); + operationFinder.QueryMocks["operationId"] = new List() { operationIdFinder }; + // Parameter's "name" property + MockJsonPathFinder parameterNameFinder = new MockJsonPathFinder(); + parameterNameFinder.ValueMock = "nameFromSpec"; + // Parameter node + MockJsonPathFinder parameterFinder = new MockJsonPathFinder(); + parameterFinder.QueryMocks["name"] = new List() { parameterNameFinder }; + parameterFinder.ParentMock = operationFinder.MakeDummyParent(2); + // Parameter's "x-ms-client-name" property + MockJsonPathFinder clientNameFinder = new MockJsonPathFinder(); + clientNameFinder.ValueMock = "psParameterName"; + clientNameFinder.ParentMock = parameterFinder.MakeDummyParent(); + + // The root will be queried for x-ms-client-name nodes + rootFinder.QueryMocks["paths..x-ms-client-name"] = new List() { clientNameFinder }; + + testModule.LoadMetadataFromSpecification(rootFinder); + + AssertModulesArEqual(expectedModule, testModule); + } + + [Fact] + public void ExtendedPathsParameterRenamedUsingAutoRestExt() + { + GeneratedModule expectedModule, testModule; + GetLoadMetadataFromSpecificationTestData(out expectedModule, out testModule); + + // Root document + MockJsonPathFinder rootFinder = new MockJsonPathFinder(); + // Operation's "operationId" property + MockJsonPathFinder operationIdFinder = new MockJsonPathFinder(); + operationIdFinder.ValueMock = "operationid"; + // Operation node + MockJsonPathFinder operationFinder = new MockJsonPathFinder(); + operationFinder.QueryMocks["operationId"] = new List() { operationIdFinder }; + // Parameter's "name" property + MockJsonPathFinder parameterNameFinder = new MockJsonPathFinder(); + parameterNameFinder.ValueMock = "nameFromSpec"; + // Parameter node + MockJsonPathFinder parameterFinder = new MockJsonPathFinder(); + parameterFinder.QueryMocks["name"] = new List() { parameterNameFinder }; + parameterFinder.ParentMock = operationFinder.MakeDummyParent(2); + // Parameter's "x-ms-client-name" property + MockJsonPathFinder clientNameFinder = new MockJsonPathFinder(); + clientNameFinder.ValueMock = "psParameterName"; + clientNameFinder.ParentMock = parameterFinder.MakeDummyParent(); + + // The root will be queried for x-ms-client-name nodes + rootFinder.QueryMocks["x-ms-paths..x-ms-client-name"] = new List() { clientNameFinder }; + + testModule.LoadMetadataFromSpecification(rootFinder); + + AssertModulesArEqual(expectedModule, testModule); + } + + [Fact] + public void FlattenedParameterRenamedUsingAutoRestExt() + { + GeneratedModule expectedModule, testModule; + GetLoadMetadataFromSpecificationTestData(out expectedModule, out testModule); + + // Root document + MockJsonPathFinder rootFinder = new MockJsonPathFinder(); + // Operation's "operationId" property + MockJsonPathFinder operationIdFinder = new MockJsonPathFinder(); + operationIdFinder.ValueMock = "operationid"; + // Operation node + MockJsonPathFinder operationFinder = new MockJsonPathFinder(); + operationFinder.QueryMocks["operationId"] = new List() { operationIdFinder }; + // Parameter's "schema.$ref" property + MockJsonPathFinder schemaRefFinder = new MockJsonPathFinder(); + schemaRefFinder.ValueMock = "#/definitions/object"; + // Parameter node + MockJsonPathFinder parameterFinder = new MockJsonPathFinder(); + parameterFinder.QueryMocks["schema.$ref"] = new List() { schemaRefFinder }; + parameterFinder.ParentMock = operationFinder.MakeDummyParent(2); + // Parameter's "x-ms-client-flatten" property + MockJsonPathFinder clientFlattenFinder = new MockJsonPathFinder(); + clientFlattenFinder.ValueMock = true; + clientFlattenFinder.ParentMock = parameterFinder.MakeDummyParent(); + // DefinitionProperty's "x-ms-client-name" property + MockJsonPathFinder clientNameFinder = new MockJsonPathFinder(); + clientNameFinder.ValueMock = "psParameterName"; + clientNameFinder.KeyMock = "nameFromSpec"; + MockJsonPathFinder objectDefinitionFinder = new MockJsonPathFinder(); + // The object definition will be queried for x-ms-client-name + objectDefinitionFinder.QueryMocks["..x-ms-client-name"] = new List() { clientNameFinder }; + // The root will be queried for x-ms-client-flatten nodes + rootFinder.QueryMocks["paths..x-ms-client-flatten"] = new List() { clientFlattenFinder }; + // The root will be queried for definitions + rootFinder.QueryMocks["$.definitions.object"] = new List() { objectDefinitionFinder }; + + testModule.LoadMetadataFromSpecification(rootFinder); + + AssertModulesArEqual(expectedModule, testModule); + } + + [Fact] + public void NestedFlattenedParameterRenamedUsingAutoRest() + { + GeneratedModule expectedModule, testModule; + GetLoadMetadataFromSpecificationTestData(out expectedModule, out testModule); + + // Root document + MockJsonPathFinder rootFinder = new MockJsonPathFinder(); + // Operation's "operationId" property + MockJsonPathFinder operationIdFinder = new MockJsonPathFinder(); + operationIdFinder.ValueMock = "operationid"; + // Operation node + MockJsonPathFinder operationFinder = new MockJsonPathFinder(); + operationFinder.QueryMocks["operationId"] = new List() { operationIdFinder }; + // Parameter's "schema.$ref" property + MockJsonPathFinder schemaRefFinder = new MockJsonPathFinder(); + schemaRefFinder.ValueMock = "#/definitions/object"; + // Parameter node + MockJsonPathFinder parameterFinder = new MockJsonPathFinder(); + parameterFinder.QueryMocks["schema.$ref"] = new List() { schemaRefFinder }; + parameterFinder.ParentMock = operationFinder.MakeDummyParent(2); + // Parameter's "x-ms-client-flatten" property + MockJsonPathFinder clientFlattenFinder = new MockJsonPathFinder(); + clientFlattenFinder.ValueMock = true; + clientFlattenFinder.ParentMock = parameterFinder.MakeDummyParent(); + // DefinitionProperty's "$ref" property + MockJsonPathFinder definitionPropertyRefFinder = new MockJsonPathFinder(); + definitionPropertyRefFinder.ValueMock = "#/definitions/nestedObject"; + // DefinitionProperty node + MockJsonPathFinder definitionPropertyFinder = new MockJsonPathFinder(); + definitionPropertyFinder.QueryMocks["$ref"] = new List() { definitionPropertyRefFinder }; + // DefinitionProperty's "x-ms-client-flatten" property + MockJsonPathFinder definitionClientFlattenFinder = new MockJsonPathFinder(); + definitionClientFlattenFinder.ValueMock = true; + definitionClientFlattenFinder.ParentMock = definitionPropertyFinder.MakeDummyParent(); + // object's node + MockJsonPathFinder objectDefinitionFinder = new MockJsonPathFinder(); + // The object definition will be queried for x-ms-client-flatten + objectDefinitionFinder.QueryMocks["..x-ms-client-flatten"] = new List() { definitionClientFlattenFinder }; + // NestedDefinitionProperty's "x-ms-client-name" property + MockJsonPathFinder clientNameFinder = new MockJsonPathFinder(); + clientNameFinder.ValueMock = "psParameterName"; + clientNameFinder.KeyMock = "nameFromSpec"; + // nestedObject's node + MockJsonPathFinder nestedObjectDefinitionFinder = new MockJsonPathFinder(); + // The object definition will be queried for x-ms-client-name + nestedObjectDefinitionFinder.QueryMocks["..x-ms-client-name"] = new List() { clientNameFinder }; + // The root will be queried for x-ms-client-flatten nodes + rootFinder.QueryMocks["paths..x-ms-client-flatten"] = new List() { clientFlattenFinder }; + // The root will be queried for definitions + rootFinder.QueryMocks["$.definitions.object"] = new List() { objectDefinitionFinder }; + rootFinder.QueryMocks["$.definitions.nestedObject"] = new List() { nestedObjectDefinitionFinder }; + + testModule.LoadMetadataFromSpecification(rootFinder); + + AssertModulesArEqual(expectedModule, testModule); + } + + private void AssertModulesArEqual(GeneratedModule expectedModule, GeneratedModule actualModule) + { + Assert.Equal(expectedModule.Operations.Count, actualModule.Operations.Count); + foreach (string key in expectedModule.Operations.Keys) + { + Assert.True(actualModule.Operations.ContainsKey(key)); + foreach (string pkey in expectedModule.Operations[key].Parameters.Keys) + { + Assert.True(actualModule.Operations[key].Parameters.ContainsKey(pkey)); + Assert.Equal(expectedModule.Operations[key].Parameters[pkey].Name, actualModule.Operations[key].Parameters[pkey].Name); + Assert.Equal(expectedModule.Operations[key].Parameters[pkey].JsonName, actualModule.Operations[key].Parameters[pkey].JsonName); + } + } + } + + private void GetLoadMetadataFromSpecificationTestData(out GeneratedModule expectedModule, out GeneratedModule testModule) + { + MockRunspaceManager runspace = new MockRunspaceManager(); + expectedModule = new GeneratedModule(runspace); + expectedModule.Operations["operationid"] = new OperationData("operationid", "command") + { + Parameters = new Dictionary() + { + { "psparametername", new ParameterData() + { + Name = "psparametername", + JsonName = "namefromspec" + } + } + } + }; + + testModule = new GeneratedModule(runspace); + testModule.Operations["operationid"] = new OperationData("operationid", "command") + { + Parameters = new Dictionary() + { + { "psparametername", new ParameterData() + { + Name = "psparametername" + } + } + } + }; + } + } + + public class GeneratedModuleTestsObject + { + public string String { get; set; } + public long Number { get; set; } + public GeneratedModuleTestsSubObject Object { get; set; } + public override string ToString() + { + return string.Format("{{[String {0}] [Number {1}] [Object {2}]}}", String, Number, Object); + } + } + + public class GeneratedModuleTestsSubObject + { + public double Decimal { get; set; } + public bool Boolean { get; set; } + public override string ToString() + { + return string.Format("[Decimal {0}] [Boolean {1}]", Decimal, Boolean); } } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonBlockPipeTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonBlockPipeTests.cs deleted file mode 100644 index 8cdba14..0000000 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonBlockPipeTests.cs +++ /dev/null @@ -1,131 +0,0 @@ -namespace PSSwagger.LTF.Lib.UnitTests -{ - using IO; - using System.Threading.Tasks; - using Xunit; - using Xunit.Abstractions; - - /// - /// Tests for JsonBlockPipe type. - /// - public class JsonBlockPipeTests - { - private readonly ITestOutputHelper output; - public JsonBlockPipeTests(ITestOutputHelper output) - { - this.output = output; - } - - /// - /// Test reading a simple object. - /// - [Fact] - public async Task ReadSimpleObject() - { - Mocks.StringPipe stringPipe = new Mocks.StringPipe(); - stringPipe.BufferInputString("{ \"stringprop\": \"test\", \"booleanprop\": true, \"numberprop\": 456 }"); - JsonBlockPipe reader = new JsonBlockPipe(stringPipe); - SimpleObject result = await reader.ReadBlockAsync(); - Assert.Equal("test", result.StringProp); - Assert.True(result.BooleanProp); - Assert.Equal((long)456, result.NumberProp); - } - - /// - /// Test that characters are ignored before and after a valid block. - /// - [Fact] - public async Task IgnoreCharsBeforeAndAfter() - { - Mocks.StringPipe stringPipe = new Mocks.StringPipe(); - stringPipe.BufferInputString("abc{ \"stringprop\": \"test\", \"booleanprop\": true, \"numberprop\": 456 }123"); - JsonBlockPipe reader = new JsonBlockPipe(stringPipe); - SimpleObject result = await reader.ReadBlockAsync(); - Assert.Equal("test", result.StringProp); - Assert.True(result.BooleanProp); - Assert.Equal((long)456, result.NumberProp); - } - - /// - /// Test reading an object with a child object. - /// - [Fact] - public async Task ReadParentChildRelationship() - { - Mocks.StringPipe stringPipe = new Mocks.StringPipe(); - stringPipe.BufferInputString("{ \"parentprop\": \"parent\", \"child\": { \"stringprop\": \"test\", \"booleanprop\": true, \"numberprop\": 456 } }"); - JsonBlockPipe reader = new JsonBlockPipe(stringPipe); - ParentChildObject result = await reader.ReadBlockAsync(); - Assert.Equal("parent", result.ParentProp); - Assert.Equal("test", result.Child.StringProp); - Assert.True(result.Child.BooleanProp); - Assert.Equal((long)456, result.Child.NumberProp); - } - - /// - /// Test reading an object that subclasses another object. - /// - [Fact] - public async Task ReadSubclassRelationship() - { - Mocks.StringPipe stringPipe = new Mocks.StringPipe(); - stringPipe.BufferInputString("{ \"stringprop\": \"test\", \"booleanprop\": true, \"numberprop\": 456, \"id\": 12345 }"); - JsonBlockPipe reader = new JsonBlockPipe(stringPipe); - SubObject result = await reader.ReadBlockAsync(); - Assert.Equal("test", result.StringProp); - Assert.True(result.BooleanProp); - Assert.Equal((long)456, result.NumberProp); - Assert.Equal((long)12345, result.Id); - } - - /// - /// Test reading multiple objects with garbage in the middle. - /// - [Fact] - public async Task ReadMultipleObjects() - { - Mocks.StringPipe stringPipe = new Mocks.StringPipe(); - stringPipe.BufferInputString("{ \"stringprop\": \"test\", \"booleanprop\": true, \"numberprop\": 456, \"id\": 12345 }garb"); - stringPipe.BufferInputString("age{ \"stringprop\": \"test2\", \"booleanprop\": true, \"numberprop\": 456, \"id\": 12345 }"); - JsonBlockPipe reader = new JsonBlockPipe(stringPipe); - SubObject result = await reader.ReadBlockAsync(); - Assert.Equal("test", result.StringProp); - Assert.True(result.BooleanProp); - Assert.Equal((long)456, result.NumberProp); - Assert.Equal((long)12345, result.Id); - - result = await reader.ReadBlockAsync(); - Assert.Equal("test2", result.StringProp); - Assert.True(result.BooleanProp); - Assert.Equal((long)456, result.NumberProp); - Assert.Equal((long)12345, result.Id); - } - } - - /// - /// Test object with simple properties. - /// - internal class SimpleObject - { - public string StringProp { get; set; } - public bool BooleanProp { get; set; } - public long NumberProp { get; set; } - } - - /// - /// Test object containing parent-child relationship. - /// - internal class ParentChildObject - { - public string ParentProp { get; set; } - public SimpleObject Child { get; set; } - } - - /// - /// Inheritance test. - /// - internal class SubObject : SimpleObject - { - public long Id { get; set; } - } -} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonPathFinderTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonPathFinderTests.cs new file mode 100644 index 0000000..9972644 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonPathFinderTests.cs @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests +{ + using Json; + using System.Linq; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests for JsonPathFinder type. + /// + public class JsonPathFinderTests + { + private readonly ITestOutputHelper output; + public JsonPathFinderTests(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void ParentNodeOfChildNotNull() + { + string json = "{\"test\":5}"; + JsonPathFinder finder = new JsonPathFinder(json); + JsonPathFinder childFinder = finder.Find("test").SingleOrDefault(); + Assert.NotNull(childFinder.Parent); + } + + [Fact] + public void ParentOfRootIsNull() + { + string json = "{\"test\":5}"; + JsonPathFinder finder = new JsonPathFinder(json); + Assert.Null(finder.Parent); + } + + [Fact] + public void FindExistingNodes() + { + string json = "{\"test\":5}"; + JsonPathFinder finder = new JsonPathFinder(json); + JsonPathFinder childFinder = finder.Find("test").SingleOrDefault(); + Assert.NotNull(childFinder); + } + + [Fact] + public void FindNodesCasing() + { + string json = "{\"test\":5}"; + JsonPathFinder finder = new JsonPathFinder(json); + JsonPathFinder childFinder = finder.Find("TEST").SingleOrDefault(); + Assert.Null(childFinder); + } + + [Fact] + public void FindNoNodes() + { + string json = "{\"test\":5}"; + JsonPathFinder finder = new JsonPathFinder(json); + JsonPathFinder childFinder = finder.Find("notATest").SingleOrDefault(); + Assert.Null(childFinder); + } + + [Fact] + public void RootDocumentKey() + { + string json = "{\"test\":5}"; + JsonPathFinder finder = new JsonPathFinder(json); + Assert.Null(finder.Key); + } + + [Fact] + public void FindKeyOfObject() + { + string json = "{\"test\":{\"sub\":{}}"; + JsonPathFinder finder = new JsonPathFinder(json); + JsonPathFinder childFinder = finder.Find("..sub").SingleOrDefault(); + Assert.NotNull(childFinder); + Assert.Equal("test", childFinder.Key); + } + + [Fact] + public void FindKeyOfArray() + { + string json = "{\"test\":{\"sub\":[]}"; + JsonPathFinder finder = new JsonPathFinder(json); + JsonPathFinder childFinder = finder.Find("..sub").SingleOrDefault(); + Assert.NotNull(childFinder); + Assert.Equal("test", childFinder.Key); + } + + [Fact] + public void FindKeyOfValue() + { + string json = "{\"test\":{\"sub\":5}"; + JsonPathFinder finder = new JsonPathFinder(json); + JsonPathFinder childFinder = finder.Find("..sub").SingleOrDefault(); + Assert.NotNull(childFinder); + Assert.Equal("test", childFinder.Key); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonQueryBuilderTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonQueryBuilderTests.cs new file mode 100644 index 0000000..033a677 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonQueryBuilderTests.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests +{ + using Json; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests for JsonQueryBuilder type. + /// + public class JsonQueryBuilderTests + { + private readonly ITestOutputHelper output; + public JsonQueryBuilderTests(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void RecursiveDescent() + { + JsonQueryBuilder builder = new JsonQueryBuilder(); + string actualQuery = builder.RecursiveDescent().ToQuery(); + Assert.Equal("..", actualQuery); + } + + [Fact] + public void Property() + { + JsonQueryBuilder builder = new JsonQueryBuilder(); + string actualQuery = builder.Property("prop").ToQuery(); + Assert.Equal("prop", actualQuery); + } + + [Fact] + public void PropertyAfterRecursiveDescent() + { + JsonQueryBuilder builder = new JsonQueryBuilder(); + builder.RecursiveDescent(); + string actualQuery = builder.Property("prop").ToQuery(); + Assert.Equal("..prop", actualQuery); + } + + [Fact] + public void PropertyAfterProperty() + { + JsonQueryBuilder builder = new JsonQueryBuilder(); + builder.Property("test"); + string actualQuery = builder.Property("prop").ToQuery(); + Assert.Equal("test.prop", actualQuery); + } + + [Fact] + public void CreateFromJsonPath() + { + JsonQueryBuilder builder = new JsonQueryBuilder("#/definitions/object"); + string actualQuery = builder.ToQuery(); + Assert.Equal("$.definitions.object", actualQuery); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonRpcPipeTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonRpcPipeTests.cs new file mode 100644 index 0000000..70bb139 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/JsonRpcPipeTests.cs @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests +{ + using IO; + using System.Threading.Tasks; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests for JsonRpcPipe type. + /// + public class JsonRpcPipeTests + { + private readonly ITestOutputHelper output; + public JsonRpcPipeTests(ITestOutputHelper output) + { + this.output = output; + } + + /// + /// Test reading a simple object. + /// + [Fact] + public async Task ReadSimpleObject() + { + Mocks.StringPipe stringPipe = new Mocks.StringPipe(); + Mocks.StringPipe outputPipe = new Mocks.StringPipe(); + SimpleObject data = new SimpleObject(); + data.BooleanProp = true; + data.StringProp = "test"; + data.NumberProp = 456; + stringPipe.BufferJsonRpcBlock(data); + JsonRpcPipe reader = new JsonRpcPipe(stringPipe, outputPipe); + SimpleObject result = await reader.ReadBlock(); + Assert.Equal(data.StringProp, result.StringProp); + Assert.Equal(data.BooleanProp, result.BooleanProp); + Assert.Equal(data.NumberProp, result.NumberProp); + } + + /// + /// Test reading an object with a child object. + /// + [Fact] + public async Task ReadParentChildRelationship() + { + Mocks.StringPipe stringPipe = new Mocks.StringPipe(); + Mocks.StringPipe outputPipe = new Mocks.StringPipe(); + ParentChildObject data = new ParentChildObject(); + data.ParentProp = "parent"; + data.Child = new SimpleObject() + { + StringProp = "test", + BooleanProp = true, + NumberProp = 456 + }; + stringPipe.BufferJsonRpcBlock(data); + JsonRpcPipe reader = new JsonRpcPipe(stringPipe, outputPipe); + ParentChildObject result = await reader.ReadBlock(); + Assert.Equal(data.ParentProp, result.ParentProp); + Assert.NotNull(data.Child); + Assert.Equal(data.Child.StringProp, result.Child.StringProp); + Assert.Equal(data.Child.BooleanProp, result.Child.BooleanProp); + Assert.Equal(data.Child.NumberProp, result.Child.NumberProp); + } + + /// + /// Test reading an object that subclasses another object. + /// + [Fact] + public async Task ReadSubclassRelationship() + { + Mocks.StringPipe stringPipe = new Mocks.StringPipe(); + Mocks.StringPipe outputPipe = new Mocks.StringPipe(); + SubObject data = new SubObject() + { + StringProp = "test", + BooleanProp = true, + NumberProp = 456, + Id = 12345 + }; + stringPipe.BufferJsonRpcBlock(data); + JsonRpcPipe reader = new JsonRpcPipe(stringPipe, outputPipe); + SubObject result = await reader.ReadBlock(); + Assert.Equal(data.StringProp, result.StringProp); + Assert.Equal(data.BooleanProp, result.BooleanProp); + Assert.Equal(data.NumberProp, result.NumberProp); + Assert.Equal(data.Id, result.Id); + } + + /// + /// Test reading multiple objects with garbage in the middle. + /// + [Fact] + public async Task ReadMultipleObjects() + { + Mocks.StringPipe stringPipe = new Mocks.StringPipe(); + Mocks.StringPipe outputPipe = new Mocks.StringPipe(); + SimpleObject data1 = new SimpleObject() + { + BooleanProp = true, + StringProp = "test", + NumberProp = 456, + }; + SimpleObject data2 = new SimpleObject() + { + BooleanProp = false, + StringProp = "test2", + NumberProp = 123, + }; + stringPipe.BufferJsonRpcBlock(new object[] { data1, data2 }); + JsonRpcPipe reader = new JsonRpcPipe(stringPipe, outputPipe); + SimpleObject result = await reader.ReadBlock(); + Assert.Equal(data1.StringProp, result.StringProp); + Assert.Equal(data1.BooleanProp, result.BooleanProp); + Assert.Equal(data1.NumberProp, result.NumberProp); + + result = await reader.ReadBlock(); + Assert.Equal(data2.StringProp, result.StringProp); + Assert.Equal(data2.BooleanProp, result.BooleanProp); + Assert.Equal(data2.NumberProp, result.NumberProp); + } + + /// + /// Test reading multiple objects with garbage in the middle. + /// + [Fact] + public async Task ReadMessageWithOptionalHeaders() + { + Mocks.StringPipe stringPipe = new Mocks.StringPipe(); + Mocks.StringPipe outputPipe = new Mocks.StringPipe(); + SimpleObject data = new SimpleObject() + { + BooleanProp = true, + StringProp = "test", + NumberProp = 456, + }; + stringPipe.BufferJsonRpcBlock(data, type: "test"); + JsonRpcPipe reader = new JsonRpcPipe(stringPipe, outputPipe); + SimpleObject result = await reader.ReadBlock(); + Assert.Equal(data.StringProp, result.StringProp); + Assert.Equal(data.BooleanProp, result.BooleanProp); + Assert.Equal(data.NumberProp, result.NumberProp); + } + + [Fact] + public async Task WriteMessageWithHeaders() + { + Mocks.StringPipe stringPipe = new Mocks.StringPipe(); + Mocks.StringPipe outputPipe = new Mocks.StringPipe(); + SimpleObject data = new SimpleObject() + { + BooleanProp = true, + StringProp = "test", + NumberProp = 456, + }; + JsonRpcPipe reader = new JsonRpcPipe(stringPipe, outputPipe); + await reader.WriteBlock(data); + string output = outputPipe.OutputBuffer.ToString(); + Assert.Equal("Content-Length: 57\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n{\"StringProp\":\"test\",\"BooleanProp\":true,\"NumberProp\":456}\r\n", output); + } + } + + /// + /// Test object with simple properties. + /// + internal class SimpleObject + { + public string StringProp { get; set; } + public bool BooleanProp { get; set; } + public long NumberProp { get; set; } + } + + /// + /// Test object containing parent-child relationship. + /// + internal class ParentChildObject + { + public string ParentProp { get; set; } + public SimpleObject Child { get; set; } + } + + /// + /// Inheritance test. + /// + internal class SubObject : SimpleObject + { + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestCredentialFactoryTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestCredentialFactoryTests.cs new file mode 100644 index 0000000..0d5899b --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestCredentialFactoryTests.cs @@ -0,0 +1,323 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests +{ + using Credentials; + using Interfaces; + using Logging; + using Messages; + using Mocks; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests for LiveTestCredentialFactory type. + /// + public class LiveTestCredentialFactoryTests + { + private const string credTypeProperty = "x-ps-credtype"; + private readonly XUnitOutputPipe output; + private readonly XUnitOutputPipe error; + private readonly Logger logger; + public LiveTestCredentialFactoryTests(ITestOutputHelper output) + { + this.output = new XUnitOutputPipe(output); + this.error = new XUnitOutputPipe(output, logAsErrors: true); + this.logger = new Logger(this.output, this.error); + } + + /// + /// Test that no x-ps-credtype property defaults to Azure auth. + /// + [Fact] + public void NoCredTypeProperty() + { + LiveTestRequest request = new LiveTestRequest(); + request.Params = new Dictionary() + { + { "__reserved", new Dictionary() + { + { "credentials", new LiveTestCredentials[] { new LiveTestCredentials() + { + Properties = new Dictionary() + { + { "tenantId", "testTenantId" }, + { "clientId", "testClientId" }, + { "secret", "testSecret" } + } + } } + } + } + } + }; + + LiveTestCredentialFactory test = new LiveTestCredentialFactory(); + IEnumerable result = test.Create(request, this.logger); + Assert.Equal(1, result.Count()); + ICredentialProvider first = result.First(); + Assert.NotNull(first); + Assert.IsType(first); + AzureCredentialProvider provider = (AzureCredentialProvider)first; + Assert.Equal("testTenantId", provider.TenantId); + Assert.Equal("testClientId", provider.ClientId); + Assert.Equal("testSecret", provider.Secret); + } + + /// + /// Test that x-ps-credtype can be set to Azure. + /// + [Fact] + public void AzureCredType() + { + LiveTestRequest request = new LiveTestRequest(); + request.Params = new Dictionary() + { + { "__reserved", new Dictionary() + { + { "credentials", new LiveTestCredentials[] { new LiveTestCredentials() + { + Type = "azure", + Properties = new Dictionary() + { + { "tenantId", "testTenantId" }, + { "clientId", "testClientId" }, + { "secret", "testSecret" } + } + } } + } + } + } + }; + + LiveTestCredentialFactory test = new LiveTestCredentialFactory(); + IEnumerable result = test.Create(request, this.logger); + Assert.Equal(1, result.Count()); + ICredentialProvider first = result.First(); + Assert.NotNull(first); + Assert.IsType(first); + AzureCredentialProvider provider = (AzureCredentialProvider)first; + Assert.Equal("testTenantId", provider.TenantId); + Assert.Equal("testClientId", provider.ClientId); + Assert.Equal("testSecret", provider.Secret); + } + + /// + /// Test that Create ignores x-ps-credtype casing. + /// + [Fact] + public void IgnoresCase() + { + LiveTestRequest request = new LiveTestRequest(); + request.Params = new Dictionary() + { + { "__reserved", new Dictionary() + { + { "credentials", new LiveTestCredentials[] { new LiveTestCredentials() + { + Type = "AzUrE", + Properties = new Dictionary() + { + { "tenantId", "testTenantId" }, + { "clientId", "testClientId" }, + { "secret", "testSecret" } + } + } } + } + } + } + }; + + LiveTestCredentialFactory test = new LiveTestCredentialFactory(); + IEnumerable result = test.Create(request, this.logger); + Assert.Equal(1, result.Count()); + ICredentialProvider first = result.First(); + Assert.NotNull(first); + Assert.IsType(first); + AzureCredentialProvider provider = (AzureCredentialProvider)first; + Assert.Equal("testTenantId", provider.TenantId); + Assert.Equal("testClientId", provider.ClientId); + Assert.Equal("testSecret", provider.Secret); + } + + /// + /// Test behavior when the x-ps-credtype provider is unknown. + /// + [Fact] + public void UnknownProvider() + { + LiveTestRequest request = new LiveTestRequest(); + request.Params = new Dictionary() + { + { "__reserved", new Dictionary() + { + { "credentials", new LiveTestCredentials[] { new LiveTestCredentials() + { + Type = Path.GetRandomFileName(), + Properties = new Dictionary() + { + { "tenantId", "testTenantId" }, + { "clientId", "testClientId" }, + { "secret", "testSecret" } + } + } } + } + } + } + }; + + LiveTestCredentialFactory test = new LiveTestCredentialFactory(); + IEnumerable result = test.Create(request, this.logger); + Assert.Equal(0, result.Count()); + } + + /// + /// Test behavior when a mix of known and unknown providers is given. + /// + [Fact] + public void MultipleProviders() + { + LiveTestRequest request = new LiveTestRequest(); + request.Params = new Dictionary() + { + { "__reserved", new Dictionary() + { + { "credentials", new LiveTestCredentials[] { new LiveTestCredentials() + { + Type = Path.GetRandomFileName(), + Properties = new Dictionary() + { + { "tenantId", "testTenantId" }, + { "clientId", "testClientId" }, + { "secret", "testSecret" } + } + }, + new LiveTestCredentials() + { + Type = "azure", + Properties = new Dictionary() + { + { "tenantId", "testTenantId" }, + { "clientId", "testClientId" }, + { "secret", "testSecret" } + } + } } + } + } + } + }; + + LiveTestCredentialFactory test = new LiveTestCredentialFactory(); + IEnumerable result = test.Create(request, this.logger); + Assert.Equal(1, result.Count()); + ICredentialProvider first = result.First(); + Assert.NotNull(first); + Assert.IsType(first); + AzureCredentialProvider provider = (AzureCredentialProvider)first; + Assert.Equal("testTenantId", provider.TenantId); + Assert.Equal("testClientId", provider.ClientId); + Assert.Equal("testSecret", provider.Secret); + } + + [Fact] + public void TranslateWithoutExtension() + { + string requestJson = "{ \"params\": { \"__reserved\": { \"credentials\": { \"tenantId\": \"testTenantId\", \"clientId\": \"testClientId\", \"secret\": \"testSecret\" } } } }"; + LiveTestRequest request = Newtonsoft.Json.JsonConvert.DeserializeObject(requestJson); + + LiveTestCredentialFactory test = new LiveTestCredentialFactory(); + test.TranslateCredentialsObjects(request); + + request.Params.ContainsKey("__reserved"); + Assert.True(request.Params["__reserved"] is Dictionary); + Dictionary reservedParams = (Dictionary)request.Params["__reserved"]; + + Assert.True(reservedParams.ContainsKey("credentials")); + Assert.True(((Dictionary)request.Params["__reserved"])["credentials"] is LiveTestCredentials[]); + LiveTestCredentials[] credsArray = (LiveTestCredentials[])reservedParams["credentials"]; + + Assert.Equal(1, credsArray.Length); + LiveTestCredentials result = credsArray[0]; + Assert.Equal("azure", result.Type); + Assert.Equal("testTenantId", result.Properties["tenantId"].ToString()); + Assert.Equal("testClientId", result.Properties["clientId"].ToString()); + Assert.Equal("testSecret", result.Properties["secret"].ToString()); + } + + [Fact] + public void TranslateWithExtension() + { + string requestJson = "{ \"params\": { \"__reserved\": { \"credentials\": { \"tenantId\": \"testTenantId\", \"clientId\": \"testClientId\", \"secret\": \"testSecret\", \"x-ps-credtype\": \"random\" } } } }"; + LiveTestRequest request = Newtonsoft.Json.JsonConvert.DeserializeObject(requestJson); + + LiveTestCredentialFactory test = new LiveTestCredentialFactory(); + test.TranslateCredentialsObjects(request); + + request.Params.ContainsKey("__reserved"); + Assert.True(request.Params["__reserved"] is Dictionary); + Dictionary reservedParams = (Dictionary)request.Params["__reserved"]; + + Assert.True(reservedParams.ContainsKey("credentials")); + Assert.True(((Dictionary)request.Params["__reserved"])["credentials"] is LiveTestCredentials[]); + LiveTestCredentials[] credsArray = (LiveTestCredentials[])reservedParams["credentials"]; + + Assert.Equal(1, credsArray.Length); + LiveTestCredentials result = credsArray[0]; + Assert.Equal("random", result.Type); + Assert.Equal("testTenantId", result.Properties["tenantId"].ToString()); + Assert.Equal("testClientId", result.Properties["clientId"].ToString()); + Assert.Equal("testSecret", result.Properties["secret"].ToString()); + } + + [Fact] + public void TranslateMultipleCredentials() + { + string requestJson = "{ \"params\": { \"__reserved\": { \"credentials\": [{ \"tenantId\": \"testTenantId\", \"clientId\": \"testClientId\", \"secret\": \"testSecret\", \"x-ps-credtype\": \"random\" },{ \"tenantId\": \"testTenantId\", \"clientId\": \"testClientId\", \"secret\": \"testSecret\" }] } } }"; + LiveTestRequest request = Newtonsoft.Json.JsonConvert.DeserializeObject(requestJson); + + LiveTestCredentialFactory test = new LiveTestCredentialFactory(); + test.TranslateCredentialsObjects(request); + + request.Params.ContainsKey("__reserved"); + Assert.True(request.Params["__reserved"] is Dictionary); + Dictionary reservedParams = (Dictionary)request.Params["__reserved"]; + + Assert.True(reservedParams.ContainsKey("credentials")); + Assert.True(((Dictionary)request.Params["__reserved"])["credentials"] is LiveTestCredentials[]); + LiveTestCredentials[] credsArray = (LiveTestCredentials[])reservedParams["credentials"]; + + Assert.Equal(2, credsArray.Length); + LiveTestCredentials result = credsArray[0]; + Assert.Equal("random", result.Type); + Assert.Equal("testTenantId", result.Properties["tenantId"].ToString()); + Assert.Equal("testClientId", result.Properties["clientId"].ToString()); + Assert.Equal("testSecret", result.Properties["secret"].ToString()); + + result = credsArray[1]; + Assert.Equal("azure", result.Type); + Assert.Equal("testTenantId", result.Properties["tenantId"].ToString()); + Assert.Equal("testClientId", result.Properties["clientId"].ToString()); + Assert.Equal("testSecret", result.Properties["secret"].ToString()); + } + + [Fact] + public void TranslateHttpResponseProperty() + { + string requestJson = "{ \"params\": { \"__reserved\": { \"httpResponse\": true } } }"; + LiveTestRequest request = Newtonsoft.Json.JsonConvert.DeserializeObject(requestJson); + + LiveTestCredentialFactory test = new LiveTestCredentialFactory(); + test.TranslateCredentialsObjects(request); + + request.Params.ContainsKey("__reserved"); + Assert.True(request.Params["__reserved"] is Dictionary); + Dictionary reservedParams = (Dictionary)request.Params["__reserved"]; + + Assert.False(reservedParams.ContainsKey("httpResponse")); + Assert.True(request.HttpResponse); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestRequestConverterTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestRequestConverterTests.cs new file mode 100644 index 0000000..6392da6 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestRequestConverterTests.cs @@ -0,0 +1,266 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests +{ + using Converters; + using Messages; + using Models; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests for LiveTestRequestConverter type. + /// + public class LiveTestRequestConverterTests + { + private readonly ITestOutputHelper output; + public LiveTestRequestConverterTests(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void ConvertsMappedParameter() + { + GeneratedModule module = new GeneratedModule(null); + OperationData test = new OperationData("testoperationid", "Get-Test"); + module.Operations["testoperationid"] = test; + test.Parameters["parm"] = new ParameterData() + { + Name = "parm", + JsonName = "jsonParm", + Type = new RuntimeTypeData() + { + Type = typeof(string) + } + }; + + LiveTestRequestConverter converter = new LiveTestRequestConverter(module); + JsonSerializerSettings settings = new JsonSerializerSettings(); + settings.Converters.Add(converter); + + string json = "{\"method\":\"A.testOperationId\",\"jsonrpc\":\"2.0\",\"id\":\"0\",\"params\":{\"jsonparm\":\"testValue\"}}"; + LiveTestRequest request = JsonConvert.DeserializeObject(json, settings); + Assert.NotNull(request); + Assert.Equal("0", request.Id); + Assert.Equal("2.0", request.JsonRpc); + Assert.Equal("A.testOperationId", request.Method); + Assert.Equal("testoperationid", request.OperationId); + Assert.True(request.Params.ContainsKey("parm")); + Assert.Equal(typeof(string), request.Params["parm"].GetType()); + Assert.Equal("testValue", request.Params["parm"] as string); + + string reserialized = JsonConvert.SerializeObject(request, settings); + Assert.Equal(json, reserialized); + } + + [Fact] + public void ConvertsMappedPropertyOfParameter() + { + GeneratedModule module = new GeneratedModule(null); + OperationData test = new OperationData("testoperationid", "Get-Test"); + module.Operations["testoperationid"] = test; + RuntimeTypeData jsonParmData = new RuntimeTypeData() + { + Type = typeof(LiveTestRequestConverterTestsObject) + }; + jsonParmData.Properties["property"] = new ParameterData() + { + Name = "property", + JsonName = "prop", + Type = new RuntimeTypeData() + { + Type = typeof(string) + } + }; + test.Parameters["jsonparm"] = new ParameterData() + { + Name = "jsonParm", + Type = jsonParmData + }; + + LiveTestRequestConverter converter = new LiveTestRequestConverter(module); + JsonSerializerSettings settings = new JsonSerializerSettings(); + settings.Converters.Add(converter); + settings.Converters.Add(new DynamicTypedObjectConverter(jsonParmData)); + + string json = "{\"method\":\"A.testOperationId\",\"jsonrpc\":\"2.0\",\"id\":\"0\",\"params\":{\"jsonparm\":{\"prop\":\"testValue\"}}}"; + LiveTestRequest request = JsonConvert.DeserializeObject(json, settings); + Assert.NotNull(request); + Assert.Equal("0", request.Id); + Assert.Equal("2.0", request.JsonRpc); + Assert.Equal("A.testOperationId", request.Method); + Assert.Equal("testoperationid", request.OperationId); + Assert.True(request.Params.ContainsKey("jsonparm")); + Assert.Equal(typeof(LiveTestRequestConverterTestsObject), request.Params["jsonparm"].GetType()); + Assert.Equal("testValue", ((LiveTestRequestConverterTestsObject)request.Params["jsonparm"]).Property); + + string reserialized = JsonConvert.SerializeObject(request, settings); + Assert.Equal(json, reserialized); + } + + [Fact] + public void ConvertsPrimitiveParameters() + { + GeneratedModule module = new GeneratedModule(null); + OperationData test = new OperationData("testoperationid", "Get-Test"); + module.Operations["testoperationid"] = test; + test.Parameters["string"] = new ParameterData() + { + Name = "string", + JsonName = "stringparm", + Type = new RuntimeTypeData() + { + Type = typeof(string) + } + }; + test.Parameters["bool"] = new ParameterData() + { + Name = "bool", + JsonName = "boolparm", + Type = new RuntimeTypeData() + { + Type = typeof(bool) + } + }; + test.Parameters["array"] = new ParameterData() + { + Name = "array", + JsonName = "arrayparm", + Type = new RuntimeTypeData() + { + Type = typeof(bool[]) + } + }; + + LiveTestRequestConverter converter = new LiveTestRequestConverter(module); + JsonSerializerSettings settings = new JsonSerializerSettings(); + converter.RegisterSelf(settings); + + string json = "{\"method\":\"A.testOperationId\",\"jsonrpc\":\"2.0\",\"id\":\"0\",\"params\":{\"stringparm\":\"testValue\",\"boolparm\":true,\"arrayparm\":[true,false]}}"; + LiveTestRequest request = JsonConvert.DeserializeObject(json, settings); + Assert.NotNull(request); + Assert.Equal("0", request.Id); + Assert.Equal("2.0", request.JsonRpc); + Assert.Equal("A.testOperationId", request.Method); + Assert.Equal("testoperationid", request.OperationId); + Assert.True(request.Params.ContainsKey("string")); + Assert.Equal(typeof(string), request.Params["string"].GetType()); + Assert.Equal("testValue", (string)request.Params["string"]); + Assert.True(request.Params.ContainsKey("bool")); + Assert.Equal(typeof(bool), request.Params["bool"].GetType()); + Assert.True((bool)request.Params["bool"]); + Assert.True(request.Params.ContainsKey("array")); + Assert.Equal(typeof(bool[]), request.Params["array"].GetType()); + Assert.Equal(2, ((bool[])request.Params["array"]).Length); + Assert.True(((bool[])request.Params["array"])[0]); + Assert.False(((bool[])request.Params["array"])[1]); + + string reserialized = JsonConvert.SerializeObject(request, settings); + Assert.Equal(json, reserialized); + } + + [Fact] + public void ConvertsNullParameters() + { + GeneratedModule module = new GeneratedModule(null); + OperationData test = new OperationData("testoperationid", "Get-Test"); + module.Operations["testoperationid"] = test; + test.Parameters["string"] = new ParameterData() + { + Name = "string", + JsonName = "stringparm", + Type = new RuntimeTypeData() + { + Type = typeof(string) + } + }; + + LiveTestRequestConverter converter = new LiveTestRequestConverter(module); + JsonSerializerSettings settings = new JsonSerializerSettings(); + settings.Converters.Add(converter); + + string json = "{\"method\":\"A.testOperationId\",\"jsonrpc\":\"2.0\",\"id\":\"0\",\"params\":{\"stringparm\":null}}"; + LiveTestRequest request = JsonConvert.DeserializeObject(json, settings); + Assert.NotNull(request); + Assert.Equal("0", request.Id); + Assert.Equal("2.0", request.JsonRpc); + Assert.Equal("A.testOperationId", request.Method); + Assert.Equal("testoperationid", request.OperationId); + Assert.True(request.Params.ContainsKey("string")); + Assert.Null((string)request.Params["string"]); + } + + [Fact] + public void ReservedParameterStaysJObject() + { + GeneratedModule module = new GeneratedModule(null); + OperationData test = new OperationData("testoperationid", "Get-Test"); + module.Operations["testoperationid"] = test; + test.Parameters["__reserved"] = new ParameterData() + { + Name = "__reserved", + Type = new RuntimeTypeData() + { + Type = typeof(string) + } + }; + + LiveTestRequestConverter converter = new LiveTestRequestConverter(module); + JsonSerializerSettings settings = new JsonSerializerSettings(); + settings.Converters.Add(converter); + + string json = "{\"method\":\"A.testOperationId\",\"jsonrpc\":\"2.0\",\"id\":\"0\",\"params\":{\"__reserved\":{}}}"; + LiveTestRequest request = JsonConvert.DeserializeObject(json, settings); + Assert.NotNull(request); + Assert.Equal("0", request.Id); + Assert.Equal("2.0", request.JsonRpc); + Assert.Equal("A.testOperationId", request.Method); + Assert.Equal("testoperationid", request.OperationId); + Assert.True(request.Params.ContainsKey("__reserved")); + Assert.Equal(typeof(JObject), request.Params["__reserved"].GetType()); + } + + [Fact] + public void RegistersAllSubConverters() + { + GeneratedModule module = new GeneratedModule(null); + OperationData test = new OperationData("testoperationid", "Get-Test"); + module.Operations["testoperationid"] = test; + RuntimeTypeData jsonParmData = new RuntimeTypeData() + { + Type = typeof(LiveTestRequestConverterTestsObject) + }; + jsonParmData.Properties["property"] = new ParameterData() + { + Name = "property", + JsonName = "prop", + Type = new RuntimeTypeData() + { + Type = typeof(string) + } + }; + test.Parameters["jsonparm"] = new ParameterData() + { + Name = "jsonParm", + Type = jsonParmData + }; + + LiveTestRequestConverter converter = new LiveTestRequestConverter(module); + JsonSerializerSettings settings = new JsonSerializerSettings(); + converter.RegisterSelf(settings); + + // 1. LiveTestRequestConverter + // 2. DynamicTypedObjectConverter for LiveTestRequestConverterTestsObject + Assert.Equal(2, settings.Converters.Count); + } + } + + public class LiveTestRequestConverterTestsObject + { + public string Property { get; set; } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestRequestTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestRequestTests.cs new file mode 100644 index 0000000..5f9b282 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestRequestTests.cs @@ -0,0 +1,321 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests +{ + using Logging; + using Messages; + using Mocks; + using Models; + using System; + using System.Collections.Generic; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests for LiveTestRequest type. + /// + public class LiveTestRequestTests + { + private readonly XUnitOutputPipe output; + private readonly XUnitOutputPipe error; + private readonly Logger logger; + public LiveTestRequestTests(ITestOutputHelper output) + { + this.output = new XUnitOutputPipe(output); + this.error = new XUnitOutputPipe(output, logAsErrors: true); + this.logger = new Logger(this.output, this.error); + } + + [Fact] + public void ExceptionResponse() + { + int errorCode = 1; + Exception ex = new NotImplementedException(); + LiveTestRequest request = new LiveTestRequest(); + request.Id = "12345"; + request.JsonRpc = "2.0"; + LiveTestResponse response = request.MakeResponse(ex, errorCode); + Assert.Equal(request.Id, response.Id); + Assert.Equal(request.JsonRpc, response.JsonRpc); + + Assert.NotNull(response.Error); + Assert.Equal(errorCode, response.Error.Code); + + Assert.NotNull(response.Error.Data); + Assert.Null(response.Error.Data.Headers); + Assert.Equal(default(long), response.Error.Data.StatusCode); + Assert.Equal(ex, response.Error.Data.Response); + } + + [Fact] + public void NullErrorResponse() + { + MockServiceTracer tracer = new MockServiceTracer(); + CommandExecutionResult result = new CommandExecutionResult(null, null, true); + LiveTestRequest request = new LiveTestRequest(); + request.Id = "12345"; + request.JsonRpc = "2.0"; + LiveTestResponse response = request.MakeResponse(result, tracer, this.logger); + Assert.Equal(request.Id, response.Id); + Assert.Equal(request.JsonRpc, response.JsonRpc); + + Assert.NotNull(response.Error); + Assert.Equal(-32600, response.Error.Code); // Invalid Request error code defined by LSP + + Assert.NotNull(response.Error.Data); + Assert.Null(response.Error.Data.Headers); + Assert.Equal(default(long), response.Error.Data.StatusCode); + Assert.Null(response.Error.Data.Response); + } + + [Fact] + public void NoErrorResponse() + { + MockServiceTracer tracer = new MockServiceTracer(); + List errors = new List(); + CommandExecutionResult result = new CommandExecutionResult(null, errors, true); + LiveTestRequest request = new LiveTestRequest(); + request.Id = "12345"; + request.JsonRpc = "2.0"; + LiveTestResponse response = request.MakeResponse(result, tracer, this.logger); + Assert.Equal(request.Id, response.Id); + Assert.Equal(request.JsonRpc, response.JsonRpc); + + Assert.NotNull(response.Error); + Assert.Equal(-32600, response.Error.Code); // Invalid Request error code defined by LSP + + Assert.NotNull(response.Error.Data); + Assert.Null(response.Error.Data.Headers); + Assert.Equal(default(long), response.Error.Data.StatusCode); + Assert.Null(response.Error.Data.Response); + } + + [Fact] + public void SingleErrorResponse() + { + MockServiceTracer tracer = new MockServiceTracer(); + object errorResult = 5; + List errors = new List(); + errors.Add(errorResult); + CommandExecutionResult result = new CommandExecutionResult(null, errors, true); + LiveTestRequest request = new LiveTestRequest(); + request.Id = "12345"; + request.JsonRpc = "2.0"; + LiveTestResponse response = request.MakeResponse(result, tracer, this.logger); + Assert.Equal(request.Id, response.Id); + Assert.Equal(request.JsonRpc, response.JsonRpc); + + Assert.NotNull(response.Error); + Assert.Equal(-32600, response.Error.Code); // Invalid Request error code defined by LSP + + Assert.NotNull(response.Error.Data); + Assert.Null(response.Error.Data.Headers); + Assert.Equal(default(long), response.Error.Data.StatusCode); + Assert.NotNull(response.Error.Data.Response); + Assert.Equal(errorResult, response.Error.Data.Response); + } + + [Fact] + public void MultipleErrorResponse() + { + MockServiceTracer tracer = new MockServiceTracer(); + object errorResult1 = 5; + object errorResult2 = "test"; + List errors = new List(); + errors.Add(errorResult1); + errors.Add(errorResult2); + CommandExecutionResult result = new CommandExecutionResult(null, errors, true); + LiveTestRequest request = new LiveTestRequest(); + request.Id = "12345"; + request.JsonRpc = "2.0"; + LiveTestResponse response = request.MakeResponse(result, tracer, this.logger); + Assert.Equal(request.Id, response.Id); + Assert.Equal(request.JsonRpc, response.JsonRpc); + + Assert.NotNull(response.Error); + Assert.Equal(-32600, response.Error.Code); // Invalid Request error code defined by LSP + + Assert.NotNull(response.Error.Data); + Assert.Null(response.Error.Data.Headers); + Assert.Equal(default(long), response.Error.Data.StatusCode); + Assert.NotNull(response.Error.Data.Response); + Assert.True(response.Error.Data.Response is object[]); + Assert.Collection((object[])response.Error.Data.Response, new Action[] + { + (obj) => + { + Assert.Equal(errorResult1, obj); + }, + (obj) => + { + Assert.Equal(errorResult2, obj); + } + }); + } + + [Fact] + public void NullResultResponse() + { + MockServiceTracer tracer = new MockServiceTracer(); + CommandExecutionResult result = new CommandExecutionResult(null, null, false); + LiveTestRequest request = new LiveTestRequest(); + request.Id = "12345"; + request.JsonRpc = "2.0"; + LiveTestResponse response = request.MakeResponse(result, tracer, this.logger); + Assert.Equal(request.Id, response.Id); + Assert.Equal(request.JsonRpc, response.JsonRpc); + + Assert.NotNull(response.Result); + Assert.Null(response.Result.Headers); + Assert.Equal(default(long), response.Result.StatusCode); + Assert.Null(response.Result.Response); + } + + [Fact] + public void NoResultResponse() + { + MockServiceTracer tracer = new MockServiceTracer(); + List psResults = new List(); + CommandExecutionResult result = new CommandExecutionResult(psResults, null, false); + LiveTestRequest request = new LiveTestRequest(); + request.Id = "12345"; + request.JsonRpc = "2.0"; + LiveTestResponse response = request.MakeResponse(result, tracer, this.logger); + Assert.Equal(request.Id, response.Id); + Assert.Equal(request.JsonRpc, response.JsonRpc); + + Assert.NotNull(response.Result); + Assert.Null(response.Result.Headers); + Assert.Equal(default(long), response.Result.StatusCode); + Assert.Null(response.Result.Response); + } + + [Fact] + public void SingleResultResponse() + { + MockServiceTracer tracer = new MockServiceTracer(); + object psResult = 5; + List psResults = new List(); + psResults.Add(psResult); + CommandExecutionResult result = new CommandExecutionResult(psResults, null, false); + LiveTestRequest request = new LiveTestRequest(); + request.Id = "12345"; + request.JsonRpc = "2.0"; + LiveTestResponse response = request.MakeResponse(result, tracer, this.logger); + Assert.Equal(request.Id, response.Id); + Assert.Equal(request.JsonRpc, response.JsonRpc); + + Assert.NotNull(response.Result); + Assert.Null(response.Result.Headers); + Assert.Equal(default(long), response.Result.StatusCode); + Assert.NotNull(response.Result.Response); + Assert.Equal(psResult, response.Result.Response); + } + + [Fact] + public void MultipleResultResponse() + { + MockServiceTracer tracer = new MockServiceTracer(); + object psResult1 = 5; + object psResult2 = "test"; + List psResults = new List(); + psResults.Add(psResult1); + psResults.Add(psResult2); + CommandExecutionResult result = new CommandExecutionResult(psResults, null, false); + LiveTestRequest request = new LiveTestRequest(); + request.Id = "12345"; + request.JsonRpc = "2.0"; + LiveTestResponse response = request.MakeResponse(result, tracer, this.logger); + Assert.Equal(request.Id, response.Id); + Assert.Equal(request.JsonRpc, response.JsonRpc); + + Assert.NotNull(response.Result); + Assert.Null(response.Result.Headers); + Assert.Equal(default(long), response.Result.StatusCode); + Assert.NotNull(response.Result.Response); + Assert.Collection((object[])response.Result.Response, new Action[] + { + (obj) => + { + Assert.Equal(psResult1, obj); + }, + (obj) => + { + Assert.Equal(psResult2, obj); + } + }); + } + + [Fact] + public void HttpResponseDataInError() + { + MockServiceTracer tracer = new MockServiceTracer(); + System.Net.Http.HttpResponseMessage httpResponse = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.BadGateway); + httpResponse.Headers.Add("x-ms-header", "value"); + tracer.HttpResponses.Add(httpResponse); + + LiveTestRequest request = new LiveTestRequest(); + request.Id = "12345"; + request.JsonRpc = "2.0"; + request.HttpResponse = true; + + CommandExecutionResult result = new CommandExecutionResult(null, null, true); + LiveTestResponse response = request.MakeResponse(result, tracer, this.logger); + + Assert.NotNull(response.Error.Data.Headers); + Assert.True(response.Error.Data.Headers is Dictionary); + Dictionary headers = (Dictionary)response.Error.Data.Headers; + Assert.Equal(1, headers.Count); + Assert.True(headers.ContainsKey("x-ms-header")); + Assert.Equal("value", headers["x-ms-header"]); + + Assert.Equal((long)System.Net.HttpStatusCode.BadGateway, response.Error.Data.StatusCode); + } + + [Fact] + public void HttpResponseDataInResult() + { + MockServiceTracer tracer = new MockServiceTracer(); + System.Net.Http.HttpResponseMessage httpResponse = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Accepted); + httpResponse.Headers.Add("x-ms-header", "value"); + tracer.HttpResponses.Add(httpResponse); + + LiveTestRequest request = new LiveTestRequest(); + request.Id = "12345"; + request.JsonRpc = "2.0"; + request.HttpResponse = true; + + CommandExecutionResult result = new CommandExecutionResult(null, null, false); + LiveTestResponse response = request.MakeResponse(result, tracer, this.logger); + + Assert.NotNull(response.Result.Headers); + Assert.True(response.Result.Headers is Dictionary); + Dictionary headers = (Dictionary)response.Result.Headers; + Assert.Equal(1, headers.Count); + Assert.True(headers.ContainsKey("x-ms-header")); + Assert.Equal("value", headers["x-ms-header"]); + + Assert.Equal((long)System.Net.HttpStatusCode.Accepted, response.Result.StatusCode); + } + + [Fact] + public void HttpResponseDataInErrorNoHttpResponseEnabled() + { + MockServiceTracer tracer = new MockServiceTracer(); + System.Net.Http.HttpResponseMessage httpResponse = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.BadGateway); + httpResponse.Headers.Add("x-ms-header", "value"); + tracer.HttpResponses.Add(httpResponse); + + LiveTestRequest request = new LiveTestRequest(); + request.Id = "12345"; + request.JsonRpc = "2.0"; + + CommandExecutionResult result = new CommandExecutionResult(null, null, true); + LiveTestResponse response = request.MakeResponse(result, tracer, this.logger); + + Assert.Null(response.Error.Data.Headers); + Assert.Equal(default(long), response.Error.Data.StatusCode); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestServerTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestServerTests.cs index cd3ee66..5509a3b 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestServerTests.cs +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/LiveTestServerTests.cs @@ -1,5 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.UnitTests { + using Credentials; using Messages; using Mocks; using System; @@ -45,7 +49,9 @@ public void BasicExecutionFlow() Input = blockPipe, Output = blockPipe, RunspaceManager = mockRunspace, - ModulePath = "test" + ModulePath = "test", + CredentialFactory = new LiveTestCredentialFactory(), + TracingManager = new MockServiceTracingManager() }; LiveTestServer server = new LiveTestServer(parms); server.RunAsync().Wait(); @@ -60,10 +66,8 @@ public void BasicExecutionFlow() // Check the expected flow // 1. Get module info - // 2. Load module // 3. Process request Assert.True(mockRunspace.GetModuleInfoCalled, "GetModuleInfo was never called."); - Assert.True(module.LoadCalled, "Load was never called."); Assert.True(module.ProcessRequestCalled, "ProcessRequest was never called."); } @@ -82,7 +86,8 @@ public void MissingInputPipe() { Output = blockPipe, RunspaceManager = mockRunspace, - ModulePath = "test" + ModulePath = "test", + CredentialFactory = new LiveTestCredentialFactory() }; Assert.Throws(() => new LiveTestServer(parms)); } @@ -102,7 +107,8 @@ public void MissingOutputPipe() { Input = blockPipe, RunspaceManager = mockRunspace, - ModulePath = "test" + ModulePath = "test", + CredentialFactory = new LiveTestCredentialFactory() }; Assert.Throws(() => new LiveTestServer(parms)); } @@ -122,7 +128,8 @@ public void MissingRunspaceManager() { Input = blockPipe, Output = blockPipe, - ModulePath = "test" + ModulePath = "test", + CredentialFactory = new LiveTestCredentialFactory() }; Assert.Throws(() => new LiveTestServer(parms)); } @@ -142,7 +149,29 @@ public void MissingModulePath() { Input = blockPipe, Output = blockPipe, - RunspaceManager = mockRunspace + RunspaceManager = mockRunspace, + CredentialFactory = new LiveTestCredentialFactory() + }; + Assert.Throws(() => new LiveTestServer(parms)); + } + + /// + /// Test LiveTestServer constructor when missing required parameter. + /// + [Fact] + public void MissingCredentialsFactory() + { + MockRunspaceManager mockRunspace = new MockRunspaceManager(); + MockGeneratedModule module = new MockGeneratedModule(mockRunspace); + mockRunspace.ModuleMocks["test"] = module; + TestBlockPipe blockPipe = new TestBlockPipe(); + + LiveTestServerStartParams parms = new LiveTestServerStartParams() + { + Input = blockPipe, + Output = blockPipe, + RunspaceManager = mockRunspace, + ModulePath = "test" }; Assert.Throws(() => new LiveTestServer(parms)); } diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/CommandBasedCredentialProvider.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/CommandBasedCredentialProvider.cs new file mode 100644 index 0000000..9ffe759 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/CommandBasedCredentialProvider.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests.Mocks +{ + using Interfaces; + + /// + /// Mock credential provider that executes a command (e.g. AzureCredentialProvider). + /// + public class CommandBasedCredentialProvider : ICredentialProvider + { + public void Process(ICommandBuilder command) + { + ICommandBuilder credCommand = command.Runspace.CreateCommand(); + credCommand.Command = "Login-Account"; + credCommand.Invoke(); + } + + public void Set(string property, object value) + { + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockCommandBuilder.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockCommandBuilder.cs index d184307..fe943b8 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockCommandBuilder.cs +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockCommandBuilder.cs @@ -1,32 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.UnitTests.Mocks { using Interfaces; + using Models; using System; - using System.Collections; using System.Collections.Generic; + using System.Runtime.InteropServices; /// /// Mock command builder. Collects parameters and history of Invoke calls. /// public class MockCommandBuilder : ICommandBuilder { - public Dictionary> Parameters { get; private set; } + private IRunspaceManager runspace; + public Dictionary Parameters { get; private set; } public string Command { get; set; } public IList InvokeHistory { get; private set; } + public CommandExecutionResult MockResult { get; set; } + public IRunspaceManager Runspace + { + get + { + return runspace; + } + } - public MockCommandBuilder() + public MockCommandBuilder(MockRunspaceManager runspace) { - this.Parameters = new Dictionary>(); + this.runspace = runspace; + this.Parameters = new Dictionary(); this.InvokeHistory = new List(); } - public ICommandBuilder AddParameter(string parameterName, object parameterValue, bool switchParameter = false) + public ICommandBuilder AddParameter(string parameterName, object parameterValue) { - this.Parameters[parameterName] = new Tuple(parameterValue, switchParameter); + this.Parameters[parameterName] = parameterValue; return this; } - public IEnumerable Invoke() + public CommandExecutionResult Invoke() { // Format for mock is: command [parameterName parameterValue.ToString() isSwitch]+ string invokeString = this.Command; @@ -34,13 +48,32 @@ public IEnumerable Invoke() { foreach (string key in this.Parameters.Keys) { - invokeString += String.Format(" [{0} {1} {2}]", key, this.Parameters[key].Item1.ToString(), this.Parameters[key].Item2); + string encodedVal = this.Parameters[key] == null ? "null" : this.Parameters[key].ToString(); + if (this.Parameters[key] is System.Management.Automation.PSCredential) + { + System.Management.Automation.PSCredential specificType = (System.Management.Automation.PSCredential)this.Parameters[key]; + string password = String.Empty; + IntPtr valuePtr = IntPtr.Zero; + try + { + valuePtr = Marshal.SecureStringToGlobalAllocUnicode(specificType.Password); + password = Marshal.PtrToStringUni(valuePtr); + } + finally + { + Marshal.ZeroFreeGlobalAllocUnicode(valuePtr); + } + + encodedVal = "(" + specificType.UserName + " " + password + ")"; + } + + invokeString += String.Format(" [{0} {1}]", key, encodedVal); } } - this.InvokeHistory.Add(invokeString); + this.runspace.Invoke(invokeString); - return null; + return this.MockResult; } } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockGeneratedModule.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockGeneratedModule.cs index 91d9836..43e9774 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockGeneratedModule.cs +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockGeneratedModule.cs @@ -1,8 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.UnitTests.Mocks { + using Credentials; using Interfaces; using Messages; - using System.Collections; + using Models; /// /// Mock generated module that tracks if methods are called. @@ -14,12 +18,13 @@ public MockGeneratedModule(IRunspaceManager runspace) : base(runspace) { } public bool LoadCalled { get; set; } public bool ProcessRequestCalled { get; set; } - public override void Load(bool force = false) + public override CommandExecutionResult Load(bool force = false) { this.LoadCalled = true; + return null; } - public override IEnumerable ProcessRequest(LiveTestRequest request) + public override CommandExecutionResult ProcessRequest(LiveTestRequest request, LiveTestCredentialFactory credentialsFactory) { this.ProcessRequestCalled = true; return null; diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockJsonPathFinder.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockJsonPathFinder.cs new file mode 100644 index 0000000..37434ed --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockJsonPathFinder.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests.Mocks +{ + using Json; + using System.Collections.Generic; + + /// + /// Provides parsing for a JSON file. + /// + public class MockJsonPathFinder : JsonPathFinder + { + public JsonPathFinder ParentMock { get; set; } + public string KeyMock { get; set; } + public object ValueMock { get; set; } + public Dictionary> QueryMocks { get; } + public override JsonPathFinder Parent + { + get + { + return this.ParentMock; + } + } + + public override string Key + { + get + { + return this.KeyMock; + } + } + + public override T GetValue() + { + return (T)this.ValueMock; + } + + public MockJsonPathFinder() + { + this.QueryMocks = new Dictionary>(); + } + + public MockJsonPathFinder MakeDummyParent(int depth = 1) + { + MockJsonPathFinder currentLowestParent = new MockJsonPathFinder() { ParentMock = this }; + while (--depth > 0) + { + currentLowestParent = new MockJsonPathFinder() { ParentMock = currentLowestParent }; + } + + return currentLowestParent; + } + + public override IEnumerable Find(string jsonQuery) + { + if (this.QueryMocks.ContainsKey(jsonQuery)) + { + return this.QueryMocks[jsonQuery]; + } + + return null; + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockRunspaceManager.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockRunspaceManager.cs index 4a7773e..b6cb315 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockRunspaceManager.cs +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockRunspaceManager.cs @@ -1,9 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.UnitTests.Mocks { - using System.Collections; - using Messages; using Interfaces; - using System; + using Models; using System.Collections.Generic; /// @@ -11,19 +12,31 @@ namespace PSSwagger.LTF.Lib.UnitTests.Mocks /// public class MockRunspaceManager : IRunspaceManager { + private int commandBuilderIndex = 0; public Dictionary ModuleMocks { get; private set; } - public MockCommandBuilder Builder { get; private set; } + public IList CommandBuilders { get; private set; } public bool GetModuleInfoCalled { get; private set; } - + public IList InvokeHistory { get; private set; } public MockRunspaceManager() { this.ModuleMocks = new Dictionary(); - this.Builder = new MockCommandBuilder(); + this.CommandBuilders = new List(); + this.InvokeHistory = new List(); } public ICommandBuilder CreateCommand() { - return this.Builder; + MockCommandBuilder builder; + if (this.CommandBuilders.Count <= commandBuilderIndex) + { + builder = new MockCommandBuilder(this); + this.CommandBuilders.Add(builder); + } else + { + builder = this.CommandBuilders[commandBuilderIndex++]; + } + + return builder; } public GeneratedModule GetModuleInfo(string modulePath) @@ -37,14 +50,10 @@ public GeneratedModule GetModuleInfo(string modulePath) return null; } - public IEnumerable Invoke(string script) + public CommandExecutionResult Invoke(object script) { - throw new NotImplementedException(); - } - - public void SetSessionVariable(string variableName, object variableValue) - { - throw new NotImplementedException(); + this.InvokeHistory.Add(script); + return null; } } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockServiceTracer.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockServiceTracer.cs new file mode 100644 index 0000000..89b1d11 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockServiceTracer.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests.Mocks +{ + using Interfaces; + using System.Collections.Generic; + using System.Net.Http; + + /// + /// Traces operations at the service layer using Microsoft.Rest.ClientRuntime. + /// + public class MockServiceTracer : IServiceTracer + { + /// + /// Gets the collection of HttpResponseMessages caught by this tracer. + /// + public IList HttpResponses { get; set; } + + public MockServiceTracer() + { + this.HttpResponses = new List(); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockServiceTracingManager.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockServiceTracingManager.cs new file mode 100644 index 0000000..e509e62 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockServiceTracingManager.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests.Mocks +{ + using Interfaces; + using Logging; + using ServiceTracing; + + public class MockServiceTracingManager : ServiceTracingManager + { + public MockServiceTracer Tracer { get; set; } + + public MockServiceTracingManager() + { + this.Tracer = new MockServiceTracer(); + } + + /// + /// Create a tracer for the given invocation ID. + /// + /// Invocation ID to create IServiceTracer for + /// Logging source to send trace messages to + /// Service tracer + public override IServiceTracer CreateTracer(long invocationId, Logger logger) + { + return this.Tracer; + } + + public override void RemoveTracer(IServiceTracer tracer) + { + } + + public override void EnableTracing() + { + } + + public override void DisableTracing() + { + } + + public override long GetNextInvocationId() + { + return 0; + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockTestCredentialFactory.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockTestCredentialFactory.cs new file mode 100644 index 0000000..5a4e9c1 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/MockTestCredentialFactory.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests.Mocks +{ + using Credentials; + using Interfaces; + using System; + + /// + /// Credential factory that allows registering ICredentialProviders + /// + public class MockTestCredentialFactory : LiveTestCredentialFactory + { + public MockTestCredentialFactory() + { + this.providers.Clear(); + } + + public void RegisterProvider(string credType, T provider) where T : ICredentialProvider + { + if (String.IsNullOrEmpty(credType)) + { + credType = "azure"; + } + + this.providers[credType] = (logger) => (T)Activator.CreateInstance(typeof(T)); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/ParameterBasedCredentialProvider.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/ParameterBasedCredentialProvider.cs new file mode 100644 index 0000000..954dce4 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/ParameterBasedCredentialProvider.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests.Mocks +{ + using Interfaces; + + /// + /// Mock credential provider that adds a parameter to the given command (e.g. supplying -APIKey). + /// + public class ParameterBasedCredentialProvider : ICredentialProvider + { + public void Process(ICommandBuilder command) + { + command.AddParameter("CredentialKey", "testCredentials"); + } + + public void Set(string property, object value) + { + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/StringPipe.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/StringPipe.cs index c5f7c2c..3b30dff 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/StringPipe.cs +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/StringPipe.cs @@ -1,10 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.UnitTests.Mocks { + using Interfaces; using System; + using System.Collections.Generic; + using System.Text; using System.Threading; using System.Threading.Tasks; - using System.Text; - using Interfaces; /// /// Basic string input/output interface. Use BufferInputString to add a string to the read buffer. @@ -13,17 +17,19 @@ public class StringPipe : IInputPipe, IOutputPipe { private StringBuilder buffer; private int bufferIndex = 0; - + private Queue byteQueue = new Queue(); + public StringBuilder OutputBuffer { get; private set; } public StringPipe() { this.buffer = new StringBuilder(); + this.OutputBuffer = new StringBuilder(); } public void Flush() { } - public char ReadChar() + public async Task ReadChar() { while (buffer.Length <= bufferIndex) { @@ -33,33 +39,68 @@ public char ReadChar() return buffer[bufferIndex++]; } - public void Write(char b) + public async Task Write(char b) { + this.OutputBuffer.Append(b); } - public void BufferInputString(string line) + public void BufferInputString(string str) { - this.buffer.Append(line); + this.buffer.Append(str); } - public string ReadLine() + public void BufferJsonRpcBlock(object message, string type = null) { - throw new NotImplementedException(); + string messageSerialized = Newtonsoft.Json.JsonConvert.SerializeObject(message); + StringBuilder sb = new StringBuilder(); + sb.Append("Content-Length: " + Encoding.ASCII.GetByteCount(messageSerialized) + "\r\n"); + if (!String.IsNullOrEmpty(type)) + { + sb.Append("Content-Type: " + type + "\r\n"); + } + sb.Append("\r\n"); + sb.Append(messageSerialized); + this.buffer.Append(sb.ToString()); } - public Task ReadBlockAsync() where T : class + public Task ReadLine() { throw new NotImplementedException(); } - public void WriteLine(string line) + public Task ReadBlock() where T : class { throw new NotImplementedException(); } - public Task WriteBlockAsync(T msg) where T : class + public async Task WriteLine(string line) + { + this.OutputBuffer.AppendLine(line); + } + + public Task WriteBlock(T msg) where T : class { throw new NotImplementedException(); } + + public async Task ReadByte() + { + if (this.byteQueue.Count > 0) + { + return this.byteQueue.Dequeue(); + } + + while (buffer.Length <= bufferIndex) + { + Thread.Sleep(1); + } + + foreach (byte b in Encoding.ASCII.GetBytes(new char[] { buffer[bufferIndex++] })) + { + this.byteQueue.Enqueue(b); + } + + return this.byteQueue.Dequeue(); + } } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/TestBlockPipe.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/TestBlockPipe.cs index ad95d64..482eebf 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/TestBlockPipe.cs +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/TestBlockPipe.cs @@ -1,12 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. namespace PSSwagger.LTF.Lib.UnitTests.Mocks { - using System; - using System.Threading; - using System.Threading.Tasks; - using System.Text; using Interfaces; using Messages; + using System; using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; /// /// Test interface for pushing LiveTestRequest blocks to input and getting LiveTestResponse from output. @@ -26,22 +28,22 @@ public void Flush() { } - public char ReadChar() + public Task ReadChar() { throw new NotImplementedException(); } - public void Write(char b) + public Task Write(char b) { throw new NotImplementedException(); } - public string ReadLine() + public Task ReadLine() { throw new NotImplementedException(); } - public async Task ReadBlockAsync() where T : class + public async Task ReadBlock() where T : class { if (typeof(T) != typeof(LiveTestRequest)) { @@ -56,12 +58,12 @@ public async Task ReadBlockAsync() where T : class return this.Requests.Dequeue() as T; } - public void WriteLine(string line) + public Task WriteLine(string line) { throw new NotImplementedException(); } - public async Task WriteBlockAsync(T msg) where T : class + public async Task WriteBlock(T msg) where T : class { if (!(msg is LiveTestResponse)) { @@ -70,5 +72,10 @@ public async Task WriteBlockAsync(T msg) where T : class this.Responses.Add(msg as LiveTestResponse); } + + public Task ReadByte() + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/XUnitOutputPipe.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/XUnitOutputPipe.cs new file mode 100644 index 0000000..76094e5 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/Mocks/XUnitOutputPipe.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests.Mocks +{ + using Interfaces; + using System.Collections.Generic; + using System.Threading.Tasks; + using Xunit.Abstractions; + + /// + /// Read from STDOUT. + /// + public class XUnitOutputPipe : IOutputPipe + { + private ITestOutputHelper output; + private bool logAsErrors; + + public IList Errors { get; set; } + + public XUnitOutputPipe(ITestOutputHelper output, bool logAsErrors = false) + { + this.output = output; + this.logAsErrors = logAsErrors; + this.Errors = new List(); + } + + /// + /// Write a single character + newline to inner ITestOutputHelper. + /// + /// Character to write. + public async Task Write(char b) + { + if (this.logAsErrors) + { + this.Errors.Add(b.ToString()); + } + this.output.WriteLine(b.ToString()); + } + + /// + /// Serialize into JSON then write to inner ITestOutputHelper. + /// + public async Task WriteBlock(T msg) where T : class + { + if (msg != null) + { + string json = Newtonsoft.Json.JsonConvert.SerializeObject(msg); + if (this.logAsErrors) + { + this.Errors.Add(json); + } + this.output.WriteLine(json); + } + } + + /// + /// Write the given string then a new line to inner ITestOutputHelper. + /// + /// Line to write, not including new line. + public async Task WriteLine(string line) + { + if (this.logAsErrors) + { + this.Errors.Add(line); + } + this.output.WriteLine(line); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/PSSwagger.LTF.Lib.UnitTests.csproj b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/PSSwagger.LTF.Lib.UnitTests.csproj index 973b0c2..8d5a7fe 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/PSSwagger.LTF.Lib.UnitTests.csproj +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/PSSwagger.LTF.Lib.UnitTests.csproj @@ -1,10 +1,25 @@ - net452;netcoreapp2.0 + net452 + + + + - + + + PreserveNewest + + + PreserveNewest + + + + + + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/PowerShellCommandTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/PowerShellCommandTests.cs new file mode 100644 index 0000000..1334216 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/PowerShellCommandTests.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests +{ + using Mocks; + using PowerShell; + using System; + using System.Management.Automation.Runspaces; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests for PowerShellCommand type. + /// + public class PowerShellCommandTests + { + private readonly XUnitOutputPipe output; + public PowerShellCommandTests(ITestOutputHelper output) + { + this.output = new XUnitOutputPipe(output); + } + + /// + /// Test that a basic command can be executed. + /// + [Fact] + public void RunGetVerbWithSwitchParameter() + { + MockRunspaceManager runspace = new MockRunspaceManager(); + PowerShellCommand command = new PowerShellCommand(runspace); + command.Command = "Get-Verb"; + command.AddParameter("Verb", "Start"); + command.AddParameter("Verbose", true); + command.Invoke(); + + Assert.Equal(1, runspace.InvokeHistory.Count); + Command result = runspace.InvokeHistory[0] as Command; + Assert.NotNull(result); + Assert.Equal("Get-Verb", result.CommandText); + Assert.Equal(2, result.Parameters.Count); + Assert.Equal("Verb", result.Parameters[0].Name); + Assert.Equal("Verbose", result.Parameters[1].Name); + } + + /// + /// Test that AddParameter without a command throws an exception. + /// + [Fact] + public void AddParameterWithoutCommandFails() + { + MockRunspaceManager runspace = new MockRunspaceManager(); + PowerShellCommand command = new PowerShellCommand(runspace); + Assert.Throws(() => command.AddParameter("Verb", "Start")); + } + + /// + /// Test that trying to add a parameter with no name fails. + /// + [Fact] + public void NoParameterNameFails() + { + MockRunspaceManager runspace = new MockRunspaceManager(); + PowerShellCommand command = new PowerShellCommand(runspace); + command.Command = "Get-Verb"; + Assert.Throws(() => command.AddParameter(null, "Start")); + } + + /// + /// Test that the same builder can be used for multiple commands once the Command property is changed. + /// + [Fact] + public void ParametersAreClearedWhenNewCommandCreated() + { + MockRunspaceManager runspace = new MockRunspaceManager(); + PowerShellCommand command = new PowerShellCommand(runspace); + command.Command = "Get-Verb"; + command.AddParameter("Verb", "Start"); + command.AddParameter("Verbose", true); + + command.Command = "Get-Verbage"; + command.AddParameter("Debug", true); + command.Invoke(); + + Assert.Equal(1, runspace.InvokeHistory.Count); + Command result = runspace.InvokeHistory[0] as Command; + Assert.NotNull(result); + Assert.Equal("Get-Verbage", result.CommandText); + Assert.Equal(1, result.Parameters.Count); + Assert.Equal("Debug", result.Parameters[0].Name); + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/PowerShellRunspaceTests.cs b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/PowerShellRunspaceTests.cs new file mode 100644 index 0000000..72afdb9 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/PowerShellRunspaceTests.cs @@ -0,0 +1,223 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.UnitTests +{ + using Interfaces; + using Logging; + using Mocks; + using Models; + using PowerShell; + using System; + using System.IO; + using System.Linq; + using System.Reflection; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests for PowerShellRunspace type. + /// + public class PowerShellRunspaceTests + { + private readonly XUnitOutputPipe output; + private readonly XUnitOutputPipe error; + private readonly Logger logger; + public PowerShellRunspaceTests(ITestOutputHelper output) + { + this.output = new XUnitOutputPipe(output); + this.error = new XUnitOutputPipe(output, logAsErrors: true); + this.logger = new Logger(this.output, this.error); + } + + /// + /// Test that a basic PowerShell module can be loaded and parsed. + /// + [Fact] + public void BasicHappyPathTest() + { + string modulePath = GetRootedPath(System.IO.Path.Combine("data", "Modules", "BasicHappyPath", "BasicHappyPath.psd1")); + PowerShellRunspace psRunspace = new PowerShellRunspace(this.logger); + GeneratedModule result = psRunspace.GetModuleInfo(modulePath); + Assert.Equal(0, this.error.Errors.Count); + Assert.NotNull(result); + Assert.Equal(modulePath, result.ModulePath); + Assert.Equal(4, result.Operations.Count); + AssertModuleContainsOperation(result, new OperationData("Guitar_Create", "New-Guitar") + { + Parameters = + { + { "id", new ParameterData() + { + Name = "id", + Type = new RuntimeTypeData(typeof(string)) + } + } + } + }); + AssertModuleContainsOperation(result, new OperationData("Guitar_CreateByName", "New-Guitar") + { + Parameters = + { + { "name", new ParameterData() + { + Name = "name", + Type = new RuntimeTypeData(typeof(string)) + } + } + } + }); + AssertModuleContainsOperation(result, new OperationData("Guitar_GetById", "Get-Guitar") + { + Parameters = + { + { "id", new ParameterData() + { + Name = "id", + Type = new RuntimeTypeData(typeof(string)) + } + } + } + }); + AssertModuleContainsOperation(result, new OperationData("Guitar_Get", "Get-Guitar")); + } + + /// + /// Test that there is a fatal error when no module exists. + /// + [Fact] + public void FatalErrorWhenModuleNotExist() + { + string modulePath = System.IO.Path.GetRandomFileName(); + PowerShellRunspace psRunspace = new PowerShellRunspace(this.logger); + Assert.Throws(() => psRunspace.GetModuleInfo(modulePath)); + } + + /// + /// Test that there is a fatal error when the given module path is an invalid module. + /// + [Fact] + public void FatalErrorWhenModulePathNotValid() + { + string modulePath = GetRootedPath(System.IO.Path.Combine("data", "Modules", "InvalidManifest", "InvalidManifest.psd1")); + PowerShellRunspace psRunspace = new PowerShellRunspace(this.logger); + Assert.Throws(() => psRunspace.GetModuleInfo(modulePath)); + } + + /// + /// Test running a basic PowerShell command and getting a result. + /// + [Fact] + public void RunGetVerb() + { + PowerShellRunspace psRunspace = new PowerShellRunspace(this.logger); + ICommandBuilder psCommand = psRunspace.CreateCommand(); + psCommand.Command = "Get-Verb"; + psCommand.AddParameter("Verb", "Start"); + CommandExecutionResult results = psCommand.Invoke(); + Assert.NotNull(results); + Assert.Equal(1, results.Results.Count()); + Assert.False(results.HadErrors); + object result = results.Results.FirstOrDefault(); + Assert.NotNull(result); + Assert.Equal("@{Verb=Start; Group=Lifecycle}", result.ToString()); + } + + /// + /// Test error execution flow for exceptions when Invoke is called. + /// + [Fact] + public void RunCommandWithExpectedErrorAsException() + { + PowerShellRunspace psRunspace = new PowerShellRunspace(this.logger); + ICommandBuilder psCommand = psRunspace.CreateCommand(); + psCommand.Command = "Get-Process"; + psCommand.AddParameter("Test", "Start"); + CommandExecutionResult results = psCommand.Invoke(); + Assert.True(results.HadErrors); + Assert.Equal(1, results.Errors.Count()); + } + + /// + /// Test error execution flow for PS error stream. + /// + [Fact] + public void RunCommandWithExpectedErrorAsErrorRecord() + { + PowerShellRunspace psRunspace = new PowerShellRunspace(this.logger); + ICommandBuilder psCommand = psRunspace.CreateCommand(); + psCommand.Command = "Get-Process"; + psCommand.AddParameter("Name", "123456789"); + CommandExecutionResult results = psCommand.Invoke(); + Assert.True(results.HadErrors); + Assert.Equal(1, results.Errors.Count()); + } + + /// + /// Test parsing a module by name instead of path. + /// + [Fact] + public void GetModuleInfoByName() + { + string modulePath = "PowerShellGet"; + PowerShellRunspace psRunspace = new PowerShellRunspace(this.logger); + GeneratedModule module = psRunspace.GetModuleInfo(modulePath); + Assert.Equal(12, module.Operations.Count); + } + + /// + /// Test that if a valid module path is specified, it must be an absolute path. + /// + [Fact] + public void ModulePathNotRooted() + { + string modulePath = System.IO.Path.Combine("data", "Modules", "InvalidManifest", "InvalidManifest.psd1"); + PowerShellRunspace psRunspace = new PowerShellRunspace(this.logger); + Assert.Throws(() => psRunspace.GetModuleInfo(modulePath)); + } + + private string GetRootedPath(string relativePath) + { + string root = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)); + return Path.Combine(root, relativePath); + } + + /// + /// Verify contains the expected operation exactly. + /// + /// Resulting GeneratedModule object. + /// Expected operation info. + private void AssertModuleContainsOperation(GeneratedModule result, OperationData expectedOperation) + { + // Ignore these 11 parameters: + /* Verbose + Debug + ErrorAction + WarningAction + InformationAction + ErrorVariable + WarningVariable + InformationVariable + OutVariable + OutBuffer + PipelineVariable */ + Assert.True(result.Operations.ContainsKey(expectedOperation.OperationId.ToLowerInvariant())); + Assert.Equal(expectedOperation.Parameters.Count, result.Operations[expectedOperation.OperationId.ToLowerInvariant()].Parameters.Count-11); + + foreach (string parmName in expectedOperation.Parameters.Keys) + { + Assert.True(result.Operations[expectedOperation.OperationId.ToLowerInvariant()].Parameters.ContainsKey(parmName), parmName); + Assert.NotNull(result.Operations[expectedOperation.OperationId.ToLowerInvariant()].Parameters[parmName].Type); + Assert.Equal(expectedOperation.Parameters[parmName].Type.Type, result.Operations[expectedOperation.OperationId.ToLowerInvariant()].Parameters[parmName].Type.Type); + Assert.Equal(expectedOperation.Parameters[parmName].Type.Properties.Count, result.Operations[expectedOperation.OperationId.ToLowerInvariant()].Parameters[parmName].Type.Properties.Count); + foreach (string propertyName in expectedOperation.Parameters[parmName].Type.Properties.Keys) + { + Assert.Equal(expectedOperation.Parameters[parmName].Type.Properties[propertyName].Type, result.Operations[expectedOperation.OperationId.ToLowerInvariant()].Parameters[parmName].Type.Properties[propertyName].Type); + Assert.Equal(expectedOperation.Parameters[parmName].Type.Properties[propertyName].Name, result.Operations[expectedOperation.OperationId.ToLowerInvariant()].Parameters[parmName].Type.Properties[propertyName].Name); + Assert.Equal(expectedOperation.Parameters[parmName].Type.Properties[propertyName].JsonName, result.Operations[expectedOperation.OperationId.ToLowerInvariant()].Parameters[parmName].Type.Properties[propertyName].JsonName); + } + } + } + } +} + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/BasicHappyPath/BasicHappyPath.psd1 b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/BasicHappyPath/BasicHappyPath.psd1 new file mode 100644 index 0000000..66869d5 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/BasicHappyPath/BasicHappyPath.psd1 @@ -0,0 +1,21 @@ +@{ + RootModule = 'BasicHappyPath.psm1' + ModuleVersion = '0.0.1' + GUID = '2cb82de2-af44-4483-9e8d-522a2c46436b' + Author = 'Microsoft Corporation' + CompanyName = 'Microsoft Corporation' + Copyright = '(c) Microsoft Corporation. All rights reserved.' + Description = 'Basic happy path module' + PowerShellVersion = '5.0' + FunctionsToExport = @('New-Guitar','Get-Guitar') + CmdletsToExport = '' + VariablesToExport = '' + AliasesToExport = '' + RequiredModules = @('PowerShellGet') + NestedModules = @() + FileList = @( + 'BasicHappyPath.psd1', + 'BasicHappyPath.psm1' + ) +} + diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/BasicHappyPath/BasicHappyPath.psm1 b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/BasicHappyPath/BasicHappyPath.psm1 new file mode 100644 index 0000000..a55f114 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/BasicHappyPath/BasicHappyPath.psm1 @@ -0,0 +1,29 @@ +function New-Guitar +{ + [CmdletBinding(DefaultParameterSetName='Guitar_Create')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Guitar_Create')] + [System.String] + $Id, + + [Parameter(Mandatory = $true, ParameterSetName = 'Guitar_CreateByName')] + [System.String] + $Name + ) + + Write-Host 'test' +} + +function Get-Guitar +{ + [CmdletBinding(DefaultParameterSetName='Guitar_Get')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Guitar_GetById')] + [System.String] + $Id + ) + + Write-Host 'test' +} + +Export-ModuleMember -Function New-Guitar,Get-Guitar \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/InvalidManifest/InvalidManifest.psd1 b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/InvalidManifest/InvalidManifest.psd1 new file mode 100644 index 0000000..cb62227 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/InvalidManifest/InvalidManifest.psd1 @@ -0,0 +1,20 @@ +@{ + RootModule = 'InvalidManifest.psm1' + ModuleVersion = '0.0.1' + GUID = 'fa800c34-46a9-4a8b-87c0-fff415923e7d' + Author = 'Microsoft Corporation' + CompanyName = 'Microsoft Corporation' + Copyright = '(c) Microsoft Corporation. All rights reserved.' + Description = 'Module with invalid manifest' + PowerShellVersion = '5.0' + FunctionsToExport = @('New-Guitar','Get-Guitar') + CmdletsToExport = '' + VariablesToExport = '' + AliasesToExport = '' + RequiredModules = @('PowerShellGet') + NestedModules = @() + FileList = @( + 'InvalidManifest.psd1', + 'InvalidManifest.psm1' + ) +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/MissingParameterSet/MissingParameterSet.psd1 b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/MissingParameterSet/MissingParameterSet.psd1 new file mode 100644 index 0000000..aa0496e --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/MissingParameterSet/MissingParameterSet.psd1 @@ -0,0 +1,20 @@ +@{ + RootModule = 'MissingParameterSet.psm1' + ModuleVersion = '0.0.1' + GUID = '7f122ac5-8607-4970-9325-db1e59016b34' + Author = 'Microsoft Corporation' + CompanyName = 'Microsoft Corporation' + Copyright = '(c) Microsoft Corporation. All rights reserved.' + Description = 'Module with at least one function missing a parameter set' + PowerShellVersion = '5.0' + FunctionsToExport = @('New-Guitar','Get-Guitar') + CmdletsToExport = '' + VariablesToExport = '' + AliasesToExport = '' + RequiredModules = @('PowerShellGet') + NestedModules = @() + FileList = @( + 'MissingParameterSet.psd1', + 'MissingParameterSet.psm1' + ) +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/MissingParameterSet/MissingParameterSet.psm1 b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/MissingParameterSet/MissingParameterSet.psm1 new file mode 100644 index 0000000..12d78e8 --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/data/Modules/MissingParameterSet/MissingParameterSet.psm1 @@ -0,0 +1,28 @@ +function New-Guitar +{ + [CmdletBinding(DefaultParameterSetName='Guitar_CreateOrUpdate')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Guitar_Create')] + [System.String] + $Id, + + [System.String] + $Name + ) + + Write-Host 'test' +} + +function Get-Guitar +{ + [CmdletBinding(DefaultParameterSetName='Guitar_Get')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Guitar_GetById')] + [System.String] + $Id + ) + + Write-Host 'test' +} + +Export-ModuleMember -Function New-Guitar,Get-Guitar \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/nuget.config b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/nuget.config index 6cedc86..8b55962 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/nuget.config +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/nuget.config @@ -1,13 +1,13 @@ - + - - + + - + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/PSSwagger.LTF.Lib.UnitTests.csproj b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/PSSwagger.LTF.Lib.UnitTests.csproj index 80cb109..0e57ea6 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/PSSwagger.LTF.Lib.UnitTests.csproj +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/PSSwagger.LTF.Lib.UnitTests.csproj @@ -33,8 +33,16 @@ 4 + + packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + True + + + packages\System.Management.Automation.6.1.7601.17515\lib\net45\System.Management.Automation.dll + True + @@ -59,33 +67,35 @@ - - Mocks\StringPipe.cs - - - Mocks\TestBlockPipe.cs + + Mocks\%(FileName).cs - - Mocks\MockGeneratedModule.cs - - - Mocks\MockRunspaceManager.cs - - - Mocks\MockCommandBuilder.cs - - + + + + + + + + + + - - {D2E4CBA1-BA97-4C60-9645-317666DF6C9F} - PSSwagger.LTF.Lib - + + - + + data\%(RecursiveDir)\%(FileName).psd1 + PreserveNewest + + + data\%(RecursiveDir)\%(FileName).psm1 + PreserveNewest + @@ -93,6 +103,16 @@ + + + {6c53850a-7758-452c-8c02-cbaa894b814e} + PSSwagger.LTF.IO.Lib + + + {d2e4cba1-ba97-4c60-9645-317666df6c9f} + PSSwagger.LTF.Lib + + diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/PSSwagger.LTF.Lib.UnitTests.sln b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/PSSwagger.LTF.Lib.UnitTests.sln index 92efc6b..9991be0 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/PSSwagger.LTF.Lib.UnitTests.sln +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/PSSwagger.LTF.Lib.UnitTests.sln @@ -5,6 +5,10 @@ VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSSwagger.LTF.Lib.UnitTests", "PSSwagger.LTF.Lib.UnitTests.csproj", "{70227536-A2BE-4A2F-9AFB-770A3996A7AF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSSwagger.LTF.Lib", "..\..\..\src\PSSwagger.LTF.Lib\vs-csproj\PSSwagger.LTF.Lib.csproj", "{D2E4CBA1-BA97-4C60-9645-317666DF6C9F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSSwagger.LTF.IO.Lib", "..\..\..\src\PSSwagger.LTF.IO.Lib\vs-csproj\PSSwagger.LTF.IO.Lib.csproj", "{6C53850A-7758-452C-8C02-CBAA894B814E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +19,14 @@ Global {70227536-A2BE-4A2F-9AFB-770A3996A7AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {70227536-A2BE-4A2F-9AFB-770A3996A7AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {70227536-A2BE-4A2F-9AFB-770A3996A7AF}.Release|Any CPU.Build.0 = Release|Any CPU + {D2E4CBA1-BA97-4C60-9645-317666DF6C9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D2E4CBA1-BA97-4C60-9645-317666DF6C9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2E4CBA1-BA97-4C60-9645-317666DF6C9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D2E4CBA1-BA97-4C60-9645-317666DF6C9F}.Release|Any CPU.Build.0 = Release|Any CPU + {6C53850A-7758-452C-8C02-CBAA894B814E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C53850A-7758-452C-8C02-CBAA894B814E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C53850A-7758-452C-8C02-CBAA894B814E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C53850A-7758-452C-8C02-CBAA894B814E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/app.config b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/app.config new file mode 100644 index 0000000..dde2c3c --- /dev/null +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/packages.config b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/packages.config index 21c654a..075dcd1 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/packages.config +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LTF.Lib.UnitTests/vs-csproj/packages.config @@ -1,5 +1,7 @@  + + diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LiveTestFramework.Tests.psd1 b/PSSwagger.LiveTestFramework/test/PSSwagger.LiveTestFramework.Tests.psd1 index 3a67351..4fd6712 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LiveTestFramework.Tests.psd1 +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LiveTestFramework.Tests.psd1 @@ -7,13 +7,13 @@ Copyright = '(c) Microsoft Corporation. All rights reserved.' Description = 'PowerShell module with commands for testing the PSSwagger.LiveTestFramework module.' PowerShellVersion = '5.0' - FunctionsToExport = @('Initialize-Dependencies','Start-Run') + FunctionsToExport = @('Initialize-TestDependency','Start-TestRun') CmdletsToExport = '' VariablesToExport = '' AliasesToExport = '' RequiredModules = @('PSSwaggerUtility') NestedModules = @() - DefaultCommandPrefix = 'LTFTests' + DefaultCommandPrefix = 'LTF' FileList = @( 'PSSwagger.LiveTestFramework.Tests.psd1', 'PSSwagger.LiveTestFramework.Tests.psm1' @@ -21,8 +21,7 @@ PrivateData = @{ PSData = @{ - Tags = @('Azure', - 'Swagger', + Tags = @('Swagger', 'PSEdition_Desktop', 'PSEdition_Core', 'Linux', diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LiveTestFramework.Tests.psm1 b/PSSwagger.LiveTestFramework/test/PSSwagger.LiveTestFramework.Tests.psm1 index 4137de3..5835cae 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LiveTestFramework.Tests.psm1 +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LiveTestFramework.Tests.psm1 @@ -4,58 +4,14 @@ Microsoft.PowerShell.Core\Set-StrictMode -Version Latest .DESCRIPTION Ensures all dependencies required for running tests are present on the current machine. #> -function Initialize-Dependencies { +function Initialize-TestDependency { [CmdletBinding()] param() $failed = $false - $expectedDotNetVersion = "2.1.0-preview1-006547"#"2.0.0-preview1-005952" Write-Host "Setting up PSSwagger.LiveTestFramework.Tests dependencies:" - Write-Host " dotnet: $expectedDotnetVersion" Write-Host " Pester: *" Write-Host "" - if (-not (Get-Command -Name 'dotnet' -ErrorAction Ignore)) { - Write-Verbose -Message "dotnet not found in path. Attempting to add the expected dotnet CLI path." - $originalPath = $env:PATH - $dotnetPath = Get-DotNetPath - $env:PATH = $dotnetPath + [IO.Path]::PathSeparator + $env:PATH - - if (-not (Get-Command -Name 'dotnet' -ErrorAction Ignore)) { - $env:PATH = $originalPath - Write-Verbose -Message "None of that worked! Re-bootstrapping dotnet CLI." - Install-Dotnet -Version $expectedDotNetVersion - } else { - $dotnetversion = dotnet --version - if ($dotnetversion -ne $expectedDotNetVersion) { - Write-Verbose -Message "Unsupported dotnet version found: $dotnetversion. Downloading dotnet CLI." - Install-Dotnet -Version $expectedDotNetVersion - } - } - } else { - $dotnetversion = dotnet --version - if ($dotnetversion -ne $expectedDotNetVersion) { - Write-Verbose -Message "Unsupported dotnet version found: $dotnetversion. Attempting to add the expected dotnet CLI path." - $originalPath = $env:PATH - $dotnetPath = Get-DotNetPath - $env:PATH = $dotnetPath + [IO.Path]::PathSeparator + $env:PATH - if (-not (Get-Command -Name 'dotnet' -ErrorAction Ignore)) { - $env:PATH = $originalPath - Write-Verbose -Message "None of that worked! Re-bootstrapping dotnet CLI." - Install-Dotnet -Version $expectedDotNetVersion - } else { - $dotnetversion = dotnet --version - if ($dotnetversion -ne $expectedDotNetVersion) { - Write-Verbose -Message "Unsupported dotnet version found: $dotnetversion. Downloading dotnet CLI." - Install-Dotnet -Version $expectedDotNetVersion - } - } - } - } - - if (-not (Get-Command -Name 'dotnet' -ErrorAction Ignore)) { - Write-Error -Message 'Failed to set up dotnet dependency.' - $failed = $true - } if (-not (Get-Command Invoke-Pester)) { $pesterModule = Get-Module Pester -ListAvailable | Select-Object -First 1 @@ -85,65 +41,53 @@ function Initialize-Dependencies { Write-Verbose -Message "Pester already installed: $((Get-Module Pester -ListAvailable | Select-Object -First 1).Version)" } + Import-Module -Name (Split-Path -Path $PSScriptRoot -Parent | Join-Path -ChildPath "build" | Join-Path -ChildPath "PSSwagger.LiveTestFramework.Build.psd1") -Force + $failed = $failed -or PSSwagger.LiveTestFramework.Build\Initialize-LTFBuildDependency if ($failed) { Write-Error -Message 'One or more dependencies failed to intialize.' } else { Write-Host "Completed setting up PSSwagger.LiveTestFramework.Tests dependencies." -BackgroundColor DarkGreen } + + $failed } <# .DESCRIPTION Initiates a test run. Also calls Initialize-Dependencies #> -function Start-Run { +function Start-TestRun { [CmdletBinding()] - param( - [ValidateSet("net452","netcoreapp2.0")] - [Parameter(Mandatory=$false)] - [string] - $Framework = 'net452' - ) + param() + # Currently running PowerShell in-process isn't supported in PowerShell Core, so we currently only support full CLR + $Framework = 'net452' Write-Host "Test run for framework: $Framework" -BackgroundColor DarkYellow - Initialize-Dependencies + Initialize-TestDependency - # Ensure C# files exist - Write-Host "Transforming code files into C# before executing unit tests" - pushd (Join-Path -Path $PSScriptRoot -ChildPath .. | Join-Path -ChildPath 'src') - .\ConvertTo-CSharpFiles.ps1 - popd - Write-Host "Discovering and running C# projects" $trxLogs = @() - $pesterLogs = @() - Get-ChildItem -Path (Join-Path -Path . -ChildPath "*.csproj") -File -Recurse | ForEach-Object { + Write-Host "Discovering and running C# test projects" + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath ".." | Join-Path -ChildPath "build" | Join-Path -ChildPath "PSSwagger.LiveTestFramework.Build.psd1") -Force + Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath "*.csproj") -File -Recurse | ForEach-Object { # Execute only if it's a Microsoft.NET.SDK project if ((Get-Content -Path $_.FullName | Out-String).Contains(" Date: Wed, 23 Aug 2017 10:32:15 -0700 Subject: [PATCH 08/40] Support default and customizable header comment for the PSSwagger generated files (#310) * Support default and customizable header comment for the PSSwagger generated files. Added Header parameter on New-PSSwaggerModule cmdlet to prepend the customized header content in the PSSwagger generated files. - It also can be a path to a .txt file with the content to be added as header in the PSSwagger generated files. - Specify 'NONE' to suppress the default header. - Below is the default header content ` Code generated by Microsoft (R) PSSwagger Changes may cause incorrect behavior and will be lost if the code is regenerated. ` - Also enabled support for header content from x-ms-code-generation-settings extension in swagger spec. - To customize the header for AutoRest generated files, use AutoRest specific info.x-ms-code-generation-settings.header in the swagger spec. --- PSSwagger/Definitions.psm1 | 39 ++++- PSSwagger/PSSwagger.Constants.ps1 | 17 ++ PSSwagger/PSSwagger.Resources.psd1 | 2 + PSSwagger/PSSwagger.psm1 | 150 ++++++++++++++++-- PSSwagger/Paths.psm1 | 19 ++- PSSwagger/SwaggerUtils.psm1 | 15 ++ README.md | 3 +- Tests/PSSwagger.Unit.Tests.ps1 | 53 +++++++ Tests/PSSwaggerScenario.Tests.ps1 | 61 ++++++- .../ParameterTypes/ParameterTypesSpec.json | 5 +- Tests/run-tests.ps1 | 3 +- docs/commands/New-PSSwaggerModule.md | 27 +++- 12 files changed, 363 insertions(+), 31 deletions(-) diff --git a/PSSwagger/Definitions.psm1 b/PSSwagger/Definitions.psm1 index 53928d4..c5d7ede 100644 --- a/PSSwagger/Definitions.psm1 +++ b/PSSwagger/Definitions.psm1 @@ -565,11 +565,24 @@ function New-SwaggerDefinitionCommand [Parameter(Mandatory = $true)] [string] - $Models + $Models, + + [Parameter(Mandatory=$false)] + [AllowEmptyString()] + [string] + $HeaderContent ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState + $PSHeaderComment = $null + $XmlHeaderComment = $null + + if($HeaderContent) { + $PSHeaderComment = ($PSCommentFormatString -f $HeaderContent) + $XmlHeaderComment = ($XmlCommentFormatString -f $HeaderContent) + } + $FunctionsToExport = @() $GeneratedCommandsPath = Join-Path -Path $SwaggerMetaDict['outputDirectory'] -ChildPath $GeneratedCommandsName $SwaggerDefinitionCommandsPath = Join-Path -Path $GeneratedCommandsPath -ChildPath 'SwaggerDefinitionCommands' @@ -584,13 +597,15 @@ function New-SwaggerDefinitionCommand if ($FunctionDetails.ContainsKey('GenerateDefinitionCmdlet') -and ($FunctionDetails['GenerateDefinitionCmdlet'] -eq $true)) { $FunctionsToExport += New-SwaggerSpecDefinitionCommand -FunctionDetails $FunctionDetails ` -GeneratedCommandsPath $SwaggerDefinitionCommandsPath ` - -ModelsNamespace "$Namespace.$Models" + -ModelsNamespace "$Namespace.$Models" ` + -PSHeaderComment $PSHeaderComment } New-SwaggerDefinitionFormatFile -FunctionDetails $FunctionDetails ` -FormatFilesPath $FormatFilesPath ` -Namespace $NameSpace ` - -Models $Models + -Models $Models ` + -XmlHeaderComment $XmlHeaderComment } } @@ -774,7 +789,12 @@ function New-SwaggerSpecDefinitionCommand [Parameter(Mandatory=$true)] [string] - $ModelsNamespace + $ModelsNamespace, + + [Parameter(Mandatory=$false)] + [AllowEmptyString()] + [string] + $PSHeaderComment ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState @@ -824,7 +844,7 @@ function New-SwaggerSpecDefinitionCommand } $CommandFilePath = Join-Path -Path $GeneratedCommandsPath -ChildPath "$CommandName.ps1" - Out-File -InputObject $CommandString -FilePath $CommandFilePath -Encoding ascii -Force -Confirm:$false -WhatIf:$false + Out-File -InputObject @($PSHeaderComment, $CommandString) -FilePath $CommandFilePath -Encoding ascii -Force -Confirm:$false -WhatIf:$false Write-Verbose -Message ($LocalizedData.GeneratedDefinitionCommand -f ($commandName, $FunctionDetails.Name)) @@ -853,7 +873,12 @@ function New-SwaggerDefinitionFormatFile [Parameter(Mandatory=$true)] [string] - $Models + $Models, + + [Parameter(Mandatory=$false)] + [AllowEmptyString()] + [string] + $XmlHeaderComment ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState @@ -883,7 +908,7 @@ function New-SwaggerDefinitionFormatFile $TableColumnHeaders = $null $TableColumnItems = $TableColumnItemsList -join "`r`n" - $FormatViewDefinition = $FormatViewDefinitionStr -f ($ViewName, $ViewTypeName, $TableColumnHeaders, $TableColumnItems) + $FormatViewDefinition = $FormatViewDefinitionStr -f ($ViewName, $ViewTypeName, $TableColumnHeaders, $TableColumnItems, $XmlHeaderComment) if(-not (Test-Path -Path $FormatFilesPath -PathType Container)) { diff --git a/PSSwagger/PSSwagger.Constants.ps1 b/PSSwagger/PSSwagger.Constants.ps1 index 9e84de7..ed6a561 100644 --- a/PSSwagger/PSSwagger.Constants.ps1 +++ b/PSSwagger/PSSwagger.Constants.ps1 @@ -483,6 +483,7 @@ $GeneratedCommandsName = 'Generated.PowerShell.Commands' $FormatViewDefinitionStr = @' +{4} @@ -517,3 +518,19 @@ $TableColumnHeaderStr = @' {0} '@ + +$DefaultGeneratedFileHeader = @' +Code generated by Microsoft (R) PSSwagger {0} +Changes may cause incorrect behavior and will be lost if the code is +regenerated. +'@ + +$PSCommentFormatString = "<# +{0} +#> +" + +$XmlCommentFormatString = " +" \ No newline at end of file diff --git a/PSSwagger/PSSwagger.Resources.psd1 b/PSSwagger/PSSwagger.Resources.psd1 index 5e53bda..334c69f 100644 --- a/PSSwagger/PSSwagger.Resources.psd1 +++ b/PSSwagger/PSSwagger.Resources.psd1 @@ -84,5 +84,7 @@ ConvertFrom-StringData @' ServiceTypeMetadataFileNotFound=The service type metadata file '{0}' does not exist. UnknownPSMetadataProperty=Unknown '{0}' property: '{1}'. InvalidPSMetaFlattenParameter=Flatten property is specified as 'true' for an invalid parameter '{0}' with type '{1}'. + InvalidHeaderFileExtension=Header '{0}' file extension should be '.txt'. + InvalidHeaderFilePath=The specified value '{0}' for Header parameter is should be a valid file path. ###PSLOC '@ \ No newline at end of file diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index 0c6e3c2..bcaf2c6 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -58,6 +58,11 @@ Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename PSSwa .PARAMETER DefaultCommandPrefix Prefix value to be prepended to cmdlet noun or to cmdlet name without verb. +.PARAMETER Header + Text to include as a header comment in the PSSwagger generated files. + It also can be a path to a .txt file with the content to be added as header in the PSSwagger generated files. + Specify 'NONE' to suppress the default header. + .PARAMETER NoAssembly Switch to disable saving the precompiled module assembly and instead enable dynamic compilation. @@ -120,6 +125,10 @@ function New-PSSwaggerModule [string] $DefaultCommandPrefix, + [Parameter(Mandatory = $false)] + [string[]] + $Header, + [Parameter()] [switch] $UseAzureCsharpGenerator, @@ -346,6 +355,7 @@ function New-PSSwaggerModule HostOverrideCommand = "" NoAuthChallenge = $false NameSpacePrefix = '' + Header = '' } # Parse the JSON and populate the dictionary @@ -354,6 +364,7 @@ function New-PSSwaggerModule ModuleName = $Name ModuleVersion = $Version DefaultCommandPrefix = $DefaultCommandPrefix + Header = $($Header -join "`r`n") SwaggerSpecFilePaths = $SwaggerSpecFilePaths DefinitionFunctionsDetails = $DefinitionFunctionsDetails AzureSpec = $UseAzureCsharpGenerator @@ -470,16 +481,27 @@ function New-PSSwaggerModule # Need to expand the definitions early as parameter flattening feature requires the parameters list of the definition/model types. Expand-SwaggerDefinition -DefinitionFunctionsDetails $DefinitionFunctionsDetails -NameSpace $NameSpace -Models $Models + $HeaderContent = Get-HeaderContent -SwaggerDict $SwaggerDict -ErrorVariable ev + if($ev) { + return + } + $PSHeaderComment = $null + if($HeaderContent) { + $PSHeaderComment = ($PSCommentFormatString -f $HeaderContent) + } + $FunctionsToExport = @() $FunctionsToExport += New-SwaggerSpecPathCommand -PathFunctionDetails $PathFunctionDetails ` -SwaggerMetaDict $swaggerMetaDict ` -SwaggerDict $swaggerDict ` - -DefinitionFunctionsDetails $DefinitionFunctionsDetails + -DefinitionFunctionsDetails $DefinitionFunctionsDetails ` + -PSHeaderComment $PSHeaderComment $FunctionsToExport += New-SwaggerDefinitionCommand -DefinitionFunctionsDetails $DefinitionFunctionsDetails ` -SwaggerMetaDict $swaggerMetaDict ` -NameSpace $nameSpace ` - -Models $models + -Models $models ` + -HeaderContent $HeaderContent $RootModuleFilePath = Join-Path $outputDirectory "$Name.psm1" $testCoreModuleRequirements = '' @@ -490,7 +512,7 @@ function New-PSSwaggerModule } Out-File -FilePath $RootModuleFilePath ` - -InputObject $ExecutionContext.InvokeCommand.ExpandString($RootModuleContents)` + -InputObject @($PSHeaderComment, $ExecutionContext.InvokeCommand.ExpandString($RootModuleContents))` -Encoding ascii ` -Force ` -Confirm:$false ` @@ -498,12 +520,21 @@ function New-PSSwaggerModule New-ModuleManifestUtility -Path $outputDirectory ` -FunctionsToExport $FunctionsToExport ` - -Info $swaggerDict['info'] + -Info $swaggerDict['info'] ` + -PSHeaderComment $PSHeaderComment + + $CopyFilesMap = [ordered]@{ + 'Generated.Resources.psd1' = "$Name.Resources.psd1" + 'GeneratedHelpers.psm1' = 'GeneratedHelpers.psm1' + 'Test-CoreRequirements.ps1' = 'Test-CoreRequirements.ps1' + 'Test-FullRequirements.ps1' = 'Test-FullRequirements.ps1' + } - Copy-Item (Join-Path -Path "$PSScriptRoot" -ChildPath "Generated.Resources.psd1") (Join-Path -Path "$outputDirectory" -ChildPath "$Name.Resources.psd1") -Force - Copy-Item (Join-Path -Path "$PSScriptRoot" -ChildPath "GeneratedHelpers.psm1") (Join-Path -Path "$outputDirectory" -ChildPath "GeneratedHelpers.psm1") -Force - Copy-Item (Join-Path -Path "$PSScriptRoot" -ChildPath "Test-CoreRequirements.ps1") (Join-Path -Path "$outputDirectory" -ChildPath "Test-CoreRequirements.ps1") -Force - Copy-Item (Join-Path -Path "$PSScriptRoot" -ChildPath "Test-FullRequirements.ps1") (Join-Path -Path "$outputDirectory" -ChildPath "Test-FullRequirements.ps1") -Force + $CopyFilesMap.GetEnumerator() | ForEach-Object { + Copy-PSFileWithHeader -SourceFilePath (Join-Path -Path "$PSScriptRoot" -ChildPath $_.Name) ` + -DestinationFilePath (Join-Path -Path "$outputDirectory" -ChildPath $_.Value) ` + -PSHeaderComment $PSHeaderComment + } Write-Verbose -Message ($LocalizedData.SuccessfullyGeneratedModule -f $Name,$outputDirectory) } @@ -790,15 +821,21 @@ function New-ModuleManifestUtility [Parameter(Mandatory=$true)] [hashtable] - $Info + $Info, + + [Parameter(Mandatory=$false)] + [AllowEmptyString()] + [string] + $PSHeaderComment ) $FormatsToProcess = Get-ChildItem -Path "$Path\$GeneratedCommandsName\FormatFiles\*.ps1xml" ` -File ` -ErrorAction Ignore | Foreach-Object { $_.FullName.Replace($Path, '.') } - + + $ModuleManifestFilePath = "$(Join-Path -Path $Path -ChildPath $Info.ModuleName).psd1" $NewModuleManifest_params = @{ - Path = "$(Join-Path -Path $Path -ChildPath $Info.ModuleName).psd1" + Path = $ModuleManifestFilePath ModuleVersion = $Info.Version Description = $Info.Description CopyRight = $info.LicenseName @@ -810,6 +847,7 @@ function New-ModuleManifestUtility CmdletsToExport = @() AliasesToExport = @() VariablesToExport = @() + PassThru = $true } if($Info.DefaultCommandPrefix) @@ -831,7 +869,95 @@ function New-ModuleManifestUtility } } - New-ModuleManifest @NewModuleManifest_params + $PassThruContent = New-ModuleManifest @NewModuleManifest_params + + # Add header comment + if ($PSHeaderComment) { + Out-File -FilePath $ModuleManifestFilePath ` + -InputObject @($PSHeaderComment, $PassThruContent)` + -Encoding ascii ` + -Force ` + -Confirm:$false ` + -WhatIf:$false + } +} + +function Get-HeaderContent { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [PSCustomObject] + $SwaggerDict + ) + + $Header = $swaggerDict['Info'].Header + $HeaderContent = ($DefaultGeneratedFileHeader -f $MyInvocation.MyCommand.Module.Version) + if ($Header) { + $HeaderFilePath = Resolve-Path -Path $Header -ErrorAction Ignore + if ($HeaderFilePath) { + # Selecting the first path when multiple paths are returned by Resolve-Path cmdlet. + if ($HeaderFilePath.PSTypeNames -contains 'System.Array') { + $HeaderFilePath = $HeaderFilePath[0] + } + + if (-not $HeaderFilePath.Path.EndsWith('.txt', [System.StringComparison]::OrdinalIgnoreCase)) { + $message = ($LocalizedData.InvalidHeaderFileExtension -f $Header) + Write-Error -Message $message -ErrorId 'InvalidHeaderFileExtension' -Category InvalidArgument + return + } + + if (-not (Test-Path -LiteralPath $HeaderFilePath -PathType Leaf)) { + $message = ($LocalizedData.InvalidHeaderFilePath -f $Header) + Write-Error -Message $message -ErrorId 'InvalidHeaderFilePath' -Category InvalidArgument + return + } + + $HeaderContent = Get-Content -LiteralPath $HeaderFilePath -Raw + } + elseif ($Header.EndsWith('.txt', [System.StringComparison]::OrdinalIgnoreCase)) { + # If this is an existing '.txt' file above Resolve-Path returns a valid header file path + $message = ($LocalizedData.PathNotFound -f $Header) + Write-Error -Message $message -ErrorId 'HeaderFilePathNotFound' -Category InvalidArgument + return + } + elseif ($Header -eq 'NONE') { + $HeaderContent = $null + } + else { + $HeaderContent = $Header + } + } + + return $HeaderContent +} + +function Copy-PSFileWithHeader { + param( + [Parameter(Mandatory = $true)] + [string] + $SourceFilePath, + + [Parameter(Mandatory = $true)] + [string] + $DestinationFilePath, + + [Parameter(Mandatory = $false)] + [AllowEmptyString()] + [string] + $PSHeaderComment + ) + + if (-not (Test-Path -Path $SourceFilePath -PathType Leaf)) { + Throw ($LocalizedData.PathNotFound -f $SourceFilePath) + } + + $FileContent = Get-Content -Path $SourceFilePath -Raw + Out-File -FilePath $DestinationFilePath ` + -InputObject @($PSHeaderComment, $FileContent)` + -Encoding ascii ` + -Force ` + -Confirm:$false ` + -WhatIf:$false } #endregion diff --git a/PSSwagger/Paths.psm1 b/PSSwagger/Paths.psm1 index 00853fd..ba3c636 100644 --- a/PSSwagger/Paths.psm1 +++ b/PSSwagger/Paths.psm1 @@ -276,7 +276,12 @@ function New-SwaggerSpecPathCommand [Parameter(Mandatory=$true)] [hashtable] - $DefinitionFunctionsDetails + $DefinitionFunctionsDetails, + + [Parameter(Mandatory=$false)] + [AllowEmptyString()] + [string] + $PSHeaderComment ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState @@ -288,7 +293,8 @@ function New-SwaggerSpecPathCommand -SwaggerMetaDict $SwaggerMetaDict ` -SwaggerDict $SwaggerDict ` -PathFunctionDetails $PathFunctionDetails ` - -DefinitionFunctionsDetails $DefinitionFunctionsDetails + -DefinitionFunctionsDetails $DefinitionFunctionsDetails ` + -PSHeaderComment $PSHeaderComment } return $FunctionsToExport @@ -393,7 +399,12 @@ function New-SwaggerPath [Parameter(Mandatory=$true)] [hashtable] - $DefinitionFunctionsDetails + $DefinitionFunctionsDetails, + + [Parameter(Mandatory=$false)] + [AllowEmptyString()] + [string] + $PSHeaderComment ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState @@ -984,7 +995,7 @@ function New-SwaggerPath } $CommandFilePath = Join-Path -Path $GeneratedCommandsPath -ChildPath "$commandName.ps1" - Out-File -InputObject $CommandString -FilePath $CommandFilePath -Encoding ascii -Force -Confirm:$false -WhatIf:$false + Out-File -InputObject @($PSHeaderComment, $CommandString) -FilePath $CommandFilePath -Encoding ascii -Force -Confirm:$false -WhatIf:$false Write-Verbose -Message ($LocalizedData.GeneratedPathCommand -f $commandName) diff --git a/PSSwagger/SwaggerUtils.psm1 b/PSSwagger/SwaggerUtils.psm1 index 78b9cba..81f5500 100644 --- a/PSSwagger/SwaggerUtils.psm1 +++ b/PSSwagger/SwaggerUtils.psm1 @@ -70,6 +70,11 @@ function ConvertTo-SwaggerDictionary { [string] $DefaultCommandPrefix, + [Parameter(Mandatory = $false)] + [AllowEmptyString()] + [string] + $Header, + [Parameter(Mandatory = $false)] [switch] $AzureSpec, @@ -117,6 +122,9 @@ function ConvertTo-SwaggerDictionary { } $swaggerDict['Info'] = Get-SwaggerInfo @GetSwaggerInfo_params $swaggerDict['Info']['DefaultCommandPrefix'] = $DefaultCommandPrefix + if($Header) { + $swaggerDict['Info']['Header'] = $Header + } $SwaggerParameters = @{} $SwaggerDefinitions = @{} @@ -192,6 +200,7 @@ function Get-SwaggerInfo { $NameSpace = '' $codeGenFileRequired = $false $modelsName = 'Models' + $Header = '' if(Get-Member -InputObject $Info -Name 'x-ms-code-generation-settings') { $prop = Test-PropertyWithAliases -InputObject $Info.'x-ms-code-generation-settings' -Aliases @('ClientName', 'Name') if ($prop) { @@ -223,6 +232,11 @@ function Get-SwaggerInfo { Write-Warning -Message $LocalizedData.CustomNamespaceNotRecommended } + $prop = Test-PropertyWithAliases -InputObject $Info.'x-ms-code-generation-settings' -Aliases @('Header', 'h') + if ($prop) { + $Header = $Info.'x-ms-code-generation-settings'.$prop + } + # When the following values are specified, the property will be overwritten by PSSwagger using a CodeGenSettings file foreach ($ignoredParameterAliases in $script:IgnoredAutoRestParameters) { $prop = Test-PropertyWithAliases -InputObject $Info.'x-ms-code-generation-settings' -Aliases $ignoredParameterAliases @@ -324,6 +338,7 @@ function Get-SwaggerInfo { CodeOutputDirectory = $CodeOutputDirectory CodeGenFileRequired = $codeGenFileRequired Models = $modelsName + Header = $Header } } diff --git a/README.md b/README.md index 6d4897f..948da70 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,8 @@ NOTE: In the short term, for best performance, the operation IDs in your Open AP # If you are trying from a clone of this repository, follow below steps to import the PSSwagger module. # Ensure PSSwaggerUtility module is available in $env:PSModulePath - $PSSwaggerFolderPath = Resolve-Path '.\PSSwagger' + # Please note the trialing back slash ('\') to ensure PSSwaggerUtility module is available. + $PSSwaggerFolderPath = Resolve-Path '.\PSSwagger\' $env:PSModulePath = "$PSSwaggerFolderPath;$env:PSModulePath" Import-Module .\PSSwagger diff --git a/Tests/PSSwagger.Unit.Tests.ps1 b/Tests/PSSwagger.Unit.Tests.ps1 index 8c07068..d063161 100644 --- a/Tests/PSSwagger.Unit.Tests.ps1 +++ b/Tests/PSSwagger.Unit.Tests.ps1 @@ -186,5 +186,58 @@ Describe "PSSwagger Unit Tests" -Tag @('BVT', 'DRT', 'UnitTest', 'P0') { Get-XDGDirectory -DirectoryType Config | should beexactly $expectedPath } } + + Context "Get-HeaderContent Unit Tests" { + BeforeAll { + $moduleInfo = Get-Module -Name PSSwagger + $DefaultGeneratedFileHeaderString = $DefaultGeneratedFileHeader -f $moduleInfo.Version + + $InvalidHeaderFileExtensionPath = Join-Path -Path $PSScriptRoot -ChildPath '*.ps1' + $UnavailableHeaderFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'NotExistingHeaderFile.txt' + + $FolderPathEndingWithDotTxt = Join-Path -Path $PSScriptRoot -ChildPath 'TestFoldeName.txt' + $null = New-Item -Path $FolderPathEndingWithDotTxt -ItemType Directory -Force + + $ValidHeaderFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'TestHeader.txt' + Out-File -FilePath $ValidHeaderFilePath -InputObject 'Content from Header file' -Force -WhatIf:$false -Confirm:$false + $HeaderFileContent = Get-Content -Path $ValidHeaderFilePath -Raw + } + + AfterAll { + Get-Item -Path $FolderPathEndingWithDotTxt -ErrorAction SilentlyContinue | Remove-Item -Force + Get-Item -Path $ValidHeaderFilePath -ErrorAction SilentlyContinue | Remove-Item -Force + } + + It "Get-HeaderContent should return null when header value is 'NONE'" { + Get-HeaderContent -SwaggerDict @{Info = @{Header = 'NONE'}} | Should BeNullOrEmpty + } + + It "Get-HeaderContent should return default header content when header value is empty" { + Get-HeaderContent -SwaggerDict @{Info = @{Header = ''}} | Should BeExactly $DefaultGeneratedFileHeaderString + } + + It "Get-HeaderContent should return default header content when header value is null" { + Get-HeaderContent -SwaggerDict @{Info = @{Header = $null}} | Should BeExactly $DefaultGeneratedFileHeaderString + } + + It "Get-HeaderContent should throw when the specified header file path is invalid" { + Get-HeaderContent -SwaggerDict @{Info = @{Header = $InvalidHeaderFileExtensionPath}} -ErrorVariable ev -ErrorAction SilentlyContinue | Should BeNullOrEmpty + $ev[0].FullyQualifiedErrorId | Should BeExactly 'InvalidHeaderFileExtension,Get-HeaderContent' + } + + It "Get-HeaderContent should throw when the specified header file path is not found" { + Get-HeaderContent -SwaggerDict @{Info = @{Header = $UnavailableHeaderFilePath}} -ErrorVariable ev -ErrorAction SilentlyContinue | Should BeNullOrEmpty + $ev[0].FullyQualifiedErrorId | Should BeExactly 'HeaderFilePathNotFound,Get-HeaderContent' + } + + It "Get-HeaderContent should throw when the specified header file path is not a file path" { + Get-HeaderContent -SwaggerDict @{Info = @{Header = $FolderPathEndingWithDotTxt}} -ErrorVariable ev -ErrorAction SilentlyContinue | Should BeNullOrEmpty + $ev[0].FullyQualifiedErrorId | Should BeExactly 'InvalidHeaderFilePath,Get-HeaderContent' + } + + It "Get-HeaderContent should return content from the header file" { + Get-HeaderContent -SwaggerDict @{Info = @{Header = $ValidHeaderFilePath}} | Should BeExactly $HeaderFileContent + } + } } } diff --git a/Tests/PSSwaggerScenario.Tests.ps1 b/Tests/PSSwaggerScenario.Tests.ps1 index 3bea8c3..b768bc2 100644 --- a/Tests/PSSwaggerScenario.Tests.ps1 +++ b/Tests/PSSwaggerScenario.Tests.ps1 @@ -786,4 +786,63 @@ Describe "PSMetadataTests" -Tag @('PSMetadata','ScenarioTest') { Get-Command List-Cupcakes -Module Generated.PSMetadataTest.Module | should not BeNullOrEmpty } } -} \ No newline at end of file +} + +Describe "Header scenario tests" -Tag @('Header','ScenarioTest') { + BeforeAll { + $PsSwaggerPath = Split-Path -Path $PSScriptRoot -Parent | Join-Path -ChildPath "PSSwagger" + Import-Module $PsSwaggerPath -Force + + $SwaggerSpecPath = Join-Path -Path $PSScriptRoot -ChildPath 'Data' | Join-Path -ChildPath 'ParameterTypes' | Join-Path -ChildPath 'ParameterTypesSpec.json' + $GeneratedPath = Join-Path -Path $PSScriptRoot -ChildPath 'Generated' + $ModuleName = 'HeaderScenarioTestModule' + $GeneratedModuleBase = Join-Path -Path $GeneratedPath -ChildPath $ModuleName + if (Test-Path -Path $GeneratedModuleBase -PathType Container) { + Remove-Item -Path $GeneratedModuleBase -Recurse -Force + } + } + + It "Validate custom header content in the PSSwagger generated files" { + $ModuleVersion = '1.1.1.1' + $GeneratedModuleVersionPath = Join-Path -Path $GeneratedModuleBase -ChildPath $ModuleVersion + $Header = '__Custom_HEADER_Content__' + if((Get-Variable -Name PSEdition -ErrorAction Ignore) -and ('Core' -eq $PSEdition)) { + & "powershell.exe" -command "& {`$env:PSModulePath=`$env:PSModulePath_Backup; + Import-Module '$PsSwaggerPath' -Force -ArgumentList `$true; + New-PSSwaggerModule -SpecificationPath '$SwaggerSpecPath' -Name $ModuleName -Version '$ModuleVersion' -UseAzureCsharpGenerator -Path '$GeneratedPath' -NoAssembly -Verbose -ConfirmBootstrap -Header '$Header'; + }" + } else { + New-PSSwaggerModule -SpecificationPath $SwaggerSpecPath -Name $ModuleName -Version $ModuleVersion -UseAzureCsharpGenerator -Path $GeneratedPath -NoAssembly -Verbose -ConfirmBootstrap -Header $Header + } + + Test-Path -Path $GeneratedModuleVersionPath -PathType Container | Should Be $true + $FileList = Get-ChildItem -Path $GeneratedModuleVersionPath -File + $GeneratedPowerShellCommandsPath = Join-Path -Path $GeneratedModuleVersionPath -ChildPath 'Generated.PowerShell.Commands' + $FileList += Get-ChildItem -Path $GeneratedPowerShellCommandsPath -File -Recurse + $FileList | ForEach-Object { + (Get-Content -Path $_.FullName) -contains $Header | Should Be $true + } + } + + It "Validate header comment from x-ms-code-generation-settings in the PSSwagger generated files" { + $ModuleVersion = '2.2.2.2' + $GeneratedModuleVersionPath = Join-Path -Path $GeneratedModuleBase -ChildPath $ModuleVersion + if((Get-Variable -Name PSEdition -ErrorAction Ignore) -and ('Core' -eq $PSEdition)) { + & "powershell.exe" -command "& {`$env:PSModulePath=`$env:PSModulePath_Backup; + Import-Module '$PsSwaggerPath' -Force -ArgumentList `$true; + New-PSSwaggerModule -SpecificationPath '$SwaggerSpecPath' -Name $ModuleName -Version '$ModuleVersion' -UseAzureCsharpGenerator -Path '$GeneratedPath' -NoAssembly -Verbose -ConfirmBootstrap; + }" + } else { + New-PSSwaggerModule -SpecificationPath $SwaggerSpecPath -Name $ModuleName -Version $ModuleVersion -UseAzureCsharpGenerator -Path $GeneratedPath -NoAssembly -Verbose -ConfirmBootstrap + } + + Test-Path -Path $GeneratedModuleVersionPath -PathType Container | Should Be $true + $ExpectedHeaderContent = 'Header content from swagger spec' + $FileList = Get-ChildItem -Path $GeneratedModuleVersionPath -File + $GeneratedPowerShellCommandsPath = Join-Path -Path $GeneratedModuleVersionPath -ChildPath 'Generated.PowerShell.Commands' + $FileList += Get-ChildItem -Path $GeneratedPowerShellCommandsPath -File -Recurse + $FileList | ForEach-Object { + (Get-Content -Path $_.FullName) -contains $ExpectedHeaderContent | Should Be $true + } + } +} diff --git a/Tests/data/ParameterTypes/ParameterTypesSpec.json b/Tests/data/ParameterTypes/ParameterTypesSpec.json index b2712ff..59fe80a 100644 --- a/Tests/data/ParameterTypes/ParameterTypesSpec.json +++ b/Tests/data/ParameterTypes/ParameterTypesSpec.json @@ -3,7 +3,10 @@ "info": { "title": "ParameterTypesSpec", "description": "API that contains tests for all known parameter types", - "version": "2017-03-24" + "version": "2017-03-24", + "x-ms-code-generation-settings": { + "Header": "Header content from swagger spec" + } }, "host": "localhost:3000", "schemes": [ diff --git a/Tests/run-tests.ps1 b/Tests/run-tests.ps1 index 76a2cc2..8edcc20 100644 --- a/Tests/run-tests.ps1 +++ b/Tests/run-tests.ps1 @@ -99,7 +99,7 @@ $autoRestInstallPath = Split-Path -Path $autoRestModule.Source $executeTestsCommand += ";`$env:Path+=`";$autoRestInstallPath\tools`"" # AzureRM.Profile requirement -$azureRmProfile = Get-Package -Name AzureRM.Profile -Provider PowerShellGet -ErrorAction Ignore +$azureRmProfile = Get-Module -Name AzureRM.Profile -ListAvailable | Select-Object -First 1 -ErrorAction Ignore if (-not $azureRmProfile) { if (-not (Get-PackageSource -Name PSGallery -ErrorAction Ignore)) { Register-PackageSource -Name PSGallery -ProviderName PowerShellGet @@ -125,6 +125,7 @@ if ($EnableTracing) { } $srcPath = Join-Path -Path $PSScriptRoot -ChildPath .. | Join-Path -ChildPath PSSwagger +$srcPath += [System.IO.Path]::DirectorySeparatorChar $executeTestsCommand += ";`$verbosepreference=`"continue`";`$env:PSModulePath=`"$srcPath;`$env:PSModulePath`";Invoke-Pester -ExcludeTag KnownIssue -OutputFormat NUnitXml -OutputFile ScenarioTestResults.xml -Verbose" # Set up Pester params diff --git a/docs/commands/New-PSSwaggerModule.md b/docs/commands/New-PSSwaggerModule.md index 304fe67..cf6deb6 100644 --- a/docs/commands/New-PSSwaggerModule.md +++ b/docs/commands/New-PSSwaggerModule.md @@ -14,15 +14,17 @@ PowerShell command to generate the PowerShell commands for a given RESTful Web S ### SwaggerPath ``` New-PSSwaggerModule -SpecificationPath -Path -Name [-Version ] - [-DefaultCommandPrefix ] [-UseAzureCsharpGenerator] [-NoAssembly] [-PowerShellCorePath ] - [-IncludeCoreFxAssembly] [-InstallToolsForAllUsers] [-TestBuild] [-SymbolPath ] [-ConfirmBootstrap] + [-DefaultCommandPrefix ] [-Header ] [-UseAzureCsharpGenerator] [-NoAssembly] + [-PowerShellCorePath ] [-IncludeCoreFxAssembly] [-InstallToolsForAllUsers] [-TestBuild] + [-SymbolPath ] [-ConfirmBootstrap] ``` ### SwaggerURI ``` New-PSSwaggerModule -SpecificationUri -Path -Name [-Version ] - [-DefaultCommandPrefix ] [-UseAzureCsharpGenerator] [-NoAssembly] [-PowerShellCorePath ] - [-IncludeCoreFxAssembly] [-InstallToolsForAllUsers] [-TestBuild] [-SymbolPath ] [-ConfirmBootstrap] + [-DefaultCommandPrefix ] [-Header ] [-UseAzureCsharpGenerator] [-NoAssembly] + [-PowerShellCorePath ] [-IncludeCoreFxAssembly] [-InstallToolsForAllUsers] [-TestBuild] + [-SymbolPath ] [-ConfirmBootstrap] ``` ## DESCRIPTION @@ -137,6 +139,23 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Header +Text to include as a header comment in the PSSwagger generated files. +It also can be a path to a .txt file with the content to be added as header in the PSSwagger generated files. +Specify 'NONE' to suppress the default header. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -UseAzureCsharpGenerator Switch to specify whether AzureCsharp code generator is required. By default, this command uses CSharp code generator. From 7d6c8b994cdf7508f128b86f5ba56dab9e01f9e9 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Mon, 28 Aug 2017 15:12:20 -0700 Subject: [PATCH 09/40] Add support for generating the C# SDK assembly using CSC.exe on Windows PS. (#312) Latest version of AutoRest generates C# 6 code. Add-Type on Windows PS doesn't support compilation of C# 6 code. Made required changes to leverage the CSC.exe for generating the C# SDK assembly on Windows PowerShell. Additional changes - Updated readme and run-tests.ps1 - Removed stale source code files under PSSwagger.Azure.Helpers folder. - Removed `Import-Module '$PSScriptRoot\PSSwaggerUtility'` usage for PowerShellCore. --- .../PSSwagger.Azure.Helpers.psd1 | 39 --- .../PSSwagger.Azure.Helpers.psm1 | 141 -------- .../Test-CoreRequirements.ps1 | 1 - .../Test-FullRequirements.ps1 | 1 - PSSwagger/PSSwagger.Resources.psd1 | 1 + PSSwagger/PSSwagger.psm1 | 12 +- .../PSSwaggerUtility/PSSwaggerUtility.psm1 | 306 ++++++++++++------ README.md | 34 +- Tests/run-tests.ps1 | 5 + 9 files changed, 237 insertions(+), 303 deletions(-) delete mode 100644 PSSwagger/PSSwagger.Azure.Helpers/PSSwagger.Azure.Helpers.psd1 delete mode 100644 PSSwagger/PSSwagger.Azure.Helpers/PSSwagger.Azure.Helpers.psm1 delete mode 100644 PSSwagger/PSSwagger.Azure.Helpers/Test-CoreRequirements.ps1 delete mode 100644 PSSwagger/PSSwagger.Azure.Helpers/Test-FullRequirements.ps1 diff --git a/PSSwagger/PSSwagger.Azure.Helpers/PSSwagger.Azure.Helpers.psd1 b/PSSwagger/PSSwagger.Azure.Helpers/PSSwagger.Azure.Helpers.psd1 deleted file mode 100644 index 0b469bb..0000000 --- a/PSSwagger/PSSwagger.Azure.Helpers/PSSwagger.Azure.Helpers.psd1 +++ /dev/null @@ -1,39 +0,0 @@ -@{ -RootModule = 'PSSwagger.Azure.Helpers.psm1' -ModuleVersion = '0.1.0' -GUID = '2a66628c-b4b8-4fb7-9188-c1dd4164cfbf' -Author = 'Microsoft Corporation' -CompanyName = 'Microsoft Corporation' -Copyright = '(c) Microsoft Corporation. All rights reserved.' -Description = 'PowerShell module with Azure common helper functions' -RequiredModules = @('PSSwagger.Common.Helpers') -FunctionsToExport = @('Get-AzDelegatingHandler', - 'Get-AzServiceCredential', - 'Get-AzSubscriptionId', - 'Get-AzResourceManagerUrl', - 'Add-AzSRmEnvironment', - 'Remove-AzSRmEnvironment', - 'Initialize-PSSwaggerDependencies') -CmdletsToExport = '' -VariablesToExport = '' -AliasesToExport = '' - -PrivateData = @{ - PSData = @{ - Tags = @('Azure', - 'Swagger', - 'PSEdition_Desktop', - 'PSEdition_Core', - 'Linux', - 'Mac') - ProjectUri = 'https://github.com/PowerShell/PSSwagger' - LicenseUri = 'https://github.com/PowerShell/PSSwagger/blob/master/LICENSE' - ReleaseNotes = @' -- Initial development release -'@ - } -} - - -} - diff --git a/PSSwagger/PSSwagger.Azure.Helpers/PSSwagger.Azure.Helpers.psm1 b/PSSwagger/PSSwagger.Azure.Helpers/PSSwagger.Azure.Helpers.psm1 deleted file mode 100644 index 4bd366d..0000000 --- a/PSSwagger/PSSwagger.Azure.Helpers/PSSwagger.Azure.Helpers.psm1 +++ /dev/null @@ -1,141 +0,0 @@ -######################################################################################### -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# Licensed under the MIT license. -# -# PSSwagger.Azure.Helpers Module -# -######################################################################################### -Microsoft.PowerShell.Core\Set-StrictMode -Version Latest - -if ((Get-Variable -Name PSEdition -ErrorAction Ignore) -and ('Core' -eq $PSEdition)) { - . (Join-Path -Path "$PSScriptRoot" -ChildPath "Test-CoreRequirements.ps1") - $moduleName = 'AzureRM.Profile.NetCore.Preview' -} else { - . (Join-Path -Path "$PSScriptRoot" -ChildPath "Test-FullRequirements.ps1") - $moduleName = 'AzureRM.Profile' -} - -function Get-AzServiceCredential -{ - [CmdletBinding()] - param() - - $AzureContext = & "$moduleName\Get-AzureRmContext" -ErrorAction Stop - $authenticationFactory = New-Object -TypeName Microsoft.Azure.Commands.Common.Authentication.Factories.AuthenticationFactory - if ((Get-Variable -Name PSEdition -ErrorAction Ignore) -and ('Core' -eq $PSEdition)) { - [Action[string]]$stringAction = {param($s)} - $serviceCredentials = $authenticationFactory.GetServiceClientCredentials($AzureContext, $stringAction) - } else { - $serviceCredentials = $authenticationFactory.GetServiceClientCredentials($AzureContext) - } - - $serviceCredentials -} - -function Get-AzDelegatingHandler -{ - [CmdletBinding()] - param() - - New-Object -TypeName System.Net.Http.DelegatingHandler[] 0 -} - -function Get-AzSubscriptionId -{ - [CmdletBinding()] - param() - - $AzureContext = & "$moduleName\Get-AzureRmContext" -ErrorAction Stop - if(Get-Member -InputObject $AzureContext.Subscription -Name SubscriptionId) - { - return $AzureContext.Subscription.SubscriptionId - } - else - { - return $AzureContext.Subscription.Id - } -} - -function Get-AzResourceManagerUrl -{ - [CmdletBinding()] - param() - - $AzureContext = & "$moduleName\Get-AzureRmContext" -ErrorAction Stop - $AzureContext.Environment.ResourceManagerUrl -} - -function Add-AzSRmEnvironment -{ - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)] - [string] - $Name, - - [Parameter(Mandatory = $true)] - [string] - $UserName, - - [Parameter(Mandatory = $true)] - [string] - $AzureStackDomain - ) - - $AadTenantId = $UserName.split('@')[-1] - $Graphendpoint = "https://graph.$azureStackDomain" - if ($AadTenantId -like '*.onmicrosoft.com') - { - $Graphendpoint = "https://graph.windows.net/" - } - - $Endpoints = Microsoft.PowerShell.Utility\Invoke-RestMethod -Method Get -Uri "https://api.$azureStackDomain/metadata/endpoints?api-version=1.0" - $ActiveDirectoryServiceEndpointResourceId = $Endpoints.authentication.audiences[0] - - # Add the Microsoft Azure Stack environment - & "$moduleName\Add-AzureRmEnvironment" -Name $Name ` - -ActiveDirectoryEndpoint "https://login.windows.net/$AadTenantId/" ` - -ActiveDirectoryServiceEndpointResourceId $ActiveDirectoryServiceEndpointResourceId ` - -ResourceManagerEndpoint "https://api.$azureStackDomain/" ` - -GalleryEndpoint "https://gallery.$azureStackDomain/" ` - -GraphEndpoint $Graphendpoint -} - -function Remove-AzSRmEnvironment -{ - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)] - [string] - $Name - ) - - & "$moduleName\Remove-AzureRmEnvironment" @PSBoundParameters -} - -<# -.DESCRIPTION - Manually initialize PSSwagger's external dependencies. Use this function with -AcceptBootstrap for silent execution scenarios. - -.PARAMETER AllUsers - Install dependencies in PSSwagger's global package cache. - -.PARAMETER AcceptBootstrap - Automatically consent to downloading missing packages. If not specified, an interactive prompt will be appear. -#> -function Initialize-PSSwaggerDependencies { - [CmdletBinding()] - param( - [Parameter(Mandatory=$false)] - [switch] - $AllUsers, - - [Parameter(Mandatory=$false)] - [switch] - $AcceptBootstrap - ) - - PSSwagger.Common.Helpers\Initialize-PSSwaggerDependencies -Azure @PSBoundParameters -} \ No newline at end of file diff --git a/PSSwagger/PSSwagger.Azure.Helpers/Test-CoreRequirements.ps1 b/PSSwagger/PSSwagger.Azure.Helpers/Test-CoreRequirements.ps1 deleted file mode 100644 index 4d03574..0000000 --- a/PSSwagger/PSSwagger.Azure.Helpers/Test-CoreRequirements.ps1 +++ /dev/null @@ -1 +0,0 @@ -#Requires -Modules AzureRM.Profile.NetCore.Preview \ No newline at end of file diff --git a/PSSwagger/PSSwagger.Azure.Helpers/Test-FullRequirements.ps1 b/PSSwagger/PSSwagger.Azure.Helpers/Test-FullRequirements.ps1 deleted file mode 100644 index e9c3d67..0000000 --- a/PSSwagger/PSSwagger.Azure.Helpers/Test-FullRequirements.ps1 +++ /dev/null @@ -1 +0,0 @@ -#Requires -Modules AzureRM.Profile \ No newline at end of file diff --git a/PSSwagger/PSSwagger.Resources.psd1 b/PSSwagger/PSSwagger.Resources.psd1 index 334c69f..5b3b8f5 100644 --- a/PSSwagger/PSSwagger.Resources.psd1 +++ b/PSSwagger/PSSwagger.Resources.psd1 @@ -25,6 +25,7 @@ ConvertFrom-StringData @' SamePropertyName=Same property name '{0}' is defined in a definition '{1}' with AllOf inheritance. DataTypeNotImplemented=Please get an implementation of '{0}' for '{1}' AutoRestNotInPath=Unable to find AutoRest in PATH environment. Ensure the PATH is updated. + CscExeNotInPath=Unable to find CSC.exe in PATH environment. Ensure the PATH is updated with CSC.exe location. AutoRestError=AutoRest resulted in an error SwaggerParamsMissing=No parameters in the Swagger SwaggerDefinitionsMissing=No definitions in the Swagger diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index bcaf2c6..de057bd 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -587,7 +587,12 @@ function ConvertTo-CsharpCode $autoRestExePath = "AutoRest" if (-not (get-command -name $autoRestExePath)) { - throw $LocalizedData.AutoRestNotInPath + throw $LocalizedData.AutoRestNotInPath + } + + if (-not (Get-OperatingSystemInfo).IsCore -and + (-not (Get-Command -Name 'Csc.Exe' -ErrorAction Ignore))) { + throw $LocalizedData.CscExeNotInPath } $outputDirectory = $SwaggerMetaDict['outputDirectory'] @@ -718,7 +723,7 @@ function ConvertTo-CsharpCode -CliXmlTmpPath $cliXmlTmpPath" $success = & "powershell" -command "& {$command}" - + $codeReflectionResult = Import-CliXml -Path $cliXmlTmpPath if ($codeReflectionResult.ContainsKey('VerboseMessages') -and $codeReflectionResult.VerboseMessages -and ($codeReflectionResult.VerboseMessages.Count -gt 0)) { $verboseMessages = $codeReflectionResult.VerboseMessages -Join [Environment]::NewLine @@ -773,8 +778,7 @@ function ConvertTo-CsharpCode } $dependencies = Get-PSSwaggerExternalDependencies -Azure:$codeCreatedByAzureGenerator -Framework 'netstandard1' $microsoftRestClientRuntimeAzureRequiredVersion = if ($dependencies.ContainsKey('Microsoft.Rest.ClientRuntime.Azure')) { $dependencies['Microsoft.Rest.ClientRuntime.Azure'].RequiredVersion } else { '' } - $command = "Import-Module '$PSScriptRoot\PSSwaggerUtility'; - Add-PSSwaggerClientType -OutputAssemblyName '$outAssembly' `` + $command = "PSSwaggerUtility\Add-PSSwaggerClientType -OutputAssemblyName '$outAssembly' `` -ClrPath '$clrPath' `` -CSharpFiles $allCSharpFilesArrayString `` -MicrosoftRestClientRuntimeAzureRequiredVersion '$microsoftRestClientRuntimeAzureRequiredVersion' `` diff --git a/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 b/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 index f2634e2..31b55c4 100644 --- a/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 +++ b/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 @@ -467,7 +467,7 @@ function Add-PSSwaggerClientType { $preprocessorDirectives = @() if ((Get-OperatingSystemInfo).IsCore) { # Base framework references - $preprocessorDirectives = @('#define DNXCORE50','#define PORTABLE') + $preprocessorDirectives = @('#define DNXCORE50','#define PORTABLE') $systemRefs = @('System.dll', 'System.Core.dll', 'System.Net.Http.dll', @@ -496,44 +496,57 @@ function Add-PSSwaggerClientType { # Get dependencies for AutoRest SDK $externalReferences = Get-PSSwaggerExternalDependencies -Framework $externalReferencesFramework -Azure:$CodeCreatedByAzureGenerator -RequiredVersionMap $requiredVersionMap - # Ensure output directory exists - if (-not (Test-Path -Path $clrPath -PathType Container)) { - $null = New-Item -Path $clrPath -ItemType Directory -Force + $AddClientTypeHelperParams = @{ + Path = $CSharpFiles | ForEach-Object { $_.FullName } + AllUsers = $AllUsers + BootstrapConsent = $BootstrapConsent + PackageDependencies = $externalReferences + PreprocessorDirectives = $PreprocessorDirectives } + if ($OutputAssemblyName) { + $AddClientTypeHelperParams['OutputDirectory'] = $clrPath + $AddClientTypeHelperParams['OutputAssemblyName'] = $OutputAssemblyName + $AddClientTypeHelperParams['TestBuild'] = $TestBuild + $AddClientTypeHelperParams['SymbolPath'] = $SymbolPath + } + $HelperResult = Add-PSSwaggerClientTypeHelper @AddClientTypeHelperParams - $addTypeParamsResult = Get-PSSwaggerAddTypeParameters -Path ($CSharpFiles | ForEach-Object { $_.FullName }) -OutputDirectory $clrPath -OutputAssemblyName $OutputAssemblyName ` - -AllUsers:$AllUsers -BootstrapConsent:$BootstrapConsent -TestBuild:$TestBuild -SymbolPath $SymbolPath -PackageDependencies $externalReferences ` - -FileReferences $systemRefs -PreprocessorDirectives $preprocessorDirectives - - if ($addTypeParamsResult['ResolvedPackageReferences']) { - # Copy package references when precompiling - foreach ($extraRef in $addTypeParamsResult['ResolvedPackageReferences']) { - $null = Copy-Item -Path $extraRef -Destination (Join-Path -Path $ClrPath -ChildPath (Split-Path -Path $extraRef -Leaf)) -Force - Add-Type -Path $extraRef -ErrorAction Ignore - } + $CompilerHelperParams = @{ + ReferencedAssemblies = $systemRefs + $HelperResult['ResolvedPackageReferences'] + SourceCodeFilePath = $HelperResult['SourceCodeFilePath'] + OutputAssembly = $HelperResult['OutputAssembly'] + TestBuild = $TestBuild } - if ($addTypeParamsResult['SourceFileName']) { - # A source code file is expected to exist - # Emit the created source code - $addTypeParamsResult['SourceCode'] | Out-File -FilePath $addTypeParamsResult['SourceFileName'] + if ((Get-OperatingSystemInfo).IsCore) { + $addTypeParams = Get-AddTypeParameters @CompilerHelperParams + Add-Type @addTypeParams + } + else { + $CscArgumentList = Get-CscParameters @CompilerHelperParams + $output = & 'Csc.exe' $CscArgumentList + if ($output) { + Write-Error -ErrorId 'SOURCE_CODE_ERROR' -Message ($output | Out-String) + return $false + } } - # Execute Add-Type - $addTypeParams = $addTypeParamsResult['Params'] - Add-Type @addTypeParams - # Copy the PDB to the symbol path if specified - if ($addTypeParamsResult['OutputAssemblyPath']) { - # Verify result of Add-Type - $outputAssemblyItem = Get-Item -Path $addTypeParamsResult['OutputAssemblyPath'] - if ((-not (Test-Path -Path $addTypeParamsResult['OutputAssemblyPath'])) -or ($outputAssemblyItem.Length -eq 0kb)) { + if ($HelperResult['OutputAssembly']) { + # Verify result of assembly compilation + $outputAssemblyItem = Get-Item -Path $HelperResult['OutputAssembly'] + if ((-not (Test-Path -Path $HelperResult['OutputAssembly'])) -or ($outputAssemblyItem.Length -eq 0kb)) { return $false } - $OutputPdbName = "$($outputAssemblyItem.BaseName).pdb" - if ($SymbolPath -and (Test-Path -Path (Join-Path -Path $ClrPath -ChildPath $OutputPdbName))) { - $null = Copy-Item -Path (Join-Path -Path $ClrPath -ChildPath $OutputPdbName) -Destination (Join-Path -Path $SymbolPath -ChildPath $OutputPdbName) + if(-not $OutputAssemblyName) { + Add-Type -Path $outputAssemblyItem + } + else { + $OutputPdbName = "$($outputAssemblyItem.BaseName).pdb" + if ($SymbolPath -and (Test-Path -Path (Join-Path -Path $ClrPath -ChildPath $OutputPdbName))) { + $null = Copy-Item -Path (Join-Path -Path $ClrPath -ChildPath $OutputPdbName) -Destination (Join-Path -Path $SymbolPath -ChildPath $OutputPdbName) + } } } @@ -542,7 +555,7 @@ function Add-PSSwaggerClientType { <# .DESCRIPTION - Compiles AutoRest generated C# code using the framework of the current PowerShell process. + Helper function to validate and install the required package dependencies. Also prepares the source code for compilation. .PARAMETER Path All *.Code.ps1 C# files to compile. @@ -568,90 +581,101 @@ function Add-PSSwaggerClientType { .PARAMETER PackageDependencies Map of package dependencies to add as referenced assemblies but don't exist on disk. -.PARAMETER FileReferences - Compilation references that exist on disk. - .PARAMETER PreprocessorDirectives Preprocessor directives to add to the top of the combined source code file. -.NOTES - This function will be deprecated when we move away from Add-Type compilation. #> -function Get-PSSwaggerAddTypeParameters { +function Add-PSSwaggerClientTypeHelper { [CmdletBinding()] param( - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string[]] $Path, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [AllowEmptyString()] [string] $OutputDirectory, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [AllowEmptyString()] [string] $OutputAssemblyName, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [switch] $AllUsers, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [switch] $BootstrapConsent, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [switch] $TestBuild, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string] $SymbolPath, - - [Parameter(Mandatory=$false)] - [ValidateSet("ConsoleApplication","Library")] - [string] - $OutputType = 'Library', - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [hashtable] $PackageDependencies, - - [Parameter(Mandatory=$false)] - [string[]] - $FileReferences, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [string[]] $PreprocessorDirectives ) $resultObj = @{ - # The add type parameters to use - Params = $null # Full path to resolved package reference assemblies ResolvedPackageReferences = @() + # The expected output assembly full path - OutputAssemblyPath = $null + OutputAssembly = $null + # The actual source to be emitted - SourceCode = $null + SourceCode = $null + # The file name the returned params expect to exist, if required - SourceFileName = $null + SourceCodeFilePath = $null + } + + if (-not $OutputDirectory -or -not $SymbolPath) { + $TempOutputPath = Join-Path -Path (Get-XDGDirectory -DirectoryType Cache) -ChildPath ([Guid]::NewGuid().Guid) + $null = New-Item -Path $TempOutputPath -ItemType Directory -Force + } + + if (-not $SymbolPath) { + $SymbolPath = $TempOutputPath + } + + if (-not $OutputDirectory) { + $OutputDirectory = $TempOutputPath + } + elseif (-not (Test-Path -Path $OutputDirectory -PathType Container)) { + $null = New-Item -Path $OutputDirectory -ItemType Directory -Force + } + + if (-not $OutputAssemblyName) { + $OutputAssemblyName = [Guid]::NewGuid().Guid + '.dll' } # Resolve package dependencies - $extraRefs = @() if ($PackageDependencies) { foreach ($entry in ($PackageDependencies.GetEnumerator() | Sort-Object { $_.Value.LoadOrder })) { $reference = $entry.Value $resolvedRef = Get-PSSwaggerDependency -PackageName $reference.PackageName ` - -RequiredVersion $reference.RequiredVersion ` - -References $reference.References ` - -Framework $reference.Framework ` - -AllUsers:$AllUsers -Install -BootstrapConsent:$BootstrapConsent - $extraRefs += $resolvedRef + -RequiredVersion $reference.RequiredVersion ` + -References $reference.References ` + -Framework $reference.Framework ` + -AllUsers:$AllUsers ` + -Install ` + -BootstrapConsent:$BootstrapConsent $resultObj['ResolvedPackageReferences'] += $resolvedRef + + # Copy package references to OutputDirectory + $null = Copy-Item -Path $resolvedRef -Destination (Join-Path -Path $OutputDirectory -ChildPath (Split-Path -Path $resolvedRef -Leaf)) -Force + Add-Type -Path $resolvedRef -ErrorAction Ignore } } @@ -660,38 +684,61 @@ function Get-PSSwaggerAddTypeParameters { $srcContent += $Path | ForEach-Object { "// File $_"; Remove-AuthenticodeSignatureBlock -Path $_ } if ($PreprocessorDirectives) { foreach ($preprocessorDirective in $PreprocessorDirectives) { - $srcContent = ,$preprocessorDirective + $srcContent + $srcContent = , $preprocessorDirective + $srcContent } } $oneSrc = $srcContent -join "`n" $resultObj['SourceCode'] = $oneSrc - if ($SymbolPath) { - if ($OutputAssemblyName) { - $OutputAssemblyBaseName = [System.IO.Path]::GetFileNameWithoutExtension("$OutputAssemblyName") - $resultObj['SourceFileName'] = Join-Path -Path $SymbolPath -ChildPath "Generated.$OutputAssemblyBaseName.cs" - } else { - $resultObj['SourceFileName'] = Join-Path -Path $SymbolPath -ChildPath "Generated.cs" - } - $addTypeParams = @{ - Path = $resultObj['SourceFileName'] - WarningAction = 'Ignore' - } - } else { - $addTypeParams = @{ - TypeDefinition = $oneSrc - Language = "CSharp" - WarningAction = 'Ignore' - } + $OutputAssemblyBaseName = [System.IO.Path]::GetFileNameWithoutExtension("$OutputAssemblyName") + $SourceCodeFilePath = Join-Path -Path $SymbolPath -ChildPath "Generated.$OutputAssemblyBaseName.cs" + $resultObj['SourceCodeFilePath'] = $SourceCodeFilePath + Out-File -InputObject $oneSrc -FilePath $SourceCodeFilePath + + $resultObj['OutputAssembly'] = Join-Path -Path $OutputDirectory -ChildPath $OutputAssemblyName + + return $resultObj +} + +function Get-AddTypeParameters { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $SourceCodeFilePath, + + [Parameter(Mandatory = $false)] + [string[]] + $ReferencedAssemblies, + + [Parameter(Mandatory = $false)] + [ValidateSet("ConsoleApplication", "Library")] + [string] + $OutputType = 'Library', + + [Parameter(Mandatory = $false)] + [switch] + $TestBuild, + + [Parameter(Mandatory = $false)] + [string] + $OutputAssembly + ) + + $AddTypeParams = @{ + WarningAction = 'Ignore' } if (-not (Get-OperatingSystemInfo).IsCore) { + $AddTypeParams['Path'] = $SourceCodeFilePath $compilerParameters = New-Object -TypeName System.CodeDom.Compiler.CompilerParameters + $compilerParameters.WarningLevel = 3 $compilerParameters.CompilerOptions = '/debug:full' if ($TestBuild) { $compilerParameters.IncludeDebugInformation = $true - } else { + } + else { $compilerParameters.CompilerOptions += ' /optimize+' } @@ -699,35 +746,82 @@ function Get-PSSwaggerAddTypeParameters { $compilerParameters.GenerateExecutable = $true } - $compilerParameters.WarningLevel = 3 - foreach ($ref in ($FileReferences + $extraRefs)) { - $null = $compilerParameters.ReferencedAssemblies.Add($ref) - } - $addTypeParams['CompilerParameters'] = $compilerParameters - } else { - $addTypeParams['ReferencedAssemblies'] = ($FileReferences + $extraRefs) - if ($OutputType -eq 'ConsoleApplication') { - $addTypeParams['ReferencedAssemblies'] = $OutputType + $ReferencedAssemblies | ForEach-Object { + $null = $compilerParameters.ReferencedAssemblies.Add($_) } + $AddTypeParams['CompilerParameters'] = $compilerParameters + } + else { + $AddTypeParams['TypeDefinition'] = Get-Content -Path $SourceCodeFilePath -Raw + $AddTypeParams['ReferencedAssemblies'] = $ReferencedAssemblies + $AddTypeParams['OutputType'] = $OutputType + $AddTypeParams['Language'] = 'CSharp' } - $OutputPdbName = '' - if ($OutputAssemblyName) { - $OutputAssembly = Join-Path -Path $OutputDirectory -ChildPath $OutputAssemblyName - $resultObj['OutputAssemblyPath'] = $OutputAssembly - if ($addTypeParams.ContainsKey('CompilerParameters')) { - $addTypeParams['CompilerParameters'].OutputAssembly = $OutputAssembly - } else { - $addTypeParams['OutputAssembly'] = $OutputAssembly + if ($OutputAssembly) { + if ($AddTypeParams.ContainsKey('CompilerParameters')) { + $AddTypeParams['CompilerParameters'].OutputAssembly = $OutputAssembly } - } else { - if ($addTypeParams.ContainsKey('CompilerParameters')) { - $addTypeParams['CompilerParameters'].GenerateInMemory = $true + else { + $AddTypeParams['OutputAssembly'] = $OutputAssembly + } + } + else { + if ($AddTypeParams.ContainsKey('CompilerParameters')) { + $AddTypeParams['CompilerParameters'].GenerateInMemory = $true } } - $resultObj['Params'] = $addTypeParams - return $resultObj + return $AddTypeParams +} +function Get-CscParameters { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $SourceCodeFilePath, + + [Parameter(Mandatory = $false)] + [ValidateSet('Exe', 'Library')] + [string] + $TargetType = 'Library', + + [Parameter(Mandatory = $false)] + [string[]] + $ReferencedAssemblies, + + [Parameter(Mandatory = $false)] + [string[]] + $ConditionalCompilationSymbol, + + [Parameter(Mandatory = $false)] + [switch] + $TestBuild, + + [Parameter(Mandatory = $false)] + [string] + $OutputAssembly + ) + + $CscParameter = @( + $SourceCodeFilePath + '/nologo', + '/langversion:latest', + '/checked', + '/warn:3', + '/debug:full', + '/platform:anycpu', + "/target:$TargetType" + ) + + $ReferencedAssemblies | ForEach-Object { $CscParameter += "/reference:$_" } + if (-not $TestBuild) { $CscParameter += '/optimize+' } + if ($OutputAssembly) { $CscParameter += "/out:$OutputAssembly" } + if ($ConditionalCompilationSymbol) { + $ConditionalCompilationSymbol | ForEach-Object { $CscParameter += "/define:$_" } + } + + return $CscParameter } <# diff --git a/README.md b/README.md index 948da70..32dcdac 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,29 @@ NOTE: In the short term, for best performance, the operation IDs in your Open AP git clone https://github.com/PowerShell/PSSwagger.git ``` -2. Ensure you AutoRest version 0.17.3 installed - ```powershell - Install-Package -Name AutoRest -Source https://www.nuget.org/api/v2 -RequiredVersion 0.17.3 -Scope CurrentUser - ``` - -3. Ensure AutoRest.exe is in $env:Path - ```powershell - $package = Get-Package -Name AutoRest -RequiredVersion 0.17.3 - $env:path += ";$(Split-Path $package.Source -Parent)\tools" - Get-Command -Name AutoRest - ``` +2. Ensure AutoRest is installed and available in $env:PATH + - Install AutoRest version 0.17.3 + ```powershell + Install-Package -Name AutoRest -Source https://www.nuget.org/api/v2 -RequiredVersion 0.17.3 -Scope CurrentUser + ``` + - Add AutoRest.exe path to $env:Path + ```powershell + $package = Get-Package -Name AutoRest -RequiredVersion 0.17.3 + $env:path += ";$(Split-Path $package.Source -Parent)\tools" + Get-Command -Name AutoRest + ``` + +3. Ensure CSC.exe is installed and available in $env:PATH + - If CSC.exe is not installed already, install Microsoft.Net.Compilers package + ```powershell + Install-Package -Name Microsoft.Net.Compilers -Source https://www.nuget.org/api/v2 -Scope CurrentUser + ``` + - Add CSC.exe path to $env:Path + ```powershell + $package = Get-Package -Name Microsoft.Net.Compilers + $env:path += ";$(Split-Path $package.Source -Parent)\tools" + Get-Command -Name CSC.exe + ``` 4. If you plan on pre-compiling the generated assembly, ensure you have the module AzureRM.Profile or AzureRM.NetCore.Preview available to PackageManagement if you are on PowerShell or PowerShell Core, respectively. diff --git a/Tests/run-tests.ps1 b/Tests/run-tests.ps1 index 8edcc20..83b396b 100644 --- a/Tests/run-tests.ps1 +++ b/Tests/run-tests.ps1 @@ -98,6 +98,11 @@ $autoRestModule = Test-Package -packageName "AutoRest" -packageSourceName $nuget $autoRestInstallPath = Split-Path -Path $autoRestModule.Source $executeTestsCommand += ";`$env:Path+=`";$autoRestInstallPath\tools`"" +# Set up Microsoft.Net.Compilers +$MicrosoftNetCompilers = Test-Package -PackageName Microsoft.Net.Compilers -PackageSourceName $nugetPackageSource.Name +$MicrosoftNetCompilersInstallPath = Split-Path -Path $MicrosoftNetCompilers.Source +$executeTestsCommand += ";`$env:Path+=`";$MicrosoftNetCompilersInstallPath\tools`"" + # AzureRM.Profile requirement $azureRmProfile = Get-Module -Name AzureRM.Profile -ListAvailable | Select-Object -First 1 -ErrorAction Ignore if (-not $azureRmProfile) { From 95949789eedf1bddf41ac2a1b56818c879ffbbe6 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Tue, 29 Aug 2017 17:11:53 -0700 Subject: [PATCH 10/40] Support latest version of AutoRest in PSSwagger (#313) * Support latest version of AutoRest in PSSwagger - Added required parameter changes to support AutoRest.cmd. - Removed dependency on AutoRest.CSharp.dll. - Added required logic in Get-CSharpModelName function to escape C# reserved words and also to remove special characters as per AutoRest functionality. - Updated run-tests.ps1 to install and use NodeJS and other minor changes. - Updated Readme with updated instructions and other info. * Using NodeJS '7.10.0' as there are some perf issues in AutoRest with NodeJS '8.4.0'. --- .gitignore | 1 + PSSwagger/PSSwagger.psm1 | 60 ++++++++++--- PSSwagger/SwaggerUtils.psm1 | 89 ++++++------------- README.md | 22 ++--- Tests/PSSwagger.Unit.Tests.ps1 | 14 +++ Tests/run-tests.ps1 | 157 +++++++++++++++++++-------------- appveyor.yml | 4 + 7 files changed, 192 insertions(+), 155 deletions(-) diff --git a/.gitignore b/.gitignore index f3885cd..f6d394a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ # Tests related files and folders /Tests/Generated/ /Tests/NodeModules/ +/Tests/package-lock.json /Tests/ScenarioTestResults.xml /Tests/pesterCommand.ps1 /Tests/dotnet-install.* diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index de057bd..6fdc323 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -585,8 +585,8 @@ function ConvertTo-CsharpCode Write-Verbose -Message $LocalizedData.GenerateCodeUsingAutoRest $info = $SwaggerDict['Info'] - $autoRestExePath = "AutoRest" - if (-not (get-command -name $autoRestExePath)) { + $AutoRestCommand = Get-Command -Name AutoRest -ErrorAction Ignore | Select-Object -First 1 -ErrorAction Ignore + if (-not $AutoRestCommand) { throw $LocalizedData.AutoRestNotInPath } @@ -631,31 +631,67 @@ function ConvertTo-CsharpCode } $tempCodeGenSettingsPath = '' + # Latest AutoRest inconsistently appends 'Client' to the specified infoName to generated the client name. + # We need to override the client name to ensure that generated PowerShell cmdlets work fine. + $ClientName = $info['infoName'] try { if ($info.ContainsKey('CodeGenFileRequired') -and $info.CodeGenFileRequired) { # Some settings need to be overwritten # Write the following parameters: AddCredentials, CodeGenerator, Modeler $tempCodeGenSettings = @{ AddCredentials = $true - CodeGenerator = $codeGenerator - Modeler = $swaggerMetaDict['AutoRestModeler'] + CodeGenerator = $codeGenerator + Modeler = $swaggerMetaDict['AutoRestModeler'] + ClientName = $ClientName } $tempCodeGenSettingsPath = "$(Join-Path -Path (Get-XDGDirectory -DirectoryType Cache) -ChildPath (Get-Random)).json" $tempCodeGenSettings | ConvertTo-Json | Out-File -FilePath $tempCodeGenSettingsPath $autoRestParams = @('-Input', $swaggerMetaDict['SwaggerSpecPath'], '-OutputDirectory', $generatedCSharpPath, '-Namespace', $NameSpace, '-CodeGenSettings', $tempCodeGenSettingsPath) - } else { + } + elseif ( ($AutoRestCommand.Name -eq 'AutoRest.exe') -or + ($swaggerMetaDict['AutoRestModeler'] -eq 'CompositeSwagger')) { # None of the PSSwagger-required params are being overwritten, just call the CLI directly to avoid the extra disk op - $autoRestParams = @('-Input', $swaggerMetaDict['SwaggerSpecPath'], '-OutputDirectory', $generatedCSharpPath, '-Namespace', $NameSpace, '-AddCredentials', $true, '-CodeGenerator', $codeGenerator, '-Modeler', $swaggerMetaDict['AutoRestModeler']) + $autoRestParams = @('-Input', $swaggerMetaDict['SwaggerSpecPath'], + '-OutputDirectory', $generatedCSharpPath, + '-Namespace', $NameSpace, + '-AddCredentials', $true, + '-CodeGenerator', $codeGenerator, + '-ClientName', $ClientName + '-Modeler', $swaggerMetaDict['AutoRestModeler'] + ) + } + else { + # See https://aka.ms/autorest/cli for AutoRest.cmd options. + $autoRestParams = @( + "--input-file=$($swaggerMetaDict['SwaggerSpecPath'])", + "--output-folder=$generatedCSharpPath", + "--namespace=$NameSpace", + '--add-credentials', + '--clear-output-folder=true', + "--override-client-name=$ClientName" + '--verbose', + '--csharp' + ) + + if ($codeGenerator -eq 'Azure.CSharp') { + $autoRestParams += '--azure-arm' + } + + if (('continue' -eq $DebugPreference) -or + ('inquire' -eq $DebugPreference)) { + $autoRestParams += '--debug' + } } Write-Verbose -Message $LocalizedData.InvokingAutoRestWithParams - for ($i = 0; $i -lt $autoRestParams.Length; $i += 2) { - Write-Verbose -Message ($LocalizedData.AutoRestParam -f ($autoRestParams[$i], $autoRestParams[$i+1])) + Write-Verbose -Message $($autoRestParams | Out-String) + + $autorestMessages = & AutoRest $autoRestParams + if ($autorestMessages) { + Write-Verbose -Message $($autorestMessages | Out-String) } - - $null = & $autoRestExePath $autoRestParams if ($LastExitCode) { throw $LocalizedData.AutoRestError @@ -691,9 +727,7 @@ function ConvertTo-CsharpCode # As of 3/2/2017, there's a version mismatch between the latest Microsoft.Rest.ClientRuntime.Azure package and the latest AzureRM.Profile package # So we have to hardcode Microsoft.Rest.ClientRuntime.Azure to at most version 3.3.4 - $modulePostfix = $info['infoName'] - $NameSpace = $info.namespace - $fullModuleName = $Namespace + '.' + $modulePostfix + $fullModuleName = $Namespace + '.' + $ClientName $cliXmlTmpPath = Get-TemporaryCliXmlFilePath -FullModuleName $fullModuleName try { Export-CliXml -InputObject $PathFunctionDetails -Path $cliXmlTmpPath diff --git a/PSSwagger/SwaggerUtils.psm1 b/PSSwagger/SwaggerUtils.psm1 index 81f5500..e662161 100644 --- a/PSSwagger/SwaggerUtils.psm1 +++ b/PSSwagger/SwaggerUtils.psm1 @@ -42,6 +42,26 @@ if(-not (Get-OperatingSystemInfo).IsCore) $script:IgnoredAutoRestParameters = @(@('Modeler', 'm'), @('AddCredentials'), @('CodeGenerator', 'g')) $script:PSSwaggerDefaultNamespace = "Microsoft.PowerShell" +$script:CSharpReservedWords = @( + 'abstract', 'as', 'async', 'await', 'base', + 'bool', 'break', 'byte', 'case', 'catch', + 'char', 'checked', 'class', 'const', 'continue', + 'decimal', 'default', 'delegate', 'do', 'double', + 'dynamic', 'else', 'enum', 'event', 'explicit', + 'extern', 'false', 'finally', 'fixed', 'float', + 'for', 'foreach', 'from', 'global', 'goto', + 'if', 'implicit', 'in', 'int', 'interface', + 'internal', 'is', 'lock', 'long', 'namespace', + 'new', 'null', 'object', 'operator', 'out', + 'override', 'params', 'private', 'protected', 'public', + 'readonly', 'ref', 'return', 'sbyte', 'sealed', + 'short', 'sizeof', 'stackalloc', 'static', 'string', + 'struct', 'switch', 'this', 'throw', 'true', + 'try', 'typeof', 'uint', 'ulong', 'unchecked', + 'unsafe', 'ushort', 'using', 'virtual', 'void', + 'volatile', 'while', 'yield', 'var' +) + function ConvertTo-SwaggerDictionary { [CmdletBinding()] @@ -1641,42 +1661,6 @@ function Get-Response return $responseBody, $outputType } - -<# - - Get-ToolsPath - -#> -function Get-ToolsPath { - [string]$AutoRestToolsPath = $null - - # Note: DLLs are automatically downloaded and extracted into the folder - # "$env:USERPROFILE/.autorest/plugins/autorest/$VERSION" if they do not - # exist for newer versions of autorest. - [string]$basePath = Join-Path -Path $env:USERPROFILE -ChildPath ".autorest" | Join-Path -ChildPath "plugins" | Join-Path -ChildPath "autorest" - - - if(Test-Path $basePath) { # Try to load newer version of autorest - $versions = @(Get-ChildItem -Directory $basePath | ForEach-Object {[System.Version]$_.Name} | Sort-Object -Descending) - - if($versions.Length -ne 0) { - [string]$version = $versions[0] # Get newest - $AutoRestToolsPath = Join-Path -Path $basePath -ChildPath $version - } - } else { # Fallback to old version of autorest - $AutoRestToolsPath = Get-Command -Name 'AutoRest.exe' | - Select-Object -First 1 -ErrorAction Ignore | - ForEach-Object {Split-Path -LiteralPath $_.Source} - } - - if (-not ($AutoRestToolsPath)) - { - throw $LocalizedData.AutoRestNotInPath - } - - return $AutoRestToolsPath -} - function Get-CSharpModelName { param( @@ -1685,34 +1669,17 @@ function Get-CSharpModelName $Name ) - if (-not $script:CSharpCodeNamerLoadAttempted) { - $script:CSharpCodeNamerLoadAttempted = $true - $script:CSharpCodeNamer = New-ObjectFromDependency -TypeNames @('AutoRest.CSharp.CodeNamerCs', 'AutoRest.CSharp.CSharpCodeNamer') -AssemblyName 'AutoRest.CSharp.dll' -AssemblyResolver { - param( - [string] - $AssemblyName - ) - - [string]$AutoRestToolsPath = Get-ToolsPath - - $AssemblyFilePath = Join-Path -Path $AutoRestToolsPath -ChildPath $AssemblyName - if(-not $AssemblyFilePath -or -not (Test-Path -LiteralPath $AssemblyFilePath -PathType Leaf)) - { - throw ($LocalizedData.PathNotFound -f $AssemblyFilePath) - } + # Below logic is as per AutoRest + $Name = $Name -replace '[[]]','Sequence' + # Remove special characters + $Name = $Name -replace '[^a-zA-Z0-9_-]','' - Add-Type -LiteralPath $AssemblyFilePath - } + # AutoRest appends 'Model' to the definition name when it is a C# reserved word. + if($script:CSharpReservedWords -contains $Name) { + $Name += 'Model' } - if($script:CSharpCodeNamer) - { - return $script:CSharpCodeNamer.GetTypeName($Name) - } - else - { - return $Name.Replace('[','').Replace(']','') - } + return Get-PascalCasedString -Name $Name } <# Create an object from an external dependency with possible type name changes. Optionally resolve the external dependency using a delegate. diff --git a/README.md b/README.md index 32dcdac..85d8412 100644 --- a/README.md +++ b/README.md @@ -33,17 +33,18 @@ A PowerShell module with commands to generate the PowerShell commands for a give ## Dependencies | Dependency | Version | Description | | ----------------| ----------- | -------------------------- | -| AutoRest | 0.17.3 | Tool to generate C# SDK from Swagger spec | +| AutoRest | 0.17.3 or newer | Tool to generate C# SDK from Swagger spec | +| CSC.exe (Microsoft.Net.Compilers) | 2.3.1 or newer | C# Compiler to generate C# SDK assembly on Windows PowerShell | | Newtonsoft.Json | Full CLR: 6.0.8, Core CLR: 9.0.1 | NuGet package containing Newtonsoft.Json assembly, required for all modules | -| Microsoft.Rest.ClientRuntime | 2.3.4 | NuGet package containing Microsoft.Rest.ClientRuntime assembly, required for all modules | -| Microsoft.Rest.ClientRuntime.Azure | 3.3.4 | NuGet package containing Microsoft.Rest.ClientRuntime.Azure assembly, required for Microsoft Azure modules | -| AzureRM.Profile | * | Module containing authentication helpers, required for Microsoft Azure modules on PowerShell | +| Microsoft.Rest.ClientRuntime | 2.3.4 or newer | NuGet package containing Microsoft.Rest.ClientRuntime assembly, required for all modules | +| Microsoft.Rest.ClientRuntime.Azure | 3.3.4 or newer | NuGet package containing Microsoft.Rest.ClientRuntime.Azure assembly, required for Microsoft Azure modules | +| AzureRM.Profile | 2.0.0 or newer | Module containing authentication helpers, required for Microsoft Azure modules on PowerShell | | AzureRM.Profile.NetCore.Preview | * | Module containing authentication helpers, required for Microsoft Azure modules on PowerShell Core | ## Usage NOTE: In the short term, for best performance, the operation IDs in your Open API specifications should be of the form "_". For example, the operation ID "Resource_GetByName" gets a resource named Resource by name. 1. Get PSSwagger! - * Install from PowerShellGallery.com: + * Install from PowerShellGallery.com ```powershell Install-Module -Name PSSwagger ``` @@ -53,17 +54,12 @@ NOTE: In the short term, for best performance, the operation IDs in your Open AP ``` 2. Ensure AutoRest is installed and available in $env:PATH - - Install AutoRest version 0.17.3 + - Follow the instructions provided at [AutoRest github repository](https://github.com/Azure/Autorest#installing-autorest). + - Ensure AutoRest.cmd path is in $env:Path ```powershell - Install-Package -Name AutoRest -Source https://www.nuget.org/api/v2 -RequiredVersion 0.17.3 -Scope CurrentUser - ``` - - Add AutoRest.exe path to $env:Path - ```powershell - $package = Get-Package -Name AutoRest -RequiredVersion 0.17.3 - $env:path += ";$(Split-Path $package.Source -Parent)\tools" + $env:path += ";$env:APPDATA\npm" Get-Command -Name AutoRest ``` - 3. Ensure CSC.exe is installed and available in $env:PATH - If CSC.exe is not installed already, install Microsoft.Net.Compilers package ```powershell diff --git a/Tests/PSSwagger.Unit.Tests.ps1 b/Tests/PSSwagger.Unit.Tests.ps1 index d063161..5da5fcd 100644 --- a/Tests/PSSwagger.Unit.Tests.ps1 +++ b/Tests/PSSwagger.Unit.Tests.ps1 @@ -239,5 +239,19 @@ Describe "PSSwagger Unit Tests" -Tag @('BVT', 'DRT', 'UnitTest', 'P0') { Get-HeaderContent -SwaggerDict @{Info = @{Header = $ValidHeaderFilePath}} | Should BeExactly $HeaderFileContent } } + + Context "Get-CSharpModelName Unit Tests" { + It "Get-CSharpModelNamee should remove special characters" { + Get-CSharpModelName -Name @" + SomeTypeWithSpecialCharacters ~!@#$%^&*()_+|}{:"<>?,./;'][\=-`` +"@ | Should BeExactly 'SomeTypeWithSpecialCharacters' + } + It "Get-CSharpModelNamee should replace [] with Sequence" { + Get-CSharpModelName -Name 'foo[]' | Should BeExactly 'FooSequence' + } + It "Get-CSharpModelNamee should append 'Model' for C# reserved words" { + Get-CSharpModelName -Name 'break' | Should BeExactly 'BreakModel' + } + } } } diff --git a/Tests/run-tests.ps1 b/Tests/run-tests.ps1 index 83b396b..78b047c 100644 --- a/Tests/run-tests.ps1 +++ b/Tests/run-tests.ps1 @@ -9,7 +9,7 @@ ######################################################################################### [CmdletBinding()] param( - [ValidateSet("All","UnitTest","ScenarioTest")] + [ValidateSet("All", "UnitTest", "ScenarioTest")] [string[]]$TestSuite = "All", [string[]]$TestName, [ValidateSet("net452", "netstandard1.7")] @@ -17,91 +17,112 @@ param( [switch]$EnableTracing ) -$executeTestsCommand = "" +Microsoft.PowerShell.Core\Set-StrictMode -Version Latest +$executeTestsCommand = "Set-Location -Path '$PSScriptRoot'" # Import test utilities Import-Module "$PSScriptRoot\TestUtilities.psm1" -Force $nugetPackageSource = Test-NugetPackageSource -$testRunGuid = [guid]::NewGuid().GUID -Write-Verbose -message "Test run GUID: $testRunGuid" $nodeModuleVersions = @{} -# Set up scenario test requirements -if ($TestSuite.Contains("All") -or $TestSuite.Contains("ScenarioTest")) { - # Ensure node.js is installed - $nodejsModule = Test-Package -packageName "Node.JS" -packageSourceName $nugetPackageSource.Name - if ($nodejsModule -eq $null) { - throw "Node.JS failed to install." - } +$NodeJSVersion = '7.10.0' +$NodeJSPackageName = "node-v$NodeJSVersion-win-x64" - $nodejsInstallPath = Split-Path -Path $nodejsModule.Source - # Ensure npm is installed - $npmModule = Test-Package -packageName "Npm" -packageSourceName $nugetPackageSource.Name - if ($npmModule -eq $null) { - throw "NPM failed to install." - } - $nodeModuleVersions['npm'] = $npmModule.Version +# Note: If we use the $PSScriptRoot, Expand-Archive cmdlet is failing with path too long error (260 characters limit). +# So using $env:SystemDrive\$NodeJSPackageName path for now. +$NodeJSLocalPath = Join-Path -Path $env:SystemDrive -ChildPath $NodeJSPackageName - $npmInstallPath = Split-Path -Path $npmModule.Source +if (-not (Test-Path -Path $NodeJSLocalPath -PathType Container)) { + $NodeJSZipURL = "https://nodejs.org/dist/v$NodeJSVersion/$NodeJSPackageName.zip" + $TempNodeJSZipLocalPath = Join-Path -Path $env:TEMP -ChildPath "$NodeJSPackageName.zip" + Invoke-WebRequest -Uri $NodeJSZipURL -OutFile $TempNodeJSZipLocalPath -UseBasicParsing + try { + # Using Join-Path to get "$env:SystemDrive\" path. + $DestinationPath = Join-Path -Path $env:SystemDrive -ChildPath '' + Expand-Archive -Path $TempNodeJSZipLocalPath -DestinationPath $DestinationPath -Force - # Ensure the location exists where we keep node and node modules - $nodeModulePath = Join-Path -Path $PSScriptRoot -ChildPath "NodeModules" - $nodeExePath = Join-Path -Path $nodeModulePath -ChildPath "node.exe" - $jsonServerPath = Join-Path -Path $nodeModulePath -ChildPath "json-server.cmd" - if (-not (Test-Path $nodeModulePath)) { - Write-Verbose "Creating local node modules directory $nodeModulePath" - New-Item -Path $nodeModulePath -ItemType Directory -Force + if (-not (Test-Path -Path $NodeJSLocalPath -PathType Container)) { + Throw "Unable to install '$NodeJSZipURL' to local machine." + } } - - # Let's copy node.exe to the other directory so we can keep everything in one place - if (-not (Test-Path $nodeExePath)) { - Write-Verbose "Copying node.exe from NuGet package to $nodeExePath" - Copy-Item -Path (Join-Path -Path $nodejsInstallPath -ChildPath "node.exe") -Destination $nodeExePath + finally { + Remove-Item -Path $TempNodeJSZipLocalPath -Force } +} + +$NpmCmdPath = Join-Path -Path $NodeJSLocalPath -ChildPath 'npm.cmd' +if (-not (Test-Path -Path $NpmCmdPath -PathType Leaf)) { + Throw "Unable to find $NpmCmdPath." +} +$nodeModuleVersions['npm'] = & $NpmCmdPath list -g npm + +$NodeModulesPath = Join-Path -Path $PSScriptRoot -ChildPath 'NodeModules' +if (-not (Test-Path -Path $NodeModulesPath -PathType Container)) { + $null = New-Item -Path $NodeModulesPath -ItemType Directory -Force +} + +# Install AutoRest using NPM, if not installed already. +$AutorestCmdPath = Join-Path -Path $NodeModulesPath -ChildPath 'autorest.cmd' +if (-not (Test-Path -Path $AutorestCmdPath -PathType Leaf)) { + Write-Verbose "Couldn't find $AutorestCmdPath. Running 'npm install -g autorest'." + & $NpmCmdPath install -g --prefix $NodeModulesPath autorest --scripts-prepend-node-path +} +$nodeModuleVersions['autorest'] = & $NpmCmdPath list -g --prefix $NodeModulesPath autorest +$executeTestsCommand += ";`$env:Path =`"$NodeModulesPath;`$env:Path`"" + +$AutoRestPluginPath = Join-Path -Path $env:USERPROFILE -ChildPath '.autorest' | + Join-Path -ChildPath 'plugins' | + Join-Path -ChildPath 'autorest' + +if (-not ((Test-Path -Path $AutoRestPluginPath -PathType Container) -and + (Get-ChildItem -Path $AutoRestPluginPath -Directory))) { + # Create the generator plugins + & $AutorestCmdPath --reset +} + +$testRunGuid = [guid]::NewGuid().GUID +Write-Verbose -message "Test run GUID: $testRunGuid" +# Set up scenario test requirements +if ($TestSuite.Contains("All") -or $TestSuite.Contains("ScenarioTest")) { # Ensure we have json-server - if (-not (Test-Path $jsonServerPath)) { - Write-Verbose "Couldn't find $jsonServerPath. Running npm install -g json-server." - & $nodeExePath (Join-Path -Path $npmInstallPath -ChildPath "node_modules\npm\bin\npm-cli.js") "install" "-g" "json-server@0.9.6" + $jsonServerPath = Join-Path -Path $NodeModulesPath -ChildPath 'json-server.cmd' + if (-not (Test-Path -Path $jsonServerPath -PathType Leaf)) { + Write-Verbose "Couldn't find $jsonServerPath. Running 'npm install -g json-server@0.9.6'." + & $NpmCmdPath install -g --prefix $NodeModulesPath json-server@0.9.6 --scripts-prepend-node-path } - $nodeModuleVersions['json-server'] = & $nodeExePath (Join-Path -Path $npmInstallPath -ChildPath "node_modules\npm\bin\npm-cli.js") 'list' '-g' 'json-server' + $nodeModuleVersions['json-server'] = & $NpmCmdPath list -g --prefix $NodeModulesPath json-server + + $localnode_modulesPath = Join-Path -Path $PSScriptRoot -ChildPath 'node_modules' # For these node modules, it's easier on the middleware script devs to just install the modules locally instead of globally # Ensure we have request (for easy HTTP request creation for some test middlewares) - if (-not (Test-Path (Join-Path $PSScriptRoot "node_modules" | Join-Path -ChildPath "request"))) { - Write-Verbose "Couldn't find request module. Running npm install request." - & $nodeExePath (Join-Path -Path $npmInstallPath -ChildPath "node_modules\npm\bin\npm-cli.js") "install" "request" + if (-not (Test-Path -Path (Join-Path -Path $localnode_modulesPath -ChildPath 'request'))) { + Write-Verbose "Couldn't find 'request' module. Running 'npm install request'." + & $NpmCmdPath install request --scripts-prepend-node-path } # Ensure we have async (for HTTP request resolution synchronously in some test middlewares) - if (-not (Test-Path (Join-Path $PSScriptRoot "node_modules" | Join-Path -ChildPath "async"))) { - Write-Verbose "Couldn't find async module. Running npm install async." - & $nodeExePath (Join-Path -Path $npmInstallPath -ChildPath "node_modules\npm\bin\npm-cli.js") "install" "async" + if (-not (Test-Path -Path (Join-Path -Path $localnode_modulesPath -ChildPath 'async'))) { + Write-Verbose "Couldn't find 'async' module. Running 'npm install async'." + & $NpmCmdPath install async --scripts-prepend-node-path } - $executeTestsCommand += ";`$env:Path+=`";$nodeModulePath`"" $executeTestsCommand += ";`$global:testRunGuid=`"$testRunGuid`"" # Set up the common generated modules location - $generatedModulesPath = Join-Path -Path "$PSScriptRoot" -ChildPath "Generated" - if (-not (Test-Path $generatedModulesPath)) { - $null = New-Item -Path $generatedModulesPath -ItemType Directory - } - if (-not (Test-Path $nodeExePath)) { - Write-Verbose "Copying node.exe from NuGet package to $nodeExePath" - Copy-Item -Path (Join-Path -Path $nodejsInstallPath -ChildPath "node.exe") -Destination $nodeExePath + $generatedModulesPath = Join-Path -Path "$PSScriptRoot" -ChildPath 'Generated' + # Remove existing Generated folder contents. + if (Test-Path -Path $generatedModulesPath -PathType Container) { + Remove-Item -Path $generatedModulesPath -Recurse -Force } + $null = New-Item -Path $generatedModulesPath -ItemType Directory } -# Set up AutoRest -$autoRestModule = Test-Package -packageName "AutoRest" -packageSourceName $nugetPackageSource.Name -requiredVersion 0.17.3 -$autoRestInstallPath = Split-Path -Path $autoRestModule.Source -$executeTestsCommand += ";`$env:Path+=`";$autoRestInstallPath\tools`"" - # Set up Microsoft.Net.Compilers $MicrosoftNetCompilers = Test-Package -PackageName Microsoft.Net.Compilers -PackageSourceName $nugetPackageSource.Name $MicrosoftNetCompilersInstallPath = Split-Path -Path $MicrosoftNetCompilers.Source -$executeTestsCommand += ";`$env:Path+=`";$MicrosoftNetCompilersInstallPath\tools`"" +$executeTestsCommand += ";`$env:Path=`"$MicrosoftNetCompilersInstallPath\tools;`$env:Path`"" # AzureRM.Profile requirement $azureRmProfile = Get-Module -Name AzureRM.Profile -ListAvailable | Select-Object -First 1 -ErrorAction Ignore @@ -116,7 +137,7 @@ if (-not $azureRmProfile) { $powershellFolder = $null if ("netstandard1.7" -eq $TestFramework) { # beta > alpha - $powershellCore = Get-Package PowerShell* -ProviderName msi | Sort-Object -Property Name -Descending | Select-Object -First 1 + $powershellCore = Get-Package -Name PowerShell* -ProviderName msi | Sort-Object -Property Name -Descending | Select-Object -First 1 -ErrorAction Ignore if ($null -eq $powershellCore) { throw "PowerShellCore not found on this machine. Run: tools\Get-PowerShellCore" } @@ -141,7 +162,8 @@ if ($PSBoundParameters.ContainsKey('TestName')) { if ($TestSuite.Contains("All")) { Write-Verbose "Invoking all tests." -} else { +} +else { Write-Verbose "Running only tests with tag: $TestSuite" $executeTestsCommand += " -Tag $TestSuite" } @@ -149,31 +171,30 @@ if ($TestSuite.Contains("All")) { Write-Verbose "Dependency versions:" Write-Verbose " -- AzureRM.Profile: $($azureRmProfile.Version)" Write-Verbose " -- Pester: $((get-command invoke-pester).Version)" -if ($autoRestModule) { - Write-Verbose " -- AutoRest: $($autoRestModule.Version)" -} foreach ($entry in $nodeModuleVersions.GetEnumerator()) { Write-Verbose " -- $($entry.Key): $($entry.Value)" } +$PesterCommandFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'PesterCommand.ps1' Write-Verbose "Executing: $executeTestsCommand" -$executeTestsCommand | Out-File pesterCommand.ps1 +$executeTestsCommand | Out-File -FilePath $PesterCommandFilePath if ($TestFramework -eq "netstandard1.7") { try { $null = Get-CimInstance Win32_OperatingSystem Write-Verbose -Message "Invoking PowerShell Core at: $powershellFolder" - & "$powershellFolder\powershell" -command .\pesterCommand.ps1 - } catch { + & "$powershellFolder\powershell" -command $PesterCommandFilePath + } + catch { # For non-Windows, keep using the basic command - powershell -command .\pesterCommand.ps1 + powershell -command $PesterCommandFilePath } -} else { - powershell -command .\pesterCommand.ps1 +} +else { + powershell -command $PesterCommandFilePath } # Verify output $x = [xml](Get-Content -raw "ScenarioTestResults.xml") -if ([int]$x.'test-results'.failures -gt 0) -{ +if ([int]$x.'test-results'.failures -gt 0) { throw "$($x.'test-results'.failures) tests failed" } \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index b0788b6..c054f28 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,6 +5,10 @@ configuration: Release platform: Any CPU install: - cinst -y pester + - ps: Install-Product node '7.10.0' + - npm install + - node --version + - npm --version build: off test_script: - ps: | From 21a2a5408fcd96b60a1b19f2cd5a16a6384bf2bf Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Wed, 6 Sep 2017 16:48:29 -0700 Subject: [PATCH 11/40] Removing the langversion from CSC parameters as latest is the default language version. (#318) Reason: '/langversion:latest' is not supported on all versions of CSC.exe, especially the version installed with Visual Studio. --- PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 | 1 - 1 file changed, 1 deletion(-) diff --git a/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 b/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 index 31b55c4..221473d 100644 --- a/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 +++ b/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 @@ -806,7 +806,6 @@ function Get-CscParameters { $CscParameter = @( $SourceCodeFilePath '/nologo', - '/langversion:latest', '/checked', '/warn:3', '/debug:full', From 15af392ed4aa0dcdc3de46e5a19379650e9585b8 Mon Sep 17 00:00:00 2001 From: brywang-msft Date: Wed, 6 Sep 2017 18:42:08 -0700 Subject: [PATCH 12/40] Changes for test server to work for Azure SDK happy path tests (#315) * Parameterless execution of PSSwagger.LTF.ConsoleServer.exe + test module fixes * Fix parse of config.json * Load config.json from .exe directory + composite logger + event log logger * ASCII -> UTF8 * Headers should always be passed as arrays * Bunch of fixes to get happy path working in Azure tests * Argument validation --- .../PSSwagger.LTF.ConsoleServer.csproj | 4 + .../PSSwagger.LTF.ConsoleServer/Program.cs | 118 +++- .../PSSwagger.LTF.ConsoleServer/config.json | 2 + .../PSSwagger.LTF.ConsoleServer.csproj | 10 + .../vs-csproj/packages.config | 4 + .../EventLogOutputPipe.cs | 40 ++ .../src/PSSwagger.LTF.IO.Lib/JsonRpcPipe.cs | 2 +- .../Converters/DynamicTypedObjectConverter.cs | 19 +- .../Converters/LiveTestRequestConverter.cs | 17 +- .../Interfaces/ICommandPostProcessor.cs | 15 + .../PSSwagger.LTF.Lib/Json/JsonPathFinder.cs | 32 +- .../src/PSSwagger.LTF.Lib/LiveTestServer.cs | 49 +- .../Logging/CompositeLogger.cs | 90 +++ .../src/PSSwagger.LTF.Lib/Logging/Logger.cs | 14 +- .../Messages/LiveTestRequest.cs | 10 +- .../PSSwagger.LTF.Lib/Models/AzurePageType.cs | 62 ++ .../Models/GeneratedModule.cs | 632 +++++++++++++----- .../PSSwagger.LTF.Lib/Models/OperationData.cs | 13 + .../PSSwagger.LTF.Lib/Models/ParameterData.cs | 45 ++ .../Models/ResponseTypeData.cs | 86 +++ .../Models/RuntimeTypeData.cs | 43 +- .../PostProcessors/AzurePagePostProcessor.cs | 40 ++ .../PowerShell/PowerShellRunspace.cs | 25 +- .../ClientRuntimeServiceTracer.cs | 8 +- .../vs-csproj/PSSwagger.LTF.Lib.csproj | 3 + .../GeneratedModuleTests.cs | 540 +++++++++++---- .../JsonPathFinderTests.cs | 6 +- .../LiveTestRequestTests.cs | 4 +- .../Mocks/MockJsonPathFinder.cs | 45 +- .../PowerShellRunspaceTests.cs | 32 +- .../BasicHappyPath/BasicHappyPath.psm1 | 1 + .../PSSwagger.LiveTestFramework.Tests.psm1 | 4 +- .../test/project-xunittest.proj | 4 +- 33 files changed, 1661 insertions(+), 358 deletions(-) create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/config.json create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/vs-csproj/packages.config create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/EventLogOutputPipe.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Interfaces/ICommandPostProcessor.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Logging/CompositeLogger.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/AzurePageType.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Models/ResponseTypeData.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PostProcessors/AzurePagePostProcessor.cs diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj index de5da85..64e6192 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj @@ -10,5 +10,9 @@ + + + + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs index 68fa4ee..31a21d2 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs @@ -10,19 +10,41 @@ namespace PSSwagger.LTF.ConsoleServer using Lib.Models; using Lib.PowerShell; using Lib.ServiceTracing; + using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Globalization; using System.IO; + using System.Reflection; using System.Threading; class Program { static void Main(string[] args) { - ServerArgs serverArgs = new ServerArgs(args); - NamedPipeServer namedPipe = new NamedPipeServer(serverArgs.LogPipeName); - Logger logger = new Logger(namedPipe, namedPipe); + ServerArgs serverArgs = new ServerArgs().Parse(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "config.json")).Parse(args).Validate(); + + EventLogOutputPipe eventLogOutputPipe = new EventLogOutputPipe(); + CompositeLogger logger = new CompositeLogger(); + if (serverArgs.EnableEventLog) + { + logger.AddLogger(new Logger(eventLogOutputPipe, eventLogOutputPipe)); + } + + if (serverArgs.EnablePipeLog) + { + try + { + NamedPipeServer namedPipe = new NamedPipeServer(serverArgs.LogPipeName); + Logger namedPipeLogger = new Logger(namedPipe, namedPipe); + logger.AddLogger(namedPipeLogger); + } + catch (Exception e) + { + logger.LogError("Failed to initialize named pipe logger: " + e.Message); + } + } + if (serverArgs.Errors.Count > 0) { logger.LogError("Server arguments had errors."); @@ -73,11 +95,11 @@ static void Main(string[] args) // Wait until server exits (usually means the server ran into an internal error) while (server.IsRunning) { - Thread.Sleep(1); + Thread.Sleep(2); } } } - + class ServerArgs { public List SpecificationPaths { get; set; } @@ -85,14 +107,24 @@ class ServerArgs public string ModulePath { get; set; } public string LogPipeName { get; set; } public List Errors { get; set; } + public bool EnablePipeLog { get; set; } + public bool EnableEventLog { get; set; } - public ServerArgs(string[] args) + public ServerArgs() { - this.LogPipeName = "psswagger-ltf-consoleserver"; - string lastArg = String.Empty; + this.Errors = new List(); this.SpecificationPaths = new List(); this.ExternalModules = new List(); - this.Errors = new List(); + this.LogPipeName = "psswagger-ltf-consoleserver"; + this.EnablePipeLog = true; + this.EnableEventLog = false; + } + + public ServerArgs Parse(string[] args) + { + string lastArg = String.Empty; + bool resetSpecificationPaths = false; + bool resetExternalModules = false; foreach (string arg in args) { if (!arg.StartsWith("/")) @@ -103,15 +135,29 @@ public ServerArgs(string[] args) if (!arg.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) { this.Errors.Add("Specification file is not a .json file."); - } else if (!File.Exists(arg)) + } + else if (!File.Exists(arg)) { this.Errors.Add(String.Format(CultureInfo.CurrentCulture, "Specification file does not exist: {0}", arg)); - } else + } + else { + if (!resetSpecificationPaths) + { + resetSpecificationPaths = true; + this.SpecificationPaths.Clear(); + } + this.SpecificationPaths.Add(arg); } break; case "extmodule": + if (!resetExternalModules) + { + resetExternalModules = true; + this.ExternalModules.Clear(); + } + this.ExternalModules.Add(arg); break; case "testmodule": @@ -125,16 +171,64 @@ public ServerArgs(string[] args) break; } lastArg = String.Empty; - } else + } + else { lastArg = arg.Substring(1); + switch (lastArg.ToLowerInvariant()) + { + case "enablepipelog": + lastArg = String.Empty; + this.EnablePipeLog = true; + break; + case "enableeventlog": + lastArg = String.Empty; + this.EnableEventLog = true; + break; + } + } + } + + return this; + } + + public ServerArgs Parse(string jsonFilePath) + { + if (File.Exists(jsonFilePath)) + { + ServerArgs fromFile = JsonConvert.DeserializeObject(File.ReadAllText(jsonFilePath)); + if (!String.IsNullOrWhiteSpace(fromFile.ModulePath)) + { + this.ModulePath = fromFile.ModulePath; + } + + if (!String.IsNullOrWhiteSpace(fromFile.LogPipeName)) + { + this.LogPipeName = fromFile.LogPipeName; + } + + if (fromFile.ExternalModules != null && fromFile.ExternalModules.Count > 0) + { + this.ExternalModules = fromFile.ExternalModules; + } + + if (fromFile.SpecificationPaths != null && fromFile.SpecificationPaths.Count > 0) + { + this.SpecificationPaths = fromFile.SpecificationPaths; } } + return this; + } + + public ServerArgs Validate() + { if (String.IsNullOrEmpty(this.ModulePath)) { this.Errors.Add("No test module path specified."); } + + return this; } } } diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/config.json b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/config.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/config.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/vs-csproj/PSSwagger.LTF.ConsoleServer.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/vs-csproj/PSSwagger.LTF.ConsoleServer.csproj index e07b498..627a09f 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/vs-csproj/PSSwagger.LTF.ConsoleServer.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/vs-csproj/PSSwagger.LTF.ConsoleServer.csproj @@ -32,6 +32,10 @@ 4 + + packages\Newtonsoft.Json.10.0.1\lib\net45\Newtonsoft.Json.dll + True + @@ -43,6 +47,9 @@ + + PreserveNewest + @@ -54,6 +61,9 @@ PSSwagger.LTF.Lib + + + -" \ No newline at end of file +" + +$DefaultGeneratedFileHeaderWithoutVersion = @' +Code generated by Microsoft (R) PSSwagger +Changes may cause incorrect behavior and will be lost if the code is regenerated. +'@ + +$MicrosoftApacheLicenseHeader = @' +Copyright (c) Microsoft and contributors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the ""License""); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and +limitations under the License. +'@ + +$MicrosoftMitLicenseHeader = @' +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. See License.txt in the project root for license information. +'@ + diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index 6fdc323..b28eb0e 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -61,7 +61,15 @@ Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename PSSwa .PARAMETER Header Text to include as a header comment in the PSSwagger generated files. It also can be a path to a .txt file with the content to be added as header in the PSSwagger generated files. - Specify 'NONE' to suppress the default header. + + Supported predefined license header values: + - NONE: Suppresses the default header. + - MICROSOFT_MIT: Adds predefined Microsoft MIT license text with default PSSwagger code generation header content. + - MICROSOFT_MIT_NO_VERSION: Adds predefined Microsoft MIT license text with default PSSwagger code generation header content without version. + - MICROSOFT_MIT_NO_CODEGEN: Adds predefined Microsoft MIT license text without default PSSwagger code generation header content. + - MICROSOFT_APACHE: Adds predefined Microsoft Apache license text with default PSSwagger code generation header content. + - MICROSOFT_APACHE_NO_VERSION: Adds predefined Microsoft Apache license text with default PSSwagger code generation header content without version. + - MICROSOFT_APACHE_NO_CODEGEN: Adds predefined Microsoft Apache license text without default PSSwagger code generation header content. .PARAMETER NoAssembly Switch to disable saving the precompiled module assembly and instead enable dynamic compilation. @@ -931,6 +939,30 @@ function Get-HeaderContent { $Header = $swaggerDict['Info'].Header $HeaderContent = ($DefaultGeneratedFileHeader -f $MyInvocation.MyCommand.Module.Version) if ($Header) { + switch ($Header) { + 'MICROSOFT_MIT' { + return $MicrosoftMitLicenseHeader + [Environment]::NewLine + [Environment]::NewLine + $HeaderContent + } + 'MICROSOFT_MIT_NO_VERSION' { + return $MicrosoftMitLicenseHeader + [Environment]::NewLine + [Environment]::NewLine + $DefaultGeneratedFileHeaderWithoutVersion + } + 'MICROSOFT_MIT_NO_CODEGEN' { + return $MicrosoftMitLicenseHeader + } + 'MICROSOFT_APACHE' { + return $MicrosoftApacheLicenseHeader + [Environment]::NewLine + [Environment]::NewLine + $HeaderContent + } + 'MICROSOFT_APACHE_NO_VERSION' { + return $MicrosoftApacheLicenseHeader + [Environment]::NewLine + [Environment]::NewLine + $DefaultGeneratedFileHeaderWithoutVersion + } + 'MICROSOFT_APACHE_NO_CODEGEN' { + return $MicrosoftApacheLicenseHeader + } + 'NONE' { + return '' + } + } + $HeaderFilePath = Resolve-Path -Path $Header -ErrorAction Ignore if ($HeaderFilePath) { # Selecting the first path when multiple paths are returned by Resolve-Path cmdlet. @@ -958,9 +990,6 @@ function Get-HeaderContent { Write-Error -Message $message -ErrorId 'HeaderFilePathNotFound' -Category InvalidArgument return } - elseif ($Header -eq 'NONE') { - $HeaderContent = $null - } else { $HeaderContent = $Header } diff --git a/Tests/PSSwagger.Unit.Tests.ps1 b/Tests/PSSwagger.Unit.Tests.ps1 index 5da5fcd..736458a 100644 --- a/Tests/PSSwagger.Unit.Tests.ps1 +++ b/Tests/PSSwagger.Unit.Tests.ps1 @@ -238,6 +238,34 @@ Describe "PSSwagger Unit Tests" -Tag @('BVT', 'DRT', 'UnitTest', 'P0') { It "Get-HeaderContent should return content from the header file" { Get-HeaderContent -SwaggerDict @{Info = @{Header = $ValidHeaderFilePath}} | Should BeExactly $HeaderFileContent } + + It "Get-HeaderContent should return MICROSOFT_MIT header content with '-Header MICROSOFT_MIT'" { + $ExpectedHeaderContent = $MicrosoftMitLicenseHeader + [Environment]::NewLine + [Environment]::NewLine + $DefaultGeneratedFileHeaderString + Get-HeaderContent -SwaggerDict @{Info = @{Header = 'MICROSOFT_MIT'}} | Should BeExactly $ExpectedHeaderContent + } + + It "Get-HeaderContent should return MICROSOFT_MIT_NO_VERSION header content with '-Header MICROSOFT_MIT_NO_VERSION'" { + $ExpectedHeaderContent = $MicrosoftMitLicenseHeader + [Environment]::NewLine + [Environment]::NewLine + $DefaultGeneratedFileHeaderWithoutVersion + Get-HeaderContent -SwaggerDict @{Info = @{Header = 'MICROSOFT_MIT_NO_VERSION'}} | Should BeExactly $ExpectedHeaderContent + } + + It "Get-HeaderContent should return MICROSOFT_MIT_NO_CODEGEN header content with '-Header MICROSOFT_MIT_NO_CODEGEN'" { + Get-HeaderContent -SwaggerDict @{Info = @{Header = 'MICROSOFT_MIT_NO_CODEGEN'}} | Should BeExactly $MicrosoftMitLicenseHeader + } + + It "Get-HeaderContent should return MICROSOFT_APACHE header content with '-Header MICROSOFT_APACHE'" { + $ExpectedHeaderContent = $MicrosoftApacheLicenseHeader + [Environment]::NewLine + [Environment]::NewLine + $DefaultGeneratedFileHeaderString + Get-HeaderContent -SwaggerDict @{Info = @{Header = 'MICROSOFT_APACHE'}} | Should BeExactly $ExpectedHeaderContent + } + + It "Get-HeaderContent should return MICROSOFT_APACHE_NO_VERSION header content with '-Header MICROSOFT_APACHE_NO_VERSION'" { + $ExpectedHeaderContent = $MicrosoftApacheLicenseHeader + [Environment]::NewLine + [Environment]::NewLine + $DefaultGeneratedFileHeaderWithoutVersion + Get-HeaderContent -SwaggerDict @{Info = @{Header = 'MICROSOFT_APACHE_NO_VERSION'}} | Should BeExactly $ExpectedHeaderContent + } + + It "Get-HeaderContent should return MICROSOFT_APACHE_NO_CODEGEN header content with '-Header MICROSOFT_APACHE_NO_CODEGEN'" { + Get-HeaderContent -SwaggerDict @{Info = @{Header = 'MICROSOFT_APACHE_NO_CODEGEN'}} | Should BeExactly $MicrosoftApacheLicenseHeader + } } Context "Get-CSharpModelName Unit Tests" { diff --git a/docs/commands/New-PSSwaggerModule.md b/docs/commands/New-PSSwaggerModule.md index cf6deb6..277547a 100644 --- a/docs/commands/New-PSSwaggerModule.md +++ b/docs/commands/New-PSSwaggerModule.md @@ -142,7 +142,15 @@ Accept wildcard characters: False ### -Header Text to include as a header comment in the PSSwagger generated files. It also can be a path to a .txt file with the content to be added as header in the PSSwagger generated files. -Specify 'NONE' to suppress the default header. + +Supported predefined license header values: +- NONE: Suppresses the default header. +- MICROSOFT_MIT: Adds predefined Microsoft MIT license text with default PSSwagger code generation header content. +- MICROSOFT_MIT_NO_VERSION: Adds predefined Microsoft MIT license text with default PSSwagger code generation header content without version. +- MICROSOFT_MIT_NO_CODEGEN: Adds predefined Microsoft MIT license text without default PSSwagger code generation header content. +- MICROSOFT_APACHE: Adds predefined Microsoft Apache license text with default PSSwagger code generation header content. +- MICROSOFT_APACHE_NO_VERSION: Adds predefined Microsoft Apache license text with default PSSwagger code generation header content without version. +- MICROSOFT_APACHE_NO_CODEGEN: Adds predefined Microsoft Apache license text without default PSSwagger code generation header content. ```yaml Type: String[] From 37d8618273de74eeacb703b444e944e3f48f8418 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Fri, 8 Sep 2017 17:25:50 -0700 Subject: [PATCH 14/40] Add support for generating PowerShell cmdlets using pre-built SDK assembly and specification. (#321) * Add support for generating PowerShell cmdlets using pre-compiled SDK assembly and Swagger specification. * Added -ModelsName parameter on New-PSSwaggerModule cmdlet, and also enabled the tests to run on PSCore. --- ... AssemblyGenerationHelpers.Resources.psd1} | 2 - PSSwagger/AssemblyGenerationHelpers.ps1 | 97 +++++ ...ratedHelpers.psm1 => GeneratedHelpers.ps1} | 0 PSSwagger/PSSwagger.Constants.ps1 | 72 +--- PSSwagger/PSSwagger.psm1 | 406 +++++++++++------- PSSwagger/Paths.psm1 | 7 +- PSSwagger/SwaggerUtils.psm1 | 67 ++- Tests/PSSwaggerScenario.Tests.ps1 | 208 +++++++++ Tests/TestUtilities.psm1 | 44 ++ Tests/run-tests.ps1 | 11 +- docs/commands/New-PSSwaggerModule.md | 89 +++- 11 files changed, 768 insertions(+), 235 deletions(-) rename PSSwagger/{Generated.Resources.psd1 => AssemblyGenerationHelpers.Resources.psd1} (97%) create mode 100644 PSSwagger/AssemblyGenerationHelpers.ps1 rename PSSwagger/{GeneratedHelpers.psm1 => GeneratedHelpers.ps1} (100%) diff --git a/PSSwagger/Generated.Resources.psd1 b/PSSwagger/AssemblyGenerationHelpers.Resources.psd1 similarity index 97% rename from PSSwagger/Generated.Resources.psd1 rename to PSSwagger/AssemblyGenerationHelpers.Resources.psd1 index c70a408..9b00b0c 100644 --- a/PSSwagger/Generated.Resources.psd1 +++ b/PSSwagger/AssemblyGenerationHelpers.Resources.psd1 @@ -4,8 +4,6 @@ # # Licensed under the MIT license. # -# PSSwagger Module -# ######################################################################################### ConvertFrom-StringData @' diff --git a/PSSwagger/AssemblyGenerationHelpers.ps1 b/PSSwagger/AssemblyGenerationHelpers.ps1 new file mode 100644 index 0000000..5bbbc01 --- /dev/null +++ b/PSSwagger/AssemblyGenerationHelpers.ps1 @@ -0,0 +1,97 @@ +######################################################################################### +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# Licensed under the MIT license. +# +######################################################################################### + +Microsoft.PowerShell.Core\Set-StrictMode -Version Latest +Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -FileName AssemblyGenerationHelpers.Resources.psd1 + +<# +.DESCRIPTION + Compiles AutoRest generated C# code using the framework of the current PowerShell process. + +.PARAMETER AssemblyFileName + Full Path to the output assembly. + +.PARAMETER IsAzureSDK + C# code generated by Azure.CSharp AutoRest code generator. +#> +function New-SDKAssembly { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $AssemblyFileName, + + [Parameter(Mandatory = $false)] + [switch] + $IsAzureSDK + ) + + $OperatingSystemInfo = Get-OperatingSystemInfo + if ($OperatingSystemInfo.IsCore) { + $clr = 'coreclr' + $framework = 'netstandard1' + } + else { + $clr = 'fullclr' + $framework = 'net4' + } + + $ClrPath = Join-Path -Path $PSScriptRoot -ChildPath 'ref' | Join-Path -ChildPath $clr + $dllFullName = Join-Path -Path $ClrPath -ChildPath $AssemblyFileName + $UserConsent = $false + if (-not (Test-Path -Path $dllFullName -PathType Leaf)) { + Write-Verbose -Message ($LocalizedData.CompilingBinaryComponent -f ($dllFullName)) + + $generatedCSharpFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'Generated.Csharp' + if (-not (Test-Path -Path $generatedCSharpFilePath -PathType Container)) { + Write-Error -ErrorId 'CSharpFilesNotFound' -Message ($LocalizedData.CSharpFilesNotFound -f ($generatedCSharpFilePath)) + return + } + + $CodePs1FilePath = Join-Path -Path $generatedCSharpFilePath -ChildPath '*.Code.ps1' + $AllCSharpFiles = Get-ChildItem -Path $CodePs1FilePath -Recurse -File + if ($OperatingSystemInfo.IsWindows) { + $AllCSharpFiles | ForEach-Object { + $AuthenticodeSignature = Get-AuthenticodeSignature -FilePath $_.FullName + if (('NotSigned' -ne $AuthenticodeSignature.Status) -and ('Valid' -ne $AuthenticodeSignature.Status)) { + Write-Error -Message $LocalizedData.HashValidationFailed -ErrorId 'HashValidationFailed' + return + } + } + Write-Verbose -Message $LocalizedData.HashValidationSuccessful + } + + $dependencies = Get-PSSwaggerExternalDependencies -Azure:$IsAzureSDK -Framework $framework + $UserConsent = Initialize-PSSwaggerLocalTool -Azure:$IsAzureSDK -Framework $framework + $microsoftRestClientRuntimeAzureRequiredVersion = '' + if ($dependencies.ContainsKey('Microsoft.Rest.ClientRuntime.Azure')) { + $microsoftRestClientRuntimeAzureRequiredVersion = $dependencies['Microsoft.Rest.ClientRuntime.Azure'].RequiredVersion + } + + $microsoftRestClientRuntimeRequiredVersion = $dependencies['Microsoft.Rest.ClientRuntime'].RequiredVersion + $newtonsoftJsonRequiredVersion = $dependencies['Newtonsoft.Json'].RequiredVersion + + $AddPSSwaggerClientType_params = @{ + CSharpFiles = $AllCSharpFiles + NewtonsoftJsonRequiredVersion = $newtonsoftJsonRequiredVersion + MicrosoftRestClientRuntimeRequiredVersion = $microsoftRestClientRuntimeRequiredVersion + MicrosoftRestClientRuntimeAzureRequiredVersion = $microsoftRestClientRuntimeAzureRequiredVersion + ClrPath = $ClrPath + BootstrapConsent = $UserConsent + CodeCreatedByAzureGenerator = $IsAzureSDK + } + $success = Add-PSSwaggerClientType @AddPSSwaggerClientType_params + if (-not $success) { + Write-Error -ErrorId 'UnableToGenerateAssembly' -Message ($LocalizedData.CompilationFailed -f ($dllFullName)) + return + } + + $message = $LocalizedData.CompilationSucceeded -f ($dllFullName) + Write-Verbose -Message $message + } +} diff --git a/PSSwagger/GeneratedHelpers.psm1 b/PSSwagger/GeneratedHelpers.ps1 similarity index 100% rename from PSSwagger/GeneratedHelpers.psm1 rename to PSSwagger/GeneratedHelpers.ps1 diff --git a/PSSwagger/PSSwagger.Constants.ps1 b/PSSwagger/PSSwagger.Constants.ps1 index 7a70c4c..06b0d5c 100644 --- a/PSSwagger/PSSwagger.Constants.ps1 +++ b/PSSwagger/PSSwagger.Constants.ps1 @@ -24,9 +24,17 @@ $parameterDefString = @' $parameterDefaultValueString = ' = $parameterDefaultValue' +$DynamicAssemblyGenerationBlock = @' +`$dllFullName = Join-Path -Path `$ClrPath -ChildPath '$DllFileName' +if(-not (Test-Path -Path `$dllFullName -PathType Leaf)) { + . (Join-Path -Path `$PSScriptRoot -ChildPath 'AssemblyGenerationHelpers.ps1') + New-SDKAssembly -AssemblyFileName '$DllFileName' -IsAzureSDK:`$$UseAzureCSharpGenerator +} +'@ + $RootModuleContents = @' Microsoft.PowerShell.Core\Set-StrictMode -Version Latest -Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename $Name.Resources.psd1 + # If the user supplied -Prefix to Import-Module, that applies to the nested module as well # Force import the nested module again without -Prefix if (-not (Get-Command Get-OperatingSystemInfo -Module PSSwaggerUtility -ErrorAction Ignore)) { @@ -35,61 +43,20 @@ if (-not (Get-Command Get-OperatingSystemInfo -Module PSSwaggerUtility -ErrorAct if ((Get-OperatingSystemInfo).IsCore) { $testCoreModuleRequirements`$clr = 'coreclr' - `$framework = 'netstandard1' -} else { - $testFullModuleRequirements`$clr = 'fullclr' - `$framework = 'net4' } - -`$clrPath = Join-Path -Path `$PSScriptRoot -ChildPath 'ref' | Join-Path -ChildPath `$clr -`$dllFullName = Join-Path -Path `$clrPath -ChildPath '$Namespace.dll' -`$isAzureCSharp = `$$UseAzureCSharpGenerator -`$consent = `$false -if (-not (Test-Path -Path `$dllFullName)) { - `$message = `$LocalizedData.CompilingBinaryComponent -f (`$dllFullName) - Write-Verbose -Message `$message - `$generatedCSharpFilePath = (Join-Path -Path "`$PSScriptRoot" -ChildPath "Generated.Csharp") - if (-not (Test-Path -Path `$generatedCSharpFilePath)) { - throw `$LocalizedData.CSharpFilesNotFound -f (`$generatedCSharpFilePath) - } - - `$allCSharpFiles = Get-ChildItem -Path (Join-Path -Path `$generatedCSharpFilePath -ChildPath "*.Code.ps1") -Recurse -Exclude Program.cs,TemporaryGeneratedFile* -File | Where-Object DirectoryName -notlike '*Azure.Csharp.Generated*' - if ((Get-OperatingSystemInfo).IsWindows) { - `$allCSharpFiles | ForEach-Object { - `$sig = Get-AuthenticodeSignature -FilePath `$_.FullName - if (('NotSigned' -ne `$sig.Status) -and ('Valid' -ne `$sig.Status)) { - throw `$LocalizedData.HashValidationFailed - } - } - - `$message = `$LocalizedData.HashValidationSuccessful - Write-Verbose -Message `$message -Verbose - } - - `$dependencies = Get-PSSwaggerExternalDependencies -Azure:`$isAzureCSharp -Framework `$framework - `$consent = Initialize-PSSwaggerLocalTool -Azure:`$isAzureCSharp -Framework `$framework - `$microsoftRestClientRuntimeAzureRequiredVersion = '' - if (`$dependencies.ContainsKey('Microsoft.Rest.ClientRuntime.Azure')) { - `$microsoftRestClientRuntimeAzureRequiredVersion = `$dependencies['Microsoft.Rest.ClientRuntime.Azure'].RequiredVersion - } - - `$microsoftRestClientRuntimeRequiredVersion = `$dependencies['Microsoft.Rest.ClientRuntime'].RequiredVersion - `$newtonsoftJsonRequiredVersion = `$dependencies['Newtonsoft.Json'].RequiredVersion - - `$success = Add-PSSwaggerClientType -CSharpFiles `$allCSharpFiles -NewtonsoftJsonRequiredVersion `$newtonsoftJsonRequiredVersion -MicrosoftRestClientRuntimeRequiredVersion `$microsoftRestClientRuntimeRequiredVersion -MicrosoftRestClientRuntimeAzureRequiredVersion "`$microsoftRestClientRuntimeAzureRequiredVersion" -ClrPath `$clrPath -BootstrapConsent:`$consent -CodeCreatedByAzureGenerator:`$isAzureCSharp - if (-not `$success) { - `$message = `$LocalizedData.CompilationFailed -f (`$dllFullName) - throw `$message - } - - `$message = `$LocalizedData.CompilationFailed -f (`$dllFullName) - Write-Verbose -Message `$message +else { + $testFullModuleRequirements`$clr = 'fullclr' } +`$ClrPath = Join-Path -Path `$PSScriptRoot -ChildPath 'ref' | Join-Path -ChildPath `$clr +$DynamicAssemblyGenerationCode +`$allDllsPath = Join-Path -Path `$ClrPath -ChildPath '*.dll' +Get-ChildItem -Path `$allDllsPath -File | ForEach-Object { Add-Type -Path `$_.FullName -ErrorAction SilentlyContinue } +. (Join-Path -Path `$PSScriptRoot -ChildPath 'GeneratedHelpers.ps1') -Get-ChildItem -Path (Join-Path -Path "`$PSScriptRoot" -ChildPath "ref" | Join-Path -ChildPath "`$clr" | Join-Path -ChildPath "*.dll") -File | ForEach-Object { Add-Type -Path `$_.FullName -ErrorAction SilentlyContinue } -Get-ChildItem -Path "`$PSScriptRoot\$GeneratedCommandsName\*.ps1" -Recurse -File | ForEach-Object { . `$_.FullName} +`$allPs1FilesPath = Join-Path -Path `$PSScriptRoot -ChildPath '$GeneratedCommandsName' | Join-Path -ChildPath '*.ps1' +Get-ChildItem -Path `$allPs1FilesPath -Recurse -File | ForEach-Object { . `$_.FullName} '@ $advFnSignatureForDefintion = @' @@ -114,7 +81,6 @@ $AsJobParameterString = @' $advFnSignatureForPath = @' -Import-Module -Name (Join-Path -Path `$PSScriptRoot -ChildPath .. | Join-Path -ChildPath .. | Join-Path -ChildPath "GeneratedHelpers.psm1") <# $commandHelp $paramHelp @@ -193,7 +159,7 @@ $functionBodyStr = @' `$ErrorActionPreference = 'Stop' $securityBlock - $clientName = New-Object -TypeName $fullModuleName -ArgumentList $clientArgumentList$apiVersion + $clientName = New-Object -TypeName $FullClientTypeName -ArgumentList $clientArgumentList$apiVersion $overrideBaseUriBlock $GlobalParameterBlock $oDataExpressionBlock diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index b28eb0e..0178318 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -46,6 +46,23 @@ Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename PSSwa .PARAMETER SpecificationUri Uri to a Swagger based JSON spec. +.PARAMETER AssemblyFileName + File name of the pre-compiled SDK assembly. + This assembly along with its dependencies should be available in '.\ref\fullclr\' folder under the target module version base path ($Path\$Name\$Version\). + If your generated module needs to work on PowerShell Core, place the coreclr assembly along with its depdencies under '.\ref\coreclr\' folder under the target module version base path ($Path\$Name\$Version\). + For FullClr, the specified assembly should be available at "$Path\$Name\$Version\ref\fullclr\$AssemblyFileName". + For CoreClr, the specified assembly should be available at "$Path\$Name\$Version\ref\coreclr\$AssemblyFileName". + +.PARAMETER ClientTypeName + Client type name in the pre-compiled SDK assembly. + Specify if client type name is different from the value of 'Title' field from the input specification, or + if client type namespace is different from the specified namespace in the specification. + It is recommended to specify the fully qualified client type name. + +.PARAMETER ModelsName + Models name if it is different from default value 'Models'. + It is recommended to specify the custom models name in using x-ms-code-generation-settings extension in specification. + .PARAMETER Path Full Path to a file where the commands are exported to. @@ -107,13 +124,15 @@ Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename PSSwa #> function New-PSSwaggerModule { - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName='SpecificationPath')] param( - [Parameter(Mandatory = $true, ParameterSetName = 'SwaggerPath')] + [Parameter(Mandatory = $true, ParameterSetName = 'SpecificationPath')] + [Parameter(Mandatory = $true, ParameterSetName = 'SdkAssemblyWithSpecificationPath')] [string] $SpecificationPath, - [Parameter(Mandatory = $true, ParameterSetName = 'SwaggerURI')] + [Parameter(Mandatory = $true, ParameterSetName = 'SpecificationUri')] + [Parameter(Mandatory = $true, ParameterSetName = 'SdkAssemblyWithSpecificationUri')] [Uri] $SpecificationUri, @@ -121,6 +140,21 @@ function New-PSSwaggerModule [string] $Path, + [Parameter(Mandatory = $true, ParameterSetName = 'SdkAssemblyWithSpecificationPath')] + [Parameter(Mandatory = $true, ParameterSetName = 'SdkAssemblyWithSpecificationUri')] + [string] + $AssemblyFileName, + + [Parameter(Mandatory = $false, ParameterSetName = 'SdkAssemblyWithSpecificationPath')] + [Parameter(Mandatory = $false, ParameterSetName = 'SdkAssemblyWithSpecificationUri')] + [string] + $ClientTypeName, + + [Parameter(Mandatory = $false, ParameterSetName = 'SdkAssemblyWithSpecificationPath')] + [Parameter(Mandatory = $false, ParameterSetName = 'SdkAssemblyWithSpecificationUri')] + [string] + $ModelsName, + [Parameter(Mandatory = $true)] [string] $Name, @@ -141,31 +175,38 @@ function New-PSSwaggerModule [switch] $UseAzureCsharpGenerator, - [Parameter()] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationPath')] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationUri')] [switch] $NoAssembly, - [Parameter()] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationPath')] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationUri')] [string] $PowerShellCorePath, - [Parameter()] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationPath')] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationUri')] [switch] $IncludeCoreFxAssembly, - [Parameter()] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationPath')] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationUri')] [switch] $InstallToolsForAllUsers, - [Parameter()] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationPath')] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationUri')] [switch] $TestBuild, - [Parameter()] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationPath')] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationUri')] [string] $SymbolPath, - [Parameter()] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationPath')] + [Parameter(Mandatory = $false, ParameterSetName = 'SpecificationUri')] [switch] $ConfirmBootstrap ) @@ -197,7 +238,8 @@ function New-PSSwaggerModule $SwaggerSpecFilePaths = @() $AutoRestModeler = 'Swagger' - if ($PSCmdlet.ParameterSetName -eq 'SwaggerURI') + if (($PSCmdlet.ParameterSetName -eq 'SpecificationUri') -or + ($PSCmdlet.ParameterSetName -eq 'SdkAssemblyWithSpecificationUri')) { # Ensure that if the URI is coming from github, it is getting the raw content if($SpecificationUri.Host -eq 'github.com'){ @@ -279,7 +321,8 @@ function New-PSSwaggerModule $PSMetaJsonObject = ConvertFrom-Json -InputObject ((Get-Content -Path $PSMetaFilePath) -join [Environment]::NewLine) -ErrorAction Stop } - if ($PSCmdlet.ParameterSetName -eq 'SwaggerPath') + if (($PSCmdlet.ParameterSetName -eq 'SpecificationPath') -or + ($PSCmdlet.ParameterSetName -eq 'SdkAssemblyWithSpecificationPath')) { $jsonObject = ConvertFrom-Json -InputObject ((Get-Content -Path $SpecificationPath) -join [Environment]::NewLine) -ErrorAction Stop if((Get-Member -InputObject $jsonObject -Name 'Documents') -and ($jsonObject.Documents.Count)) @@ -309,44 +352,48 @@ function New-PSSwaggerModule } } - $frameworksToCheckDependencies = @('net4') - if ($IncludeCoreFxAssembly) { - if ((-not (Get-OperatingSystemInfo).IsCore) -and (-not $PowerShellCorePath)) { - $psCore = Get-PSSwaggerMsi -Name "PowerShell*" -MaximumVersion "6.0.0.11" | Sort-Object -Property Version -Descending - if ($null -ne $psCore) { - # PSCore exists via MSI, but the MSI provider doesn't seem to provide an install path - # First check the default path (for now, just Windows) - $psCore | ForEach-Object { - if (-not $PowerShellCorePath) { - $message = $LocalizedData.FoundPowerShellCoreMsi -f ($($_.Version)) - Write-Verbose -Message $message - $possiblePsPath = (Join-Path -Path "$env:ProgramFiles" -ChildPath "PowerShell" | Join-Path -ChildPath "$($_.Version)" | Join-Path -ChildPath "PowerShell.exe") - if (Test-Path -Path $possiblePsPath) { - $PowerShellCorePath = $possiblePsPath + if (($PSCmdlet.ParameterSetName -eq 'SpecificationPath') -or + ($PSCmdlet.ParameterSetName -eq 'SpecificationUri')) + { + $frameworksToCheckDependencies = @('net4') + if ($IncludeCoreFxAssembly) { + if ((-not (Get-OperatingSystemInfo).IsCore) -and (-not $PowerShellCorePath)) { + $psCore = Get-PSSwaggerMsi -Name "PowerShell*" -MaximumVersion "6.0.0.11" | Sort-Object -Property Version -Descending + if ($null -ne $psCore) { + # PSCore exists via MSI, but the MSI provider doesn't seem to provide an install path + # First check the default path (for now, just Windows) + $psCore | ForEach-Object { + if (-not $PowerShellCorePath) { + $message = $LocalizedData.FoundPowerShellCoreMsi -f ($($_.Version)) + Write-Verbose -Message $message + $possiblePsPath = (Join-Path -Path "$env:ProgramFiles" -ChildPath "PowerShell" | Join-Path -ChildPath "$($_.Version)" | Join-Path -ChildPath "PowerShell.exe") + if (Test-Path -Path $possiblePsPath) { + $PowerShellCorePath = $possiblePsPath + } } } } } - } - if (-not $PowerShellCorePath) { - throw $LocalizedData.MustSpecifyPsCorePath - } + if (-not $PowerShellCorePath) { + throw $LocalizedData.MustSpecifyPsCorePath + } - if ((Get-Item $PowerShellCorePath).PSIsContainer) { - $PowerShellCorePath = Join-Path -Path $PowerShellCorePath -ChildPath "PowerShell.exe" - } + if ((Get-Item $PowerShellCorePath).PSIsContainer) { + $PowerShellCorePath = Join-Path -Path $PowerShellCorePath -ChildPath "PowerShell.exe" + } + + if (-not (Test-Path -Path $PowerShellCorePath)) { + $message = $LocalizedData.PsCorePathNotFound -f ($PowerShellCorePath) + throw $message + } - if (-not (Test-Path -Path $PowerShellCorePath)) { - $message = $LocalizedData.PsCorePathNotFound -f ($PowerShellCorePath) - throw $message + $frameworksToCheckDependencies += 'netstandard1' } - $frameworksToCheckDependencies += 'netstandard1' + $userConsent = Initialize-PSSwaggerLocalTool -AllUsers:$InstallToolsForAllUsers -Azure:$UseAzureCsharpGenerator -Framework $frameworksToCheckDependencies -AcceptBootstrap:$ConfirmBootstrap } - $userConsent = Initialize-PSSwaggerLocalTool -AllUsers:$InstallToolsForAllUsers -Azure:$UseAzureCsharpGenerator -Framework $frameworksToCheckDependencies -AcceptBootstrap:$ConfirmBootstrap - $DefinitionFunctionsDetails = @{} $PowerShellCodeGen = @{ CodeGenerator = "" @@ -368,16 +415,18 @@ function New-PSSwaggerModule # Parse the JSON and populate the dictionary $ConvertToSwaggerDictionary_params = @{ - SwaggerSpecPath = $SpecificationPath - ModuleName = $Name - ModuleVersion = $Version - DefaultCommandPrefix = $DefaultCommandPrefix - Header = $($Header -join "`r`n") - SwaggerSpecFilePaths = $SwaggerSpecFilePaths + SwaggerSpecPath = $SpecificationPath + ModuleName = $Name + ModuleVersion = $Version + DefaultCommandPrefix = $DefaultCommandPrefix + Header = $($Header -join "`r`n") + SwaggerSpecFilePaths = $SwaggerSpecFilePaths DefinitionFunctionsDetails = $DefinitionFunctionsDetails - AzureSpec = $UseAzureCsharpGenerator - PowerShellCodeGen = $PowerShellCodeGen - PSMetaJsonObject = $PSMetaJsonObject + AzureSpec = $UseAzureCsharpGenerator + PowerShellCodeGen = $PowerShellCodeGen + PSMetaJsonObject = $PSMetaJsonObject + ClientTypeName = $ClientTypeName + ModelsName = $ModelsName } $swaggerDict = ConvertTo-SwaggerDictionary @ConvertToSwaggerDictionary_params @@ -473,18 +522,40 @@ function New-PSSwaggerModule } } - $codePhaseResult = ConvertTo-CsharpCode -SwaggerDict $swaggerDict ` - -SwaggerMetaDict $swaggerMetaDict ` - -PowerShellCorePath $PowerShellCorePath ` - -InstallToolsForAllUsers:$InstallToolsForAllUsers ` - -UserConsent:$userConsent ` - -TestBuild:$TestBuild ` - -PathFunctionDetails $PathFunctionDetails ` - -NoAssembly:$NoAssembly ` - -SymbolPath $SymbolPath + $FullClrAssemblyFilePath = $null + if($AssemblyFileName) { + $FullClrAssemblyFilePath = Join-Path -Path $outputDirectory -ChildPath 'ref' | Join-Path -ChildPath 'fullclr' | Join-Path -ChildPath $AssemblyFileName + if(-not (Test-Path -Path $FullClrAssemblyFilePath -PathType Leaf)) { + $message = $LocalizedData.PathNotFound -f $FullClrAssemblyFilePath + Write-Error -Message $message -ErrorId AssemblyNotFound + return + } + } + else { + $ConvertToCsharpCode_params = @{ + SwaggerDict = $swaggerDict + SwaggerMetaDict = $swaggerMetaDict + PowerShellCorePath = $PowerShellCorePath + InstallToolsForAllUsers = $InstallToolsForAllUsers + UserConsent = $userConsent + TestBuild = $TestBuild + NoAssembly = $NoAssembly + SymbolPath = $SymbolPath + } + $AssemblyGenerationResult = ConvertTo-CsharpCode @ConvertToCsharpCode_params + if(-not $AssemblyGenerationResult) { + return + } + $FullClrAssemblyFilePath = $AssemblyGenerationResult['FullClrAssemblyFilePath'] + } + + $NameSpace = $SwaggerDict['info'].NameSpace + $FullClientTypeName = $Namespace + '.' + $SwaggerDict['Info'].ClientTypeName - $PathFunctionDetails = $codePhaseResult.PathFunctionDetails - $generatedCSharpFilePath = $codePhaseResult.GeneratedCSharpPath + $PathFunctionDetails = Update-PathFunctionDetails -PathFunctionDetails $PathFunctionDetails -FullClientTypeName $FullClientTypeName + if(-not $PathFunctionDetails) { + return + } # Need to expand the definitions early as parameter flattening feature requires the parameters list of the definition/model types. Expand-SwaggerDefinition -DefinitionFunctionsDetails $DefinitionFunctionsDetails -NameSpace $NameSpace -Models $Models @@ -519,6 +590,15 @@ function New-PSSwaggerModule $testFullModuleRequirements = '. (Join-Path -Path $PSScriptRoot "Test-FullRequirements.ps1")' + [Environment]::NewLine + " " } + $DynamicAssemblyGenerationCode = $null + if ($AssemblyFileName) { + $DllFileName = $AssemblyFileName + } + else { + $DllFileName = "$Namespace.dll" + $DynamicAssemblyGenerationCode = $ExecutionContext.InvokeCommand.ExpandString($DynamicAssemblyGenerationBlock) + } + Out-File -FilePath $RootModuleFilePath ` -InputObject @($PSHeaderComment, $ExecutionContext.InvokeCommand.ExpandString($RootModuleContents))` -Encoding ascii ` @@ -527,17 +607,21 @@ function New-PSSwaggerModule -WhatIf:$false New-ModuleManifestUtility -Path $outputDirectory ` - -FunctionsToExport $FunctionsToExport ` - -Info $swaggerDict['info'] ` - -PSHeaderComment $PSHeaderComment + -FunctionsToExport $FunctionsToExport ` + -Info $swaggerDict['info'] ` + -PSHeaderComment $PSHeaderComment $CopyFilesMap = [ordered]@{ - 'Generated.Resources.psd1' = "$Name.Resources.psd1" - 'GeneratedHelpers.psm1' = 'GeneratedHelpers.psm1' + 'GeneratedHelpers.ps1' = 'GeneratedHelpers.ps1' 'Test-CoreRequirements.ps1' = 'Test-CoreRequirements.ps1' 'Test-FullRequirements.ps1' = 'Test-FullRequirements.ps1' } + if (-not $AssemblyFileName) { + $CopyFilesMap['AssemblyGenerationHelpers.ps1'] = 'AssemblyGenerationHelpers.ps1' + $CopyFilesMap['AssemblyGenerationHelpers.Resources.psd1'] = 'AssemblyGenerationHelpers.Resources.psd1' + } + $CopyFilesMap.GetEnumerator() | ForEach-Object { Copy-PSFileWithHeader -SourceFilePath (Join-Path -Path "$PSScriptRoot" -ChildPath $_.Name) ` -DestinationFilePath (Join-Path -Path "$outputDirectory" -ChildPath $_.Value) ` @@ -549,6 +633,61 @@ function New-PSSwaggerModule #region Module Generation Helpers +function Update-PathFunctionDetails { + param( + [Parameter(Mandatory=$true)] + [PSCustomObject] + $PathFunctionDetails, + + [Parameter(Mandatory=$true)] + [string] + $FullClientTypeName + ) + + $cliXmlTmpPath = Get-TemporaryCliXmlFilePath -FullClientTypeName $FullClientTypeName + + try { + Export-CliXml -InputObject $PathFunctionDetails -Path $cliXmlTmpPath + $PathsPsm1FilePath = Join-Path -Path $PSScriptRoot -ChildPath Paths.psm1 + $command = @" + Add-Type -Path '$FullClrAssemblyFilePath' + Import-Module -Name '$PathsPsm1FilePath' -DisableNameChecking + Set-ExtendedCodeMetadata -MainClientTypeName '$FullClientTypeName' -CliXmlTmpPath '$cliXmlTmpPath' +"@ + $null = & PowerShell.exe -command "& {$command}" + + $codeReflectionResult = Import-CliXml -Path $cliXmlTmpPath + if ($codeReflectionResult.ContainsKey('VerboseMessages') -and + $codeReflectionResult.VerboseMessages -and + ($codeReflectionResult.VerboseMessages.Count -gt 0)) { + $verboseMessages = $codeReflectionResult.VerboseMessages -Join [Environment]::NewLine + Write-Verbose -Message $verboseMessages + } + + if ($codeReflectionResult.ContainsKey('WarningMessages') -and + $codeReflectionResult.WarningMessages -and + ($codeReflectionResult.WarningMessages.Count -gt 0)) { + $warningMessages = $codeReflectionResult.WarningMessages -Join [Environment]::NewLine + Write-Warning -Message $warningMessages + } + + if (-not $codeReflectionResult.Result -or + $codeReflectionResult.ErrorMessages.Count -gt 0) { + $errorMessage = (, ($LocalizedData.MetadataExtractFailed) + + $codeReflectionResult.ErrorMessages) -Join [Environment]::NewLine + Write-Error -Message $errorMessage -ErrorId 'UnableToExtractDetailsFromSdkAssembly' + return + } + + return $codeReflectionResult.Result + } + finally { + if (Test-Path -Path $cliXmlTmpPath -PathType Leaf) { + $null = Remove-Item -Path $cliXmlTmpPath -Force -WhatIf:$false -Confirm:$false + } + } +} + function ConvertTo-CsharpCode { param @@ -577,10 +716,6 @@ function ConvertTo-CsharpCode [switch] $TestBuild, - [Parameter(Mandatory=$true)] - [hashtable] - $PathFunctionDetails, - [Parameter()] [switch] $NoAssembly, @@ -591,7 +726,7 @@ function ConvertTo-CsharpCode ) Write-Verbose -Message $LocalizedData.GenerateCodeUsingAutoRest - $info = $SwaggerDict['Info'] + $info = $SwaggerDict['Info'] $AutoRestCommand = Get-Command -Name AutoRest -ErrorAction Ignore | Select-Object -First 1 -ErrorAction Ignore if (-not $AutoRestCommand) { @@ -634,14 +769,16 @@ function ConvertTo-CsharpCode $outAssembly = '' } - $return = @{ + $result = @{ GeneratedCSharpPath = $generatedCSharpPath + FullClrAssemblyFilePath = '' + CoreClrAssemblyFilePath = '' } $tempCodeGenSettingsPath = '' # Latest AutoRest inconsistently appends 'Client' to the specified infoName to generated the client name. # We need to override the client name to ensure that generated PowerShell cmdlets work fine. - $ClientName = $info['infoName'] + $ClientName = $info['ClientTypeName'] try { if ($info.ContainsKey('CodeGenFileRequired') -and $info.CodeGenFileRequired) { # Some settings need to be overwritten @@ -702,7 +839,8 @@ function ConvertTo-CsharpCode } if ($LastExitCode) { - throw $LocalizedData.AutoRestError + Write-Error -Message $LocalizedData.AutoRestError -ErrorId 'SourceCodeGenerationError' + return } } finally { @@ -710,7 +848,6 @@ function ConvertTo-CsharpCode $null = Remove-Item -Path $tempCodeGenSettingsPath -Force -ErrorAction Ignore } } - Write-Verbose -Message $LocalizedData.GenerateAssemblyFromCode if ($info.ContainsKey('CodeOutputDirectory') -and $info.CodeOutputDirectory) { @@ -733,76 +870,42 @@ function ConvertTo-CsharpCode # Compile full CLR (PSSwagger requires to be invoked from full PowerShell) $codeCreatedByAzureGenerator = [bool]$SwaggerMetaDict['UseAzureCsharpGenerator'] - # As of 3/2/2017, there's a version mismatch between the latest Microsoft.Rest.ClientRuntime.Azure package and the latest AzureRM.Profile package - # So we have to hardcode Microsoft.Rest.ClientRuntime.Azure to at most version 3.3.4 - $fullModuleName = $Namespace + '.' + $ClientName - $cliXmlTmpPath = Get-TemporaryCliXmlFilePath -FullModuleName $fullModuleName - try { - Export-CliXml -InputObject $PathFunctionDetails -Path $cliXmlTmpPath - $dependencies = Get-PSSwaggerExternalDependencies -Azure:$codeCreatedByAzureGenerator -Framework 'net4' - $microsoftRestClientRuntimeAzureRequiredVersion = if ($dependencies.ContainsKey('Microsoft.Rest.ClientRuntime.Azure')) { $dependencies['Microsoft.Rest.ClientRuntime.Azure'].RequiredVersion } else { '' } - $command = "PSSwaggerUtility\Add-PSSwaggerClientType -OutputAssemblyName '$outAssembly' `` - -ClrPath '$clrPath' `` - -CSharpFiles $allCSharpFilesArrayString `` - -CodeCreatedByAzureGenerator:`$$codeCreatedByAzureGenerator `` - -MicrosoftRestClientRuntimeAzureRequiredVersion '$microsoftRestClientRuntimeAzureRequiredVersion' `` - -MicrosoftRestClientRuntimeRequiredVersion '$($dependencies['Microsoft.Rest.ClientRuntime'].RequiredVersion)' `` - -NewtonsoftJsonRequiredVersion '$($dependencies['Newtonsoft.Json'].RequiredVersion)' `` - -AllUsers:`$$InstallToolsForAllUsers `` - -BootstrapConsent:`$$UserConsent `` - -TestBuild:`$$TestBuild `` - -SymbolPath $SymbolPath; - if('$outAssembly') { - # Load the generated assembly to extract the extended metadata - `$AssemblyPath = Join-Path -Path '$clrPath' -ChildPath '$outAssembly' - if(Test-Path -Path `$AssemblyPath -PathType Leaf) { - Add-Type -Path `$AssemblyPath - } - } - - Import-Module `"`$(Join-Path -Path `"$PSScriptRoot`" -ChildPath `"Paths.psm1`")` -DisableNameChecking; - Set-ExtendedCodeMetadata -MainClientTypeName $fullModuleName `` - -CliXmlTmpPath $cliXmlTmpPath" - - $success = & "powershell" -command "& {$command}" - - $codeReflectionResult = Import-CliXml -Path $cliXmlTmpPath - if ($codeReflectionResult.ContainsKey('VerboseMessages') -and $codeReflectionResult.VerboseMessages -and ($codeReflectionResult.VerboseMessages.Count -gt 0)) { - $verboseMessages = $codeReflectionResult.VerboseMessages -Join [Environment]::NewLine - Write-Verbose -Message $verboseMessages - } - - if ($codeReflectionResult.ContainsKey('WarningMessages') -and $codeReflectionResult.WarningMessages -and ($codeReflectionResult.WarningMessages.Count -gt 0)) { - $warningMessages = $codeReflectionResult.WarningMessages -Join [Environment]::NewLine - Write-Warning -Message $warningMessages + $dependencies = Get-PSSwaggerExternalDependencies -Azure:$codeCreatedByAzureGenerator -Framework 'net4' + $microsoftRestClientRuntimeAzureRequiredVersion = if ($dependencies.ContainsKey('Microsoft.Rest.ClientRuntime.Azure')) { $dependencies['Microsoft.Rest.ClientRuntime.Azure'].RequiredVersion } else { '' } + + if(-not $OutAssembly) { + $TempGuid = [Guid]::NewGuid().Guid + if (-not $OutAssembly) { + $OutAssembly = "$TempGuid.dll" } + $ClrPath = Join-Path -Path (Get-XDGDirectory -DirectoryType Cache) -ChildPath ([Guid]::NewGuid().Guid) + $null = New-Item -Path $ClrPath -ItemType Directory -Force -WhatIf:$false -Confirm:$false + } - if ((Test-AssemblyCompilationSuccess -Output ($success | Out-String))) { - $message = $LocalizedData.GeneratedAssembly -f ($outAssembly) - Write-Verbose -Message $message - } else { - # This should be enough to let the user know we failed to generate their module's assembly. - if (-not $outAssembly) { - $outAssembly = "$NameSpace.dll" - } + $AddPSSwaggerClientType_params = @{ + OutputAssemblyName = $outAssembly + ClrPath = $clrPath + CSharpFiles = $allCodeFiles + CodeCreatedByAzureGenerator = $codeCreatedByAzureGenerator + MicrosoftRestClientRuntimeAzureRequiredVersion = $microsoftRestClientRuntimeAzureRequiredVersion + MicrosoftRestClientRuntimeRequiredVersion = $dependencies['Microsoft.Rest.ClientRuntime'].RequiredVersion + NewtonsoftJsonRequiredVersion = $dependencies['Newtonsoft.Json'].RequiredVersion + AllUsers = $InstallToolsForAllUsers + BootstrapConsent = $UserConsent + TestBuild = $TestBuild + SymbolPath = $SymbolPath + } - $message = $LocalizedData.UnableToGenerateAssembly -f ($outAssembly) - Throw $message - } + if(-not (PSSwaggerUtility\Add-PSSwaggerClientType @AddPSSwaggerClientType_params)) { + $message = $LocalizedData.UnableToGenerateAssembly -f ($outAssembly) + Write-Error -ErrorId 'UnableToGenerateAssembly' -Message $message + return + } - if (-not $codeReflectionResult.Result -or $codeReflectionResult.ErrorMessages.Count -gt 0) { - $errorMessage = (,($LocalizedData.MetadataExtractFailed) + - $codeReflectionResult.ErrorMessages) -Join [Environment]::NewLine - throw $errorMessage - } + $message = $LocalizedData.GeneratedAssembly -f ($outAssembly) + Write-Verbose -Message $message + $result['FullClrAssemblyFilePath'] = Join-Path -Path $ClrPath -ChildPath $OutAssembly - $return.PathFunctionDetails = $codeReflectionResult.Result - } finally { - if (Test-Path -Path $cliXmlTmpPath) { - $null = Remove-Item -Path $cliXmlTmpPath - } - } - # If we're not going to save the assembly, no need to generate the core CLR one now if ($PowerShellCorePath -and (-not $NoAssembly)) { if (-not $outAssembly) { @@ -820,26 +923,33 @@ function ConvertTo-CsharpCode } $dependencies = Get-PSSwaggerExternalDependencies -Azure:$codeCreatedByAzureGenerator -Framework 'netstandard1' $microsoftRestClientRuntimeAzureRequiredVersion = if ($dependencies.ContainsKey('Microsoft.Rest.ClientRuntime.Azure')) { $dependencies['Microsoft.Rest.ClientRuntime.Azure'].RequiredVersion } else { '' } - $command = "PSSwaggerUtility\Add-PSSwaggerClientType -OutputAssemblyName '$outAssembly' `` - -ClrPath '$clrPath' `` - -CSharpFiles $allCSharpFilesArrayString `` - -MicrosoftRestClientRuntimeAzureRequiredVersion '$microsoftRestClientRuntimeAzureRequiredVersion' `` - -MicrosoftRestClientRuntimeRequiredVersion '$($dependencies['Microsoft.Rest.ClientRuntime'].RequiredVersion)' `` - -NewtonsoftJsonRequiredVersion '$($dependencies['Newtonsoft.Json'].RequiredVersion)' `` - -CodeCreatedByAzureGenerator:`$$codeCreatedByAzureGenerator `` - -BootstrapConsent:`$$UserConsent" + $command = @" + `$AddPSSwaggerClientType_params = @{ + OutputAssemblyName = '$outAssembly' + ClrPath = '$clrPath' + CSharpFiles = $allCSharpFilesArrayString + MicrosoftRestClientRuntimeAzureRequiredVersion = '$microsoftRestClientRuntimeAzureRequiredVersion' + MicrosoftRestClientRuntimeRequiredVersion = '$($dependencies['Microsoft.Rest.ClientRuntime'].RequiredVersion)' + NewtonsoftJsonRequiredVersion = '$($dependencies['Newtonsoft.Json'].RequiredVersion)' + CodeCreatedByAzureGenerator = `$$codeCreatedByAzureGenerator + BootstrapConsent = `$$UserConsent + } + PSSwaggerUtility\Add-PSSwaggerClientType @AddPSSwaggerClientType_params +"@ $success = & "$PowerShellCorePath" -command "& {$command}" if ((Test-AssemblyCompilationSuccess -Output ($success | Out-String))) { $message = $LocalizedData.GeneratedAssembly -f ($outAssembly) Write-Verbose -Message $message } else { $message = $LocalizedData.UnableToGenerateAssembly -f ($outAssembly) - Throw $message + Write-Error -ErrorId 'UnableToGenerateCoreClrAssembly' -Message $message + return } + $result['CoreClrAssemblyFilePath'] = Join-Path -Path $ClrPath -ChildPath $OutAssembly } - return $return + return $result } function Test-AssemblyCompilationSuccess { diff --git a/PSSwagger/Paths.psm1 b/PSSwagger/Paths.psm1 index ba3c636..d0d52cf 100644 --- a/PSSwagger/Paths.psm1 +++ b/PSSwagger/Paths.psm1 @@ -416,8 +416,7 @@ function New-SwaggerPath $info = $SwaggerDict['Info'] $namespace = $info['NameSpace'] $models = $info['Models'] - $modulePostfix = $info['infoName'] - $clientName = '$' + $modulePostfix + $clientName = '$' + $info['ClientTypeName'] $UseAzureCsharpGenerator = $SwaggerMetaDict['UseAzureCsharpGenerator'] $description = '' @@ -1352,10 +1351,10 @@ function Get-TemporaryCliXmlFilePath { param( [Parameter(Mandatory=$true)] [string] - $FullModuleName + $FullClientTypeName ) $random = [Guid]::NewGuid().Guid - $filePath = Join-Path -Path (Get-XDGDirectory -DirectoryType Cache) -ChildPath "$FullModuleName.$random.xml" + $filePath = Join-Path -Path (Get-XDGDirectory -DirectoryType Cache) -ChildPath "$FullClientTypeName.$random.xml" return $filePath } \ No newline at end of file diff --git a/PSSwagger/SwaggerUtils.psm1 b/PSSwagger/SwaggerUtils.psm1 index e662161..dc4396d 100644 --- a/PSSwagger/SwaggerUtils.psm1 +++ b/PSSwagger/SwaggerUtils.psm1 @@ -86,6 +86,16 @@ function ConvertTo-SwaggerDictionary { [Version] $ModuleVersion = '0.0.1', + [Parameter(Mandatory=$false)] + [AllowEmptyString()] + [string] + $ClientTypeName, + + [Parameter(Mandatory=$false)] + [AllowEmptyString()] + [string] + $ModelsName, + [Parameter(Mandatory = $false)] [string] $DefaultCommandPrefix, @@ -140,6 +150,12 @@ function ConvertTo-SwaggerDictionary { { $GetSwaggerInfo_params['ModuleName'] = $ModuleName } + if($ClientTypeName) { + $GetSwaggerInfo_params['ClientTypeName'] = $ClientTypeName + } + if($ModelsName) { + $GetSwaggerInfo_params['ModelsName'] = $ModelsName + } $swaggerDict['Info'] = Get-SwaggerInfo @GetSwaggerInfo_params $swaggerDict['Info']['DefaultCommandPrefix'] = $DefaultCommandPrefix if($Header) { @@ -204,7 +220,15 @@ function Get-SwaggerInfo { [Parameter(Mandatory=$false)] [Version] - $ModuleVersion = '0.0.1' + $ModuleVersion = '0.0.1', + + [Parameter(Mandatory=$false)] + [string] + $ClientTypeName, + + [Parameter(Mandatory=$false)] + [string] + $ModelsName ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState @@ -219,7 +243,10 @@ function Get-SwaggerInfo { $infoName = '' $NameSpace = '' $codeGenFileRequired = $false - $modelsName = 'Models' + if(-not $ModelsName) { + $modelsName = 'Models' + } + $Header = '' if(Get-Member -InputObject $Info -Name 'x-ms-code-generation-settings') { $prop = Test-PropertyWithAliases -InputObject $Info.'x-ms-code-generation-settings' -Aliases @('ClientName', 'Name') @@ -238,10 +265,12 @@ function Get-SwaggerInfo { } } - $prop = Test-PropertyWithAliases -InputObject $Info.'x-ms-code-generation-settings' -Aliases @('ModelsName', 'mname') - if ($prop) { - # When ModelsName is specified, this changes the subnamespace of the models from 'Models' to whatever is specified - $modelsName = $Info.'x-ms-code-generation-settings'.$prop + if(-not $PSBoundParameters.ContainsKey('ModelsName')) { + $prop = Test-PropertyWithAliases -InputObject $Info.'x-ms-code-generation-settings' -Aliases @('ModelsName', 'mname') + if ($prop) { + # When ModelsName is specified, this changes the subnamespace of the models from 'Models' to whatever is specified + $modelsName = $Info.'x-ms-code-generation-settings'.$prop + } } $prop = Test-PropertyWithAliases -InputObject $Info.'x-ms-code-generation-settings' -Aliases @('Namespace', 'n') @@ -336,16 +365,29 @@ function Get-SwaggerInfo { $NameSpace = "$script:PSSwaggerDefaultNamespace.$ModuleName.$NamespaceVersionSuffix" } - # AutoRest generates client name with 'Client' appended to info title when a NameSpace part is same as the info name. - if($NameSpace.Split('.', [System.StringSplitOptions]::RemoveEmptyEntries) -contains $infoName) - { - $infoName = $infoName + 'Client' + if($ClientTypeName) { + # Get the namespace from namespace qualified client type name. + $LastDotIndex = $ClientTypeName.LastIndexOf('.') + if($LastDotIndex -ne -1){ + $NameSpace = $ClientTypeName.Substring(0, $LastDotIndex) + $ClientTypeName = $ClientTypeName.Substring($LastDotIndex+1) + } + } + else { + # AutoRest generates client name with 'Client' appended to info title when a NameSpace part is same as the info name. + if($NameSpace.Split('.', [System.StringSplitOptions]::RemoveEmptyEntries) -contains $infoName) { + $ClientTypeName = $infoName + 'Client' + } + else { + $ClientTypeName = $infoName + } } return @{ InfoVersion = $infoVersion InfoTitle = $infoTitle InfoName = $infoName + ClientTypeName = $ClientTypeName Version = $ModuleVersion NameSpace = $NameSpace ModuleName = $ModuleName @@ -1369,10 +1411,9 @@ function Get-PathFunctionBody $DefinitionList = $swaggerDict['Definitions'] $UseAzureCsharpGenerator = $SwaggerMetaDict['UseAzureCsharpGenerator'] $infoVersion = $Info['infoVersion'] - $modulePostfix = $Info['infoName'] - $clientName = '$' + $modulePostfix + $clientName = '$' + $Info['ClientTypeName'] $NameSpace = $info.namespace - $fullModuleName = $Namespace + '.' + $modulePostfix + $FullClientTypeName = $Namespace + '.' + $Info['ClientTypeName'] $apiVersion = $null $SubscriptionId = $null $BaseUri = $null diff --git a/Tests/PSSwaggerScenario.Tests.ps1 b/Tests/PSSwaggerScenario.Tests.ps1 index b768bc2..41b5d6e 100644 --- a/Tests/PSSwaggerScenario.Tests.ps1 +++ b/Tests/PSSwaggerScenario.Tests.ps1 @@ -846,3 +846,211 @@ Describe "Header scenario tests" -Tag @('Header','ScenarioTest') { } } } + +Describe "Pre-compiled SDK Assmebly scenario tests" -Tag @('SDKAssembly','ScenarioTest') { + BeforeAll { + $PsSwaggerPath = Split-Path -Path $PSScriptRoot -Parent | Join-Path -ChildPath "PSSwagger" + Import-Module $PsSwaggerPath -Force + $SwaggerSpecPath = Join-Path -Path $PSScriptRoot -ChildPath 'Data' | Join-Path -ChildPath 'ParameterTypes' | Join-Path -ChildPath 'ParameterTypesSpec.json' + $GeneratedPath = Join-Path -Path $PSScriptRoot -ChildPath 'Generated' + $ModuleName = 'GeneratedModuleForSdkAssemblyScenario' + $GeneratedModuleBase = Join-Path -Path $GeneratedPath -ChildPath $ModuleName + if (Test-Path -Path $GeneratedModuleBase -PathType Container) { + Remove-Item -Path $GeneratedModuleBase -Recurse -Force + } + + # Generating the first version, so that PSSwagger generated the SDK Assembly, + # later this assembly will be used for testing the precompiled SDK assembly scenarios. + $ModuleVersion = '1.1.1.1' + $params = @{ + SpecificationPath = $SwaggerSpecPath + Name = $ModuleName + Version = $ModuleVersion + UseAzureCsharpGenerator = $true + Path = $GeneratedPath + ConfirmBootstrap = $true + Verbose = $true + } + Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $params -IncludeAssembly + + $GeneratedModuleRefPath = Join-Path -Path $GeneratedModuleBase -ChildPath $ModuleVersion | Join-Path -ChildPath ref + $GeneratedModuleFullClrPath = Join-Path -Path $GeneratedModuleRefPath -ChildPath fullclr + $NameSpace = "Microsoft.PowerShell.$ModuleName.v$("$ModuleVersion" -replace '\.','')" + $ClientTypeName = 'ParameterTypesSpec' + $AssemblyName = "$NameSpace.dll" + Test-Path -Path $GeneratedModuleFullClrPath -PathType Container | Should Be $true + } + + It 'Validate module generation using pre-compiled SDK assembly and full client type name' { + $ModuleVersion = '2.2.1.1' + $GeneratedModuleVersionPath = Join-Path -Path $GeneratedModuleBase -ChildPath $ModuleVersion + + if (Test-Path -Path $GeneratedModuleVersionPath -PathType Container) { + Remove-Item -Path $GeneratedModuleVersionPath -Recurse -Force + } + + $null = New-Item -Path $GeneratedModuleVersionPath -Type Directory -Force + Copy-Item -Path $GeneratedModuleRefPath -Destination $GeneratedModuleVersionPath -Recurse -Force + + $NewPSSwaggerModule_params = @{ + SpecificationPath = $SwaggerSpecPath + AssemblyFileName = $AssemblyName + ClientTypeName = "$NameSpace.$ClientTypeName" + ModelsName = 'Models' + Name = $ModuleName + Version = $ModuleVersion + UseAzureCsharpGenerator = $true + Path = $GeneratedPath + Verbose = $true + } + Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $NewPSSwaggerModule_params -ErrorVariable 'ev' -ErrorAction 'SilentlyContinue' + + $ev | Where-Object {$_.PSTypeNames -contains 'System.Management.Automation.ErrorRecord'} | Should BeNullOrEmpty + + # Test module manifest + $CurrentVersionManifestPath = Join-Path -Path $GeneratedModuleVersionPath -ChildPath "$ModuleName.psd1" + Test-ModuleManifest -Path $CurrentVersionManifestPath -ErrorAction SilentlyContinue -ErrorVariable 'ev2' | Should Not BeNullOrEmpty + $ev2 | Should BeNullOrEmpty + } + + It 'Validate module generation using pre-compiled SDK assembly without client type name' { + $ModuleVersion = '1.1.1.1' + $GeneratedModuleVersionPath = Join-Path -Path $GeneratedModuleBase -ChildPath $ModuleVersion + + $NewPSSwaggerModule_params = @{ + SpecificationPath = $SwaggerSpecPath + AssemblyFileName = $AssemblyName + Name = $ModuleName + Version = $ModuleVersion + UseAzureCsharpGenerator = $true + Path = $GeneratedPath + Verbose = $true + } + Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $NewPSSwaggerModule_params -ErrorVariable 'ev' -ErrorAction 'SilentlyContinue' + $ev | Where-Object {$_.PSTypeNames -contains 'System.Management.Automation.ErrorRecord'} | Should BeNullOrEmpty + + # Test module manifest + $CurrentVersionManifestPath = Join-Path -Path $GeneratedModuleVersionPath -ChildPath "$ModuleName.psd1" + Test-ModuleManifest -Path $CurrentVersionManifestPath -ErrorAction SilentlyContinue -ErrorVariable 'ev2' | Should Not BeNullOrEmpty + $ev2 | Should BeNullOrEmpty + } + + It 'Validate module generation using pre-compiled SDK assembly with client type name' { + $ModuleVersion = '1.1.1.1' + $GeneratedModuleVersionPath = Join-Path -Path $GeneratedModuleBase -ChildPath $ModuleVersion + + $NewPSSwaggerModule_params = @{ + SpecificationPath = $SwaggerSpecPath + AssemblyFileName = $AssemblyName + ClientTypeName = $ClientTypeName + Name = $ModuleName + Version = $ModuleVersion + UseAzureCsharpGenerator = $true + Path = $GeneratedPath + Verbose = $true + } + Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $NewPSSwaggerModule_params -ErrorVariable 'ev' -ErrorAction 'SilentlyContinue' + $ev | Where-Object {$_.PSTypeNames -contains 'System.Management.Automation.ErrorRecord'} | Should BeNullOrEmpty + + # Test module manifest + $CurrentVersionManifestPath = Join-Path -Path $GeneratedModuleVersionPath -ChildPath "$ModuleName.psd1" + Test-ModuleManifest -Path $CurrentVersionManifestPath -ErrorAction SilentlyContinue -ErrorVariable 'ev2' | Should Not BeNullOrEmpty + $ev2 | Should BeNullOrEmpty + } + + It 'Validate module generation of pre-compiled SDK assembly scenario with incorrect assembly file name' { + $ModuleVersion = '1.1.1.1' + $GeneratedModuleVersionPath = Join-Path -Path $GeneratedModuleBase -ChildPath $ModuleVersion + + $NewPSSwaggerModule_params = @{ + SpecificationPath = $SwaggerSpecPath + AssemblyFileName = "IncorrectAssemblyName.dll" + ClientTypeName = $ClientTypeName + Name = $ModuleName + Version = $ModuleVersion + UseAzureCsharpGenerator = $true + Path = $GeneratedPath + Verbose = $true + } + Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $NewPSSwaggerModule_params -ErrorVariable 'ev' -ErrorAction 'SilentlyContinue' + $ev.FullyQualifiedErrorId | Should Be 'AssemblyNotFound,New-PSSwaggerModule' + } + + It 'Should fail when client type name is not found in pre-compiled SDK assembly scenario' { + $ModuleVersion = '3.3.3.0' + $GeneratedModuleVersionPath = Join-Path -Path $GeneratedModuleBase -ChildPath $ModuleVersion + + if (Test-Path -Path $GeneratedModuleVersionPath -PathType Container) { + Remove-Item -Path $GeneratedModuleVersionPath -Recurse -Force + } + + $CurrentVersionRefPath = Join-Path -Path $GeneratedModuleVersionPath -ChildPath ref + $null = New-Item -Path $CurrentVersionRefPath -Type Directory -Force + Copy-Item -Path $GeneratedModuleFullClrPath -Destination $CurrentVersionRefPath -Recurse -Force + + $NewPSSwaggerModule_params = @{ + SpecificationPath = $SwaggerSpecPath + AssemblyFileName = $AssemblyName + ClientTypeName = $ClientTypeName + Name = $ModuleName + Version = $ModuleVersion + UseAzureCsharpGenerator = $true + Path = $GeneratedPath + Verbose = $true + } + Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $NewPSSwaggerModule_params -ErrorVariable 'ev' -ErrorAction 'SilentlyContinue' + $ev.FullyQualifiedErrorId | Should Be 'UnableToExtractDetailsFromSdkAssembly,Update-PathFunctionDetails' + } + + It 'Should fail when incorrect namespace in client type name is specified in pre-compiled SDK assembly scenario' { + $ModuleVersion = '3.3.3.1' + $GeneratedModuleVersionPath = Join-Path -Path $GeneratedModuleBase -ChildPath $ModuleVersion + + if (Test-Path -Path $GeneratedModuleVersionPath -PathType Container) { + Remove-Item -Path $GeneratedModuleVersionPath -Recurse -Force + } + + $CurrentVersionRefPath = Join-Path -Path $GeneratedModuleVersionPath -ChildPath ref + $null = New-Item -Path $CurrentVersionRefPath -Type Directory -Force + Copy-Item -Path $GeneratedModuleFullClrPath -Destination $CurrentVersionRefPath -Recurse -Force + + $NewPSSwaggerModule_params = @{ + SpecificationPath = $SwaggerSpecPath + AssemblyFileName = $AssemblyName + ClientTypeName = "Incorrect.NameSpec.$ClientTypeName" + Name = $ModuleName + Version = $ModuleVersion + UseAzureCsharpGenerator = $true + Path = $GeneratedPath + Verbose = $true + } + Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $NewPSSwaggerModule_params -ErrorVariable 'ev' -ErrorAction 'SilentlyContinue' + $ev.FullyQualifiedErrorId | Should Be 'UnableToExtractDetailsFromSdkAssembly,Update-PathFunctionDetails' + } + + It 'Should fail when incorrect client type name is specified in pre-compiled SDK assembly scenario' { + $ModuleVersion = '3.3.3.2' + $GeneratedModuleVersionPath = Join-Path -Path $GeneratedModuleBase -ChildPath $ModuleVersion + + if (Test-Path -Path $GeneratedModuleVersionPath -PathType Container) { + Remove-Item -Path $GeneratedModuleVersionPath -Recurse -Force + } + + $CurrentVersionRefPath = Join-Path -Path $GeneratedModuleVersionPath -ChildPath ref + $null = New-Item -Path $CurrentVersionRefPath -Type Directory -Force + Copy-Item -Path $GeneratedModuleFullClrPath -Destination $CurrentVersionRefPath -Recurse -Force + + $NewPSSwaggerModule_params = @{ + SpecificationPath = $SwaggerSpecPath + AssemblyFileName = $AssemblyName + ClientTypeName = 'IncorrectClientName' + Name = $ModuleName + Version = $ModuleVersion + UseAzureCsharpGenerator = $true + Path = $GeneratedPath + Verbose = $true + } + Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $NewPSSwaggerModule_params -ErrorVariable 'ev' -ErrorAction 'SilentlyContinue' + $ev.FullyQualifiedErrorId | Should Be 'UnableToExtractDetailsFromSdkAssembly,Update-PathFunctionDetails' + } +} diff --git a/Tests/TestUtilities.psm1 b/Tests/TestUtilities.psm1 index 4bed74d..c681dd1 100644 --- a/Tests/TestUtilities.psm1 +++ b/Tests/TestUtilities.psm1 @@ -59,6 +59,50 @@ function Test-Package { $package } +function Invoke-NewPSSwaggerModuleCommand { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [PSCustomObject] + $NewPSSwaggerModuleParameters, + + [Parameter(Mandatory = $false)] + [switch] + $IncludeAssembly + ) + + Initialize-PSSwaggerDependencies -AllFrameworks -AcceptBootstrap -Azure + + $NewPSSwaggerModuleParameters['ErrorAction'] = 'SilentlyContinue' + if ($IncludeAssembly) { + $NewPSSwaggerModuleParameters['NoAssembly'] = $false + $NewPSSwaggerModuleParameters['ConfirmBootstrap'] = $true + } + + if ((Get-Variable -Name PSEdition -ErrorAction Ignore) -and ('Core' -eq $PSEdition)) { + if ($IncludeAssembly) { + $NewPSSwaggerModuleParameters['IncludeCoreFxAssembly'] = $true + $NewPSSwaggerModuleParameters['PowerShellCorePath'] = Join-Path -Path $PSHOME -ChildPath 'PowerShell.exe' + } + + $ParametersString = '' + $NewPSSwaggerModuleParameters.GetEnumerator() | ForEach-Object { + if ($_.Value -eq $true) { + $ParametersString += " -$($_.Name)" + } + elseif ($_.Value -ne $false) { + $ParametersString += " -$($_.Name) '$($_.Value)'" + } + } + & "powershell.exe" -command "& { + `$env:PSModulePath=`$env:PSModulePath_Backup; + New-PSSwaggerModule $ParametersString + }" + } + else { + New-PSSwaggerModule @NewPSSwaggerModuleParameters + } +} function Initialize-Test { [CmdletBinding()] param( diff --git a/Tests/run-tests.ps1 b/Tests/run-tests.ps1 index 78b047c..097d03c 100644 --- a/Tests/run-tests.ps1 +++ b/Tests/run-tests.ps1 @@ -116,7 +116,7 @@ if ($TestSuite.Contains("All") -or $TestSuite.Contains("ScenarioTest")) { if (Test-Path -Path $generatedModulesPath -PathType Container) { Remove-Item -Path $generatedModulesPath -Recurse -Force } - $null = New-Item -Path $generatedModulesPath -ItemType Directory + $null = New-Item -Path $generatedModulesPath -ItemType Directory -Force } # Set up Microsoft.Net.Compilers @@ -152,8 +152,11 @@ if ($EnableTracing) { $srcPath = Join-Path -Path $PSScriptRoot -ChildPath .. | Join-Path -ChildPath PSSwagger $srcPath += [System.IO.Path]::DirectorySeparatorChar -$executeTestsCommand += ";`$verbosepreference=`"continue`";`$env:PSModulePath=`"$srcPath;`$env:PSModulePath`";Invoke-Pester -ExcludeTag KnownIssue -OutputFormat NUnitXml -OutputFile ScenarioTestResults.xml -Verbose" - +$executeTestsCommand += @" + ;`$verbosepreference=`'continue`'; + `$env:PSModulePath=`"$srcPath;`$env:PSModulePath`"; + Invoke-Pester -Script `'$PSScriptRoot`' -ExcludeTag KnownIssue -OutputFormat NUnitXml -OutputFile ScenarioTestResults.xml -Verbose; +"@ # Set up Pester params $pesterParams = @{'ExcludeTag' = 'KnownIssue'; 'OutputFormat' = 'NUnitXml'; 'OutputFile' = 'TestResults.xml'} if ($PSBoundParameters.ContainsKey('TestName')) { @@ -194,7 +197,7 @@ else { } # Verify output -$x = [xml](Get-Content -raw "ScenarioTestResults.xml") +$x = [xml](Get-Content -Raw (Join-Path -Path $PSScriptRoot -ChildPath 'ScenarioTestResults.xml')) if ([int]$x.'test-results'.failures -gt 0) { throw "$($x.'test-results'.failures) tests failed" } \ No newline at end of file diff --git a/docs/commands/New-PSSwaggerModule.md b/docs/commands/New-PSSwaggerModule.md index 277547a..a1612f7 100644 --- a/docs/commands/New-PSSwaggerModule.md +++ b/docs/commands/New-PSSwaggerModule.md @@ -11,7 +11,7 @@ PowerShell command to generate the PowerShell commands for a given RESTful Web S ## SYNTAX -### SwaggerPath +### SpecificationPath (Default) ``` New-PSSwaggerModule -SpecificationPath -Path -Name [-Version ] [-DefaultCommandPrefix ] [-Header ] [-UseAzureCsharpGenerator] [-NoAssembly] @@ -19,7 +19,21 @@ New-PSSwaggerModule -SpecificationPath -Path -Name [- [-SymbolPath ] [-ConfirmBootstrap] ``` -### SwaggerURI +### SdkAssemblyWithSpecificationPath +``` +New-PSSwaggerModule -SpecificationPath -Path -AssemblyFileName + [-ClientTypeName ] [-ModelsName ] -Name [-Version ] + [-DefaultCommandPrefix ] [-Header ] [-UseAzureCsharpGenerator] +``` + +### SdkAssemblyWithSpecificationUri +``` +New-PSSwaggerModule -SpecificationUri -Path -AssemblyFileName + [-ClientTypeName ] [-ModelsName ] -Name [-Version ] + [-DefaultCommandPrefix ] [-Header ] [-UseAzureCsharpGenerator] +``` + +### SpecificationUri ``` New-PSSwaggerModule -SpecificationUri -Path -Name [-Version ] [-DefaultCommandPrefix ] [-Header ] [-UseAzureCsharpGenerator] [-NoAssembly] @@ -53,7 +67,7 @@ Full Path to a Swagger based JSON spec. ```yaml Type: String -Parameter Sets: SwaggerPath +Parameter Sets: SpecificationPath, SdkAssemblyWithSpecificationPath Aliases: Required: True @@ -68,7 +82,7 @@ Uri to a Swagger based JSON spec. ```yaml Type: Uri -Parameter Sets: SwaggerURI +Parameter Sets: SdkAssemblyWithSpecificationUri, SpecificationUri Aliases: Required: True @@ -93,6 +107,59 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -AssemblyFileName +File name of the pre-compiled SDK assembly. +This assembly along with its dependencies should be available in '.\ref\fullclr\' folder under the target module version base path ($Path\$Name\$Version\\). +If your generated module needs to work on PowerShell Core, place the coreclr assembly along with its depdencies under '.\ref\coreclr\' folder under the target module version base path ($Path\$Name\$Version\\). +For FullClr, the specified assembly should be available at "$Path\$Name\$Version\ref\fullclr\$AssemblyFileName". +For CoreClr, the specified assembly should be available at "$Path\$Name\$Version\ref\coreclr\$AssemblyFileName". + +```yaml +Type: String +Parameter Sets: SdkAssemblyWithSpecificationPath, SdkAssemblyWithSpecificationUri +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ClientTypeName +Client type name in the pre-compiled SDK assembly. +Specify if client type name is different from the value of 'Title' field from the input specification, or +if client type namespace is different from the specified namespace in the specification. +It is recommended to specify the fully qualified client type name. + +```yaml +Type: String +Parameter Sets: SdkAssemblyWithSpecificationPath, SdkAssemblyWithSpecificationUri +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ModelsName +Models name if it is different from default value 'Models'. +It is recommended to specify the custom models name in using x-ms-code-generation-settings extension in specification. + +```yaml +Type: String +Parameter Sets: SdkAssemblyWithSpecificationPath, SdkAssemblyWithSpecificationUri +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Name Name of the module to be generated. A folder with this name will be created in the location specified by Path parameter. @@ -185,7 +252,7 @@ Switch to disable saving the precompiled module assembly and instead enable dyna ```yaml Type: SwitchParameter -Parameter Sets: (All) +Parameter Sets: SpecificationPath, SpecificationUri Aliases: Required: False @@ -201,7 +268,7 @@ Only required if PowerShell Core not installed via MSI in the default path. ```yaml Type: String -Parameter Sets: (All) +Parameter Sets: SpecificationPath, SpecificationUri Aliases: Required: False @@ -216,7 +283,7 @@ Switch to additionally compile the module's binary component for Core CLR. ```yaml Type: SwitchParameter -Parameter Sets: (All) +Parameter Sets: SpecificationPath, SpecificationUri Aliases: Required: False @@ -231,7 +298,7 @@ User wants to install local tools for all users. ```yaml Type: SwitchParameter -Parameter Sets: (All) +Parameter Sets: SpecificationPath, SpecificationUri Aliases: Required: False @@ -246,7 +313,7 @@ Switch to disable optimizations during build of full CLR binary component. ```yaml Type: SwitchParameter -Parameter Sets: (All) +Parameter Sets: SpecificationPath, SpecificationUri Aliases: Required: False @@ -262,7 +329,7 @@ Defaults to $Path\symbols. ```yaml Type: String -Parameter Sets: (All) +Parameter Sets: SpecificationPath, SpecificationUri Aliases: Required: False @@ -277,7 +344,7 @@ Automatically consent to downloading nuget.exe or NuGet packages as required. ```yaml Type: SwitchParameter -Parameter Sets: (All) +Parameter Sets: SpecificationPath, SpecificationUri Aliases: Required: False From 7a8f321ad44bbc3776e349cfaff854f0db6ae33b Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Wed, 13 Sep 2017 11:27:04 -0700 Subject: [PATCH 15/40] Add New-ServiceClient utility function in generated module to enable mock testing (#325) * Add Get-ServiceClient utility function in generated module to enable mock testing. Resolves #298 * Added documentation * Updated New-ServiceClient uility to set ServiceCredentials, SubscriptionId, BaseUri, HttpClientHandler and global parameter properties on the service client object. --- PSSwagger/GeneratedHelpers.ps1 | 15 ++-- PSSwagger/New-ServiceClient.ps1 | 110 ++++++++++++++++++++++++++++++ PSSwagger/PSSwagger.Constants.ps1 | 74 +++++++++++--------- PSSwagger/PSSwagger.psm1 | 3 +- PSSwagger/Paths.psm1 | 67 +++++++++--------- PSSwagger/SwaggerUtils.psm1 | 38 ++++++----- README.md | 3 + docs/testing/NewServiceClient.md | 6 ++ 8 files changed, 227 insertions(+), 89 deletions(-) create mode 100644 PSSwagger/New-ServiceClient.ps1 create mode 100644 docs/testing/NewServiceClient.md diff --git a/PSSwagger/GeneratedHelpers.ps1 b/PSSwagger/GeneratedHelpers.ps1 index 30552cb..27cc989 100644 --- a/PSSwagger/GeneratedHelpers.ps1 +++ b/PSSwagger/GeneratedHelpers.ps1 @@ -50,13 +50,16 @@ function Get-AzSubscriptionId param() $AzureContext = & "Get-AzureRmContext" -ErrorAction Stop - if(Get-Member -InputObject $AzureContext.Subscription -Name SubscriptionId) + if($AzureContext) { - return $AzureContext.Subscription.SubscriptionId - } - else - { - return $AzureContext.Subscription.Id + if(Get-Member -InputObject $AzureContext.Subscription -Name SubscriptionId) + { + return $AzureContext.Subscription.SubscriptionId + } + else + { + return $AzureContext.Subscription.Id + } } } diff --git a/PSSwagger/New-ServiceClient.ps1 b/PSSwagger/New-ServiceClient.ps1 new file mode 100644 index 0000000..1b84c90 --- /dev/null +++ b/PSSwagger/New-ServiceClient.ps1 @@ -0,0 +1,110 @@ +Microsoft.PowerShell.Core\Set-StrictMode -Version Latest + +<# +.DESCRIPTION + Creates Service Client object. + +.PARAMETER FullClientTypeName + Client type full name. + +.PARAMETER AddHttpClientHandler + Switch to determine whether the client type constructor expects the HttpClientHandler object. + +.PARAMETER Credential + Credential is required for for creating the HttpClientHandler object. + +.PARAMETER AuthenticationCommand + Command that should return a Microsoft.Rest.ServiceClientCredentials object that implements custom authentication logic. + +.PARAMETER AuthenticationCommandArgumentList + Arguments to the AuthenticationCommand, if any. + +.PARAMETER HostOverrideCommand + Command should return a custom hostname string. + Overrides the default host in the specification. + +.PARAMETER SubscriptionIdCommand + Custom command get SubscriptionId value. + +.PARAMETER GlobalParameterHashtable + Global parameters to be set on client object. +#> +function New-ServiceClient { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $FullClientTypeName, + + [Parameter(Mandatory = $false)] + [switch] + $AddHttpClientHandler, + + [Parameter(Mandatory = $false)] + [pscredential] + $Credential, + + [Parameter(Mandatory = $true)] + [string] + $AuthenticationCommand, + + [Parameter(Mandatory = $false)] + [Object[]] + $AuthenticationCommandArgumentList, + + [Parameter(Mandatory = $false)] + [string] + $HostOverrideCommand, + + [Parameter(Mandatory = $false)] + [string] + $SubscriptionIdCommand, + + [Parameter(Mandatory = $false)] + [PSCustomObject] + $GlobalParameterHashtable + ) + + $ClientArgumentList = @() + $InvokeCommand_parameters = @{ + ScriptBlock = [scriptblock]::Create($AuthenticationCommand) + } + if ($AuthenticationCommandArgumentList) { + $InvokeCommand_parameters['ArgumentList'] = $AuthenticationCommandArgumentList + } + $ClientArgumentList += Invoke-Command @InvokeCommand_parameters + + if ($AddHttpClientHandler) { + $httpClientHandler = New-HttpClientHandler -Credential $Credential + $ClientArgumentList += $httpClientHandler + } + + $delegatingHandler = New-Object -TypeName System.Net.Http.DelegatingHandler[] -ArgumentList 0 + $ClientArgumentList += $delegatingHandler + + $Client = New-Object -TypeName $FullClientTypeName -ArgumentList $ClientArgumentList + + if ($HostOverrideCommand) { + $Client.BaseUri = Invoke-Command -ScriptBlock [scriptblock]::Create($HostOverrideCommand) + } + + if ($GlobalParameterHashtable) { + $GlobalParameterHashtable.GetEnumerator() | ForEach-Object { + if (Get-Member -InputObject $Client -Name $_.Key -MemberType Property) { + if ((-not $_.Value) -and ($_.Key -eq 'SubscriptionId')) { + if($SubscriptionIdCommand) { + $Client.SubscriptionId = Invoke-Command -ScriptBlock [scriptblock]::Create($SubscriptionIdCommand) + } + else { + $Client.SubscriptionId = Get-AzSubscriptionId + } + } + else { + $Client."$($_.Key)" = $_.Value + } + } + } + } + + return $Client +} diff --git a/PSSwagger/PSSwagger.Constants.ps1 b/PSSwagger/PSSwagger.Constants.ps1 index 06b0d5c..1a4094e 100644 --- a/PSSwagger/PSSwagger.Constants.ps1 +++ b/PSSwagger/PSSwagger.Constants.ps1 @@ -53,6 +53,7 @@ $DynamicAssemblyGenerationCode `$allDllsPath = Join-Path -Path `$ClrPath -ChildPath '*.dll' Get-ChildItem -Path `$allDllsPath -File | ForEach-Object { Add-Type -Path `$_.FullName -ErrorAction SilentlyContinue } +. (Join-Path -Path `$PSScriptRoot -ChildPath 'New-ServiceClient.ps1') . (Join-Path -Path `$PSScriptRoot -ChildPath 'GeneratedHelpers.ps1') `$allPs1FilesPath = Join-Path -Path `$PSScriptRoot -ChildPath '$GeneratedCommandsName' | Join-Path -ChildPath '*.ps1' @@ -157,11 +158,49 @@ $constructFlattenedParameter = @' $functionBodyStr = @' `$ErrorActionPreference = 'Stop' - $securityBlock - $clientName = New-Object -TypeName $FullClientTypeName -ArgumentList $clientArgumentList$apiVersion - $overrideBaseUriBlock - $GlobalParameterBlock + `$NewServiceClient_params = @{ + FullClientTypeName = '$FullClientTypeName' + } +$( +if($AuthenticationCommand){ +" + `$NewServiceClient_params['AuthenticationCommand'] = @' + $AuthenticationCommand +`'@ " + if($AuthenticationCommandArgumentName){ +" + `$NewServiceClient_params['AuthenticationCommandArgumentList'] = `$$AuthenticationCommandArgumentName" + } +} +if($AddHttpClientHandler){ +" + `$NewServiceClient_params['AddHttpClientHandler'] = `$true + `$NewServiceClient_params['Credential'] = `$Credential" +} +if($hostOverrideCommand){ +" + `$NewServiceClient_params['HostOverrideCommand'] = @' + $hostOverrideCommand +`'@" +} +if($GlobalParameters) { +' + $GlobalParameterHashtable = @{} ' + + foreach($parameter in $GlobalParameters) { +" + `$GlobalParameterHashtable['$parameter'] = `$null + if(`$PSBoundParameters.ContainsKey('$parameter')) { + `$GlobalParameterHashtable['$parameter'] = `$PSBoundParameters['$parameter'] + } +" + } +" + `$NewServiceClient_params['GlobalParameterHashtable'] = `$GlobalParameterHashtable " +} +) + $clientName = New-ServiceClient @NewServiceClient_params $oDataExpressionBlock $parameterGroupsExpressionBlock $flattenedParametersBlock @@ -174,16 +213,6 @@ $functionBodyStr = @' } '@ -$clientArgumentListNoHandler = "`$serviceCredentials,`$delegatingHandler" -$clientArgumentListHttpClientHandler = "`$serviceCredentials,`$httpClientHandler,`$delegatingHandler" - -$securityBlockStr = @' -`$serviceCredentials = $authFunctionCall - $azSubscriptionIdBlock - $httpClientHandlerCall - `$delegatingHandler = New-Object -TypeName System.Net.Http.DelegatingHandler[] 0 -'@ - $parameterSetBasedMethodStrIfCase = @' if ('$operationId' -eq `$PsCmdlet.ParameterSetName) { $additionalConditionStart$methodBlock$additionalConditionEnd @@ -428,23 +457,6 @@ $createObjectStr = @' return `$Object '@ -$ApiVersionStr = @' - - if(Get-Member -InputObject $clientName -Name 'ApiVersion' -MemberType Property) - { - $clientName.ApiVersion = "$infoVersion" - } -'@ - -$GlobalParameterBlockStr = @' - if(Get-Member -InputObject `$clientName -Name '$globalParameterName' -MemberType Property) - { - `$clientName.$globalParameterName = $globalParameterValue - } -'@ - -$HostOverrideBlock = '`$ResourceManagerUrl = $hostOverrideCommand`n $clientName.BaseUri = `$ResourceManagerUrl' - $GeneratedCommandsName = 'Generated.PowerShell.Commands' $FormatViewDefinitionStr = @' diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index 0178318..81bdc62 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -612,9 +612,10 @@ function New-PSSwaggerModule -PSHeaderComment $PSHeaderComment $CopyFilesMap = [ordered]@{ - 'GeneratedHelpers.ps1' = 'GeneratedHelpers.ps1' + 'GeneratedHelpers.ps1' = 'GeneratedHelpers.ps1' 'Test-CoreRequirements.ps1' = 'Test-CoreRequirements.ps1' 'Test-FullRequirements.ps1' = 'Test-FullRequirements.ps1' + 'New-ServiceClient.ps1' = 'New-ServiceClient.ps1' } if (-not $AssemblyFileName) { diff --git a/PSSwagger/Paths.psm1 b/PSSwagger/Paths.psm1 index d0d52cf..4806794 100644 --- a/PSSwagger/Paths.psm1 +++ b/PSSwagger/Paths.psm1 @@ -425,7 +425,7 @@ function New-SwaggerPath $parametersToAdd = @{} $flattenedParametersOnPSCmdlet = @{} $parameterHitCount = @{} - $globalParameterBlock = '' + $globalParameters = @() $x_ms_pageableObject = $null foreach ($parameterSetDetail in $parameterSetDetails) { if ($parameterSetDetail.ContainsKey('x-ms-pageable') -and $parameterSetDetail.'x-ms-pageable' -and (-not $isNextPageOperation)) { @@ -505,7 +505,7 @@ function New-SwaggerPath $globalParameterValue = $parameterDetails.ConstantValue } - $globalParameterBlock += [Environment]::NewLine + $executionContext.InvokeCommand.ExpandString($GlobalParameterBlockStr) + $globalParameters += $globalParameterName } } @@ -609,24 +609,24 @@ function New-SwaggerPath } # Process security section - $azSubscriptionIdBlock = "" - $authFunctionCall = "" - $overrideBaseUriBlock = "" - $httpClientHandlerCall = "" + $SubscriptionIdCommand = "" + $AuthenticationCommand = "" + $AuthenticationCommandArgumentName = '' + $hostOverrideCommand = '' + $AddHttpClientHandler = $false $securityParametersToAdd = @() $PowerShellCodeGen = $SwaggerMetaDict['PowerShellCodeGen'] if (($PowerShellCodeGen['ServiceType'] -eq 'azure') -or ($PowerShellCodeGen['ServiceType'] -eq 'azure_stack')) { - $azSubscriptionIdBlock = "`$subscriptionId = Get-AzSubscriptionId" + $SubscriptionIdCommand = 'Get-AzSubscriptionId' } if ($PowerShellCodeGen['CustomAuthCommand']) { - $authFunctionCall = $PowerShellCodeGen['CustomAuthCommand'] + $AuthenticationCommand = $PowerShellCodeGen['CustomAuthCommand'] } if ($PowerShellCodeGen['HostOverrideCommand']) { $hostOverrideCommand = $PowerShellCodeGen['HostOverrideCommand'] - $overrideBaseUriBlock = $executionContext.InvokeCommand.ExpandString($HostOverrideBlock) } # If the auth function hasn't been set by metadata, try to discover it from the security and securityDefinition objects in the spec - if (-not $authFunctionCall) { + if (-not $AuthenticationCommand) { if ($FunctionDetails.ContainsKey('Security')) { # For now, just take the first security object if ($FunctionDetails.Security.Count -gt 1) { @@ -674,11 +674,12 @@ function New-SwaggerPath } # If the service is specified to not issue authentication challenges, we can't rely on HttpClientHandler if ($PowerShellCodeGen['NoAuthChallenge'] -and ($PowerShellCodeGen['NoAuthChallenge'] -eq $true)) { - $authFunctionCall = 'Get-AutoRestCredential -Credential $Credential' + $AuthenticationCommand = 'param([pscredential]$Credential) Get-AutoRestCredential -Credential $Credential' + $AuthenticationCommandArgumentName = 'Credential' } else { # Use an empty service client credentials object because we're using HttpClientHandler instead - $authFunctionCall = 'Get-AutoRestCredential' - $httpClientHandlerCall = '$httpClientHandler = New-HttpClientHandler -Credential $Credential' + $AuthenticationCommand = 'Get-AutoRestCredential' + $AddHttpClientHandler = $true } } elseif ($type -eq 'apiKey') { if (-not (Get-Member -InputObject $securityDefinition -Name 'name')) { @@ -712,7 +713,8 @@ function New-SwaggerPath Parameter = $credentialParameter IsConflictingWithOperationParameter = $false } - $authFunctionCall = "Get-AutoRestCredential -APIKey `$APIKey -Location '$in' -Name '$name'" + $AuthenticationCommand = "param([string]`$APIKey) Get-AutoRestCredential -APIKey `$APIKey -Location '$in' -Name '$name'" + $AuthenticationCommandArgumentName = 'APIKey' } else { Write-Warning -Message ($LocalizedData.UnsupportedAuthenticationType -f ($type)) } @@ -720,14 +722,9 @@ function New-SwaggerPath } } - if (-not $authFunctionCall) { + if (-not $AuthenticationCommand) { # At this point, there was no supported security object or overridden auth function, so assume no auth - $authFunctionCall = 'Get-AutoRestCredential' - } - - $clientArgumentList = $clientArgumentListNoHandler - if ($httpClientHandlerCall) { - $clientArgumentList = $clientArgumentListHttpClientHandler + $AuthenticationCommand = 'Get-AutoRestCredential' } $nonUniqueParameterSets = @() @@ -961,18 +958,22 @@ function New-SwaggerPath } $functionBodyParams = @{ - ParameterSetDetails = $parameterSetDetails - ODataExpressionBlock = $oDataExpressionBlock - ParameterGroupsExpressionBlock = $parameterGroupsExpressionBlock - GlobalParameterBlock = $GlobalParameterBlock - SwaggerDict = $SwaggerDict - SwaggerMetaDict = $SwaggerMetaDict - SecurityBlock = $executionContext.InvokeCommand.ExpandString($securityBlockStr) - OverrideBaseUriBlock = $overrideBaseUriBlock - ClientArgumentList = $clientArgumentList - FlattenedParametersOnPSCmdlet = $flattenedParametersOnPSCmdlet - } - + ParameterSetDetails = $parameterSetDetails + ODataExpressionBlock = $oDataExpressionBlock + ParameterGroupsExpressionBlock = $parameterGroupsExpressionBlock + SwaggerDict = $SwaggerDict + SwaggerMetaDict = $SwaggerMetaDict + AddHttpClientHandler = $AddHttpClientHandler + HostOverrideCommand = $hostOverrideCommand + AuthenticationCommand = $AuthenticationCommand + AuthenticationCommandArgumentName = $AuthenticationCommandArgumentName + SubscriptionIdCommand = $SubscriptionIdCommand + FlattenedParametersOnPSCmdlet = $flattenedParametersOnPSCmdlet + } + if($globalParameters) { + $functionBodyParams['GlobalParameters'] = $globalParameters + } + $pathGenerationPhaseResult = Get-PathFunctionBody @functionBodyParams $bodyObject = $pathGenerationPhaseResult.BodyObject diff --git a/PSSwagger/SwaggerUtils.psm1 b/PSSwagger/SwaggerUtils.psm1 index dc4396d..1b01a69 100644 --- a/PSSwagger/SwaggerUtils.psm1 +++ b/PSSwagger/SwaggerUtils.psm1 @@ -1373,10 +1373,9 @@ function Get-PathFunctionBody [AllowEmptyString()] $ParameterGroupsExpressionBlock, - [Parameter(Mandatory=$true)] - [string] - [AllowEmptyString()] - $GlobalParameterBlock, + [Parameter(Mandatory=$false)] + [string[]] + $GlobalParameters, [Parameter(Mandatory=$true)] [PSCustomObject] @@ -1386,22 +1385,29 @@ function Get-PathFunctionBody [PSCustomObject] $SwaggerMetaDict, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory=$false)] + [switch] + $AddHttpClientHandler, + + [Parameter(Mandatory=$false)] [string] - $SecurityBlock, + $HostOverrideCommand, [Parameter(Mandatory=$true)] [string] - [AllowEmptyString()] - $OverrideBaseUriBlock, - - [Parameter(Mandatory=$true)] - [PSCustomObject] - $FlattenedParametersOnPSCmdlet, + $AuthenticationCommand, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory=$false)] [string] - $ClientArgumentList + $AuthenticationCommandArgumentName, + + [Parameter(Mandatory=$false)] + [string] + $SubscriptionIdCommand, + + [Parameter(Mandatory=$true)] + [PSCustomObject] + $FlattenedParametersOnPSCmdlet ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState @@ -1414,16 +1420,12 @@ function Get-PathFunctionBody $clientName = '$' + $Info['ClientTypeName'] $NameSpace = $info.namespace $FullClientTypeName = $Namespace + '.' + $Info['ClientTypeName'] - $apiVersion = $null $SubscriptionId = $null $BaseUri = $null $GetServiceCredentialStr = '' $AdvancedFunctionEndCodeBlock = '' $GetServiceCredentialStr = 'Get-AzServiceCredential' - # Expanding again expands $clientName - $GlobalParameterBlock = $executionContext.InvokeCommand.ExpandString($GlobalParameterBlock) - $parameterSetBasedMethodStr = '' foreach ($parameterSetDetail in $ParameterSetDetails) { # Responses isn't actually used right now, but keeping this when we need to handle responses per parameter set diff --git a/README.md b/README.md index 85d8412..ca889a0 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,9 @@ A PowerShell module with commands to generate the PowerShell commands for a give ## Customizing PowerShell Metadata - [PowerShell Extensions](/docs/extensions/readme.md) +## Testing generated module +- [Mocking New-ServiceClient utility](/docs/testing/NewServiceClient.md) + ## Supported Platforms | Usage | Platforms | | ----------------| ------------------------------------- | diff --git a/docs/testing/NewServiceClient.md b/docs/testing/NewServiceClient.md new file mode 100644 index 0000000..4a37cb9 --- /dev/null +++ b/docs/testing/NewServiceClient.md @@ -0,0 +1,6 @@ +# Mocking New-ServiceClient utility function +- Implement your own New-ServiceClient function with mock/customized functionality in MockedNewServiceClient.ps1 +- Either rename or backup the New-ServiceClient.ps1 file under generated module base folder. +- Copy MockedNewServiceClient.ps1 to module base folder as New-ServiceClient.ps1 file. +- Invoke your tests to validate the cmdlets from generated module. +- Restore the original New-ServiceClient.ps1 under module base folder. From 90ed6d0e6bcf61a1b35b51a22f448ed7435c0b31 Mon Sep 17 00:00:00 2001 From: brywang-msft Date: Wed, 13 Sep 2017 13:47:03 -0700 Subject: [PATCH 16/40] Write exceptions in generated commands (#324) * Write exceptions in generated commands, some fixes for core CLR test pass --- PSSwagger/PSSwagger.Constants.ps1 | 20 ++++++++- PSSwagger/PSSwagger.psm1 | 5 ++- Tests/PSSwaggerScenario.Tests.ps1 | 19 +++++---- Tests/TestUtilities.psm1 | 69 ++++++++++++++++++++++++++++--- Tests/run-tests.ps1 | 8 +++- 5 files changed, 104 insertions(+), 17 deletions(-) diff --git a/PSSwagger/PSSwagger.Constants.ps1 b/PSSwagger/PSSwagger.Constants.ps1 index 1a4094e..d0cca21 100644 --- a/PSSwagger/PSSwagger.Constants.ps1 +++ b/PSSwagger/PSSwagger.Constants.ps1 @@ -51,7 +51,9 @@ else { `$ClrPath = Join-Path -Path `$PSScriptRoot -ChildPath 'ref' | Join-Path -ChildPath `$clr $DynamicAssemblyGenerationCode `$allDllsPath = Join-Path -Path `$ClrPath -ChildPath '*.dll' -Get-ChildItem -Path `$allDllsPath -File | ForEach-Object { Add-Type -Path `$_.FullName -ErrorAction SilentlyContinue } +if (Test-Path -Path `$ClrPath -PathType Container) { + Get-ChildItem -Path `$allDllsPath -File | ForEach-Object { Add-Type -Path `$_.FullName -ErrorAction SilentlyContinue } +} . (Join-Path -Path `$PSScriptRoot -ChildPath 'New-ServiceClient.ps1') . (Join-Path -Path `$PSScriptRoot -ChildPath 'GeneratedHelpers.ps1') @@ -247,7 +249,21 @@ $getTaskResultBlock = @' if(`$taskResult.IsFaulted) { Write-Verbose -Message 'Operation failed.' - Throw "`$(`$taskResult.Exception.InnerExceptions | Out-String)" + if (`$taskResult.Exception) + { + if ((Get-Member -InputObject `$taskResult.Exception -Name 'InnerExceptions') -and `$taskResult.Exception.InnerExceptions) + { + foreach (`$ex in `$taskResult.Exception.InnerExceptions) + { + Write-Error -Exception `$ex + } + } elseif ((Get-Member -InputObject `$taskResult.Exception -Name 'InnerException') -and `$taskResult.Exception.InnerException) + { + Write-Error -Exception `$taskResult.Exception.InnerException + } else { + Write-Error -Exception `$taskResult.Exception + } + } } elseif (`$taskResult.IsCanceled) { diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index 81bdc62..fbe9cfd 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -779,6 +779,7 @@ function ConvertTo-CsharpCode $tempCodeGenSettingsPath = '' # Latest AutoRest inconsistently appends 'Client' to the specified infoName to generated the client name. # We need to override the client name to ensure that generated PowerShell cmdlets work fine. + # Note: -ClientName doesn't seem to work for legacy invocation $ClientName = $info['ClientTypeName'] try { if ($info.ContainsKey('CodeGenFileRequired') -and $info.CodeGenFileRequired) { @@ -833,7 +834,6 @@ function ConvertTo-CsharpCode Write-Verbose -Message $LocalizedData.InvokingAutoRestWithParams Write-Verbose -Message $($autoRestParams | Out-String) - $autorestMessages = & AutoRest $autoRestParams if ($autorestMessages) { Write-Verbose -Message $($autorestMessages | Out-String) @@ -925,7 +925,9 @@ function ConvertTo-CsharpCode $dependencies = Get-PSSwaggerExternalDependencies -Azure:$codeCreatedByAzureGenerator -Framework 'netstandard1' $microsoftRestClientRuntimeAzureRequiredVersion = if ($dependencies.ContainsKey('Microsoft.Rest.ClientRuntime.Azure')) { $dependencies['Microsoft.Rest.ClientRuntime.Azure'].RequiredVersion } else { '' } + # In some cases, PSCore doesn't inherit this process's PSModulePath $command = @" + `$env:PSModulePath = `"$env:PSModulePath`"; `$AddPSSwaggerClientType_params = @{ OutputAssemblyName = '$outAssembly' ClrPath = '$clrPath' @@ -936,6 +938,7 @@ function ConvertTo-CsharpCode CodeCreatedByAzureGenerator = `$$codeCreatedByAzureGenerator BootstrapConsent = `$$UserConsent } + `$env:PSModulePath += ';$env:PSModulePath' PSSwaggerUtility\Add-PSSwaggerClientType @AddPSSwaggerClientType_params "@ $success = & "$PowerShellCorePath" -command "& {$command}" diff --git a/Tests/PSSwaggerScenario.Tests.ps1 b/Tests/PSSwaggerScenario.Tests.ps1 index 41b5d6e..c993cef 100644 --- a/Tests/PSSwaggerScenario.Tests.ps1 +++ b/Tests/PSSwaggerScenario.Tests.ps1 @@ -512,7 +512,7 @@ Describe "Composite Swagger Tests" -Tag @('Composite','ScenarioTest') { Import-Module $PsSwaggerPath -Force if((Get-Variable -Name PSEdition -ErrorAction Ignore) -and ('Core' -eq $PSEdition)) { - & "powershell.exe" -command "& {`$env:PSModulePath=`$env:PSModulePath_Backup; + & "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -command "& {`$env:PSModulePath=`$env:PSModulePath_Backup; Import-Module (Join-Path `"$PsSwaggerPath`" `"PSSwagger.psd1`") -Force -ArgumentList `$true; New-PSSwaggerModule -SpecificationPath $SwaggerSpecPath -Name $ModuleName -UseAzureCsharpGenerator -Path $Path -NoAssembly -Verbose -ConfirmBootstrap; }" @@ -521,6 +521,11 @@ Describe "Composite Swagger Tests" -Tag @('Composite','ScenarioTest') { } $ModulePath = Join-Path -Path $Path -ChildPath $ModuleName + # Destroy the full and core CLR requirements so that AzureRM modules aren't required + # For now, composite swagger specs don't work without the -UseAzureCsharpGenerator flag because of AutoRest naming inconsistency + "" | Out-File -FilePath (Join-Path -Path $ModulePath -ChildPath "0.0.1" | Join-Path -ChildPath "Test-CoreRequirements.ps1") + "" | Out-File -FilePath (Join-Path -Path $ModulePath -ChildPath "0.0.1" | Join-Path -ChildPath "Test-FullRequirements.ps1") + Get-Module -ListAvailable -Name $ModulePath | Should BeOfType 'System.Management.Automation.PSModuleInfo' # Import generated module @@ -807,7 +812,7 @@ Describe "Header scenario tests" -Tag @('Header','ScenarioTest') { $GeneratedModuleVersionPath = Join-Path -Path $GeneratedModuleBase -ChildPath $ModuleVersion $Header = '__Custom_HEADER_Content__' if((Get-Variable -Name PSEdition -ErrorAction Ignore) -and ('Core' -eq $PSEdition)) { - & "powershell.exe" -command "& {`$env:PSModulePath=`$env:PSModulePath_Backup; + & "$env:SystemRoot\System32\WindowsPowerShell\v1.0\PowerShell.exe" -command "& {`$env:PSModulePath=`$env:PSModulePath_Backup; Import-Module '$PsSwaggerPath' -Force -ArgumentList `$true; New-PSSwaggerModule -SpecificationPath '$SwaggerSpecPath' -Name $ModuleName -Version '$ModuleVersion' -UseAzureCsharpGenerator -Path '$GeneratedPath' -NoAssembly -Verbose -ConfirmBootstrap -Header '$Header'; }" @@ -828,7 +833,7 @@ Describe "Header scenario tests" -Tag @('Header','ScenarioTest') { $ModuleVersion = '2.2.2.2' $GeneratedModuleVersionPath = Join-Path -Path $GeneratedModuleBase -ChildPath $ModuleVersion if((Get-Variable -Name PSEdition -ErrorAction Ignore) -and ('Core' -eq $PSEdition)) { - & "powershell.exe" -command "& {`$env:PSModulePath=`$env:PSModulePath_Backup; + & "$env:SystemRoot\System32\WindowsPowerShell\v1.0\PowerShell.exe" -command "& {`$env:PSModulePath=`$env:PSModulePath_Backup; Import-Module '$PsSwaggerPath' -Force -ArgumentList `$true; New-PSSwaggerModule -SpecificationPath '$SwaggerSpecPath' -Name $ModuleName -Version '$ModuleVersion' -UseAzureCsharpGenerator -Path '$GeneratedPath' -NoAssembly -Verbose -ConfirmBootstrap; }" @@ -973,7 +978,7 @@ Describe "Pre-compiled SDK Assmebly scenario tests" -Tag @('SDKAssembly','Scenar Verbose = $true } Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $NewPSSwaggerModule_params -ErrorVariable 'ev' -ErrorAction 'SilentlyContinue' - $ev.FullyQualifiedErrorId | Should Be 'AssemblyNotFound,New-PSSwaggerModule' + (Remove-TestErrorId -FullyQualifiedErrorId $ev.FullyQualifiedErrorId) | Should Be 'AssemblyNotFound,New-PSSwaggerModule' } It 'Should fail when client type name is not found in pre-compiled SDK assembly scenario' { @@ -999,7 +1004,7 @@ Describe "Pre-compiled SDK Assmebly scenario tests" -Tag @('SDKAssembly','Scenar Verbose = $true } Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $NewPSSwaggerModule_params -ErrorVariable 'ev' -ErrorAction 'SilentlyContinue' - $ev.FullyQualifiedErrorId | Should Be 'UnableToExtractDetailsFromSdkAssembly,Update-PathFunctionDetails' + (Remove-TestErrorId -FullyQualifiedErrorId $ev.FullyQualifiedErrorId) | Should Be 'UnableToExtractDetailsFromSdkAssembly,Update-PathFunctionDetails' } It 'Should fail when incorrect namespace in client type name is specified in pre-compiled SDK assembly scenario' { @@ -1025,7 +1030,7 @@ Describe "Pre-compiled SDK Assmebly scenario tests" -Tag @('SDKAssembly','Scenar Verbose = $true } Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $NewPSSwaggerModule_params -ErrorVariable 'ev' -ErrorAction 'SilentlyContinue' - $ev.FullyQualifiedErrorId | Should Be 'UnableToExtractDetailsFromSdkAssembly,Update-PathFunctionDetails' + (Remove-TestErrorId -FullyQualifiedErrorId $ev.FullyQualifiedErrorId) | Should Be 'UnableToExtractDetailsFromSdkAssembly,Update-PathFunctionDetails' } It 'Should fail when incorrect client type name is specified in pre-compiled SDK assembly scenario' { @@ -1051,6 +1056,6 @@ Describe "Pre-compiled SDK Assmebly scenario tests" -Tag @('SDKAssembly','Scenar Verbose = $true } Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $NewPSSwaggerModule_params -ErrorVariable 'ev' -ErrorAction 'SilentlyContinue' - $ev.FullyQualifiedErrorId | Should Be 'UnableToExtractDetailsFromSdkAssembly,Update-PathFunctionDetails' + (Remove-TestErrorId -FullyQualifiedErrorId $ev.FullyQualifiedErrorId) | Should Be 'UnableToExtractDetailsFromSdkAssembly,Update-PathFunctionDetails' } } diff --git a/Tests/TestUtilities.psm1 b/Tests/TestUtilities.psm1 index c681dd1..d9d2781 100644 --- a/Tests/TestUtilities.psm1 +++ b/Tests/TestUtilities.psm1 @@ -93,16 +93,76 @@ function Invoke-NewPSSwaggerModuleCommand { elseif ($_.Value -ne $false) { $ParametersString += " -$($_.Name) '$($_.Value)'" } - } - & "powershell.exe" -command "& { + } + # For now this hides Exception and ErrorObject + $command = "& { `$env:PSModulePath=`$env:PSModulePath_Backup; - New-PSSwaggerModule $ParametersString + New-PSSwaggerModule $ParametersString -ErrorVariable evtemp + foreach (`$ev in `$evtemp) { + `$returnEv = `$false + `$evEncoded = 'ErrorVariable: ' + if (`$ev.FullyQualifiedErrorId) { + `$evEncoded += 'ErrorId=' + `$evEncoded += `$ev.FullyQualifiedErrorId + `$evEncoded += ';' + `$returnEv = `$true + } + + if (`$ev.CategoryInfo) { + `$evEncoded += 'ErrorCategory=' + `$evEncoded += `$ev.CategoryInfo.Category + `$evEncoded += ';' + `$returnEv = `$true + } + + if (`$returnEv) { + `$evEncoded + } + } }" + $result = & "$env:SystemRoot\System32\WindowsPowerShell\v1.0\PowerShell.exe" -command $command + if ($PSBoundParameters.ContainsKey("ErrorVariable")) { + foreach ($resultLine in $result) { + if ($resultLine.StartsWith("ErrorVariable: ")) { + $errorVariableInfoTokenPairs = $resultLine.Substring(15).Split(';') + $errorId = '' + $errorCategory = '' + for ($i = 0; $i -lt $errorVariableInfoTokenPairs.Length; $i++) { + $errorVariableInfoTokens = $errorVariableInfoTokenPairs[$i].Split('=') + if ($errorVariableInfoTokens[0] -eq 'ErrorId') { + $errorId = $errorVariableInfoTokens[1] + } elseif ($errorVariableInfoTokens[0] -eq 'ErrorCategory') { + $errorCategory = $errorVariableInfoTokens[1] + } + } + + Write-Error -Message 'New-PSSwaggerModule remote error' -ErrorId $errorId -Category $errorCategory + } + } + } + $result } else { New-PSSwaggerModule @NewPSSwaggerModuleParameters } } + +function Remove-TestErrorId { + [CmdletBinding()] + param( + [string]$FullyQualifiedErrorId + ) + + $errorIds = $FullyQualifiedErrorId.Split(',') + $NewFullyQualifiedErrorId = '' + for ($i = 0; $i -lt $errorIds.Length; $i++) { + if ($errorIds[$i] -ne 'Invoke-NewPSSwaggerModuleCommand') { + $NewFullyQualifiedErrorId += "$($errorIds[$i])," + } + } + + return $NewFullyQualifiedErrorId.Substring(0, $NewFullyQualifiedErrorId.Length - 1) +} function Initialize-Test { [CmdletBinding()] param( @@ -132,9 +192,8 @@ function Initialize-Test { # Module generation part needs to happen in full powershell Write-Verbose "Generating module" if((Get-Variable -Name PSEdition -ErrorAction Ignore) -and ('Core' -eq $PSEdition)) { - & "powershell.exe" -command "& {`$env:PSModulePath=`$env:PSModulePath_Backup; + & "$env:SystemRoot\System32\WindowsPowerShell\v1.0\PowerShell.exe" -command "& {`$env:PSModulePath=`$env:PSModulePath_Backup; Import-Module (Join-Path `"$PsSwaggerPath`" `"PSSwagger.psd1`") -Force; - Import-Module (Join-Path `"$PsSwaggerPath`" `"PSSwaggerUtility`") -Force; Initialize-PSSwaggerDependencies -AllFrameworks -AcceptBootstrap -Azure:`$$UseAzureCSharpGenerator; New-PSSwaggerModule -SpecificationPath (Join-Path -Path `"$testCaseDataLocation`" -ChildPath $TestSpecFileName) -Path "$generatedModulesPath" -Name $GeneratedModuleName -Verbose -NoAssembly -UseAzureCSharpGenerator:`$$UseAzureCSharpGenerator -ConfirmBootstrap; }" diff --git a/Tests/run-tests.ps1 b/Tests/run-tests.ps1 index 097d03c..6440b44 100644 --- a/Tests/run-tests.ps1 +++ b/Tests/run-tests.ps1 @@ -135,6 +135,10 @@ if (-not $azureRmProfile) { } $powershellFolder = $null +$srcPath = Join-Path -Path $PSScriptRoot -ChildPath .. | Join-Path -ChildPath PSSwagger +$srcPath += [System.IO.Path]::DirectorySeparatorChar +$modulePath = Join-Path -Path $PSScriptRoot -ChildPath .. +$modulePath += [System.IO.Path]::DirectorySeparatorChar if ("netstandard1.7" -eq $TestFramework) { # beta > alpha $powershellCore = Get-Package -Name PowerShell* -ProviderName msi | Sort-Object -Property Name -Descending | Select-Object -First 1 -ErrorAction Ignore @@ -143,7 +147,7 @@ if ("netstandard1.7" -eq $TestFramework) { } $psVersion = $powershellCore.Name.Substring(11) $powershellFolder = "$Env:ProgramFiles\PowerShell\$($psVersion)" - $executeTestsCommand += ";`$env:PSModulePath_Backup=`"$env:PSModulePath`"" + $executeTestsCommand += ";`$env:PSModulePath_Backup=`"$srcPath;$modulePath;$env:PSModulePath`"" } if ($EnableTracing) { @@ -154,7 +158,7 @@ $srcPath = Join-Path -Path $PSScriptRoot -ChildPath .. | Join-Path -ChildPath PS $srcPath += [System.IO.Path]::DirectorySeparatorChar $executeTestsCommand += @" ;`$verbosepreference=`'continue`'; - `$env:PSModulePath=`"$srcPath;`$env:PSModulePath`"; + `$env:PSModulePath=`"$srcPath;$modulePath;`$env:PSModulePath`"; Invoke-Pester -Script `'$PSScriptRoot`' -ExcludeTag KnownIssue -OutputFormat NUnitXml -OutputFile ScenarioTestResults.xml -Verbose; "@ # Set up Pester params From 31e3236138674cab0fce6be0ee4a6e92c7be00a1 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Fri, 15 Sep 2017 13:11:05 -0700 Subject: [PATCH 17/40] Update taskresult handling logic to give priority to the $taskResult.Result.Body. (#326) Couple of other minor fixes found while generating modules for the Azure Swagger specs. Resolves #319. --- PSSwagger/PSSwagger.Constants.ps1 | 42 ++++++++++++++++--------------- PSSwagger/SwaggerUtils.psm1 | 26 ++++++++++--------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/PSSwagger/PSSwagger.Constants.ps1 b/PSSwagger/PSSwagger.Constants.ps1 index d0cca21..49efb96 100644 --- a/PSSwagger/PSSwagger.Constants.ps1 +++ b/PSSwagger/PSSwagger.Constants.ps1 @@ -246,7 +246,18 @@ $getTaskResultBlock = @' Write-Debug -Message "`$(`$taskResult | Out-String)" - if(`$taskResult.IsFaulted) + + if((Get-Member -InputObject `$taskResult -Name 'Result') -and + `$taskResult.Result -and + (Get-Member -InputObject `$taskResult.Result -Name 'Body') -and + `$taskResult.Result.Body) + { + Write-Verbose -Message 'Operation completed successfully.' + `$result = `$taskResult.Result.Body + Write-Debug -Message "`$(`$result | Out-String)" + $resultBlockStr + } + elseif(`$taskResult.IsFaulted) { Write-Verbose -Message 'Operation failed.' if (`$taskResult.Exception) @@ -273,15 +284,6 @@ $getTaskResultBlock = @' else { Write-Verbose -Message 'Operation completed successfully.' - - if(`$taskResult.Result -and - (Get-Member -InputObject `$taskResult.Result -Name 'Body') -and - `$taskResult.Result.Body) - { - `$result = `$taskResult.Result.Body - Write-Debug -Message "`$(`$result | Out-String)" - $resultBlockStr - } } '@ @@ -382,9 +384,9 @@ $PagingBlockStrFunctionCallWithTop = @' Write-Verbose -Message 'Flattening paged results.' # Get the next page iff 1) there is a next page and 2) any result in the next page would be returned - while (`$result -and (Get-Member -InputObject `$result -Name $NextLinkName) -and `$result.$NextLinkName -and ((`$Top -eq -1) -or (`$returnedCount -lt `$Top))) { - Write-Debug -Message "Retrieving next page: `$(`$result.$NextLinkName)" - `$taskResult = $clientName$pagingOperations.$pagingOperationName(`$result.$NextLinkName) + while (`$result -and (Get-Member -InputObject `$result -Name '$NextLinkName') -and `$result.'$NextLinkName' -and ((`$Top -eq -1) -or (`$returnedCount -lt `$Top))) { + Write-Debug -Message "Retrieving next page: `$(`$result.'$NextLinkName')" + `$taskResult = $clientName$pagingOperations.$pagingOperationName(`$result.'$NextLinkName') $getTaskResult } '@ @@ -392,9 +394,9 @@ $PagingBlockStrFunctionCallWithTop = @' $PagingBlockStrFunctionCall = @' Write-Verbose -Message 'Flattening paged results.' - while (`$result -and (Get-Member -InputObject `$result -Name $NextLinkName) -and `$result.$NextLinkName) { - Write-Debug -Message "Retrieving next page: `$(`$result.$NextLinkName)" - `$taskResult = $clientName$pagingOperations.$pagingOperationName(`$result.$NextLinkName) + while (`$result -and (Get-Member -InputObject `$result -Name '$NextLinkName') -and `$result.'$NextLinkName') { + Write-Debug -Message "Retrieving next page: `$(`$result.'$NextLinkName')" + `$taskResult = $clientName$pagingOperations.$pagingOperationName(`$result.'$NextLinkName') $getTaskResult } '@ @@ -404,8 +406,8 @@ $PagingBlockStrCmdletCallWithTop = @' Write-Verbose -Message 'Flattening paged results.' # Get the next page iff 1) there is a next page and 2) any result in the next page would be returned - while (`$result -and (Get-Member -InputObject `$result -Name $NextLinkName) -and `$result.$NextLinkName -and ((`$Top -eq -1) -or (`$returnedCount -lt `$Top))) { - Write-Debug -Message "Retrieving next page: `$(`$result.$NextLinkName)" + while (`$result -and (Get-Member -InputObject `$result -Name '$NextLinkName') -and `$result.'$NextLinkName' -and ((`$Top -eq -1) -or (`$returnedCount -lt `$Top))) { + Write-Debug -Message "Retrieving next page: `$(`$result.'$NextLinkName')" $Cmdlet $CmdletArgs } '@ @@ -413,8 +415,8 @@ $PagingBlockStrCmdletCallWithTop = @' $PagingBlockStrCmdletCall = @' Write-Verbose -Message 'Flattening paged results.' - while (`$result -and (Get-Member -InputObject `$result -Name $NextLinkName) -and `$result.$NextLinkName) { - Write-Debug -Message "Retrieving next page: `$(`$result.$NextLinkName)" + while (`$result -and (Get-Member -InputObject `$result -Name '$NextLinkName') -and `$result.'$NextLinkName') { + Write-Debug -Message "Retrieving next page: `$(`$result.'$NextLinkName')" $Cmdlet $CmdletArgs } '@ diff --git a/PSSwagger/SwaggerUtils.psm1 b/PSSwagger/SwaggerUtils.psm1 index 1b01a69..d6cffba 100644 --- a/PSSwagger/SwaggerUtils.psm1 +++ b/PSSwagger/SwaggerUtils.psm1 @@ -630,18 +630,20 @@ function Get-PathParamInfo $operationId = $JsonPathItemObject.operationId } - $JsonPathItemObject.parameters | ForEach-Object { - $AllParameterDetails = Get-ParameterDetails -ParameterJsonObject $_ ` - -SwaggerDict $SwaggerDict ` - -DefinitionFunctionsDetails $DefinitionFunctionsDetails ` - -OperationId $operationId ` - -ParameterGroupCache $ParameterGroupCache ` - -PSMetaParametersJsonObject $PSMetaParametersJsonObject - foreach ($ParameterDetails in $AllParameterDetails) { - if($ParameterDetails -and ($ParameterDetails.ContainsKey('x_ms_parameter_grouping_group') -or $ParameterDetails.Type)) - { - $ParametersTable[$index] = $ParameterDetails - $index = $index + 1 + if(Get-Member -InputObject $JsonPathItemObject -Name 'Parameters'){ + $JsonPathItemObject.parameters | ForEach-Object { + $AllParameterDetails = Get-ParameterDetails -ParameterJsonObject $_ ` + -SwaggerDict $SwaggerDict ` + -DefinitionFunctionsDetails $DefinitionFunctionsDetails ` + -OperationId $operationId ` + -ParameterGroupCache $ParameterGroupCache ` + -PSMetaParametersJsonObject $PSMetaParametersJsonObject + foreach ($ParameterDetails in $AllParameterDetails) { + if($ParameterDetails -and ($ParameterDetails.ContainsKey('x_ms_parameter_grouping_group') -or $ParameterDetails.Type)) + { + $ParametersTable[$index] = $ParameterDetails + $index = $index + 1 + } } } } From ae8463d62400005f362896de4c72fd54f122f160 Mon Sep 17 00:00:00 2001 From: brywang-msft Date: Mon, 18 Sep 2017 10:44:38 -0700 Subject: [PATCH 18/40] Handle unwrapping exceptions to CloudError objects, update (#327) Newtonsoft.Json because of bug, add emptied HttpResponseMessage to error response --- .../CloudErrorTransform.json | 19 ++++ .../PSSwagger.LTF.ConsoleServer.csproj | 5 +- .../PSSwagger.LTF.ConsoleServer/Program.cs | 27 +++++- .../PSSwagger.LTF.ConsoleServer/config.json | 1 + .../PSSwagger.LTF.IO.Lib.csproj | 2 +- .../src/PSSwagger.LTF.Lib/LiveTestServer.cs | 5 +- .../Messages/LiveTestError.cs | 11 ++- .../Messages/LiveTestRequest.cs | 61 ++++++++++++- .../PSSwagger.LTF.Lib.csproj | 2 +- .../Transforms/DynamicObjectTransform.cs | 86 +++++++++++++++++++ .../DynamicObjectTransformDefinition.cs | 53 ++++++++++++ .../vs-csproj/PSSwagger.LTF.Lib.csproj | 3 + 12 files changed, 260 insertions(+), 15 deletions(-) create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/CloudErrorTransform.json create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransform.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransformDefinition.cs diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/CloudErrorTransform.json b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/CloudErrorTransform.json new file mode 100644 index 0000000..ca67260 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/CloudErrorTransform.json @@ -0,0 +1,19 @@ +{ + "type": "System.Management.Automation.ActionPreferenceStopException", + "transforms": [ + { + "query": "select", + "property": "ErrorRecord", + "result": "System.Management.Automation.ErrorRecord" + }, + { + "query": "select", + "property": "Exception", + "result": "Microsoft.Rest.Azure.CloudException" + }, + { + "query": "select", + "property": "Body" + } + ] +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj index 64e6192..5c58507 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj @@ -10,9 +10,12 @@ - + + + PreserveNewest + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs index 31a21d2..e6b1b62 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs @@ -11,6 +11,7 @@ namespace PSSwagger.LTF.ConsoleServer using Lib.PowerShell; using Lib.ServiceTracing; using Newtonsoft.Json; + using PSSwagger.LTF.Lib.Transforms; using System; using System.Collections.Generic; using System.Globalization; @@ -81,7 +82,8 @@ static void Main(string[] args) RunspaceManager = runspace, ModulePath = serverArgs.ModulePath, TracingManager = new ServiceTracingManager(), - SpecificationPaths = serverArgs.SpecificationPaths + SpecificationPaths = serverArgs.SpecificationPaths, + ObjectTransforms = serverArgs.GetTransforms() }); try @@ -109,6 +111,7 @@ class ServerArgs public List Errors { get; set; } public bool EnablePipeLog { get; set; } public bool EnableEventLog { get; set; } + public List TransformDefinitionFiles { get; set; } public ServerArgs() { @@ -118,6 +121,18 @@ public ServerArgs() this.LogPipeName = "psswagger-ltf-consoleserver"; this.EnablePipeLog = true; this.EnableEventLog = false; + this.TransformDefinitionFiles = new List(); + } + + public IList GetTransforms() + { + List transforms = new List(); + foreach (string file in this.TransformDefinitionFiles) + { + transforms.Add(JsonConvert.DeserializeObject(File.ReadAllText(file))); + } + + return transforms; } public ServerArgs Parse(string[] args) @@ -166,6 +181,10 @@ public ServerArgs Parse(string[] args) case "logpipename": this.LogPipeName = arg; break; + case "transform": + // Let's merge these instead of overwriting maybe? + this.TransformDefinitionFiles.Add(arg); + break; default: this.Errors.Add(String.Format(CultureInfo.CurrentCulture, "Unknown argument: {0}", lastArg)); break; @@ -216,6 +235,12 @@ public ServerArgs Parse(string jsonFilePath) { this.SpecificationPaths = fromFile.SpecificationPaths; } + + if (fromFile.TransformDefinitionFiles != null && fromFile.TransformDefinitionFiles.Count > 0) + { + // Let's merge these instead of overwriting maybe? + this.TransformDefinitionFiles.AddRange(fromFile.TransformDefinitionFiles); + } } return this; diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/config.json b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/config.json index 7a73a41..8506565 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/config.json +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/config.json @@ -1,2 +1,3 @@ { + "TransformDefinitionFiles": [ "CloudErrorTransform.json" ] } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/PSSwagger.LTF.IO.Lib.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/PSSwagger.LTF.IO.Lib.csproj index 58dd87f..93881b2 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/PSSwagger.LTF.IO.Lib.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/PSSwagger.LTF.IO.Lib.csproj @@ -3,7 +3,7 @@ net452 - + diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs index d80b65c..d53ffb3 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs @@ -16,6 +16,7 @@ namespace PSSwagger.LTF.Lib using System.IO; using System.Threading; using System.Threading.Tasks; + using Transforms; public class LiveTestServerStartParams { @@ -27,9 +28,11 @@ public class LiveTestServerStartParams public IList SpecificationPaths { get; set; } public LiveTestCredentialFactory CredentialFactory { get; set; } public ServiceTracingManager TracingManager { get; set; } + public IList ObjectTransforms { get; set; } public LiveTestServerStartParams() { this.SpecificationPaths = new List(); + this.ObjectTransforms = new List(); } } @@ -187,7 +190,7 @@ public async Task RunAsync() } else { - response = msg.MakeResponse(commandResult, serviceTracer, this.parameters.Logger); + response = msg.MakeResponse(commandResult, serviceTracer, parameters.ObjectTransforms, this.parameters.Logger); } } catch (Exception exRequest) diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestError.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestError.cs index 068e9f4..46985d9 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestError.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestError.cs @@ -3,6 +3,9 @@ // Licensed under the MIT license. namespace PSSwagger.LTF.Lib.Messages { + using Microsoft.Rest; + using System.Net.Http; + /// /// Error response from test operation. /// @@ -10,11 +13,7 @@ public class LiveTestError { public long Code { get; set; } public string Message { get; set; } - public LiveTestResult Data { get; set; } - - public LiveTestError() - { - this.Data = new LiveTestResult(); - } + public object Data { get; set; } + public HttpResponseMessage HttpResponse { get; set; } } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestRequest.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestRequest.cs index 950bb2a..bb0b727 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestRequest.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestRequest.cs @@ -11,6 +11,7 @@ namespace PSSwagger.LTF.Lib.Messages using System.Collections.Generic; using System.Linq; using System.Net.Http; + using Transforms; /// /// An Azure Live Test Framework JSON-RPC request. @@ -28,7 +29,7 @@ public class LiveTestRequest : JsonRpcBase [JsonIgnore] public bool HttpResponse { get; set; } - public LiveTestResponse MakeResponse(CommandExecutionResult commandResult, IServiceTracer tracer, Logger logger) + public LiveTestResponse MakeResponse(CommandExecutionResult commandResult, IServiceTracer tracer, IList transforms, Logger logger) { LiveTestResponse response = MakeBaseResponse(); if (commandResult.HadErrors) @@ -41,7 +42,31 @@ public LiveTestResponse MakeResponse(CommandExecutionResult commandResult, IServ response.Error = new LiveTestError(); response.Error.Code = InvalidRequest; - response.Error.Data = GetLiveTestResult(commandResult.Errors, tracer); + List errors = new List(); + foreach (object originalError in commandResult.Errors) + { + errors.AddRange(TransformObject(originalError, transforms)); + } + + response.Error.Data = errors.Count == 0 ? null : errors.Count == 1 ? errors[0] : errors; + if (this.HttpResponse) + { + HttpResponseMessage responseMessage = tracer.HttpResponses.LastOrDefault(); + if (responseMessage != null) + { + // Kill the Content property - doesn't work with Newtonsoft.Json serialization + HttpResponseMessage clonedMessage = new HttpResponseMessage(responseMessage.StatusCode); + foreach (var header in responseMessage.Headers) + { + clonedMessage.Headers.Add(header.Key, header.Value); + } + + clonedMessage.ReasonPhrase = responseMessage.ReasonPhrase; + clonedMessage.RequestMessage = responseMessage.RequestMessage; + clonedMessage.Version = responseMessage.Version; + response.Error.HttpResponse = clonedMessage; + } + } } else { @@ -50,7 +75,13 @@ public LiveTestResponse MakeResponse(CommandExecutionResult commandResult, IServ logger.LogAsync("Command executed successfully."); } - response.Result = GetLiveTestResult(commandResult.Results, tracer); + List results = new List(); + foreach (object originalResult in commandResult.Results) + { + results.AddRange(TransformObject(originalResult, transforms)); + } + + response.Result = GetLiveTestResult(results, tracer); } return response; @@ -61,10 +92,32 @@ public LiveTestResponse MakeResponse(Exception ex, int errorCode) LiveTestResponse response = MakeBaseResponse(); response.Error = new LiveTestError(); response.Error.Code = errorCode; - response.Error.Data.Response = ex; + response.Error.Data = ex; return response; } + private IEnumerable TransformObject(object obj, IList transforms) + { + bool transformed = false; + foreach (object result in transforms.Where(t => t.CanTransform(obj)).SelectMany(t => t.Transform(obj))) + { + transformed = true; + IEnumerable transformedResults = TransformObject(result, transforms); + if (transformedResults != null) + { + foreach (object innerResult in transformedResults) + { + yield return innerResult; + } + } + } + + if (!transformed) + { + yield return obj; + } + } + private LiveTestResult GetLiveTestResult(IEnumerable resultsEnumerable, IServiceTracer tracer) { LiveTestResult result = new LiveTestResult(); diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PSSwagger.LTF.Lib.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PSSwagger.LTF.Lib.csproj index 9f36504..c6078b9 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PSSwagger.LTF.Lib.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PSSwagger.LTF.Lib.csproj @@ -3,7 +3,7 @@ net452 - + diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransform.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransform.cs new file mode 100644 index 0000000..c339f3d --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransform.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Transforms +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + /// + /// Defines transform steps for a single type. + /// + public class DynamicObjectTransform + { + /// + /// Gets or sets the type to apply transform to. + /// + public string Type { get; set; } + + /// + /// Gets or sets the ordered list of transforms to apply to this type. + /// + public DynamicObjectTransformDefinition[] Transforms { get; set; } + + /// + /// Checks if exactly matches the Type property. + /// + /// Object to check. Can be null. + /// False if null or .GetType().FullName does not match this.Type; True otherwise. + public bool CanTransform(object obj) + { + if (obj == null) + { + return false; + } + + return obj.GetType().FullName.Equals(this.Type, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Transform the given object into one or more objects. For multi-step transforms, all steps should define an expected output type. Otherwise, the client should re-query all transformers. + /// + /// Object to transform. Can be null. + /// Null if is null. Otherwise, returns the result of valid transforms. + public IEnumerable Transform(object obj) + { + if (this.Transforms != null) + { + List toTransform = new List(); + List transformResult = new List(); + List swap = null; + toTransform.Add(obj); + foreach (DynamicObjectTransformDefinition definition in this.Transforms) + { + transformResult.Clear(); + foreach (object transformObject in toTransform) + { + IEnumerable newObjects = definition.Transform(transformObject); + if (!String.IsNullOrEmpty(definition.Result)) + { + newObjects = newObjects.Where(o => o.GetType().FullName.Equals(definition.Result, StringComparison.OrdinalIgnoreCase)); + } + + transformResult.AddRange(newObjects); + } + + // Nothing to transform to the next phase, just return the previous phase + if (transformResult.Count == 0) + { + return toTransform; + } + + swap = toTransform; + toTransform = transformResult; + transformResult = swap; + } + + // The result of the last phase will be in toTransform + return toTransform; + } + + return null; + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransformDefinition.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransformDefinition.cs new file mode 100644 index 0000000..0978425 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransformDefinition.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +using System.Collections.Generic; +using System.Reflection; + +namespace PSSwagger.LTF.Lib.Transforms +{ + /// + /// Defines a single transform step. + /// + public class DynamicObjectTransformDefinition + { + /// + /// Gets or sets the transform type. Supported: 'select' + /// + public string Query { get; set; } + + /// + /// Gets or sets the property to select. Valid for query types: 'select' + /// + public string Property { get; set; } + + /// + /// Gets or sets the result type. Optional. If defined, subsequent transforms will only take place if the output type matches this type. This optimizes multi-step transforms. + /// + public string Result { get; set; } + + public IEnumerable Transform(object obj) + { + if (!string.IsNullOrEmpty(this.Query)) + { + switch (this.Query.ToLowerInvariant()) + { + case "select": + yield return Select(obj); + break; + } + } + } + + private object Select(object obj) + { + PropertyInfo pi = obj.GetType().GetProperty(this.Property); + if (pi != null) + { + return pi.GetValue(obj); + } + + return null; + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.csproj index ab9d0bb..89f9014 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.csproj @@ -94,6 +94,9 @@ PostProcessors\%(FileName).cs + + Transforms\%(FileName).cs + From 37e72af3166c656271858f90cdf0a16be1742856 Mon Sep 17 00:00:00 2001 From: brywang-msft Date: Mon, 18 Sep 2017 12:40:03 -0700 Subject: [PATCH 19/40] Fix for server spinning bug (#330) --- .../src/PSSwagger.LTF.IO.Lib/JsonRpcPipe.cs | 15 +++- .../src/PSSwagger.LTF.Lib/LiveTestServer.cs | 82 +++++++++++-------- 2 files changed, 60 insertions(+), 37 deletions(-) diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/JsonRpcPipe.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/JsonRpcPipe.cs index 6b6b3f0..73f8cfb 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/JsonRpcPipe.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/JsonRpcPipe.cs @@ -20,6 +20,7 @@ public class JsonRpcPipe : IInputPipe, IOutputPipe /// Constant to indicate that headers are done. /// private const string HeaderEndConstant = "end"; + private const string InputStreamClosedConstant = "closed"; private IInputPipe characterReader; private IOutputPipe characterWriter; private ConcurrentQueue blockQueue; @@ -48,7 +49,7 @@ public JsonRpcPipe(IInputPipe characterReader, IOutputPipe characterWriter) /// Read a single valid JSON-RPC block. /// /// Type of block to read and deserialize. - /// Block of type . + /// Block of type . Returns null if input stream has been closed. public async Task ReadBlock() where T : class { object returnObj = null; @@ -69,7 +70,14 @@ public async Task ReadBlock() where T : class do { headers[header.Item1.ToLowerInvariant()] = header.Item2; - } while ((header = await ReadHeader()) != null && !header.Item1.Equals(HeaderEndConstant)); + } while ((header = await ReadHeader()) != null && !header.Item1.Equals(HeaderEndConstant) && !header.Item1.Equals(InputStreamClosedConstant)); + + if (header.Item1.Equals(InputStreamClosedConstant)) + { + // Input stream closed - return null to indicate this + return null; + } + byte[] bytes = new byte[Int32.Parse(headers["content-length"])]; for (int i = 0; i < bytes.Length; i++) { @@ -189,6 +197,9 @@ private async Task> ReadHeader() break; case '\r': break; + case '\uffff': + // When this character is read, assume the input stream has been closed + return new Tuple(InputStreamClosedConstant, String.Empty); default: if (spaceRead) { diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs index d53ffb3..2803597 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs @@ -163,58 +163,70 @@ public async Task RunAsync() LiveTestRequest msg = await this.Input.ReadBlock(); if (this.IsRunning) { - if (this.parameters.Logger != null) + if (msg == null) { - this.parameters.Logger.LogAsync("Processing message: {0}", msg); + if (this.parameters.Logger != null) + { + this.parameters.Logger.LogAsync("Input stream has been closed, stopping server.", msg); + } + + this.IsRunning = false; } - Task.Run(() => + else { - LiveTestResponse response = null; - IServiceTracer serviceTracer = null; - try + if (this.parameters.Logger != null) + { + this.parameters.Logger.LogAsync("Processing message: {0}", msg); + } + Task.Run(() => { + LiveTestResponse response = null; + IServiceTracer serviceTracer = null; + try + { // Enable service tracing so that we can get service layer information required by test protocol long invocationId = this.parameters.TracingManager.GetNextInvocationId(); - serviceTracer = this.parameters.TracingManager.CreateTracer(invocationId, this.parameters.Logger); - this.parameters.TracingManager.EnableTracing(); + serviceTracer = this.parameters.TracingManager.CreateTracer(invocationId, this.parameters.Logger); + this.parameters.TracingManager.EnableTracing(); // Process teh request CommandExecutionResult commandResult = this.currentModule.ProcessRequest(msg, this.parameters.CredentialFactory); - if (commandResult == null) + if (commandResult == null) + { + if (this.parameters.Logger != null) + { + this.parameters.Logger.LogAsync("Command not found."); + } + + response = msg.MakeResponse(null, MethodNotFound); + } + else + { + response = msg.MakeResponse(commandResult, serviceTracer, parameters.ObjectTransforms, this.parameters.Logger); + } + } + catch (Exception exRequest) { if (this.parameters.Logger != null) { - this.parameters.Logger.LogAsync("Command not found."); + this.parameters.Logger.LogError("Exception processing request: " + exRequest.ToString()); } - response = msg.MakeResponse(null, MethodNotFound); + response = msg.MakeResponse(exRequest, InternalError); } - else + finally { - response = msg.MakeResponse(commandResult, serviceTracer, parameters.ObjectTransforms, this.parameters.Logger); - } - } - catch (Exception exRequest) - { - if (this.parameters.Logger != null) - { - this.parameters.Logger.LogError("Exception processing request: " + exRequest.ToString()); - } - - response = msg.MakeResponse(exRequest, InternalError); - } - finally - { - if (response != null) - { - this.Output.WriteBlock(response); - } + if (response != null) + { + this.Output.WriteBlock(response); + } - if (serviceTracer != null) - { - this.parameters.TracingManager.RemoveTracer(serviceTracer); + if (serviceTracer != null) + { + this.parameters.TracingManager.RemoveTracer(serviceTracer); + } } - } - }); + }); + } } } catch (Exception eRead) From d7a552803408a4ee731ed9be1847588192c6d904 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Tue, 19 Sep 2017 12:46:34 -0700 Subject: [PATCH 20/40] Change warninglevel to 1 for generating assembly for Azure Swagger specs. (#331) ```powershell Generating module for 'C:\temp\AzureSwaggerSpecs\SwaggerSpecs\monitor\autoscale_API.json'. PSSwaggerUtility\Add-PSSwaggerClientType : C:\temp\AzureSwaggerSpecs\AzModules\symbols\Azmonitor\0.0.1\Generated.Microsoft.PowerShell.Azmonitor.v001.cs(433,23): warning CS0108: 'ActivityLogAlertLeafCondition.Equals' hides inherited member 'object. the new keyword if hiding was intended. At C:\Code\PSSwagger\PSSwagger\PSSwagger.psm1:900 char:14 + ... if(-not (PSSwaggerUtility\Add-PSSwaggerClientType @AddPSSwaggerCli ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : SOURCE_CODE_ERROR,Add-PSSwaggerClientType ConvertTo-CsharpCode : Unable to generate 'Microsoft.PowerShell.Azmonitor.v001.dll' assembly At C:\Code\PSSwagger\PSSwagger\PSSwagger.psm1:545 char:37 + ... lyGenerationResult = ConvertTo-CsharpCode @ConvertToCsharpCode_params + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : UnableToGenerateAssembly,ConvertTo-CsharpCode ``` Minor additional changes - Fixed the issue for setting $env:PSModulePath to compile coreclr binary - Added verb mapping for 'Verify' --> 'Test'. --- PSSwagger/PSCommandVerbMap.ps1 | 199 +++++++++--------- PSSwagger/PSSwagger.psm1 | 3 +- .../PSSwaggerUtility/PSSwaggerUtility.psm1 | 4 +- 3 files changed, 103 insertions(+), 103 deletions(-) diff --git a/PSSwagger/PSCommandVerbMap.ps1 b/PSSwagger/PSCommandVerbMap.ps1 index 9962412..ad28acc 100644 --- a/PSSwagger/PSCommandVerbMap.ps1 +++ b/PSSwagger/PSCommandVerbMap.ps1 @@ -9,134 +9,135 @@ ######################################################################################### $script:PSCommandVerbMap = @{ - Access = 'Get' - List = 'Get' - Cat = 'Get' - Type = 'Get' - Dir = 'Get' - Obtain = 'Get' - Dump = 'Get' - Acquire = 'Get' - Examine = 'Get' - Suggest = 'Get' - - Create = 'New' - Generate = 'New' - Allocate = 'New' - Provision = 'New' - Make = 'New' + Access = 'Get' + List = 'Get' + Cat = 'Get' + Type = 'Get' + Dir = 'Get' + Obtain = 'Get' + Dump = 'Get' + Acquire = 'Get' + Examine = 'Get' + Suggest = 'Get' + + Create = 'New' + Generate = 'New' + Allocate = 'New' + Provision = 'New' + Make = 'New' CreateOrUpdate = 'New,Set' - Failover = 'Set' - Assign = 'Set' - Configure = 'Set' + Failover = 'Set' + Assign = 'Set' + Configure = 'Set' - Activate = 'Initialize' + Activate = 'Initialize' - Build = 'Build' - Compile = 'Build' + Build = 'Build' + Compile = 'Build' - Deploy = 'Deploy' + Deploy = 'Deploy' - Apply = 'Add' - Append = 'Add' - Attach = 'Add' - Concatenate = 'Add' - Insert = 'Add' + Apply = 'Add' + Append = 'Add' + Attach = 'Add' + Concatenate = 'Add' + Insert = 'Add' - Delete = 'Remove' - Cut = 'Remove' - Dispose = 'Remove' - Discard = 'Remove' + Delete = 'Remove' + Cut = 'Remove' + Dispose = 'Remove' + Discard = 'Remove' - Generalize = 'Reset' - - Patch = 'Update' - Refresh = 'Update' - Regenerate = 'Update' # Alternatives: Redo, New, Reset - Reprocess = "Update" # Alternatives: Redo - Upgrade = 'Update' - Reimage = 'Update' # Alternatives: Format, Reset - - Validate = 'Test' - Check = 'Test' - Analyze = 'Test' - Is = 'Test' - Evaluate = 'Test' # Alternatives: Invoke - - Power = 'Start' - PowerOn = 'Start' - Run = 'Start' # Alternatives: Invoke - Trigger = 'Start' - - Pause = 'Suspend' + Generalize = 'Reset' + + Patch = 'Update' + Refresh = 'Update' + Regenerate = 'Update' # Alternatives: Redo, New, Reset + Reprocess = "Update" # Alternatives: Redo + Upgrade = 'Update' + Reimage = 'Update' # Alternatives: Format, Reset + + Validate = 'Test' + Check = 'Test' + Verify = 'Test' + Analyze = 'Test' + Is = 'Test' + Evaluate = 'Test' # Alternatives: Invoke + + Power = 'Start' + PowerOn = 'Start' + Run = 'Start' # Alternatives: Invoke + Trigger = 'Start' + + Pause = 'Suspend' - Cancel = 'Stop' - PowerOff = 'Stop' - End = 'Stop' - Shutdown = 'Stop' + Cancel = 'Stop' + PowerOff = 'Stop' + End = 'Stop' + Shutdown = 'Stop' - Reboot = 'Restart' - ForceReboot = 'Restart' + Reboot = 'Restart' + ForceReboot = 'Restart' - Finish = 'Complete' + Finish = 'Complete' - Wipe = 'Clear' - Purge = 'Clear' # Alternatives: Remove - Flush = 'Clear' - Erase = 'Clear' - Unmark = 'Clear' - Unset = 'Clear' - Nullify = 'Clear' + Wipe = 'Clear' + Purge = 'Clear' # Alternatives: Remove + Flush = 'Clear' + Erase = 'Clear' + Unmark = 'Clear' + Unset = 'Clear' + Nullify = 'Clear' - Recover = 'Restore' - Undelete = 'Restore' + Recover = 'Restore' + Undelete = 'Restore' - Synchronize = 'Sync' - Synch = 'Sync' + Synchronize = 'Sync' + Synch = 'Sync' - Load = 'Import' + Load = 'Import' - Capture = 'Export' # Alternatives: Trace + Capture = 'Export' # Alternatives: Trace - Migrate = 'Move' # Alternatives: Export - Transfer = 'Move' - Name = 'Move' + Migrate = 'Move' # Alternatives: Export + Transfer = 'Move' + Name = 'Move' - Change = 'Rename' + Change = 'Rename' - Swap = 'Switch' # Alternatives: Move + Swap = 'Switch' # Alternatives: Move - Execute = 'Invoke' + Execute = 'Invoke' - Discover = 'Find' # Alternatives: Search - Locate = 'Find' + Discover = 'Find' # Alternatives: Search + Locate = 'Find' - Release = 'Publish' # Alternatives: Clear, Unlock + Release = 'Publish' # Alternatives: Clear, Unlock - Resubmit = 'Submit' + Resubmit = 'Submit' - Duplicate = 'Copy' - Clone = 'Copy' - Replicate = 'Copy' + Duplicate = 'Copy' + Clone = 'Copy' + Replicate = 'Copy' - Into = 'Enter' + Into = 'Enter' - Combine = 'Join' - Unite = 'Join' - Associate = 'Join' + Combine = 'Join' + Unite = 'Join' + Associate = 'Join' - Restrict = 'Lock' - Secure = 'Lock' + Restrict = 'Lock' + Secure = 'Lock' - Unrestrict = 'Unlock' - Unsecure = 'Unlock' + Unrestrict = 'Unlock' + Unsecure = 'Unlock' - Display = 'Show' - Produce = 'Show' + Display = 'Show' + Produce = 'Show' - Bypass = 'Skip' - Jump = 'Skip' + Bypass = 'Skip' + Jump = 'Skip' - Separate = 'Split' + Separate = 'Split' } \ No newline at end of file diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index fbe9cfd..d0163e4 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -927,7 +927,7 @@ function ConvertTo-CsharpCode # In some cases, PSCore doesn't inherit this process's PSModulePath $command = @" - `$env:PSModulePath = `"$env:PSModulePath`"; + `$env:PSModulePath += ';$env:PSModulePath' `$AddPSSwaggerClientType_params = @{ OutputAssemblyName = '$outAssembly' ClrPath = '$clrPath' @@ -938,7 +938,6 @@ function ConvertTo-CsharpCode CodeCreatedByAzureGenerator = `$$codeCreatedByAzureGenerator BootstrapConsent = `$$UserConsent } - `$env:PSModulePath += ';$env:PSModulePath' PSSwaggerUtility\Add-PSSwaggerClientType @AddPSSwaggerClientType_params "@ $success = & "$PowerShellCorePath" -command "& {$command}" diff --git a/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 b/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 index 221473d..0c3c91d 100644 --- a/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 +++ b/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 @@ -733,7 +733,7 @@ function Get-AddTypeParameters { if (-not (Get-OperatingSystemInfo).IsCore) { $AddTypeParams['Path'] = $SourceCodeFilePath $compilerParameters = New-Object -TypeName System.CodeDom.Compiler.CompilerParameters - $compilerParameters.WarningLevel = 3 + $compilerParameters.WarningLevel = 1 $compilerParameters.CompilerOptions = '/debug:full' if ($TestBuild) { $compilerParameters.IncludeDebugInformation = $true @@ -807,7 +807,7 @@ function Get-CscParameters { $SourceCodeFilePath '/nologo', '/checked', - '/warn:3', + '/warn:1', '/debug:full', '/platform:anycpu', "/target:$TargetType" From c050b4cdb055cdd3600f257083ba2628f4d2e306 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Wed, 20 Sep 2017 10:50:09 -0700 Subject: [PATCH 21/40] Resolve UnableToExtractDetailsFromSdkAssembly error when OperationType in OperationId conflicts with Definition name. (#332) When OperationType value conflicts with a definition name, AutoREST generates method name by adding Method to the OperationType. Current error for swagger specs of Azure Servicebus and Relay services. ```powershell Generating module for 'C:\temp\AzureSwaggerSpecs\SwaggerSpecs\servicebus\servicebus.json'. Update-PathFunctionDetails : Module generation failed. If no error messages follow, check the output of code metadata extraction above Unable to find expected method 'CheckNameAvailabilityWithHttpMessagesAsync' on type 'Microsoft.PowerShell.Azservicebus.v001.INamespacesOperations' At C:\Code\PSSwagger\PSSwagger\PSSwagger.psm1:555 char:28 + ... onDetails = Update-PathFunctionDetails -PathFunctionDetails $PathFunc ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : UnableToExtractDetailsFromSdkAssembly,Update-PathFunctionDetails ``` ```powershell Generating module for 'C:\temp\AzureSwaggerSpecs\SwaggerSpecs\relay\relay.json'. Update-PathFunctionDetails : Module generation failed. If no error messages follow, check the output of code metadata extraction above Unable to find expected method 'CheckNameAvailabilityWithHttpMessagesAsync' on type 'Microsoft.PowerShell.Azrelay.v001.INamespacesOperations' At C:\Code\PSSwagger\PSSwagger\PSSwagger.psm1:555 char:28 + ... onDetails = Update-PathFunctionDetails -PathFunctionDetails $PathFunc ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : UnableToExtractDetailsFromSdkAssembly,Update-PathFunctionDetails ``` --- PSSwagger/Paths.psm1 | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/PSSwagger/Paths.psm1 b/PSSwagger/Paths.psm1 index 4806794..e1b68f3 100644 --- a/PSSwagger/Paths.psm1 +++ b/PSSwagger/Paths.psm1 @@ -1034,12 +1034,13 @@ function Set-ExtendedCodeMetadata { } $operationId = $parameterSetDetail.OperationId - $methodName = '' + $methodNames = @() $operations = '' $operationsWithSuffix = '' $opIdValues = $operationId -split '_',2 if(-not $opIdValues -or ($opIdValues.count -ne 2)) { - $methodName = $operationId + 'WithHttpMessagesAsync' + $methodNames += $operationId + 'WithHttpMessagesAsync' + $methodNames += $operationId + 'Method' + 'WithHttpMessagesAsync' } else { $operationName = $opIdValues[0] $operationType = $opIdValues[1] @@ -1049,10 +1050,11 @@ function Set-ExtendedCodeMetadata { $operationsWithSuffix = $operations + 'Operations' } - $methodName = $operationType + 'WithHttpMessagesAsync' + $methodNames += $operationType + 'WithHttpMessagesAsync' + # When OperationType value conflicts with a definition name, AutoREST generates method name by adding Method to the OperationType. + $methodNames += $operationType + 'Method' + 'WithHttpMessagesAsync' } - $parameterSetDetail['MethodName'] = $methodName $parameterSetDetail['Operations'] = $operations # For some reason, moving this out of this loop causes issues @@ -1119,13 +1121,14 @@ function Set-ExtendedCodeMetadata { $clientType = $propertyObject.PropertyType } - $methodInfo = $clientType.GetMethods() | Where-Object { $_.Name -eq $MethodName } | Select-Object -First 1 + $methodInfo = $clientType.GetMethods() | Where-Object {$MethodNames -contains $_.Name} | Select-Object -First 1 if (-not $methodInfo) { - $resultRecord.ErrorMessages += $LocalizedData.ExpectedMethodOnTypeNotFound -f ($MethodName, $clientType) + $resultRecord.ErrorMessages += $LocalizedData.ExpectedMethodOnTypeNotFound -f (($MethodNames -join ', or '), $clientType) Export-CliXml -InputObject $resultRecord -Path $CliXmlTmpPath $errorOccurred = $true return } + $parameterSetDetail['MethodName'] = $methodInfo.Name # Process output type $returnType = $methodInfo.ReturnType From 79d83fd235bb9e1dda70935eb2119b89950aa50c Mon Sep 17 00:00:00 2001 From: Florian Feldhaus Date: Wed, 20 Sep 2017 22:44:02 +0200 Subject: [PATCH 22/40] Rename IsOSX to IsMacOS after breaking change in PowerShell 6.0.0-beta.7 to fix #333 (#334) --- .../build/PSSwagger.LiveTestFramework.Build.psm1 | 8 ++++---- .../test/PSSwagger.LiveTestFramework.Tests.psm1 | 6 +++--- PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 | 8 ++++---- Tests/PSSwagger.Unit.Tests.ps1 | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/PSSwagger.LiveTestFramework/build/PSSwagger.LiveTestFramework.Build.psm1 b/PSSwagger.LiveTestFramework/build/PSSwagger.LiveTestFramework.Build.psm1 index a72c8fd..05e43a8 100644 --- a/PSSwagger.LiveTestFramework/build/PSSwagger.LiveTestFramework.Build.psm1 +++ b/PSSwagger.LiveTestFramework/build/PSSwagger.LiveTestFramework.Build.psm1 @@ -252,15 +252,15 @@ function Install-Dotnet { $osInfo = PSSwaggerUtility\Get-OperatingSystemInfo $obtainUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain" - # Install for Linux and OS X - if ($osInfo.IsLinux -or $osInfo.IsOSX) { + # Install for Linux and Mac OS + if ($osInfo.IsLinux -or $osInfo.IsMacOS) { $LinuxInfo = Get-Content /etc/os-release -Raw | ConvertFrom-StringData $IsUbuntu = $LinuxInfo.ID -match 'ubuntu' # Uninstall all previous dotnet packages $uninstallScript = if ($IsUbuntu) { "dotnet-uninstall-debian-packages.sh" - } elseif ($osInfo.IsOSX) { + } elseif ($osInfo.IsMacOS) { "dotnet-uninstall-pkgs.sh" } @@ -339,4 +339,4 @@ function script:Start-NativeExecution([scriptblock]$sb, [switch]$IgnoreExitcode) } } -Export-ModuleMember -Function Initialize-BuildDependency,Invoke-Build,Start-BuildDotNetProject \ No newline at end of file +Export-ModuleMember -Function Initialize-BuildDependency,Invoke-Build,Start-BuildDotNetProject diff --git a/PSSwagger.LiveTestFramework/test/PSSwagger.LiveTestFramework.Tests.psm1 b/PSSwagger.LiveTestFramework/test/PSSwagger.LiveTestFramework.Tests.psm1 index e6b194c..4d896ac 100644 --- a/PSSwagger.LiveTestFramework/test/PSSwagger.LiveTestFramework.Tests.psm1 +++ b/PSSwagger.LiveTestFramework/test/PSSwagger.LiveTestFramework.Tests.psm1 @@ -157,14 +157,14 @@ function Install-Dotnet { $obtainUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain" # Install for Linux and OS X - if ($osInfo.IsLinux -or $osInfo.IsOSX) { + if ($osInfo.IsLinux -or $osInfo.IsMacOS) { $LinuxInfo = Get-Content /etc/os-release -Raw | ConvertFrom-StringData $IsUbuntu = $LinuxInfo.ID -match 'ubuntu' # Uninstall all previous dotnet packages $uninstallScript = if ($IsUbuntu) { "dotnet-uninstall-debian-packages.sh" - } elseif ($osInfo.IsOSX) { + } elseif ($osInfo.IsMacOS) { "dotnet-uninstall-pkgs.sh" } @@ -243,4 +243,4 @@ function script:Start-NativeExecution([scriptblock]$sb, [switch]$IgnoreExitcode) } } -Export-ModuleMember -Function Initialize-TestDependency,Start-TestRun \ No newline at end of file +Export-ModuleMember -Function Initialize-TestDependency,Start-TestRun diff --git a/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 b/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 index 0c3c91d..b5c3b6c 100644 --- a/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 +++ b/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psm1 @@ -250,13 +250,13 @@ function Start-PSSwaggerJobHelper <# .DESCRIPTION - Gets operating system information. Returns an object with the following boolean properties: IsCore, IsLinux, IsWindows, IsOSX, IsNanoServer, IsIoT + Gets operating system information. Returns an object with the following boolean properties: IsCore, IsLinux, IsWindows, IsMacOS, IsNanoServer, IsIoT #> function Get-OperatingSystemInfo { $info = @{ IsCore = $false IsLinux = $false - IsOSX = $false + IsMacOS = $false IsWindows = $false IsNanoServer = $false IsIoT = $false @@ -265,7 +265,7 @@ function Get-OperatingSystemInfo { if ('System.Management.Automation.Platform' -as [Type]) { $info.IsCore = [System.Management.Automation.Platform]::IsCoreCLR $info.IsLinux = [System.Management.Automation.Platform]::IsLinux - $info.IsOSX = [System.Management.Automation.Platform]::IsOSX + $info.IsMacOS = [System.Management.Automation.Platform]::IsMacOS $info.IsWindows = [System.Management.Automation.Platform]::IsWindows $info.IsNanoServer = [System.Management.Automation.Platform]::IsNanoServer $info.IsIoT = [System.Management.Automation.Platform]::IsIoT @@ -1974,4 +1974,4 @@ function Get-AutoRestCredential { } else { Get-EmptyAuthCredentialInternal } -} \ No newline at end of file +} diff --git a/Tests/PSSwagger.Unit.Tests.ps1 b/Tests/PSSwagger.Unit.Tests.ps1 index 736458a..c531150 100644 --- a/Tests/PSSwagger.Unit.Tests.ps1 +++ b/Tests/PSSwagger.Unit.Tests.ps1 @@ -73,7 +73,7 @@ Describe "PSSwagger Unit Tests" -Tag @('BVT', 'DRT', 'UnitTest', 'P0') { return @{ IsCore = $true IsLinux = $true - IsOSX = $false + IsMacOS = $false IsWindows = $false } } @@ -120,7 +120,7 @@ Describe "PSSwagger Unit Tests" -Tag @('BVT', 'DRT', 'UnitTest', 'P0') { return @{ IsCore = $true IsLinux = $true - IsOSX = $false + IsMacOS = $false IsWindows = $false } } @@ -161,7 +161,7 @@ Describe "PSSwagger Unit Tests" -Tag @('BVT', 'DRT', 'UnitTest', 'P0') { return @{ IsCore = $true IsLinux = $false - IsOSX = $false + IsMacOS = $false IsWindows = $true } } From 9891ec8570c5a7a5f9f10715fe2a8521404b149b Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Wed, 20 Sep 2017 15:03:07 -0700 Subject: [PATCH 23/40] Generate SYNOPSIS help content in the generated cmdlets. (#337) --- PSSwagger/Definitions.psm1 | 3 +++ PSSwagger/PSSwagger.Constants.ps1 | 3 +++ PSSwagger/Paths.psm1 | 24 +++++++++++++++++------- Tests/PSSwaggerScenario.Tests.ps1 | 11 +++++++++++ 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/PSSwagger/Definitions.psm1 b/PSSwagger/Definitions.psm1 index c5d7ede..9d7906e 100644 --- a/PSSwagger/Definitions.psm1 +++ b/PSSwagger/Definitions.psm1 @@ -127,6 +127,8 @@ function Get-SwaggerSpecDefinitionInfo $FunctionDetails['Name'] = $Name $FunctionDetails['Description'] = $FunctionDescription + # Definition doesn't have Summary property, so using specifying Description as Function Synopsis. + $FunctionDetails['Synopsis'] = $FunctionDescription $FunctionDetails['ParametersTable'] = $ParametersTable $FunctionDetails['x_ms_Client_flatten_DefinitionNames'] = $x_ms_Client_flatten_DefinitionNames $FunctionDetails['AllOf_DefinitionNames'] = $AllOf_DefinitionNames @@ -802,6 +804,7 @@ function New-SwaggerSpecDefinitionCommand $commandName = "New-$($FunctionDetails.Name)Object" $description = $FunctionDetails.description + $synopsis = $FunctionDetails.synopsis $commandHelp = $executionContext.InvokeCommand.ExpandString($helpDescStr) [string]$paramHelp = "" diff --git a/PSSwagger/PSSwagger.Constants.ps1 b/PSSwagger/PSSwagger.Constants.ps1 index 49efb96..5da4695 100644 --- a/PSSwagger/PSSwagger.Constants.ps1 +++ b/PSSwagger/PSSwagger.Constants.ps1 @@ -9,6 +9,9 @@ ######################################################################################### $helpDescStr = @' +.SYNOPSIS + $synopsis + .DESCRIPTION $description '@ diff --git a/PSSwagger/Paths.psm1 b/PSSwagger/Paths.psm1 index e1b68f3..3316da6 100644 --- a/PSSwagger/Paths.psm1 +++ b/PSSwagger/Paths.psm1 @@ -152,6 +152,11 @@ function Get-SwaggerSpecPathInfo if((Get-Member -InputObject $_.value -Name 'description') -and $_.value.description) { $FunctionDescription = $_.value.description } + + $FunctionSynopsis = '' + if((Get-Member -InputObject $_.value -Name 'Summary') -and $_.value.Summary) { + $FunctionSynopsis = $_.value.Summary + } $ParametersTable = @{} # Add Path common parameters to the operation's parameters list. @@ -193,15 +198,16 @@ function Get-SwaggerSpecPathInfo } $ParameterSetDetail = @{ - Description = $FunctionDescription - ParameterDetails = $ParametersTable - Responses = $responses - OperationId = $operationId - OperationType = $operationType + Description = $FunctionDescription + Synopsis = $FunctionSynopsis + ParameterDetails = $ParametersTable + Responses = $responses + OperationId = $operationId + OperationType = $operationType EndpointRelativePath = $EndpointRelativePath PathCommonParameters = $PathCommonParameters - Priority = 100 # Default - 'x-ms-pageable' = $x_ms_pageableObject + Priority = 100 # Default + 'x-ms-pageable' = $x_ms_pageableObject } if ((Get-Member -InputObject $_.Value -Name 'x-ms-odata') -and $_.Value.'x-ms-odata') { @@ -420,6 +426,7 @@ function New-SwaggerPath $UseAzureCsharpGenerator = $SwaggerMetaDict['UseAzureCsharpGenerator'] $description = '' + $synopsis = '' $paramBlock = '' $paramHelp = '' $parametersToAdd = @{} @@ -827,16 +834,19 @@ function New-SwaggerPath $defaultParameterSet = $nonUniqueParameterSets | Sort-Object -Property Priority | Select-Object -First 1 $DefaultParameterSetName = $defaultParameterSet.OperationId $description = $defaultParameterSet.Description + $synopsis = $defaultParameterSet.Synopsis Write-Warning -Message ($LocalizedData.CmdletHasAmbiguousParameterSets -f ($commandName)) } elseif ($nonUniqueParameterSets.Length -eq 1) { # If there's only one non-unique, we can prevent errors by making this the default $DefaultParameterSetName = $nonUniqueParameterSets[0].OperationId $description = $nonUniqueParameterSets[0].Description + $synopsis = $nonUniqueParameterSets[0].Synopsis } else { # Pick the highest priority set among all sets $defaultParameterSet = $parameterSetDetails | Sort-Object @{e = {$_.Priority -as [int] }} | Select-Object -First 1 $DefaultParameterSetName = $defaultParameterSet.OperationId $description = $defaultParameterSet.Description + $synopsis = $defaultParameterSet.Synopsis } $oDataExpression = "" diff --git a/Tests/PSSwaggerScenario.Tests.ps1 b/Tests/PSSwaggerScenario.Tests.ps1 index c993cef..c44a4f0 100644 --- a/Tests/PSSwaggerScenario.Tests.ps1 +++ b/Tests/PSSwaggerScenario.Tests.ps1 @@ -387,6 +387,17 @@ Describe "ParameterTypes tests" -Tag @('ParameterTypes','ScenarioTest') { $CommandList.Name -CContains $_ | Should be $True } } + + It "Test 'Synopsis' and 'Description' help contents of generated commands" { + $HelpInfo1 = Get-Help -Name 'Get-Cupcake' + $HelpInfo1.Description.Text | Should BeExactly 'Make a cupcake or update an existing one.' + $HelpInfo1.Synopsis | Should BeExactly 'List all cupcakes matching parameters' + + $HelpInfo2 = Get-Help -Name 'New-DefWithDummyRefObject' + $ExpectedText = 'The workflow properties.' + $HelpInfo2.Description.Text | Should BeExactly $ExpectedText + $HelpInfo2.Synopsis | Should BeExactly $ExpectedText + } } AfterAll { From f31ce374f6ee392282b04a6b89b2135f8dd28a99 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Thu, 21 Sep 2017 13:13:05 -0700 Subject: [PATCH 24/40] Add support for AdditionalProperties Json schema with array type (#339) * Added support for AdditionalProperties Json schema with array type. ```json "type": "string", "additionalProperties": { "type": "array", "items": { "type": "string" } } ``` Current error ```powershell Generating module for 'C:\temp\AzureSwaggerSpecs\SwaggerSpecs\network\applicationGateway.json'. WARNING: 'ParameterJsonObject' has unsupported properties. type additionalProperties description ---- -------------------- ----------- string @{type=array; items=; description=List of IP Addresses within the tag (key)} Mapping of tags to list of IP Addresses included within the tag. ``` * Added support for $ParameterJsonObject.AdditionalProperties.Items.'$ref' --- PSSwagger/Definitions.psm1 | 38 ++++++++++ PSSwagger/SwaggerUtils.psm1 | 38 ++++++++++ Tests/PSSwaggerScenario.Tests.ps1 | 13 ++++ .../ParameterTypes/ParameterTypesSpec.json | 72 +++++++++++++++++++ 4 files changed, 161 insertions(+) diff --git a/PSSwagger/Definitions.psm1 b/PSSwagger/Definitions.psm1 index 9d7906e..16687da 100644 --- a/PSSwagger/Definitions.psm1 +++ b/PSSwagger/Definitions.psm1 @@ -383,6 +383,44 @@ function Get-DefinitionParameterType Write-Warning -Message $Message } } + elseif($ParameterJsonObject.Type -eq 'string') { + if((Get-Member -InputObject $ParameterJsonObject.AdditionalProperties -Name 'Type') -and + ($ParameterJsonObject.AdditionalProperties.Type -eq 'array')) + { + if(Get-Member -InputObject $ParameterJsonObject.AdditionalProperties -Name 'Items') + { + if((Get-Member -InputObject $ParameterJsonObject.AdditionalProperties.Items -Name 'Type') -and + $ParameterJsonObject.AdditionalProperties.Items.Type) + { + $ItemsType = Get-PSTypeFromSwaggerObject -JsonObject $ParameterJsonObject.AdditionalProperties.Items + $ParameterType = "System.Collections.Generic.Dictionary[[string],[System.Collections.Generic.List[$ItemsType]]]" + } + elseif((Get-Member -InputObject $ParameterJsonObject.AdditionalProperties.Items -Name '$ref') -and + $ParameterJsonObject.AdditionalProperties.Items.'$ref') + { + $ReferenceTypeValue = $ParameterJsonObject.AdditionalProperties.Items.'$ref' + $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceTypeValue.Substring( $( $ReferenceTypeValue.LastIndexOf('/') ) + 1 ) + $ItemsType = $DefinitionTypeNamePrefix + "$ReferenceTypeName" + $ParameterType = "System.Collections.Generic.Dictionary[[string],[System.Collections.Generic.List[$ItemsType]]]" + } + else + { + $Message = $LocalizedData.UnsupportedSwaggerProperties -f ('ParameterJsonObject', $($ParameterJsonObject | Out-String)) + Write-Warning -Message $Message + } + } + else + { + $Message = $LocalizedData.UnsupportedSwaggerProperties -f ('ParameterJsonObject', $($ParameterJsonObject | Out-String)) + Write-Warning -Message $Message + } + } + else + { + $Message = $LocalizedData.UnsupportedSwaggerProperties -f ('ParameterJsonObject', $($ParameterJsonObject | Out-String)) + Write-Warning -Message $Message + } + } else { $Message = $LocalizedData.UnsupportedSwaggerProperties -f ('ParameterJsonObject', $($ParameterJsonObject | Out-String)) Write-Warning -Message $Message diff --git a/PSSwagger/SwaggerUtils.psm1 b/PSSwagger/SwaggerUtils.psm1 index d6cffba..c5571e7 100644 --- a/PSSwagger/SwaggerUtils.psm1 +++ b/PSSwagger/SwaggerUtils.psm1 @@ -987,6 +987,44 @@ function Get-ParamType Write-Warning -Message $Message } } + elseif($ParameterJsonObject.Type -eq 'string') { + if((Get-Member -InputObject $ParameterJsonObject.AdditionalProperties -Name 'Type') -and + ($ParameterJsonObject.AdditionalProperties.Type -eq 'array')) + { + if(Get-Member -InputObject $ParameterJsonObject.AdditionalProperties -Name 'Items') + { + if((Get-Member -InputObject $ParameterJsonObject.AdditionalProperties.Items -Name 'Type') -and + $ParameterJsonObject.AdditionalProperties.Items.Type) + { + $ItemsType = Get-PSTypeFromSwaggerObject -JsonObject $ParameterJsonObject.AdditionalProperties.Items + $paramType = "System.Collections.Generic.Dictionary[[string],[System.Collections.Generic.List[$ItemsType]]]" + } + elseif((Get-Member -InputObject $ParameterJsonObject.AdditionalProperties.Items -Name '$ref') -and + $ParameterJsonObject.AdditionalProperties.Items.'$ref') + { + $ReferenceTypeValue = $ParameterJsonObject.AdditionalProperties.Items.'$ref' + $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceTypeValue.Substring( $( $ReferenceTypeValue.LastIndexOf('/') ) + 1 ) + $ItemsType = $DefinitionTypeNamePrefix + "$ReferenceTypeName" + $paramType = "System.Collections.Generic.Dictionary[[string],[System.Collections.Generic.List[$ItemsType]]]" + } + else + { + $Message = $LocalizedData.UnsupportedSwaggerProperties -f ('ParameterJsonObject', $($ParameterJsonObject | Out-String)) + Write-Warning -Message $Message + } + } + else + { + $Message = $LocalizedData.UnsupportedSwaggerProperties -f ('ParameterJsonObject', $($ParameterJsonObject | Out-String)) + Write-Warning -Message $Message + } + } + else + { + $Message = $LocalizedData.UnsupportedSwaggerProperties -f ('ParameterJsonObject', $($ParameterJsonObject | Out-String)) + Write-Warning -Message $Message + } + } else { $Message = $LocalizedData.UnsupportedSwaggerProperties -f ('ParameterJsonObject', $($ParameterJsonObject | Out-String)) Write-Warning -Message $Message diff --git a/Tests/PSSwaggerScenario.Tests.ps1 b/Tests/PSSwaggerScenario.Tests.ps1 index c44a4f0..c7e8003 100644 --- a/Tests/PSSwaggerScenario.Tests.ps1 +++ b/Tests/PSSwaggerScenario.Tests.ps1 @@ -398,6 +398,19 @@ Describe "ParameterTypes tests" -Tag @('ParameterTypes','ScenarioTest') { $HelpInfo2.Description.Text | Should BeExactly $ExpectedText $HelpInfo2.Synopsis | Should BeExactly $ExpectedText } + + It 'Test parameter types with array of items in AdditionalProperties json schema' { + $ModuleName = 'Generated.ParamTypes.Module' + $ev = $null + $null = Get-Command -Module $ModuleName -Syntax -ErrorVariable ev + $ev | Should BeNullOrEmpty + + $OperationCommandInfo = Get-Command -name Get-EffectiveNetworkSecurityGroup -Module $ModuleName + $OperationCommandInfo.Parameters.OperationTagMap.ParameterType.ToString() | Should BeExactly 'System.Collections.Generic.IDictionary`2[System.String,System.Collections.Generic.IList`1[System.String]]' + + $NewObjectCommandInfo = Get-Command -Name New-EffectiveNetworkSecurityGroupObject -Module $ModuleName + $NewObjectCommandInfo.Parameters.TagMap.ParameterType.ToString() | Should BeExactly 'System.Collections.Generic.Dictionary`2[System.String,System.Collections.Generic.List`1[System.String]]' + } } AfterAll { diff --git a/Tests/data/ParameterTypes/ParameterTypesSpec.json b/Tests/data/ParameterTypes/ParameterTypesSpec.json index 59fe80a..7f591a1 100644 --- a/Tests/data/ParameterTypes/ParameterTypesSpec.json +++ b/Tests/data/ParameterTypes/ParameterTypesSpec.json @@ -298,6 +298,58 @@ } } } + }, + "/PathWithEffectiveNetworkSecurityGroup": { + "get": { + "summary": "List all dinosaurs matching parameters", + "operationId": "EffectiveNetworkSecurityGroup_Get", + "description": "List all dinosaurs matching parameters", + "parameters": [ + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + }, + { + "name": "OperationTagMap", + "in": "query", + "required": false, + "type": "string", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of IP Addresses within the tag (key)" + }, + "description": "Mapping of tags to list of IP Addresses included within the tag." + }, + { + "name": "EffectiveNetworkSecurityGroup", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/EffectiveNetworkSecurityGroup" + }, + "description": "Group filtering parameters." + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Namespace" + } + }, + "default": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } } }, "definitions": { @@ -538,6 +590,26 @@ } }, "description": "The response of the List Namespace operation." + }, + "EffectiveNetworkSecurityGroup": { + "properties": { + "id": { + "type": "string", + "description": "Unique identifier" + }, + "tagMap": { + "type": "string", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of IP Addresses within the tag (key)" + }, + "description": "Mapping of tags to list of IP Addresses included within the tag." + } + }, + "description": "Effective network security group." } }, "parameters": { From 8fd56eb427b799242bad7ee8cee87e6edb6fa016 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Fri, 22 Sep 2017 11:20:38 -0700 Subject: [PATCH 25/40] Add support for parameter type references to enum definitions. (#341) Resolves #115. --- PSSwagger/Definitions.psm1 | 108 +++++++++++++---- PSSwagger/SwaggerUtils.psm1 | 98 +++++++++++++-- Tests/PSSwaggerScenario.Tests.ps1 | 35 ++++++ .../ParameterTypes/ParameterTypesSpec.json | 113 ++++++++++++++++++ 4 files changed, 321 insertions(+), 33 deletions(-) diff --git a/PSSwagger/Definitions.psm1 b/PSSwagger/Definitions.psm1 index 16687da..39b7aa7 100644 --- a/PSSwagger/Definitions.psm1 +++ b/PSSwagger/Definitions.psm1 @@ -137,6 +137,7 @@ function Get-SwaggerSpecDefinitionInfo $FunctionDetails['ExpandedParameters'] = $ExpandedParameters $DefinitionType = "" + $ValidateSet = $null if ((Get-HashtableKeyCount -Hashtable $ParametersTable) -lt 1) { $GetDefinitionParameterType_params = @{ @@ -145,9 +146,12 @@ function Get-SwaggerSpecDefinitionInfo ModelsNamespace = "$NameSpace.$Models" DefinitionFunctionsDetails = $DefinitionFunctionsDetails } - $DefinitionType = Get-DefinitionParameterType @GetDefinitionParameterType_params + $TypeResult = Get-DefinitionParameterType @GetDefinitionParameterType_params + $DefinitionType = $TypeResult['ParameterType'] + $ValidateSet = $TypeResult['ValidateSet'] } $FunctionDetails['Type'] = $DefinitionType + $FunctionDetails['ValidateSet'] = $ValidateSet if(-not $FunctionDetails.ContainsKey('IsUsedAs_x_ms_client_flatten')) { @@ -238,13 +242,20 @@ function Get-DefinitionParameters $ValidateSetString = $null $ParameterDescription = '' - $ParameterType = Get-DefinitionParameterType -ParameterJsonObject $ParameterJsonObject ` - -DefinitionName $DefinitionName ` - -ParameterName $ParameterName ` - -DefinitionFunctionsDetails $DefinitionFunctionsDetails ` - -ModelsNamespace "$NameSpace.Models" ` - -ParametersTable $ParametersTable + $GetDefinitionParameterType_params = @{ + ParameterJsonObject = $ParameterJsonObject + DefinitionName = $DefinitionName + ParameterName = $ParameterName + DefinitionFunctionsDetails = $DefinitionFunctionsDetails + ModelsNamespace = "$NameSpace.Models" + ParametersTable = $ParametersTable + } + $TypeResult = Get-DefinitionParameterType @GetDefinitionParameterType_params + $ParameterType = $TypeResult['ParameterType'] + if($TypeResult['ValidateSet']) { + $ValidateSetString = "'$($TypeResult['ValidateSet'] -join "', '")'" + } if ((Get-Member -InputObject $JsonDefinitionItemObject.Value -Name 'Required') -and $JsonDefinitionItemObject.Value.Required -and ($JsonDefinitionItemObject.Value.Required -contains $ParameterName) ) @@ -320,6 +331,7 @@ function Get-DefinitionParameterType $DefinitionTypeNamePrefix = "$ModelsNamespace." $ParameterType = $null + $ValidateSet = $null if ((Get-Member -InputObject $ParameterJsonObject -Name 'Type') -and $ParameterJsonObject.Type) { @@ -327,13 +339,18 @@ function Get-DefinitionParameterType # When a definition property has single enum value, AutoRest doesn't generate an enum type. if ((Get-Member -InputObject $ParameterJsonObject -Name 'Enum') -and - ($ParameterJsonObject.Enum.Count -gt 1) -and - (Get-Member -InputObject $ParameterJsonObject -Name 'x-ms-enum') -and - $ParameterJsonObject.'x-ms-enum' -and - (-not (Get-Member -InputObject $ParameterJsonObject.'x-ms-enum' -Name 'modelAsString') -or - ($ParameterJsonObject.'x-ms-enum'.modelAsString -eq $false))) + $ParameterJsonObject.Enum -and ($ParameterJsonObject.Enum.Count -gt 1)) { - $ParameterType = $DefinitionTypeNamePrefix + (Get-CSharpModelName -Name $ParameterJsonObject.'x-ms-enum'.Name) + if ((Get-Member -InputObject $ParameterJsonObject -Name 'x-ms-enum') -and + $ParameterJsonObject.'x-ms-enum' -and + (-not (Get-Member -InputObject $ParameterJsonObject.'x-ms-enum' -Name 'modelAsString') -or + ($ParameterJsonObject.'x-ms-enum'.modelAsString -eq $false))) + { + $ParameterType = $DefinitionTypeNamePrefix + (Get-CSharpModelName -Name $ParameterJsonObject.'x-ms-enum'.Name) + } + else { + $ValidateSet = $ParameterJsonObject.Enum | ForEach-Object {$_ -replace "'", "''"} + } } # Use the format as parameter type if that is available as a type in PowerShell elseif ((Get-Member -InputObject $ParameterJsonObject -Name 'Format') -and @@ -350,8 +367,17 @@ function Get-DefinitionParameterType $ParameterJsonObject.Items.'$ref') { $ReferenceTypeValue = $ParameterJsonObject.Items.'$ref' - $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceTypeValue.Substring( $( $ReferenceTypeValue.LastIndexOf('/') ) + 1 ) - $ParameterType = $DefinitionTypeNamePrefix + "$ReferenceTypeName[]" + $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceTypeValue.Substring( $( $ReferenceTypeValue.LastIndexOf('/') ) + 1 ) + $ResolveReferenceParameterType_params = @{ + DefinitionFunctionsDetails = $DefinitionFunctionsDetails + ReferenceTypeName = $ReferenceTypeName + DefinitionTypeNamePrefix = $DefinitionTypeNamePrefix + } + $ResolvedResult = Resolve-ReferenceParameterType @ResolveReferenceParameterType_params + $ParameterType = $ResolvedResult.ParameterType + '[]' + if($ResolvedResult.ValidateSet) { + $ValidateSet = $ResolvedResult.ValidateSet + } } elseif((Get-Member -InputObject $ParameterJsonObject.Items -Name 'Type') -and $ParameterJsonObject.Items.Type) { @@ -374,9 +400,14 @@ function Get-DefinitionParameterType { $ReferenceTypeValue = $ParameterJsonObject.AdditionalProperties.'$ref' $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceTypeValue.Substring( $( $ReferenceTypeValue.LastIndexOf('/') ) + 1 ) - $AdditionalPropertiesType = $DefinitionTypeNamePrefix + "$ReferenceTypeName" + $ResolveReferenceParameterType_params = @{ + DefinitionFunctionsDetails = $DefinitionFunctionsDetails + ReferenceTypeName = $ReferenceTypeName + DefinitionTypeNamePrefix = $DefinitionTypeNamePrefix + } + $ResolvedResult = Resolve-ReferenceParameterType @ResolveReferenceParameterType_params # Dictionary - $ParameterType = "System.Collections.Generic.Dictionary[[string],[$AdditionalPropertiesType]]" + $ParameterType = "System.Collections.Generic.Dictionary[[string],[$($ResolvedResult.ParameterType)]]" } else { $Message = $LocalizedData.UnsupportedSwaggerProperties -f ('ParameterJsonObject', $($ParameterJsonObject | Out-String)) @@ -400,8 +431,13 @@ function Get-DefinitionParameterType { $ReferenceTypeValue = $ParameterJsonObject.AdditionalProperties.Items.'$ref' $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceTypeValue.Substring( $( $ReferenceTypeValue.LastIndexOf('/') ) + 1 ) - $ItemsType = $DefinitionTypeNamePrefix + "$ReferenceTypeName" - $ParameterType = "System.Collections.Generic.Dictionary[[string],[System.Collections.Generic.List[$ItemsType]]]" + $ResolveReferenceParameterType_params = @{ + DefinitionFunctionsDetails = $DefinitionFunctionsDetails + ReferenceTypeName = $ReferenceTypeName + DefinitionTypeNamePrefix = $DefinitionTypeNamePrefix + } + $ResolvedResult = Resolve-ReferenceParameterType @ResolveReferenceParameterType_params + $ParameterType = "System.Collections.Generic.Dictionary[[string],[System.Collections.Generic.List[$($ResolvedResult.ParameterType)]]]" } else { @@ -461,7 +497,16 @@ function Get-DefinitionParameterType { $ReferenceParameterValue = $ParameterJsonObject.'$ref' $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceParameterValue.Substring( $( $ReferenceParameterValue.LastIndexOf('/') ) + 1 ) - $ParameterType = $DefinitionTypeNamePrefix + $ReferenceTypeName + $ResolveReferenceParameterType_params = @{ + DefinitionFunctionsDetails = $DefinitionFunctionsDetails + ReferenceTypeName = $ReferenceTypeName + DefinitionTypeNamePrefix = $DefinitionTypeNamePrefix + } + $ResolvedResult = Resolve-ReferenceParameterType @ResolveReferenceParameterType_params + $ParameterType = $ResolvedResult.ParameterType + if($ResolvedResult.ValidateSet) { + $ValidateSet = $ResolvedResult.ValidateSet + } } else { @@ -477,7 +522,10 @@ function Get-DefinitionParameterType Write-Warning -Message ($LocalizedData.InvalidDefinitionParameterType -f $ParameterType, $ParameterName, $DefinitionName) } - return $ParameterType + return @{ + ParameterType = $ParameterType + ValidateSet = $ValidateSet + } } function Expand-SwaggerDefinition @@ -778,13 +826,25 @@ function Expand-NonModelDefinition { $FunctionDetails.ParametersTable.GetEnumerator() | ForEach-Object { $ParameterDetails = $_.Value - if ($ParameterDetails.Type -eq "$Namespace.$Models.$($DefFunctionDetails.Name)") { + if (($ParameterDetails.Type -eq "$Namespace.$Models.$($DefFunctionDetails.Name)") -or + ($ParameterDetails.Type -eq "$Namespace.$Models.$($DefFunctionDetails.Name)[]")) { + if($SourceDetails.ContainsKey('Type')) { - $ParameterDetails['Type'] = $SourceDetails.Type + if($ParameterDetails.Type -eq "$Namespace.$Models.$($DefFunctionDetails.Name)[]") { + $ParameterDetails['Type'] = $SourceDetails.Type + '[]' + } + else { + $ParameterDetails['Type'] = $SourceDetails.Type + } } if($SourceDetails.ContainsKey('ValidateSet')) { - $ParameterDetails['ValidateSet'] = $SourceDetails.ValidateSet + if($SourceDetails.ValidateSet.PSTypeNames -contains 'System.Array') { + $ParameterDetails['ValidateSet'] = "'$($SourceDetails.ValidateSet -join "', '")'" + } + else { + $ParameterDetails['ValidateSet'] = $SourceDetails.ValidateSet + } } if((-not $ParameterDetails.Description) -and diff --git a/PSSwagger/SwaggerUtils.psm1 b/PSSwagger/SwaggerUtils.psm1 index c5571e7..58f7971 100644 --- a/PSSwagger/SwaggerUtils.psm1 +++ b/PSSwagger/SwaggerUtils.psm1 @@ -956,7 +956,16 @@ function Get-ParamType { $ReferenceTypeValue = $ParameterJsonObject.Items.'$ref' $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceTypeValue.Substring( $( $ReferenceTypeValue.LastIndexOf('/') ) + 1 ) - $paramType = $DefinitionTypeNamePrefix + "$ReferenceTypeName[]" + $ResolveReferenceParameterType_params = @{ + DefinitionFunctionsDetails = $DefinitionFunctionsDetails + ReferenceTypeName = $ReferenceTypeName + DefinitionTypeNamePrefix = $DefinitionTypeNamePrefix + } + $ResolvedResult = Resolve-ReferenceParameterType @ResolveReferenceParameterType_params + $paramType = $ResolvedResult.ParameterType + '[]' + if($ResolvedResult.ValidateSetString) { + $ValidateSetString = $ResolvedResult.ValidateSetString + } } elseif((Get-Member -InputObject $ParameterJsonObject.Items -Name 'Type') -and $ParameterJsonObject.Items.Type) { @@ -979,8 +988,13 @@ function Get-ParamType { $ReferenceTypeValue = $ParameterJsonObject.AdditionalProperties.'$ref' $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceTypeValue.Substring( $( $ReferenceTypeValue.LastIndexOf('/') ) + 1 ) - $AdditionalPropertiesType = $DefinitionTypeNamePrefix + "$ReferenceTypeName" - $paramType = "System.Collections.Generic.Dictionary[[string],[$AdditionalPropertiesType]]" + $ResolveReferenceParameterType_params = @{ + DefinitionFunctionsDetails = $DefinitionFunctionsDetails + ReferenceTypeName = $ReferenceTypeName + DefinitionTypeNamePrefix = $DefinitionTypeNamePrefix + } + $ResolvedResult = Resolve-ReferenceParameterType @ResolveReferenceParameterType_params + $paramType = "System.Collections.Generic.Dictionary[[string],[$($ResolvedResult.ParameterType)]]" } else { $Message = $LocalizedData.UnsupportedSwaggerProperties -f ('ParameterJsonObject', $($ParameterJsonObject | Out-String)) @@ -1004,8 +1018,13 @@ function Get-ParamType { $ReferenceTypeValue = $ParameterJsonObject.AdditionalProperties.Items.'$ref' $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceTypeValue.Substring( $( $ReferenceTypeValue.LastIndexOf('/') ) + 1 ) - $ItemsType = $DefinitionTypeNamePrefix + "$ReferenceTypeName" - $paramType = "System.Collections.Generic.Dictionary[[string],[System.Collections.Generic.List[$ItemsType]]]" + $ResolveReferenceParameterType_params = @{ + DefinitionFunctionsDetails = $DefinitionFunctionsDetails + ReferenceTypeName = $ReferenceTypeName + DefinitionTypeNamePrefix = $DefinitionTypeNamePrefix + } + $ResolvedResult = Resolve-ReferenceParameterType @ResolveReferenceParameterType_params + $paramType = "System.Collections.Generic.Dictionary[[string],[System.Collections.Generic.List[$($ResolvedResult.ParameterType)]]]" } else { @@ -1079,7 +1098,16 @@ function Get-ParamType { # #<...>/definitions/ $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceParts[-1] - $paramType = $DefinitionTypeNamePrefix + $ReferenceTypeName + $ResolveReferenceParameterType_params = @{ + DefinitionFunctionsDetails = $DefinitionFunctionsDetails + ReferenceTypeName = $ReferenceTypeName + DefinitionTypeNamePrefix = $DefinitionTypeNamePrefix + } + $ResolvedResult = Resolve-ReferenceParameterType @ResolveReferenceParameterType_params + $paramType = $ResolvedResult.ParameterType + if($ResolvedResult.ValidateSetString) { + $ValidateSetString = $ResolvedResult.ValidateSetString + } } } } @@ -1088,7 +1116,17 @@ function Get-ParamType { $ReferenceParameterValue = $ParameterJsonObject.Schema.'$ref' $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceParameterValue.Substring( $( $ReferenceParameterValue.LastIndexOf('/') ) + 1 ) - $paramType = $DefinitionTypeNamePrefix + $ReferenceTypeName + + $ResolveReferenceParameterType_params = @{ + DefinitionFunctionsDetails = $DefinitionFunctionsDetails + ReferenceTypeName = $ReferenceTypeName + DefinitionTypeNamePrefix = $DefinitionTypeNamePrefix + } + $ResolvedResult = Resolve-ReferenceParameterType @ResolveReferenceParameterType_params + $paramType = $ResolvedResult.ParameterType + if($ResolvedResult.ValidateSetString) { + $ValidateSetString = $ResolvedResult.ValidateSetString + } if((Get-Member -InputObject $ParameterJsonObject -Name 'x-ms-client-flatten') -and ($ParameterJsonObject.'x-ms-client-flatten')) @@ -1632,8 +1670,9 @@ function Get-OutputType $DefRefParts = $defRef -split '/' | ForEach-Object { if($_.Trim()){ $_.Trim() } } if(($DefRefParts.Count -ge 3) -and ($DefRefParts[-2] -eq 'definitions')) { - $defKey = Get-CSharpModelName -Name $DefRefParts[-1] - $fullPathDataType = "$ModelsNamespace.$defKey" + $ReferenceTypeName = $DefRefParts[-1] + $ReferenceTypeName = Get-CSharpModelName -Name $ReferenceTypeName + $fullPathDataType = "$ModelsNamespace.$ReferenceTypeName" } if(Get-member -InputObject $defValue -Name 'type') { @@ -1838,3 +1877,44 @@ function Get-PowerShellCodeGenSettings { } } } + +function Resolve-ReferenceParameterType { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $ReferenceTypeName, + + [Parameter(Mandatory = $true)] + [string] + $DefinitionTypeNamePrefix, + + [Parameter(Mandatory = $true)] + [PSCustomObject] + $DefinitionFunctionsDetails + ) + + $ParameterType = $DefinitionTypeNamePrefix + $ReferenceTypeName + $ValidateSet = $null + $ValidateSetString = $null + + # Some referenced definitions can be non-models like enums with validateset. + if ($DefinitionFunctionsDetails.ContainsKey($ReferenceTypeName) -and + $DefinitionFunctionsDetails[$ReferenceTypeName].ContainsKey('Type') -and + $DefinitionFunctionsDetails[$ReferenceTypeName].Type -and + -not $DefinitionFunctionsDetails[$ReferenceTypeName].IsModel) + { + $ParameterType = $DefinitionFunctionsDetails[$ReferenceTypeName].Type + + if($DefinitionFunctionsDetails[$ReferenceTypeName].ValidateSet) { + $ValidateSet = $DefinitionFunctionsDetails[$ReferenceTypeName].ValidateSet + $ValidateSetString = "'$($ValidateSet -join "', '")'" + } + } + + return @{ + ParameterType = $ParameterType + ValidateSet = $ValidateSet + ValidateSetString = $ValidateSetString + } +} \ No newline at end of file diff --git a/Tests/PSSwaggerScenario.Tests.ps1 b/Tests/PSSwaggerScenario.Tests.ps1 index c7e8003..95f0717 100644 --- a/Tests/PSSwaggerScenario.Tests.ps1 +++ b/Tests/PSSwaggerScenario.Tests.ps1 @@ -411,6 +411,41 @@ Describe "ParameterTypes tests" -Tag @('ParameterTypes','ScenarioTest') { $NewObjectCommandInfo = Get-Command -Name New-EffectiveNetworkSecurityGroupObject -Module $ModuleName $NewObjectCommandInfo.Parameters.TagMap.ParameterType.ToString() | Should BeExactly 'System.Collections.Generic.Dictionary`2[System.String,System.Collections.Generic.List`1[System.String]]' } + + It 'Test parameter types with references to enum definition type' { + $ModuleName = 'Generated.ParamTypes.Module' + + # Swagger operation command with parameter type reference to enum definition type + $OperationCommandInfo = Get-Command -Name Get-PathWithEnumDefinitionType -Module $ModuleName + + $OperationCommandInfo.Parameters.PolicyNameEnumParameter.ParameterType.ToString() | Should BeExactly 'System.String' + @('AppGwSslPolicy20150501', 'AppGwSslPolicy20170401','AppGwSslPolicy20170401S') | ForEach-Object { + $OperationCommandInfo.Parameters.PolicyNameEnumParameter.Attributes.ValidValues -contains $_ | Should Be $true + } + + # Swagger definition command with parameter type reference to enum definition type + $NewObjectCommandInfo = Get-Command -Name New-ApplicationGatewaySslPolicyObject -Module $ModuleName + + $NewObjectCommandInfo.Parameters.PolicyType.ParameterType.ToString() | Should BeExactly 'System.String' + @('Predefined','Custom') | ForEach-Object { + $NewObjectCommandInfo.Parameters.PolicyType.Attributes.ValidValues -contains $_ | Should Be $true + } + + $NewObjectCommandInfo.Parameters.DisabledSslProtocols.ParameterType.ToString() | Should BeExactly 'System.String[]' + @('TLSv1_0','TLSv1_1', 'TLSv1_2') | ForEach-Object { + $NewObjectCommandInfo.Parameters.DisabledSslProtocols.Attributes.ValidValues -contains $_ | Should Be $true + } + + $NewObjectCommandInfo.Parameters.PolicyName.ParameterType.ToString() | Should BeExactly 'System.String' + @('AppGwSslPolicy20150501', 'AppGwSslPolicy20170401','AppGwSslPolicy20170401S') | ForEach-Object { + $NewObjectCommandInfo.Parameters.PolicyName.Attributes.ValidValues -contains $_ | Should Be $true + } + + $NewObjectCommandInfo.Parameters.MinProtocolVersion.ParameterType.ToString() | Should BeExactly 'System.String' + @('TLSv1_0','TLSv1_1', 'TLSv1_2') | ForEach-Object { + $NewObjectCommandInfo.Parameters.MinProtocolVersion.Attributes.ValidValues -contains $_ | Should Be $true + } + } } AfterAll { diff --git a/Tests/data/ParameterTypes/ParameterTypesSpec.json b/Tests/data/ParameterTypes/ParameterTypesSpec.json index 7f591a1..39a873c 100644 --- a/Tests/data/ParameterTypes/ParameterTypesSpec.json +++ b/Tests/data/ParameterTypes/ParameterTypesSpec.json @@ -350,6 +350,56 @@ } } } + }, + "/PathWithEnumDefinitionType": { + "get": { + "summary": "List all dinosaurs matching parameters", + "operationId": "PathWithEnumDefinitionType_Get", + "description": "List all dinosaurs matching parameters", + "parameters": [ + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + }, + { + "name": "ApplicationGatewaySslPolicy", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/ApplicationGatewaySslPolicy" + }, + "description": "Group filtering parameters." + }, + { + "name": "PolicyNameEnumParameter", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/PolicyNameEnum" + }, + "description": "Group filtering parameters." + } + ], + "tags": [ + "Dinosaurs" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Namespace" + } + }, + "default": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } } }, "definitions": { @@ -610,6 +660,69 @@ } }, "description": "Effective network security group." + }, + "PolicyNameEnum": { + "type": "string", + "description": "Ssl predefined policy name enums.", + "enum": [ + "AppGwSslPolicy20150501", + "AppGwSslPolicy20170401", + "AppGwSslPolicy20170401S" + ], + "x-ms-enum": { + "name": "ApplicationGatewaySslPolicyName", + "modelAsString": true + } + }, + "ApplicationGatewaySslPolicy": { + "properties": { + "disabledSslProtocols": { + "type": "array", + "description": "Ssl protocols to be disabled on application gateway.", + "items": { + "type": "string", + "$ref": "#/definitions/ProtocolsEnum", + "x-ms-enum": { + "name": "ApplicationGatewaySslProtocol", + "modelAsString": true + } + } + }, + "policyType": { + "type": "string", + "description": "Type of Ssl Policy", + "enum": [ + "Predefined", + "Custom" + ], + "x-ms-enum": { + "name": "ApplicationGatewaySslPolicyType", + "modelAsString": true + } + }, + "policyName": { + "$ref": "#/definitions/PolicyNameEnum", + "description": "Name of Ssl predefined policy" + }, + "minProtocolVersion": { + "$ref": "#/definitions/ProtocolsEnum", + "description": "Minimum version of Ssl protocol to be supported on application gateway." + } + }, + "description": "Application Gateway Ssl policy." + }, + "ProtocolsEnum": { + "type": "string", + "description": "Ssl protocol enums.", + "enum": [ + "TLSv1_0", + "TLSv1_1", + "TLSv1_2" + ], + "x-ms-enum": { + "name": "ApplicationGatewaySslProtocol", + "modelAsString": true + } } }, "parameters": { From f0e1c5ace61b654a111f86595ba4dce3b34251d9 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Thu, 28 Sep 2017 15:26:20 -0700 Subject: [PATCH 26/40] Add AutoRest version in run-tests.ps1 (#344) * Added AutoRest version in run-tests.ps1 --- Tests/run-tests.ps1 | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Tests/run-tests.ps1 b/Tests/run-tests.ps1 index 6440b44..61d2a92 100644 --- a/Tests/run-tests.ps1 +++ b/Tests/run-tests.ps1 @@ -27,6 +27,7 @@ $nugetPackageSource = Test-NugetPackageSource $nodeModuleVersions = @{} $NodeJSVersion = '7.10.0' $NodeJSPackageName = "node-v$NodeJSVersion-win-x64" +$AutoRestVersion = '1.2.2' # Note: If we use the $PSScriptRoot, Expand-Archive cmdlet is failing with path too long error (260 characters limit). # So using $env:SystemDrive\$NodeJSPackageName path for now. @@ -64,22 +65,18 @@ if (-not (Test-Path -Path $NodeModulesPath -PathType Container)) { # Install AutoRest using NPM, if not installed already. $AutorestCmdPath = Join-Path -Path $NodeModulesPath -ChildPath 'autorest.cmd' if (-not (Test-Path -Path $AutorestCmdPath -PathType Leaf)) { - Write-Verbose "Couldn't find $AutorestCmdPath. Running 'npm install -g autorest'." - & $NpmCmdPath install -g --prefix $NodeModulesPath autorest --scripts-prepend-node-path + Write-Verbose "Couldn't find $AutorestCmdPath. Running 'npm install -g autorest@$AutoRestVersion'." + & $NpmCmdPath install -g --prefix $NodeModulesPath "autorest@$AutoRestVersion" --scripts-prepend-node-path +} +else { + $npmListResult = & $NpmCmdPath list -g --prefix $NodeModulesPath "autorest@$AutoRestVersion" + if("$npmListResult" -notmatch "autorest@$AutoRestVersion") { + Write-Warning "The required AutoRest version to run the PSSwagger tests is $AutoRestVersion. You might run into some test failures if the installed version '$npmListResult' is incompatible with the current version of PSSwagger." + } } $nodeModuleVersions['autorest'] = & $NpmCmdPath list -g --prefix $NodeModulesPath autorest $executeTestsCommand += ";`$env:Path =`"$NodeModulesPath;`$env:Path`"" -$AutoRestPluginPath = Join-Path -Path $env:USERPROFILE -ChildPath '.autorest' | - Join-Path -ChildPath 'plugins' | - Join-Path -ChildPath 'autorest' - -if (-not ((Test-Path -Path $AutoRestPluginPath -PathType Container) -and - (Get-ChildItem -Path $AutoRestPluginPath -Directory))) { - # Create the generator plugins - & $AutorestCmdPath --reset -} - $testRunGuid = [guid]::NewGuid().GUID Write-Verbose -message "Test run GUID: $testRunGuid" # Set up scenario test requirements From fb8b946a2632227e201958e29417f4285e68ec81 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Thu, 28 Sep 2017 15:45:45 -0700 Subject: [PATCH 27/40] Add support for generating proper output type for the Swagger operations with x-ms-pageable extension (#342) * Added support for generating proper output type for the Swaagger operations with x-ms-pageable extension. --- PSSwagger/Paths.psm1 | 16 +++- Tests/PSSwagger.Unit.Tests.ps1 | 21 ++++- Tests/PSSwaggerScenario.Tests.ps1 | 42 +++++++--- .../AzureExtensions/AzureExtensionsSpec.json | 81 +++++++++++++++++++ 4 files changed, 145 insertions(+), 15 deletions(-) diff --git a/PSSwagger/Paths.psm1 b/PSSwagger/Paths.psm1 index 3316da6..a771d0e 100644 --- a/PSSwagger/Paths.psm1 +++ b/PSSwagger/Paths.psm1 @@ -561,7 +561,7 @@ function New-SwaggerPath $Cmdlet = '' $CmdletParameter = '' $CmdletArgs = '' - $pageType = 'Array' + $pageType = '' $resultBlockStr = $resultBlockNoPaging if ($x_ms_pageableObject) { if ($x_ms_pageableObject.ReturnType -ne 'NONE') { @@ -988,7 +988,13 @@ function New-SwaggerPath $bodyObject = $pathGenerationPhaseResult.BodyObject $body = $bodyObject.Body - $outputTypeBlock = $bodyObject.OutputTypeBlock + if($pageType){ + $fullPathDataType = $pageType + $outputTypeBlock = $executionContext.InvokeCommand.ExpandString($outputTypeStr) + } + else { + $outputTypeBlock = $bodyObject.OutputTypeBlock + } if ($UseAzureCsharpGenerator) { $dependencyInitFunction = "Initialize-PSSwaggerDependencies -Azure" @@ -1150,6 +1156,10 @@ function Set-ExtendedCodeMetadata { $returnType = $returnType.GenericTypeArguments[0] } + if (($returnType.Name -eq 'IPage`1') -and $returnType.GenericTypeArguments) { + $returnType = $returnType.GenericTypeArguments[0] + } + # Note: ReturnType is currently used for Swagger operations which supports x-ms-pageable. $returnTypeString = Convert-GenericTypeToString -Type $returnType $parameterSetDetail['ReturnType'] = $returnTypeString @@ -1272,7 +1282,7 @@ function Convert-GenericTypeToString { ) if (-not $Type.IsGenericType) { - return $Type.FullName + return $Type.FullName.Trim('[]') } $genericTypeStr = '' diff --git a/Tests/PSSwagger.Unit.Tests.ps1 b/Tests/PSSwagger.Unit.Tests.ps1 index c531150..da4aa69 100644 --- a/Tests/PSSwagger.Unit.Tests.ps1 +++ b/Tests/PSSwagger.Unit.Tests.ps1 @@ -269,17 +269,32 @@ Describe "PSSwagger Unit Tests" -Tag @('BVT', 'DRT', 'UnitTest', 'P0') { } Context "Get-CSharpModelName Unit Tests" { - It "Get-CSharpModelNamee should remove special characters" { + It "Get-CSharpModelName should remove special characters" { Get-CSharpModelName -Name @" SomeTypeWithSpecialCharacters ~!@#$%^&*()_+|}{:"<>?,./;'][\=-`` "@ | Should BeExactly 'SomeTypeWithSpecialCharacters' } - It "Get-CSharpModelNamee should replace [] with Sequence" { + It "Get-CSharpModelName should replace [] with Sequence" { Get-CSharpModelName -Name 'foo[]' | Should BeExactly 'FooSequence' } - It "Get-CSharpModelNamee should append 'Model' for C# reserved words" { + It "Get-CSharpModelName should append 'Model' for C# reserved words" { Get-CSharpModelName -Name 'break' | Should BeExactly 'BreakModel' } } + + Context "Convert-GenericTypeToString Unit Tests" { + It "Convert-GenericTypeToString with 'System.Int32' type" { + Convert-GenericTypeToString -Type ('System.Int32' -as [type]) | Should BeExactly 'System.Int32' + } + It "Convert-GenericTypeToString with 'System.String[]' type" { + Convert-GenericTypeToString -Type ('System.String[]' -as [type]) | Should BeExactly 'System.String' + } + It "Convert-GenericTypeToString with 'System.Collections.Generic.List[string]' type" { + Convert-GenericTypeToString -Type ('System.Collections.Generic.List[string]' -as [type]) | Should BeExactly 'System.Collections.Generic.List[System.String]' + } + It "Convert-GenericTypeToString with 'System.Collections.Generic.Dictionary[string, string]' type" { + Convert-GenericTypeToString -Type ('System.Collections.Generic.Dictionary[string, string]' -as [type]) | Should BeExactly 'System.Collections.Generic.Dictionary[System.String,System.String]' + } + } } } diff --git a/Tests/PSSwaggerScenario.Tests.ps1 b/Tests/PSSwaggerScenario.Tests.ps1 index 95f0717..4884a56 100644 --- a/Tests/PSSwaggerScenario.Tests.ps1 +++ b/Tests/PSSwaggerScenario.Tests.ps1 @@ -245,16 +245,17 @@ Describe "Optional parameter tests" -Tag ScenarioTest { Describe "ParameterTypes tests" -Tag @('ParameterTypes','ScenarioTest') { BeforeAll { + $ModuleName = 'Generated.ParamTypes.Module' Import-Module (Join-Path -Path $PSScriptRoot -ChildPath ".." | Join-Path -ChildPath "PSSwagger" | Join-Path -ChildPath "PSSwaggerUtility" | ` Join-Path -ChildPath "PSSwaggerUtility.psd1") -Force - Initialize-Test -GeneratedModuleName "Generated.ParamTypes.Module" -GeneratedModuleVersion "0.0.2" -TestApiName "ParameterTypes" ` + Initialize-Test -GeneratedModuleName $ModuleName -GeneratedModuleVersion "0.0.2" -TestApiName "ParameterTypes" ` -TestSpecFileName "ParameterTypesSpec.json" -TestDataFileName "ParameterTypesData.json" ` -PsSwaggerPath (Join-Path -Path $PSScriptRoot -ChildPath ".." | Join-Path -ChildPath "PSSwagger") -TestRootPath $PSScriptRoot # Import generated module Write-Verbose "Importing modules" Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "Generated" | ` - Join-Path -ChildPath "Generated.ParamTypes.Module") + Join-Path -ChildPath $ModuleName) $processes = Start-JsonServer -TestRootPath $PSScriptRoot -TestApiName "ParameterTypes" if ($global:PSSwaggerTest_EnableTracing -and $script:EnableTracer) { @@ -325,7 +326,6 @@ Describe "ParameterTypes tests" -Tag @('ParameterTypes','ScenarioTest') { } It "Test dummy definition references" { - $ModuleName = 'Generated.ParamTypes.Module' $ev = $null $CommandList = Get-Command -Module $ModuleName -ErrorVariable ev $ev | Should BeNullOrEmpty @@ -345,7 +345,6 @@ Describe "ParameterTypes tests" -Tag @('ParameterTypes','ScenarioTest') { } It "Test CSharp reserved keywords as definition or type names" { - $ModuleName = 'Generated.ParamTypes.Module' $ev = $null $CommandList = Get-Command -Module $ModuleName -ErrorVariable ev $ev | Should BeNullOrEmpty @@ -375,7 +374,6 @@ Describe "ParameterTypes tests" -Tag @('ParameterTypes','ScenarioTest') { } It "Test Definition commands 'New-Object' for nested definitions" { - $ModuleName = 'Generated.ParamTypes.Module' $ev = $null $CommandList = Get-Command -Module $ModuleName -ErrorVariable ev $ev | Should BeNullOrEmpty @@ -400,7 +398,6 @@ Describe "ParameterTypes tests" -Tag @('ParameterTypes','ScenarioTest') { } It 'Test parameter types with array of items in AdditionalProperties json schema' { - $ModuleName = 'Generated.ParamTypes.Module' $ev = $null $null = Get-Command -Module $ModuleName -Syntax -ErrorVariable ev $ev | Should BeNullOrEmpty @@ -413,8 +410,6 @@ Describe "ParameterTypes tests" -Tag @('ParameterTypes','ScenarioTest') { } It 'Test parameter types with references to enum definition type' { - $ModuleName = 'Generated.ParamTypes.Module' - # Swagger operation command with parameter type reference to enum definition type $OperationCommandInfo = Get-Command -Name Get-PathWithEnumDefinitionType -Module $ModuleName @@ -453,7 +448,7 @@ Describe "ParameterTypes tests" -Tag @('ParameterTypes','ScenarioTest') { } } -Describe "AzureExtensions" { +Describe "AzureExtensions" -Tag @('AzureExtension','ScenarioTest') { BeforeAll { Import-Module (Join-Path -Path $PSScriptRoot -ChildPath ".." | Join-Path -ChildPath "PSSwagger" | Join-Path -ChildPath "PSSwaggerUtility" | ` Join-Path -ChildPath "PSSwaggerUtility.psd1") -Force @@ -1118,3 +1113,32 @@ Describe "Pre-compiled SDK Assmebly scenario tests" -Tag @('SDKAssembly','Scenar (Remove-TestErrorId -FullyQualifiedErrorId $ev.FullyQualifiedErrorId) | Should Be 'UnableToExtractDetailsFromSdkAssembly,Update-PathFunctionDetails' } } + +Describe "Output type scenario tests" -Tag @('OutputType','ScenarioTest') { + BeforeAll { + $ModuleName = 'Generated.AzExt.OutputType.Module' + $SwaggerSpecPath = Join-Path -Path $PSScriptRoot -ChildPath 'Data' | Join-Path -ChildPath 'AzureExtensions' | Join-Path -ChildPath 'AzureExtensionsSpec.json' + $GeneratedPath = Join-Path -Path $PSScriptRoot -ChildPath 'Generated' + $GeneratedModuleBase = Join-Path -Path $GeneratedPath -ChildPath $ModuleName + if (Test-Path -Path $GeneratedModuleBase -PathType Container) { + Remove-Item -Path $GeneratedModuleBase -Recurse -Force + } + + $params = @{ + SpecificationPath = $SwaggerSpecPath + Name = $ModuleName + UseAzureCsharpGenerator = $true + Path = $GeneratedPath + ConfirmBootstrap = $true + Verbose = $true + } + Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $params + + Import-Module $GeneratedModuleBase -Force + } + + It 'Test output type of swagger operation which supports x-ms-pageable' { + $CommandInfo = Get-Command -Name Get-IotHubResourceEventHubConsumerGroup -Module $ModuleName + $CommandInfo.OutputType.Type.ToString() | Should BeExactly 'System.String' + } +} \ No newline at end of file diff --git a/Tests/data/AzureExtensions/AzureExtensionsSpec.json b/Tests/data/AzureExtensions/AzureExtensionsSpec.json index dc5c47f..463a52e 100644 --- a/Tests/data/AzureExtensions/AzureExtensionsSpec.json +++ b/Tests/data/AzureExtensions/AzureExtensionsSpec.json @@ -449,6 +449,69 @@ } } } + }, + "/PathWithPagingResult": { + "get": { + "tags": [ + "GET" + ], + "summary": "Get a list of the consumer groups in the Event Hub-compatible device-to-cloud endpoint in an IoT hub.", + "description": "Get a list of the consumer groups in the Event Hub-compatible device-to-cloud endpoint in an IoT hub.", + "operationId": "IotHubResource_ListEventHubConsumerGroups", + "parameters": [ + { + "$ref": "#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/SubscriptionIdParameter" + }, + { + "name": "resourceGroupName", + "description": "The name of the resource group that contains the IoT hub.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "resourceName", + "in": "path", + "description": "The name of the IoT hub.", + "required": true, + "type": "string" + }, + { + "name": "eventHubEndpointName", + "in": "path", + "description": "The name of the Event Hub-compatible endpoint.", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "This is a synchronous operation. The body contains a JSON-serialized list of the consumer groups in the the Event Hub-compatible endpoint in this IoT hub", + "schema": { + "$ref": "#/definitions/EventHubConsumerGroupsListResult" + } + }, + "default": { + "description": "DefaultErrorResponse", + "schema": { + "$ref": "#/definitions/Error" + } + } + }, + "deprecated": false, + "x-ms-pageable": { + "nextLinkName": "nextLink" + }, + "produces": [ + "application/json" + ], + "consumes": [ + "application/json" + ] + } } }, "x-ms-paths": { @@ -672,6 +735,24 @@ "description": "Message" } } + }, + "EventHubConsumerGroupsListResult": { + "description": "The JSON-serialized array of Event Hub-compatible consumer group names with a next link.", + "type": "object", + "properties": { + "value": { + "description": "The array of Event Hub-compatible consumer group names.", + "type": "array", + "items": { + "type": "string" + } + }, + "nextLink": { + "description": "The next link.", + "type": "string", + "readOnly": true + } + } } }, "parameters": { From 6360475af019e56f44eae4a8757bbbbac2bd6edb Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Fri, 29 Sep 2017 11:43:44 -0700 Subject: [PATCH 28/40] Add CHANGELOG.md, and minor update for releasing the 0.3.0 version of PSSwagger and PSSwaggerUtility modules. (#345) --- CHANGELOG.md | 43 +++++++++++++++++++ PSSwagger/PSSwagger.psd1 | 8 +++- .../PSSwaggerUtility/PSSwaggerUtility.psd1 | 10 +++-- 3 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9cc7342 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,43 @@ +# Changelog + +## v0.3.0 - 2017-09-29 +- Add support for generating proper output type for the Swagger operations with x-ms-pageable extension (#342) +- Add support for parameter type references to enum definitions (#341) +- Add support for AdditionalProperties Json schema with array type (#339) +- Generate SYNOPSIS help content in the generated cmdlets (#337) +- Rename IsOSX to IsMacOS after breaking change in PowerShell 6.0.0-beta.7 to fix #333 (#334) +- Resolve UnableToExtractDetailsFromSdkAssembly error when OperationType in OperationId conflicts with Definition name (#332) +- Change warninglevel to 1 for generating assembly for Azure Swagger specs (#331) +- Write exceptions in generated commands (#324) +- Add New-ServiceClient utility function in generated module to enable mock testing (#325) +- Add support for generating PowerShell cmdlets using pre-built SDK assembly and specification (#321) +- Add support for predefined header values (#320) +- Removing the langversion from CSC parameters as latest is the default language version (#318) +- Support latest version of AutoRest in PSSwagger (#313) +- Add support for generating the C# SDK assembly using CSC.exe on Windows PS (#312) +- Support default and customizable header comment for the PSSwagger generated files (#310) +- Ensure $oDataQuery expression is generated properly when a global parameter is referenced in more than one swagger operations with or without x-ms-odata extension (#307) +- Fix localization error in SwaggerUtil for PluralizationService (#303) +- Update Readme and fix an error related to importing the PSSwaggerUtility module (#300) +- Support custom x-ms-pageable\NextLinkName field name (#294) + +## v0.2.0 - 2017-08-15 + +* First preview release + + First preview release of PSSwagger and PSSwaggerUtility modules. While the goal is to support all web APIs, scenarios are focused on Microsoft Azure for this first release. + +* Supported Scenarios + - From an Open API v2 specification, generate a PowerShell module using [Azure AutoRest](https://github.com/azure/autorest) + - Generating modules is only supported on PowerShell 5.1 + - Customize the generation process with Open API v2 extensions in either the same specification as your web API or a separate file + - Rename automatically generated cmdlets + - Flatten complex parameters without flattening the underlying .NET API + - Generated modules support PowerShell on Windows (PowerShell 4 or greater) or PowerShell Core on Windows, Linux or Mac + - Compile the underlying .NET API before you publish your module or compile it on-the-fly on your end-user's machine + - Debugging symbols for underlying .NET API available + - Currently supported authentication schemes: + - Basic authentication, with or without challenge + - API key based authentication + - No authentication + - Authentication using AzureRM.Profile \ No newline at end of file diff --git a/PSSwagger/PSSwagger.psd1 b/PSSwagger/PSSwagger.psd1 index 4e5e537..da84e59 100644 --- a/PSSwagger/PSSwagger.psd1 +++ b/PSSwagger/PSSwagger.psd1 @@ -6,7 +6,10 @@ GUID = '6c925abf-49bc-49f4-8a47-12b95c9a8b37' Author = 'Microsoft Corporation' CompanyName = 'Microsoft Corporation' Copyright = '(c) Microsoft Corporation. All rights reserved.' -Description = 'PowerShell module with commands for generating the PowerShell Cmdlets using Swagger based specifications.' +Description = @' +The PowerShell cmdlet generator from OpenAPI (f.k.a Swagger) specification. +Please refer to https://github.com/PowerShell/PSSwagger/blob/developer/README.md for more details. +'@ FunctionsToExport = @( 'New-PSSwaggerModule', 'New-PSSwaggerMetadataFile' @@ -38,11 +41,12 @@ PrivateData = @{ PSData = @{ Tags = @('Azure', 'Swagger', + 'OpenApi', 'PSEdition_Desktop') ProjectUri = 'https://github.com/PowerShell/PSSwagger' LicenseUri = 'https://github.com/PowerShell/PSSwagger/blob/master/LICENSE' ReleaseNotes = @' -- Initial development release +Please refer to https://github.com/PowerShell/PSSwagger/blob/developer/CHANGELOG.md '@ } } diff --git a/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psd1 b/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psd1 index d918b2f..eb27719 100644 --- a/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psd1 +++ b/PSSwagger/PSSwaggerUtility/PSSwaggerUtility.psd1 @@ -1,11 +1,14 @@ @{ RootModule = 'PSSwaggerUtility.psm1' -ModuleVersion = '0.2.0' +ModuleVersion = '0.3.0' GUID = '49b0a58f-c657-49a1-8c16-e48031f5e2e4' Author = 'Microsoft Corporation' CompanyName = 'Microsoft Corporation' Copyright = '(c) Microsoft Corporation. All rights reserved.' -Description = 'PowerShell module with PSSwagger common helper functions' +Description = @' +PowerShell module with PSSwagger common helper functions. +Please refer to https://github.com/PowerShell/PSSwagger/blob/developer/README.md for more details. +'@ FunctionsToExport = @('Start-PSSwaggerJobHelper', 'New-PSSwaggerClientTracing', 'Register-PSSwaggerClientTracing', @@ -27,6 +30,7 @@ AliasesToExport = '' PrivateData = @{ PSData = @{ Tags = @('Swagger', + 'OpenApi', 'PSEdition_Desktop', 'PSEdition_Core', 'Linux', @@ -34,7 +38,7 @@ PrivateData = @{ ProjectUri = 'https://github.com/PowerShell/PSSwagger' LicenseUri = 'https://github.com/PowerShell/PSSwagger/blob/master/LICENSE' ReleaseNotes = @' -- Initial development release +Please refer to https://github.com/PowerShell/PSSwagger/blob/developer/CHANGELOG.md '@ } } From 2b2782ea0278ec521432aa1b2047903950504cbe Mon Sep 17 00:00:00 2001 From: Jeffrey Robinson Date: Tue, 3 Oct 2017 11:13:34 -0700 Subject: [PATCH 29/40] New-ServiceClient error on custom host (#350) --- PSSwagger/New-ServiceClient.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PSSwagger/New-ServiceClient.ps1 b/PSSwagger/New-ServiceClient.ps1 index 1b84c90..81945ab 100644 --- a/PSSwagger/New-ServiceClient.ps1 +++ b/PSSwagger/New-ServiceClient.ps1 @@ -85,7 +85,8 @@ function New-ServiceClient { $Client = New-Object -TypeName $FullClientTypeName -ArgumentList $ClientArgumentList if ($HostOverrideCommand) { - $Client.BaseUri = Invoke-Command -ScriptBlock [scriptblock]::Create($HostOverrideCommand) + [scriptblock]$HostOverrideCommand = [scriptblock]::Create($HostOverrideCommand) + $Client.BaseUri = Invoke-Command -ScriptBlock $HostOverrideCommand } if ($GlobalParameterHashtable) { From bdb849f10266218f6020c80a2836c2365974e660 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Tue, 3 Oct 2017 11:36:26 -0700 Subject: [PATCH 30/40] [Azure and AzureStack] Use IClientFactory to create ARM Client in Azure PowerShell way. (#348) This takes care of both Azure and AzureStack services including setting the proper values for SubscriptionId and BaseUri, etc., This also ensure that the generated cmdlets remain compatible over changes to the Profile cmdlets and Azure authentication. Additional minor update - Removed 'MIT license' fixed header from the generated module helper script files. --- .../AssemblyGenerationHelpers.Resources.psd1 | 8 -- PSSwagger/AssemblyGenerationHelpers.ps1 | 8 -- PSSwagger/GeneratedHelpers.ps1 | 76 ------------------- PSSwagger/New-ArmServiceClient.ps1 | 49 ++++++++++++ PSSwagger/New-ServiceClient.ps1 | 21 +---- PSSwagger/PSSwagger.psm1 | 12 ++- PSSwagger/Paths.psm1 | 38 ++++++---- PSSwagger/ServiceTypes/azure.PSMeta.json | 4 +- .../ServiceTypes/azure_stack.PSMeta.json | 5 +- PSSwagger/SwaggerUtils.psm1 | 6 +- 10 files changed, 85 insertions(+), 142 deletions(-) create mode 100644 PSSwagger/New-ArmServiceClient.ps1 diff --git a/PSSwagger/AssemblyGenerationHelpers.Resources.psd1 b/PSSwagger/AssemblyGenerationHelpers.Resources.psd1 index 9b00b0c..4ff8f77 100644 --- a/PSSwagger/AssemblyGenerationHelpers.Resources.psd1 +++ b/PSSwagger/AssemblyGenerationHelpers.Resources.psd1 @@ -1,11 +1,3 @@ -######################################################################################### -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# Licensed under the MIT license. -# -######################################################################################### - ConvertFrom-StringData @' ###PSLOC diff --git a/PSSwagger/AssemblyGenerationHelpers.ps1 b/PSSwagger/AssemblyGenerationHelpers.ps1 index 5bbbc01..33dcfb3 100644 --- a/PSSwagger/AssemblyGenerationHelpers.ps1 +++ b/PSSwagger/AssemblyGenerationHelpers.ps1 @@ -1,11 +1,3 @@ -######################################################################################### -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# Licensed under the MIT license. -# -######################################################################################### - Microsoft.PowerShell.Core\Set-StrictMode -Version Latest Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -FileName AssemblyGenerationHelpers.Resources.psd1 diff --git a/PSSwagger/GeneratedHelpers.ps1 b/PSSwagger/GeneratedHelpers.ps1 index 27cc989..ca5fc69 100644 --- a/PSSwagger/GeneratedHelpers.ps1 +++ b/PSSwagger/GeneratedHelpers.ps1 @@ -1,81 +1,5 @@ -######################################################################################### -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# Licensed under the MIT license. -# -######################################################################################### Microsoft.PowerShell.Core\Set-StrictMode -Version Latest -<# -.DESCRIPTION - Creates AutoRest ServiceClientCredentials for Microsoft Azure using the logged in AzureRM context. -#> -function Get-AzServiceCredential -{ - [CmdletBinding()] - param() - - $AzureContext = & "Get-AzureRmContext" -ErrorAction Stop - $authenticationFactory = New-Object -TypeName Microsoft.Azure.Commands.Common.Authentication.Factories.AuthenticationFactory - if ((Get-Variable -Name PSEdition -ErrorAction Ignore) -and ('Core' -eq $PSEdition)) { - [Action[string]]$stringAction = {param($s)} - $serviceCredentials = $authenticationFactory.GetServiceClientCredentials($AzureContext, $stringAction) - } else { - $serviceCredentials = $authenticationFactory.GetServiceClientCredentials($AzureContext) - } - - $serviceCredentials -} - -<# -.DESCRIPTION - Creates delegating handlers for Microsoft Azure generated modules. -#> -function Get-AzDelegatingHandler -{ - [CmdletBinding()] - param() - - New-Object -TypeName System.Net.Http.DelegatingHandler[] 0 -} - -<# -.DESCRIPTION - Gets the Azure subscription ID from the logged in AzureRM context. -#> -function Get-AzSubscriptionId -{ - [CmdletBinding()] - param() - - $AzureContext = & "Get-AzureRmContext" -ErrorAction Stop - if($AzureContext) - { - if(Get-Member -InputObject $AzureContext.Subscription -Name SubscriptionId) - { - return $AzureContext.Subscription.SubscriptionId - } - else - { - return $AzureContext.Subscription.Id - } - } -} - -<# -.DESCRIPTION - Gets the resource manager URL from the logged in AzureRM context. -#> -function Get-AzResourceManagerUrl -{ - [CmdletBinding()] - param() - - $AzureContext = & "Get-AzureRmContext" -ErrorAction Stop - $AzureContext.Environment.ResourceManagerUrl -} - <# .DESCRIPTION Creates a System.Net.Http.HttpClientHandler for the given credentials and sets preauthentication to true. diff --git a/PSSwagger/New-ArmServiceClient.ps1 b/PSSwagger/New-ArmServiceClient.ps1 new file mode 100644 index 0000000..d21496a --- /dev/null +++ b/PSSwagger/New-ArmServiceClient.ps1 @@ -0,0 +1,49 @@ +Microsoft.PowerShell.Core\Set-StrictMode -Version Latest + +<# +.DESCRIPTION + Creates Service Client object. + +.PARAMETER FullClientTypeName + Client type full name. + +.PARAMETER GlobalParameterHashtable + Global parameters to be set on client object. +#> +function New-ServiceClient { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $FullClientTypeName, + + [Parameter(Mandatory = $false)] + [PSCustomObject] + $GlobalParameterHashtable + ) + + # Azure Powershell way + [Microsoft.Azure.Commands.Common.Authentication.Abstractions.IAzureContext]$Context = Get-AzureRmContext + if (-not $Context -or -not $Context.Account) { + Write-Error -Message 'Run Login-AzureRmAccount to login.' -ErrorId 'AzureRmContextError' + return + } + + $Factory = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.ClientFactory + [System.Type[]]$Types = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.IAzureContext], [string] + $CreateArmClientMethod = [Microsoft.Azure.Commands.Common.Authentication.IClientFactory].GetMethod('CreateArmClient', $Types) + $ClientType = $FullClientTypeName -as [Type] + $ClosedMethod = $CreateArmClientMethod.MakeGenericMethod($ClientType) + $Arguments = $Context, [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureEnvironment+Endpoint]::ResourceManager + $Client = $closedMethod.Invoke($Factory, $Arguments) + + if ($GlobalParameterHashtable) { + $GlobalParameterHashtable.GetEnumerator() | ForEach-Object { + if ($_.Value -and (Get-Member -InputObject $Client -Name $_.Key -MemberType Property)) { + $Client."$($_.Key)" = $_.Value + } + } + } + + return $Client +} \ No newline at end of file diff --git a/PSSwagger/New-ServiceClient.ps1 b/PSSwagger/New-ServiceClient.ps1 index 81945ab..2c84f18 100644 --- a/PSSwagger/New-ServiceClient.ps1 +++ b/PSSwagger/New-ServiceClient.ps1 @@ -23,9 +23,6 @@ Microsoft.PowerShell.Core\Set-StrictMode -Version Latest Command should return a custom hostname string. Overrides the default host in the specification. -.PARAMETER SubscriptionIdCommand - Custom command get SubscriptionId value. - .PARAMETER GlobalParameterHashtable Global parameters to be set on client object. #> @@ -56,10 +53,6 @@ function New-ServiceClient { [string] $HostOverrideCommand, - [Parameter(Mandatory = $false)] - [string] - $SubscriptionIdCommand, - [Parameter(Mandatory = $false)] [PSCustomObject] $GlobalParameterHashtable @@ -91,18 +84,8 @@ function New-ServiceClient { if ($GlobalParameterHashtable) { $GlobalParameterHashtable.GetEnumerator() | ForEach-Object { - if (Get-Member -InputObject $Client -Name $_.Key -MemberType Property) { - if ((-not $_.Value) -and ($_.Key -eq 'SubscriptionId')) { - if($SubscriptionIdCommand) { - $Client.SubscriptionId = Invoke-Command -ScriptBlock [scriptblock]::Create($SubscriptionIdCommand) - } - else { - $Client.SubscriptionId = Get-AzSubscriptionId - } - } - else { - $Client."$($_.Key)" = $_.Value - } + if ($_.Value -and (Get-Member -InputObject $Client -Name $_.Key -MemberType Property)) { + $Client."$($_.Key)" = $_.Value } } } diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index d0163e4..c4308d4 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -613,9 +613,15 @@ function New-PSSwaggerModule $CopyFilesMap = [ordered]@{ 'GeneratedHelpers.ps1' = 'GeneratedHelpers.ps1' - 'Test-CoreRequirements.ps1' = 'Test-CoreRequirements.ps1' - 'Test-FullRequirements.ps1' = 'Test-FullRequirements.ps1' - 'New-ServiceClient.ps1' = 'New-ServiceClient.ps1' + } + + if($UseAzureCsharpGenerator) { + $CopyFilesMap['New-ArmServiceClient.ps1'] = 'New-ServiceClient.ps1' + $CopyFilesMap['Test-FullRequirements.ps1'] = 'Test-FullRequirements.ps1' + $CopyFilesMap['Test-CoreRequirements.ps1'] = 'Test-CoreRequirements.ps1' + } + else { + $CopyFilesMap['New-ServiceClient.ps1'] = 'New-ServiceClient.ps1' } if (-not $AssemblyFileName) { diff --git a/PSSwagger/Paths.psm1 b/PSSwagger/Paths.psm1 index a771d0e..208a82d 100644 --- a/PSSwagger/Paths.psm1 +++ b/PSSwagger/Paths.psm1 @@ -616,24 +616,25 @@ function New-SwaggerPath } # Process security section - $SubscriptionIdCommand = "" $AuthenticationCommand = "" $AuthenticationCommandArgumentName = '' $hostOverrideCommand = '' $AddHttpClientHandler = $false $securityParametersToAdd = @() $PowerShellCodeGen = $SwaggerMetaDict['PowerShellCodeGen'] - if (($PowerShellCodeGen['ServiceType'] -eq 'azure') -or ($PowerShellCodeGen['ServiceType'] -eq 'azure_stack')) { - $SubscriptionIdCommand = 'Get-AzSubscriptionId' - } - if ($PowerShellCodeGen['CustomAuthCommand']) { - $AuthenticationCommand = $PowerShellCodeGen['CustomAuthCommand'] - } - if ($PowerShellCodeGen['HostOverrideCommand']) { - $hostOverrideCommand = $PowerShellCodeGen['HostOverrideCommand'] + + # CustomAuthCommand and HostOverrideCommand are not required for Arm Services + if (($PowerShellCodeGen['ServiceType'] -ne 'azure') -and ($PowerShellCodeGen['ServiceType'] -eq 'azure_stack')) { + if ($PowerShellCodeGen['CustomAuthCommand']) { + $AuthenticationCommand = $PowerShellCodeGen['CustomAuthCommand'] + } + if ($PowerShellCodeGen['HostOverrideCommand']) { + $hostOverrideCommand = $PowerShellCodeGen['HostOverrideCommand'] + } } + # If the auth function hasn't been set by metadata, try to discover it from the security and securityDefinition objects in the spec - if (-not $AuthenticationCommand) { + if (-not $AuthenticationCommand -and -not $UseAzureCsharpGenerator) { if ($FunctionDetails.ContainsKey('Security')) { # For now, just take the first security object if ($FunctionDetails.Security.Count -gt 1) { @@ -729,7 +730,7 @@ function New-SwaggerPath } } - if (-not $AuthenticationCommand) { + if (-not $AuthenticationCommand -and -not $UseAzureCsharpGenerator) { # At this point, there was no supported security object or overridden auth function, so assume no auth $AuthenticationCommand = 'Get-AutoRestCredential' } @@ -973,13 +974,18 @@ function New-SwaggerPath ParameterGroupsExpressionBlock = $parameterGroupsExpressionBlock SwaggerDict = $SwaggerDict SwaggerMetaDict = $SwaggerMetaDict - AddHttpClientHandler = $AddHttpClientHandler - HostOverrideCommand = $hostOverrideCommand - AuthenticationCommand = $AuthenticationCommand - AuthenticationCommandArgumentName = $AuthenticationCommandArgumentName - SubscriptionIdCommand = $SubscriptionIdCommand FlattenedParametersOnPSCmdlet = $flattenedParametersOnPSCmdlet } + if($AuthenticationCommand) { + $functionBodyParams['AuthenticationCommand'] = $AuthenticationCommand + $functionBodyParams['AuthenticationCommandArgumentName'] = $AuthenticationCommandArgumentName + } + if($AddHttpClientHandler) { + $functionBodyParams['AddHttpClientHandler'] = $AddHttpClientHandler + } + if($hostOverrideCommand) { + $functionBodyParams['hostOverrideCommand'] = $hostOverrideCommand + } if($globalParameters) { $functionBodyParams['GlobalParameters'] = $globalParameters } diff --git a/PSSwagger/ServiceTypes/azure.PSMeta.json b/PSSwagger/ServiceTypes/azure.PSMeta.json index c3de351..ffc184b 100644 --- a/PSSwagger/ServiceTypes/azure.PSMeta.json +++ b/PSSwagger/ServiceTypes/azure.PSMeta.json @@ -1,7 +1,5 @@ { "info": { - "x-ps-code-generation-settings": { - "customAuthCommand": "Get-AzServiceCredential" - } + "x-ps-code-generation-settings": {} } } \ No newline at end of file diff --git a/PSSwagger/ServiceTypes/azure_stack.PSMeta.json b/PSSwagger/ServiceTypes/azure_stack.PSMeta.json index 8bcd7a5..ffc184b 100644 --- a/PSSwagger/ServiceTypes/azure_stack.PSMeta.json +++ b/PSSwagger/ServiceTypes/azure_stack.PSMeta.json @@ -1,8 +1,5 @@ { "info": { - "x-ps-code-generation-settings": { - "customAuthCommand": "Get-AzServiceCredential", - "hostOverrideCommand": "Get-AzResourceManagerUrl" - } + "x-ps-code-generation-settings": {} } } \ No newline at end of file diff --git a/PSSwagger/SwaggerUtils.psm1 b/PSSwagger/SwaggerUtils.psm1 index 58f7971..972e810 100644 --- a/PSSwagger/SwaggerUtils.psm1 +++ b/PSSwagger/SwaggerUtils.psm1 @@ -1471,7 +1471,7 @@ function Get-PathFunctionBody [string] $HostOverrideCommand, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory=$false)] [string] $AuthenticationCommand, @@ -1479,10 +1479,6 @@ function Get-PathFunctionBody [string] $AuthenticationCommandArgumentName, - [Parameter(Mandatory=$false)] - [string] - $SubscriptionIdCommand, - [Parameter(Mandatory=$true)] [PSCustomObject] $FlattenedParametersOnPSCmdlet From 33dfbcb43ecd0b405a24924ac456de153746c474 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Tue, 3 Oct 2017 11:36:41 -0700 Subject: [PATCH 31/40] Verb map change: 'Regenerate' to 'New' instead of 'Update' as per the feedback recieved from Azure PowerShell team. (#347) --- PSSwagger/PSCommandVerbMap.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PSSwagger/PSCommandVerbMap.ps1 b/PSSwagger/PSCommandVerbMap.ps1 index ad28acc..df0c491 100644 --- a/PSSwagger/PSCommandVerbMap.ps1 +++ b/PSSwagger/PSCommandVerbMap.ps1 @@ -25,7 +25,8 @@ $script:PSCommandVerbMap = @{ Allocate = 'New' Provision = 'New' Make = 'New' - + Regenerate = 'New' # Alternatives: Redo, Update, Reset + CreateOrUpdate = 'New,Set' Failover = 'Set' Assign = 'Set' @@ -53,7 +54,6 @@ $script:PSCommandVerbMap = @{ Patch = 'Update' Refresh = 'Update' - Regenerate = 'Update' # Alternatives: Redo, New, Reset Reprocess = "Update" # Alternatives: Redo Upgrade = 'Update' Reimage = 'Update' # Alternatives: Format, Reset From 0cb7ebe29614c938593009efb1586de0cc874a36 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Tue, 3 Oct 2017 13:33:19 -0700 Subject: [PATCH 32/40] Use separate PSCmdletOutputItemType variable for getting the output item type of pageable swagger operations. (#351) Reason: $pageType is used in expanding the result script blocks for $skip and $top. --- PSSwagger/Paths.psm1 | 23 +++++++++++++++-------- Tests/PSSwagger.Unit.Tests.ps1 | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/PSSwagger/Paths.psm1 b/PSSwagger/Paths.psm1 index 208a82d..ec3e18a 100644 --- a/PSSwagger/Paths.psm1 +++ b/PSSwagger/Paths.psm1 @@ -445,6 +445,9 @@ function New-SwaggerPath } elseif (-not $x_ms_pageableObject) { $x_ms_pageableObject = $parameterSetDetail.'x-ms-pageable' $x_ms_pageableObject['ReturnType'] = $parameterSetDetail.ReturnType + if($parameterSetDetail.ContainsKey('PSCmdletOutputItemType')) { + $x_ms_pageableObject['PSCmdletOutputItemType'] = $parameterSetDetail.PSCmdletOutputItemType + } if ($x_ms_pageableObject.Containskey('operationName')) { # Search for the cmdlet with a parameter set with the given operationName $pagingFunctionDetails = $PathFunctionDetails.GetEnumerator() | Where-Object { $_.Value.ParameterSetDetails | Where-Object { $_.OperationId -eq $x_ms_pageableObject.operationName }} | Select-Object -First 1 @@ -561,11 +564,15 @@ function New-SwaggerPath $Cmdlet = '' $CmdletParameter = '' $CmdletArgs = '' - $pageType = '' + $pageType = 'Array' + $PSCmdletOutputItemType = '' $resultBlockStr = $resultBlockNoPaging if ($x_ms_pageableObject) { if ($x_ms_pageableObject.ReturnType -ne 'NONE') { $pageType = $x_ms_pageableObject.ReturnType + if($x_ms_pageableObject.ContainsKey('PSCmdletOutputItemType')) { + $PSCmdletOutputItemType = $x_ms_pageableObject.PSCmdletOutputItemType + } } if ($x_ms_pageableObject.ContainsKey('Operations')) { @@ -994,8 +1001,8 @@ function New-SwaggerPath $bodyObject = $pathGenerationPhaseResult.BodyObject $body = $bodyObject.Body - if($pageType){ - $fullPathDataType = $pageType + if($PSCmdletOutputItemType){ + $fullPathDataType = $PSCmdletOutputItemType $outputTypeBlock = $executionContext.InvokeCommand.ExpandString($outputTypeStr) } else { @@ -1162,12 +1169,12 @@ function Set-ExtendedCodeMetadata { $returnType = $returnType.GenericTypeArguments[0] } + # Note: ReturnType and PSCmdletOutputItemType are currently used for Swagger operations which supports x-ms-pageable. if (($returnType.Name -eq 'IPage`1') -and $returnType.GenericTypeArguments) { - $returnType = $returnType.GenericTypeArguments[0] + $PSCmdletOutputItemTypeString = Convert-GenericTypeToString -Type $returnType.GenericTypeArguments[0] + $parameterSetDetail['PSCmdletOutputItemType'] = $PSCmdletOutputItemTypeString.Trim('[]') } - # Note: ReturnType is currently used for Swagger operations which supports x-ms-pageable. - $returnTypeString = Convert-GenericTypeToString -Type $returnType - $parameterSetDetail['ReturnType'] = $returnTypeString + $parameterSetDetail['ReturnType'] = Convert-GenericTypeToString -Type $returnType $ParamList = @() $oDataQueryFound = $false @@ -1288,7 +1295,7 @@ function Convert-GenericTypeToString { ) if (-not $Type.IsGenericType) { - return $Type.FullName.Trim('[]') + return $Type.FullName } $genericTypeStr = '' diff --git a/Tests/PSSwagger.Unit.Tests.ps1 b/Tests/PSSwagger.Unit.Tests.ps1 index da4aa69..8428b1f 100644 --- a/Tests/PSSwagger.Unit.Tests.ps1 +++ b/Tests/PSSwagger.Unit.Tests.ps1 @@ -287,7 +287,7 @@ Describe "PSSwagger Unit Tests" -Tag @('BVT', 'DRT', 'UnitTest', 'P0') { Convert-GenericTypeToString -Type ('System.Int32' -as [type]) | Should BeExactly 'System.Int32' } It "Convert-GenericTypeToString with 'System.String[]' type" { - Convert-GenericTypeToString -Type ('System.String[]' -as [type]) | Should BeExactly 'System.String' + Convert-GenericTypeToString -Type ('System.String[]' -as [type]) | Should BeExactly 'System.String[]' } It "Convert-GenericTypeToString with 'System.Collections.Generic.List[string]' type" { Convert-GenericTypeToString -Type ('System.Collections.Generic.List[string]' -as [type]) | Should BeExactly 'System.Collections.Generic.List[System.String]' From 2b537ae431e315be0cd8c6eceff181bb3aa3fabb Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Tue, 3 Oct 2017 16:40:50 -0700 Subject: [PATCH 33/40] Escape '<#' and '#>', and replace '--' with '==' in Header content (#352) Resolves #349. * Updated to escape block comment character sequence, if any, using the PowerShell escape character, grave-accent(`). * Changes to replace -- with == and warning message. --- PSSwagger/PSSwagger.Resources.psd1 | 1 + PSSwagger/PSSwagger.psm1 | 8 ++++++++ Tests/PSSwagger.Unit.Tests.ps1 | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/PSSwagger/PSSwagger.Resources.psd1 b/PSSwagger/PSSwagger.Resources.psd1 index 5b3b8f5..276c238 100644 --- a/PSSwagger/PSSwagger.Resources.psd1 +++ b/PSSwagger/PSSwagger.Resources.psd1 @@ -87,5 +87,6 @@ ConvertFrom-StringData @' InvalidPSMetaFlattenParameter=Flatten property is specified as 'true' for an invalid parameter '{0}' with type '{1}'. InvalidHeaderFileExtension=Header '{0}' file extension should be '.txt'. InvalidHeaderFilePath=The specified value '{0}' for Header parameter is should be a valid file path. + HeaderContentTwoHyphenWarning=The specified Header content has '--', replacing '--' with '=='. ###PSLOC '@ \ No newline at end of file diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index c4308d4..3c94963 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -1114,6 +1114,14 @@ function Get-HeaderContent { } } + # Escape block comment character sequence, if any, using the PowerShell escape character, grave-accent(`). + $HeaderContent = $HeaderContent.Replace('<#', '<`#').Replace('#>', '#`>') + + if ($HeaderContent -match '--') { + Write-Warning -Message $LocalizedData.HeaderContentTwoHyphenWarning + $HeaderContent = $HeaderContent.Replace('--', '==') + } + return $HeaderContent } diff --git a/Tests/PSSwagger.Unit.Tests.ps1 b/Tests/PSSwagger.Unit.Tests.ps1 index 8428b1f..31e272a 100644 --- a/Tests/PSSwagger.Unit.Tests.ps1 +++ b/Tests/PSSwagger.Unit.Tests.ps1 @@ -266,6 +266,24 @@ Describe "PSSwagger Unit Tests" -Tag @('BVT', 'DRT', 'UnitTest', 'P0') { It "Get-HeaderContent should return MICROSOFT_APACHE_NO_CODEGEN header content with '-Header MICROSOFT_APACHE_NO_CODEGEN'" { Get-HeaderContent -SwaggerDict @{Info = @{Header = 'MICROSOFT_APACHE_NO_CODEGEN'}} | Should BeExactly $MicrosoftApacheLicenseHeader } + + It 'Get-HeaderContent should escape <#' { + Get-HeaderContent -SwaggerDict @{Info = @{Header = 'Header content with <#'}} | Should BeExactly 'Header content with <`#' + } + + It 'Get-HeaderContent should escape #>' { + Get-HeaderContent -SwaggerDict @{Info = @{Header = 'Header content with #>'}} | Should BeExactly 'Header content with #`>' + } + + It 'Get-HeaderContent should replace -- with ==' { + Get-HeaderContent -SwaggerDict @{Info = @{Header = 'Header content with --'}} -WarningVariable wv -WarningAction SilentlyContinue | Should BeExactly 'Header content with ==' + $wv | Should not BeNullOrEmpty + $wv.Message -match '==' | Should Be $true + } + + It "Get-HeaderContent should escape '<#' and '#>', and replace '--' with '=='" { + Get-HeaderContent -SwaggerDict @{Info = @{Header = 'Header content with <# PS comment #> and --.'}} -WarningAction SilentlyContinue | Should BeExactly 'Header content with <`# PS comment #`> and ==.' + } } Context "Get-CSharpModelName Unit Tests" { From 248d5c220ef55d7640648e5ff633b13bc014b56b Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Wed, 4 Oct 2017 12:01:45 -0700 Subject: [PATCH 34/40] Add all non-complex type properties in output format ps1xml files. (#354) Also added TableColumnHeader with width. --- PSSwagger/Definitions.psm1 | 33 ++++++++++++++++++++---------- PSSwagger/PSSwagger.Constants.ps1 | 7 ++++++- PSSwagger/PSSwagger.Resources.psd1 | 1 + 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/PSSwagger/Definitions.psm1 b/PSSwagger/Definitions.psm1 index 39b7aa7..10ef031 100644 --- a/PSSwagger/Definitions.psm1 +++ b/PSSwagger/Definitions.psm1 @@ -988,26 +988,37 @@ function New-SwaggerDefinitionFormatFile $ViewTypeName = $ViewName $TableColumnItemsList = @() $TableColumnItemCount = 0 - $ParametersCount = Get-HashtableKeyCount -Hashtable $FunctionDetails.ParametersTable - $SkipParameterList = @('id', 'tags') $FunctionDetails.ParametersTable.GetEnumerator() | ForEach-Object { - $ParameterDetails = $_.Value - - # Add all properties when definition has 4 or less properties. - # Otherwise add the first 4 properties with basic types by skipping the complex types, id and tags. - if(($ParametersCount -le 4) -or - (($TableColumnItemCount -le 4) -and - ($SkipParameterList -notcontains $ParameterDetails.Name) -and - (-not $ParameterDetails.Type.StartsWith($Namespace, [System.StringComparison]::OrdinalIgnoreCase)))) + # Add all properties otherthan complex typed properties. + # Complex typed properties are not displayed by the PowerShell Format viewer. + if(-not $ParameterDetails.Type.StartsWith($Namespace, [System.StringComparison]::OrdinalIgnoreCase)) { $TableColumnItemsList += $TableColumnItemStr -f ($ParameterDetails.Name) $TableColumnItemCount += 1 } } - $TableColumnHeaders = $null + if(-not $TableColumnItemCount) { + Write-Verbose -Message ($LocalizedData.FormatFileNotRequired -f $FunctionDetails.Name) + return + } + + $TableColumnHeadersList = @() + $DefaultWindowSizeWidth = 120 + # Getting the width value for each property column. Default console window width is 120. + $TableColumnHeaderWidth = [int]($DefaultWindowSizeWidth/$TableColumnItemCount) + + if ($TableColumnItemCount -ge 2) { + 1..($TableColumnItemCount - 1) | ForEach-Object { + $TableColumnHeadersList += $TableColumnHeaderStr -f ($TableColumnHeaderWidth) + } + } + # Allowing the last property to get the remaining column width, this is useful when customer increases the default window width. + $TableColumnHeadersList += $LastTableColumnHeaderStr + + $TableColumnHeaders = $TableColumnHeadersList -join "`r`n" $TableColumnItems = $TableColumnItemsList -join "`r`n" $FormatViewDefinition = $FormatViewDefinitionStr -f ($ViewName, $ViewTypeName, $TableColumnHeaders, $TableColumnItems, $XmlHeaderComment) diff --git a/PSSwagger/PSSwagger.Constants.ps1 b/PSSwagger/PSSwagger.Constants.ps1 index 5da4695..6b84c48 100644 --- a/PSSwagger/PSSwagger.Constants.ps1 +++ b/PSSwagger/PSSwagger.Constants.ps1 @@ -491,7 +491,8 @@ $FormatViewDefinitionStr = @' {1} - {2} + +{2} @@ -518,6 +519,10 @@ $TableColumnHeaderStr = @' '@ +$LastTableColumnHeaderStr = @' + +'@ + $DefaultGeneratedFileHeader = @' Code generated by Microsoft (R) PSSwagger {0} Changes may cause incorrect behavior and will be lost if the code is regenerated. diff --git a/PSSwagger/PSSwagger.Resources.psd1 b/PSSwagger/PSSwagger.Resources.psd1 index 276c238..ee0a3cf 100644 --- a/PSSwagger/PSSwagger.Resources.psd1 +++ b/PSSwagger/PSSwagger.Resources.psd1 @@ -43,6 +43,7 @@ ConvertFrom-StringData @' GeneratedPathCommand=Generated path command '{0}'. GeneratedDefinitionCommand=Generated command '{0}' for the definition name '{1}'. GeneratedFormatFile=Generated output format file for the definition name '{0}'. + FormatFileNotRequired=It is not required to generated the format file as this definition '{0}' doesn't have non-complex typed properties. DeleteGeneratedFile=Deleting generated file '{0}' ExtractingMetadata=Extracting metadata from generated assembly ExpectedServiceClientTypeNotFound=Unable to find expected service client type: {0} From a9946e59ebbaeeb57c47a562a53e71dcb8548e8b Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Wed, 4 Oct 2017 12:02:00 -0700 Subject: [PATCH 35/40] Add NoVersionFolder switch parameter on New-PSSwaggerModule cmdlet to not create the version folder. (#355) Resolves #353 --- PSSwagger/PSSwagger.psm1 | 9 ++++++++- Tests/PSSwaggerScenario.Tests.ps1 | 30 +++++++++++++++++++++++++++- Tests/TestUtilities.psm1 | 3 +++ docs/commands/New-PSSwaggerModule.md | 27 +++++++++++++++++++------ 4 files changed, 61 insertions(+), 8 deletions(-) diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index 3c94963..36a3fff 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -72,6 +72,9 @@ Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename PSSwa .PARAMETER Version Version of the generated PowerShell module. +.PARAMETER NoVersionFolder + Switch to not create the version folder under the generated module folder. + .PARAMETER DefaultCommandPrefix Prefix value to be prepended to cmdlet noun or to cmdlet name without verb. @@ -163,6 +166,10 @@ function New-PSSwaggerModule [Version] $Version = '0.0.1', + [Parameter(Mandatory = $false)] + [switch] + $NoVersionFolder, + [Parameter(Mandatory = $false)] [string] $DefaultCommandPrefix, @@ -449,7 +456,7 @@ function New-PSSwaggerModule $nameSpace = $swaggerDict['info'].NameSpace $models = $swaggerDict['info'].Models - if($PSVersionTable.PSVersion -lt '5.0.0') { + if($NoVersionFolder -or $PSVersionTable.PSVersion -lt '5.0.0') { if (-not $outputDirectory.EndsWith($Name, [System.StringComparison]::OrdinalIgnoreCase)) { $outputDirectory = Join-Path -Path $outputDirectory -ChildPath $Name $SymbolPath = Join-Path -Path $SymbolPath -ChildPath $Name diff --git a/Tests/PSSwaggerScenario.Tests.ps1 b/Tests/PSSwaggerScenario.Tests.ps1 index 4884a56..068d68e 100644 --- a/Tests/PSSwaggerScenario.Tests.ps1 +++ b/Tests/PSSwaggerScenario.Tests.ps1 @@ -1141,4 +1141,32 @@ Describe "Output type scenario tests" -Tag @('OutputType','ScenarioTest') { $CommandInfo = Get-Command -Name Get-IotHubResourceEventHubConsumerGroup -Module $ModuleName $CommandInfo.OutputType.Type.ToString() | Should BeExactly 'System.String' } -} \ No newline at end of file +} + +Describe 'New-PSSwaggerModule cmdlet parameter tests' -Tag @('CmdletParameterTest','ScenarioTest') { + BeforeAll { + $ModuleName = 'Generated.Module.NoVersionFolder' + $SwaggerSpecPath = Join-Path -Path $PSScriptRoot -ChildPath 'Data' | Join-Path -ChildPath 'AzureExtensions' | Join-Path -ChildPath 'AzureExtensionsSpec.json' + $GeneratedPath = Join-Path -Path $PSScriptRoot -ChildPath 'Generated' + $GeneratedModuleBase = Join-Path -Path $GeneratedPath -ChildPath $ModuleName + if (Test-Path -Path $GeneratedModuleBase -PathType Container) { + Remove-Item -Path $GeneratedModuleBase -Recurse -Force + } + } + + It 'Test NoVersionFolder switch parameter' { + $params = @{ + SpecificationPath = $SwaggerSpecPath + Name = $ModuleName + UseAzureCsharpGenerator = $true + Path = $GeneratedPath + NoVersionFolder = $true + ConfirmBootstrap = $true + Verbose = $true + } + Invoke-NewPSSwaggerModuleCommand -NewPSSwaggerModuleParameters $params + + $ModuleInfo = Import-Module $GeneratedModuleBase -Force -PassThru + $ModuleInfo.ModuleBase | Should Be $GeneratedModuleBase + } +} diff --git a/Tests/TestUtilities.psm1 b/Tests/TestUtilities.psm1 index d9d2781..3fdb292 100644 --- a/Tests/TestUtilities.psm1 +++ b/Tests/TestUtilities.psm1 @@ -78,6 +78,9 @@ function Invoke-NewPSSwaggerModuleCommand { $NewPSSwaggerModuleParameters['NoAssembly'] = $false $NewPSSwaggerModuleParameters['ConfirmBootstrap'] = $true } + elseif (-not $NewPSSwaggerModuleParameters.ContainsKey('AssemblyFileName')) { + $NewPSSwaggerModuleParameters['NoAssembly'] = $true + } if ((Get-Variable -Name PSEdition -ErrorAction Ignore) -and ('Core' -eq $PSEdition)) { if ($IncludeAssembly) { diff --git a/docs/commands/New-PSSwaggerModule.md b/docs/commands/New-PSSwaggerModule.md index a1612f7..87db1f5 100644 --- a/docs/commands/New-PSSwaggerModule.md +++ b/docs/commands/New-PSSwaggerModule.md @@ -14,30 +14,30 @@ PowerShell command to generate the PowerShell commands for a given RESTful Web S ### SpecificationPath (Default) ``` New-PSSwaggerModule -SpecificationPath -Path -Name [-Version ] - [-DefaultCommandPrefix ] [-Header ] [-UseAzureCsharpGenerator] [-NoAssembly] - [-PowerShellCorePath ] [-IncludeCoreFxAssembly] [-InstallToolsForAllUsers] [-TestBuild] + [-NoVersionFolder] [-DefaultCommandPrefix ] [-Header ] [-UseAzureCsharpGenerator] + [-NoAssembly] [-PowerShellCorePath ] [-IncludeCoreFxAssembly] [-InstallToolsForAllUsers] [-TestBuild] [-SymbolPath ] [-ConfirmBootstrap] ``` ### SdkAssemblyWithSpecificationPath ``` New-PSSwaggerModule -SpecificationPath -Path -AssemblyFileName - [-ClientTypeName ] [-ModelsName ] -Name [-Version ] + [-ClientTypeName ] [-ModelsName ] -Name [-Version ] [-NoVersionFolder] [-DefaultCommandPrefix ] [-Header ] [-UseAzureCsharpGenerator] ``` ### SdkAssemblyWithSpecificationUri ``` New-PSSwaggerModule -SpecificationUri -Path -AssemblyFileName - [-ClientTypeName ] [-ModelsName ] -Name [-Version ] + [-ClientTypeName ] [-ModelsName ] -Name [-Version ] [-NoVersionFolder] [-DefaultCommandPrefix ] [-Header ] [-UseAzureCsharpGenerator] ``` ### SpecificationUri ``` New-PSSwaggerModule -SpecificationUri -Path -Name [-Version ] - [-DefaultCommandPrefix ] [-Header ] [-UseAzureCsharpGenerator] [-NoAssembly] - [-PowerShellCorePath ] [-IncludeCoreFxAssembly] [-InstallToolsForAllUsers] [-TestBuild] + [-NoVersionFolder] [-DefaultCommandPrefix ] [-Header ] [-UseAzureCsharpGenerator] + [-NoAssembly] [-PowerShellCorePath ] [-IncludeCoreFxAssembly] [-InstallToolsForAllUsers] [-TestBuild] [-SymbolPath ] [-ConfirmBootstrap] ``` @@ -191,6 +191,21 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -NoVersionFolder +Switch to not create the version folder under the generated module folder. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -DefaultCommandPrefix Prefix value to be prepended to cmdlet noun or to cmdlet name without verb. From 8b86be2472ee2f9d6591cb73e96efae10424488f Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Wed, 4 Oct 2017 12:57:24 -0700 Subject: [PATCH 36/40] Add few verb mappings for Azure RPs (#356) --- PSSwagger/PSCommandVerbMap.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/PSSwagger/PSCommandVerbMap.ps1 b/PSSwagger/PSCommandVerbMap.ps1 index df0c491..aa19d9c 100644 --- a/PSSwagger/PSCommandVerbMap.ps1 +++ b/PSSwagger/PSCommandVerbMap.ps1 @@ -19,6 +19,7 @@ $script:PSCommandVerbMap = @{ Acquire = 'Get' Examine = 'Get' Suggest = 'Get' + Retrieve = 'Get' Create = 'New' Generate = 'New' @@ -57,6 +58,7 @@ $script:PSCommandVerbMap = @{ Reprocess = "Update" # Alternatives: Redo Upgrade = 'Update' Reimage = 'Update' # Alternatives: Format, Reset + Retarget = 'Update' Validate = 'Test' Check = 'Test' @@ -103,12 +105,14 @@ $script:PSCommandVerbMap = @{ Migrate = 'Move' # Alternatives: Export Transfer = 'Move' Name = 'Move' + Reassociate = 'Move' Change = 'Rename' Swap = 'Switch' # Alternatives: Move Execute = 'Invoke' + Perform = 'Invoke' Discover = 'Find' # Alternatives: Search Locate = 'Find' @@ -140,4 +144,8 @@ $script:PSCommandVerbMap = @{ Jump = 'Skip' Separate = 'Split' + + Notify = 'Send' + + Authorize = 'Grant' } \ No newline at end of file From 2fb99b90ab0a8bb06052183f92fe0881cb6c9309 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Wed, 4 Oct 2017 13:21:15 -0700 Subject: [PATCH 37/40] Move New-HttpClientHandler logic into New-ServiceClient for non-Azure services. (#357) Currently, GeneratedHelpers.ps1 get copied to the generated module folder of Azure modules. This unification ensures that non-Azure services can replace the functionality in New-ServiceClient.ps1 and also helps in mock testing. Going forward, if we add any new helper utility function it will go into a separate .ps1 file, that's why GeneratedHelpers.ps1 file got removed in this PR. --- PSSwagger/GeneratedHelpers.ps1 | 20 -------------------- PSSwagger/New-ServiceClient.ps1 | 7 ++++++- PSSwagger/PSSwagger.Constants.ps1 | 1 - PSSwagger/PSSwagger.psm1 | 5 +---- 4 files changed, 7 insertions(+), 26 deletions(-) delete mode 100644 PSSwagger/GeneratedHelpers.ps1 diff --git a/PSSwagger/GeneratedHelpers.ps1 b/PSSwagger/GeneratedHelpers.ps1 deleted file mode 100644 index ca5fc69..0000000 --- a/PSSwagger/GeneratedHelpers.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -Microsoft.PowerShell.Core\Set-StrictMode -Version Latest - -<# -.DESCRIPTION - Creates a System.Net.Http.HttpClientHandler for the given credentials and sets preauthentication to true. -#> -function New-HttpClientHandler { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [PSCredential] - $Credential - ) - - Add-Type -AssemblyName System.Net.Http - $httpClientHandler = New-Object -TypeName System.Net.Http.HttpClientHandler - $httpClientHandler.PreAuthenticate = $true - $httpClientHandler.Credentials = $Credential - $httpClientHandler -} \ No newline at end of file diff --git a/PSSwagger/New-ServiceClient.ps1 b/PSSwagger/New-ServiceClient.ps1 index 2c84f18..0bfad60 100644 --- a/PSSwagger/New-ServiceClient.ps1 +++ b/PSSwagger/New-ServiceClient.ps1 @@ -68,7 +68,12 @@ function New-ServiceClient { $ClientArgumentList += Invoke-Command @InvokeCommand_parameters if ($AddHttpClientHandler) { - $httpClientHandler = New-HttpClientHandler -Credential $Credential + if(-not ('System.Net.Http.HttpClientHandler' -as [Type])) { + Add-Type -AssemblyName System.Net.Http + } + $httpClientHandler = New-Object -TypeName System.Net.Http.HttpClientHandler + $httpClientHandler.PreAuthenticate = $true + $httpClientHandler.Credentials = $Credential $ClientArgumentList += $httpClientHandler } diff --git a/PSSwagger/PSSwagger.Constants.ps1 b/PSSwagger/PSSwagger.Constants.ps1 index 6b84c48..fe6be5a 100644 --- a/PSSwagger/PSSwagger.Constants.ps1 +++ b/PSSwagger/PSSwagger.Constants.ps1 @@ -59,7 +59,6 @@ if (Test-Path -Path `$ClrPath -PathType Container) { } . (Join-Path -Path `$PSScriptRoot -ChildPath 'New-ServiceClient.ps1') -. (Join-Path -Path `$PSScriptRoot -ChildPath 'GeneratedHelpers.ps1') `$allPs1FilesPath = Join-Path -Path `$PSScriptRoot -ChildPath '$GeneratedCommandsName' | Join-Path -ChildPath '*.ps1' Get-ChildItem -Path `$allPs1FilesPath -Recurse -File | ForEach-Object { . `$_.FullName} diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index 36a3fff..0595470 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -618,10 +618,7 @@ function New-PSSwaggerModule -Info $swaggerDict['info'] ` -PSHeaderComment $PSHeaderComment - $CopyFilesMap = [ordered]@{ - 'GeneratedHelpers.ps1' = 'GeneratedHelpers.ps1' - } - + $CopyFilesMap = [ordered]@{} if($UseAzureCsharpGenerator) { $CopyFilesMap['New-ArmServiceClient.ps1'] = 'New-ServiceClient.ps1' $CopyFilesMap['Test-FullRequirements.ps1'] = 'Test-FullRequirements.ps1' From 749c39183194c1bcfd1bb5e0c06e5af6072f5bb8 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Fri, 6 Oct 2017 14:36:36 -0700 Subject: [PATCH 38/40] Generate single Verb-Noun cmdlet for OperationIds like Noun_Verb and Noun_VerbBySomething (#358) - Added support for generating the single Verb-Noun cmdlet with multuple parameter sets for OperationIds like Noun_Verb and Noun_VerbBySomething. - Updated the parameterset prioritazation logic to retrieve the default parameter set with minimum number of mandatory parameters. --- PSSwagger/Paths.psm1 | 27 +++++++++++++++++++++------ PSSwagger/SwaggerUtils.psm1 | 14 ++++++++++---- Tests/PSSwaggerScenario.Tests.ps1 | 22 +++++++++++++++++++--- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/PSSwagger/Paths.psm1 b/PSSwagger/Paths.psm1 index ec3e18a..435348d 100644 --- a/PSSwagger/Paths.psm1 +++ b/PSSwagger/Paths.psm1 @@ -196,6 +196,25 @@ function Get-SwaggerSpecPathInfo } else { $commandNames = Get-PathCommandName -OperationId $operationId } + + # Priority of a parameterset will be used to determine the default parameterset of a cmdlet. + $Priority = 0 + $ParametersCount = Get-HashtableKeyCount -Hashtable $ParametersTable + if($ParametersCount) { + # Priority for parameter sets with mandatory parameters starts at 100 + $Priority = 100 + + $ParametersTable.GetEnumerator() | ForEach-Object { + if($_.Value.ContainsKey('Mandatory') -and $_.Value.Mandatory -eq '$true') { + $Priority++ + } + } + + # If there are no mandatory parameters, use the parameter count as the priority. + if($Priority -eq 100) { + $Priority = $ParametersCount + } + } $ParameterSetDetail = @{ Description = $FunctionDescription @@ -206,7 +225,7 @@ function Get-SwaggerSpecPathInfo OperationType = $operationType EndpointRelativePath = $EndpointRelativePath PathCommonParameters = $PathCommonParameters - Priority = 100 # Default + Priority = $Priority 'x-ms-pageable' = $x_ms_pageableObject } @@ -228,10 +247,6 @@ function Get-SwaggerSpecPathInfo } } - if ($approximateVerb.StartsWith("List")) { - $ParameterSetDetail.Priority = 0 - } - $commandNames | ForEach-Object { $FunctionDetails = @{} if ($PathFunctionDetails.ContainsKey($_.name)) { @@ -839,7 +854,7 @@ function New-SwaggerPath if ($nonUniqueParameterSets.Length -gt 1) { # Pick the highest priority set among $nonUniqueParameterSets, but really it doesn't matter, cause... # Print warning that this generated cmdlet has ambiguous parameter sets - $defaultParameterSet = $nonUniqueParameterSets | Sort-Object -Property Priority | Select-Object -First 1 + $defaultParameterSet = $nonUniqueParameterSets | Sort-Object {$_.Priority} | Select-Object -First 1 $DefaultParameterSetName = $defaultParameterSet.OperationId $description = $defaultParameterSet.Description $synopsis = $defaultParameterSet.Synopsis diff --git a/PSSwagger/SwaggerUtils.psm1 b/PSSwagger/SwaggerUtils.psm1 index 972e810..8a0f891 100644 --- a/PSSwagger/SwaggerUtils.psm1 +++ b/PSSwagger/SwaggerUtils.psm1 @@ -1402,10 +1402,16 @@ function Get-PathCommandName # This is still empty when a verb match is found that is the entire string, but it might not be worth checking for that case and skipping the below operation $cmdNounSuffix = $UnapprovedVerb.Substring($beginningOfSuffix) # Add command noun suffix only when the current noun doesn't contain it or vice-versa. - if(-not $cmdNoun -or (($cmdNoun -notmatch $cmdNounSuffix) -and ($cmdNounSuffix -notmatch $cmdNoun))) { - $cmdNoun = $cmdNoun + (Get-PascalCasedString -Name $UnapprovedVerb.Substring($beginningOfSuffix)) - } elseif($cmdNounSuffix -match $cmdNoun) { - $cmdNoun = $cmdNounSuffix + if(-not $cmdNoun) { + $cmdNoun = Get-PascalCasedString -Name $cmdNounSuffix + } + elseif(-not $cmdNounSuffix.StartsWith('By', [System.StringComparison]::OrdinalIgnoreCase)) { + if(($cmdNoun -notmatch $cmdNounSuffix) -and ($cmdNounSuffix -notmatch $cmdNoun)) { + $cmdNoun = $cmdNoun + (Get-PascalCasedString -Name $cmdNounSuffix) + } + elseif($cmdNounSuffix -match $cmdNoun) { + $cmdNoun = $cmdNounSuffix + } } } } diff --git a/Tests/PSSwaggerScenario.Tests.ps1 b/Tests/PSSwaggerScenario.Tests.ps1 index 068d68e..2c1d29b 100644 --- a/Tests/PSSwaggerScenario.Tests.ps1 +++ b/Tests/PSSwaggerScenario.Tests.ps1 @@ -223,7 +223,7 @@ Describe "Optional parameter tests" -Tag ScenarioTest { } It "Generates cmdlet using optional path parameters" { - $results = Get-CupcakeByMaker -Flavor "chocolate" -Maker "bob" + $results = Get-Cupcake -Flavor "chocolate" -Maker "bob" $results.Length | should be 1 } @@ -528,10 +528,10 @@ Describe "AzureExtensions" -Tag @('AzureExtension','ScenarioTest') { } It "Test x-ms-paths generated cmdlets" { - $results = Get-CupcakeById -Id 1 + $results = Get-Cupcake -Id 1 $results.Count | should be 1 - $results = Get-CupcakeByFlavor -Flavor 'vanilla' + $results = Get-Cupcake -Flavor 'vanilla' $results.Count | should be 1 } @@ -546,6 +546,22 @@ Describe "AzureExtensions" -Tag @('AzureExtension','ScenarioTest') { $cmdInfo = Should BeNullOrEmpty $ev.FullyQualifiedErrorId | Should Be 'CommandNotFoundException,Microsoft.PowerShell.Commands.GetCommandCommand' } + + It "Validate default parameterset name of generated cmdlet" { + $CommandInfo = Get-Command -Name Get-Cupcake + $DefaultParameterSet = 'Cupcake_List' + $ParameterSetNames = @( + $DefaultParameterSet, + 'Cupcake_GetById', + 'Cupcake_GetByFlavor' + ) + $CommandInfo.DefaultParameterSet | Should Be $DefaultParameterSet + $CommandInfo.ParameterSets.Count | Should Be $ParameterSetNames.Count + + $ParameterSetNames | ForEach-Object { + $CommandInfo.ParameterSets.Name -contains $_ | Should Be $true + } + } } AfterAll { From 542c8c2eec6fc5a7135706b048df33a6a07884c5 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Mon, 9 Oct 2017 10:49:22 -0700 Subject: [PATCH 39/40] Update change log to include new changes into 0.3.0 release. (#359) - Removed the date next to the version as it requires additional commit when we need to release the newer versions at different date than the one mentioned in the changelog.md. --- CHANGELOG.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cc7342..e395e7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ # Changelog -## v0.3.0 - 2017-09-29 +## v0.3.0 +- Generate single Verb-Noun cmdlet for OperationIds like Noun_Verb and Noun_VerbBySomething (#358) +- Move New-HttpClientHandler logic into New-ServiceClient for non-Azure services. (#357) +- Add few verb mappings for Azure RPs (#356) +- Add NoVersionFolder switch parameter on New-PSSwaggerModule cmdlet to not create the version folder. (#355) +- Add all non-complex type properties in output format ps1xml files. (#354) +- Escape '<#' and '#>', and replace '--' with '==' in Header content (#352) +- Use separate PSCmdletOutputItemType variable for getting the output item type of pageable swagger operations. (#351) +- Verb map change: 'Regenerate' to 'New' instead of 'Update' as per the feedback recieved from Azure PowerShell team. (#347) +- [Azure and AzureStack] Use IClientFactory to create ARM Client in Azure PowerShell way. (#348) +- New-ServiceClient error on custom host (#350) +- Add CHANGELOG.md, and minor update for releasing the 0.3.0 version of PSSwagger and PSSwaggerUtility modules. (#345) - Add support for generating proper output type for the Swagger operations with x-ms-pageable extension (#342) - Add support for parameter type references to enum definitions (#341) - Add support for AdditionalProperties Json schema with array type (#339) @@ -21,7 +32,7 @@ - Update Readme and fix an error related to importing the PSSwaggerUtility module (#300) - Support custom x-ms-pageable\NextLinkName field name (#294) -## v0.2.0 - 2017-08-15 +## v0.2.0 * First preview release From 87ae3bafbb1e7d94bee9020fc398077fd76df034 Mon Sep 17 00:00:00 2001 From: Manikyam Bavandla Date: Mon, 9 Oct 2017 14:55:41 -0700 Subject: [PATCH 40/40] Revert the changes from automatic merge commit from master to developer branch. (#361) --- PSSwagger/GeneratedHelpers.ps1 | 96 ---------------------------------- PSSwagger/Paths.psm1 | 2 +- 2 files changed, 1 insertion(+), 97 deletions(-) delete mode 100644 PSSwagger/GeneratedHelpers.ps1 diff --git a/PSSwagger/GeneratedHelpers.ps1 b/PSSwagger/GeneratedHelpers.ps1 deleted file mode 100644 index 27cc989..0000000 --- a/PSSwagger/GeneratedHelpers.ps1 +++ /dev/null @@ -1,96 +0,0 @@ -######################################################################################### -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# Licensed under the MIT license. -# -######################################################################################### -Microsoft.PowerShell.Core\Set-StrictMode -Version Latest - -<# -.DESCRIPTION - Creates AutoRest ServiceClientCredentials for Microsoft Azure using the logged in AzureRM context. -#> -function Get-AzServiceCredential -{ - [CmdletBinding()] - param() - - $AzureContext = & "Get-AzureRmContext" -ErrorAction Stop - $authenticationFactory = New-Object -TypeName Microsoft.Azure.Commands.Common.Authentication.Factories.AuthenticationFactory - if ((Get-Variable -Name PSEdition -ErrorAction Ignore) -and ('Core' -eq $PSEdition)) { - [Action[string]]$stringAction = {param($s)} - $serviceCredentials = $authenticationFactory.GetServiceClientCredentials($AzureContext, $stringAction) - } else { - $serviceCredentials = $authenticationFactory.GetServiceClientCredentials($AzureContext) - } - - $serviceCredentials -} - -<# -.DESCRIPTION - Creates delegating handlers for Microsoft Azure generated modules. -#> -function Get-AzDelegatingHandler -{ - [CmdletBinding()] - param() - - New-Object -TypeName System.Net.Http.DelegatingHandler[] 0 -} - -<# -.DESCRIPTION - Gets the Azure subscription ID from the logged in AzureRM context. -#> -function Get-AzSubscriptionId -{ - [CmdletBinding()] - param() - - $AzureContext = & "Get-AzureRmContext" -ErrorAction Stop - if($AzureContext) - { - if(Get-Member -InputObject $AzureContext.Subscription -Name SubscriptionId) - { - return $AzureContext.Subscription.SubscriptionId - } - else - { - return $AzureContext.Subscription.Id - } - } -} - -<# -.DESCRIPTION - Gets the resource manager URL from the logged in AzureRM context. -#> -function Get-AzResourceManagerUrl -{ - [CmdletBinding()] - param() - - $AzureContext = & "Get-AzureRmContext" -ErrorAction Stop - $AzureContext.Environment.ResourceManagerUrl -} - -<# -.DESCRIPTION - Creates a System.Net.Http.HttpClientHandler for the given credentials and sets preauthentication to true. -#> -function New-HttpClientHandler { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [PSCredential] - $Credential - ) - - Add-Type -AssemblyName System.Net.Http - $httpClientHandler = New-Object -TypeName System.Net.Http.HttpClientHandler - $httpClientHandler.PreAuthenticate = $true - $httpClientHandler.Credentials = $Credential - $httpClientHandler -} \ No newline at end of file diff --git a/PSSwagger/Paths.psm1 b/PSSwagger/Paths.psm1 index 3e3d4e6..435348d 100644 --- a/PSSwagger/Paths.psm1 +++ b/PSSwagger/Paths.psm1 @@ -1310,7 +1310,7 @@ function Convert-GenericTypeToString { ) if (-not $Type.IsGenericType) { - return $Type.FullName.Trim('[]') + return $Type.FullName } $genericTypeStr = ''