From ca893289096ab1ab0199247642546e6c2af10d23 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Thu, 16 Sep 2021 16:13:11 -0700 Subject: [PATCH] Intial set-up for llc tests (#23909) * intial set-up for llc tests * don't generate the code * saving work in progress * remove edits to Core * simplify tests * implement LLC method with mock transport * Add in basic model cast functionality, without new Core features --- .../Azure.Core.Experimental.Tests.csproj | 11 ++ .../LowLevelClientModels/Pet.Serialization.cs | 46 ++++++ .../LowLevelClientModels/Pet.cs | 54 +++++++ .../SerializationHelpers.cs | 22 +++ .../tests/LowLevelClient/PetStoreClient.cs | 145 ++++++++++++++++++ .../LowLevelClient/PetStoreClientOptions.cs | 35 +++++ .../tests/LowLevelClientTests.cs | 98 ++++++++++++ 7 files changed, 411 insertions(+) create mode 100644 sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/Pet.Serialization.cs create mode 100644 sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/Pet.cs create mode 100644 sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/SerializationHelpers.cs create mode 100644 sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs create mode 100644 sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClientOptions.cs create mode 100644 sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs diff --git a/sdk/core/Azure.Core.Experimental/tests/Azure.Core.Experimental.Tests.csproj b/sdk/core/Azure.Core.Experimental/tests/Azure.Core.Experimental.Tests.csproj index b3b93480b660e..6b97834af9b43 100644 --- a/sdk/core/Azure.Core.Experimental/tests/Azure.Core.Experimental.Tests.csproj +++ b/sdk/core/Azure.Core.Experimental/tests/Azure.Core.Experimental.Tests.csproj @@ -1,6 +1,7 @@  $(RequiredTargetFrameworks) + true @@ -17,5 +18,15 @@ + + + + + + + + + + diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/Pet.Serialization.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/Pet.Serialization.cs new file mode 100644 index 0000000000000..0f4335399d5ea --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/Pet.Serialization.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable disable + +using System.Text.Json; +using Azure.Core; + +namespace Azure.Core.Experimental.Tests.Models +{ + public partial class Pet : IUtf8JsonSerializable + { + void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) + { + writer.WriteStartObject(); + + writer.WritePropertyName("name"); + writer.WriteStringValue(Name); + + writer.WritePropertyName("species"); + writer.WriteStringValue(Species); + + writer.WriteEndObject(); + } + + internal static Pet DeserializePet(JsonElement element) + { + Optional name = default; + Optional species = default; + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("name")) + { + name = property.Value.GetString(); + continue; + } + if (property.NameEquals("species")) + { + species = property.Value.GetString(); + continue; + } + } + return new Pet(name.Value, species.Value); + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/Pet.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/Pet.cs new file mode 100644 index 0000000000000..e4498142c357a --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/Pet.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable disable + +using System; +using System.Text.Json; + +namespace Azure.Core.Experimental.Tests.Models +{ + /// The Pet output model. + public partial class Pet + { + /// Initializes a new instance of Pet. + internal Pet() + { + } + + /// Initializes a new instance of Pet. + /// + /// + /// + internal Pet(string name, string species) + { + Name = name; + Species = species; + } + + public string Name { get; } + public string Species { get; } + + // Cast from Response to Pet + public static implicit operator Pet(Response response) + { + // [X] TODO: Add in HLC error semantics + // [ ] TODO: Use response.IsError + // [ ] TODO: Use throw new ResponseFailedException(response); + switch (response.Status) + { + case 200: + return DeserializePet(JsonDocument.Parse(response.Content.ToMemory())); + default: + throw new RequestFailedException("Received a non-success status code."); + } + } + + private static Pet DeserializePet(JsonDocument document) + { + return new Pet( + document.RootElement.GetProperty("name").GetString(), + document.RootElement.GetProperty("species").GetString()); + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/SerializationHelpers.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/SerializationHelpers.cs new file mode 100644 index 0000000000000..27c5033cccd43 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/SerializationHelpers.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; +using Azure.Core; + +namespace Azure.Core.Experimental.Tests.Models +{ + internal class SerializationHelpers + { + public delegate void SerializerFunc(ref Utf8JsonWriter writer, T t); + + public static byte[] Serialize(T t, SerializerFunc serializerFunc) + { + var writer = new ArrayBufferWriter(); + var json = new Utf8JsonWriter(writer); + serializerFunc(ref json, t); + json.Flush(); + return writer.WrittenMemory.ToArray(); + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs new file mode 100644 index 0000000000000..fdcbcc68229d7 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable disable + +using System; +using System.Threading.Tasks; +using Azure; +using Azure.Core; +using Azure.Core.Pipeline; + +namespace Azure.Core.Experimental.Tests +{ + /// The PetStore service client. + public partial class PetStoreClient + { + /// The HTTP pipeline for sending and receiving REST requests and responses. + public virtual HttpPipeline Pipeline { get; } + private readonly string[] AuthorizationScopes = { "https://example.azurepetshop.com/.default" }; + private readonly TokenCredential _tokenCredential; + private Uri endpoint; + private readonly string apiVersion; + private readonly ClientDiagnostics _clientDiagnostics; + + /// Initializes a new instance of PetStoreClient for mocking. + protected PetStoreClient() + { + } + + /// Initializes a new instance of PetStoreClient. + /// The workspace development endpoint, for example https://myworkspace.dev.azuresynapse.net. + /// A credential used to authenticate to an Azure Service. + /// The options for configuring the client. + public PetStoreClient(Uri endpoint, TokenCredential credential, PetStoreClientOptions options = null) + { + if (endpoint == null) + { + throw new ArgumentNullException(nameof(endpoint)); + } + if (credential == null) + { + throw new ArgumentNullException(nameof(credential)); + } + + options ??= new PetStoreClientOptions(); + _clientDiagnostics = new ClientDiagnostics(options); + _tokenCredential = credential; + var authPolicy = new BearerTokenAuthenticationPolicy(_tokenCredential, AuthorizationScopes); + Pipeline = HttpPipelineBuilder.Build(options, new HttpPipelinePolicy[] { new LowLevelCallbackPolicy() }, new HttpPipelinePolicy[] { authPolicy }, new ResponseClassifier()); + this.endpoint = endpoint; + apiVersion = options.Version; + } + + /// Get a pet by its Id. + /// Id of pet to return. + /// The request options. +#pragma warning disable AZC0002 + public virtual async Task GetPetAsync(string id, RequestOptions options = null) +#pragma warning restore AZC0002 + { + options ??= new RequestOptions(); + using HttpMessage message = CreateGetPetRequest(id, options); + RequestOptions.Apply(options, message); + using var scope = _clientDiagnostics.CreateScope("PetStoreClient.GetPet"); + scope.Start(); + try + { + await Pipeline.SendAsync(message, options.CancellationToken).ConfigureAwait(false); + if (options.StatusOption == ResponseStatusOption.Default) + { + switch (message.Response.Status) + { + case 200: + return message.Response; + default: + throw await _clientDiagnostics.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); + } + } + else + { + return message.Response; + } + } + catch (Exception e) + { + scope.Failed(e); + throw; + } + } + + /// Get a pet by its Id. + /// Id of pet to return. + /// The request options. +#pragma warning disable AZC0002 + public virtual Response GetPet(string id, RequestOptions options = null) +#pragma warning restore AZC0002 + { + options ??= new RequestOptions(); + using HttpMessage message = CreateGetPetRequest(id, options); + RequestOptions.Apply(options, message); + using var scope = _clientDiagnostics.CreateScope("PetStoreClient.GetPet"); + scope.Start(); + try + { + Pipeline.Send(message, options.CancellationToken); + if (options.StatusOption == ResponseStatusOption.Default) + { + switch (message.Response.Status) + { + case 200: + return message.Response; + default: + throw _clientDiagnostics.CreateRequestFailedException(message.Response); + } + } + else + { + return message.Response; + } + } + catch (Exception e) + { + scope.Failed(e); + throw; + } + } + + /// Create Request for and operations. + /// Id of pet to return. + /// The request options. + private HttpMessage CreateGetPetRequest(string id, RequestOptions options = null) + { + var message = Pipeline.CreateMessage(); + var request = message.Request; + request.Method = RequestMethod.Get; + var uri = new RawRequestUriBuilder(); + uri.Reset(endpoint); + uri.AppendPath("/pets/", false); + uri.AppendPath(id, true); + request.Uri = uri; + request.Headers.Add("Accept", "application/json, text/json"); + return message; + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClientOptions.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClientOptions.cs new file mode 100644 index 0000000000000..f400dd2b82a64 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClientOptions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable disable + +using System; +using Azure.Core; + +namespace Azure.Core.Experimental.Tests +{ + /// Client options for PetStoreClient. + public partial class PetStoreClientOptions : ClientOptions + { + private const ServiceVersion LatestVersion = ServiceVersion.V2020_12_01; + + /// The version of the service to use. + public enum ServiceVersion + { + /// Service version "2020-12-01". + V2020_12_01 = 1, + } + + internal string Version { get; } + + /// Initializes new instance of PetStoreClientOptions. + public PetStoreClientOptions(ServiceVersion version = LatestVersion) + { + Version = version switch + { + ServiceVersion.V2020_12_01 => "2020-12-01", + _ => throw new NotSupportedException() + }; + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs new file mode 100644 index 0000000000000..e557529d67ca0 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Azure.Core.Experimental.Tests; +using Azure.Core.Experimental.Tests.Models; +using Azure.Core.Pipeline; +using Azure.Core.TestFramework; +using NUnit.Framework; + +namespace Azure.Core.Tests +{ + public class LowLevelClientTests : ClientTestBase + { + public LowLevelClientTests(bool isAsync) : base(isAsync) + { + } + + private PetStoreClient client { get; set; } + private readonly Uri _url = new Uri("https://example.azurepetstore.com"); + + public PetStoreClient CreateClient(HttpPipelineTransport transport) + { + var options = new PetStoreClientOptions() + { + Transport = transport + }; + + return new PetStoreClient(_url, new MockCredential(), options); + } + + [Test] + public async Task CanGetResponseFromLlcGetMethodAsync() + { + var mockResponse = new MockResponse(200); + + Pet pet = new Pet("snoopy", "beagle"); + mockResponse.SetContent(SerializationHelpers.Serialize(pet, SerializePet)); + + var mockTransport = new MockTransport(mockResponse); + PetStoreClient client = CreateClient(mockTransport); + + Response response = await client.GetPetAsync("snoopy", new RequestOptions()); + var doc = JsonDocument.Parse(response.Content.ToMemory()); + + Assert.AreEqual(200, response.Status); + Assert.AreEqual("snoopy", doc.RootElement.GetProperty("name").GetString()); + Assert.AreEqual("beagle", doc.RootElement.GetProperty("species").GetString()); + } + + [Test] + public async Task CanGetOutputModelOnSuccessCodeAsync() + { + var mockResponse = new MockResponse(200); + + Pet petResponse = new Pet("snoopy", "beagle"); + mockResponse.SetContent(SerializationHelpers.Serialize(petResponse, SerializePet)); + + var mockTransport = new MockTransport(mockResponse); + PetStoreClient client = CreateClient(mockTransport); + + Pet pet = await client.GetPetAsync("pet1"); + + Assert.AreEqual("snoopy", pet.Name); + Assert.AreEqual("beagle", pet.Species); + } + + [Test] + public void CannotGetOutputModelOnFailureCodeAsync() + { + var mockResponse = new MockResponse(404); + + var mockTransport = new MockTransport(mockResponse); + PetStoreClient client = CreateClient(mockTransport); + + Assert.ThrowsAsync(async () => await client.GetPetAsync("pet1")); + } + + #region Helpers + + private void SerializePet(ref Utf8JsonWriter writer, Pet pet) + { + writer.WriteStartObject(); + + writer.WritePropertyName("name"); + writer.WriteStringValue(pet.Name); + + writer.WritePropertyName("species"); + writer.WriteStringValue(pet.Species); + + writer.WriteEndObject(); + } + + #endregion + } +}