From 9c72d9291d91af9048fcffc3133aa3f1a4920e29 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Thu, 21 Oct 2021 09:06:42 -0700 Subject: [PATCH 1/4] Add tests for grow-up methods for vanilla pageable --- .../Azure.Core.Experimental.Tests.csproj | 1 + .../tests/LowLevelClient/PetStoreClient.cs | 141 ++++++++++++++++-- .../tests/LowLevelClientTests.cs | 19 +++ 3 files changed, 151 insertions(+), 10 deletions(-) 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 7d5cbf62640f..c69a4b498edc 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 @@ -2,6 +2,7 @@ $(RequiredTargetFrameworks) true + $(DefineConstants);EXPERIMENTAL diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs index 402d875b1aea..4c2b2e582ec4 100644 --- a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs @@ -4,7 +4,11 @@ #nullable disable using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; +using Azure.Core; using Azure.Core.Pipeline; namespace Azure.Core.Experimental.Tests @@ -16,7 +20,7 @@ public partial class PetStoreClient public virtual HttpPipeline Pipeline { get; } private readonly string[] AuthorizationScopes = { "https://example.azurepetshop.com/.default" }; private readonly TokenCredential _tokenCredential; - private Uri endpoint; + private Uri _endpoint; private readonly string apiVersion; private readonly ClientDiagnostics _clientDiagnostics; private ResponseClassifier _classifier200; @@ -60,7 +64,7 @@ public PetStoreClient(Uri endpoint, TokenCredential credential, PetStoreClientOp // TODO: When we move the IsError functionality into Core, we'll move the addition of ResponsePropertiesPolicy to the pipeline into HttpPipelineBuilder. Pipeline = HttpPipelineBuilder.Build(options, new HttpPipelinePolicy[] { new LowLevelCallbackPolicy() }, new HttpPipelinePolicy[] { authPolicy, new ResponsePropertiesPolicy(options) }, new ResponseClassifier()); - this.endpoint = endpoint; + _endpoint = endpoint; apiVersion = options.Version; } @@ -152,7 +156,7 @@ private HttpMessage CreateGetPetRequest(string id, RequestOptions options = null var request = message.Request; request.Method = RequestMethod.Get; var uri = new RawRequestUriBuilder(); - uri.Reset(endpoint); + uri.Reset(_endpoint); uri.AppendPath("/pets/", false); uri.AppendPath(id, true); request.Uri = uri; @@ -161,17 +165,134 @@ private HttpMessage CreateGetPetRequest(string id, RequestOptions options = null return message; } - private class ResponseClassifier200 : ResponseClassifier + /// Get a pet by its Id. + /// The request options. + /// + /// Schema for Response Body: + /// { + /// id: number, + /// name: string, + /// species: string + /// } + /// + /// Schema for Response Error: + /// { + /// message: string, + /// code: string + /// } + /// + /// + /// +#pragma warning disable AZC0002 + public virtual AsyncPageable GetPetsAsync(RequestOptions options) +#pragma warning restore AZC0002 + { + return PageableHelpers.CreateAsyncPageable(CreateEnumerableAsync, _clientDiagnostics, "PetStoreClient.GetPets"); + async IAsyncEnumerable> CreateEnumerableAsync(string nextLink, int? pageSizeHint, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + do + { + var message = string.IsNullOrEmpty(nextLink) + ? CreateGetPetsRequest() + : CreateGetPetsNextPageRequest(nextLink); + var page = await LowLevelPageableHelpers.ProcessMessageAsync(Pipeline, message, _clientDiagnostics, options, "value", "nextLink", cancellationToken).ConfigureAwait(false); + nextLink = page.ContinuationToken; + yield return page; + } while (!string.IsNullOrEmpty(nextLink)); + } + } + + /// Get a pet by its Id. + /// The request options. + /// + /// Schema for Response Body: + /// { + /// id: number, + /// name: string, + /// species: string + /// } + /// + /// Schema for Response Error: + /// { + /// message: string, + /// code: string + /// } + /// + /// + /// +#pragma warning disable AZC0002 + public virtual Pageable GetPets(RequestOptions options) +#pragma warning restore AZC0002 { + return PageableHelpers.CreatePageable(CreateEnumerable, _clientDiagnostics, "PetStoreClient.GetPets"); + IEnumerable> CreateEnumerable(string nextLink, int? pageSizeHint) + { + do + { + var message = string.IsNullOrEmpty(nextLink) + ? CreateGetPetsRequest() + : CreateGetPetsNextPageRequest(nextLink); + var page = LowLevelPageableHelpers.ProcessMessage(Pipeline, message, _clientDiagnostics, options, "value", "nextLink"); + nextLink = page.ContinuationToken; + yield return page; + } while (!string.IsNullOrEmpty(nextLink)); + } + } + + internal HttpMessage CreateGetPetRequest(long id) + { + 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"); + message.ResponseClassifier = ResponseClassifier200.Instance; + return message; + } + + internal HttpMessage CreateGetPetsRequest() + { + var message = Pipeline.CreateMessage(); + var request = message.Request; + request.Method = RequestMethod.Get; + var uri = new RawRequestUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/pets", false); + request.Uri = uri; + request.Headers.Add("Accept", "application/json, text/json"); + message.ResponseClassifier = ResponseClassifier200.Instance; + return message; + } + + internal HttpMessage CreateGetPetsNextPageRequest(string nextLink) + { + var message = Pipeline.CreateMessage(); + var request = message.Request; + request.Method = RequestMethod.Get; + var uri = new RawRequestUriBuilder(); + uri.Reset(_endpoint); + uri.AppendRawNextLink(nextLink, false); + request.Uri = uri; + request.Headers.Add("Accept", "application/json, text/json"); + message.ResponseClassifier = ResponseClassifier200.Instance; + return message; + } + + private sealed class ResponseClassifier200 : ResponseClassifier + { + private static ResponseClassifier _instance; + public static ResponseClassifier Instance => _instance ??= new ResponseClassifier200(); public override bool IsErrorResponse(HttpMessage message) { - switch (message.Response.Status) + return message.Response.Status switch { - case 200: - return false; - default: - return true; - } + 200 => false, + _ => true + }; } } } diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs index ca97804c03b7..c7dbb352b55b 100644 --- a/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs @@ -211,6 +211,25 @@ public void ThrowOnErrorThrowsOnError() }); } + [Test] + public async Task CanGetPageableResponseFromLlcGetMethodAsync() + { + 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()); + } + #region Helpers private void SerializePet(ref Utf8JsonWriter writer, Pet pet) { From 2bf72516e6dfbbd5ac68d90c433973ae402bd5c8 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Thu, 21 Oct 2021 11:46:37 -0700 Subject: [PATCH 2/4] make test pass --- .../tests/LowLevelClient/PetStoreClient.cs | 36 ------------------ .../tests/LowLevelClientTests.cs | 38 +++++++++++++------ 2 files changed, 27 insertions(+), 47 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs index 4c2b2e582ec4..faf162b1fc9a 100644 --- a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs @@ -165,24 +165,6 @@ private HttpMessage CreateGetPetRequest(string id, RequestOptions options = null return message; } - /// Get a pet by its Id. - /// The request options. - /// - /// Schema for Response Body: - /// { - /// id: number, - /// name: string, - /// species: string - /// } - /// - /// Schema for Response Error: - /// { - /// message: string, - /// code: string - /// } - /// - /// - /// #pragma warning disable AZC0002 public virtual AsyncPageable GetPetsAsync(RequestOptions options) #pragma warning restore AZC0002 @@ -202,24 +184,6 @@ async IAsyncEnumerable> CreateEnumerableAsync(string nextLink, } } - /// Get a pet by its Id. - /// The request options. - /// - /// Schema for Response Body: - /// { - /// id: number, - /// name: string, - /// species: string - /// } - /// - /// Schema for Response Error: - /// { - /// message: string, - /// code: string - /// } - /// - /// - /// #pragma warning disable AZC0002 public virtual Pageable GetPets(RequestOptions options) #pragma warning restore AZC0002 diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs index c7dbb352b55b..2ffe4cfddc76 100644 --- a/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Text.Json; using System.Threading.Tasks; using Azure.Core.Experimental.Tests; @@ -214,20 +215,35 @@ public void ThrowOnErrorThrowsOnError() [Test] public async Task CanGetPageableResponseFromLlcGetMethodAsync() { - var mockResponse = new MockResponse(200); - - Pet pet = new Pet("snoopy", "beagle"); - mockResponse.SetContent(SerializationHelpers.Serialize(pet, SerializePet)); - - var mockTransport = new MockTransport(mockResponse); + var page1Response = new MockResponse(200); + page1Response.SetContent( + @"{ + ""value"": [ + { ""name"": ""snoopy"", ""species"": ""beagle"" }, + { ""name"": ""lassie"", ""species"": ""collie"" } + ], + ""nextLink"": ""https://example.petstore.com"" + }"); + + var page2Response = new MockResponse(200); + page2Response.SetContent( + @"{ + ""value"": [ + { ""name"": ""rintintin"", ""species"": ""german shepherd"" } + ] + }"); + + var mockTransport = new MockTransport(page1Response, page2Response); PetStoreClient client = CreateClient(mockTransport); - Response response = await client.GetPetAsync("snoopy", new RequestOptions()); - var doc = JsonDocument.Parse(response.Content.ToMemory()); + AsyncPageable pets = client.GetPetsAsync(new()); + int count = 0; + await foreach (var pet in pets) + { + count++; + } - Assert.AreEqual(200, response.Status); - Assert.AreEqual("snoopy", doc.RootElement.GetProperty("name").GetString()); - Assert.AreEqual("beagle", doc.RootElement.GetProperty("species").GetString()); + Assert.AreEqual(3, count); } #region Helpers From 5892fc3c26e58e53b91c9332579a2f0aefec5b82 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Mon, 25 Oct 2021 09:01:29 -0700 Subject: [PATCH 3/4] work in progress --- .../LowLevelClientModels/Pet.cs | 19 +++++ .../PetStoreClient_GrowUpHelpers.cs | 72 +++++++++++++++++++ .../tests/LowLevelClientTests.cs | 36 +++++++++- 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient_GrowUpHelpers.cs diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/Pet.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/Pet.cs index 5ad8e8ae2e86..0cd746403797 100644 --- a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/Pet.cs +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/LowLevelClientModels/Pet.cs @@ -49,6 +49,25 @@ public static implicit operator Pet(Response response) return DeserializePet(JsonDocument.Parse(response.Content.ToMemory())); } + // Cast from BinaryData to Pet + public static implicit operator Pet(BinaryData binaryData) + { + //// [X] TODO: Add in HLC error semantics + //// [X] TODO: Use response.IsError + //// [X] TODO: Use throw new ResponseFailedException(response); + + //// TODO: When we move this functionality out of Experimental into Core, it will be replaced by + //// > if (response.IsError) + //if (response.IsError()) + //{ + // // TODO: When we move this functionality out of Experimental into Core, it will be replaced by + // // > throw new RequestFailedException(response); + // throw response.CreateRequestFailedException(); + //} + + return DeserializePet(JsonDocument.Parse(binaryData.ToMemory())); + } + private static Pet DeserializePet(JsonDocument document) { return new Pet( diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient_GrowUpHelpers.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient_GrowUpHelpers.cs new file mode 100644 index 000000000000..9e57b9bf9ba7 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient_GrowUpHelpers.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; +using Azure.Core.Experimental.Tests.Models; +using Azure.Core.Pipeline; +using Azure; +using System.Linq; + +namespace Azure.Core.Experimental.Tests +{ + /// The PetStore service client. + public partial class PetStoreClient + { + public static Pet GetPetFromBinaryData(BinaryData data) + { + return data; + } + + // public virtual AsyncPageable GetPetsAsync() + //#pragma warning restore AZC0002 + // { + // // TODO: handle need for continuation token and + // AsyncPageable pets = GetPetsAsync(new()); + // pets.AsPages() + + // //return PageableHelpers.create(i => { pets.AsPages}, _clientDiagnostics, "PetStoreClient.GetPets"); + // //async IAsyncEnumerable> CreateEnumerableAsync(string nextLink, int? pageSizeHint, [EnumeratorCancellation] CancellationToken cancellationToken = default) + // //{ + // // do + // // { + // // var message = string.IsNullOrEmpty(nextLink) + // // ? CreateGetPetsRequest() + // // : CreateGetPetsNextPageRequest(nextLink); + // // var page = await LowLevelPageableHelpers.ProcessMessageAsync(Pipeline, message, _clientDiagnostics, options, "value", "nextLink", cancellationToken).ConfigureAwait(false); + // // nextLink = page.ContinuationToken; + // // yield return page; + // // } while (!string.IsNullOrEmpty(nextLink)); + // //} + // } + + // TODO: extend to generic + private class PetPage : Page + { + private Page _page; + + public PetPage(Page page) + { + _page = page; + } + + //public override IReadOnlyList Values => ()_page.Values.Select(data => GetPetFromBinaryData(data)).ToList().AsReadOnly(); + //public override IReadOnlyList Values => (IReadOnlyList)_page.Values.Select(data => GetPetFromBinaryData(data));//.ToList().AsReadOnly(); + public override IReadOnlyList Values => (IReadOnlyList)_page.Values.Select(data => + { + Pet pet = (Pet)data; + return pet; + });//.ToList().AsReadOnly(); + + public override string ContinuationToken => _page.ContinuationToken; + + public override Response GetRawResponse() => _page.GetRawResponse(); + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs index 2ffe4cfddc76..cf773f6ef6f2 100644 --- a/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs @@ -213,7 +213,7 @@ public void ThrowOnErrorThrowsOnError() } [Test] - public async Task CanGetPageableResponseFromLlcGetMethodAsync() + public async Task CanGetPageableResponse_LlcMethodAsync() { var page1Response = new MockResponse(200); page1Response.SetContent( @@ -246,6 +246,40 @@ public async Task CanGetPageableResponseFromLlcGetMethodAsync() Assert.AreEqual(3, count); } + //[Test] + //public async Task CanGetPageableResponse_GrowUpHelperMethodAsync() + //{ + // var page1Response = new MockResponse(200); + // page1Response.SetContent( + // @"{ + // ""value"": [ + // { ""name"": ""snoopy"", ""species"": ""beagle"" }, + // { ""name"": ""lassie"", ""species"": ""collie"" } + // ], + // ""nextLink"": ""https://example.petstore.com"" + // }"); + + // var page2Response = new MockResponse(200); + // page2Response.SetContent( + // @"{ + // ""value"": [ + // { ""name"": ""rintintin"", ""species"": ""german shepherd"" } + // ] + // }"); + + // var mockTransport = new MockTransport(page1Response, page2Response); + // PetStoreClient client = CreateClient(mockTransport); + + // AsyncPageable pets = client.GetPetsAsync(); + // int count = 0; + // await foreach (var pet in pets) + // { + // count++; + // } + + // Assert.AreEqual(3, count); + //} + #region Helpers private void SerializePet(ref Utf8JsonWriter writer, Pet pet) { From dd71c5fb7390f09163165f5be739801570432820 Mon Sep 17 00:00:00 2001 From: Anne Thompson Date: Tue, 2 Nov 2021 10:40:02 -0700 Subject: [PATCH 4/4] updates --- .../tests/LowLevelClient/PetStoreClient.cs | 8 ++++---- .../tests/LowLevelClient/PetStoreClient_GrowUpHelpers.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs index 731477f1ee73..58b415179f49 100644 --- a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient.cs @@ -164,7 +164,7 @@ private HttpMessage CreateGetPetRequest(string id, RequestContext context = null } #pragma warning disable AZC0002 - public virtual AsyncPageable GetPetsAsync(RequestOptions options) + public virtual AsyncPageable GetPetsAsync(RequestContext context = null) #pragma warning restore AZC0002 { return PageableHelpers.CreateAsyncPageable(CreateEnumerableAsync, _clientDiagnostics, "PetStoreClient.GetPets"); @@ -175,7 +175,7 @@ async IAsyncEnumerable> CreateEnumerableAsync(string nextLink, var message = string.IsNullOrEmpty(nextLink) ? CreateGetPetsRequest() : CreateGetPetsNextPageRequest(nextLink); - var page = await LowLevelPageableHelpers.ProcessMessageAsync(Pipeline, message, _clientDiagnostics, options, "value", "nextLink", cancellationToken).ConfigureAwait(false); + var page = await LowLevelPageableHelpers.ProcessMessageAsync(Pipeline, message, _clientDiagnostics, context, "value", "nextLink", cancellationToken).ConfigureAwait(false); nextLink = page.ContinuationToken; yield return page; } while (!string.IsNullOrEmpty(nextLink)); @@ -183,7 +183,7 @@ async IAsyncEnumerable> CreateEnumerableAsync(string nextLink, } #pragma warning disable AZC0002 - public virtual Pageable GetPets(RequestOptions options) + public virtual Pageable GetPets(RequestContext context = null) #pragma warning restore AZC0002 { return PageableHelpers.CreatePageable(CreateEnumerable, _clientDiagnostics, "PetStoreClient.GetPets"); @@ -194,7 +194,7 @@ IEnumerable> CreateEnumerable(string nextLink, int? pageSizeHin var message = string.IsNullOrEmpty(nextLink) ? CreateGetPetsRequest() : CreateGetPetsNextPageRequest(nextLink); - var page = LowLevelPageableHelpers.ProcessMessage(Pipeline, message, _clientDiagnostics, options, "value", "nextLink"); + var page = LowLevelPageableHelpers.ProcessMessage(Pipeline, message, _clientDiagnostics, context, "value", "nextLink"); nextLink = page.ContinuationToken; yield return page; } while (!string.IsNullOrEmpty(nextLink)); diff --git a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient_GrowUpHelpers.cs b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient_GrowUpHelpers.cs index 9e57b9bf9ba7..5285b9a4af2b 100644 --- a/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient_GrowUpHelpers.cs +++ b/sdk/core/Azure.Core.Experimental/tests/LowLevelClient/PetStoreClient_GrowUpHelpers.cs @@ -27,7 +27,7 @@ public static Pet GetPetFromBinaryData(BinaryData data) // public virtual AsyncPageable GetPetsAsync() //#pragma warning restore AZC0002 // { - // // TODO: handle need for continuation token and + // // TODO: handle need for continuation token and // AsyncPageable pets = GetPetsAsync(new()); // pets.AsPages() @@ -60,9 +60,9 @@ public PetPage(Page page) //public override IReadOnlyList Values => (IReadOnlyList)_page.Values.Select(data => GetPetFromBinaryData(data));//.ToList().AsReadOnly(); public override IReadOnlyList Values => (IReadOnlyList)_page.Values.Select(data => { - Pet pet = (Pet)data; - return pet; - });//.ToList().AsReadOnly(); + // Pet pet = (Pet)data; // does not compile + return GetPetFromBinaryData(data); + }); public override string ContinuationToken => _page.ContinuationToken;