Adding unit tests (#157)
This commit is contained in:
Родитель
ca4619a63b
Коммит
559e5e593b
|
@ -20,6 +20,12 @@
|
|||
|
||||
# Change Log
|
||||
|
||||
## 2.0.1909.3
|
||||
|
||||
* Authentication
|
||||
* Address issue [#156](https://github.com/microsoft/Partner-Center-PowerShell/issues/156) where the refresh token was not being returned if it had not been previously used by the module during an interactive authentication attempt
|
||||
* After successfully authenticating the module will attempt to get country and locale based on the partner organization profile
|
||||
|
||||
## 2.0.1909.2
|
||||
|
||||
* Authentication
|
||||
|
|
|
@ -16,6 +16,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{BE2D140E-51A4-469F-A12A-279192E0986F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShell.UnitTests", "test\PowerShell.UnitTests\PowerShell.UnitTests.csproj", "{08C08BAF-107D-43BB-9560-DA2D40B27816}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -26,12 +30,17 @@ Global
|
|||
{0DAC3162-7D38-40EC-A412-496ECAFDDC8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0DAC3162-7D38-40EC-A412-496ECAFDDC8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0DAC3162-7D38-40EC-A412-496ECAFDDC8B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{08C08BAF-107D-43BB-9560-DA2D40B27816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{08C08BAF-107D-43BB-9560-DA2D40B27816}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{08C08BAF-107D-43BB-9560-DA2D40B27816}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{08C08BAF-107D-43BB-9560-DA2D40B27816}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{0DAC3162-7D38-40EC-A412-496ECAFDDC8B} = {D88E76D6-773B-4691-B6AB-3323E46D486F}
|
||||
{08C08BAF-107D-43BB-9560-DA2D40B27816} = {BE2D140E-51A4-469F-A12A-279192E0986F}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BE398BA5-A85F-43C4-9317-C08C61C2DB8B}
|
||||
|
|
52
build.proj
52
build.proj
|
@ -1,26 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
# Targets
|
||||
/t:Build
|
||||
Builds assemblies.
|
||||
/t:Clean
|
||||
Removes temporary build outputs.
|
||||
/t:Test
|
||||
Runs tests
|
||||
-->
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildThisFileDirectory)repo.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Folders -->
|
||||
<TestOutputDirectory>$(RepoArtifacts)TestResults</TestOutputDirectory>
|
||||
<StaticAnalysisOutputDirectory>$(RepoArtifacts)StaticAnalysisResults</StaticAnalysisOutputDirectory>
|
||||
|
||||
<!-- General -->
|
||||
<Configuration Condition="'$(Configuration)' != 'Release'">Debug</Configuration>
|
||||
|
||||
|
||||
<!-- PowerShell -->
|
||||
<PowerShellCoreCommandPrefix>pwsh -NonInteractive -NoLogo -NoProfile -Command</PowerShellCoreCommandPrefix>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="Build">
|
||||
|
||||
<!-- Build the project -->
|
||||
<!-- Build the project -->
|
||||
<PropertyGroup>
|
||||
<BuildAction Condition="'$(Configuration)' != 'Release'">build</BuildAction>
|
||||
<BuildAction Condition="'$(Configuration)' == 'Release'">publish</BuildAction>
|
||||
|
||||
<TestOutputDirectory>$(RepoArtifacts)/TestResults</TestOutputDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
<Exec Command="dotnet $(BuildAction) Partner-Center-PowerShell.sln -c $(Configuration)" />
|
||||
|
||||
<!-- Delete PowerShell runtime files -->
|
||||
<!-- Delete PowerShell runtime files -->
|
||||
<PropertyGroup>
|
||||
<RuntimeDllsIncludeList>Microsoft.Powershell.*.dll,System*.dll,Microsoft.VisualBasic.dll,Microsoft.CSharp.dll,Microsoft.CodeAnalysis.dll,Microsoft.CodeAnalysis.CSharp.dll</RuntimeDllsIncludeList>
|
||||
<RuntimeDllsExcludeList>System.Security.Cryptography.ProtectedData.dll,System.Configuration.ConfigurationManager.dll,System.Runtime.CompilerServices.Unsafe.dll,System.IO.FileSystem.AccessControl.dll,System.Buffers.dll,System.Text.Encodings.Web.dll,System.CodeDom.dll</RuntimeDllsExcludeList>
|
||||
|
@ -30,7 +45,7 @@
|
|||
<Exec Command="$(PowerShellCoreCommandPrefix) "Get-ChildItem -Path $(RepoArtifacts)/$(Configuration) -Recurse -Include 'runtimes' | Remove-Item -Recurse -Force"" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Clean">
|
||||
<Target Name="Clean">
|
||||
<Message Importance="high" Text="Cleaning Cmdlets..." />
|
||||
|
||||
<!-- Clean out the NuGet cache -->
|
||||
|
@ -40,4 +55,33 @@
|
|||
<Exec Command="$(PowerShellCoreCommandPrefix) "Remove-Item -Path $(RepoArtifacts) -Recurse -Force -ErrorAction Ignore"" IgnoreExitCode="true" />
|
||||
<Exec Command="$(PowerShellCoreCommandPrefix) "Get-ChildItem -Path $(MSBuildThisFileDirectory) -Recurse -Include 'bin','obj','TestResults' | Remove-Item -Recurse -Force -ErrorAction Ignore"" IgnoreExitCode="true" />
|
||||
</Target>
|
||||
|
||||
<Target Name="GenerateHelp" Condition="'$(SkipHelp)' == 'false'">
|
||||
<Message Importance="high" Text="Running help generation..." />
|
||||
<MakeDir Directories="$(StaticAnalysisOutputDirectory)" />
|
||||
|
||||
<Exec Command="$(PowerShellCoreCommandPrefix) "Set-Variable -Name ProgressPreference -Value 'SilentlyContinue';. $(RepoTools)/GenerateHelp.ps1 -ValidateMarkdownHelp -GenerateMamlHelp -BuildConfig $(Configuration)"" />
|
||||
<Exec Command="dotnet $(RepoArtifacts)StaticAnalysis/StaticAnalysis.Netcore.dll -p $(RepoArtifacts)$(Configuration) -r $(StaticAnalysisOutputDirectory) -h -u -m @(ModulesChanged)" Condition="'$(RunStaticAnalysis)' == 'true'" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Full" DependsOnTargets="Clean;Build;GenerateHelp;StaticAnalysis;Test" />
|
||||
|
||||
<Target Name="StaticAnalysis" Condition="'$(RunStaticAnalysis)' == 'true'">
|
||||
<Message Importance="high" Text="Running static analysis..." />
|
||||
<MakeDir Directories="$(StaticAnalysisOutputDirectory)" />
|
||||
|
||||
<Exec Command="dotnet $(RepoArtifacts)StaticAnalysis/StaticAnalysis.Netcore.dll -p $(RepoArtifacts)$(Configuration) -r $(StaticAnalysisOutputDirectory) -s -u -m @(ModulesChanged)" />
|
||||
<Exec Command="$(PowerShellCoreCommandPrefix) ". $(RepoTools)/CheckAssemblies.ps1 -BuildConfig $(Configuration) "" />
|
||||
<OnError ExecuteTargets="StaticAnalysisErrorMessage" />
|
||||
</Target>
|
||||
|
||||
<Target Name="StaticAnalysisErrorMessage">
|
||||
<Error Text="StaticAnalysis has failed. Please follow the instructions on this doc: https://github.com/microsoft/partner-center-powershell/blob/master/docs/Debugging-StaticAnalysis-Errors.md" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Test">
|
||||
<Message Importance="high" Text="Running unit tests..." />
|
||||
<MakeDir Directories="$(TestOutputDirectory)" ContinueOnError="false" />
|
||||
<Exec Command="dotnet test Partner-Center-PowerShell.sln --configuration $(Configuration) --framework netcoreapp2.2 --logger trx --results-directory "$(TestOutputDirectory)"" />
|
||||
</Target>
|
||||
</Project>
|
|
@ -5,6 +5,7 @@
|
|||
<RepoRoot>$(MSBuildThisFileDirectory)</RepoRoot>
|
||||
<RepoSrc>$(RepoRoot)src/</RepoSrc>
|
||||
<RepoArtifacts>$(RepoRoot)artifacts/</RepoArtifacts>
|
||||
<RepoTools>$(RepoRoot)tools/</RepoTools>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -31,7 +31,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators
|
|||
}
|
||||
|
||||
return await app.AsRefreshTokenClient().AcquireTokenByRefreshToken(
|
||||
parameters.Scopes,
|
||||
null,
|
||||
parameters.Account.GetProperty(PartnerAccountPropertyType.RefreshToken)).ExecuteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,9 +7,11 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands
|
|||
using System.Management.Automation;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Exceptions;
|
||||
using Extensions;
|
||||
using Factories;
|
||||
using Models.Authentication;
|
||||
using PartnerCenter.Models.Partners;
|
||||
|
||||
/// <summary>
|
||||
/// Cmdlet to login to a Partner Center environment.
|
||||
|
@ -157,6 +159,8 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands
|
|||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
IPartner partnerOperations;
|
||||
OrganizationProfile profile;
|
||||
PartnerAccount account = new PartnerAccount();
|
||||
PartnerEnvironment environment = PartnerEnvironment.PublicEnvironments[Environment];
|
||||
|
||||
|
@ -227,6 +231,19 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands
|
|||
Environment = environment
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
partnerOperations = PartnerSession.Instance.ClientFactory.CreatePartnerOperations();
|
||||
profile = partnerOperations.Profiles.OrganizationProfile.GetAsync().GetAwaiter().GetResult();
|
||||
|
||||
PartnerSession.Instance.Context.CountryCode = profile.DefaultAddress.Country;
|
||||
PartnerSession.Instance.Context.Locale = profile.Culture;
|
||||
}
|
||||
catch (PartnerException)
|
||||
{
|
||||
/* This error can safely be ignored */
|
||||
}
|
||||
|
||||
WriteObject(PartnerSession.Instance.Context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,6 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories
|
|||
|
||||
public static class SharedTokenCacheClientFactory
|
||||
{
|
||||
private const string PowerShellClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46";
|
||||
|
||||
private const string CacheFileName = "msal.cache";
|
||||
|
||||
private static readonly string CacheFilePath =
|
||||
|
@ -46,7 +44,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories
|
|||
string redirectUri = null,
|
||||
string tenantId = null)
|
||||
{
|
||||
ConfidentialClientApplicationBuilder builder = ConfidentialClientApplicationBuilder.Create(clientId ?? PowerShellClientId);
|
||||
ConfidentialClientApplicationBuilder builder = ConfidentialClientApplicationBuilder.Create(clientId);
|
||||
|
||||
if (!string.IsNullOrEmpty(authority))
|
||||
{
|
||||
|
@ -87,7 +85,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories
|
|||
string redirectUri = null,
|
||||
string tenantId = null)
|
||||
{
|
||||
PublicClientApplicationBuilder builder = PublicClientApplicationBuilder.Create(clientId ?? PowerShellClientId);
|
||||
PublicClientApplicationBuilder builder = PublicClientApplicationBuilder.Create(clientId);
|
||||
|
||||
if (!string.IsNullOrEmpty(authority))
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1812:NativeWrapper.Dochostuiinfo is an internal class that is apparently never instantiated. If so, remove the code from the assembly. If this class is intended to contain only static members, make it static (Shared in Visual Basic).", Justification = "Required for the native operations.", Scope = "type", Target = "~T:Microsoft.Store.PartnerCenter.PowerShell.Platforms.NativeWrapper.Dochostuiinfo")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Partner exception is an error thrown by the SDK and in this case we want to catch that error.", Scope = "member", Target = "~M:Microsoft.Store.PartnerCenter.PowerShell.Commands.ConnectPartnerCenter.ProcessRecord")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "Property used as part of a PowerShell cmdlet", Scope = "member", Target = "~P:Microsoft.Store.PartnerCenter.PowerShell.Commands.NewPartnerCustomerOrder.LineItems")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "Property used as part of a PowerShell cmdlet", Scope = "member", Target = "~P:Microsoft.Store.PartnerCenter.PowerShell.Commands.NewPartnerCustomerCart.LineItems")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "Property used as part of a PowerShell cmdlet", Scope = "member", Target = "~P:Microsoft.Store.PartnerCenter.PowerShell.Commands.SetPartnerCustomerCart.LineItems")]
|
||||
|
@ -15,4 +16,3 @@
|
|||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Property used as part of a PowerShell cmdlet", Scope = "member", Target = "~P:Microsoft.Store.PartnerCenter.PowerShell.Commands.NewPartnerCustomerOrderLineItem.ProvisioningContext")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Property used as part of a PowerShell cmdlet", Scope = "member", Target = "~P:Microsoft.Store.PartnerCenter.PowerShell.Commands.NewPartnerCustomerCartLineItem.ProvisioningContext")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Property used as part of a PowerShell cmdlet", Scope = "member", Target = "~P:Microsoft.Store.PartnerCenter.PowerShell.Commands.GetPartnerProductInventory.Variables")]
|
||||
|
||||
|
|
|
@ -3,10 +3,14 @@
|
|||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Context information used for the execution of various tasks.
|
||||
/// </summary>
|
||||
public class PartnerContext
|
||||
public class PartnerContext : IExtensibleModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the account.
|
||||
|
@ -23,6 +27,11 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication
|
|||
/// </summary>
|
||||
public PartnerEnvironment Environment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the extended properties.
|
||||
/// </summary>
|
||||
public IDictionary<string, string> ExtendedProperties { get; } = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the locale.
|
||||
/// </summary>
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputPath>$(RepoArtifacts)$(Configuration)\</OutputPath>
|
||||
<PublishDir>$(OutputPath)</PublishDir>
|
||||
<Version>2.0.1909.2</Version>
|
||||
<Version>2.0.1909.3</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AssemblyVersion>2.0.1909.3</AssemblyVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Commands
|
||||
{
|
||||
using VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the commands related to offers.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class OfferTests : TestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit test for the Get-PartnerOffer cmdlet.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetPartnerOffer()
|
||||
{
|
||||
RunPowerShellTest("Test-GetPartnerOffer");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unit test for the Get-PartnerOffer cmdlet.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetPartnerOfferWithAddOnFlag()
|
||||
{
|
||||
RunPowerShellTest("Test-GetPartnerOfferWithAddOnFlag");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unit test for the Get-PartnerOffer cmdlet.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetPartnerOfferWithOfferId()
|
||||
{
|
||||
RunPowerShellTest("Test-GetPartnerOfferWithOfferId");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unit test for the Get-PartnerOfferCategory cmdlet.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetPartnerOfferCategory()
|
||||
{
|
||||
RunPowerShellTest("Test-GetPartnerOfferCategory");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Exceptions
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Security.Permissions;
|
||||
|
||||
[Serializable]
|
||||
public class ResponseNotFoundException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResponseNotFoundException" /> class.
|
||||
/// </summary>
|
||||
public ResponseNotFoundException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResponseNotFoundException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public ResponseNotFoundException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResponseNotFoundException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
|
||||
public ResponseNotFoundException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResponseNotFoundException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="info">The serialization information that holds the serialized object data about the exception being thrown.</param>
|
||||
/// <param name="context">The context that contains contextual information about the source or destination.</param>
|
||||
protected ResponseNotFoundException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When overridden in a derived class, sets the serialization information with information about the exception.
|
||||
/// </summary>
|
||||
/// <param name="info">The serialization information that holds the serialized object data about the exception being thrown.</param>
|
||||
/// <param name="context">The context that contains contextual information about the source or destination.</param>
|
||||
/// <PermissionSet>
|
||||
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*" />
|
||||
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter" />
|
||||
/// </PermissionSet>
|
||||
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
if (info == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
}
|
||||
|
||||
base.GetObjectData(info, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Extensions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for enumerators.
|
||||
/// </summary>
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Perform an action on each element of a sequence.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of elements in the sequence.</typeparam>
|
||||
/// <param name="sequence">The sequence.</param>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
public static void ForEach<T>(this IEnumerable<T> sequence, Action<T> action)
|
||||
{
|
||||
foreach (T element in sequence)
|
||||
{
|
||||
action(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Factories
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Identity.Client;
|
||||
using PowerShell.Factories;
|
||||
using PowerShell.Models.Authentication;
|
||||
|
||||
/// <summary>
|
||||
/// Factory that mocks authenticaton operations.
|
||||
/// </summary>
|
||||
public class MockAuthenticationFactory : IAuthenticationFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Acquires the security token from the authority.
|
||||
/// </summary>
|
||||
/// <param name="context">Context to be used when requesting a security token.</param>
|
||||
/// <returns>The result from the authentication request.</returns>
|
||||
public AuthenticationResult Authenticate(PartnerAccount account, PartnerEnvironment environment, IEnumerable<string> scopes)
|
||||
{
|
||||
return new AuthenticationResult(
|
||||
"STUB_TOKEN",
|
||||
true,
|
||||
"uniquedId",
|
||||
DateTimeOffset.UtcNow.AddHours(1),
|
||||
DateTimeOffset.UtcNow.AddHours(1),
|
||||
"xxxx-xxxx-xxxx-xxxx",
|
||||
null,
|
||||
"STUB_TOKEN",
|
||||
scopes);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Factories
|
||||
{
|
||||
using Network;
|
||||
using PowerShell.Factories;
|
||||
|
||||
/// <summary>
|
||||
/// Factory that provides initialized clients used to mock interactions with online services.
|
||||
/// </summary>
|
||||
public class MockClientFactory : IClientFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegating handler used to intercept partner service client operations.
|
||||
/// </summary>
|
||||
private readonly HttpMockHandler httpMockHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Credentials used when communicating with the partner service.
|
||||
/// </summary>
|
||||
private readonly IPartnerCredentials credentials;
|
||||
|
||||
/// <summary>
|
||||
/// Provides the ability to interact with the partner service.
|
||||
/// </summary>
|
||||
private static IPartner partnerOperations;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MockClientFactory" /> class.
|
||||
/// </summary>
|
||||
/// <param name="httpMockHandler">The delegating handler used test HTTP operations.</param>
|
||||
/// <param name="credentials">Credentials used when communicating with the partner service.</param>
|
||||
public MockClientFactory(HttpMockHandler httpMockHandler, IPartnerCredentials credentials)
|
||||
{
|
||||
this.credentials = credentials;
|
||||
this.httpMockHandler = httpMockHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the object used to interface with Partner Center.
|
||||
/// </summary>
|
||||
/// <returns>An instance of the <see cref="PartnerOperations" /> class.</returns>
|
||||
public IPartner CreatePartnerOperations()
|
||||
{
|
||||
if (partnerOperations == null)
|
||||
{
|
||||
partnerOperations = TestPartnerService.CreatePartnerOperations(
|
||||
credentials,
|
||||
httpMockHandler);
|
||||
}
|
||||
|
||||
return partnerOperations;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// The available HTTP content types.
|
||||
/// </summary>
|
||||
internal enum HttpContentType
|
||||
{
|
||||
/// <summary>
|
||||
/// The type if the content contains binary.
|
||||
/// </summary>
|
||||
Binary,
|
||||
|
||||
/// <summary>
|
||||
/// The type if the content contains one ore more string literal.
|
||||
/// </summary>
|
||||
Ascii,
|
||||
|
||||
/// <summary>
|
||||
/// The type if the content is empty.
|
||||
/// </summary>
|
||||
Null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Network
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
using PartnerCenter.Models.JsonConverters;
|
||||
|
||||
/// <summary>
|
||||
/// A mock delegating handler used for testing HTTP server interactions.
|
||||
/// </summary>
|
||||
public class HttpMockHandler : DelegatingHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the session records directory.
|
||||
/// </summary>
|
||||
private const string SessionsDirectory = "SessionRecords";
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that entries are enqueued in a thread safe fashion.
|
||||
/// </summary>
|
||||
private static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Provides the ability to map a request with it's corresponding response.
|
||||
/// </summary>
|
||||
private readonly IHttpRecordMatcher matcher;
|
||||
|
||||
/// <summary>
|
||||
/// A collection of the <see cref="HttpResponseRecord" /> class that represent the HTTP operations performed.
|
||||
/// </summary>
|
||||
private readonly HttpResponseRecords records;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpMockHandler" /> class.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode of the mock server.</param>
|
||||
public HttpMockHandler(HttpMockHandlerMode mode)
|
||||
{
|
||||
Mode = mode;
|
||||
matcher = new HttpRecordMatcher("x-ms-version");
|
||||
records = new HttpResponseRecords(matcher);
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mode for the mock handler.
|
||||
/// </summary>
|
||||
public HttpMockHandlerMode Mode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sends a HTTP request to the inner handler to send to the server as an asynchronous operation.
|
||||
/// </summary>
|
||||
/// <param name="request">The HTTP request message to send to the server.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
|
||||
/// <returns>The response from the HTTP operation.</returns>
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
HttpResponseMessage response;
|
||||
string key;
|
||||
|
||||
if (Mode == HttpMockHandlerMode.Playback)
|
||||
{
|
||||
lock (records)
|
||||
{
|
||||
key = matcher.GetMatchingKey(request);
|
||||
|
||||
if (records[key] == null)
|
||||
{
|
||||
throw new ResponseNotFoundException($"Unable to locate a reponse for {key}");
|
||||
}
|
||||
|
||||
response = records[matcher.GetMatchingKey(request)].GetResponse();
|
||||
response.RequestMessage = request;
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
await Semaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
lock (records)
|
||||
{
|
||||
records.Enqueue(new HttpResponseRecord(response));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Flush(string identity)
|
||||
{
|
||||
List<HttpResponseRecord> processedRecords;
|
||||
JsonSerializer serializer;
|
||||
string location;
|
||||
|
||||
if (Mode == HttpMockHandlerMode.Record && records.Count > 0)
|
||||
{
|
||||
processedRecords = new List<HttpResponseRecord>();
|
||||
|
||||
lock (records)
|
||||
{
|
||||
foreach (HttpResponseRecord response in records.GetAllEntities())
|
||||
{
|
||||
response.RequestHeaders.Remove("Authorization");
|
||||
response.RequestHeaders.Remove("MS-ContinuationToken");
|
||||
processedRecords.Add(response);
|
||||
}
|
||||
}
|
||||
|
||||
serializer = new JsonSerializer
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||
TypeNameHandling = TypeNameHandling.None
|
||||
};
|
||||
|
||||
serializer.Converters.Add(new EnumJsonConverter());
|
||||
location = Path.Combine(AppContext.BaseDirectory, SessionsDirectory);
|
||||
|
||||
if (!Directory.Exists(location))
|
||||
{
|
||||
Directory.CreateDirectory(location);
|
||||
}
|
||||
|
||||
using (StreamWriter writer = File.CreateText(Path.Combine(location, $"{identity}.json")))
|
||||
{
|
||||
serializer.Serialize(writer, processedRecords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
List<HttpResponseRecord> processed;
|
||||
JsonSerializer serializer;
|
||||
string location;
|
||||
|
||||
if (Mode == HttpMockHandlerMode.Playback)
|
||||
{
|
||||
serializer = new JsonSerializer
|
||||
{
|
||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||
TypeNameHandling = TypeNameHandling.None
|
||||
};
|
||||
|
||||
processed = new List<HttpResponseRecord>();
|
||||
|
||||
#if FullNetFx
|
||||
location = Path.Combine(Assembly.GetCallingAssembly().Location, SessionsDirectory);
|
||||
#else
|
||||
location = Path.Combine(AppContext.BaseDirectory, SessionsDirectory);
|
||||
#endif
|
||||
|
||||
foreach (string fileName in Directory.GetFiles(location))
|
||||
{
|
||||
using (StreamReader reader = File.OpenText(fileName))
|
||||
{
|
||||
using (JsonReader jsonReader = new JsonTextReader(reader))
|
||||
{
|
||||
processed.AddRange(serializer.Deserialize<List<HttpResponseRecord>>(jsonReader));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lock (records)
|
||||
{
|
||||
foreach (HttpResponseRecord record in processed)
|
||||
{
|
||||
records.Enqueue(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// The possible modes for the mock HTTP handler.
|
||||
/// </summary>
|
||||
public enum HttpMockHandlerMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The playback mode should always be after a successful record session.
|
||||
/// The mock server matches the given requests and return their stored
|
||||
/// corresponding responses.
|
||||
/// </summary>
|
||||
Playback,
|
||||
|
||||
/// <summary>
|
||||
/// In this mode the mock server watches the out-going requests and records
|
||||
/// their corresponding responses.
|
||||
/// </summary>
|
||||
Record
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Network
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Used by the mock server for mapping a request with it's corresponding response.
|
||||
/// </summary>
|
||||
public class HttpRecordMatcher : IHttpRecordMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpRecordMatcher" /> class.
|
||||
/// </summary>
|
||||
/// <param name="matchingHeaders">The headers used to match the record.</param>
|
||||
public HttpRecordMatcher(params string[] matchingHeaders)
|
||||
{
|
||||
MatchingHeaders = new HashSet<string>(matchingHeaders, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the matching headers.
|
||||
/// </summary>
|
||||
public HashSet<string> MatchingHeaders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key used for mapping a given record request's with its response.
|
||||
/// </summary>
|
||||
/// <param name="record">The record entry containing the request info</param>
|
||||
/// <returns>The key used for the mapping.</returns>
|
||||
public string GetMatchingKey(HttpResponseRecord record)
|
||||
{
|
||||
return GetMatchingKey(record.RequestMethod,
|
||||
(record.EncodedReqeustAddress ?? Convert.ToBase64String(Encoding.UTF8.GetBytes(record.RequestUri.PathAndQuery))),
|
||||
record.RequestHeaders);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key mapping for the given request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request to be mapped.</param>
|
||||
/// <returns>The key corresponding to this request.</returns>
|
||||
public string GetMatchingKey(HttpRequestMessage request)
|
||||
{
|
||||
Dictionary<string, List<string>> requestHeaders = new Dictionary<string, List<string>>();
|
||||
|
||||
request.Headers.ForEach(h => requestHeaders.Add(h.Key, h.Value.ToList()));
|
||||
|
||||
if (request.Content != null)
|
||||
{
|
||||
request.Content.Headers.ForEach(h => requestHeaders.Add(h.Key, h.Value.ToList()));
|
||||
}
|
||||
|
||||
return GetMatchingKey(
|
||||
request.Method.Method,
|
||||
Convert.ToBase64String(Encoding.UTF8.GetBytes(request.RequestUri.PathAndQuery)),
|
||||
requestHeaders);
|
||||
}
|
||||
|
||||
private string GetMatchingKey(string httpMethod, string requestUri, Dictionary<string, List<string>> requestHeaders)
|
||||
{
|
||||
StringBuilder key = new StringBuilder($"{httpMethod} {requestUri}");
|
||||
|
||||
if (requestHeaders != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, List<string>> requestHeader in requestHeaders.OrderBy(h => h.Key))
|
||||
{
|
||||
if (MatchingHeaders.Contains(requestHeader.Key))
|
||||
{
|
||||
key.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
" ({0}={1})",
|
||||
requestHeader.Key,
|
||||
string.Join(",", requestHeader.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return key.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Network
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the response of a HTTP operation.
|
||||
/// </summary>
|
||||
public sealed class HttpResponseRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpResponseRecord" /> class.
|
||||
/// </summary>
|
||||
public HttpResponseRecord()
|
||||
{
|
||||
RequestHeaders = new Dictionary<string, List<string>>();
|
||||
ResponseHeaders = new Dictionary<string, List<string>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpResponseRecord" /> class.
|
||||
/// </summary>
|
||||
/// <param name="response">The response from the HTTP operation.</param>
|
||||
public HttpResponseRecord(HttpResponseMessage response)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(response));
|
||||
}
|
||||
|
||||
EncodedReqeustAddress = Convert.ToBase64String(Encoding.UTF8.GetBytes(response.RequestMessage.RequestUri.PathAndQuery));
|
||||
RequestHeaders = new Dictionary<string, List<string>>();
|
||||
RequestMethod = response.RequestMessage.Method.ToString();
|
||||
RequestUri = response.RequestMessage.RequestUri;
|
||||
ResponseHeaders = new Dictionary<string, List<string>>();
|
||||
StatusCode = response.StatusCode;
|
||||
|
||||
response.RequestMessage.Headers.ForEach(h => RequestHeaders.Add(h.Key, h.Value.ToList()));
|
||||
response.Headers.ForEach(h => ResponseHeaders.Add(h.Key, h.Value.ToList()));
|
||||
|
||||
if (DetectContentType(response.Content) != HttpContentType.Null)
|
||||
{
|
||||
ResponseBody = HttpUtilities.FormatHttpContent(response.Content);
|
||||
ResponseBody = Regex.Replace(ResponseBody, @"(?<=continuation_token\=)(.*)(?=%3d%3d)", string.Empty);
|
||||
response.Content.Headers.ForEach(h => ResponseHeaders.Add(h.Key, h.Value.ToList()));
|
||||
}
|
||||
|
||||
if (DetectContentType(response.RequestMessage.Content) != HttpContentType.Null)
|
||||
{
|
||||
RequestBody = HttpUtilities.FormatHttpContent(response.RequestMessage.Content);
|
||||
response.RequestMessage.Content.Headers.ForEach(h => RequestHeaders.Add(h.Key, h.Value.ToList()));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request addressed encoded as a base64 string.
|
||||
/// </summary>
|
||||
public string EncodedReqeustAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the body of the HTTP request.
|
||||
/// </summary>
|
||||
public string RequestBody { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP headers for the HTTP request.
|
||||
/// </summary>
|
||||
public Dictionary<string, List<string>> RequestHeaders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request HTTP method.
|
||||
/// </summary>
|
||||
public string RequestMethod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URI used for the HTTP request.
|
||||
/// </summary>
|
||||
public Uri RequestUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the body of the HTTP response.
|
||||
/// </summary>
|
||||
public string ResponseBody { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP headers for the HTTP response.
|
||||
/// </summary>
|
||||
public Dictionary<string, List<string>> ResponseHeaders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status code of the HTTP response.
|
||||
/// </summary>
|
||||
public HttpStatusCode StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance the <see cref="HttpResponseMessage" /> class that represents this HTTP operation.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An instance of the <see cref="HttpResponseMessage" /> class that represents the HTTP operation.
|
||||
/// </returns>
|
||||
public HttpResponseMessage GetResponse()
|
||||
{
|
||||
HttpResponseMessage response = new HttpResponseMessage()
|
||||
{
|
||||
Content = HttpUtilities.CreateHttpContent(ResponseBody),
|
||||
StatusCode = StatusCode
|
||||
};
|
||||
|
||||
ResponseHeaders.ForEach(h => response.Content.Headers.TryAddWithoutValidation(h.Key, h.Value));
|
||||
ResponseHeaders.ForEach(h => response.Headers.TryAddWithoutValidation(h.Key, h.Value));
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static HttpContentType DetectContentType(HttpContent content)
|
||||
{
|
||||
HttpContentType contentType = HttpContentType.Null;
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
if (HttpUtilities.IsHttpContentBinary(content))
|
||||
{
|
||||
contentType = HttpContentType.Binary;
|
||||
}
|
||||
else
|
||||
{
|
||||
contentType = HttpContentType.Ascii;
|
||||
}
|
||||
}
|
||||
|
||||
return contentType;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Network
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public sealed class HttpResponseRecords
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of HTTP operations.
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, HttpResponseRecord> sessionRecords;
|
||||
|
||||
/// <summary>
|
||||
/// Provides the ability to map a request with it's corresponding response.
|
||||
/// </summary>
|
||||
private readonly IHttpRecordMatcher matcher;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpResponseRecords" /> class.
|
||||
/// </summary>
|
||||
/// <param name="matcher">The matcher used to map a request with it's corresponding response.</param>
|
||||
public HttpResponseRecords(IHttpRecordMatcher matcher)
|
||||
{
|
||||
this.matcher = matcher;
|
||||
sessionRecords = new Dictionary<string, HttpResponseRecord>();
|
||||
}
|
||||
|
||||
public HttpResponseRecord this[string key]
|
||||
{
|
||||
get => sessionRecords[key];
|
||||
set => sessionRecords[key] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of records contained in the collecion.
|
||||
/// </summary>
|
||||
public int Count => sessionRecords.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all available records.
|
||||
/// </summary>
|
||||
/// <returns>All of the available records.</returns>
|
||||
public IEnumerable<HttpResponseRecord> GetAllEntities()
|
||||
{
|
||||
foreach (HttpResponseRecord record in sessionRecords.Values)
|
||||
{
|
||||
yield return record;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an instance of the <see cref="HttpResponseRecord" /> class to the collection.
|
||||
/// </summary>
|
||||
/// <param name="record">The instance of the <see cref="HttpResponseRecord" /> class to be added.</param>
|
||||
public void Enqueue(HttpResponseRecord record)
|
||||
{
|
||||
sessionRecords[matcher.GetMatchingKey(record)] = record;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Network
|
||||
{
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
/// <summary>
|
||||
/// Various utility functions for HTTP operations.
|
||||
/// </summary>
|
||||
public static class HttpUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// The regular expression to be used when checking if the HTTP content contains binary.
|
||||
/// </summary>
|
||||
private static readonly Regex binaryMimeRegex = new Regex("(image/*|audio/*|video/*|application/octet-stream|multipart/form-data)");
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the <see cref="HttpContent" /> class that represents the content.
|
||||
/// </summary>
|
||||
/// <param name="contentData">The data used to build the HTTP content object.</param>
|
||||
/// <returns>An instance of the <see cref="HttpContent"/> that represents the content.</returns>
|
||||
public static HttpContent CreateHttpContent(string contentData)
|
||||
{
|
||||
HttpContent createdContent = null;
|
||||
byte[] hashBytes = null;
|
||||
bool isContentDataBinary = true;
|
||||
|
||||
if (contentData != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
hashBytes = Convert.FromBase64String(contentData);
|
||||
|
||||
if (hashBytes != null)
|
||||
{
|
||||
createdContent = new ByteArrayContent(hashBytes);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
isContentDataBinary = false;
|
||||
}
|
||||
|
||||
if (!isContentDataBinary)
|
||||
{
|
||||
createdContent = new StringContent(contentData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
createdContent = new StringContent(string.Empty);
|
||||
}
|
||||
|
||||
return createdContent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the HTTP content to a string literal.
|
||||
/// </summary>
|
||||
/// <param name="httpContent">The HTTP content to be converted.</param>
|
||||
/// <returns>A formatted string that represents the HTTP content.</returns>
|
||||
public static string FormatHttpContent(HttpContent httpContent)
|
||||
{
|
||||
string formattedContent = string.Empty;
|
||||
|
||||
if (IsHttpContentBinary(httpContent))
|
||||
{
|
||||
formattedContent = Convert.ToBase64String(Task.Run(() => httpContent.ReadAsByteArrayAsync()).Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
formattedContent = FormatString(httpContent.ReadAsStringAsync().Result);
|
||||
}
|
||||
|
||||
return formattedContent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <returns></returns>
|
||||
public static string FormatString(string content)
|
||||
{
|
||||
if (IsJson(content))
|
||||
{
|
||||
return TryFormatJson(content);
|
||||
}
|
||||
else if (IsXml(content))
|
||||
{
|
||||
return TryFormatXml(content);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the content contains binary.
|
||||
/// </summary>
|
||||
/// <param name="content">The HTTP content to be checked.</param>
|
||||
/// <returns><c>true</c> if the content contains binary; otherwise <c>false</c>.</returns>
|
||||
public static bool IsHttpContentBinary(HttpContent content)
|
||||
{
|
||||
bool isBinary = false;
|
||||
string contentType = content?.Headers?.ContentType?.MediaType;
|
||||
|
||||
if (!string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
isBinary = binaryMimeRegex.IsMatch(contentType);
|
||||
}
|
||||
|
||||
return isBinary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the content is JSON.
|
||||
/// </summary>
|
||||
/// <param name="content">The content to be checked.</param>
|
||||
/// <returns><c>true</c> if the content is JSON; otherwise <c>false</c>.</returns>
|
||||
public static bool IsJson(string content)
|
||||
{
|
||||
content = content.Trim();
|
||||
|
||||
return content.StartsWith("{", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& content.EndsWith("}", StringComparison.InvariantCultureIgnoreCase)
|
||||
|| content.StartsWith("[", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& content.EndsWith("]", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the content is valid XML or not.
|
||||
/// </summary>
|
||||
/// <param name="content">The content to be checked.</param>
|
||||
/// <returns><c>true</c> if the content is XML; otherwise <c>false</c>.</returns>
|
||||
public static bool IsXml(string content)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument.Parse(content);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the JSON using the appropriate indentation.
|
||||
/// </summary>
|
||||
/// <param name="content">The JSON to be formatted.</param>
|
||||
/// <returns>The formatted JSON string.</returns>
|
||||
public static string TryFormatJson(string content)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonConvert.SerializeObject(JsonConvert.DeserializeObject(content), Formatting.Indented);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the XML using the appropriate indentation.
|
||||
/// </summary>
|
||||
/// <param name="content">The XML to be formatted.</param>
|
||||
/// <returns>The formatted XML string.</returns>
|
||||
public static string TryFormatXml(string content)
|
||||
{
|
||||
try
|
||||
{
|
||||
return XDocument.Parse(content).ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests.Network
|
||||
{
|
||||
using System.Net.Http;
|
||||
|
||||
/// <summary>
|
||||
/// Interface that used by the mock server for mapping a request with it's corresponding response.
|
||||
/// </summary>
|
||||
public interface IHttpRecordMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the key used for mapping a given record request's with its response.
|
||||
/// </summary>
|
||||
/// <param name="record">The record entry containing the request info</param>
|
||||
/// <returns>The key used for the mapping.</returns>
|
||||
string GetMatchingKey(HttpResponseRecord record);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key mapping for the given request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request to be mapped.</param>
|
||||
/// <returns>The key corresponding to this request.</returns>
|
||||
string GetMatchingKey(HttpRequestMessage request);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\..\repo.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AssemblyName>Microsoft.Store.PartnerCenter.PowerShell.UnitTests</AssemblyName>
|
||||
<RootNamespace>Microsoft.Store.PartnerCenter.PowerShell.UnitTests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="Microsoft.PowerShell.SDK" Version="6.2.3" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\PowerShell\PowerShell.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Network\" />
|
||||
<Folder Include="Exceptions\" />
|
||||
<Folder Include="SessionRecords\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="SessionRecords\*.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="ScenarioTests\*.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,65 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Verifies that two given objects are equal.
|
||||
#>
|
||||
function Assert-AreEqual
|
||||
{
|
||||
param([object] $expected, [object] $actual, [string] $message)
|
||||
|
||||
if (!$message)
|
||||
{
|
||||
$message = "Assertion failed because expected '$expected' does not match actual '$actual'"
|
||||
}
|
||||
|
||||
if ($expected -ne $actual)
|
||||
{
|
||||
throw $message
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Validates that the given scriptblock returns false.
|
||||
#>
|
||||
function Assert-False
|
||||
{
|
||||
param([ScriptBlock]$script, [string]$message)
|
||||
|
||||
if (!$message)
|
||||
{
|
||||
$message = "Assertion failed: " + $script
|
||||
}
|
||||
|
||||
$result = &$script
|
||||
|
||||
if ($result)
|
||||
{
|
||||
throw $message
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Validates a string is not null.
|
||||
#>
|
||||
function Assert-NotNull
|
||||
{
|
||||
param($value)
|
||||
|
||||
Assert-False { $null -eq $value }
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Validates a string is not null or empty.
|
||||
#>
|
||||
function Assert-NotNullOrEmpty
|
||||
{
|
||||
param([string]$value)
|
||||
|
||||
Assert-False { [string]::IsNullOrEmpty($value) }
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Tests to be performed using the Get-PartnerOffer cmdlet.
|
||||
#>
|
||||
function Test-GetPartnerOffer
|
||||
{
|
||||
$offers = Get-PartnerOffer -CountryCode 'US'
|
||||
|
||||
Assert-NotNull $offers
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Tests to be performed using the Get-PartnerOfferCategory cmdlet.
|
||||
#>
|
||||
function Test-GetPartnerOfferCategory
|
||||
{
|
||||
$categories = Get-PartnerOfferCategory -CountryCode 'US'
|
||||
|
||||
Assert-NotNull $categories
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Tests to be performed using the Get-PartnerOffer cmdlet.
|
||||
#>
|
||||
function Test-GetPartnerOfferWithAddOnFlag
|
||||
{
|
||||
$offers = Get-PartnerOffer -CountryCode 'US' -AddOn
|
||||
|
||||
Assert-NotNull $offers
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Tests to be performed using the Get-PartnerOffer cmdlet.
|
||||
#>
|
||||
function Test-GetPartnerOfferWithOfferId
|
||||
{
|
||||
$offer = Get-PartnerOffer -CountryCode 'US' -OfferId '8BDBB60B-E526-43E9-92EF-AB760C8E0B72'
|
||||
|
||||
Assert-NotNull $offer
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
[
|
||||
{
|
||||
"EncodedReqeustAddress": "L3YxL2NvdW50cnl2YWxpZGF0aW9ucnVsZXMvVVM=",
|
||||
"RequestBody": null,
|
||||
"RequestHeaders": {
|
||||
"Accept": [
|
||||
"application/json"
|
||||
],
|
||||
"MS-PartnerCenter-Client": [
|
||||
"Partner Center PowerShell"
|
||||
],
|
||||
"MS-CorrelationId": [
|
||||
"85569ec5-7476-46bb-b336-3a99fa3a2705"
|
||||
],
|
||||
"X-Locale": [
|
||||
"en-US"
|
||||
],
|
||||
"MS-RequestId": [
|
||||
"bb845bcd-25c3-4d04-ad69-04c253425f98"
|
||||
],
|
||||
"MS-SdkVersion": [
|
||||
"1.5.1901.1"
|
||||
],
|
||||
"User-Agent": [
|
||||
"FxVersion/4.8.3710.0",
|
||||
"OSName/Windows",
|
||||
"OSVersion/10.0.18312.0",
|
||||
"Microsoft.Store.PartnerCenter.Network.PartnerServiceClient/1.0.0.0"
|
||||
]
|
||||
},
|
||||
"RequestMethod": "GET",
|
||||
"RequestUri": "https://api.partnercenter.microsoft.com/v1/countryvalidationrules/US",
|
||||
"ResponseBody": "{\r\n \"iso2Code\": \"US\",\r\n \"defaultCulture\": \"en-US\",\r\n \"isStateRequired\": true,\r\n \"supportedStatesList\": [\r\n \"AK\",\r\n \"AL\",\r\n \"AR\",\r\n \"AZ\",\r\n \"CA\",\r\n \"CO\",\r\n \"CT\",\r\n \"DC\",\r\n \"DE\",\r\n \"FL\",\r\n \"GA\",\r\n \"HI\",\r\n \"IA\",\r\n \"ID\",\r\n \"IL\",\r\n \"IN\",\r\n \"KS\",\r\n \"KY\",\r\n \"LA\",\r\n \"MA\",\r\n \"MD\",\r\n \"ME\",\r\n \"MI\",\r\n \"MN\",\r\n \"MO\",\r\n \"MS\",\r\n \"MT\",\r\n \"NC\",\r\n \"ND\",\r\n \"NE\",\r\n \"NH\",\r\n \"NJ\",\r\n \"NM\",\r\n \"NV\",\r\n \"NY\",\r\n \"OH\",\r\n \"OK\",\r\n \"OR\",\r\n \"PA\",\r\n \"RI\",\r\n \"SC\",\r\n \"SD\",\r\n \"TN\",\r\n \"TX\",\r\n \"UT\",\r\n \"VA\",\r\n \"VT\",\r\n \"WA\",\r\n \"WI\",\r\n \"WV\",\r\n \"WY\"\r\n ],\r\n \"supportedLanguagesList\": [\r\n \"en\",\r\n \"es\"\r\n ],\r\n \"supportedCulturesList\": [\r\n \"en-US\",\r\n \"es-US\"\r\n ],\r\n \"isPostalCodeRequired\": true,\r\n \"postalCodeRegex\": \"^\\\\d{5}(-\\\\d{4})?$\",\r\n \"isCityRequired\": true,\r\n \"isVatIdSupported\": false,\r\n \"phoneNumberRegex\": \"^(1[ \\\\-\\\\/\\\\.]?)?(\\\\((\\\\d{3})\\\\)|(\\\\d{3}))[ \\\\-\\\\/\\\\.]?(\\\\d{3})[ \\\\-\\\\/\\\\.]?(\\\\d{4})$\",\r\n \"isTaxIdSupported\": true,\r\n \"isTaxIdOptional\": true,\r\n \"countryCallingCodesList\": [\r\n \"1\"\r\n ],\r\n \"attributes\": {\r\n \"objectType\": \"CountryValidationRules\"\r\n }\r\n}",
|
||||
"ResponseHeaders": {
|
||||
"MS-CorrelationId": [
|
||||
"85569ec5-7476-46bb-b336-3a99fa3a2705"
|
||||
],
|
||||
"MS-RequestId": [
|
||||
"bb845bcd-25c3-4d04-ad69-04c253425f98"
|
||||
],
|
||||
"MS-CV": [
|
||||
"xqqtn+OEVE2ridoS.0"
|
||||
],
|
||||
"MS-ServerId": [
|
||||
"0000112D"
|
||||
],
|
||||
"Request-Context": [
|
||||
"appId=cid-v1:03ce8ca8-8373-4021-8f25-d5dd45c7b12f"
|
||||
],
|
||||
"Date": [
|
||||
"Fri, 11 Jan 2019 23:26:03 GMT"
|
||||
],
|
||||
"Content-Length": [
|
||||
"780"
|
||||
],
|
||||
"Content-Type": [
|
||||
"application/json; charset=utf-8"
|
||||
]
|
||||
},
|
||||
"StatusCode": "OK"
|
||||
}
|
||||
]
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,62 @@
|
|||
[
|
||||
{
|
||||
"EncodedReqeustAddress": "L3YxL29mZmVycy84QkRCQjYwQi1FNTI2LTQzRTktOTJFRi1BQjc2MEM4RTBCNzI/Y291bnRyeT1VUw==",
|
||||
"RequestBody": null,
|
||||
"RequestHeaders": {
|
||||
"Accept": [
|
||||
"application/json"
|
||||
],
|
||||
"MS-PartnerCenter-Client": [
|
||||
"Partner Center PowerShell"
|
||||
],
|
||||
"MS-CorrelationId": [
|
||||
"1465c64a-9e93-46a4-a8da-e3ab67665e64"
|
||||
],
|
||||
"X-Locale": [
|
||||
"en-US"
|
||||
],
|
||||
"MS-RequestId": [
|
||||
"cb8fa358-00b0-45c0-a410-35beb9ffbfd9"
|
||||
],
|
||||
"MS-SdkVersion": [
|
||||
"1.5.1901.1"
|
||||
],
|
||||
"User-Agent": [
|
||||
"FxVersion/4.8.3710.0",
|
||||
"OSName/Windows",
|
||||
"OSVersion/10.0.18312.0",
|
||||
"Microsoft.Store.PartnerCenter.Network.PartnerServiceClient/1.0.0.0"
|
||||
]
|
||||
},
|
||||
"RequestMethod": "GET",
|
||||
"RequestUri": "https://api.partnercenter.microsoft.com:443/v1/offers/8BDBB60B-E526-43E9-92EF-AB760C8E0B72?country=US",
|
||||
"ResponseBody": "{\r\n \"id\": \"8BDBB60B-E526-43E9-92EF-AB760C8E0B72\",\r\n \"name\": \"Microsoft 365 E5\",\r\n \"description\": \"Office 365 Enterprise E5, Enterprise Mobility + Security E5, and Window 10 Enterprise E5. This per-user licensed suite of products offers customers the latest, most advanced enterprise security, management, collaboration, and business analytics.\",\r\n \"minimumQuantity\": 1,\r\n \"maximumQuantity\": 10000000,\r\n \"rank\": 174,\r\n \"uri\": \"/3c95518e-8c37-41e3-9627-0ca339200f53/Offers/8BDBB60B-E526-43E9-92EF-AB760C8E0B72\",\r\n \"locale\": \"en-US\",\r\n \"country\": \"US\",\r\n \"category\": {\r\n \"id\": \"Enterprise_Key\",\r\n \"name\": \"Enterprise\",\r\n \"rank\": 20,\r\n \"locale\": \"en-US\",\r\n \"country\": \"US\",\r\n \"attributes\": {\r\n \"objectType\": \"OfferCategory\"\r\n }\r\n },\r\n \"limitUnitOfMeasure\": \"None\",\r\n \"limit\": 0,\r\n \"termUnitOfMeasure\": \"Year\",\r\n \"term\": 1,\r\n \"prerequisiteOffers\": [],\r\n \"isAddOn\": false,\r\n \"hasAddOns\": true,\r\n \"isAvailableForPurchase\": true,\r\n \"billing\": \"license\",\r\n \"supportedBillingCycles\": [\r\n \"annual\",\r\n \"monthly\"\r\n ],\r\n \"isAutoRenewable\": true,\r\n \"isInternal\": false,\r\n \"isMicrosoftProduct\": true,\r\n \"conversionTargetOffers\": [],\r\n \"reselleeQualifications\": [],\r\n \"resellerQualifications\": [],\r\n \"salesGroupId\": \"Default\",\r\n \"isTrial\": false,\r\n \"acquisitionType\": \"purchase\",\r\n \"supportedCatalogTypes\": [\r\n \"sandbox\",\r\n \"live\"\r\n ],\r\n \"actions\": [\r\n \"Cancel\",\r\n \"Edit\"\r\n ],\r\n \"product\": {\r\n \"id\": \"06EBC4EE-1BB5-47DD-8120-11324BC54E06\",\r\n \"name\": \"Microsoft 365 E5\",\r\n \"unit\": \"Licenses\"\r\n },\r\n \"unitType\": \"Licenses\",\r\n \"links\": {\r\n \"learnMore\": {\r\n \"method\": \"GET\",\r\n \"headers\": []\r\n },\r\n \"self\": {\r\n \"uri\": \"/offers/8BDBB60B-E526-43E9-92EF-AB760C8E0B72?country=US\",\r\n \"method\": \"GET\",\r\n \"headers\": []\r\n }\r\n },\r\n \"attributes\": {\r\n \"objectType\": \"Offer\"\r\n }\r\n}",
|
||||
"ResponseHeaders": {
|
||||
"MS-CorrelationId": [
|
||||
"1465c64a-9e93-46a4-a8da-e3ab67665e64"
|
||||
],
|
||||
"MS-RequestId": [
|
||||
"cb8fa358-00b0-45c0-a410-35beb9ffbfd9"
|
||||
],
|
||||
"MS-CV": [
|
||||
"fc1RI18AXEK3Tdmj.0"
|
||||
],
|
||||
"MS-ServerId": [
|
||||
"0000112E"
|
||||
],
|
||||
"Request-Context": [
|
||||
"appId=cid-v1:03ce8ca8-8373-4021-8f25-d5dd45c7b12f"
|
||||
],
|
||||
"Date": [
|
||||
"Fri, 11 Jan 2019 23:18:28 GMT"
|
||||
],
|
||||
"Content-Length": [
|
||||
"1488"
|
||||
],
|
||||
"Content-Type": [
|
||||
"application/json; charset=utf-8"
|
||||
]
|
||||
},
|
||||
"StatusCode": "OK"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,62 @@
|
|||
[
|
||||
{
|
||||
"EncodedReqeustAddress": "L3YxL29mZmVyY2F0ZWdvcmllcz9jb3VudHJ5PVVT",
|
||||
"RequestBody": null,
|
||||
"RequestHeaders": {
|
||||
"Accept": [
|
||||
"application/json"
|
||||
],
|
||||
"MS-PartnerCenter-Client": [
|
||||
"Partner Center PowerShell"
|
||||
],
|
||||
"MS-CorrelationId": [
|
||||
"1465c64a-9e93-46a4-a8da-e3ab67665e64"
|
||||
],
|
||||
"X-Locale": [
|
||||
"en-US"
|
||||
],
|
||||
"MS-RequestId": [
|
||||
"b32574ec-fae7-4ea2-915a-0e1ab46e92ca"
|
||||
],
|
||||
"MS-SdkVersion": [
|
||||
"1.5.1901.1"
|
||||
],
|
||||
"User-Agent": [
|
||||
"FxVersion/4.8.3710.0",
|
||||
"OSName/Windows",
|
||||
"OSVersion/10.0.18312.0",
|
||||
"Microsoft.Store.PartnerCenter.Network.PartnerServiceClient/1.0.0.0"
|
||||
]
|
||||
},
|
||||
"RequestMethod": "GET",
|
||||
"RequestUri": "https://api.partnercenter.microsoft.com:443/v1/offercategories?country=US",
|
||||
"ResponseBody": "{\r\n \"totalCount\": 4,\r\n \"items\": [\r\n {\r\n \"id\": \"Enterprise_Key\",\r\n \"name\": \"Enterprise\",\r\n \"rank\": 20,\r\n \"locale\": \"en-US\",\r\n \"country\": \"US\",\r\n \"attributes\": {\r\n \"objectType\": \"OfferCategory\"\r\n }\r\n },\r\n {\r\n \"id\": \"SmallBusiness_Key\",\r\n \"name\": \"Small business\",\r\n \"rank\": 30,\r\n \"locale\": \"en-US\",\r\n \"country\": \"US\",\r\n \"attributes\": {\r\n \"objectType\": \"OfferCategory\"\r\n }\r\n },\r\n {\r\n \"id\": \"Nonprofit_Key\",\r\n \"name\": \"Nonprofit\",\r\n \"rank\": 60,\r\n \"locale\": \"en-US\",\r\n \"country\": \"US\",\r\n \"attributes\": {\r\n \"objectType\": \"OfferCategory\"\r\n }\r\n },\r\n {\r\n \"id\": \"Trial_Key\",\r\n \"name\": \"Trial\",\r\n \"rank\": 70,\r\n \"locale\": \"en-US\",\r\n \"country\": \"US\",\r\n \"attributes\": {\r\n \"objectType\": \"OfferCategory\"\r\n }\r\n }\r\n ],\r\n \"attributes\": {\r\n \"objectType\": \"Collection\"\r\n }\r\n}",
|
||||
"ResponseHeaders": {
|
||||
"MS-CorrelationId": [
|
||||
"1465c64a-9e93-46a4-a8da-e3ab67665e64"
|
||||
],
|
||||
"MS-RequestId": [
|
||||
"b32574ec-fae7-4ea2-915a-0e1ab46e92ca"
|
||||
],
|
||||
"MS-CV": [
|
||||
"UGInjBojP0y0mAqo.0"
|
||||
],
|
||||
"MS-ServerId": [
|
||||
"0000112E"
|
||||
],
|
||||
"Request-Context": [
|
||||
"appId=cid-v1:03ce8ca8-8373-4021-8f25-d5dd45c7b12f"
|
||||
],
|
||||
"Date": [
|
||||
"Fri, 11 Jan 2019 23:18:26 GMT"
|
||||
],
|
||||
"Content-Length": [
|
||||
"585"
|
||||
],
|
||||
"Content-Type": [
|
||||
"application/json; charset=utf-8"
|
||||
]
|
||||
},
|
||||
"StatusCode": "OK"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Management.Automation;
|
||||
using System.Reflection;
|
||||
using Factories;
|
||||
using Network;
|
||||
using PowerShell.Models.Authentication;
|
||||
|
||||
/// <summary>
|
||||
/// Test base for all Partner Center PowerShell commands.
|
||||
/// </summary>
|
||||
public abstract class TestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegating handler used to intercept partner service client operations.
|
||||
/// </summary>
|
||||
private static readonly HttpMockHandler httpMockHandler = new HttpMockHandler(HttpMockHandlerMode.Playback);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TestBase" /> class.
|
||||
/// </summary>
|
||||
static TestBase()
|
||||
{
|
||||
IPartnerCredentials credentials = new TestPartnerCredentials();
|
||||
PartnerSession.Instance.ClientFactory = new MockClientFactory(httpMockHandler, credentials);
|
||||
|
||||
Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
PartnerSession.Instance.AuthenticationFactory = new MockAuthenticationFactory();
|
||||
|
||||
PartnerAccount account = new PartnerAccount
|
||||
{
|
||||
Type = AccountType.User
|
||||
};
|
||||
|
||||
PartnerSession.Instance.Context = new PartnerContext
|
||||
{
|
||||
Account = account,
|
||||
CountryCode = "US",
|
||||
Environment = PartnerEnvironment.PublicEnvironments[EnvironmentName.AzureCloud],
|
||||
Locale = "en-US",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the specified test script.
|
||||
/// </summary>
|
||||
protected Collection<PSObject> RunPowerShellTest(string script)
|
||||
{
|
||||
Collection<PSObject> output;
|
||||
|
||||
if (string.IsNullOrEmpty(script))
|
||||
{
|
||||
throw new ArgumentException(nameof(script));
|
||||
}
|
||||
|
||||
using (PowerShell powershell = PowerShell.Create())
|
||||
{
|
||||
powershell.AddScript("$Error.clear()");
|
||||
powershell.AddScript($"Write-Debug \"Current directory: {AppDomain.CurrentDomain.BaseDirectory}\"");
|
||||
powershell.AddScript($"Write-Debug \"Current executing assembly: {Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)}\"");
|
||||
powershell.AddScript($"cd \"{AppDomain.CurrentDomain.BaseDirectory}\"");
|
||||
|
||||
powershell.AddScript($"Import-Module \"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PartnerCenter.psm1")}\"");
|
||||
powershell.AddScript($"Import-Module \"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ScenarioTests\\Common.ps1")}\"");
|
||||
powershell.AddScript($"Import-Module \"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"ScenarioTests\\{GetType().Name}.ps1")}\"");
|
||||
|
||||
string test = GetType().Name;
|
||||
|
||||
powershell.AddScript("$VerbosePreference='Continue'");
|
||||
powershell.AddScript("$DebugPreference='Continue'");
|
||||
powershell.AddScript("$ErrorActionPreference='Stop'");
|
||||
powershell.AddScript(script);
|
||||
|
||||
try
|
||||
{
|
||||
powershell.Runspace.Events.Subscribers.Clear();
|
||||
output = powershell.Invoke();
|
||||
|
||||
if (powershell.Streams.Error.Count > 0)
|
||||
{
|
||||
throw new RuntimeException(
|
||||
$"Test failed due to a non-empty error stream. First error: {FormatErrorRecord(powershell.Streams.Error[0])}{(powershell.Streams.Error.Count > 0 ? "Check the error stream in the test log for additional errors." : "")}");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
finally
|
||||
{
|
||||
powershell.Streams.Error.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats an <see cref="ErrorRecord"/> to a detailed string for logging.
|
||||
/// </summary>
|
||||
internal static string FormatErrorRecord(ErrorRecord record)
|
||||
{
|
||||
return $"PowerShell Error Record: {record}\nException:{record.Exception}\nDetails:{record.ErrorDetails}\nScript Stack Trace: {record.ScriptStackTrace}\n: Target: {record.TargetObject}\n";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Test credentials used during testing with the partner service.
|
||||
/// </summary>
|
||||
public class TestPartnerCredentials : IPartnerCredentials
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the token needed to authenticate with the partner service.
|
||||
/// </summary>
|
||||
public string PartnerServiceToken => "STUB_TOKEN";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the expiry time in UTC for the token.
|
||||
/// </summary>
|
||||
public DateTimeOffset ExpiresAt => DateTime.UtcNow.AddHours(1);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag indicating whether the partner credentials have expired or not.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if credentials have expired; otherwise <c>false</c>.</returns>
|
||||
public bool IsExpired()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Store.PartnerCenter.PowerShell.UnitTests
|
||||
{
|
||||
using Network;
|
||||
|
||||
/// <summary>
|
||||
/// This class contains the entry point the partner SDK test framework.
|
||||
/// </summary>
|
||||
public static class TestPartnerService
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an object used to access the Partner Center API with the specified credentials.
|
||||
/// </summary>
|
||||
/// <param name="credentials">Credentials to be used when accessing resources.</param>
|
||||
/// <param name="handler">Mock delegating handler used for testing purposes.</param>
|
||||
/// <returns>A configured partner operations object.</returns>
|
||||
public static IPartner CreatePartnerOperations(IPartnerCredentials credentials, HttpMockHandler handler)
|
||||
{
|
||||
return PartnerService.Instance.CreatePartnerOperations(credentials, handler);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче