From d81d8acec5b564a6e02670e965146c1b38f8377b Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Thu, 14 Sep 2023 15:07:57 +0200 Subject: [PATCH 01/24] Wrapping up interceptors, fixing typos, removing Moq --- .github/workflows/pull-request.yml | 13 +- Directory.Packages.props | 37 +++ .../RestSharp.Benchmarks.csproj | 21 +- gen/SourceGenerator/ImmutableGenerator.cs | 16 +- global.json | 7 - props/Common.props | 2 +- src/Directory.Build.props | 9 +- .../RestSharp.Serializers.CsvHelper.csproj | 2 +- ...estSharp.Serializers.NewtonsoftJson.csproj | 2 +- .../Extensions/GenerateImmutableAttribute.cs | 5 +- src/RestSharp/Interceptors/Interceptor.cs | 49 ++-- .../Options/ReadOnlyRestClientOptions.cs | 32 +++ src/RestSharp/Options/RestClientOptions.cs | 5 + src/RestSharp/Parameters/DefaultParameters.cs | 32 ++- src/RestSharp/Request/RequestContent.cs | 2 +- src/RestSharp/Request/RestRequest.cs | 9 + src/RestSharp/RestClient.Async.cs | 83 ++++--- src/RestSharp/RestClient.Extensions.cs | 20 +- src/RestSharp/RestClient.cs | 22 +- src/RestSharp/RestSharp.csproj | 36 +-- src/RestSharp/Serializers/RestSerializers.cs | 11 +- test/Directory.Build.props | 18 +- .../RestSharp.InteractiveTests.csproj | 2 +- .../Interceptor/InterceptorTests.cs | 231 ++++++++++-------- .../Interceptor/TestInterceptor.cs | 45 ++++ .../RestSharp.Tests.Integrated.csproj | 25 +- .../NewtonsoftJson/IntegratedSimpleTests.cs | 4 +- test/RestSharp.Tests/RestSharp.Tests.csproj | 2 +- 28 files changed, 477 insertions(+), 265 deletions(-) create mode 100644 Directory.Packages.props delete mode 100644 global.json create mode 100644 src/RestSharp/Options/ReadOnlyRestClientOptions.cs create mode 100644 test/RestSharp.Tests.Integrated/Interceptor/TestInterceptor.cs diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index c566a4cc8..7e1b03b78 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -20,8 +20,17 @@ jobs: dotnet-version: '7.0' - name: Run tests - run: dotnet test -c Release - + run: dotnet test + - + name: Publish test results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: | + test-results/**/*.xml + test-results/**/*.trx + test-results/**/*.json + docs: runs-on: ubuntu-latest diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 000000000..e8fecbe13 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,37 @@ + + + true + + + [6.0.22,7) + + + 7.0.11 + + + 8.0.0-rc.1.23421.29 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj b/benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj index e1da87b9a..f60295a8f 100644 --- a/benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj +++ b/benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj @@ -1,24 +1,25 @@ Exe - net6.0 + net7.0 false - 10 + preview enable + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'RestSharp.sln')) - - - + + + - - + + - - - + + + diff --git a/gen/SourceGenerator/ImmutableGenerator.cs b/gen/SourceGenerator/ImmutableGenerator.cs index 367560d68..24adff24d 100644 --- a/gen/SourceGenerator/ImmutableGenerator.cs +++ b/gen/SourceGenerator/ImmutableGenerator.cs @@ -58,17 +58,22 @@ static string GenerateImmutableClass(TypeDeclarationSyntax mutableClass, Compila var mutableProperties = props .Select(prop => $" {prop.Identifier.Text} = {argName}.{prop.Identifier.Text};"); - var constructor = $@" public ReadOnly{className}({className} {argName}) {{ -{string.Join("\n", mutableProperties)} - }}"; + var constructor = $$""" + public ReadOnly{{className}}({{className}} {{argName}}) { + {{string.Join("\n", mutableProperties)}} + CopyAdditionalProperties({{argName}}); + } + """; const string template = @"{Usings} namespace {Namespace}; -public class ReadOnly{ClassName} { +public partial class ReadOnly{ClassName} { {Constructor} + partial void CopyAdditionalProperties({ClassName} {ArgName}); + {Properties} }"; @@ -77,6 +82,7 @@ public class ReadOnly{ClassName} { .Replace("{Namespace}", namespaceName) .Replace("{ClassName}", className) .Replace("{Constructor}", constructor) + .Replace("{ArgName}", argName) .Replace("{Properties}", string.Join("\n", properties)); return code; @@ -86,7 +92,7 @@ IEnumerable GetDefinitions(SyntaxKind kind) .OfType() .Where( prop => - prop.AccessorList!.Accessors.Any(accessor => accessor.Keyword.IsKind(kind)) + prop.AccessorList!.Accessors.Any(accessor => accessor.Keyword.IsKind(kind)) && prop.AttributeLists.All(list => list.Attributes.All(attr => attr.Name.ToString() != "Exclude")) ); } } diff --git a/global.json b/global.json deleted file mode 100644 index 36e1a9e95..000000000 --- a/global.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "sdk": { - "version": "7.0.0", - "rollForward": "latestMajor", - "allowPrerelease": false - } -} \ No newline at end of file diff --git a/props/Common.props b/props/Common.props index 26bf7321a..f0ee2b686 100644 --- a/props/Common.props +++ b/props/Common.props @@ -3,7 +3,7 @@ $([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory)).Parent.FullName) $(RepoRoot)\RestSharp.snk true - 10 + preview enable enable diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 69705872a..cdc900fad 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,7 +1,7 @@ - netstandard2.0;net471;net6.0;net7.0 + netstandard2.0;net471;net6.0;net7.0;net8.0 restsharp.png Apache-2.0 https://restsharp.dev @@ -15,13 +15,12 @@ snupkg true $(NoWarn);1591 - 11 - - - + + + diff --git a/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj b/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj index 5fff5b301..fcb03ecfe 100644 --- a/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj +++ b/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj @@ -1,6 +1,6 @@  - + diff --git a/src/RestSharp.Serializers.NewtonsoftJson/RestSharp.Serializers.NewtonsoftJson.csproj b/src/RestSharp.Serializers.NewtonsoftJson/RestSharp.Serializers.NewtonsoftJson.csproj index bdc5a5c18..66eb06cd0 100644 --- a/src/RestSharp.Serializers.NewtonsoftJson/RestSharp.Serializers.NewtonsoftJson.csproj +++ b/src/RestSharp.Serializers.NewtonsoftJson/RestSharp.Serializers.NewtonsoftJson.csproj @@ -1,6 +1,6 @@ - + diff --git a/src/RestSharp/Extensions/GenerateImmutableAttribute.cs b/src/RestSharp/Extensions/GenerateImmutableAttribute.cs index fcd8c546d..c4fe51817 100644 --- a/src/RestSharp/Extensions/GenerateImmutableAttribute.cs +++ b/src/RestSharp/Extensions/GenerateImmutableAttribute.cs @@ -16,4 +16,7 @@ namespace RestSharp.Extensions; [AttributeUsage(AttributeTargets.Class)] -public class GenerateImmutableAttribute : Attribute { } +class GenerateImmutableAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.Property)] +class Exclude : Attribute { } diff --git a/src/RestSharp/Interceptors/Interceptor.cs b/src/RestSharp/Interceptors/Interceptor.cs index 2d0c016be..8d138eadc 100644 --- a/src/RestSharp/Interceptors/Interceptor.cs +++ b/src/RestSharp/Interceptors/Interceptor.cs @@ -13,45 +13,54 @@ // limitations under the License. // -namespace RestSharp.Interceptors; +namespace RestSharp.Interceptors; /// /// Base Interceptor /// public abstract class Interceptor { + static readonly ValueTask Completed = +#if NET + ValueTask.CompletedTask; +#else + new (); +#endif /// - /// Intercepts the request before serialization + /// Intercepts the request before composing the request message /// - /// RestRequest before serialization + /// RestRequest before composing the request message + /// Cancellation token /// Value Tags - public virtual ValueTask InterceptBeforeSerialization(RestRequest request) { - return new(); - } + public virtual ValueTask BeforeRequest(RestRequest request, CancellationToken cancellationToken) => Completed; /// /// Intercepts the request before being sent /// - /// HttpRequestMessage before being sent + /// HttpRequestMessage before being sent + /// Cancellation token /// Value Tags - public virtual ValueTask InterceptBeforeRequest(HttpRequestMessage req) { - return new(); - } + public virtual ValueTask BeforeHttpRequest(HttpRequestMessage requestMessage, CancellationToken cancellationToken) => Completed; /// /// Intercepts the request before being sent /// - /// HttpResponseMessage as received from Server + /// HttpResponseMessage as received from the remote server + /// Cancellation token /// Value Tags - public virtual ValueTask InterceptAfterRequest(HttpResponseMessage responseMessage) { - return new(); - } + public virtual ValueTask AfterHttpRequest(HttpResponseMessage responseMessage, CancellationToken cancellationToken) => Completed; /// - /// Intercepts the request before deserialization + /// Intercepts the request after it's created from HttpResponseMessage /// - /// HttpResponseMessage as received from Server + /// HttpResponseMessage as received from the remote server + /// Cancellation token /// Value Tags - public virtual ValueTask InterceptBeforeDeserialize(RestResponse response) { - return new(); - } -} \ No newline at end of file + public virtual ValueTask AfterRequest(RestResponse response, CancellationToken cancellationToken) => Completed; + + /// + /// Intercepts the request before deserialization, won't be called if using non-generic ExecuteAsync + /// + /// HttpResponseMessage as received from the remote server + /// Cancellation token + public virtual ValueTask BeforeDeserialization(RestResponse response, CancellationToken cancellationToken) => Completed; +} diff --git a/src/RestSharp/Options/ReadOnlyRestClientOptions.cs b/src/RestSharp/Options/ReadOnlyRestClientOptions.cs new file mode 100644 index 000000000..abe93af0d --- /dev/null +++ b/src/RestSharp/Options/ReadOnlyRestClientOptions.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using RestSharp.Interceptors; + +namespace RestSharp; + +public partial class ReadOnlyRestClientOptions { + public IReadOnlyCollection? Interceptors { get; private set; } + + // partial void CopyAdditionalProperties(RestClientOptions inner); + partial void CopyAdditionalProperties(RestClientOptions inner) => Interceptors = GetInterceptors(inner); + + static IReadOnlyCollection? GetInterceptors(RestClientOptions? options) { + if (options == null || options.Interceptors.Count == 0) return null; + + var interceptors = new List(options.Interceptors); + return interceptors.AsReadOnly(); + } +} diff --git a/src/RestSharp/Options/RestClientOptions.cs b/src/RestSharp/Options/RestClientOptions.cs index 95db15020..9b28c66ad 100644 --- a/src/RestSharp/Options/RestClientOptions.cs +++ b/src/RestSharp/Options/RestClientOptions.cs @@ -65,6 +65,10 @@ public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(ba /// public IAuthenticator? Authenticator { get; set; } + /// + /// List of interceptors that will be executed before the request is sent + /// + [Exclude] public List Interceptors { get; set; } = new(); /// @@ -114,6 +118,7 @@ public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(ba #if NET [UnsupportedOSPlatform("browser")] #endif + [Exclude] public X509CertificateCollection? ClientCertificates { get; set; } /// diff --git a/src/RestSharp/Parameters/DefaultParameters.cs b/src/RestSharp/Parameters/DefaultParameters.cs index 6d3dc89a7..019cf79a2 100644 --- a/src/RestSharp/Parameters/DefaultParameters.cs +++ b/src/RestSharp/Parameters/DefaultParameters.cs @@ -13,13 +13,13 @@ // limitations under the License. // +using System.Runtime.CompilerServices; + namespace RestSharp; public sealed class DefaultParameters : ParametersCollection { readonly ReadOnlyRestClientOptions _options; - readonly object _lock = new(); - public DefaultParameters(ReadOnlyRestClientOptions options) => _options = options; /// @@ -29,22 +29,21 @@ public sealed class DefaultParameters : ParametersCollection { /// /// /// + [MethodImpl(MethodImplOptions.Synchronized)] public DefaultParameters AddParameter(Parameter parameter) { - lock (_lock) { - if (parameter.Type == ParameterType.RequestBody) - throw new NotSupportedException( - "Cannot set request body using default parameters. Use Request.AddBody() instead." - ); - - if (!_options.AllowMultipleDefaultParametersWithSameName && - !MultiParameterTypes.Contains(parameter.Type) && - this.Any(x => x.Name == parameter.Name)) { - throw new ArgumentException("A default parameters with the same name has already been added", nameof(parameter)); - } + if (parameter.Type == ParameterType.RequestBody) + throw new NotSupportedException( + "Cannot set request body using default parameters. Use Request.AddBody() instead." + ); - Parameters.Add(parameter); + if (!_options.AllowMultipleDefaultParametersWithSameName && + !MultiParameterTypes.Contains(parameter.Type) && + this.Any(x => x.Name == parameter.Name)) { + throw new ArgumentException("A default parameters with the same name has already been added", nameof(parameter)); } + Parameters.Add(parameter); + return this; } @@ -55,10 +54,9 @@ public DefaultParameters AddParameter(Parameter parameter) { /// Parameter type /// [PublicAPI] + [MethodImpl(MethodImplOptions.Synchronized)] public DefaultParameters RemoveParameter(string name, ParameterType type) { - lock (_lock) { - Parameters.RemoveAll(x => x.Name == name && x.Type == type); - } + Parameters.RemoveAll(x => x.Name == name && x.Type == type); return this; } diff --git a/src/RestSharp/Request/RequestContent.cs b/src/RestSharp/Request/RequestContent.cs index cfc7995ca..38d1ac90e 100644 --- a/src/RestSharp/Request/RequestContent.cs +++ b/src/RestSharp/Request/RequestContent.cs @@ -83,7 +83,7 @@ StreamContent ToStreamContent(FileParameter fileParameter) { var dispositionHeader = fileParameter.Options.DisableFilenameEncoding ? ContentDispositionHeaderValue.Parse($"form-data; name=\"{fileParameter.Name}\"; filename=\"{fileParameter.FileName}\"") : new ContentDispositionHeaderValue("form-data") { Name = $"\"{fileParameter.Name}\"", FileName = $"\"{fileParameter.FileName}\"" }; - if (!fileParameter.Options.DisableFileNameStar) dispositionHeader.FileNameStar = fileParameter.FileName; + if (!fileParameter.Options.DisableFilenameStar) dispositionHeader.FileNameStar = fileParameter.FileName; streamContent.Headers.ContentDisposition = dispositionHeader; return streamContent; diff --git a/src/RestSharp/Request/RestRequest.cs b/src/RestSharp/Request/RestRequest.cs index 9748fa9cf..283a440e7 100644 --- a/src/RestSharp/Request/RestRequest.cs +++ b/src/RestSharp/Request/RestRequest.cs @@ -16,6 +16,7 @@ using System.Net.Http.Headers; using RestSharp.Authenticators; using RestSharp.Extensions; +using RestSharp.Interceptors; // ReSharper disable ReplaceSubstringWithRangeIndexer // ReSharper disable UnusedAutoPropertyAccessor.Global @@ -167,16 +168,19 @@ public RestRequest(Uri resource, Method method = Method.Get) /// /// When supplied, the function will be called before calling the deserializer /// + [Obsolete("Use Interceptors instead")] public Action? OnBeforeDeserialization { get; set; } /// /// When supplied, the function will be called before making a request /// + [Obsolete("Use Interceptors instead")] public Func? OnBeforeRequest { get; set; } /// /// When supplied, the function will be called after the request is complete /// + [Obsolete("Use Interceptors instead")] public Func? OnAfterRequest { get; set; } internal void IncreaseNumAttempts() => Attempts++; @@ -226,6 +230,11 @@ public Func? AdvancedResponseWri _advancedResponseHandler = value; } } + + /// + /// Request-level interceptors. Will be combined with client-level interceptors if set. + /// + public List? Interceptors { get; set; } /// /// Adds a parameter object to the request parameters diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs index 9c99b9fd7..ec93bd803 100644 --- a/src/RestSharp/RestClient.Async.cs +++ b/src/RestSharp/RestClient.Async.cs @@ -14,6 +14,7 @@ using System.Net; using RestSharp.Extensions; +using RestSharp.Interceptors; namespace RestSharp; @@ -33,6 +34,7 @@ public async Task ExecuteAsync(RestRequest request, CancellationTo ) .ConfigureAwait(false) : GetErrorResponse(request, internalResponse.Exception, internalResponse.TimeoutToken); + await OnAfterRequest(response, cancellationToken).ConfigureAwait(false); return Options.ThrowOnAnyError ? response.ThrowIfError() : response; } @@ -69,6 +71,21 @@ static RestResponse GetErrorResponse(RestRequest request, Exception exception, C bool TimedOut() => timeoutToken.IsCancellationRequested || exception.Message.Contains("HttpClient.Timeout"); } + void CombineInterceptors(RestRequest request) { + if (request.Interceptors == null) { + if (Options.Interceptors == null) { + return; + } + + request.Interceptors = Options.Interceptors.ToList(); + return; + } + + if (Options.Interceptors != null) { + request.Interceptors.AddRange(Options.Interceptors); + } + } + async Task ExecuteRequestAsync(RestRequest request, CancellationToken cancellationToken) { Ensure.NotNull(request, nameof(request)); @@ -77,7 +94,8 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo throw new ObjectDisposedException(nameof(RestClient)); } - await OnBeforeSerialization(request).ConfigureAwait(false); + CombineInterceptors(request); + await OnBeforeRequest(request, cancellationToken).ConfigureAwait(false); request.ValidateParameters(); var authenticator = request.Authenticator ?? Options.Authenticator; @@ -90,7 +108,8 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo var httpMethod = AsHttpMethod(request.Method); var url = this.BuildUri(request); - using var message = new HttpRequestMessage(httpMethod, url) { Content = requestContent.BuildContent() }; + using var message = new HttpRequestMessage(httpMethod, url); + message.Content = requestContent.BuildContent(); message.Headers.Host = Options.BaseHost; message.Headers.CacheControl = request.CachePolicy ?? Options.CachePolicy; @@ -99,11 +118,10 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo var ct = cts.Token; - HttpResponseMessage? responseMessage; // Make sure we have a cookie container if not provided in the request - CookieContainer cookieContainer = request.CookieContainer ??= new CookieContainer(); - + var cookieContainer = request.CookieContainer ??= new CookieContainer(); + var headers = new RequestHeaders() .AddHeaders(request.Parameters) .AddHeaders(DefaultParameters) @@ -113,10 +131,11 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo message.AddHeaders(headers); if (request.OnBeforeRequest != null) await request.OnBeforeRequest(message).ConfigureAwait(false); - await OnBeforeRequest(message).ConfigureAwait(false); - + await OnBeforeHttpRequest(request, message, cancellationToken).ConfigureAwait(false); + try { responseMessage = await HttpClient.SendAsync(message, request.CompletionOption, ct).ConfigureAwait(false); + // Parse all the cookies from the response and update the cookie jar with cookies if (responseMessage.Headers.TryGetValues(KnownHeaders.SetCookie, out var cookiesHeader)) { // ReSharper disable once PossibleMultipleEnumeration @@ -128,37 +147,41 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo catch (Exception ex) { return new HttpResponse(null, url, null, ex, timeoutCts.Token); } + if (request.OnAfterRequest != null) await request.OnAfterRequest(responseMessage).ConfigureAwait(false); - await OnAfterRequest(responseMessage).ConfigureAwait(false); + await OnAfterHttpRequest(request, responseMessage, cancellationToken).ConfigureAwait(false); return new HttpResponse(responseMessage, url, cookieContainer, null, timeoutCts.Token); - } - /// - /// Will be called before the Request becomes Serialized - /// - /// RestRequest before it will be serialized - async Task OnBeforeSerialization(RestRequest request) { - foreach (var interceptor in Options.Interceptors) { - await interceptor.InterceptBeforeSerialization(request); //.ThrowExceptionIfAvailable(); + static async ValueTask OnBeforeRequest(RestRequest request, CancellationToken cancellationToken) { + if (request.Interceptors == null) return; + + foreach (var interceptor in request.Interceptors) { + await interceptor.BeforeRequest(request, cancellationToken).ConfigureAwait(false); + } + } + + static async ValueTask OnBeforeHttpRequest(RestRequest request, HttpRequestMessage requestMessage, CancellationToken cancellationToken) { + if (request.Interceptors == null) return; + + foreach (var interceptor in request.Interceptors) { + await interceptor.BeforeHttpRequest(requestMessage, cancellationToken).ConfigureAwait(false); } } - /// - /// Will be called before the Request will be sent - /// - /// HttpRequestMessage ready to be sent - async Task OnBeforeRequest(HttpRequestMessage requestMessage) { - foreach (var interceptor in Options.Interceptors) { - await interceptor.InterceptBeforeRequest(requestMessage); + + static async ValueTask OnAfterHttpRequest(RestRequest request, HttpResponseMessage responseMessage, CancellationToken cancellationToken) { + if (request.Interceptors == null) return; + + foreach (var interceptor in request.Interceptors) { + await interceptor.AfterHttpRequest(responseMessage, cancellationToken).ConfigureAwait(false); } } - /// - /// Will be called after the Response has been received from Server - /// - /// HttpResponseMessage as received from server - async Task OnAfterRequest(HttpResponseMessage responseMessage) { - foreach (var interceptor in Options.Interceptors) { - await interceptor.InterceptAfterRequest(responseMessage); + + static async ValueTask OnAfterRequest(RestResponse response, CancellationToken cancellationToken) { + if (response.Request.Interceptors == null) return; + + foreach (var interceptor in response.Request.Interceptors) { + await interceptor.AfterRequest(response, cancellationToken).ConfigureAwait(false); } } diff --git a/src/RestSharp/RestClient.Extensions.cs b/src/RestSharp/RestClient.Extensions.cs index 4daede03b..5ac631d87 100644 --- a/src/RestSharp/RestClient.Extensions.cs +++ b/src/RestSharp/RestClient.Extensions.cs @@ -20,8 +20,8 @@ namespace RestSharp; [PublicAPI] public static partial class RestClientExtensions { [PublicAPI] - public static RestResponse Deserialize(this IRestClient client, RestResponse response) - => client.Serializers.Deserialize(response.Request, response, client.Options); + public static ValueTask> Deserialize(this IRestClient client, RestResponse response, CancellationToken cancellationToken) + => client.Serializers.Deserialize(response.Request, response, client.Options, cancellationToken); /// /// Executes the request asynchronously, authenticating if needed @@ -35,22 +35,10 @@ public static async Task> ExecuteAsync( RestRequest request, CancellationToken cancellationToken = default ) { - if (request == null) throw new ArgumentNullException(nameof(request)); + Ensure.NotNull(request, nameof(request)); var response = await client.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); - await OnBeforeDeserialization(response, client.Options).ConfigureAwait(false); - return client.Serializers.Deserialize(request, response, client.Options); - } - - /// - /// Will be called before the Data will be serialized - /// - /// RestResponse with Data still in Content - /// RestClient options but readonly - static async Task OnBeforeDeserialization(RestResponse raw, ReadOnlyRestClientOptions options) { - foreach (var interceptor in options.Interceptors) { - await interceptor.InterceptBeforeDeserialize(raw); - } + return await client.Serializers.Deserialize(request, response, client.Options, cancellationToken); } /// diff --git a/src/RestSharp/RestClient.cs b/src/RestSharp/RestClient.cs index d4ae91867..5b6298a11 100644 --- a/src/RestSharp/RestClient.cs +++ b/src/RestSharp/RestClient.cs @@ -14,7 +14,9 @@ using System.Diagnostics.CodeAnalysis; using System.Net.Http.Headers; +using System.Runtime.CompilerServices; using RestSharp.Authenticators; +using RestSharp.Interceptors; using RestSharp.Serializers; // ReSharper disable VirtualMemberCallInConstructor @@ -36,7 +38,11 @@ public partial class RestClient : IRestClient { /// Content types that will be sent in the Accept header. The list is populated from the known serializers. /// If you need to send something else by default, set this property to a different value. /// - public string[] AcceptedContentTypes { get; set; } + public string[] AcceptedContentTypes { + get; + [MethodImpl(MethodImplOptions.Synchronized)] + set; + } internal HttpClient HttpClient { get; } @@ -83,11 +89,12 @@ public RestClient( HttpClient = GetClient(); } - DefaultParameters = new DefaultParameters(Options); + DefaultParameters = new DefaultParameters(Options); + return; HttpClient GetClient() { var handler = new HttpClientHandler(); - ConfigureHttpMessageHandler(handler, Options); + ConfigureHttpMessageHandler(handler, options); var finalHandler = options.ConfigureMessageHandler?.Invoke(handler) ?? handler; var httpClient = new HttpClient(finalHandler); @@ -181,7 +188,9 @@ public RestClient( Options = new ReadOnlyRestClientOptions(opt); DefaultParameters = new DefaultParameters(Options); - if (options != null) ConfigureHttpClient(httpClient, options); + if (options != null) { + ConfigureHttpClient(httpClient, options); + } } /// @@ -227,7 +236,7 @@ static void ConfigureHttpClient(HttpClient httpClient, RestClientOptions options } // ReSharper disable once CognitiveComplexity - static void ConfigureHttpMessageHandler(HttpClientHandler handler, ReadOnlyRestClientOptions options) { + static void ConfigureHttpMessageHandler(HttpClientHandler handler, RestClientOptions options) { #if NET if (!OperatingSystem.IsBrowser()) { #endif @@ -271,8 +280,7 @@ void ConfigureSerializers(ConfigureSerialization? configureSerialization) { } readonly bool _disposeHttpClient; - - bool _disposed; + bool _disposed; protected virtual void Dispose(bool disposing) { if (disposing && !_disposed) { diff --git a/src/RestSharp/RestSharp.csproj b/src/RestSharp/RestSharp.csproj index 0620c6ac9..deebaf130 100644 --- a/src/RestSharp/RestSharp.csproj +++ b/src/RestSharp/RestSharp.csproj @@ -3,18 +3,20 @@ true - + - - + + - - - + + + + - + + @@ -24,34 +26,34 @@ RestClient.cs - PropertyCache.cs + PropertyCache.cs - PropertyCache.cs + PropertyCache.cs - RestClient.Extensions.cs + RestClient.Extensions.cs - RestClient.Extensions.cs + RestClient.Extensions.cs - RestClient.Extensions.cs + RestClient.Extensions.cs - RestClient.Extensions.cs + RestClient.Extensions.cs - RestClient.Extensions.cs + RestClient.Extensions.cs - RestClient.Extensions.cs + RestClient.Extensions.cs - RestClient.Extensions.cs + RestClient.Extensions.cs - + diff --git a/src/RestSharp/Serializers/RestSerializers.cs b/src/RestSharp/Serializers/RestSerializers.cs index 0133db6cf..e2379030b 100644 --- a/src/RestSharp/Serializers/RestSerializers.cs +++ b/src/RestSharp/Serializers/RestSerializers.cs @@ -20,6 +20,7 @@ namespace RestSharp.Serializers; public class RestSerializers { + [PublicAPI] public IReadOnlyDictionary Serializers { get; } public RestSerializers(Dictionary records) @@ -34,10 +35,11 @@ public IRestSerializer GetSerializer(DataFormat dataFormat) internal string[] GetAcceptedContentTypes() => Serializers.SelectMany(x => x.Value.AcceptedContentTypes).Distinct().ToArray(); - internal RestResponse Deserialize(RestRequest request, RestResponse raw, ReadOnlyRestClientOptions options) { + internal async ValueTask> Deserialize(RestRequest request, RestResponse raw, ReadOnlyRestClientOptions options, CancellationToken cancellationToken) { var response = RestResponse.FromResponse(raw); try { + await OnBeforeDeserialization(response, cancellationToken).ConfigureAwait(false); request.OnBeforeDeserialization?.Invoke(raw); response.Data = DeserializeContent(raw); } @@ -54,6 +56,13 @@ internal RestResponse Deserialize(RestRequest request, RestResponse raw, R return response; } + static async ValueTask OnBeforeDeserialization(RestResponse response, CancellationToken cancellationToken) { + if (response.Request.Interceptors == null) return; + + foreach (var interceptor in response.Request.Interceptors) { + await interceptor.BeforeDeserialization(response, cancellationToken).ConfigureAwait(false); + } + } /// /// Deserialize the response content into the specified type diff --git a/test/Directory.Build.props b/test/Directory.Build.props index ec109e2e1..04eb8a60d 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -3,23 +3,25 @@ true false - net472;net6.0;net7.0 + net472;net6.0;net7.0;net8.0 disable xUnit1033 + trx%3bLogFileName=$(MSBuildProjectName).trx + $(RepoRoot)/test-results/$(TargetFramework) - - - + + + - - - + + + - + diff --git a/test/RestSharp.InteractiveTests/RestSharp.InteractiveTests.csproj b/test/RestSharp.InteractiveTests/RestSharp.InteractiveTests.csproj index c25f980cf..36f53edc9 100644 --- a/test/RestSharp.InteractiveTests/RestSharp.InteractiveTests.csproj +++ b/test/RestSharp.InteractiveTests/RestSharp.InteractiveTests.csproj @@ -2,7 +2,7 @@ Exe false - net6 + net6.0 diff --git a/test/RestSharp.Tests.Integrated/Interceptor/InterceptorTests.cs b/test/RestSharp.Tests.Integrated/Interceptor/InterceptorTests.cs index 3357f5204..ee70683b3 100644 --- a/test/RestSharp.Tests.Integrated/Interceptor/InterceptorTests.cs +++ b/test/RestSharp.Tests.Integrated/Interceptor/InterceptorTests.cs @@ -1,127 +1,162 @@ -// Copyright (c) .NET Foundation and Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using Moq; -using RestSharp.Tests.Integrated.Server; - -namespace RestSharp.Tests.Integrated.Interceptor; +using RestSharp.Tests.Integrated.Server; + +namespace RestSharp.Tests.Integrated.Interceptor; [Collection(nameof(TestServerCollection))] -public class InterceptorTests { - readonly RestClient _client; +public class InterceptorTests(TestServerFixture fixture) { + [Fact] + public async Task Should_call_client_interceptor() { + // Arrange + var request = CreateRequest(); + + var (client, interceptor) = SetupClient( + test => test.BeforeRequestAction = req => req.AddHeader("foo", "bar") + ); - public InterceptorTests(TestServerFixture fixture) => _client = new RestClient(fixture.Server.Url); + //Act + var response = await client.ExecutePostAsync(request); + + //Assert + response.Request.Parameters.Should().Contain(x => x.Name == "foo" && (string)x.Value! == "bar"); + interceptor.BeforeRequestCalled.Should().BeTrue(); + interceptor.BeforeHttpRequestCalled.Should().BeTrue(); + interceptor.AfterHttpRequestCalled.Should().BeTrue(); + interceptor.AfterRequestCalled.Should().BeTrue(); + interceptor.BeforeDeserializationCalled.Should().BeTrue(); + + client.Dispose(); + } [Fact] - public async Task AddInterceptor_ShouldBeUsed() { - //Arrange - var body = new TestRequest("foo", 100); - var request = new RestRequest("post/json").AddJsonBody(body); - - var mockInterceptor = new Mock(); - var interceptor = mockInterceptor.Object; - var options = _client.Options; - options.Interceptors.Add(interceptor); + public async Task Should_call_request_interceptor() { + // Arrange + var request = CreateRequest(); + + var client = new RestClient(fixture.Server.Url); + var interceptor = new TestInterceptor(); + request.Interceptors = new List { interceptor }; + //Act - var response = await _client.ExecutePostAsync(request); + await client.ExecutePostAsync(request); + //Assert - mockInterceptor.Verify(m => m.InterceptBeforeSerialization(It.IsAny())); - mockInterceptor.Verify(m => m.InterceptBeforeRequest(It.IsAny())); - mockInterceptor.Verify(m => m.InterceptAfterRequest(It.IsAny())); - mockInterceptor.Verify(m => m.InterceptBeforeDeserialize(It.IsAny())); + interceptor.ShouldHaveCalledAll(); + + client.Dispose(); } + + [Fact] + public async Task Should_call_both_client_and_request_interceptors() { + // Arrange + var request = CreateRequest(); + var (client, interceptor) = SetupClient(); + var requestInterceptor = new TestInterceptor(); + request.Interceptors = new List { requestInterceptor }; + + //Act + await client.ExecutePostAsync(request); + + //Assert + interceptor.ShouldHaveCalledAll(); + requestInterceptor.ShouldHaveCalledAll(); + + client.Dispose(); + } + [Fact] - public async Task ThrowExceptionIn_InterceptBeforeSerialization_ShouldBeCatchedInTest() { + public async Task ThrowExceptionIn_InterceptBeforeRequest() { //Arrange - var body = new TestRequest("foo", 100); - var request = new RestRequest("post/json").AddJsonBody(body); - - var mockInterceptor = new Mock(); - mockInterceptor.Setup(m => m.InterceptBeforeSerialization(It.IsAny())).Throws(() => throw new Exception("DummyException")); - var interceptor = mockInterceptor.Object; - var options = _client.Options; - options.Interceptors.Add(interceptor); + var request = CreateRequest(); + var (client, interceptor) = SetupClient(test => test.BeforeRequestAction = req => throw new Exception("DummyException")); + //Act - var action = () => _client.ExecutePostAsync(request); + var action = () => client.ExecutePostAsync(request); + //Assert await action.Should().ThrowAsync().WithMessage("DummyException"); - mockInterceptor.Verify(m => m.InterceptBeforeSerialization(It.IsAny())); - mockInterceptor.Verify(m => m.InterceptBeforeRequest(It.IsAny()),Times.Never); - mockInterceptor.Verify(m => m.InterceptAfterRequest(It.IsAny()),Times.Never); - mockInterceptor.Verify(m => m.InterceptBeforeDeserialize(It.IsAny()),Times.Never); + interceptor.BeforeRequestCalled.Should().BeTrue(); + interceptor.BeforeHttpRequestCalled.Should().BeFalse(); + interceptor.AfterHttpRequestCalled.Should().BeFalse(); + interceptor.AfterRequestCalled.Should().BeFalse(); + interceptor.BeforeDeserializationCalled.Should().BeFalse(); } + [Fact] - public async Task ThrowExceptionIn_InterceptBeforeRequest_ShouldBeCatchableInTest() { - //Arrange - var body = new TestRequest("foo", 100); - var request = new RestRequest("post/json").AddJsonBody(body); - - var mockInterceptor = new Mock(); - mockInterceptor.Setup(m => m.InterceptBeforeRequest(It.IsAny())).Throws(() => throw new Exception("DummyException")); - var interceptor = mockInterceptor.Object; - var options = _client.Options; - options.Interceptors.Add(interceptor); + public async Task ThrowExceptionIn_InterceptBeforeHttpRequest() { + // Arrange + var request = CreateRequest(); + var (client, interceptor) = SetupClient(test => test.BeforeHttpRequestAction = req => throw new Exception("DummyException")); + //Act - var action = () => _client.ExecutePostAsync(request); + var action = () => client.ExecutePostAsync(request); + //Assert await action.Should().ThrowAsync().WithMessage("DummyException"); - mockInterceptor.Verify(m => m.InterceptBeforeSerialization(It.IsAny())); - mockInterceptor.Verify(m => m.InterceptBeforeRequest(It.IsAny())); - mockInterceptor.Verify(m => m.InterceptAfterRequest(It.IsAny()),Times.Never); - mockInterceptor.Verify(m => m.InterceptBeforeDeserialize(It.IsAny()),Times.Never); + interceptor.BeforeRequestCalled.Should().BeTrue(); + interceptor.BeforeHttpRequestCalled.Should().BeTrue(); + interceptor.AfterHttpRequestCalled.Should().BeFalse(); + interceptor.AfterRequestCalled.Should().BeFalse(); + interceptor.BeforeDeserializationCalled.Should().BeFalse(); } + [Fact] - public async Task ThrowExceptionIn_InterceptAfterRequest_ShouldBeCatchableInTest() { - //Arrange - var body = new TestRequest("foo", 100); - var request = new RestRequest("post/json").AddJsonBody(body); - - var mockInterceptor = new Mock(); - mockInterceptor.Setup(m => m.InterceptAfterRequest(It.IsAny())).Throws(() => throw new Exception("DummyException")); - var interceptor = mockInterceptor.Object; - var options = _client.Options; - options.Interceptors.Add(interceptor); + public async Task ThrowException_InInterceptAfterHttpRequest() { + // Arrange + var request = CreateRequest(); + var (client, interceptor) = SetupClient(test => test.AfterHttpRequestAction = req => throw new Exception("DummyException")); + //Act - var action = () => _client.ExecutePostAsync(request); + var action = () => client.ExecutePostAsync(request); + //Assert await action.Should().ThrowAsync().WithMessage("DummyException"); - mockInterceptor.Verify(m => m.InterceptBeforeSerialization(It.IsAny())); - mockInterceptor.Verify(m => m.InterceptBeforeRequest(It.IsAny())); - mockInterceptor.Verify(m => m.InterceptAfterRequest(It.IsAny())); - mockInterceptor.Verify(m => m.InterceptBeforeDeserialize(It.IsAny()),Times.Never); + interceptor.BeforeRequestCalled.Should().BeTrue(); + interceptor.BeforeHttpRequestCalled.Should().BeTrue(); + interceptor.AfterHttpRequestCalled.Should().BeTrue(); + interceptor.AfterRequestCalled.Should().BeFalse(); + interceptor.BeforeDeserializationCalled.Should().BeFalse(); } + [Fact] - public async Task ThrowException_InInterceptBeforeDeserialize_ShouldBeCatchableInTest() { - //Arrange - var body = new TestRequest("foo", 100); - var request = new RestRequest("post/json").AddJsonBody(body); - - var mockInterceptor = new Mock(); - mockInterceptor.Setup(m => m.InterceptBeforeDeserialize(It.IsAny())).Throws(() => throw new Exception("DummyException")); - var interceptor = mockInterceptor.Object; - var options = _client.Options; - options.Interceptors.Add(interceptor); + public async Task ThrowExceptionIn_InterceptAfterRequest() { + // Arrange + var request = CreateRequest(); + var (client, interceptor) = SetupClient(test => test.AfterRequestAction = req => throw new Exception("DummyException")); + //Act - var action = () => _client.PostAsync(request); + var action = () => client.ExecutePostAsync(request); + //Assert await action.Should().ThrowAsync().WithMessage("DummyException"); - mockInterceptor.Verify(m => m.InterceptBeforeSerialization(It.IsAny())); - mockInterceptor.Verify(m => m.InterceptBeforeRequest(It.IsAny())); - mockInterceptor.Verify(m => m.InterceptAfterRequest(It.IsAny())); - mockInterceptor.Verify(m => m.InterceptBeforeDeserialize(It.IsAny())); + interceptor.BeforeRequestCalled.Should().BeTrue(); + interceptor.BeforeHttpRequestCalled.Should().BeTrue(); + interceptor.AfterHttpRequestCalled.Should().BeTrue(); + interceptor.AfterRequestCalled.Should().BeTrue(); + interceptor.BeforeDeserializationCalled.Should().BeFalse(); + } + + (RestClient client, TestInterceptor interceptor) SetupClient(Action? configureInterceptor = null) { + var interceptor = new TestInterceptor(); + configureInterceptor?.Invoke(interceptor); + + var options = new RestClientOptions(fixture.Server.Url) { + Interceptors = new List { interceptor } + }; + return (new RestClient(options), interceptor); + } + + static RestRequest CreateRequest() { + var body = new TestRequest("foo", 100); + return new RestRequest("post/json").AddJsonBody(body); + } +} + +static class InterceptorChecks { + public static void ShouldHaveCalledAll(this TestInterceptor interceptor) { + interceptor.BeforeRequestCalled.Should().BeTrue(); + interceptor.BeforeHttpRequestCalled.Should().BeTrue(); + interceptor.AfterHttpRequestCalled.Should().BeTrue(); + interceptor.AfterRequestCalled.Should().BeTrue(); + interceptor.BeforeDeserializationCalled.Should().BeTrue(); } - - -} \ No newline at end of file +} diff --git a/test/RestSharp.Tests.Integrated/Interceptor/TestInterceptor.cs b/test/RestSharp.Tests.Integrated/Interceptor/TestInterceptor.cs new file mode 100644 index 000000000..c5c88f8f9 --- /dev/null +++ b/test/RestSharp.Tests.Integrated/Interceptor/TestInterceptor.cs @@ -0,0 +1,45 @@ +namespace RestSharp.Tests.Integrated.Interceptor; + +class TestInterceptor : Interceptors.Interceptor { + internal bool BeforeRequestCalled { get; private set; } + internal bool BeforeHttpRequestCalled { get; private set; } + internal bool AfterHttpRequestCalled { get; private set; } + internal bool AfterRequestCalled { get; private set; } + internal bool BeforeDeserializationCalled { get; private set; } + + internal Action? BeforeRequestAction { get; set; } + internal Action? BeforeHttpRequestAction { get; set; } + internal Action? AfterHttpRequestAction { get; set; } + internal Action? AfterRequestAction { get; set; } + internal Action? BeforeDeserializationAction { get; set; } + + public override ValueTask BeforeHttpRequest(HttpRequestMessage req, CancellationToken cancellationToken) { + BeforeHttpRequestCalled = true; + BeforeHttpRequestAction?.Invoke(req); + return base.BeforeHttpRequest(req, cancellationToken); + } + + public override ValueTask AfterHttpRequest(HttpResponseMessage responseMessage, CancellationToken cancellationToken) { + AfterHttpRequestCalled = true; + AfterHttpRequestAction?.Invoke(responseMessage); + return base.AfterHttpRequest(responseMessage, cancellationToken); + } + + public override ValueTask AfterRequest(RestResponse response, CancellationToken cancellationToken) { + AfterRequestCalled = true; + AfterRequestAction?.Invoke(response); + return base.AfterRequest(response, cancellationToken); + } + + public override ValueTask BeforeRequest(RestRequest request, CancellationToken cancellationToken) { + BeforeRequestCalled = true; + BeforeRequestAction?.Invoke(request); + return base.BeforeRequest(request, cancellationToken); + } + + public override ValueTask BeforeDeserialization(RestResponse response, CancellationToken cancellationToken) { + BeforeDeserializationCalled = true; + BeforeDeserializationAction?.Invoke(response); + return base.BeforeDeserialization(response, cancellationToken); + } +} diff --git a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj index cd511eb9b..96a0bbc24 100644 --- a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj +++ b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj @@ -4,24 +4,23 @@ net6.0;net7.0 - - - + + + - - - - + + + + - - - - - + + + + - + \ No newline at end of file diff --git a/test/RestSharp.Tests.Serializers.Json/NewtonsoftJson/IntegratedSimpleTests.cs b/test/RestSharp.Tests.Serializers.Json/NewtonsoftJson/IntegratedSimpleTests.cs index 214d94795..c86a4d332 100644 --- a/test/RestSharp.Tests.Serializers.Json/NewtonsoftJson/IntegratedSimpleTests.cs +++ b/test/RestSharp.Tests.Serializers.Json/NewtonsoftJson/IntegratedSimpleTests.cs @@ -65,7 +65,7 @@ public async Task DeserilizationFails_IsSuccessful_Should_BeFalse() { var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseNewtonsoftJson()); - var response = await client.ExecuteAsync(new RestRequest()); + var response = await client.ExecuteAsync(new RestRequest(), default); response.IsSuccessStatusCode.Should().BeTrue(); response.IsSuccessful.Should().BeFalse(); @@ -88,7 +88,7 @@ public async Task DeserilizationSucceeds_IsSuccessful_Should_BeTrue() { var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseNewtonsoftJson()); - var response = await client.ExecuteAsync(new RestRequest()); + var response = await client.ExecuteAsync(new RestRequest(), default); response.IsSuccessStatusCode.Should().BeTrue(); response.IsSuccessful.Should().BeTrue(); diff --git a/test/RestSharp.Tests/RestSharp.Tests.csproj b/test/RestSharp.Tests/RestSharp.Tests.csproj index eda460e5b..bdc1da038 100644 --- a/test/RestSharp.Tests/RestSharp.Tests.csproj +++ b/test/RestSharp.Tests/RestSharp.Tests.csproj @@ -1,6 +1,6 @@  - + From b2500c2d80520eb8f09212e5caaf4957fcf24ac7 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Fri, 15 Sep 2023 14:15:32 +0200 Subject: [PATCH 02/24] Add Linux runner, and publish tests results from Linux --- .github/workflows/pull-request.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 7e1b03b78..28ad59e38 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -6,9 +6,26 @@ permissions: contents: read jobs: - test: + test-windows: runs-on: windows-latest + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '8.0' + dotnet-quality: 'preview' + - + name: Run tests + run: dotnet test + + test-linux: + runs-on: ubuntu-latest + steps: - name: Checkout @@ -17,7 +34,8 @@ jobs: name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0' + dotnet-version: '8.0' + dotnet-quality: 'preview' - name: Run tests run: dotnet test From 8fd9767b3bcab8d23317cb671f5c0eea22665efe Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Fri, 15 Sep 2023 14:41:17 +0200 Subject: [PATCH 03/24] A bit of cleanup and adding package versions for source gen --- Directory.Packages.props | 72 ++++++++++--------- gen/SourceGenerator/SourceGenerator.csproj | 6 +- .../CompressionTests.cs | 4 -- .../DefaultParameterTests.cs | 16 ++--- .../HttpClientTests.cs | 17 ++--- .../HttpHeadersTests.cs | 10 +-- .../JsonBodyTests.cs | 15 ++-- .../NonProtocolExceptionHandlingTests.cs | 4 +- test/RestSharp.Tests.Integrated/PostTests.cs | 6 +- test/RestSharp.Tests.Integrated/PutTests.cs | 10 +-- .../RedirectTests.cs | 11 +-- .../RequestBodyTests.cs | 14 ++-- .../RequestFailureTests.cs | 15 ++-- .../RequestTests.cs | 12 +--- .../ResourceStringParametersTests.cs | 5 +- .../StructuredSyntaxSuffixTests.cs | 1 + .../UploadFileTests.cs | 3 +- .../NamespacedXmlTests.cs | 2 +- .../XmlAttributeDeserializerTests.cs | 6 +- .../XmlDeserializerTests.cs | 2 +- 20 files changed, 81 insertions(+), 150 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index e8fecbe13..170800515 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,37 +1,39 @@ - - true - - - [6.0.22,7) - - - 7.0.11 - - - 8.0.0-rc.1.23421.29 - - - - - - - - - - - - - - - - - - - - - - - - + + true + + + [6.0.22,7) + + + 7.0.11 + + + 8.0.0-rc.1.23421.29 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gen/SourceGenerator/SourceGenerator.csproj b/gen/SourceGenerator/SourceGenerator.csproj index 4ff3c5025..98c5207cf 100644 --- a/gen/SourceGenerator/SourceGenerator.csproj +++ b/gen/SourceGenerator/SourceGenerator.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 11 + preview enable disable false @@ -11,8 +11,8 @@ - - + + diff --git a/test/RestSharp.Tests.Integrated/CompressionTests.cs b/test/RestSharp.Tests.Integrated/CompressionTests.cs index 2d03cb9ab..42896c08f 100644 --- a/test/RestSharp.Tests.Integrated/CompressionTests.cs +++ b/test/RestSharp.Tests.Integrated/CompressionTests.cs @@ -6,8 +6,6 @@ namespace RestSharp.Tests.Integrated; public class CompressionTests { - readonly ITestOutputHelper _output; - static Action GzipEchoValue(string value) => context => { context.Response.Headers.Add("Content-encoding", "gzip"); @@ -25,8 +23,6 @@ static Action DeflateEchoValue(string value) gzip.WriteStringUtf8(value); }; - - public CompressionTests(ITestOutputHelper output) => _output = output; [Fact] public async Task Can_Handle_Deflate_Compressed_Content() { diff --git a/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs b/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs index 742717d88..d2b0208a2 100644 --- a/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs +++ b/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs @@ -5,16 +5,8 @@ namespace RestSharp.Tests.Integrated; [Collection(nameof(TestServerCollection))] -public sealed class DefaultParameterTests : IDisposable { - readonly TestServerFixture _fixture; - readonly ITestOutputHelper _testOutputHelper; - readonly SimpleServer _server; - - public DefaultParameterTests(TestServerFixture fixture, ITestOutputHelper testOutputHelper) { - _fixture = fixture; - _testOutputHelper = testOutputHelper; - _server = SimpleServer.Create(RequestHandler.Handle); - } +public sealed class DefaultParameterTests(TestServerFixture fixture) : IDisposable { + readonly SimpleServer _server = SimpleServer.Create(RequestHandler.Handle); public void Dispose() => _server.Dispose(); @@ -42,7 +34,7 @@ public async Task Should_add_default_and_request_url_get_parameters() { [Fact] public async Task Should_not_throw_exception_when_name_is_null() { - var client = new RestClient($"{_fixture.Server.Url}/request-echo").AddDefaultParameter("foo", "bar", ParameterType.UrlSegment); + var client = new RestClient($"{fixture.Server.Url}/request-echo").AddDefaultParameter("foo", "bar", ParameterType.UrlSegment); var request = new RestRequest("{foo1}").AddParameter(null, "value", ParameterType.RequestBody); await client.ExecuteAsync(request); @@ -56,4 +48,4 @@ public static void Handle(HttpListenerContext context) { Handlers.Echo(context); } } -} \ No newline at end of file +} diff --git a/test/RestSharp.Tests.Integrated/HttpClientTests.cs b/test/RestSharp.Tests.Integrated/HttpClientTests.cs index fdd63e692..91a86f651 100644 --- a/test/RestSharp.Tests.Integrated/HttpClientTests.cs +++ b/test/RestSharp.Tests.Integrated/HttpClientTests.cs @@ -1,19 +1,16 @@ using System.Net; using RestSharp.Tests.Integrated.Server; -namespace RestSharp.Tests.Integrated; +namespace RestSharp.Tests.Integrated; [Collection(nameof(TestServerCollection))] -public class HttpClientTests { - readonly TestServerFixture _fixture; - - public HttpClientTests(TestServerFixture fixture) => _fixture = fixture; - +public class HttpClientTests(TestServerFixture fixture) { [Fact] public async Task ShouldUseBaseAddress() { - using var httpClient = new HttpClient { BaseAddress = _fixture.Server.Url }; - using var client = new RestClient(httpClient); - + using var httpClient = new HttpClient(); + httpClient.BaseAddress = fixture.Server.Url; + using var client = new RestClient(httpClient); + var request = new RestRequest("success"); var response = await client.ExecuteAsync(request); @@ -22,4 +19,4 @@ public async Task ShouldUseBaseAddress() { } record Response(string Message); -} \ No newline at end of file +} diff --git a/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs b/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs index 434a23828..c9a2c1338 100644 --- a/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs +++ b/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs @@ -4,14 +4,8 @@ namespace RestSharp.Tests.Integrated; [Collection(nameof(TestServerCollection))] -public class HttpHeadersTests { - readonly ITestOutputHelper _output; - readonly RestClient _client; - - public HttpHeadersTests(TestServerFixture fixture, ITestOutputHelper output) { - _output = output; - _client = new RestClient(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = true }); - } +public class HttpHeadersTests(TestServerFixture fixture) { + readonly RestClient _client = new(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = true }); [Fact] public async Task Ensure_headers_correctly_set_in_the_hook() { diff --git a/test/RestSharp.Tests.Integrated/JsonBodyTests.cs b/test/RestSharp.Tests.Integrated/JsonBodyTests.cs index 2687a7a38..8a9935dc3 100644 --- a/test/RestSharp.Tests.Integrated/JsonBodyTests.cs +++ b/test/RestSharp.Tests.Integrated/JsonBodyTests.cs @@ -5,16 +5,9 @@ namespace RestSharp.Tests.Integrated; #pragma warning disable xUnit1033 -public sealed class JsonBodyTests : IClassFixture { - readonly SimpleServer _server; - readonly ITestOutputHelper _output; - readonly RestClient _client; - - public JsonBodyTests(RequestBodyFixture fixture, ITestOutputHelper output) { - _output = output; - _server = fixture.Server; - _client = new RestClient(_server.Url); - } +public sealed class JsonBodyTests(RequestBodyFixture fixture) : IClassFixture { + readonly SimpleServer _server = fixture.Server; + readonly RestClient _client = new(fixture.Server.Url); [Fact] public async Task Query_Parameters_With_Json_Body() { @@ -55,7 +48,7 @@ public async Task Add_JSON_body_string() { },"; var expected = JsonSerializer.Serialize(payload); - var request = new RestRequest(RequestBodyCapturer.Resource, Method.Post).AddJsonBody(payload, true); + var request = new RestRequest(RequestBodyCapturer.Resource, Method.Post).AddJsonBody(payload, true); await _client.ExecuteAsync(request); diff --git a/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs b/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs index 6f6d8ebda..4262c2802 100644 --- a/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs +++ b/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs @@ -16,11 +16,9 @@ class StupidClass { /// static void TimeoutHandler(HttpListenerContext context) => Thread.Sleep(101000); - public NonProtocolExceptionHandlingTests() => _server = SimpleServer.Create(TimeoutHandler); - public void Dispose() => _server.Dispose(); - readonly SimpleServer _server; + readonly SimpleServer _server = SimpleServer.Create(TimeoutHandler); /// /// Success of this test is based largely on the behavior of your current DNS. diff --git a/test/RestSharp.Tests.Integrated/PostTests.cs b/test/RestSharp.Tests.Integrated/PostTests.cs index d31e7c801..7dc4325e7 100644 --- a/test/RestSharp.Tests.Integrated/PostTests.cs +++ b/test/RestSharp.Tests.Integrated/PostTests.cs @@ -4,10 +4,8 @@ namespace RestSharp.Tests.Integrated; [Collection(nameof(TestServerCollection))] -public class PostTests { - readonly RestClient _client; - - public PostTests(TestServerFixture fixture) => _client = new RestClient(fixture.Server.Url); +public class PostTests(TestServerFixture fixture) { + readonly RestClient _client = new(fixture.Server.Url); [Fact] public async Task Should_post_json() { diff --git a/test/RestSharp.Tests.Integrated/PutTests.cs b/test/RestSharp.Tests.Integrated/PutTests.cs index 1a0e09f89..19b238d33 100644 --- a/test/RestSharp.Tests.Integrated/PutTests.cs +++ b/test/RestSharp.Tests.Integrated/PutTests.cs @@ -5,17 +5,11 @@ namespace RestSharp.Tests.Integrated; [Collection(nameof(TestServerCollection))] -public class PutTests { - readonly ITestOutputHelper _output; - readonly RestClient _client; +public class PutTests(TestServerFixture fixture) { + readonly RestClient _client = new(fixture.Server.Url); static readonly JsonSerializerOptions Options = new(JsonSerializerDefaults.Web); - public PutTests(TestServerFixture fixture, ITestOutputHelper output) { - _output = output; - _client = new RestClient(fixture.Server.Url); - } - [Fact] public async Task Should_put_json_body() { var body = new TestRequest("foo", 100); diff --git a/test/RestSharp.Tests.Integrated/RedirectTests.cs b/test/RestSharp.Tests.Integrated/RedirectTests.cs index 47b4954a2..0abadb358 100644 --- a/test/RestSharp.Tests.Integrated/RedirectTests.cs +++ b/test/RestSharp.Tests.Integrated/RedirectTests.cs @@ -19,15 +19,8 @@ namespace RestSharp.Tests.Integrated; [Collection(nameof(TestServerCollection))] -public class RedirectTests { - readonly RestClient _client; - - public RedirectTests(TestServerFixture fixture) { - var options = new RestClientOptions(fixture.Server.Url) { - FollowRedirects = true - }; - _client = new RestClient(options); - } +public class RedirectTests(TestServerFixture fixture) { + readonly RestClient _client = new(new RestClientOptions(fixture.Server.Url) { FollowRedirects = true }); [Fact] public async Task Can_Perform_GET_Async_With_Redirect() { diff --git a/test/RestSharp.Tests.Integrated/RequestBodyTests.cs b/test/RestSharp.Tests.Integrated/RequestBodyTests.cs index a7c5df2c0..27bf6f271 100644 --- a/test/RestSharp.Tests.Integrated/RequestBodyTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestBodyTests.cs @@ -3,9 +3,8 @@ namespace RestSharp.Tests.Integrated; -public class RequestBodyTests : IClassFixture { - readonly ITestOutputHelper _output; - readonly SimpleServer _server; +public class RequestBodyTests(RequestBodyFixture fixture, ITestOutputHelper output) : IClassFixture { + readonly SimpleServer _server = fixture.Server; const string NewLine = "\r\n"; @@ -13,19 +12,14 @@ public class RequestBodyTests : IClassFixture { const string ExpectedTextContentType = $"{TextPlainContentType}; charset=utf-8"; const string ExpectedTextContentTypeNoCharset = TextPlainContentType; - public RequestBodyTests(RequestBodyFixture fixture, ITestOutputHelper output) { - _output = output; - _server = fixture.Server; - } - async Task AssertBody(Method method, bool disableCharset = false) { var options = new RestClientOptions(_server.Url) { DisableCharset = disableCharset }; var client = new RestClient(options); var request = new RestRequest(RequestBodyCapturer.Resource, method) { OnBeforeRequest = async m => { - _output.WriteLine(m.ToString()); - _output.WriteLine(await m.Content!.ReadAsStringAsync()); + output.WriteLine(m.ToString()); + output.WriteLine(await m.Content!.ReadAsStringAsync()); } }; diff --git a/test/RestSharp.Tests.Integrated/RequestFailureTests.cs b/test/RestSharp.Tests.Integrated/RequestFailureTests.cs index 7c62b5ea5..26954e86e 100644 --- a/test/RestSharp.Tests.Integrated/RequestFailureTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestFailureTests.cs @@ -1,18 +1,13 @@ using System.Net; using RestSharp.Tests.Integrated.Server; + // ReSharper disable ClassNeverInstantiated.Local namespace RestSharp.Tests.Integrated; [Collection(nameof(TestServerCollection))] -public class RequestFailureTests { - readonly RestClient _client; - readonly TestServerFixture _fixture; - - public RequestFailureTests(TestServerFixture fixture) { - _client = new RestClient(fixture.Server.Url); - _fixture = fixture; - } +public class RequestFailureTests(TestServerFixture fixture) { + readonly RestClient _client = new(fixture.Server.Url); [Fact] public async Task Handles_GET_Request_Errors() { @@ -33,7 +28,7 @@ public async Task Handles_GET_Request_Errors_With_Response_Type() { [Fact] public async Task Throws_on_unsuccessful_call() { - var client = new RestClient(new RestClientOptions(_fixture.Server.Url) { ThrowOnAnyError = true }); + var client = new RestClient(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = true }); var request = new RestRequest("status?code=500"); var task = () => client.ExecuteAsync(request); @@ -77,4 +72,4 @@ class Response { // ReSharper disable once UnusedMember.Local public string Message { get; set; } = null!; } -} \ No newline at end of file +} diff --git a/test/RestSharp.Tests.Integrated/RequestTests.cs b/test/RestSharp.Tests.Integrated/RequestTests.cs index 98c04e65e..fa3655507 100644 --- a/test/RestSharp.Tests.Integrated/RequestTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestTests.cs @@ -4,16 +4,8 @@ namespace RestSharp.Tests.Integrated; [Collection(nameof(TestServerCollection))] -public class AsyncTests { - readonly ITestOutputHelper _output; - readonly RestClient _client; - readonly string _host; - - public AsyncTests(TestServerFixture fixture, ITestOutputHelper output) { - _output = output; - _client = new RestClient(fixture.Server.Url); - _host = _client.Options.BaseUrl!.Host; - } +public class AsyncTests(TestServerFixture fixture) { + readonly RestClient _client = new(fixture.Server.Url); class Response { public string Message { get; set; } = null!; diff --git a/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs b/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs index f214a38db..a4206b6e4 100644 --- a/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs +++ b/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs @@ -4,9 +4,7 @@ namespace RestSharp.Tests.Integrated; public sealed class ResourceStringParametersTests : IDisposable { - readonly SimpleServer _server; - - public ResourceStringParametersTests() => _server = SimpleServer.Create(RequestHandler.Handle); + readonly SimpleServer _server = SimpleServer.Create(RequestHandler.Handle); public void Dispose() => _server.Dispose(); @@ -32,5 +30,4 @@ public static void Handle(HttpListenerContext context) { Handlers.Echo(context); } } - #nullable enable } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs b/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs index 1011f88b1..3f4d68499 100644 --- a/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs +++ b/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs @@ -23,6 +23,7 @@ class Person { public StructuredSyntaxSuffixTests() { _server = new TestHttpServer(0, "", HandleRequest); _url = $"http://localhost:{_server.Port}"; + return; static void HandleRequest(HttpListenerRequest request, HttpListenerResponse response, Dictionary p) { response.ContentType = request.QueryString["ct"]; diff --git a/test/RestSharp.Tests.Integrated/UploadFileTests.cs b/test/RestSharp.Tests.Integrated/UploadFileTests.cs index fd9f2c498..9e2bc1d79 100644 --- a/test/RestSharp.Tests.Integrated/UploadFileTests.cs +++ b/test/RestSharp.Tests.Integrated/UploadFileTests.cs @@ -15,8 +15,7 @@ public class UploadFileTests { public UploadFileTests(TestServerFixture fixture, ITestOutputHelper output) { _output = output; - // _client = new RestClient(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = true }); - _client = new RestClient(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = false }); + _client = new RestClient(new RestClientOptions(fixture.Server.Url)); _path = Path.Combine(_basePath, "Assets", Filename); _expected = new UploadResponse(Filename, new FileInfo(_path).Length, true); } diff --git a/test/RestSharp.Tests.Serializers.Xml/NamespacedXmlTests.cs b/test/RestSharp.Tests.Serializers.Xml/NamespacedXmlTests.cs index 8b4ad7809..4d3a32b64 100644 --- a/test/RestSharp.Tests.Serializers.Xml/NamespacedXmlTests.cs +++ b/test/RestSharp.Tests.Serializers.Xml/NamespacedXmlTests.cs @@ -63,7 +63,7 @@ static string CreateUnderscoresXml() { foes.Add(new XAttribute(ns + "Team", "Yankees")); - for (var i = 0; i < 5; i++) foes.Add(new XElement(ns + "Foe", new XElement(ns + "Nickname", "Foe" + i))); + for (var i = 0; i < 5; i++) foes.Add(new XElement(ns + "Foe", new XElement(ns + "Nickname", $"Foe{i}"))); root.Add(foes); doc.Add(root); diff --git a/test/RestSharp.Tests.Serializers.Xml/XmlAttributeDeserializerTests.cs b/test/RestSharp.Tests.Serializers.Xml/XmlAttributeDeserializerTests.cs index 9f6277c6f..ca07e7b53 100644 --- a/test/RestSharp.Tests.Serializers.Xml/XmlAttributeDeserializerTests.cs +++ b/test/RestSharp.Tests.Serializers.Xml/XmlAttributeDeserializerTests.cs @@ -6,11 +6,9 @@ namespace RestSharp.Tests.Serializers.Xml; public class XmlAttributeDeserializerTests { - readonly ITestOutputHelper _output; - const string GuidString = "AC1FC4BC-087A-4242-B8EE-C53EBE9887A5"; - #if NETCORE + #if NET readonly string _sampleDataPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SampleData"); #else readonly string _sampleDataPath = Path.Combine(Directory.GetCurrentDirectory(), "SampleData"); @@ -18,8 +16,6 @@ public class XmlAttributeDeserializerTests { string PathFor(string sampleFile) => Path.Combine(_sampleDataPath, sampleFile); - public XmlAttributeDeserializerTests(ITestOutputHelper output) => _output = output; - [Fact] public void Can_Deserialize_Lists_of_Simple_Types() { var xmlPath = PathFor("xmllists.xml"); diff --git a/test/RestSharp.Tests.Serializers.Xml/XmlDeserializerTests.cs b/test/RestSharp.Tests.Serializers.Xml/XmlDeserializerTests.cs index c1e39af0f..f9c680b3a 100644 --- a/test/RestSharp.Tests.Serializers.Xml/XmlDeserializerTests.cs +++ b/test/RestSharp.Tests.Serializers.Xml/XmlDeserializerTests.cs @@ -9,7 +9,7 @@ namespace RestSharp.Tests.Serializers.Xml; public class XmlDeserializerTests { const string GuidString = "AC1FC4BC-087A-4242-B8EE-C53EBE9887A5"; -#if NETCORE +#if NET readonly string _sampleDataPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SampleData"); #else readonly string _sampleDataPath = Path.Combine(Directory.GetCurrentDirectory(), "SampleData"); From 121bf93075d23cd401b8786c10cb8ff762461e2b Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Fri, 15 Sep 2023 15:10:15 +0200 Subject: [PATCH 04/24] Add missing version --- Directory.Packages.props | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 170800515..a063d1c58 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -33,7 +33,6 @@ - - + \ No newline at end of file From 4a40bcd585ff5dd944035753bd9b3df210253f19 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Mon, 18 Sep 2023 09:59:28 +0200 Subject: [PATCH 05/24] Fixed centralized package versions --- Directory.Packages.props | 4 ++ gen/SourceGenerator/SourceGenerator.csproj | 3 - .../RestSharp.Tests.Serializers.Json.csproj | 20 ++++--- .../Fixtures/TestHttpServer.cs | 9 +-- .../Fixtures/TestRequestHandler.cs | 21 +++---- test/RestSharp.Tests/RestSharp.Tests.csproj | 56 +++++++++---------- 6 files changed, 54 insertions(+), 59 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a063d1c58..2593a0734 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -34,5 +34,9 @@ + + + + \ No newline at end of file diff --git a/gen/SourceGenerator/SourceGenerator.csproj b/gen/SourceGenerator/SourceGenerator.csproj index 98c5207cf..b4aa74be5 100644 --- a/gen/SourceGenerator/SourceGenerator.csproj +++ b/gen/SourceGenerator/SourceGenerator.csproj @@ -1,5 +1,4 @@  - netstandard2.0 preview @@ -9,12 +8,10 @@ true false - - diff --git a/test/RestSharp.Tests.Serializers.Json/RestSharp.Tests.Serializers.Json.csproj b/test/RestSharp.Tests.Serializers.Json/RestSharp.Tests.Serializers.Json.csproj index a7703166c..94a871e1f 100644 --- a/test/RestSharp.Tests.Serializers.Json/RestSharp.Tests.Serializers.Json.csproj +++ b/test/RestSharp.Tests.Serializers.Json/RestSharp.Tests.Serializers.Json.csproj @@ -1,18 +1,22 @@ - - - + + + - + - - + + - - + + + + + + diff --git a/test/RestSharp.Tests.Shared/Fixtures/TestHttpServer.cs b/test/RestSharp.Tests.Shared/Fixtures/TestHttpServer.cs index b3c49b16e..d52704502 100644 --- a/test/RestSharp.Tests.Shared/Fixtures/TestHttpServer.cs +++ b/test/RestSharp.Tests.Shared/Fixtures/TestHttpServer.cs @@ -4,6 +4,8 @@ namespace RestSharp.Tests.Shared.Fixtures; +public delegate void HandlerAction(HttpListenerRequest request, HttpListenerResponse response, Dictionary urlMap); + public class TestHttpServer : IDisposable { readonly HttpListener _listener; readonly List _requestHandlers; @@ -12,12 +14,7 @@ public class TestHttpServer : IDisposable { public int Port { get; } - public TestHttpServer( - int port, - string url, - Action> handlerAction, - string hostName = "localhost" - ) + public TestHttpServer(int port, string url, HandlerAction handlerAction, string hostName = "localhost") : this(port, new List { new(url, handlerAction) }, hostName) { } public TestHttpServer(int port, List handlers, string hostName = "localhost") { diff --git a/test/RestSharp.Tests.Shared/Fixtures/TestRequestHandler.cs b/test/RestSharp.Tests.Shared/Fixtures/TestRequestHandler.cs index e83a8ba01..b01569bc9 100644 --- a/test/RestSharp.Tests.Shared/Fixtures/TestRequestHandler.cs +++ b/test/RestSharp.Tests.Shared/Fixtures/TestRequestHandler.cs @@ -1,4 +1,3 @@ -using System.Net; using System.Text.RegularExpressions; namespace RestSharp.Tests.Shared.Fixtures; @@ -8,11 +7,7 @@ public class TestRequestHandler { readonly List _urlParameterNames = new(); - public TestRequestHandler( - string url, - string httpMethod, - Action> handlerAction - ) { + public TestRequestHandler(string url, string httpMethod, HandlerAction handlerAction) { Url = url; HttpMethod = httpMethod; HandlerAction = handlerAction; @@ -20,12 +15,11 @@ Action> ha _comparisonRegex = CreateComparisonRegex(url); } - public TestRequestHandler(string url, Action> handlerAction) - : this(url, null, handlerAction) { } + public TestRequestHandler(string url, HandlerAction handlerAction) : this(url, null, handlerAction) { } - string Url { get; } - string HttpMethod { get; } - internal Action> HandlerAction { get; } + string Url { get; } + string HttpMethod { get; } + internal HandlerAction HandlerAction { get; } Regex CreateComparisonRegex(string url) { var regexString = Regex.Escape(url).Replace(@"\{", "{"); @@ -57,8 +51,7 @@ public bool TryMatchUrl(string rawUrl, string httpMethod, out Dictionary(); - for (var i = 0; i < _urlParameterNames.Count; i++) - parameters[_urlParameterNames[i]] = match.Groups[i + 1].Value; + for (var i = 0; i < _urlParameterNames.Count; i++) parameters[_urlParameterNames[i]] = match.Groups[i + 1].Value; return true; } -} \ No newline at end of file +} diff --git a/test/RestSharp.Tests/RestSharp.Tests.csproj b/test/RestSharp.Tests/RestSharp.Tests.csproj index bdc1da038..4fdfc1277 100644 --- a/test/RestSharp.Tests/RestSharp.Tests.csproj +++ b/test/RestSharp.Tests/RestSharp.Tests.csproj @@ -1,30 +1,30 @@  - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 2bda7ee8e4ee18891bb559264eea53ddc519646c Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Tue, 24 Oct 2023 15:07:41 +0200 Subject: [PATCH 06/24] Leave exception handling as it is --- .github/ISSUE_TEMPLATE/bug_report.md | 12 +++++++++--- src/RestSharp/Interceptors/Interceptor.cs | 4 ---- src/RestSharp/RestClient.cs | 1 - 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c4e29888f..78f38d41b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,6 +9,13 @@ assignees: '' **DO NOT USE ISSUES FOR QUESTIONS** +Note: New issues raised, where it is clear the submitter has not read the issue template, +are likely to be closed with `invalid` label. Please understand that this is not meant to be rude, +but to keep the issue list clean and useful for everyone. If one opening the issue decides to ignore the issue template, +the maintainers might also decide to ignore the issue. + +**Remove** all the text above the next line when submitting your issue. + **Describe the bug** A clear and concise description of what the bug is. Hint: use a tool like https://requestbin.com to compare working and non-working requests. @@ -22,14 +29,13 @@ A clear and concise description of what you expected to happen. Post the working request here as well if you made it work using Postman, Swagger, or any other client. You can use https://requestbin.com/r to create a public request bin and share the link in the issue. - **Stack trace** Copy the full stack trace here if you get an exception. **Desktop (please complete the following information):** - OS: [e.g. macOS] - - .NET version [e.g. .NET 5] - - Version [e.g. 107.0.4] + - .NET version [e.g. .NET 6] + - Version [e.g. 110.2.0] **Additional context** Add any other context about the problem here. diff --git a/src/RestSharp/Interceptors/Interceptor.cs b/src/RestSharp/Interceptors/Interceptor.cs index 8d138eadc..ddd765f52 100644 --- a/src/RestSharp/Interceptors/Interceptor.cs +++ b/src/RestSharp/Interceptors/Interceptor.cs @@ -30,7 +30,6 @@ public abstract class Interceptor { /// /// RestRequest before composing the request message /// Cancellation token - /// Value Tags public virtual ValueTask BeforeRequest(RestRequest request, CancellationToken cancellationToken) => Completed; /// @@ -38,7 +37,6 @@ public abstract class Interceptor { /// /// HttpRequestMessage before being sent /// Cancellation token - /// Value Tags public virtual ValueTask BeforeHttpRequest(HttpRequestMessage requestMessage, CancellationToken cancellationToken) => Completed; /// @@ -46,7 +44,6 @@ public abstract class Interceptor { /// /// HttpResponseMessage as received from the remote server /// Cancellation token - /// Value Tags public virtual ValueTask AfterHttpRequest(HttpResponseMessage responseMessage, CancellationToken cancellationToken) => Completed; /// @@ -54,7 +51,6 @@ public abstract class Interceptor { /// /// HttpResponseMessage as received from the remote server /// Cancellation token - /// Value Tags public virtual ValueTask AfterRequest(RestResponse response, CancellationToken cancellationToken) => Completed; /// diff --git a/src/RestSharp/RestClient.cs b/src/RestSharp/RestClient.cs index 5b6298a11..0d456525d 100644 --- a/src/RestSharp/RestClient.cs +++ b/src/RestSharp/RestClient.cs @@ -96,7 +96,6 @@ HttpClient GetClient() { var handler = new HttpClientHandler(); ConfigureHttpMessageHandler(handler, options); var finalHandler = options.ConfigureMessageHandler?.Invoke(handler) ?? handler; - var httpClient = new HttpClient(finalHandler); ConfigureHttpClient(httpClient, options); configureDefaultHeaders?.Invoke(httpClient.DefaultRequestHeaders); From b582f5987f90ef91b0829a9c58524444abea2ac2 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Tue, 24 Oct 2023 15:12:45 +0200 Subject: [PATCH 07/24] Remove duplicate package version --- Directory.Packages.props | 1 - 1 file changed, 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 2593a0734..e8bf74d26 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,7 +13,6 @@ - From 95ed004873d77714ce5b7003a946057a628e6a21 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Tue, 2 Apr 2024 17:27:18 +0200 Subject: [PATCH 08/24] Cleanup --- RestSharp.sln.DotSettings | 4 ++ .../Authenticators/AuthenticatorBase.cs | 6 +- .../Authenticators/HttpBasicAuthenticator.cs | 6 +- .../Authenticators/JwtAuthenticator.cs | 4 +- src/RestSharp/Authenticators/OAuth/WebPair.cs | 14 ++--- src/RestSharp/Parameters/DefaultParameters.cs | 8 +-- src/RestSharp/Parameters/ObjectParser.cs | 29 +++++---- .../Parameters/UrlSegmentParameter.cs | 11 +++- .../Request/HttpRequestMessageExtensions.cs | 3 +- src/RestSharp/Request/RequestContent.cs | 61 ++++++++----------- src/RestSharp/Request/RestRequest.cs | 12 ++-- src/RestSharp/Response/RestResponse.cs | 10 +-- .../Serializers/DeseralizationException.cs | 11 ++-- src/RestSharp/Serializers/RestSerializers.cs | 7 +-- .../SampleClasses/twitter.cs | 2 + 15 files changed, 87 insertions(+), 101 deletions(-) diff --git a/RestSharp.sln.DotSettings b/RestSharp.sln.DotSettings index f22b9a27f..d0bb9d525 100644 --- a/RestSharp.sln.DotSettings +++ b/RestSharp.sln.DotSettings @@ -67,6 +67,9 @@ False FDIC <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + True True True True @@ -77,6 +80,7 @@ True True True + True True True True diff --git a/src/RestSharp/Authenticators/AuthenticatorBase.cs b/src/RestSharp/Authenticators/AuthenticatorBase.cs index 1f311dba4..dc765e8c7 100644 --- a/src/RestSharp/Authenticators/AuthenticatorBase.cs +++ b/src/RestSharp/Authenticators/AuthenticatorBase.cs @@ -14,10 +14,8 @@ namespace RestSharp.Authenticators; -public abstract class AuthenticatorBase : IAuthenticator { - protected AuthenticatorBase(string token) => Token = token; - - protected string Token { get; set; } +public abstract class AuthenticatorBase(string token) : IAuthenticator { + protected string Token { get; set; } = token; protected abstract ValueTask GetAuthenticationParameter(string accessToken); diff --git a/src/RestSharp/Authenticators/HttpBasicAuthenticator.cs b/src/RestSharp/Authenticators/HttpBasicAuthenticator.cs index 006ac813f..07d775664 100644 --- a/src/RestSharp/Authenticators/HttpBasicAuthenticator.cs +++ b/src/RestSharp/Authenticators/HttpBasicAuthenticator.cs @@ -24,12 +24,10 @@ namespace RestSharp.Authenticators; /// UTF-8 is used by default but some servers might expect ISO-8859-1 encoding. /// [PublicAPI] -public class HttpBasicAuthenticator : AuthenticatorBase { +public class HttpBasicAuthenticator(string username, string password, Encoding encoding) + : AuthenticatorBase(GetHeader(username, password, encoding)) { public HttpBasicAuthenticator(string username, string password) : this(username, password, Encoding.UTF8) { } - public HttpBasicAuthenticator(string username, string password, Encoding encoding) - : base(GetHeader(username, password, encoding)) { } - static string GetHeader(string username, string password, Encoding encoding) => Convert.ToBase64String(encoding.GetBytes($"{username}:{password}")); diff --git a/src/RestSharp/Authenticators/JwtAuthenticator.cs b/src/RestSharp/Authenticators/JwtAuthenticator.cs index 10e8493df..1b90bdd60 100644 --- a/src/RestSharp/Authenticators/JwtAuthenticator.cs +++ b/src/RestSharp/Authenticators/JwtAuthenticator.cs @@ -18,9 +18,7 @@ namespace RestSharp.Authenticators; /// JSON WEB TOKEN (JWT) Authenticator class. /// https://tools.ietf.org/html/draft-ietf-oauth-json-web-token /// -public class JwtAuthenticator : AuthenticatorBase { - public JwtAuthenticator(string accessToken) : base(GetToken(accessToken)) { } - +public class JwtAuthenticator(string accessToken) : AuthenticatorBase(GetToken(accessToken)) { /// /// Set the new bearer token so the request gets the new header value /// diff --git a/src/RestSharp/Authenticators/OAuth/WebPair.cs b/src/RestSharp/Authenticators/OAuth/WebPair.cs index d83572e05..381fbbe24 100644 --- a/src/RestSharp/Authenticators/OAuth/WebPair.cs +++ b/src/RestSharp/Authenticators/OAuth/WebPair.cs @@ -14,16 +14,10 @@ namespace RestSharp.Authenticators.OAuth; -class WebPair { - public WebPair(string name, string? value, bool encode = false) { - Name = name; - Value = value; - WebValue = encode ? OAuthTools.UrlEncodeRelaxed(value) : value; - } - - public string Name { get; } - public string? Value { get; } - string? WebValue { get; } +class WebPair(string name, string? value, bool encode = false) { + public string Name { get; } = name; + public string? Value { get; } = value; + string? WebValue { get; } = encode ? OAuthTools.UrlEncodeRelaxed(value) : value; public string GetQueryParameter(bool web) { var value = web ? $"\"{WebValue}\"" : Value; diff --git a/src/RestSharp/Parameters/DefaultParameters.cs b/src/RestSharp/Parameters/DefaultParameters.cs index 019cf79a2..6ceb740fd 100644 --- a/src/RestSharp/Parameters/DefaultParameters.cs +++ b/src/RestSharp/Parameters/DefaultParameters.cs @@ -17,11 +17,7 @@ namespace RestSharp; -public sealed class DefaultParameters : ParametersCollection { - readonly ReadOnlyRestClientOptions _options; - - public DefaultParameters(ReadOnlyRestClientOptions options) => _options = options; - +public sealed class DefaultParameters(ReadOnlyRestClientOptions options) : ParametersCollection { /// /// Safely add a default parameter to the collection. /// @@ -36,7 +32,7 @@ public DefaultParameters AddParameter(Parameter parameter) { "Cannot set request body using default parameters. Use Request.AddBody() instead." ); - if (!_options.AllowMultipleDefaultParametersWithSameName && + if (!options.AllowMultipleDefaultParametersWithSameName && !MultiParameterTypes.Contains(parameter.Type) && this.Any(x => x.Name == parameter.Name)) { throw new ArgumentException("A default parameters with the same name has already been added", nameof(parameter)); diff --git a/src/RestSharp/Parameters/ObjectParser.cs b/src/RestSharp/Parameters/ObjectParser.cs index fec2a0a85..52ab89865 100644 --- a/src/RestSharp/Parameters/ObjectParser.cs +++ b/src/RestSharp/Parameters/ObjectParser.cs @@ -36,8 +36,6 @@ public static IEnumerable GetProperties(this object obj, params properties.Add(GetValue(prop, val)); } - string? ParseValue(string? format, object? value) => format == null ? value?.ToString() : string.Format($"{{0:{format}}}", value); - IEnumerable GetArray(PropertyInfo propertyInfo, object? value) { var elementType = propertyInfo.PropertyType.GetElementType(); var array = (Array)value!; @@ -47,20 +45,19 @@ IEnumerable GetArray(PropertyInfo propertyInfo, object? value) var queryType = attribute?.ArrayQueryType ?? RequestArrayQueryType.CommaSeparated; var encode = attribute?.Encode ?? true; - if (array.Length > 0 && elementType != null) { - // convert the array to an array of strings - var values = array - .Cast() - .Select(item => ParseValue(attribute?.Format, item)); + if (array.Length <= 0 || elementType == null) return new ParsedParameter[] { new(name, null, encode) }; - return queryType switch { - RequestArrayQueryType.CommaSeparated => new[] { new ParsedParameter(name, string.Join(",", values), encode) }, - RequestArrayQueryType.ArrayParameters => values.Select(x => new ParsedParameter($"{name}[]", x, encode)), - _ => throw new ArgumentOutOfRangeException() - }; - } + // convert the array to an array of strings + var values = array + .Cast() + .Select(item => ParseValue(attribute?.Format, item)); + + return queryType switch { + RequestArrayQueryType.CommaSeparated => new[] { new ParsedParameter(name, string.Join(",", values), encode) }, + RequestArrayQueryType.ArrayParameters => values.Select(x => new ParsedParameter($"{name}[]", x, encode)), + _ => throw new ArgumentOutOfRangeException() + }; - return new ParsedParameter[] { new(name, null, encode) }; } ParsedParameter GetValue(PropertyInfo propertyInfo, object? value) { @@ -70,10 +67,12 @@ ParsedParameter GetValue(PropertyInfo propertyInfo, object? value) { return new ParsedParameter(name, val, attribute?.Encode ?? true); } + return properties; + bool IsAllowedProperty(string propertyName) => includedProperties.Length == 0 || includedProperties.Length > 0 && includedProperties.Contains(propertyName); - return properties; + string? ParseValue(string? format, object? value) => format == null ? value?.ToString() : string.Format($"{{0:{format}}}", value); } } diff --git a/src/RestSharp/Parameters/UrlSegmentParameter.cs b/src/RestSharp/Parameters/UrlSegmentParameter.cs index deb0b3bdc..402215a7f 100644 --- a/src/RestSharp/Parameters/UrlSegmentParameter.cs +++ b/src/RestSharp/Parameters/UrlSegmentParameter.cs @@ -12,9 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Text.RegularExpressions; + namespace RestSharp; public record UrlSegmentParameter : NamedParameter { + static readonly Regex RegexPattern = new("%2f", RegexOptions.IgnoreCase | RegexOptions.Compiled); + /// /// Instantiates a new query parameter instance that will be added to the request URL by replacing part of the absolute path. /// The request resource should have a placeholder {name} that will be replaced with the parameter value when the request is made. @@ -23,5 +27,10 @@ public record UrlSegmentParameter : NamedParameter { /// Parameter value /// Optional: encode the value, default is true public UrlSegmentParameter(string name, string value, bool encode = true) - : base(name, Ensure.NotEmpty(value, nameof(value)).Replace("%2F", "/").Replace("%2f", "/"), ParameterType.UrlSegment, encode) { } + : base( + name, + RegexPattern.Replace(Ensure.NotEmpty(value, nameof(value)), "/"), + ParameterType.UrlSegment, + encode + ) { } } \ No newline at end of file diff --git a/src/RestSharp/Request/HttpRequestMessageExtensions.cs b/src/RestSharp/Request/HttpRequestMessageExtensions.cs index a8dc5629a..00601d2d5 100644 --- a/src/RestSharp/Request/HttpRequestMessageExtensions.cs +++ b/src/RestSharp/Request/HttpRequestMessageExtensions.cs @@ -23,6 +23,7 @@ public static void AddHeaders(this HttpRequestMessage message, RequestHeaders he var headerParameters = headers.Parameters.Where(x => !KnownHeaders.IsContentHeader(x.Name!)); headerParameters.ForEach(x => AddHeader(x, message.Headers)); + return; void AddHeader(Parameter parameter, HttpHeaders httpHeaders) { var parameterStringValue = parameter.Value!.ToString(); @@ -31,4 +32,4 @@ void AddHeader(Parameter parameter, HttpHeaders httpHeaders) { httpHeaders.TryAddWithoutValidation(parameter.Name!, parameterStringValue); } } -} +} \ No newline at end of file diff --git a/src/RestSharp/Request/RequestContent.cs b/src/RestSharp/Request/RequestContent.cs index 38d1ac90e..5f8f26151 100644 --- a/src/RestSharp/Request/RequestContent.cs +++ b/src/RestSharp/Request/RequestContent.cs @@ -22,30 +22,22 @@ namespace RestSharp; -class RequestContent : IDisposable { - readonly RestClient _client; - readonly RestRequest _request; - readonly List _streams = new(); - readonly ParametersCollection _parameters; +class RequestContent(IRestClient client, RestRequest request) : IDisposable { + readonly List _streams = new(); + readonly ParametersCollection _parameters = new RequestParameters(request.Parameters.Union(client.DefaultParameters)); HttpContent? Content { get; set; } - public RequestContent(RestClient client, RestRequest request) { - _client = client; - _request = request; - _parameters = new RequestParameters(_request.Parameters.Union(_client.DefaultParameters)); - } - public HttpContent BuildContent() { - var postParameters = _parameters.GetContentParameters(_request.Method).ToArray(); + var postParameters = _parameters.GetContentParameters(request.Method).ToArray(); var postParametersExists = postParameters.Length > 0; - var bodyParametersExists = _request.TryGetBodyParameter(out var bodyParameter); - var filesExists = _request.Files.Any(); + var bodyParametersExists = request.TryGetBodyParameter(out var bodyParameter); + var filesExists = request.Files.Any(); - if (_request.HasFiles() || + if (request.HasFiles() || BodyShouldBeMultipartForm(bodyParameter) || - filesExists || - _request.AlwaysMultipartFormData) { + filesExists || + request.AlwaysMultipartFormData) { Content = CreateMultipartFormDataContent(); } @@ -62,15 +54,15 @@ public HttpContent BuildContent() { void AddFiles() { // File uploading without multipart/form-data - if (_request is { AlwaysSingleFileAsContent: true, Files.Count: 1 }) { - var fileParameter = _request.Files.First(); + if (request is { AlwaysSingleFileAsContent: true, Files.Count: 1 }) { + var fileParameter = request.Files.First(); Content?.Dispose(); Content = ToStreamContent(fileParameter); return; } var mpContent = Content as MultipartFormDataContent; - foreach (var fileParameter in _request.Files) mpContent!.Add(ToStreamContent(fileParameter)); + foreach (var fileParameter in request.Files) mpContent!.Add(ToStreamContent(fileParameter)); } StreamContent ToStreamContent(FileParameter fileParameter) { @@ -91,7 +83,7 @@ StreamContent ToStreamContent(FileParameter fileParameter) { HttpContent Serialize(BodyParameter body) { return body.DataFormat switch { - DataFormat.None => new StringContent(body.Value!.ToString()!, _client.Options.Encoding, body.ContentType.Value), + DataFormat.None => new StringContent(body.Value!.ToString(), client.Options.Encoding, body.ContentType.Value), DataFormat.Binary => GetBinary(), _ => GetSerialized() }; @@ -109,14 +101,14 @@ HttpContent GetBinary() { } HttpContent GetSerialized() { - var serializer = _client.Serializers.GetSerializer(body.DataFormat); + var serializer = client.Serializers.GetSerializer(body.DataFormat); var content = serializer.Serialize(body); if (content == null) throw new SerializationException("Request body serialized to null"); var contentType = body.ContentType.Or(serializer.Serializer.ContentType); - return new StringContent(content, _client.Options.Encoding, contentType.Value); + return new StringContent(content, client.Options.Encoding, contentType.Value); } } @@ -127,13 +119,13 @@ static bool BodyShouldBeMultipartForm(BodyParameter? bodyParameter) { return bodyParameter.Name.IsNotEmpty() && bodyParameter.Name != bodyContentType; } - string GetOrSetFormBoundary() => _request.FormBoundary ?? (_request.FormBoundary = Guid.NewGuid().ToString()); + string GetOrSetFormBoundary() => request.FormBoundary ?? (request.FormBoundary = Guid.NewGuid().ToString()); MultipartFormDataContent CreateMultipartFormDataContent() { var boundary = GetOrSetFormBoundary(); var mpContent = new MultipartFormDataContent(boundary); var contentType = new MediaTypeHeaderValue("multipart/form-data"); - contentType.Parameters.Add(new NameValueHeaderValue(nameof(boundary), GetBoundary(boundary, _request.MultipartFormQuoteBoundary))); + contentType.Parameters.Add(new NameValueHeaderValue(nameof(boundary), GetBoundary(boundary, request.MultipartFormQuoteBoundary))); mpContent.Headers.ContentType = contentType; return mpContent; } @@ -142,7 +134,7 @@ void AddBody(bool hasPostParameters, BodyParameter bodyParameter) { var bodyContent = Serialize(bodyParameter); // we need to send the body - if (hasPostParameters || _request.HasFiles() || BodyShouldBeMultipartForm(bodyParameter) || _request.AlwaysMultipartFormData) { + if (hasPostParameters || request.HasFiles() || BodyShouldBeMultipartForm(bodyParameter) || request.AlwaysMultipartFormData) { // here we must use multipart form data var mpContent = Content as MultipartFormDataContent ?? CreateMultipartFormDataContent(); var ct = bodyContent.Headers.ContentType?.MediaType; @@ -159,7 +151,7 @@ void AddBody(bool hasPostParameters, BodyParameter bodyParameter) { Content = bodyContent; } - if (_client.Options.DisableCharset) { + if (client.Options.DisableCharset) { Content.Headers.ContentType!.CharSet = ""; } } @@ -173,16 +165,16 @@ void AddPostParameters(GetOrPostParameter[] postParameters) { var parameterName = postParameter.Name!; mpContent.Add( - new StringContent(postParameter.Value?.ToString() ?? string.Empty, _client.Options.Encoding, postParameter.ContentType.Value), - _request.MultipartFormQuoteParameters ? $"\"{parameterName}\"" : parameterName + new StringContent(postParameter.Value?.ToString() ?? string.Empty, client.Options.Encoding, postParameter.ContentType.Value), + request.MultipartFormQuoteParameters ? $"\"{parameterName}\"" : parameterName ); } } else { - var encodedItems = postParameters.Select(x => $"{x.Name!.UrlEncode()}={x.Value?.ToString()!.UrlEncode() ?? string.Empty}"); - var encodedContent = new StringContent(encodedItems.JoinToString("&"), _client.Options.Encoding, ContentType.FormUrlEncoded.Value); + var encodedItems = postParameters.Select(x => $"{x.Name!.UrlEncode()}={x.Value?.ToString().UrlEncode() ?? string.Empty}"); + var encodedContent = new StringContent(encodedItems.JoinToString("&"), client.Options.Encoding, ContentType.FormUrlEncoded.Value); - if (_client.Options.DisableCharset) { + if (client.Options.DisableCharset) { encodedContent.Headers.ContentType!.CharSet = ""; } @@ -204,6 +196,7 @@ void AddHeaders() { } contentHeaders.ForEach(AddHeader); + return; void AddHeader(HeaderParameter parameter) { var parameterStringValue = parameter.Value!.ToString(); @@ -218,7 +211,7 @@ void AddHeader(HeaderParameter parameter) { string GetContentTypeHeader(string contentType) => Content is MultipartFormDataContent - ? $"{contentType}; boundary={GetBoundary(GetOrSetFormBoundary(), _request.MultipartFormQuoteBoundary)}" + ? $"{contentType}; boundary={GetBoundary(GetOrSetFormBoundary(), request.MultipartFormQuoteBoundary)}" : contentType; } @@ -231,4 +224,4 @@ public void Dispose() { _streams.ForEach(x => x.Dispose()); Content?.Dispose(); } -} +} \ No newline at end of file diff --git a/src/RestSharp/Request/RestRequest.cs b/src/RestSharp/Request/RestRequest.cs index 283a440e7..38251e846 100644 --- a/src/RestSharp/Request/RestRequest.cs +++ b/src/RestSharp/Request/RestRequest.cs @@ -48,12 +48,14 @@ public RestRequest(string? resource, Method method = Method.Get) : this() { var queryStringStart = Resource.IndexOf('?'); - if (queryStringStart >= 0 && Resource.IndexOf('=') > queryStringStart) { - var queryParams = ParseQuery(Resource.Substring(queryStringStart + 1)); - Resource = Resource.Substring(0, queryStringStart); + if (queryStringStart < 0 || Resource.IndexOf('=') <= queryStringStart) return; - foreach (var param in queryParams) this.AddQueryParameter(param.Key, param.Value, false); - } + var queryParams = ParseQuery(Resource.Substring(queryStringStart + 1)); + Resource = Resource.Substring(0, queryStringStart); + + foreach (var param in queryParams) this.AddQueryParameter(param.Key, param.Value, false); + + return; static IEnumerable> ParseQuery(string query) => query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries) diff --git a/src/RestSharp/Response/RestResponse.cs b/src/RestSharp/Response/RestResponse.cs index 0240946e0..68290b28d 100644 --- a/src/RestSharp/Response/RestResponse.cs +++ b/src/RestSharp/Response/RestResponse.cs @@ -26,7 +26,7 @@ namespace RestSharp; /// /// Type of data to deserialize to [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "()}")] -public class RestResponse : RestResponse { +public class RestResponse(RestRequest request) : RestResponse(request) { /// /// Deserialized entity data /// @@ -52,15 +52,13 @@ public static RestResponse FromResponse(RestResponse response) StatusDescription = response.StatusDescription, RootElement = response.RootElement }; - - public RestResponse(RestRequest request) : base(request) { } } /// /// Container for data sent back from API /// [DebuggerDisplay($"{{{nameof(DebuggerDisplay)}()}}")] -public class RestResponse : RestResponseBase { +public class RestResponse(RestRequest request) : RestResponseBase(request) { internal static async Task FromHttpResponse( HttpResponseMessage httpResponse, RestRequest request, @@ -103,9 +101,7 @@ async Task GetDefaultResponse() { } } - public RestResponse(RestRequest request) : base(request) { } - - public RestResponse() : base(new RestRequest()) { } + public RestResponse() : this(new RestRequest()) { } } public delegate ResponseStatus CalculateResponseStatus(HttpResponseMessage httpResponse); diff --git a/src/RestSharp/Serializers/DeseralizationException.cs b/src/RestSharp/Serializers/DeseralizationException.cs index 61c590287..6bb193edd 100644 --- a/src/RestSharp/Serializers/DeseralizationException.cs +++ b/src/RestSharp/Serializers/DeseralizationException.cs @@ -13,12 +13,11 @@ // limitations under the License. // ReSharper disable once CheckNamespace -namespace RestSharp; -public class DeserializationException : Exception { - public DeserializationException(RestResponse response, Exception innerException) - : base("Error occured while deserializing the response", innerException) - => Response = response; +namespace RestSharp; - public RestResponse Response { get; } +public class DeserializationException(RestResponse response, Exception innerException) + : Exception("Error occured while deserializing the response", innerException) { + [PublicAPI] + public RestResponse Response { get; } = response; } \ No newline at end of file diff --git a/src/RestSharp/Serializers/RestSerializers.cs b/src/RestSharp/Serializers/RestSerializers.cs index e2379030b..0cbf978e2 100644 --- a/src/RestSharp/Serializers/RestSerializers.cs +++ b/src/RestSharp/Serializers/RestSerializers.cs @@ -19,12 +19,9 @@ namespace RestSharp.Serializers; -public class RestSerializers { +public class RestSerializers(Dictionary records) { [PublicAPI] - public IReadOnlyDictionary Serializers { get; } - - public RestSerializers(Dictionary records) - => Serializers = new ReadOnlyDictionary(records); + public IReadOnlyDictionary Serializers { get; } = new ReadOnlyDictionary(records); public RestSerializers(SerializerConfig config) : this(config.Serializers) { } diff --git a/test/RestSharp.Tests.Serializers.Xml/SampleClasses/twitter.cs b/test/RestSharp.Tests.Serializers.Xml/SampleClasses/twitter.cs index df64ad212..cd02cc6ad 100644 --- a/test/RestSharp.Tests.Serializers.Xml/SampleClasses/twitter.cs +++ b/test/RestSharp.Tests.Serializers.Xml/SampleClasses/twitter.cs @@ -1,6 +1,8 @@ using RestSharp.Serializers; // ReSharper disable InconsistentNaming // ReSharper disable UnusedMember.Global +#pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. +#pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. #pragma warning disable CS8981 namespace RestSharp.Tests.Serializers.Xml.SampleClasses; From 78d7d0bebf119b1806b0b01753d936f5be4b1c16 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Tue, 2 Apr 2024 17:45:19 +0200 Subject: [PATCH 09/24] Fix the readme issue --- docs/serialization.md | 28 ++++++++++++++++++- src/RestSharp.Serializers.CsvHelper/README.md | 2 ++ .../RestSharp.Serializers.CsvHelper.csproj | 3 ++ .../README.md | 19 +++++++++++++ ...estSharp.Serializers.NewtonsoftJson.csproj | 3 ++ src/RestSharp.Serializers.Xml/README.md | 22 +++++++++++++++ .../RestSharp.Serializers.Xml.csproj | 3 ++ 7 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/RestSharp.Serializers.CsvHelper/README.md create mode 100644 src/RestSharp.Serializers.NewtonsoftJson/README.md create mode 100644 src/RestSharp.Serializers.Xml/README.md diff --git a/docs/serialization.md b/docs/serialization.md index a9458523d..cf8d8387b 100644 --- a/docs/serialization.md +++ b/docs/serialization.md @@ -37,7 +37,10 @@ In previous versions of RestSharp, the default XML serializer was a custom RestS You can add it back if necessary by installing the package and adding it to the client: ```csharp -client.UseXmlSerializer(); +var client = new RestClient( + options, + configureSerialization: s => s.UseXmlSerializer() +); ``` As before, you can supply three optional arguments for a custom namespace, custom root element, and if you want to use `SerializeAs` and `DeserializeAs` attributed. @@ -77,6 +80,29 @@ JsonSerializerSettings DefaultSettings = new JsonSerializerSettings { If you need to use different settings, you can supply your instance of `JsonSerializerSettings` as a parameter for the extension method. +## CSV + +A separate package `RestSharp.Serializers.CsvHelper` provides a CSV serializer for RestSharp. It is based on the +`CsvHelper` library. + +Use the extension method provided by the package to configure the client: + +```csharp +var client = new RestClient( + options, + configureSerialization: s => s.UseCsvHelper() +); +``` + +You can also supply your instance of `CsvConfiguration` as a parameter for the extension method. + +```csharp +var client = new RestClient( + options, + configureSerialization: s => s.UseCsvHelper(new CsvConfiguration(CultureInfo.InvariantCulture) {...}) +); +``` + ## Custom You can also implement your custom serializer. To support both serialization and diff --git a/src/RestSharp.Serializers.CsvHelper/README.md b/src/RestSharp.Serializers.CsvHelper/README.md new file mode 100644 index 000000000..79ac4c2a0 --- /dev/null +++ b/src/RestSharp.Serializers.CsvHelper/README.md @@ -0,0 +1,2 @@ +# About + diff --git a/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj b/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj index fcb03ecfe..9c503dd50 100644 --- a/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj +++ b/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj @@ -8,4 +8,7 @@ + + + diff --git a/src/RestSharp.Serializers.NewtonsoftJson/README.md b/src/RestSharp.Serializers.NewtonsoftJson/README.md new file mode 100644 index 000000000..5cc11a71b --- /dev/null +++ b/src/RestSharp.Serializers.NewtonsoftJson/README.md @@ -0,0 +1,19 @@ +# About + +This library allows using Newtonsoft.Json as a serializer for RestSharp instead of the default JSON serializer based +on `System.Text.Json`. + +# How to use + +The default JSON serializer uses `System.Text.Json`, which is a part of .NET since .NET 6. + +If you want to use Newtonsoft.Json, you can install the `RestSharp.Serializers.NewtonsoftJson` package and configure +the +client to use it: + +```csharp +var client = new RestClient( + options, + configureSerialization: s => s.UseNewtonsoftJson() +); +``` \ No newline at end of file diff --git a/src/RestSharp.Serializers.NewtonsoftJson/RestSharp.Serializers.NewtonsoftJson.csproj b/src/RestSharp.Serializers.NewtonsoftJson/RestSharp.Serializers.NewtonsoftJson.csproj index 66eb06cd0..45bcf8ca9 100644 --- a/src/RestSharp.Serializers.NewtonsoftJson/RestSharp.Serializers.NewtonsoftJson.csproj +++ b/src/RestSharp.Serializers.NewtonsoftJson/RestSharp.Serializers.NewtonsoftJson.csproj @@ -9,4 +9,7 @@ + + + diff --git a/src/RestSharp.Serializers.Xml/README.md b/src/RestSharp.Serializers.Xml/README.md new file mode 100644 index 000000000..63a7f53cc --- /dev/null +++ b/src/RestSharp.Serializers.Xml/README.md @@ -0,0 +1,22 @@ +# About + +This package is a custom XML serializer for RestSharp. It is based on the original XML serializer that was part of RestSharp but was removed in version 107.0.0. + +# How to use + +The default XML serializer in RestSharp is `DotNetXmlSerializer`, which uses `System.Xml.Serialization` library from . +NET. + +In previous versions of RestSharp, the default XML serializer was a custom RestSharp XML serializer. To make the +code library size smaller, the custom serializer was removed from RestSharp. + +You can add it back if necessary by installing the `RestSharp.Serializers.Xml` package and adding it to the client: + +```csharp +var client = new RestClient( + options, + configureSerialization: s => s.UseXmlSerializer() +); +``` + +As before, you can supply three optional arguments for a custom namespace, custom root element, and if you want to use `SerializeAs` and `DeserializeAs` attributed. diff --git a/src/RestSharp.Serializers.Xml/RestSharp.Serializers.Xml.csproj b/src/RestSharp.Serializers.Xml/RestSharp.Serializers.Xml.csproj index 1d6c8eaab..42ee74306 100644 --- a/src/RestSharp.Serializers.Xml/RestSharp.Serializers.Xml.csproj +++ b/src/RestSharp.Serializers.Xml/RestSharp.Serializers.Xml.csproj @@ -8,4 +8,7 @@ + + + From 74970c2a04fdc27f6ae37e3dd8d5e94cd811ebef Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Tue, 2 Apr 2024 20:51:03 +0200 Subject: [PATCH 10/24] - Added compatibility interceptor - Added docs - Refactored tests for the hooks - Updated packages --- Directory.Packages.props | 52 ++++++------ docs/.vuepress/config.js | 1 + docs/interceptors.md | 84 +++++++++++++++++++ src/Directory.Build.props | 4 + src/RestSharp.Serializers.CsvHelper/README.md | 22 +++++ .../Interceptors/CompatibilityInterceptor.cs | 40 +++++++++ src/RestSharp/Parameters/FileParameter.cs | 5 -- src/RestSharp/Request/RequestContent.cs | 4 +- src/RestSharp/Request/RestRequest.cs | 2 +- src/RestSharp/Response/RestResponseBase.cs | 2 +- src/RestSharp/RestClient.Async.cs | 4 + src/RestSharp/RestSharp.csproj | 7 +- src/RestSharp/Serializers/RestSerializers.cs | 15 ++-- test/RestSharp.InteractiveTests/Program.cs | 2 + .../HttpHeadersTests.cs | 29 ++++++- .../RequestBodyTests.cs | 10 +-- .../RequestTests.cs | 21 ++++- .../StatusCodeTests.cs | 22 +++-- 18 files changed, 266 insertions(+), 60 deletions(-) create mode 100644 docs/interceptors.md create mode 100644 src/RestSharp/Interceptors/CompatibilityInterceptor.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index e8bf74d26..70b600f6d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,39 +3,43 @@ true - [6.0.22,7) + [6.0.28,7) - 7.0.11 + 7.0.17 - 8.0.0-rc.1.23421.29 + 8.0.3 - - - - - - - - - - - - - - - + - - - - + + + + + + + + + + + + + + + + + + - - + + + + + + \ No newline at end of file diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 52aa3bf19..31d27d4b4 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -27,6 +27,7 @@ module.exports = { "usage.md", "serialization.md", "authenticators.md", + "interceptors.md", "error-handling.md" ] } diff --git a/docs/interceptors.md b/docs/interceptors.md new file mode 100644 index 000000000..86387fb3d --- /dev/null +++ b/docs/interceptors.md @@ -0,0 +1,84 @@ +--- +title: Interceptors +--- + +## Intercepting requests and responses + +Interceptors are a powerful feature of RestSharp that allows you to modify requests and responses before they are sent or received. You can use interceptors to add headers, modify the request body, or even cancel the request. You can also use interceptors to modify the response before it is returned to the caller. + +### Implementing an interceptor + +To implement an interceptor, you need to create a class that inherits the `Interceptor` base class. The base class implements all interceptor methods as virtual, so you can override them in your derived class. + +Methods that you can override are: +- `BeforeRequest(RestRequest request, CancellationToken cancellationToken)` +- `AfterRequest(RestResponse response, CancellationToken cancellationToken)` +- `BeforeHttpRequest(HttpRequestMessage requestMessage, CancellationToken cancellationToken)` +- `AfterHttpResponse(HttpResponseMessage responseMessage, CancellationToken cancellationToken)` +- `BeforeDeserialization(RestResponse response, CancellationToken cancellationToken)` + +All those functions must return a `ValueTask` instance. + +Here's an example of an interceptor that adds a header to a request: + +```csharp +// This interceptor adds a header to the request +// You'd not normally use this interceptor, as RestSharp already has a method +// to add headers to the request +class HeaderInterceptor(string headerName, string headerValue) : Interceptors.Interceptor { + public override ValueTask BeforeHttpRequest(HttpRequestMessage requestMessage, CancellationToken cancellationToken) { + requestMessage.Headers.Add(headerName, headerValue); + return ValueTask.CompletedTask; + } +} +``` + +Because interceptor functions return `ValueTask`, you can use `async` and `await` inside them. + +### Using an interceptor + +It's possible to add as many interceptors as you want, both to the client and to the request. The interceptors are executed in the order they were added. + +Adding interceptors to the client is done via the client options: + +```csharp +var options = new RestClientOptions("https://api.example.com") { + Interceptors = [new HeaderInterceptor("Authorization", token)] +}; +var client = new RestClient(options); +``` + +When you add an interceptor to the client, it will be executed for every request made by that client. + +You can also add an interceptor to a specific request: + +```csharp +var request = new RestRequest("resource") { + Interceptors = [new HeaderInterceptor("Authorization", token)] +}; +``` + +In this case, the interceptor will only be executed for that specific request. + +### Deprecation notice + +Interceptors aim to replace the existing request hooks available in RestSharp prior to version 111.0. Those hooks are marked with `Obsolete` attribute and will be removed in the future. If you are using those hooks, we recommend migrating to interceptors as soon as possible. + +To make the migration easier, RestSharp provides a class called `CompatibilityInterceptor`. It has properties for the hooks available in RestSharp 110.0 and earlier. You can use it to migrate your code to interceptors without changing the existing logic. + +For example, a code that uses `OnBeforeRequest` hook: + +```csharp +var request = new RestRequest("success"); +request.OnBeforeDeserialization += _ => throw new Exception(exceptionMessage); +``` + +Can be migrated to interceptors like this: + +```csharp +var request = new RestRequest("success") { + Interceptors = [new CompatibilityInterceptor { + OnBeforeDeserialization = _ => throw new Exception(exceptionMessage) + }] +}; +``` \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e9d773e8d..3263cbade 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -18,6 +18,10 @@ README.md + + true + true + diff --git a/src/RestSharp.Serializers.CsvHelper/README.md b/src/RestSharp.Serializers.CsvHelper/README.md index 79ac4c2a0..3c7d3815c 100644 --- a/src/RestSharp.Serializers.CsvHelper/README.md +++ b/src/RestSharp.Serializers.CsvHelper/README.md @@ -1,2 +1,24 @@ # About +The `RestSharp.Serializers.CsvHelper` library provides a CSV serializer for RestSharp. It is based on the +`CsvHelper` library. + +# How to use + +Use the extension method provided by the package to configure the client: + +```csharp +var client = new RestClient( + options, + configureSerialization: s => s.UseCsvHelper() +); +``` + +You can also supply your instance of `CsvConfiguration` as a parameter for the extension method. + +```csharp +var client = new RestClient( + options, + configureSerialization: s => s.UseCsvHelper(new CsvConfiguration(CultureInfo.InvariantCulture) {...}) +); +``` diff --git a/src/RestSharp/Interceptors/CompatibilityInterceptor.cs b/src/RestSharp/Interceptors/CompatibilityInterceptor.cs new file mode 100644 index 000000000..f39b2f361 --- /dev/null +++ b/src/RestSharp/Interceptors/CompatibilityInterceptor.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace RestSharp.Interceptors; + +/// +/// This class allows easier migration of legacy request hooks to interceptors. +/// +public class CompatibilityInterceptor : Interceptor { + public Action? OnBeforeDeserialization { get; set; } + public Func? OnBeforeRequest { get; set; } + public Func? OnAfterRequest { get; set; } + + /// + public override ValueTask BeforeDeserialization(RestResponse response, CancellationToken cancellationToken) { + OnBeforeDeserialization?.Invoke(response); + return default; + } + + public override ValueTask BeforeHttpRequest(HttpRequestMessage requestMessage, CancellationToken cancellationToken) { + OnBeforeRequest?.Invoke(requestMessage); + return default; + } + + public override ValueTask AfterHttpRequest(HttpResponseMessage responseMessage, CancellationToken cancellationToken) { + OnAfterRequest?.Invoke(responseMessage); + return default; + } +} \ No newline at end of file diff --git a/src/RestSharp/Parameters/FileParameter.cs b/src/RestSharp/Parameters/FileParameter.cs index 5b58bf44d..dfb4d25d1 100644 --- a/src/RestSharp/Parameters/FileParameter.cs +++ b/src/RestSharp/Parameters/FileParameter.cs @@ -113,11 +113,6 @@ public static FileParameter FromFile( [PublicAPI] public class FileParameterOptions { - [Obsolete("Use DisableFilenameStar instead")] - public bool DisableFileNameStar { - get => DisableFilenameStar; - set => DisableFilenameStar = value; - } public bool DisableFilenameStar { get; set; } = true; public bool DisableFilenameEncoding { get; set; } } diff --git a/src/RestSharp/Request/RequestContent.cs b/src/RestSharp/Request/RequestContent.cs index 5f8f26151..a38b46036 100644 --- a/src/RestSharp/Request/RequestContent.cs +++ b/src/RestSharp/Request/RequestContent.cs @@ -83,7 +83,7 @@ StreamContent ToStreamContent(FileParameter fileParameter) { HttpContent Serialize(BodyParameter body) { return body.DataFormat switch { - DataFormat.None => new StringContent(body.Value!.ToString(), client.Options.Encoding, body.ContentType.Value), + DataFormat.None => new StringContent(body.Value!.ToString()!, client.Options.Encoding, body.ContentType.Value), DataFormat.Binary => GetBinary(), _ => GetSerialized() }; @@ -171,7 +171,7 @@ void AddPostParameters(GetOrPostParameter[] postParameters) { } } else { - var encodedItems = postParameters.Select(x => $"{x.Name!.UrlEncode()}={x.Value?.ToString().UrlEncode() ?? string.Empty}"); + var encodedItems = postParameters.Select(x => $"{x.Name!.UrlEncode()}={x.Value?.ToString()?.UrlEncode() ?? string.Empty}"); var encodedContent = new StringContent(encodedItems.JoinToString("&"), client.Options.Encoding, ContentType.FormUrlEncoded.Value); if (client.Options.DisableCharset) { diff --git a/src/RestSharp/Request/RestRequest.cs b/src/RestSharp/Request/RestRequest.cs index 38251e846..16be4837d 100644 --- a/src/RestSharp/Request/RestRequest.cs +++ b/src/RestSharp/Request/RestRequest.cs @@ -185,7 +185,7 @@ public RestRequest(Uri resource, Method method = Method.Get) [Obsolete("Use Interceptors instead")] public Func? OnAfterRequest { get; set; } - internal void IncreaseNumAttempts() => Attempts++; + internal void IncreaseNumberOfAttempts() => Attempts++; /// /// How many attempts were made to send this Request diff --git a/src/RestSharp/Response/RestResponseBase.cs b/src/RestSharp/Response/RestResponseBase.cs index 6bbe710b0..0bbff2d55 100644 --- a/src/RestSharp/Response/RestResponseBase.cs +++ b/src/RestSharp/Response/RestResponseBase.cs @@ -28,7 +28,7 @@ public abstract class RestResponseBase { protected RestResponseBase(RestRequest request) { ResponseStatus = ResponseStatus.None; Request = request; - Request.IncreaseNumAttempts(); + Request.IncreaseNumberOfAttempts(); } /// diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs index ec93bd803..0e9c80c9e 100644 --- a/src/RestSharp/RestClient.Async.cs +++ b/src/RestSharp/RestClient.Async.cs @@ -130,7 +130,9 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo .AddCookieHeaders(url, Options.CookieContainer); message.AddHeaders(headers); +#pragma warning disable CS0618 // Type or member is obsolete if (request.OnBeforeRequest != null) await request.OnBeforeRequest(message).ConfigureAwait(false); +#pragma warning restore CS0618 // Type or member is obsolete await OnBeforeHttpRequest(request, message, cancellationToken).ConfigureAwait(false); try { @@ -148,7 +150,9 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo return new HttpResponse(null, url, null, ex, timeoutCts.Token); } +#pragma warning disable CS0618 // Type or member is obsolete if (request.OnAfterRequest != null) await request.OnAfterRequest(responseMessage).ConfigureAwait(false); +#pragma warning restore CS0618 // Type or member is obsolete await OnAfterHttpRequest(request, responseMessage, cancellationToken).ConfigureAwait(false); return new HttpResponse(responseMessage, url, cookieContainer, null, timeoutCts.Token); } diff --git a/src/RestSharp/RestSharp.csproj b/src/RestSharp/RestSharp.csproj index 83e8c4bd1..719d6ceb9 100644 --- a/src/RestSharp/RestSharp.csproj +++ b/src/RestSharp/RestSharp.csproj @@ -5,17 +5,16 @@ - - - - + + + diff --git a/src/RestSharp/Serializers/RestSerializers.cs b/src/RestSharp/Serializers/RestSerializers.cs index dfca57a39..4216c0452 100644 --- a/src/RestSharp/Serializers/RestSerializers.cs +++ b/src/RestSharp/Serializers/RestSerializers.cs @@ -36,8 +36,10 @@ internal async ValueTask> Deserialize(RestRequest request, Re var response = RestResponse.FromResponse(raw); try { - await OnBeforeDeserialization(response, cancellationToken).ConfigureAwait(false); + await OnBeforeDeserialization(raw, cancellationToken).ConfigureAwait(false); +#pragma warning disable CS0618 // Type or member is obsolete request.OnBeforeDeserialization?.Invoke(raw); +#pragma warning restore CS0618 // Type or member is obsolete response.Data = DeserializeContent(raw); } catch (Exception ex) { @@ -80,13 +82,14 @@ static async ValueTask OnBeforeDeserialization(RestResponse response, Cancellati // This can happen when a request returns for example a 404 page instead of the requested JSON/XML resource var deserializer = GetContentDeserializer(response); - if (deserializer is IXmlDeserializer xml && response.Request is RestXmlRequest xmlRequest) { - if (xmlRequest.XmlNamespace.IsNotEmpty()) xml.Namespace = xmlRequest.XmlNamespace!; + if (deserializer is not IXmlDeserializer xml || response.Request is not RestXmlRequest xmlRequest) + return deserializer != null ? deserializer.Deserialize(response) : default; - if (xml is IWithDateFormat withDateFormat && xmlRequest.DateFormat.IsNotEmpty()) withDateFormat.DateFormat = xmlRequest.DateFormat!; - } + if (xmlRequest.XmlNamespace.IsNotEmpty()) xml.Namespace = xmlRequest.XmlNamespace!; + + if (xml is IWithDateFormat withDateFormat && xmlRequest.DateFormat.IsNotEmpty()) withDateFormat.DateFormat = xmlRequest.DateFormat!; - return deserializer != null ? deserializer.Deserialize(response) : default; + return deserializer.Deserialize(response); } IDeserializer? GetContentDeserializer(RestResponseBase response) { diff --git a/test/RestSharp.InteractiveTests/Program.cs b/test/RestSharp.InteractiveTests/Program.cs index cee0cbbcc..78c36c141 100644 --- a/test/RestSharp.InteractiveTests/Program.cs +++ b/test/RestSharp.InteractiveTests/Program.cs @@ -8,12 +8,14 @@ return; +#pragma warning disable CS0162 // Unreachable code detected var keys = new AuthenticationTests.TwitterKeys { ConsumerKey = Prompt("Consumer key"), ConsumerSecret = Prompt("Consumer secret"), }; await AuthenticationTests.Can_Authenticate_With_OAuth_Async_With_Callback(keys); +#pragma warning restore CS0162 // Unreachable code detected static string Prompt(string message) { Console.Write($"{message}: "); diff --git a/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs b/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs index c9a2c1338..8abf40c53 100644 --- a/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs +++ b/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs @@ -1,13 +1,32 @@ using System.Net; using RestSharp.Tests.Integrated.Server; -namespace RestSharp.Tests.Integrated; +namespace RestSharp.Tests.Integrated; [Collection(nameof(TestServerCollection))] public class HttpHeadersTests(TestServerFixture fixture) { readonly RestClient _client = new(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = true }); [Fact] + public async Task Ensure_headers_correctly_set_in_the_interceptor() { + const string headerName = "HeaderName"; + const string headerValue = "HeaderValue"; + + var request = new RestRequest("/headers") { + Interceptors = [new HeaderInterceptor(headerName, headerValue)] + }; + + // Run + var response = await _client.ExecuteAsync(request); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var header = response.Data!.First(x => x.Name == headerName); + header.Should().NotBeNull(); + header.Value.Should().Be(headerValue); + } + + [Fact, Obsolete("Obsolete")] public async Task Ensure_headers_correctly_set_in_the_hook() { const string headerName = "HeaderName"; const string headerValue = "HeaderValue"; @@ -42,6 +61,7 @@ public async Task Should_use_both_default_and_request_headers() { var response = await _client.ExecuteAsync(request); CheckHeader(defaultHeader); CheckHeader(requestHeader); + return; void CheckHeader(Header header) { var h = response.Data!.First(x => x.Name == header.Name); @@ -51,4 +71,11 @@ void CheckHeader(Header header) { } record Header(string Name, string Value); + + class HeaderInterceptor(string headerName, string headerValue) : Interceptors.Interceptor { + public override ValueTask BeforeHttpRequest(HttpRequestMessage requestMessage, CancellationToken cancellationToken) { + requestMessage.Headers.Add(headerName, headerValue); + return ValueTask.CompletedTask; + } + } } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/RequestBodyTests.cs b/test/RestSharp.Tests.Integrated/RequestBodyTests.cs index 27bf6f271..2f15de0aa 100644 --- a/test/RestSharp.Tests.Integrated/RequestBodyTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestBodyTests.cs @@ -3,7 +3,7 @@ namespace RestSharp.Tests.Integrated; -public class RequestBodyTests(RequestBodyFixture fixture, ITestOutputHelper output) : IClassFixture { +public class RequestBodyTests(RequestBodyFixture fixture) : IClassFixture { readonly SimpleServer _server = fixture.Server; const string NewLine = "\r\n"; @@ -15,13 +15,7 @@ public class RequestBodyTests(RequestBodyFixture fixture, ITestOutputHelper outp async Task AssertBody(Method method, bool disableCharset = false) { var options = new RestClientOptions(_server.Url) { DisableCharset = disableCharset }; var client = new RestClient(options); - - var request = new RestRequest(RequestBodyCapturer.Resource, method) { - OnBeforeRequest = async m => { - output.WriteLine(m.ToString()); - output.WriteLine(await m.Content!.ReadAsStringAsync()); - } - }; + var request = new RestRequest(RequestBodyCapturer.Resource, method); const string bodyData = "abc123 foo bar baz BING!"; diff --git a/test/RestSharp.Tests.Integrated/RequestTests.cs b/test/RestSharp.Tests.Integrated/RequestTests.cs index fa3655507..a4f07b092 100644 --- a/test/RestSharp.Tests.Integrated/RequestTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestTests.cs @@ -12,6 +12,20 @@ class Response { } [Fact] + public async Task Can_Handle_Exception_Thrown_By_Interceptor_BeforeDeserialization() { + const string exceptionMessage = "Thrown from OnBeforeDeserialization"; + + var request = new RestRequest("success") { + Interceptors = [new ThrowingInterceptor(exceptionMessage)] + }; + + var response = await _client.ExecuteAsync(request); + + Assert.Equal(exceptionMessage, response.ErrorMessage); + Assert.Equal(ResponseStatus.Error, response.ResponseStatus); + } + + [Fact, Obsolete("Obsolete")] public async Task Can_Handle_Exception_Thrown_By_OnBeforeDeserialization_Handler() { const string exceptionMessage = "Thrown from OnBeforeDeserialization"; @@ -72,4 +86,9 @@ public async Task Can_Delete_With_Response_Type_using_extension() { response!.Message.Should().Be("Works!"); } -} + + class ThrowingInterceptor(string errorMessage) : Interceptors.Interceptor { + public override ValueTask BeforeDeserialization(RestResponse response, CancellationToken cancellationToken) + => throw new Exception(errorMessage); + } +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/StatusCodeTests.cs b/test/RestSharp.Tests.Integrated/StatusCodeTests.cs index e78a386db..0c020ccf3 100644 --- a/test/RestSharp.Tests.Integrated/StatusCodeTests.cs +++ b/test/RestSharp.Tests.Integrated/StatusCodeTests.cs @@ -1,4 +1,5 @@ using System.Net; +using RestSharp.Interceptors; using RestSharp.Serializers.Xml; using RestSharp.Tests.Shared.Extensions; using RestSharp.Tests.Shared.Fixtures; @@ -49,9 +50,12 @@ public async Task Handles_Default_Root_Element_On_No_Error() { RootElement = "Success" }; - request.OnBeforeDeserialization = resp => { - if (resp.StatusCode == HttpStatusCode.NotFound) request.RootElement = "Error"; + var interceptor = new CompatibilityInterceptor { + OnBeforeDeserialization = resp => { + if (resp.StatusCode == HttpStatusCode.NotFound) request.RootElement = "Error"; + } }; + request.Interceptors = [interceptor]; var response = await _client.ExecuteAsync(request); @@ -64,10 +68,14 @@ public async Task Handles_Different_Root_Element_On_Http_Error() { _server.SetHandler(Handlers.Generic()); var request = new RestRequest("error") { - RootElement = "Success", - OnBeforeDeserialization = resp => { - if (resp.StatusCode == HttpStatusCode.BadRequest) resp.RootElement = "Error"; - } + RootElement = "Success", + Interceptors = [ + new CompatibilityInterceptor { + OnBeforeDeserialization = resp => { + if (resp.StatusCode == HttpStatusCode.BadRequest) resp.RootElement = "Error"; + } + } + ] }; var response = await _client.ExecuteAsync(request); @@ -178,4 +186,4 @@ void success(HttpListenerContext context) public class TestResponse { public string Message { get; set; } = null!; -} +} \ No newline at end of file From 7d8f957c52ca91f9d0232d6732aa4c2a6cb5033f Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Tue, 2 Apr 2024 20:56:56 +0200 Subject: [PATCH 11/24] Add permissions to publish test results --- .github/workflows/pull-request.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 28ad59e38..2a094274f 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -4,6 +4,7 @@ on: [pull_request] permissions: contents: read + checks: write jobs: test-windows: @@ -18,7 +19,6 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: '8.0' - dotnet-quality: 'preview' - name: Run tests run: dotnet test @@ -35,7 +35,6 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: '8.0' - dotnet-quality: 'preview' - name: Run tests run: dotnet test From 56a116165608fa91eb4644e73671dbd662bad8ce Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Wed, 3 Apr 2024 16:04:31 +0200 Subject: [PATCH 12/24] Refactoring tests to WireMock --- Directory.Packages.props | 87 +++++------ .../CompressionTests.cs | 62 ++++---- .../DefaultParameterTests.cs | 33 ++--- .../Fixtures/RequestBodyCapturer.cs | 29 ++++ .../Fixtures/RequestBodyFixture.cs | 11 -- .../Fixtures/WireMockExtensions.cs | 17 +++ .../JsonBodyTests.cs | 31 ++-- .../MultipartFormDataTests.cs | 68 ++++----- .../NonProtocolExceptionHandlingTests.cs | 33 ++--- .../{RequestHeadTests.cs => NtlmTests.cs} | 2 +- .../RequestBodyTests.cs | 52 +++---- .../ResourceStringParametersTests.cs | 27 ++-- .../RestSharp.Tests.Integrated.csproj | 7 + .../StatusCodeTests.cs | 137 ++---------------- .../TestResponse.cs | 5 + .../XmlResponseTests.cs | 126 ++++++++++++++++ .../Fixtures/Handlers.cs | 6 - .../Fixtures/RequestBodyCapturer.cs | 23 --- .../Fixtures/SimpleServer.cs | 8 +- .../Fixtures/TestHttpServer.cs | 2 +- 20 files changed, 396 insertions(+), 370 deletions(-) create mode 100644 test/RestSharp.Tests.Integrated/Fixtures/RequestBodyCapturer.cs delete mode 100644 test/RestSharp.Tests.Integrated/Fixtures/RequestBodyFixture.cs create mode 100644 test/RestSharp.Tests.Integrated/Fixtures/WireMockExtensions.cs rename test/RestSharp.Tests.Integrated/{RequestHeadTests.cs => NtlmTests.cs} (98%) create mode 100644 test/RestSharp.Tests.Integrated/TestResponse.cs create mode 100644 test/RestSharp.Tests.Integrated/XmlResponseTests.cs delete mode 100644 test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 70b600f6d..265862dd2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,45 +1,46 @@ - - true - - - [6.0.28,7) - - - 7.0.17 - - - 8.0.3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + true + + + [6.0.28,7) + + + 7.0.17 + + + 8.0.3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/CompressionTests.cs b/test/RestSharp.Tests.Integrated/CompressionTests.cs index 42896c08f..831f2172c 100644 --- a/test/RestSharp.Tests.Integrated/CompressionTests.cs +++ b/test/RestSharp.Tests.Integrated/CompressionTests.cs @@ -1,59 +1,69 @@ using System.IO.Compression; -using System.Net; +using RestSharp.Extensions; using RestSharp.Tests.Shared.Extensions; -using RestSharp.Tests.Shared.Fixtures; -namespace RestSharp.Tests.Integrated; +namespace RestSharp.Tests.Integrated; public class CompressionTests { - static Action GzipEchoValue(string value) - => context => { - context.Response.Headers.Add("Content-encoding", "gzip"); + static async Task GetBody(Func getStream, string value) { + using var memoryStream = new MemoryStream(); - using var gzip = new GZipStream(context.Response.OutputStream, CompressionMode.Compress, true); + await using (var stream = getStream(memoryStream)) { + stream.WriteStringUtf8(value); + } - gzip.WriteStringUtf8(value); - }; - - static Action DeflateEchoValue(string value) - => context => { - context.Response.Headers.Add("Content-encoding", "deflate"); - - using var gzip = new DeflateStream(context.Response.OutputStream, CompressionMode.Compress, true); + memoryStream.Seek(0, SeekOrigin.Begin); + var body = await memoryStream.ReadAsBytes(default); + return body; + } - gzip.WriteStringUtf8(value); - }; + static void ConfigureServer(WireMockServer server, byte[] body, string encoding) + => server + .Given(Request.Create().WithPath("/").UsingGet()) + .RespondWith(Response.Create().WithBody(body).WithHeader("Content-Encoding", encoding)); [Fact] public async Task Can_Handle_Deflate_Compressed_Content() { - using var server = SimpleServer.Create(DeflateEchoValue("This is some deflated content")); + const string value = "This is some deflated content"; + using var server = WireMockServer.Start(); + + var body = await GetBody(s => new DeflateStream(s, CompressionMode.Compress, true), value); + ConfigureServer(server, body, "deflate"); - var client = new RestClient(server.Url); + var client = new RestClient(server.Url!); var request = new RestRequest(""); var response = await client.ExecuteAsync(request); - Assert.Equal("This is some deflated content", response.Content); + response.Content.Should().Be(value); } [Fact] public async Task Can_Handle_Gzip_Compressed_Content() { - using var server = SimpleServer.Create(GzipEchoValue("This is some gzipped content")); + const string value = "This is some gzipped content"; + using var server = WireMockServer.Start(); + + var body = await GetBody(s => new GZipStream(s, CompressionMode.Compress, true), value); + ConfigureServer(server, body, "gzip"); - var client = new RestClient(server.Url); + var client = new RestClient(server.Url!); var request = new RestRequest(""); var response = await client.ExecuteAsync(request); - Assert.Equal("This is some gzipped content", response.Content); + response.Content.Should().Be(value); } [Fact] public async Task Can_Handle_Uncompressed_Content() { - using var server = SimpleServer.Create(Handlers.EchoValue("This is some sample content")); + const string value = "This is some sample content"; + using var server = WireMockServer.Start(); + server + .Given(Request.Create().WithPath("/").UsingGet()) + .RespondWith(Response.Create().WithBody(value)); - var client = new RestClient(server.Url); + var client = new RestClient(server.Url!); var request = new RestRequest(""); var response = await client.ExecuteAsync(request); - Assert.Equal("This is some sample content", response.Content); + response.Content.Should().Be(value); } } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs b/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs index d2b0208a2..b371090fc 100644 --- a/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs +++ b/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs @@ -1,51 +1,44 @@ -using System.Net; +using RestSharp.Tests.Integrated.Fixtures; using RestSharp.Tests.Integrated.Server; -using RestSharp.Tests.Shared.Fixtures; namespace RestSharp.Tests.Integrated; [Collection(nameof(TestServerCollection))] -public sealed class DefaultParameterTests(TestServerFixture fixture) : IDisposable { - readonly SimpleServer _server = SimpleServer.Create(RequestHandler.Handle); +public sealed class DefaultParameterTests : IDisposable { + readonly WireMockServer _server = WireMockServer.Start(); + readonly RequestBodyCapturer _capturer; + + public DefaultParameterTests() => _capturer = _server.ConfigureBodyCapturer(Method.Get, false); public void Dispose() => _server.Dispose(); [Fact] public async Task Should_add_default_and_request_query_get_parameters() { - var client = new RestClient(_server.Url).AddDefaultParameter("foo", "bar", ParameterType.QueryString); - var request = new RestRequest().AddParameter("foo1", "bar1", ParameterType.QueryString); + var client = new RestClient(_server.Url!).AddDefaultParameter("foo", "bar", ParameterType.QueryString); + var request = new RestRequest().AddParameter("foo1", "bar1", ParameterType.QueryString); await client.GetAsync(request); - var query = RequestHandler.Url!.Query; + var query = _capturer.Url!.Query; query.Should().Contain("foo=bar"); query.Should().Contain("foo1=bar1"); } [Fact] public async Task Should_add_default_and_request_url_get_parameters() { - var client = new RestClient($"{_server.Url}{{foo}}/").AddDefaultParameter("foo", "bar", ParameterType.UrlSegment); + var client = new RestClient($"{_server.Url}/{{foo}}/").AddDefaultParameter("foo", "bar", ParameterType.UrlSegment); var request = new RestRequest("{foo1}").AddParameter("foo1", "bar1", ParameterType.UrlSegment); await client.GetAsync(request); - RequestHandler.Url!.Segments.Should().BeEquivalentTo("/", "bar/", "bar1"); + _capturer.Url!.Segments.Should().BeEquivalentTo("/", "bar/", "bar1"); } [Fact] public async Task Should_not_throw_exception_when_name_is_null() { - var client = new RestClient($"{fixture.Server.Url}/request-echo").AddDefaultParameter("foo", "bar", ParameterType.UrlSegment); + var client = new RestClient($"{_server.Url}/request-echo").AddDefaultParameter("foo", "bar", ParameterType.UrlSegment); var request = new RestRequest("{foo1}").AddParameter(null, "value", ParameterType.RequestBody); await client.ExecuteAsync(request); } - - static class RequestHandler { - public static Uri? Url { get; private set; } - - public static void Handle(HttpListenerContext context) { - Url = context.Request.Url; - Handlers.Echo(context); - } - } -} +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/Fixtures/RequestBodyCapturer.cs b/test/RestSharp.Tests.Integrated/Fixtures/RequestBodyCapturer.cs new file mode 100644 index 000000000..d42117797 --- /dev/null +++ b/test/RestSharp.Tests.Integrated/Fixtures/RequestBodyCapturer.cs @@ -0,0 +1,29 @@ +namespace RestSharp.Tests.Integrated.Fixtures; + +class RequestBodyCapturer { + public const string Resource = "/capture"; + + public string? ContentType { get; private set; } + public bool HasBody { get; private set; } + public string? Body { get; private set; } + public Uri? Url { get; private set; } + + public bool CaptureBody(string? content) { + Body = content; + HasBody = !string.IsNullOrWhiteSpace(content); + return true; + } + + public bool CaptureHeaders(IDictionary headers) { + if (headers.TryGetValue("Content-Type", out var contentType)) { + ContentType = contentType[0]; + } + + return true; + } + + public bool CaptureUrl(string url) { + Url = new Uri(url); + return true; + } +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/Fixtures/RequestBodyFixture.cs b/test/RestSharp.Tests.Integrated/Fixtures/RequestBodyFixture.cs deleted file mode 100644 index 367563729..000000000 --- a/test/RestSharp.Tests.Integrated/Fixtures/RequestBodyFixture.cs +++ /dev/null @@ -1,11 +0,0 @@ -using RestSharp.Tests.Shared.Fixtures; - -namespace RestSharp.Tests.Integrated.Fixtures; - -public sealed class RequestBodyFixture : IDisposable { - public SimpleServer Server { get; } - - public RequestBodyFixture() => Server = SimpleServer.Create(Handlers.Generic()); - - public void Dispose() => Server.Dispose(); -} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/Fixtures/WireMockExtensions.cs b/test/RestSharp.Tests.Integrated/Fixtures/WireMockExtensions.cs new file mode 100644 index 000000000..26a3f85a5 --- /dev/null +++ b/test/RestSharp.Tests.Integrated/Fixtures/WireMockExtensions.cs @@ -0,0 +1,17 @@ +namespace RestSharp.Tests.Integrated.Fixtures; + +static class WireMockExtensions { + public static RequestBodyCapturer ConfigureBodyCapturer(this WireMockServer server, Method method, bool usePath = true) { + var capturer = new RequestBodyCapturer(); + + var requestBuilder = Request + .Create() + .WithPath(usePath ? RequestBodyCapturer.Resource : "/") + .WithUrl(capturer.CaptureUrl) + .WithBody(capturer.CaptureBody) + .WithHeader(capturer.CaptureHeaders) + .UsingMethod(method.ToString().ToUpper()); + server.Given(requestBuilder).RespondWith(Response.Create().WithStatusCode(200)); + return capturer; + } +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/JsonBodyTests.cs b/test/RestSharp.Tests.Integrated/JsonBodyTests.cs index 8a9935dc3..8d2025eae 100644 --- a/test/RestSharp.Tests.Integrated/JsonBodyTests.cs +++ b/test/RestSharp.Tests.Integrated/JsonBodyTests.cs @@ -1,37 +1,41 @@ using System.Text.Json; using RestSharp.Tests.Integrated.Fixtures; -using RestSharp.Tests.Shared.Fixtures; namespace RestSharp.Tests.Integrated; #pragma warning disable xUnit1033 -public sealed class JsonBodyTests(RequestBodyFixture fixture) : IClassFixture { - readonly SimpleServer _server = fixture.Server; - readonly RestClient _client = new(fixture.Server.Url); +public sealed class JsonBodyTests { + readonly WireMockServer _server = WireMockServer.Start(); + readonly RestClient _client; + + public JsonBodyTests() => _client = new RestClient(_server.Url!); [Fact] public async Task Query_Parameters_With_Json_Body() { + var capturer = _server.ConfigureBodyCapturer(Method.Put); + var request = new RestRequest(RequestBodyCapturer.Resource, Method.Put) .AddJsonBody(new { displayName = "Display Name" }) .AddQueryParameter("key", "value"); await _client.ExecuteAsync(request); - RequestBodyCapturer.CapturedUrl.ToString().Should().Be($"{_server.Url}Capture?key=value"); - RequestBodyCapturer.CapturedContentType.Should().Be("application/json; charset=utf-8"); - RequestBodyCapturer.CapturedEntityBody.Should().Be("{\"displayName\":\"Display Name\"}"); + capturer.ContentType.Should().Be("application/json; charset=utf-8"); + capturer.Body.Should().Be("{\"displayName\":\"Display Name\"}"); + capturer.Url.Should().Be($"{_server.Url}{RequestBodyCapturer.Resource}?key=value"); } [Fact] public async Task Add_JSON_body_JSON_string() { const string payload = "{\"displayName\":\"Display Name\"}"; - var request = new RestRequest(RequestBodyCapturer.Resource, Method.Post).AddJsonBody(payload); + var capturer = _server.ConfigureBodyCapturer(Method.Post); + var request = new RestRequest(RequestBodyCapturer.Resource, Method.Post).AddJsonBody(payload); await _client.ExecuteAsync(request); - RequestBodyCapturer.CapturedContentType.Should().Be("application/json; charset=utf-8"); - RequestBodyCapturer.CapturedEntityBody.Should().Be(payload); + capturer.ContentType.Should().Be("application/json; charset=utf-8"); + capturer.Body.Should().Be(payload); } [Fact] @@ -48,11 +52,12 @@ public async Task Add_JSON_body_string() { },"; var expected = JsonSerializer.Serialize(payload); + var capturer = _server.ConfigureBodyCapturer(Method.Post); var request = new RestRequest(RequestBodyCapturer.Resource, Method.Post).AddJsonBody(payload, true); await _client.ExecuteAsync(request); - RequestBodyCapturer.CapturedContentType.Should().Be("application/json; charset=utf-8"); - RequestBodyCapturer.CapturedEntityBody.Should().Be(expected); + capturer.ContentType.Should().Be("application/json; charset=utf-8"); + capturer.Body.Should().Be(expected); } -} +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs b/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs index 0dc147825..7223da777 100644 --- a/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs +++ b/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs @@ -1,9 +1,5 @@ -using System.Net; -using HttpTracer; +using HttpTracer; using RestSharp.Tests.Integrated.Fixtures; -using RestSharp.Tests.Shared.Fixtures; -#pragma warning disable CS8618 -#pragma warning disable CS8601 namespace RestSharp.Tests.Integrated; @@ -12,9 +8,10 @@ public sealed class MultipartFormDataTests : IDisposable { public MultipartFormDataTests(ITestOutputHelper output) { _output = output; - _server = SimpleServer.Create(RequestHandler.Handle); + _server = WireMockServer.Start(); - var options = new RestClientOptions(_server.Url) { + _capturer = _server.ConfigureBodyCapturer(Method.Post); + var options = new RestClientOptions($"{_server.Url!}{RequestBodyCapturer.Resource}") { ConfigureMessageHandler = handler => new HttpTracerHandler(handler, new OutputLogger(output), HttpMessageParts.All) }; _client = new RestClient(options); @@ -34,30 +31,22 @@ public MultipartFormDataTests(ITestOutputHelper output) { $"--{{0}}--{LineBreak}"; const string ExpectedFileAndBodyRequestContent = - "--{0}" + - $"{LineBreak}{KnownHeaders.ContentType}: application/octet-stream" + + "--{0}" + + $"{LineBreak}{KnownHeaders.ContentType}: application/octet-stream" + $"{LineBreak}{KnownHeaders.ContentDisposition}: form-data; name=\"fileName\"; filename=\"TestFile.txt\"" + - $"{LineBreak}{LineBreak}This is a test file for RestSharp.{LineBreak}" + - $"--{{0}}{LineBreak}{KnownHeaders.ContentType}: application/json; {CharsetString}" + - $"{LineBreak}{KnownHeaders.ContentDisposition}: form-data; name=controlName" + - $"{LineBreak}{LineBreak}test{LineBreak}" + + $"{LineBreak}{LineBreak}This is a test file for RestSharp.{LineBreak}" + + $"--{{0}}{LineBreak}{KnownHeaders.ContentType}: application/json; {CharsetString}" + + $"{LineBreak}{KnownHeaders.ContentDisposition}: form-data; name=controlName" + + $"{LineBreak}{LineBreak}test{LineBreak}" + $"--{{0}}--{LineBreak}"; const string ExpectedDefaultMultipartContentType = "multipart/form-data; boundary=\"{0}\""; const string ExpectedCustomMultipartContentType = "multipart/vnd.resteasy+form-data; boundary=\"{0}\""; - readonly SimpleServer _server; - readonly RestClient _client; - - static class RequestHandler { - public static string CapturedContentType { get; set; } - - public static void Handle(HttpListenerContext context) { - CapturedContentType = context.Request.ContentType; - Handlers.Echo(context); - } - } + readonly WireMockServer _server; + readonly RestClient _client; + readonly RequestBodyCapturer _capturer; static void AddParameters(RestRequest request) { request.AddParameter("foo", "bar"); @@ -85,12 +74,12 @@ public async Task MultipartFormData_NoBoundaryQuotes() { AddParameters(request); request.MultipartFormQuoteBoundary = false; - var response = await _client.ExecuteAsync(request); + await _client.ExecuteAsync(request); var expected = string.Format(Expected, request.FormBoundary); - response.Content.Should().Be(expected); - RequestHandler.CapturedContentType.Should().Be($"multipart/form-data; boundary={request.FormBoundary}"); + _capturer.Body.Should().Be(expected); + _capturer.ContentType.Should().Be($"multipart/form-data; boundary={request.FormBoundary}"); } [Fact] @@ -106,8 +95,8 @@ public async Task MultipartFormData() { _output.WriteLine($"Expected: {expected}"); _output.WriteLine($"Actual: {response.Content}"); - response.Content.Should().Be(expected); - RequestHandler.CapturedContentType.Should().Be($"multipart/form-data; boundary=\"{request.FormBoundary}\""); + _capturer.Body.Should().Be(expected); + _capturer.ContentType.Should().Be($"multipart/form-data; boundary=\"{request.FormBoundary}\""); } [Fact] @@ -129,8 +118,8 @@ public async Task MultipartFormData_HasDefaultContentType() { _output.WriteLine($"Expected: {expectedFileAndBodyRequestContent}"); _output.WriteLine($"Actual: {response.Content}"); - response.Content.Should().Be(expectedFileAndBodyRequestContent); - expectedDefaultMultipartContentType.Should().Be(RequestHandler.CapturedContentType); + _capturer.Body.Should().Be(expectedFileAndBodyRequestContent); + _capturer.ContentType.Should().Be(expectedDefaultMultipartContentType); } [Fact] @@ -144,14 +133,14 @@ public async Task MultipartFormData_WithCustomContentType() { request.AddFile("fileName", path); request.AddParameter(new BodyParameter("controlName", "test", "application/json")); - var response = await _client.ExecuteAsync(request); + await _client.ExecuteAsync(request); var boundary = request.FormBoundary; var expectedFileAndBodyRequestContent = string.Format(ExpectedFileAndBodyRequestContent, boundary); var expectedCustomMultipartContentType = string.Format(ExpectedCustomMultipartContentType, boundary); - response.Content.Should().Be(expectedFileAndBodyRequestContent); - RequestHandler.CapturedContentType.Should().Be(expectedCustomMultipartContentType); + _capturer.Body.Should().Be(expectedFileAndBodyRequestContent); + _capturer.ContentType.Should().Be(expectedCustomMultipartContentType); } [Fact] @@ -165,12 +154,12 @@ public async Task MultipartFormData_WithParameterAndFile_Async() { request.AddParameter(new BodyParameter("controlName", "test", "application/json")); - var response = await _client.ExecuteAsync(request); + await _client.ExecuteAsync(request); var boundary = request.FormBoundary; var expectedFileAndBodyRequestContent = string.Format(ExpectedFileAndBodyRequestContent, boundary); - response.Content.Should().Be(expectedFileAndBodyRequestContent); + _capturer.Body.Should().Be(expectedFileAndBodyRequestContent); } [Fact] @@ -179,12 +168,11 @@ public async Task MultipartFormDataAsync() { AddParameters(request); - var response = await _client.ExecuteAsync(request); + await _client.ExecuteAsync(request); var boundary = request.FormBoundary; var expected = string.Format(Expected, boundary); - response.Content.Should().Be(expected); + _capturer.Body.Should().Be(expected); } - -} +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs b/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs index 4262c2802..d1f3caef3 100644 --- a/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs +++ b/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs @@ -1,24 +1,20 @@ -using System.Net; -using RestSharp.Tests.Shared.Fixtures; - -namespace RestSharp.Tests.Integrated; +namespace RestSharp.Tests.Integrated; public sealed class NonProtocolExceptionHandlingTests : IDisposable { + public NonProtocolExceptionHandlingTests() + => _server + .Given(Request.Create().WithPath("/timeout")) + .RespondWith(Response.Create().WithDelay(TimeSpan.FromSeconds(1))); + // ReSharper disable once ClassNeverInstantiated.Local class StupidClass { // ReSharper disable once UnusedMember.Local public string Property { get; set; } = null!; } - /// - /// Simulates a long server process that should result in a client timeout - /// - /// - static void TimeoutHandler(HttpListenerContext context) => Thread.Sleep(101000); - public void Dispose() => _server.Dispose(); - readonly SimpleServer _server = SimpleServer.Create(TimeoutHandler); + readonly WireMockServer _server = WireMockServer.Start(); /// /// Success of this test is based largely on the behavior of your current DNS. @@ -30,14 +26,14 @@ public async Task Handles_Non_Existent_Domain() { var request = new RestRequest("foo"); var response = await client.ExecuteAsync(request); - Assert.Equal(ResponseStatus.Error, response.ResponseStatus); + response.ResponseStatus.Should().Be(ResponseStatus.Error); } [Fact] public async Task Handles_HttpClient_Timeout_Error() { - var client = new RestClient(new HttpClient {Timeout = TimeSpan.FromMilliseconds(500)}); + var client = new RestClient(new HttpClient { Timeout = TimeSpan.FromMilliseconds(500) }); - var request = new RestRequest($"{_server.Url}/404"); + var request = new RestRequest($"{_server.Url}/timeout"); var response = await client.ExecuteAsync(request); response.ErrorException.Should().BeOfType(); @@ -46,9 +42,8 @@ public async Task Handles_HttpClient_Timeout_Error() { [Fact] public async Task Handles_Server_Timeout_Error() { - var client = new RestClient(_server.Url); - - var request = new RestRequest("404") { Timeout = 500 }; + var client = new RestClient(_server.Url!); + var request = new RestRequest("timeout") { Timeout = 500 }; var response = await client.ExecuteAsync(request); response.ErrorException.Should().BeOfType(); @@ -57,8 +52,8 @@ public async Task Handles_Server_Timeout_Error() { [Fact] public async Task Handles_Server_Timeout_Error_With_Deserializer() { - var client = new RestClient(_server.Url); - var request = new RestRequest("404") { Timeout = 500 }; + var client = new RestClient(_server.Url!); + var request = new RestRequest("timeout") { Timeout = 500 }; var response = await client.ExecuteAsync(request); response.Data.Should().BeNull(); diff --git a/test/RestSharp.Tests.Integrated/RequestHeadTests.cs b/test/RestSharp.Tests.Integrated/NtlmTests.cs similarity index 98% rename from test/RestSharp.Tests.Integrated/RequestHeadTests.cs rename to test/RestSharp.Tests.Integrated/NtlmTests.cs index 77c2d1414..cfa73419e 100644 --- a/test/RestSharp.Tests.Integrated/RequestHeadTests.cs +++ b/test/RestSharp.Tests.Integrated/NtlmTests.cs @@ -4,7 +4,7 @@ namespace RestSharp.Tests.Integrated; -public class RequestHeadTests : CaptureFixture { +public class NtlmTests : CaptureFixture { [Fact] public async Task Does_Not_Pass_Default_Credentials_When_Server_Does_Not_Negotiate() { using var server = SimpleServer.Create(Handlers.Generic()); diff --git a/test/RestSharp.Tests.Integrated/RequestBodyTests.cs b/test/RestSharp.Tests.Integrated/RequestBodyTests.cs index 2f15de0aa..6f10f9315 100644 --- a/test/RestSharp.Tests.Integrated/RequestBodyTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestBodyTests.cs @@ -1,30 +1,30 @@ using RestSharp.Tests.Integrated.Fixtures; -using RestSharp.Tests.Shared.Fixtures; namespace RestSharp.Tests.Integrated; -public class RequestBodyTests(RequestBodyFixture fixture) : IClassFixture { - readonly SimpleServer _server = fixture.Server; - +public class RequestBodyTests { const string NewLine = "\r\n"; const string TextPlainContentType = "text/plain"; const string ExpectedTextContentType = $"{TextPlainContentType}; charset=utf-8"; const string ExpectedTextContentTypeNoCharset = TextPlainContentType; + readonly WireMockServer _server = WireMockServer.Start(); + async Task AssertBody(Method method, bool disableCharset = false) { - var options = new RestClientOptions(_server.Url) { DisableCharset = disableCharset }; - var client = new RestClient(options); - var request = new RestRequest(RequestBodyCapturer.Resource, method); + var options = new RestClientOptions(_server.Url!) { DisableCharset = disableCharset }; + var client = new RestClient(options); + var request = new RestRequest(RequestBodyCapturer.Resource, method); + var capturer = _server.ConfigureBodyCapturer(method); const string bodyData = "abc123 foo bar baz BING!"; - request.AddParameter(TextPlainContentType, bodyData, ParameterType.RequestBody); + request.AddBody(bodyData, TextPlainContentType); await client.ExecuteAsync(request); var expected = disableCharset ? ExpectedTextContentTypeNoCharset : ExpectedTextContentType; - AssertHasRequestBody(expected, bodyData); + AssertHasRequestBody(capturer, expected, bodyData); } [Fact] @@ -41,7 +41,7 @@ async Task AssertBody(Method method, bool disableCharset = false) { [Fact] public Task Can_Be_Added_To_POST_Request_NoCharset() => AssertBody(Method.Post, true); - + [Fact] public Task Can_Be_Added_To_POST_Request() => AssertBody(Method.Post); @@ -55,27 +55,29 @@ async Task AssertBody(Method method, bool disableCharset = false) { public async Task Can_Have_No_Body_Added_To_POST_Request() { const Method httpMethod = Method.Post; - var client = new RestClient(_server.Url); - var request = new RestRequest(RequestBodyCapturer.Resource, httpMethod); + var client = new RestClient(_server.Url!); + var request = new RestRequest(RequestBodyCapturer.Resource, httpMethod); + var capturer = _server.ConfigureBodyCapturer(httpMethod); await client.ExecuteAsync(request); - AssertHasNoRequestBody(); + AssertHasNoRequestBody(capturer); } - [Fact] + [Fact(Skip = "GET with body is not supported on all platforms")] public Task Can_Be_Added_To_GET_Request() => AssertBody(Method.Get); - [Fact] + [Fact(Skip = "GET with body is not supported on all platforms")] public Task Can_Be_Added_To_HEAD_Request() => AssertBody(Method.Head); [Fact] public async Task MultipartFormData_Without_File_Creates_A_Valid_RequestBody() { - var client = new RestClient(_server.Url); + var client = new RestClient(_server.Url!); var request = new RestRequest(RequestBodyCapturer.Resource, Method.Post) { AlwaysMultipartFormData = true }; + var capturer = _server.ConfigureBodyCapturer(Method.Post); const string bodyData = "abc123 foo bar baz BING!"; const string multipartName = "mybody"; @@ -90,19 +92,19 @@ public async Task MultipartFormData_Without_File_Creates_A_Valid_RequestBody() { bodyData }; - var actual = RequestBodyCapturer.CapturedEntityBody.Split(NewLine); + var actual = capturer.Body!.Split(NewLine); actual.Should().Contain(expectedBody); } - static void AssertHasNoRequestBody() { - RequestBodyCapturer.CapturedContentType.Should().BeNull(); - RequestBodyCapturer.CapturedHasEntityBody.Should().BeFalse(); - RequestBodyCapturer.CapturedEntityBody.Should().BeNullOrEmpty(); + static void AssertHasNoRequestBody(RequestBodyCapturer capturer) { + capturer.ContentType.Should().BeNull(); + capturer.HasBody.Should().BeFalse(); + capturer.Body.Should().BeNullOrEmpty(); } - static void AssertHasRequestBody(string contentType, string bodyData) { - RequestBodyCapturer.CapturedContentType.Should().Be(contentType); - RequestBodyCapturer.CapturedHasEntityBody.Should().BeTrue(); - RequestBodyCapturer.CapturedEntityBody.Should().Be(bodyData); + static void AssertHasRequestBody(RequestBodyCapturer capturer, string contentType, string bodyData) { + capturer.ContentType.Should().Be(contentType); + capturer.HasBody.Should().BeTrue(); + capturer.Body.Should().Be(bodyData); } } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs b/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs index a4206b6e4..dab4271c6 100644 --- a/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs +++ b/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs @@ -1,10 +1,7 @@ -using System.Net; -using RestSharp.Tests.Shared.Fixtures; - namespace RestSharp.Tests.Integrated; public sealed class ResourceStringParametersTests : IDisposable { - readonly SimpleServer _server = SimpleServer.Create(RequestHandler.Handle); + readonly WireMockServer _server = WireMockServer.Start(); public void Dispose() => _server.Dispose(); @@ -12,22 +9,20 @@ public sealed class ResourceStringParametersTests : IDisposable { public async Task Should_keep_to_parameters_with_the_same_name() { const string parameters = "?priority=Low&priority=Medium"; - var client = new RestClient(_server.Url); + var url = ""; + _server + .Given(Request.Create()) + .RespondWith(Response.Create().WithCallback(req => { + url = req.Url; + return new ResponseMessage(); + })); + + var client = new RestClient(_server.Url!); var request = new RestRequest(parameters); await client.GetAsync(request); - var query = RequestHandler.Url?.Query; + var query = new Uri(url).Query; query.Should().Be(parameters); } - - #nullable disable - static class RequestHandler { - public static Uri Url { get; private set; } - - public static void Handle(HttpListenerContext context) { - Url = context.Request.Url; - Handlers.Echo(context); - } - } } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj index 96a0bbc24..645df0d94 100644 --- a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj +++ b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj @@ -18,9 +18,16 @@ + + + + + + + \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/StatusCodeTests.cs b/test/RestSharp.Tests.Integrated/StatusCodeTests.cs index 0c020ccf3..292e2522a 100644 --- a/test/RestSharp.Tests.Integrated/StatusCodeTests.cs +++ b/test/RestSharp.Tests.Integrated/StatusCodeTests.cs @@ -1,8 +1,6 @@ using System.Net; -using RestSharp.Interceptors; using RestSharp.Serializers.Xml; -using RestSharp.Tests.Shared.Extensions; -using RestSharp.Tests.Shared.Fixtures; +using WireMock; // ReSharper disable UnusedMember.Local // ReSharper disable InconsistentNaming @@ -11,78 +9,25 @@ namespace RestSharp.Tests.Integrated; public sealed class StatusCodeTests : IDisposable { public StatusCodeTests() { - _server = SimpleServer.Create(UrlToStatusCodeHandler); - _client = new RestClient(_server.Url, configureSerialization: cfg => cfg.UseXmlSerializer()); + _client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseXmlSerializer()); + _server + .Given(Request.Create()) + .RespondWith(Response.Create().WithCallback(CreateResponse)); + return; + + ResponseMessage CreateResponse(IRequestMessage request) { + var url = new Uri(request.Url); + + return new ResponseMessage() { + StatusCode = int.Parse(url.Segments.Last()) + }; + } } public void Dispose() => _server.Dispose(); - readonly SimpleServer _server; - readonly RestClient _client; - - static void UrlToStatusCodeHandler(HttpListenerContext obj) => obj.Response.StatusCode = int.Parse(obj.Request.Url!.Segments.Last()); - - [Fact] - public async Task ContentType_Additional_Information() { - _server.SetHandler(Handlers.Generic()); - - var request = new RestRequest("", Method.Post) { - RequestFormat = DataFormat.Json, - Resource = "contenttype_odata" - }; - request.AddBody("bodyadsodajjd"); - request.AddHeader("X-RequestDigest", "xrequestdigestasdasd"); - request.AddHeader(KnownHeaders.Accept, $"{ContentType.Json}; odata=verbose"); - request.AddHeader(KnownHeaders.ContentType, $"{ContentType.Json}; odata=verbose"); - - var response = await _client.ExecuteAsync(request); - - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.IsSuccessful.Should().BeTrue(); - response.IsSuccessStatusCode.Should().BeTrue(); - } - - [Fact] - public async Task Handles_Default_Root_Element_On_No_Error() { - _server.SetHandler(Handlers.Generic()); - - var request = new RestRequest("success") { - RootElement = "Success" - }; - - var interceptor = new CompatibilityInterceptor { - OnBeforeDeserialization = resp => { - if (resp.StatusCode == HttpStatusCode.NotFound) request.RootElement = "Error"; - } - }; - request.Interceptors = [interceptor]; - - var response = await _client.ExecuteAsync(request); - - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Data!.Message.Should().Be("Works!"); - } - - [Fact] - public async Task Handles_Different_Root_Element_On_Http_Error() { - _server.SetHandler(Handlers.Generic()); - - var request = new RestRequest("error") { - RootElement = "Success", - Interceptors = [ - new CompatibilityInterceptor { - OnBeforeDeserialization = resp => { - if (resp.StatusCode == HttpStatusCode.BadRequest) resp.RootElement = "Error"; - } - } - ] - }; - - var response = await _client.ExecuteAsync(request); - - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - response.Data!.Message.Should().Be("Not found!"); - } + readonly WireMockServer _server = WireMockServer.Start(); + readonly RestClient _client; [Fact] public async Task Handles_GET_Request_404_Error() { @@ -136,54 +81,4 @@ public async Task Reports_5xx_Status_Code_Success_Accurately() { response.IsSuccessful.Should().BeFalse(); response.IsSuccessStatusCode.Should().BeFalse(); } -} - -public class ResponseHandler { - void contenttype_odata(HttpListenerContext context) { - var contentType = context.Request.Headers[KnownHeaders.ContentType]; - var hasCorrectHeader = contentType!.Contains($"{ContentType.Json}; odata=verbose"); - context.Response.StatusCode = hasCorrectHeader ? 200 : 400; - } - - void error(HttpListenerContext context) { - context.Response.StatusCode = 400; - context.Response.Headers.Add(KnownHeaders.ContentType, ContentType.Xml); - - context.Response.OutputStream.WriteStringUtf8( - @" - - - Not found! - -" - ); - } - - void errorwithbody(HttpListenerContext context) { - context.Response.StatusCode = 400; - context.Response.Headers.Add(KnownHeaders.ContentType, "application/xml"); - - context.Response.OutputStream.WriteStringUtf8( - @" - - - Not found! - -" - ); - } - - void success(HttpListenerContext context) - => context.Response.OutputStream.WriteStringUtf8( - @" - - - Works! - -" - ); -} - -public class TestResponse { - public string Message { get; set; } = null!; } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/TestResponse.cs b/test/RestSharp.Tests.Integrated/TestResponse.cs new file mode 100644 index 000000000..ccf918b32 --- /dev/null +++ b/test/RestSharp.Tests.Integrated/TestResponse.cs @@ -0,0 +1,5 @@ +namespace RestSharp.Tests.Integrated; + +public class TestResponse { + public string Message { get; set; } = null!; +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/XmlResponseTests.cs b/test/RestSharp.Tests.Integrated/XmlResponseTests.cs new file mode 100644 index 000000000..a30f511c6 --- /dev/null +++ b/test/RestSharp.Tests.Integrated/XmlResponseTests.cs @@ -0,0 +1,126 @@ +using System.Net; +using RestSharp.Interceptors; +using RestSharp.Serializers.Xml; +using WireMock; + +namespace RestSharp.Tests.Integrated; + +public sealed class XmlResponseTests : IDisposable { + public XmlResponseTests() { + _server = WireMockServer.Start(); + + _server + .Given(Request.Create().WithPath("/contenttype_odata")) + .RespondWith(Response.Create().WithCallback(ContentTypeOData)); + + _server + .Given(Request.Create().WithPath("/success")) + .RespondWith( + Response + .Create() + .WithHeader(KnownHeaders.ContentType, ContentType.Xml) + .WithBody( + """ + + + + Works! + + + """ + ) + ); + + _server + .Given(Request.Create().WithPath("/error")) + .RespondWith( + Response + .Create() + .WithStatusCode(400) + .WithHeader(KnownHeaders.ContentType, ContentType.Xml) + .WithBody( + """ + + + + Not found! + + + """ + ) + ); + + _client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseXmlSerializer()); + } + + public void Dispose() => _server.Dispose(); + + readonly WireMockServer _server; + readonly RestClient _client; + + [Fact] + public async Task Handles_Default_Root_Element_On_No_Error() { + var request = new RestRequest("success") { + RootElement = "Success" + }; + + var interceptor = new CompatibilityInterceptor { + OnBeforeDeserialization = resp => { + if (resp.StatusCode == HttpStatusCode.NotFound) request.RootElement = "Error"; + } + }; + request.Interceptors = [interceptor]; + + var response = await _client.ExecuteAsync(request); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Data!.Message.Should().Be("Works!"); + } + + [Fact] + public async Task Handles_Different_Root_Element_On_Http_Error() { + var request = new RestRequest("error") { + RootElement = "Success", + Interceptors = [ + new CompatibilityInterceptor { + OnBeforeDeserialization = resp => { + if (resp.StatusCode == HttpStatusCode.BadRequest) resp.RootElement = "Error"; + } + } + ] + }; + + var response = await _client.ExecuteAsync(request); + + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + response.Data!.Message.Should().Be("Not found!"); + } + + [Fact] + public async Task ContentType_Additional_Information() { + var request = new RestRequest("", Method.Post) { + RequestFormat = DataFormat.Json, + Resource = "contenttype_odata" + }; + request.AddBody("bodyadsodajjd"); + request.AddHeader("X-RequestDigest", "xrequestdigestasdasd"); + request.AddHeader(KnownHeaders.Accept, $"{ContentType.Json}; odata=verbose"); + request.AddHeader(KnownHeaders.ContentType, $"{ContentType.Json}; odata=verbose"); + + var response = await _client.ExecuteAsync(request); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.IsSuccessful.Should().BeTrue(); + response.IsSuccessStatusCode.Should().BeTrue(); + } + + static ResponseMessage ContentTypeOData(IRequestMessage request) { + var contentType = request.Headers![KnownHeaders.ContentType]; + var hasCorrectHeader = contentType!.Contains($"{ContentType.Json}; odata=verbose"); + + var response = new ResponseMessage { + StatusCode = hasCorrectHeader ? 200 : 400 + }; + return response; + } +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Shared/Fixtures/Handlers.cs b/test/RestSharp.Tests.Shared/Fixtures/Handlers.cs index 8183d4607..d324e881b 100644 --- a/test/RestSharp.Tests.Shared/Fixtures/Handlers.cs +++ b/test/RestSharp.Tests.Shared/Fixtures/Handlers.cs @@ -1,6 +1,5 @@ using System.Net; using System.Reflection; -using RestSharp.Tests.Shared.Extensions; namespace RestSharp.Tests.Shared.Fixtures; @@ -10,11 +9,6 @@ public static class Handlers { /// public static void Echo(HttpListenerContext context) => context.Request.InputStream.CopyTo(context.Response.OutputStream); - /// - /// Echoes the given value back to the output. - /// - public static Action EchoValue(string value) => ctx => ctx.Response.OutputStream.WriteStringUtf8(value); - /// /// T should be a class that implements methods whose names match the urls being called, and take one parameter, an /// HttpListenerContext. diff --git a/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs b/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs deleted file mode 100644 index bb21fe4ae..000000000 --- a/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Net; -using RestSharp.Tests.Shared.Extensions; - -namespace RestSharp.Tests.Shared.Fixtures; - -public class RequestBodyCapturer { - public const string Resource = "Capture"; - - public static string CapturedContentType { get; set; } - public static bool CapturedHasEntityBody { get; set; } - public static string CapturedEntityBody { get; set; } - public static Uri CapturedUrl { get; set; } - - // ReSharper disable once UnusedMember.Global - public static void Capture(HttpListenerContext context) { - var request = context.Request; - - CapturedContentType = request.ContentType; - CapturedHasEntityBody = request.HasEntityBody; - CapturedEntityBody = request.InputStream.StreamToString(); - CapturedUrl = request.Url; - } -} \ No newline at end of file diff --git a/test/RestSharp.Tests.Shared/Fixtures/SimpleServer.cs b/test/RestSharp.Tests.Shared/Fixtures/SimpleServer.cs index d53863c2f..6ebb81d13 100644 --- a/test/RestSharp.Tests.Shared/Fixtures/SimpleServer.cs +++ b/test/RestSharp.Tests.Shared/Fixtures/SimpleServer.cs @@ -8,17 +8,15 @@ public sealed class SimpleServer : IDisposable { readonly WebServer _server; readonly CancellationTokenSource _cts = new(); - public string Url { get; } - public string ServerUrl { get; } + public string Url { get; } SimpleServer( int port, Action handler = null, AuthenticationSchemes authenticationSchemes = AuthenticationSchemes.Anonymous ) { - Url = $"http://localhost:{port}/"; - ServerUrl = $"http://{Environment.MachineName}:{port}/"; - _server = new WebServer(Url, handler, authenticationSchemes); + Url = $"http://localhost:{port}/"; + _server = new WebServer(Url, handler, authenticationSchemes); Task.Run(() => _server.Run(_cts.Token)); } diff --git a/test/RestSharp.Tests.Shared/Fixtures/TestHttpServer.cs b/test/RestSharp.Tests.Shared/Fixtures/TestHttpServer.cs index d52704502..25e8fd4c6 100644 --- a/test/RestSharp.Tests.Shared/Fixtures/TestHttpServer.cs +++ b/test/RestSharp.Tests.Shared/Fixtures/TestHttpServer.cs @@ -15,7 +15,7 @@ public class TestHttpServer : IDisposable { public int Port { get; } public TestHttpServer(int port, string url, HandlerAction handlerAction, string hostName = "localhost") - : this(port, new List { new(url, handlerAction) }, hostName) { } + : this(port, [new(url, handlerAction)], hostName) { } public TestHttpServer(int port, List handlers, string hostName = "localhost") { _requestHandlers = handlers; From 3165eaff23b64a26e2d0df75ad1fcfd84a235ef8 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Wed, 3 Apr 2024 16:18:52 +0200 Subject: [PATCH 13/24] Allow body for GET and HEAD in tests --- test/RestSharp.Tests.Integrated/NtlmTests.cs | 8 +++----- test/RestSharp.Tests.Integrated/RequestBodyTests.cs | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/test/RestSharp.Tests.Integrated/NtlmTests.cs b/test/RestSharp.Tests.Integrated/NtlmTests.cs index cfa73419e..240fed016 100644 --- a/test/RestSharp.Tests.Integrated/NtlmTests.cs +++ b/test/RestSharp.Tests.Integrated/NtlmTests.cs @@ -7,16 +7,14 @@ namespace RestSharp.Tests.Integrated; public class NtlmTests : CaptureFixture { [Fact] public async Task Does_Not_Pass_Default_Credentials_When_Server_Does_Not_Negotiate() { + if (!OperatingSystem.IsWindows()) return; using var server = SimpleServer.Create(Handlers.Generic()); - var client = new RestClient(new RestClientOptions(server.Url) { UseDefaultCredentials = true }); - + var client = new RestClient(new RestClientOptions(server.Url) { UseDefaultCredentials = true }); var request = new RestRequest(RequestHeadCapturer.Resource); - await client.ExecuteAsync(request); Assert.NotNull(RequestHeadCapturer.CapturedHeaders); - var keys = RequestHeadCapturer.CapturedHeaders.Keys.Cast().ToArray(); Assert.False( @@ -58,4 +56,4 @@ public async Task Passes_Default_Credentials_When_UseDefaultCredentials_Is_True( "Authorization header not present in HTTP request from client, even though UseDefaultCredentials = true" ); } -} +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/RequestBodyTests.cs b/test/RestSharp.Tests.Integrated/RequestBodyTests.cs index 6f10f9315..2780d13d6 100644 --- a/test/RestSharp.Tests.Integrated/RequestBodyTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestBodyTests.cs @@ -9,7 +9,7 @@ public class RequestBodyTests { const string ExpectedTextContentType = $"{TextPlainContentType}; charset=utf-8"; const string ExpectedTextContentTypeNoCharset = TextPlainContentType; - readonly WireMockServer _server = WireMockServer.Start(); + readonly WireMockServer _server = WireMockServer.Start(s => s.AllowBodyForAllHttpMethods = true); async Task AssertBody(Method method, bool disableCharset = false) { var options = new RestClientOptions(_server.Url!) { DisableCharset = disableCharset }; @@ -64,10 +64,10 @@ public async Task Can_Have_No_Body_Added_To_POST_Request() { AssertHasNoRequestBody(capturer); } - [Fact(Skip = "GET with body is not supported on all platforms")] + [Fact] public Task Can_Be_Added_To_GET_Request() => AssertBody(Method.Get); - [Fact(Skip = "GET with body is not supported on all platforms")] + [Fact] public Task Can_Be_Added_To_HEAD_Request() => AssertBody(Method.Head); [Fact] From 3e09a51e085af720575ceb7330a9b05ac5f17d86 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Wed, 3 Apr 2024 17:56:48 +0200 Subject: [PATCH 14/24] Cleaning up fixtures --- Directory.Packages.props | 1 + .../CsvHelperSerializer.cs | 18 +-- src/RestSharp/ContentType.cs | 3 +- .../DefaultParameterTests.cs | 1 + .../DownloadFileTests.cs | 77 ++++++------- .../JsonBodyTests.cs | 1 + .../MultipartFormDataTests.cs | 1 + test/RestSharp.Tests.Integrated/ProxyTests.cs | 6 +- .../RequestBodyTests.cs | 1 + .../RootElementTests.cs | 38 +++--- .../StructuredSyntaxSuffixTests.cs | 46 +++++--- .../CsvHelperTests.cs | 92 ++++++--------- .../RestSharp.Tests.Serializers.Csv.csproj | 9 ++ .../NewtonsoftJson/IntegratedSimpleTests.cs | 77 ++++--------- .../NewtonsoftJson/IntegratedTests.cs | 23 ++-- .../RestSharp.Tests.Serializers.Json.csproj | 18 +-- .../SystemTextJson/SystemTextJsonTests.cs | 65 ++++------- .../Fixtures/Handlers.cs | 5 - .../Fixtures/HttpServerFixture.cs | 23 ---- .../Fixtures/RequestBodyCapturer.cs | 2 +- .../Fixtures/TestHttpServer.cs | 109 ------------------ .../Fixtures/TestHttpServerExtensions.cs | 19 --- .../Fixtures/TestRequestHandler.cs | 57 --------- .../Fixtures/WireMockExtensions.cs | 9 +- .../RestSharp.Tests.Shared.csproj | 6 + 25 files changed, 216 insertions(+), 491 deletions(-) delete mode 100644 test/RestSharp.Tests.Shared/Fixtures/HttpServerFixture.cs rename test/{RestSharp.Tests.Integrated => RestSharp.Tests.Shared}/Fixtures/RequestBodyCapturer.cs (95%) delete mode 100644 test/RestSharp.Tests.Shared/Fixtures/TestHttpServer.cs delete mode 100644 test/RestSharp.Tests.Shared/Fixtures/TestHttpServerExtensions.cs delete mode 100644 test/RestSharp.Tests.Shared/Fixtures/TestRequestHandler.cs rename test/{RestSharp.Tests.Integrated => RestSharp.Tests.Shared}/Fixtures/WireMockExtensions.cs (74%) diff --git a/Directory.Packages.props b/Directory.Packages.props index 265862dd2..129ca2884 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,6 +15,7 @@ + diff --git a/src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs b/src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs index 96c19536c..ac4c852c0 100644 --- a/src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs +++ b/src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs @@ -5,26 +5,20 @@ namespace RestSharp.Serializers.CsvHelper; -public class CsvHelperSerializer : IDeserializer, IRestSerializer, ISerializer { - const string TextCsvContentType = "text/csv"; - - readonly CsvConfiguration _configuration; - +public class CsvHelperSerializer(CsvConfiguration configuration) : IDeserializer, IRestSerializer, ISerializer { public ISerializer Serializer => this; public IDeserializer Deserializer => this; - public string[] AcceptedContentTypes => new[] { TextCsvContentType, "application/x-download" }; + public string[] AcceptedContentTypes => [ContentType.Csv, "application/x-download"]; public SupportsContentType SupportsContentType => x => Array.IndexOf(AcceptedContentTypes, x) != -1 || x.Value.Contains("csv"); public DataFormat DataFormat => DataFormat.None; - public ContentType ContentType { get; set; } = TextCsvContentType; - - public CsvHelperSerializer() => _configuration = new CsvConfiguration(CultureInfo.InvariantCulture); + public ContentType ContentType { get; set; } = ContentType.Csv; - public CsvHelperSerializer(CsvConfiguration configuration) => _configuration = configuration; + public CsvHelperSerializer() : this(new CsvConfiguration(CultureInfo.InvariantCulture)) { } public T? Deserialize(RestResponse response) { try { @@ -33,7 +27,7 @@ public class CsvHelperSerializer : IDeserializer, IRestSerializer, ISerializer { using var stringReader = new StringReader(response.Content); - using var csvReader = new CsvReader(stringReader, _configuration); + using var csvReader = new CsvReader(stringReader, configuration); var @interface = typeof(T).GetInterface("IEnumerable`1"); @@ -81,7 +75,7 @@ public class CsvHelperSerializer : IDeserializer, IRestSerializer, ISerializer { using var stringWriter = new StringWriter(); - using var csvWriter = new CsvWriter(stringWriter, _configuration); + using var csvWriter = new CsvWriter(stringWriter, configuration); if (obj is IEnumerable records) { csvWriter.WriteRecords(records); diff --git a/src/RestSharp/ContentType.cs b/src/RestSharp/ContentType.cs index 54bec16e8..3a277de69 100644 --- a/src/RestSharp/ContentType.cs +++ b/src/RestSharp/ContentType.cs @@ -29,6 +29,7 @@ public class ContentType : IEquatable { public static readonly ContentType Json = "application/json"; public static readonly ContentType Xml = "application/xml"; public static readonly ContentType Plain = "text/plain"; + public static readonly ContentType Csv = "text/csv"; public static readonly ContentType Binary = "application/octet-stream"; public static readonly ContentType GZip = "application/x-gzip"; public static readonly ContentType FormUrlEncoded = "application/x-www-form-urlencoded"; @@ -84,4 +85,4 @@ public override bool Equals(object? obj) { } public override int GetHashCode() => _value.GetHashCode(); -} +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs b/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs index b371090fc..12ee9dc18 100644 --- a/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs +++ b/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs @@ -1,5 +1,6 @@ using RestSharp.Tests.Integrated.Fixtures; using RestSharp.Tests.Integrated.Server; +using RestSharp.Tests.Shared.Fixtures; namespace RestSharp.Tests.Integrated; diff --git a/test/RestSharp.Tests.Integrated/DownloadFileTests.cs b/test/RestSharp.Tests.Integrated/DownloadFileTests.cs index f2cd59482..2dcae0ac6 100644 --- a/test/RestSharp.Tests.Integrated/DownloadFileTests.cs +++ b/test/RestSharp.Tests.Integrated/DownloadFileTests.cs @@ -1,64 +1,56 @@ -using System.Net; -using System.Text; -using RestSharp.Tests.Shared.Fixtures; +using System.Text; namespace RestSharp.Tests.Integrated; public sealed class DownloadFileTests : IDisposable { + const string LocalPath = "Assets/Koala.jpg"; + public DownloadFileTests() { - _server = HttpServerFixture.StartServer("Assets/Koala.jpg", FileHandler); - var options = new RestClientOptions(_server.Url) { ThrowOnAnyError = true }; + // _server = HttpServerFixture.StartServer("Assets/Koala.jpg", FileHandler); + + var pathToFile = Path.Combine(_path, Path.Combine(LocalPath.Split('/'))); + + _server + .Given(Request.Create().WithPath($"/{LocalPath}")) + .RespondWith(Response.Create().WithBodyFromFile(pathToFile)); + var options = new RestClientOptions($"{_server.Url}/{LocalPath}") { ThrowOnAnyError = true }; _client = new RestClient(options); } public void Dispose() => _server.Dispose(); - void FileHandler(HttpListenerRequest request, HttpListenerResponse response) { - var pathToFile = Path.Combine( - _path, - Path.Combine( - request.Url!.Segments.Select(s => s.Replace("/", "")).ToArray() - ) - ); - - using var reader = new StreamReader(pathToFile); - - reader.BaseStream.CopyTo(response.OutputStream); - } - - readonly HttpServerFixture _server; - readonly RestClient _client; - readonly string _path = AppDomain.CurrentDomain.BaseDirectory; + readonly WireMockServer _server = WireMockServer.Start(); + readonly RestClient _client; + readonly string _path = AppDomain.CurrentDomain.BaseDirectory; [Fact] public async Task AdvancedResponseWriter_without_ResponseWriter_reads_stream() { var tag = string.Empty; - // ReSharper disable once UseObjectOrCollectionInitializer - var rr = new RestRequest("Assets/Koala.jpg"); - - rr.AdvancedResponseWriter = (response, request) => { - var buf = new byte[16]; - // ReSharper disable once MustUseReturnValue - response.Content.ReadAsStream().Read(buf, 0, buf.Length); - tag = Encoding.ASCII.GetString(buf, 6, 4); - return new RestResponse(request); + var rr = new RestRequest("") { + AdvancedResponseWriter = (response, request) => { + var buf = new byte[16]; + // ReSharper disable once MustUseReturnValue + response.Content.ReadAsStream().Read(buf, 0, buf.Length); + tag = Encoding.ASCII.GetString(buf, 6, 4); + return new RestResponse(request); + } }; await _client.ExecuteAsync(rr); - Assert.True(string.Compare("JFIF", tag, StringComparison.Ordinal) == 0); + Assert.Equal(0, string.Compare("JFIF", tag, StringComparison.Ordinal)); } [Fact] public async Task Handles_File_Download_Failure() { - var request = new RestRequest("Assets/Koala1.jpg"); + var request = new RestRequest(""); var task = () => _client.DownloadDataAsync(request); await task.Should().ThrowAsync().WithMessage("Request failed with status code NotFound"); } [Fact] public async Task Handles_Binary_File_Download() { - var request = new RestRequest("Assets/Koala.jpg"); + var request = new RestRequest(""); var response = await _client.DownloadDataAsync(request); var expected = await File.ReadAllBytesAsync(Path.Combine(_path, "Assets", "Koala.jpg")); @@ -69,22 +61,21 @@ public async Task Handles_Binary_File_Download() { public async Task Writes_Response_To_Stream() { var tempFile = Path.GetTempFileName(); - // ReSharper disable once UseObjectOrCollectionInitializer - var request = new RestRequest("Assets/Koala.jpg"); - - request.ResponseWriter = responseStream => { - using var writer = File.OpenWrite(tempFile); - - responseStream.CopyTo(writer); - return null; + var request = new RestRequest("") { + ResponseWriter = responseStream => { + using var writer = File.OpenWrite(tempFile); + responseStream.CopyTo(writer); + return null; + } }; + var response = await _client.DownloadDataAsync(request); Assert.Null(response); var fromTemp = await File.ReadAllBytesAsync(tempFile); - var expected = await File.ReadAllBytesAsync(Path.Combine(_path, "Assets", "Koala.jpg")); + var expected = await File.ReadAllBytesAsync(Path.Combine(_path, Path.Combine(LocalPath.Split('/')))); Assert.Equal(expected, fromTemp); } -} +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/JsonBodyTests.cs b/test/RestSharp.Tests.Integrated/JsonBodyTests.cs index 8d2025eae..bc7efb201 100644 --- a/test/RestSharp.Tests.Integrated/JsonBodyTests.cs +++ b/test/RestSharp.Tests.Integrated/JsonBodyTests.cs @@ -1,5 +1,6 @@ using System.Text.Json; using RestSharp.Tests.Integrated.Fixtures; +using RestSharp.Tests.Shared.Fixtures; namespace RestSharp.Tests.Integrated; diff --git a/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs b/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs index 7223da777..93ae61130 100644 --- a/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs +++ b/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs @@ -1,5 +1,6 @@ using HttpTracer; using RestSharp.Tests.Integrated.Fixtures; +using RestSharp.Tests.Shared.Fixtures; namespace RestSharp.Tests.Integrated; diff --git a/test/RestSharp.Tests.Integrated/ProxyTests.cs b/test/RestSharp.Tests.Integrated/ProxyTests.cs index cec1571bd..67cc0f50d 100644 --- a/test/RestSharp.Tests.Integrated/ProxyTests.cs +++ b/test/RestSharp.Tests.Integrated/ProxyTests.cs @@ -6,14 +6,14 @@ namespace RestSharp.Tests.Integrated; public class ProxyTests { [Fact] public async Task Set_Invalid_Proxy_Fails() { - using var server = HttpServerFixture.StartServer((_, _) => { }); + using var server = WireMockServer.Start(); - var client = new RestClient(new RestClientOptions(server.Url) { Proxy = new WebProxy("non_existent_proxy", false) }); + var client = new RestClient(new RestClientOptions(server.Url!) { Proxy = new WebProxy("non_existent_proxy", false) }); var request = new RestRequest(); var response = await client.ExecuteAsync(request); - Assert.False(response.IsSuccessful); + response.IsSuccessful.Should().BeFalse(); response.ErrorException.Should().BeOfType(); } } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/RequestBodyTests.cs b/test/RestSharp.Tests.Integrated/RequestBodyTests.cs index 2780d13d6..47ba86f04 100644 --- a/test/RestSharp.Tests.Integrated/RequestBodyTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestBodyTests.cs @@ -1,4 +1,5 @@ using RestSharp.Tests.Integrated.Fixtures; +using RestSharp.Tests.Shared.Fixtures; namespace RestSharp.Tests.Integrated; diff --git a/test/RestSharp.Tests.Integrated/RootElementTests.cs b/test/RestSharp.Tests.Integrated/RootElementTests.cs index 608bf5238..3e9a0feba 100644 --- a/test/RestSharp.Tests.Integrated/RootElementTests.cs +++ b/test/RestSharp.Tests.Integrated/RootElementTests.cs @@ -1,16 +1,26 @@ -using System.Net; -using RestSharp.Serializers.Xml; -using RestSharp.Tests.Shared.Extensions; -using RestSharp.Tests.Shared.Fixtures; +using RestSharp.Serializers.Xml; namespace RestSharp.Tests.Integrated; public class RootElementTests { [Fact] public async Task Copy_RootElement_From_Request_To_IWithRootElement_Deserializer() { - using var server = HttpServerFixture.StartServer("success", Handle); - - var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseXmlSerializer()); + using var server = WireMockServer.Start(); + + const string xmlBody = + """ + + + + Works! + + + """; + server + .Given(Request.Create().WithPath("/success")) + .RespondWith(Response.Create().WithBody(xmlBody).WithHeader(KnownHeaders.ContentType, ContentType.Xml)); + + var client = new RestClient(server.Url!, configureSerialization: cfg => cfg.UseXmlSerializer()); var request = new RestRequest("success") { RootElement = "Success" }; @@ -18,19 +28,5 @@ public async Task Copy_RootElement_From_Request_To_IWithRootElement_Deserializer response.Data.Should().NotBeNull(); response.Data!.Message.Should().Be("Works!"); - - static void Handle(HttpListenerRequest req, HttpListenerResponse response) { - response.StatusCode = 200; - response.Headers.Add(KnownHeaders.ContentType, ContentType.Xml); - - response.OutputStream.WriteStringUtf8( - @" - - - Works! - -" - ); - } } } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs b/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs index 3f4d68499..5cfd56d33 100644 --- a/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs +++ b/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs @@ -1,15 +1,14 @@ -using System.Net; +using System.Text; using RestSharp.Serializers.Xml; -using RestSharp.Tests.Shared.Extensions; -using RestSharp.Tests.Shared.Fixtures; +using WireMock.Types; +using WireMock.Util; // ReSharper disable UnusedAutoPropertyAccessor.Local namespace RestSharp.Tests.Integrated; public sealed class StructuredSyntaxSuffixTests : IDisposable { - readonly TestHttpServer _server; - readonly string _url; + readonly WireMockServer _server; class Person { public string Name { get; set; } = null!; @@ -21,22 +20,37 @@ class Person { const string JsonContent = @"{ ""name"":""Bob"", ""age"":50 }"; public StructuredSyntaxSuffixTests() { - _server = new TestHttpServer(0, "", HandleRequest); - _url = $"http://localhost:{_server.Port}"; + _server = WireMockServer.Start(); + _server.Given(Request.Create().WithPath("/").UsingGet()).RespondWith(Response.Create().WithCallback(Handle)); return; - static void HandleRequest(HttpListenerRequest request, HttpListenerResponse response, Dictionary p) { - response.ContentType = request.QueryString["ct"]; - response.OutputStream.WriteStringUtf8(request.QueryString["c"]); - response.StatusCode = 200; + static ResponseMessage Handle(IRequestMessage request) { + var response = new ResponseMessage { + Headers = new Dictionary> { + [KnownHeaders.ContentType] = new(request.Query!["ct"]) + }, + StatusCode = 200, + BodyData = new BodyData { + BodyAsString = request.Query["c"].First(), + Encoding = Encoding.UTF8, + DetectedBodyType = BodyType.String + } + }; + return response; } + + // static void HandleRequest(HttpListenerRequest request, HttpListenerResponse response, Dictionary p) { + // response.ContentType = request.QueryString["ct"]; + // response.OutputStream.WriteStringUtf8(request.QueryString["c"]); + // response.StatusCode = 200; + // } } public void Dispose() => _server.Dispose(); [Fact] public async Task By_default_application_json_content_type_should_deserialize_as_JSON() { - var client = new RestClient(_url); + var client = new RestClient(_server.Url!); var request = new RestRequest() .AddParameter("ct", "application/json") @@ -50,7 +64,7 @@ public async Task By_default_application_json_content_type_should_deserialize_as [Fact] public async Task By_default_content_types_with_JSON_structured_syntax_suffix_should_deserialize_as_JSON() { - var client = new RestClient(_url); + var client = new RestClient(_server.Url!); var request = new RestRequest() .AddParameter("ct", "application/vnd.somebody.something+json") @@ -64,7 +78,7 @@ public async Task By_default_content_types_with_JSON_structured_syntax_suffix_sh [Fact] public async Task By_default_content_types_with_XML_structured_syntax_suffix_should_deserialize_as_XML() { - var client = new RestClient(_url, configureSerialization: cfg => cfg.UseXmlSerializer()); + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseXmlSerializer()); var request = new RestRequest() .AddParameter("ct", "application/vnd.somebody.something+xml") @@ -78,7 +92,7 @@ public async Task By_default_content_types_with_XML_structured_syntax_suffix_sho [Fact] public async Task By_default_text_xml_content_type_should_deserialize_as_XML() { - var client = new RestClient(_url, configureSerialization: cfg => cfg.UseXmlSerializer()); + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseXmlSerializer()); var request = new RestRequest() .AddParameter("ct", "text/xml") @@ -89,4 +103,4 @@ public async Task By_default_text_xml_content_type_should_deserialize_as_XML() { Assert.Equal("Bob", response.Data!.Name); Assert.Equal(50, response.Data.Age); } -} +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs b/test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs index e4aee68e8..4f3202b97 100644 --- a/test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs +++ b/test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs @@ -9,11 +9,20 @@ namespace RestSharp.Tests.Serializers.Csv; -public class CsvHelperTests { +public sealed class CsvHelperTests : IDisposable { static readonly Fixture Fixture = new(); - [Fact] - public async Task Use_CsvHelper_For_Response() { + readonly WireMockServer _server = WireMockServer.Start(); + + void ConfigureResponse(object expected) { + var serializer = new CsvHelperSerializer(); + + _server + .Given(Request.Create().WithPath("/").UsingGet()) + .RespondWith(Response.Create().WithBody(serializer.Serialize(expected)!).WithHeader(KnownHeaders.ContentType, ContentType.Csv)); + } + + TestObject CreateTestObject() { var expected = Fixture.Create(); expected.DateTimeValue = new DateTime( @@ -25,18 +34,16 @@ public async Task Use_CsvHelper_For_Response() { expected.DateTimeValue.Second ); - using var server = HttpServerFixture.StartServer( - (_, response) => { - var serializer = new CsvHelperSerializer(); + return expected; + } - response.ContentType = "text/csv"; - response.ContentEncoding = Encoding.UTF8; - response.OutputStream.WriteStringUtf8(serializer.Serialize(expected)!); - } - ); + [Fact] + public async Task Use_CsvHelper_For_Response() { + var expected = CreateTestObject(); - var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseCsvHelper()); + ConfigureResponse(expected); + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseCsvHelper()); var actual = await client.GetAsync(new RestRequest()); actual.Should().BeEquivalentTo(expected); @@ -48,49 +55,25 @@ public async Task Use_CsvHelper_For_Collection_Response() { var expected = new List(count); for (var i = 0; i < count; i++) { - var item = Fixture.Create(); - - item.DateTimeValue = new DateTime( - item.DateTimeValue.Year, - item.DateTimeValue.Month, - item.DateTimeValue.Day, - item.DateTimeValue.Hour, - item.DateTimeValue.Minute, - item.DateTimeValue.Second - ); - + var item = CreateTestObject(); expected.Add(item); } - using var server = HttpServerFixture.StartServer( - (_, response) => { - var serializer = new CsvHelperSerializer(); - - response.ContentType = "text/csv"; - response.ContentEncoding = Encoding.UTF8; - response.OutputStream.WriteStringUtf8(serializer.Serialize(expected)); - } - ); - - var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseCsvHelper()); + ConfigureResponse(expected); + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseCsvHelper()); var actual = await client.GetAsync>(new RestRequest()); actual.Should().BeEquivalentTo(expected); } [Fact] - public async Task DeserilizationFails_IsSuccessfull_Should_BeFalse() { - using var server = HttpServerFixture.StartServer( - (_, response) => { - response.StatusCode = (int)HttpStatusCode.OK; - response.ContentType = "text/csv"; - response.ContentEncoding = Encoding.UTF8; - response.OutputStream.WriteStringUtf8("invalid csv"); - } - ); + public async Task DeserilizationFails_IsSuccessful_Should_BeFalse() { + _server + .Given(Request.Create().WithPath("/").UsingGet()) + .RespondWith(Response.Create().WithBody("invalid csv").WithHeader(KnownHeaders.ContentType, ContentType.Csv)); - var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseCsvHelper()); + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseCsvHelper()); var response = await client.ExecuteAsync(new RestRequest()); @@ -100,21 +83,10 @@ public async Task DeserilizationFails_IsSuccessfull_Should_BeFalse() { [Fact] public async Task DeserilizationSucceeds_IsSuccessful_Should_BeTrue() { - var item = Fixture.Create(); - - using var server = HttpServerFixture.StartServer( - (_, response) => { - var serializer = new SystemTextJsonSerializer(); - - response.StatusCode = (int)HttpStatusCode.OK; - response.ContentType = "text/csv"; - response.ContentEncoding = Encoding.UTF8; - response.OutputStream.WriteStringUtf8(serializer.Serialize(item)!); - } - ); - - var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseSystemTextJson()); + var item = CreateTestObject(); + ConfigureResponse(item); + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseSystemTextJson()); var response = await client.ExecuteAsync(new RestRequest()); response.IsSuccessStatusCode.Should().BeTrue(); @@ -180,4 +152,6 @@ public void SerializedCollection_Should_Be() { "StringValue,Int32Value,DecimalValue,DoubleValue,SingleValue,DateTimeValue,TimeSpanValue;hello,32,0,0,16.5,01/20/2024 00:00:00,00:10:00;,65,89.555,0,0,08/19/2022 05:15:21,00:01:01;\"String, with comma\",0,0,20.00001,80000,01/01/0001 00:00:00,00:00:00;" ); } -} + + public void Dispose() => _server?.Dispose(); +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Serializers.Csv/RestSharp.Tests.Serializers.Csv.csproj b/test/RestSharp.Tests.Serializers.Csv/RestSharp.Tests.Serializers.Csv.csproj index 4477eea34..c5305e02b 100644 --- a/test/RestSharp.Tests.Serializers.Csv/RestSharp.Tests.Serializers.Csv.csproj +++ b/test/RestSharp.Tests.Serializers.Csv/RestSharp.Tests.Serializers.Csv.csproj @@ -3,4 +3,13 @@ + + + + + + + + + diff --git a/test/RestSharp.Tests.Serializers.Json/NewtonsoftJson/IntegratedSimpleTests.cs b/test/RestSharp.Tests.Serializers.Json/NewtonsoftJson/IntegratedSimpleTests.cs index c86a4d332..573a04f52 100644 --- a/test/RestSharp.Tests.Serializers.Json/NewtonsoftJson/IntegratedSimpleTests.cs +++ b/test/RestSharp.Tests.Serializers.Json/NewtonsoftJson/IntegratedSimpleTests.cs @@ -1,32 +1,24 @@ -using System.Net; -using System.Text; using RestSharp.Serializers.NewtonsoftJson; -using RestSharp.Tests.Shared.Extensions; using RestSharp.Tests.Shared.Fixtures; namespace RestSharp.Tests.Serializers.Json.NewtonsoftJson; -public class IntegratedSimpleTests { - string _body; - - void CaptureBody(HttpListenerRequest request, HttpListenerResponse response) => _body = request.InputStream.StreamToString(); - +public sealed class IntegratedSimpleTests : IDisposable { static readonly Fixture Fixture = new(); + readonly WireMockServer _server = WireMockServer.Start(); + [Fact] public async Task Use_JsonNet_For_Requests() { - using var server = HttpServerFixture.StartServer(CaptureBody); - _body = null; + var capturer = _server.ConfigureBodyCapturer(Method.Post, false); var serializer = new JsonNetSerializer(); - - var testData = Fixture.Create(); - - var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseNewtonsoftJson()); - var request = new RestRequest().AddJsonBody(testData); + var testData = Fixture.Create(); + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseNewtonsoftJson()); + var request = new RestRequest().AddJsonBody(testData); await client.PostAsync(request); - var actual = serializer.Deserialize(new RestResponse(request) { Content = _body! }); + var actual = serializer.Deserialize(new RestResponse(request) { Content = capturer.Body! }); actual.Should().BeEquivalentTo(testData); } @@ -34,19 +26,11 @@ public async Task Use_JsonNet_For_Requests() { [Fact] public async Task Use_JsonNet_For_Response() { var expected = Fixture.Create(); + _server + .Given(Request.Create().WithPath("/").UsingGet()) + .RespondWith(Response.Create().WithBodyAsJson(expected)); - using var server = HttpServerFixture.StartServer( - (_, response) => { - var serializer = new JsonNetSerializer(); - - response.ContentType = "application/json"; - response.ContentEncoding = Encoding.UTF8; - response.OutputStream.WriteStringUtf8(serializer.Serialize(expected)!); - } - ); - - var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseNewtonsoftJson()); - + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseNewtonsoftJson()); var actual = await client.GetAsync(new RestRequest()); actual.Should().BeEquivalentTo(expected); @@ -54,18 +38,13 @@ public async Task Use_JsonNet_For_Response() { [Fact] public async Task DeserilizationFails_IsSuccessful_Should_BeFalse() { - using var server = HttpServerFixture.StartServer( - (_, response) => { - response.StatusCode = (int)HttpStatusCode.OK; - response.ContentType = "application/json"; - response.ContentEncoding = Encoding.UTF8; - response.OutputStream.WriteStringUtf8("invalid json"); - } - ); + _server + .Given(Request.Create().WithPath("/").UsingGet()) + .RespondWith(Response.Create().WithBody("invalid json").WithHeader(KnownHeaders.ContentType, ContentType.Json)); - var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseNewtonsoftJson()); + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseNewtonsoftJson()); - var response = await client.ExecuteAsync(new RestRequest(), default); + var response = await client.ExecuteAsync(new RestRequest()); response.IsSuccessStatusCode.Should().BeTrue(); response.IsSuccessful.Should().BeFalse(); @@ -74,23 +53,17 @@ public async Task DeserilizationFails_IsSuccessful_Should_BeFalse() { [Fact] public async Task DeserilizationSucceeds_IsSuccessful_Should_BeTrue() { var item = Fixture.Create(); + _server + .Given(Request.Create().WithPath("/").UsingGet()) + .RespondWith(Response.Create().WithBodyAsJson(item)); - using var server = HttpServerFixture.StartServer( - (_, response) => { - var serializer = new JsonNetSerializer(); - - response.StatusCode = (int)HttpStatusCode.OK; - response.ContentType = "application/json"; - response.ContentEncoding = Encoding.UTF8; - response.OutputStream.WriteStringUtf8(serializer.Serialize(item)!); - } - ); + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseNewtonsoftJson()); - var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseNewtonsoftJson()); - - var response = await client.ExecuteAsync(new RestRequest(), default); + var response = await client.ExecuteAsync(new RestRequest()); response.IsSuccessStatusCode.Should().BeTrue(); response.IsSuccessful.Should().BeTrue(); } -} + + public void Dispose() => _server?.Dispose(); +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Serializers.Json/NewtonsoftJson/IntegratedTests.cs b/test/RestSharp.Tests.Serializers.Json/NewtonsoftJson/IntegratedTests.cs index ce74b3a86..9fcd45865 100644 --- a/test/RestSharp.Tests.Serializers.Json/NewtonsoftJson/IntegratedTests.cs +++ b/test/RestSharp.Tests.Serializers.Json/NewtonsoftJson/IntegratedTests.cs @@ -1,25 +1,24 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using RestMockCore; using RestSharp.Serializers.NewtonsoftJson; namespace RestSharp.Tests.Serializers.Json.NewtonsoftJson; -public class IntegratedTests { +public sealed class IntegratedTests : IDisposable { static readonly Fixture Fixture = new(); - const int Port = 5001; + readonly WireMockServer _server = WireMockServer.Start(); [Fact] public async Task Use_with_GetJsonAsync() { var data = Fixture.Create(); var serialized = JsonConvert.SerializeObject(data, JsonNetSerializer.DefaultSettings); - using var server = new HttpServer(Port); - server.Config.Get("/test").Send(serialized); - server.Run(); + _server + .Given(Request.Create().WithPath("/test").UsingGet()) + .RespondWith(Response.Create().WithBody(serialized).WithHeader(KnownHeaders.ContentType, ContentType.Json)); - using var client = new RestClient($"http://localhost:{Port}", configureSerialization: cfg => cfg.UseNewtonsoftJson()); + using var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseNewtonsoftJson()); var response = await client.GetJsonAsync("/test"); @@ -34,14 +33,16 @@ public async Task Use_with_GetJsonAsync_custom_settings() { var data = Fixture.Create(); var serialized = JsonConvert.SerializeObject(data, settings); - using var server = new HttpServer(Port); - server.Config.Get("/test").Send(serialized); - server.Run(); + _server + .Given(Request.Create().WithPath("/test").UsingGet()) + .RespondWith(Response.Create().WithBody(serialized).WithHeader(KnownHeaders.ContentType, ContentType.Json)); - using var client = new RestClient($"http://localhost:{Port}", configureSerialization: cfg => cfg.UseNewtonsoftJson(settings)); + using var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseNewtonsoftJson(settings)); var response = await client.GetJsonAsync("/test"); response.Should().BeEquivalentTo(data); } + + public void Dispose() => _server?.Dispose(); } diff --git a/test/RestSharp.Tests.Serializers.Json/RestSharp.Tests.Serializers.Json.csproj b/test/RestSharp.Tests.Serializers.Json/RestSharp.Tests.Serializers.Json.csproj index 94a871e1f..ef87290b5 100644 --- a/test/RestSharp.Tests.Serializers.Json/RestSharp.Tests.Serializers.Json.csproj +++ b/test/RestSharp.Tests.Serializers.Json/RestSharp.Tests.Serializers.Json.csproj @@ -5,18 +5,12 @@ - + - - - - - - - - - - - + + + + + diff --git a/test/RestSharp.Tests.Serializers.Json/SystemTextJson/SystemTextJsonTests.cs b/test/RestSharp.Tests.Serializers.Json/SystemTextJson/SystemTextJsonTests.cs index 5d5976d48..5cfd6ac5d 100644 --- a/test/RestSharp.Tests.Serializers.Json/SystemTextJson/SystemTextJsonTests.cs +++ b/test/RestSharp.Tests.Serializers.Json/SystemTextJson/SystemTextJsonTests.cs @@ -1,51 +1,37 @@ -using System.Net; -using System.Text; using RestSharp.Serializers.Json; -using RestSharp.Tests.Shared.Extensions; using RestSharp.Tests.Shared.Fixtures; namespace RestSharp.Tests.Serializers.Json.SystemTextJson; -public class SystemTextJsonTests { +public sealed class SystemTextJsonTests : IDisposable { static readonly Fixture Fixture = new(); - string _body; - + readonly WireMockServer _server = WireMockServer.Start(); + [Fact] public async Task Use_JsonNet_For_Requests() { - using var server = HttpServerFixture.StartServer(CaptureBody); - _body = null; var serializer = new SystemTextJsonSerializer(); + var capturer = _server.ConfigureBodyCapturer(Method.Post, false); var testData = Fixture.Create(); - - var client = new RestClient(server.Url); + var client = new RestClient(_server.Url!); var request = new RestRequest().AddJsonBody(testData); await client.PostAsync(request); - var actual = serializer.Deserialize(new RestResponse(request) { Content = _body }); + var actual = serializer.Deserialize(new RestResponse(request) { Content = capturer.Body }); actual.Should().BeEquivalentTo(testData); - - void CaptureBody(HttpListenerRequest req, HttpListenerResponse response) => _body = req.InputStream.StreamToString(); } [Fact] public async Task Use_JsonNet_For_Response() { var expected = Fixture.Create(); + _server + .Given(Request.Create().WithPath("/").UsingGet()) + .RespondWith(Response.Create().WithBodyAsJson(expected)); - using var server = HttpServerFixture.StartServer( - (_, response) => { - var serializer = new SystemTextJsonSerializer(); - - response.ContentType = "application/json"; - response.ContentEncoding = Encoding.UTF8; - response.OutputStream.WriteStringUtf8(serializer.Serialize(expected)!); - } - ); - - var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseSystemTextJson()); + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseSystemTextJson()); var actual = await client.GetAsync(new RestRequest()); @@ -54,16 +40,11 @@ public async Task Use_JsonNet_For_Response() { [Fact] public async Task DeserilizationFails_IsSuccessful_Should_BeFalse() { - using var server = HttpServerFixture.StartServer( - (_, response) => { - response.StatusCode = (int)HttpStatusCode.OK; - response.ContentType = "application/json"; - response.ContentEncoding = Encoding.UTF8; - response.OutputStream.WriteStringUtf8("invalid json"); - } - ); + _server + .Given(Request.Create().WithPath("/").UsingGet()) + .RespondWith(Response.Create().WithBody("invalid json").WithHeader(KnownHeaders.ContentType, ContentType.Json)); - var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseSystemTextJson()); + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseSystemTextJson()); var response = await client.ExecuteAsync(new RestRequest()); @@ -74,23 +55,17 @@ public async Task DeserilizationFails_IsSuccessful_Should_BeFalse() { [Fact] public async Task DeserilizationSucceeds_IsSuccessful_Should_BeTrue() { var item = Fixture.Create(); + _server + .Given(Request.Create().WithPath("/").UsingGet()) + .RespondWith(Response.Create().WithBodyAsJson(item)); - using var server = HttpServerFixture.StartServer( - (_, response) => { - var serializer = new SystemTextJsonSerializer(); - - response.StatusCode = (int)HttpStatusCode.OK; - response.ContentType = "application/json"; - response.ContentEncoding = Encoding.UTF8; - response.OutputStream.WriteStringUtf8(serializer.Serialize(item)!); - } - ); - - var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseSystemTextJson()); + var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseSystemTextJson()); var response = await client.ExecuteAsync(new RestRequest()); response.IsSuccessStatusCode.Should().BeTrue(); response.IsSuccessful.Should().BeTrue(); } + + public void Dispose() => _server?.Dispose(); } diff --git a/test/RestSharp.Tests.Shared/Fixtures/Handlers.cs b/test/RestSharp.Tests.Shared/Fixtures/Handlers.cs index d324e881b..6aa76d27b 100644 --- a/test/RestSharp.Tests.Shared/Fixtures/Handlers.cs +++ b/test/RestSharp.Tests.Shared/Fixtures/Handlers.cs @@ -4,11 +4,6 @@ namespace RestSharp.Tests.Shared.Fixtures; public static class Handlers { - /// - /// Echoes the request input back to the output. - /// - public static void Echo(HttpListenerContext context) => context.Request.InputStream.CopyTo(context.Response.OutputStream); - /// /// T should be a class that implements methods whose names match the urls being called, and take one parameter, an /// HttpListenerContext. diff --git a/test/RestSharp.Tests.Shared/Fixtures/HttpServerFixture.cs b/test/RestSharp.Tests.Shared/Fixtures/HttpServerFixture.cs deleted file mode 100644 index d94595845..000000000 --- a/test/RestSharp.Tests.Shared/Fixtures/HttpServerFixture.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Net; - -namespace RestSharp.Tests.Shared.Fixtures; - -public sealed class HttpServerFixture : IDisposable { - public static HttpServerFixture StartServer(string url, Action handle) { - var server = new TestHttpServer(0, url, (request, response, _) => handle(request, response)); - return new HttpServerFixture(server); - } - - public static HttpServerFixture StartServer(Action handle) => StartServer("", handle); - - HttpServerFixture(TestHttpServer server) { - Url = $"http://localhost:{server.Port}"; - _server = server; - } - - public string Url { get; } - - readonly TestHttpServer _server; - - public void Dispose() => _server.Dispose(); -} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/Fixtures/RequestBodyCapturer.cs b/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs similarity index 95% rename from test/RestSharp.Tests.Integrated/Fixtures/RequestBodyCapturer.cs rename to test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs index d42117797..7dc65a7e3 100644 --- a/test/RestSharp.Tests.Integrated/Fixtures/RequestBodyCapturer.cs +++ b/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs @@ -1,6 +1,6 @@ namespace RestSharp.Tests.Integrated.Fixtures; -class RequestBodyCapturer { +public class RequestBodyCapturer { public const string Resource = "/capture"; public string? ContentType { get; private set; } diff --git a/test/RestSharp.Tests.Shared/Fixtures/TestHttpServer.cs b/test/RestSharp.Tests.Shared/Fixtures/TestHttpServer.cs deleted file mode 100644 index 25e8fd4c6..000000000 --- a/test/RestSharp.Tests.Shared/Fixtures/TestHttpServer.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System.Net; -using System.Net.Sockets; -using System.Text; - -namespace RestSharp.Tests.Shared.Fixtures; - -public delegate void HandlerAction(HttpListenerRequest request, HttpListenerResponse response, Dictionary urlMap); - -public class TestHttpServer : IDisposable { - readonly HttpListener _listener; - readonly List _requestHandlers; - readonly object _requestHandlersLock = new(); - readonly CancellationTokenSource _cts = new(); - - public int Port { get; } - - public TestHttpServer(int port, string url, HandlerAction handlerAction, string hostName = "localhost") - : this(port, [new(url, handlerAction)], hostName) { } - - public TestHttpServer(int port, List handlers, string hostName = "localhost") { - _requestHandlers = handlers; - - Port = port > 0 ? port : GetRandomUnusedPort(); - - //create and start listener - _listener = new HttpListener(); - _listener.Prefixes.Add($"http://{hostName}:{Port}/"); - _listener.Start(); - - Task.Run(() => HandleRequests(_cts.Token)); - } - - static int GetRandomUnusedPort() { - var listener = new TcpListener(IPAddress.Any, 0); - listener.Start(); - var port = ((IPEndPoint)listener.LocalEndpoint).Port; - listener.Stop(); - return port; - } - - async Task HandleRequests(CancellationToken cancellationToken) { - try { - //listen for all requests - while (_listener.IsListening && !cancellationToken.IsCancellationRequested) { - //get the request - var context = await _listener.GetContextAsync(); - - try { - Dictionary parameters = null; - TestRequestHandler handler; - - lock (_requestHandlersLock) { - handler = _requestHandlers.FirstOrDefault( - h => h.TryMatchUrl(context.Request.RawUrl, context.Request.HttpMethod, out parameters) - ); - } - - string responseString = null; - - if (handler != null) { - //add the query string parameters to the pre-defined url parameters that were set from MatchesUrl() - foreach (var qsParamName in context.Request.QueryString.AllKeys) - parameters[qsParamName] = context.Request.QueryString[qsParamName]; - - try { - handler.HandlerAction(context.Request, context.Response, parameters); - } - catch (Exception ex) { - responseString = $"Exception in handler: {ex.Message}"; - context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - } - } - else { - context.Response.ContentType("text/plain").StatusCode(404); - responseString = $"No handler provided for URL: {context.Request.RawUrl}"; - } - - context.Request.ClearContent(); - - //send the response, if there is not (if responseString is null, then the handler method should have manually set the output stream) - if (responseString != null) { - var buffer = Encoding.UTF8.GetBytes(responseString); - context.Response.ContentLength64 += buffer.Length; - await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length, cancellationToken); - } - } - finally { - context.Response.OutputStream.Close(); - context.Response.Close(); - } - } - } - catch (HttpListenerException ex) { - //when the listener is stopped, it will throw an exception for being cancelled, so just ignore it - if (ex.Message != "The I/O operation has been aborted because of either a thread exit or an application request") throw; - } - } - - public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) { - if (disposing && _listener.IsListening) { - _listener.Stop(); - } - } -} diff --git a/test/RestSharp.Tests.Shared/Fixtures/TestHttpServerExtensions.cs b/test/RestSharp.Tests.Shared/Fixtures/TestHttpServerExtensions.cs deleted file mode 100644 index b1202c772..000000000 --- a/test/RestSharp.Tests.Shared/Fixtures/TestHttpServerExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Net; - -namespace RestSharp.Tests.Shared.Fixtures; - -public static class TestHttpServerExtensions { - static readonly Dictionary RequestContent = new(); - - internal static void ClearContent(this HttpListenerRequest request) => RequestContent.Remove(request); - - public static HttpListenerResponse ContentType(this HttpListenerResponse response, string contentType) { - response.ContentType = contentType; - return response; - } - - public static HttpListenerResponse StatusCode(this HttpListenerResponse response, int statusCode) { - response.StatusCode = statusCode; - return response; - } -} \ No newline at end of file diff --git a/test/RestSharp.Tests.Shared/Fixtures/TestRequestHandler.cs b/test/RestSharp.Tests.Shared/Fixtures/TestRequestHandler.cs deleted file mode 100644 index b01569bc9..000000000 --- a/test/RestSharp.Tests.Shared/Fixtures/TestRequestHandler.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Text.RegularExpressions; - -namespace RestSharp.Tests.Shared.Fixtures; - -public class TestRequestHandler { - readonly Regex _comparisonRegex; - - readonly List _urlParameterNames = new(); - - public TestRequestHandler(string url, string httpMethod, HandlerAction handlerAction) { - Url = url; - HttpMethod = httpMethod; - HandlerAction = handlerAction; - - _comparisonRegex = CreateComparisonRegex(url); - } - - public TestRequestHandler(string url, HandlerAction handlerAction) : this(url, null, handlerAction) { } - - string Url { get; } - string HttpMethod { get; } - internal HandlerAction HandlerAction { get; } - - Regex CreateComparisonRegex(string url) { - var regexString = Regex.Escape(url).Replace(@"\{", "{"); - - regexString += regexString.EndsWith("/") ? "?" : "/?"; - regexString = (regexString.StartsWith("/") ? "^" : "^/") + regexString; - - var regex = new Regex(@"{(.*?)}"); - - foreach (Match match in regex.Matches(regexString)) { - regexString = regexString.Replace(match.Value, @"(.*?)"); - _urlParameterNames.Add(match.Groups[1].Value); - } - - regexString += !regexString.Contains(@"\?") ? @"(\?.*)?$" : "$"; - - return new Regex(regexString); - } - - public bool TryMatchUrl(string rawUrl, string httpMethod, out Dictionary parameters) { - var match = _comparisonRegex.Match(rawUrl); - - var isMethodMatched = HttpMethod == null || HttpMethod.Split(',').Contains(httpMethod); - - if (!match.Success || !isMethodMatched) { - parameters = null; - return false; - } - - parameters = new Dictionary(); - - for (var i = 0; i < _urlParameterNames.Count; i++) parameters[_urlParameterNames[i]] = match.Groups[i + 1].Value; - return true; - } -} diff --git a/test/RestSharp.Tests.Integrated/Fixtures/WireMockExtensions.cs b/test/RestSharp.Tests.Shared/Fixtures/WireMockExtensions.cs similarity index 74% rename from test/RestSharp.Tests.Integrated/Fixtures/WireMockExtensions.cs rename to test/RestSharp.Tests.Shared/Fixtures/WireMockExtensions.cs index 26a3f85a5..922a0c207 100644 --- a/test/RestSharp.Tests.Integrated/Fixtures/WireMockExtensions.cs +++ b/test/RestSharp.Tests.Shared/Fixtures/WireMockExtensions.cs @@ -1,6 +1,11 @@ -namespace RestSharp.Tests.Integrated.Fixtures; +using RestSharp.Tests.Integrated.Fixtures; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Server; -static class WireMockExtensions { +namespace RestSharp.Tests.Shared.Fixtures; + +public static class WireMockExtensions { public static RequestBodyCapturer ConfigureBodyCapturer(this WireMockServer server, Method method, bool usePath = true) { var capturer = new RequestBodyCapturer(); diff --git a/test/RestSharp.Tests.Shared/RestSharp.Tests.Shared.csproj b/test/RestSharp.Tests.Shared/RestSharp.Tests.Shared.csproj index 274f12fb9..06f32e8e6 100644 --- a/test/RestSharp.Tests.Shared/RestSharp.Tests.Shared.csproj +++ b/test/RestSharp.Tests.Shared/RestSharp.Tests.Shared.csproj @@ -2,4 +2,10 @@ false + + + + + + From f7f689e5df4814314f75f5b909a4bd868dc0e7db Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Wed, 3 Apr 2024 18:58:58 +0200 Subject: [PATCH 15/24] Fix the download test --- test/RestSharp.Tests.Integrated/DownloadFileTests.cs | 4 ++-- .../RestSharp.Tests.Integrated.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/RestSharp.Tests.Integrated/DownloadFileTests.cs b/test/RestSharp.Tests.Integrated/DownloadFileTests.cs index 2dcae0ac6..a9cd9f6b4 100644 --- a/test/RestSharp.Tests.Integrated/DownloadFileTests.cs +++ b/test/RestSharp.Tests.Integrated/DownloadFileTests.cs @@ -43,7 +43,7 @@ public async Task AdvancedResponseWriter_without_ResponseWriter_reads_stream() { [Fact] public async Task Handles_File_Download_Failure() { - var request = new RestRequest(""); + var request = new RestRequest("some/other/path"); var task = () => _client.DownloadDataAsync(request); await task.Should().ThrowAsync().WithMessage("Request failed with status code NotFound"); } @@ -52,7 +52,7 @@ public async Task Handles_File_Download_Failure() { public async Task Handles_Binary_File_Download() { var request = new RestRequest(""); var response = await _client.DownloadDataAsync(request); - var expected = await File.ReadAllBytesAsync(Path.Combine(_path, "Assets", "Koala.jpg")); + var expected = await File.ReadAllBytesAsync(Path.Combine(_path, Path.Combine(LocalPath.Split('/')))); Assert.Equal(expected, response); } diff --git a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj index 645df0d94..93b466408 100644 --- a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj +++ b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj @@ -1,7 +1,7 @@ enable - net6.0;net7.0 + net6.0;net7.0;net8.0 From 1b11eb2539aae20a8c7115005eb2c08c8441da6b Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Wed, 3 Apr 2024 19:15:04 +0200 Subject: [PATCH 16/24] Disable NTML tests on Linux --- test/RestSharp.Tests.Integrated/NtlmTests.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/RestSharp.Tests.Integrated/NtlmTests.cs b/test/RestSharp.Tests.Integrated/NtlmTests.cs index 240fed016..1a1dc7247 100644 --- a/test/RestSharp.Tests.Integrated/NtlmTests.cs +++ b/test/RestSharp.Tests.Integrated/NtlmTests.cs @@ -4,10 +4,14 @@ namespace RestSharp.Tests.Integrated; +/// +/// These tests use NTML auth and don't work on Linux, at least not in GH Actions +/// public class NtlmTests : CaptureFixture { [Fact] public async Task Does_Not_Pass_Default_Credentials_When_Server_Does_Not_Negotiate() { - if (!OperatingSystem.IsWindows()) return; + if (OperatingSystem.IsLinux()) return; + using var server = SimpleServer.Create(Handlers.Generic()); var client = new RestClient(new RestClientOptions(server.Url) { UseDefaultCredentials = true }); @@ -25,6 +29,8 @@ public async Task Does_Not_Pass_Default_Credentials_When_Server_Does_Not_Negotia [Fact] public async Task Does_Not_Pass_Default_Credentials_When_UseDefaultCredentials_Is_False() { + if (OperatingSystem.IsLinux()) return; + using var server = SimpleServer.Create(Handlers.Generic(), AuthenticationSchemes.Negotiate); var client = new RestClient(new RestClientOptions(server.Url) { UseDefaultCredentials = false }); @@ -37,7 +43,7 @@ public async Task Does_Not_Pass_Default_Credentials_When_UseDefaultCredentials_I [Fact] public async Task Passes_Default_Credentials_When_UseDefaultCredentials_Is_True() { - if (!OperatingSystem.IsWindows()) return; + if (OperatingSystem.IsLinux()) return; using var server = SimpleServer.Create(Handlers.Generic(), AuthenticationSchemes.Negotiate); From 8446276bc5215c1ee37d228132b1e5b541421938 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Wed, 3 Apr 2024 21:38:25 +0200 Subject: [PATCH 17/24] Add matrix --- .github/workflows/pull-request.yml | 56 ++++++++++++++++++++++-------- .github/workflows/test-results.yml | 37 ++++++++++++++++++++ 2 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/test-results.yml diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 2a094274f..7a987bebb 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -7,43 +7,69 @@ permissions: checks: write jobs: + event_file: + name: "Event File" + runs-on: ubuntu-latest + steps: + - name: Upload + uses: actions/upload-artifact@v4 + with: + name: Event File + path: ${{ github.event_path }} test-windows: runs-on: windows-latest + strategy: + matrix: + dotnet: ['net472', 'net6.0', 'net7.0', 'net8.0'] steps: - name: Checkout - uses: actions/checkout@v3 - - - name: Setup .NET - uses: actions/setup-dotnet@v3 - with: - dotnet-version: '8.0' + uses: actions/checkout@v4 +# - +# name: Setup .NET +# uses: actions/setup-dotnet@v3 +# with: +# dotnet-version: '8.0' - name: Run tests - run: dotnet test + run: dotnet test -f ${{ matrix.dotnet }} + - + name: Upload Test Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: Test Results Windows ${{ matrix.dotnet }} + path: | + test-results/**/*.xml + test-results/**/*.trx + test-results/**/*.json test-linux: runs-on: ubuntu-latest + strategy: + matrix: + dotnet: ['net6.0', 'net7.0', 'net8.0'] steps: - name: Checkout uses: actions/checkout@v3 - - - name: Setup .NET - uses: actions/setup-dotnet@v3 - with: - dotnet-version: '8.0' +# - +# name: Setup .NET +# uses: actions/setup-dotnet@v3 +# with: +# dotnet-version: '8.0' - name: Run tests run: dotnet test - - name: Publish test results - uses: EnricoMi/publish-unit-test-result-action@v2 + name: Upload Test Results if: always() + uses: actions/upload-artifact@v4 with: - files: | + name: Test Results Ubuntu ${{ matrix.dotnet }} + path: | test-results/**/*.xml test-results/**/*.trx test-results/**/*.json diff --git a/.github/workflows/test-results.yml b/.github/workflows/test-results.yml new file mode 100644 index 000000000..394bf9dc7 --- /dev/null +++ b/.github/workflows/test-results.yml @@ -0,0 +1,37 @@ +name: Test Results + +on: + workflow_run: + workflows: ["Build and test PRs"] + types: + - completed +permissions: {} + +jobs: + test-results: + name: Test Results + runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion != 'skipped' + + permissions: + checks: write + pull-requests: write + actions: read + + steps: + - + name: Download and Extract Artifacts + uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d + with: + run_id: ${{ github.event.workflow_run.id }} + path: artifacts + - + name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + commit: ${{ github.event.workflow_run.head_sha }} + event_file: artifacts/Event File/event.json + event_name: ${{ github.event.workflow_run.event }} + files: | + artifacts/**/*.xml + artifacts/**/*.trx \ No newline at end of file From 8657a8bba8a70bc8ce91eee4525044f123b7fc47 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Wed, 3 Apr 2024 22:26:50 +0200 Subject: [PATCH 18/24] Try running 472 too --- .github/workflows/pull-request.yml | 2 +- .../RestSharp.Tests.Integrated.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 7a987bebb..ecbf59455 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -62,7 +62,7 @@ jobs: # dotnet-version: '8.0' - name: Run tests - run: dotnet test + run: dotnet test -f ${{ matrix.dotnet }} - name: Upload Test Results if: always() diff --git a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj index 93b466408..13982fddb 100644 --- a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj +++ b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj @@ -1,7 +1,7 @@ enable - net6.0;net7.0;net8.0 + From 251aee8799516af80079cb28169bf3696eda7af6 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Fri, 5 Apr 2024 12:25:44 +0200 Subject: [PATCH 19/24] Refactoring tests --- Directory.Packages.props | 2 + test/Directory.Build.props | 2 +- .../Authentication/AuthenticationTests.cs | 17 +- .../Authentication/OAuth2Tests.cs | 13 +- .../CompressionTests.cs | 2 +- .../RestSharp.Tests.Integrated/CookieTests.cs | 91 +++++++++-- .../DefaultParameterTests.cs | 1 - .../DownloadFileTests.cs | 8 +- .../Fixtures/CookieExtensions.cs | 15 ++ .../HttpClientTests.cs | 11 +- .../HttpHeadersTests.cs | 17 +- .../Interceptor/InterceptorTests.cs | 20 ++- .../JsonBodyTests.cs | 7 +- .../MultipartFormDataTests.cs | 31 +++- .../NonProtocolExceptionHandlingTests.cs | 6 +- test/RestSharp.Tests.Integrated/NtlmTests.cs | 7 +- test/RestSharp.Tests.Integrated/PostTests.cs | 10 +- test/RestSharp.Tests.Integrated/ProxyTests.cs | 10 +- test/RestSharp.Tests.Integrated/PutTests.cs | 29 ++-- .../RedirectTests.cs | 22 +-- .../RequestBodyTests.cs | 42 +---- .../RequestFailureTests.cs | 31 ++-- .../RequestTests.cs | 23 +-- .../ResourceStringParametersTests.cs | 2 +- .../RestSharp.Tests.Integrated.csproj | 11 +- .../RootElementTests.cs | 3 +- .../Server/Handlers/CookieHandlers.cs | 22 +-- .../Server/Handlers/FileHandlers.cs | 78 ++++----- .../Server/Handlers/FormRequest.cs | 24 +-- .../Server/Handlers/HeaderHandlers.cs | 12 +- .../Server/Models.cs | 8 +- .../Server/TestServer.cs | 154 +++++++++--------- .../Server/TestServerFixture.cs | 14 +- .../Server/WireMockTestServer.cs | 135 +++++++++++++++ .../StatusCodeTests.cs | 5 +- .../StructuredSyntaxSuffixTests.cs | 16 +- .../TestResponse.cs | 4 - .../UploadFileTests.cs | 66 +++++++- .../XmlResponseTests.cs | 1 + .../OAuth1Tests.cs | 3 +- test/RestSharp.Tests/RestSharp.Tests.csproj | 1 + 41 files changed, 642 insertions(+), 334 deletions(-) create mode 100644 test/RestSharp.Tests.Integrated/Fixtures/CookieExtensions.cs create mode 100644 test/RestSharp.Tests.Integrated/Server/WireMockTestServer.cs rename test/{RestSharp.Tests.Integrated => RestSharp.Tests}/OAuth1Tests.cs (98%) diff --git a/Directory.Packages.props b/Directory.Packages.props index 129ca2884..df91988b6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,8 +12,10 @@ 8.0.3 + + diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 04eb8a60d..8bbc23b5b 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -3,7 +3,7 @@ true false - net472;net6.0;net7.0;net8.0 + net48;net6.0;net7.0;net8.0 disable xUnit1033 trx%3bLogFileName=$(MSBuildProjectName).trx diff --git a/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs b/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs index 8c9f0081d..0bb8257d3 100644 --- a/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs +++ b/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs @@ -5,15 +5,8 @@ namespace RestSharp.Tests.Integrated.Authentication; -[Collection(nameof(TestServerCollection))] -public class AuthenticationTests { - readonly TestServerFixture _fixture; - readonly ITestOutputHelper _output; - - public AuthenticationTests(TestServerFixture fixture, ITestOutputHelper output) { - _fixture = fixture; - _output = output; - } +public class AuthenticationTests : IDisposable { + readonly WireMockServer _server = WireMockTestServer.StartTestServer(); [Fact] public async Task Can_Authenticate_With_Basic_Http_Auth() { @@ -21,7 +14,7 @@ public async Task Can_Authenticate_With_Basic_Http_Auth() { const string password = "testpassword"; var client = new RestClient( - _fixture.Server.Url, + _server.Url!, o => o.Authenticator = new HttpBasicAuthenticator(userName, password) ); var request = new RestRequest("headers"); @@ -35,4 +28,6 @@ public async Task Can_Authenticate_With_Basic_Http_Auth() { parts[0].Should().Be(userName); parts[1].Should().Be(password); } -} + + public void Dispose() => _server.Dispose(); +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/Authentication/OAuth2Tests.cs b/test/RestSharp.Tests.Integrated/Authentication/OAuth2Tests.cs index 78a1bf985..d9195bfd2 100644 --- a/test/RestSharp.Tests.Integrated/Authentication/OAuth2Tests.cs +++ b/test/RestSharp.Tests.Integrated/Authentication/OAuth2Tests.cs @@ -3,16 +3,13 @@ namespace RestSharp.Tests.Integrated.Authentication; -[Collection(nameof(TestServerCollection))] -public class OAuth2Tests { - readonly TestServerFixture _fixture; - - public OAuth2Tests(TestServerFixture fixture) => _fixture = fixture; - +public class OAuth2Tests : IDisposable { + readonly WireMockServer _server = WireMockTestServer.StartTestServer(); + [Fact] public async Task ShouldHaveProperHeader() { var auth = new OAuth2AuthorizationRequestHeaderAuthenticator("token", "Bearer"); - var client = new RestClient(_fixture.Server.Url, o => o.Authenticator = auth); + var client = new RestClient(_server.Url!, o => o.Authenticator = auth); var response = await client.GetJsonAsync("headers"); var authHeader = response!.FirstOrDefault(x => x.Name == KnownHeaders.Authorization); @@ -20,4 +17,6 @@ public async Task ShouldHaveProperHeader() { authHeader.Should().NotBeNull(); authHeader!.Value.Should().Be("Bearer token"); } + + public void Dispose() => _server.Dispose(); } diff --git a/test/RestSharp.Tests.Integrated/CompressionTests.cs b/test/RestSharp.Tests.Integrated/CompressionTests.cs index 831f2172c..9aefcd8a0 100644 --- a/test/RestSharp.Tests.Integrated/CompressionTests.cs +++ b/test/RestSharp.Tests.Integrated/CompressionTests.cs @@ -8,7 +8,7 @@ public class CompressionTests { static async Task GetBody(Func getStream, string value) { using var memoryStream = new MemoryStream(); - await using (var stream = getStream(memoryStream)) { + using (var stream = getStream(memoryStream)) { stream.WriteStringUtf8(value); } diff --git a/test/RestSharp.Tests.Integrated/CookieTests.cs b/test/RestSharp.Tests.Integrated/CookieTests.cs index 30c4d2743..9c294b237 100644 --- a/test/RestSharp.Tests.Integrated/CookieTests.cs +++ b/test/RestSharp.Tests.Integrated/CookieTests.cs @@ -1,19 +1,32 @@ -using System.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +using RestSharp.Tests.Integrated.Fixtures; using RestSharp.Tests.Integrated.Server; +using WireMock.Types; +using WireMock.Util; namespace RestSharp.Tests.Integrated; -[Collection(nameof(TestServerCollection))] -public class CookieTests { - readonly RestClient _client; - readonly string _host; +public sealed class CookieTests : IDisposable { + readonly RestClient _client; + readonly string _host; + readonly WireMockServer _server = WireMockServer.Start(); - public CookieTests(TestServerFixture fixture) { - var options = new RestClientOptions(fixture.Server.Url) { + public CookieTests() { + var options = new RestClientOptions(_server.Url!) { CookieContainer = new CookieContainer() }; _client = new RestClient(options); _host = _client.Options.BaseUrl!.Host; + + _server + .Given(Request.Create().WithPath("/get-cookies")) + .RespondWith(Response.Create().WithCallback(HandleGetCookies)); + + _server + .Given(Request.Create().WithPath("/set-cookies")) + .RespondWith(Response.Create().WithCallback(HandleSetCookies)); } [Fact] @@ -49,16 +62,15 @@ public async Task Can_Perform_GET_Async_With_Response_Cookies() { response.Content.Should().Be("success"); AssertCookie("cookie1", "value1", x => x == DateTime.MinValue); - FindCookie("cookie2").Should().BeNull("Cookie 2 should vanish as the path will not match"); + response.Cookies.Find("cookie2").Should().BeNull("Cookie 2 should vanish as the path will not match"); AssertCookie("cookie3", "value3", x => x > DateTime.Now); AssertCookie("cookie4", "value4", x => x > DateTime.Now); - FindCookie("cookie5").Should().BeNull("Cookie 5 should vanish as the request is not SSL"); + response.Cookies.Find("cookie5").Should().BeNull("Cookie 5 should vanish as the request is not SSL"); AssertCookie("cookie6", "value6", x => x == DateTime.MinValue, true); - - Cookie? FindCookie(string name) => response.Cookies!.FirstOrDefault(p => p.Name == name); + return; void AssertCookie(string name, string value, Func checkExpiration, bool httpOnly = false) { - var c = FindCookie(name)!; + var c = response.Cookies.Find(name)!; c.Value.Should().Be(value); c.Path.Should().Be("/"); c.Domain.Should().Be(_host); @@ -73,14 +85,65 @@ public async Task GET_Async_With_Response_Cookies_Should_Not_Fail_With_Cookie_Wi var response = await _client.ExecuteAsync(request); response.Content.Should().Be("success"); - var notFoundCookie = FindCookie("cookie_empty_domain"); + var notFoundCookie = response.Cookies.Find("cookie_empty_domain"); notFoundCookie.Should().BeNull(); var emptyDomainCookieHeader = response.Headers! .SingleOrDefault(h => h.Name == KnownHeaders.SetCookie && ((string)h.Value!).StartsWith("cookie_empty_domain")); emptyDomainCookieHeader.Should().NotBeNull(); ((string)emptyDomainCookieHeader!.Value!).Should().Contain("domain=;"); + } - Cookie? FindCookie(string name) => response.Cookies!.FirstOrDefault(p => p.Name == name); + static ResponseMessage HandleGetCookies(IRequestMessage request) { + var response = request.Cookies!.Select(x => $"{x.Key}={x.Value}").ToArray(); + return WireMockTestServer.CreateJson(response); + } + + static ResponseMessage HandleSetCookies(IRequestMessage request) { + var cookies = new List { + new("cookie1", "value1", new CookieOptions()), + new("cookie2", "value2", new CookieOptions { Path = "/path_extra" }), + new("cookie3", "value3", new CookieOptions { Expires = DateTimeOffset.Now.AddDays(2) }), + new("cookie4", "value4", new CookieOptions { MaxAge = TimeSpan.FromSeconds(100) }), + new("cookie5", "value5", new CookieOptions { Secure = true }), + new("cookie6", "value6", new CookieOptions { HttpOnly = true }), + new("cookie_empty_domain", "value_empty_domain", new CookieOptions { HttpOnly = true, Domain = string.Empty }) + }; + + var response = new ResponseMessage { + Headers = new Dictionary>(), + BodyData = new BodyData { + DetectedBodyType = BodyType.String, + BodyAsString = "success" + } + }; + + var valuesList = new WireMockList(); + valuesList.AddRange(cookies.Select(cookie => cookie.Options.GetHeader(cookie.Name, cookie.Value))); + response.Headers.Add(KnownHeaders.SetCookie, valuesList); + + return response; + } + + record CookieInternal(string Name, string Value, CookieOptions Options); + + public void Dispose() { + _client.Dispose(); + _server.Dispose(); } } + +static class CookieExtensions { + public static string GetHeader(this CookieOptions self, string name, string value) { + var cookieHeader = new SetCookieHeaderValue((StringSegment)name, (StringSegment)value) { + Domain = (StringSegment)self.Domain, + Path = (StringSegment)self.Path, + Expires = self.Expires, + Secure = self.Secure, + HttpOnly = self.HttpOnly, + MaxAge = self.MaxAge, + SameSite = (Microsoft.Net.Http.Headers.SameSiteMode)self.SameSite + }; + return cookieHeader.ToString(); + } +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs b/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs index 12ee9dc18..ec9aaefd2 100644 --- a/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs +++ b/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs @@ -4,7 +4,6 @@ namespace RestSharp.Tests.Integrated; -[Collection(nameof(TestServerCollection))] public sealed class DefaultParameterTests : IDisposable { readonly WireMockServer _server = WireMockServer.Start(); readonly RequestBodyCapturer _capturer; diff --git a/test/RestSharp.Tests.Integrated/DownloadFileTests.cs b/test/RestSharp.Tests.Integrated/DownloadFileTests.cs index a9cd9f6b4..686cc0a0e 100644 --- a/test/RestSharp.Tests.Integrated/DownloadFileTests.cs +++ b/test/RestSharp.Tests.Integrated/DownloadFileTests.cs @@ -31,7 +31,7 @@ public async Task AdvancedResponseWriter_without_ResponseWriter_reads_stream() { AdvancedResponseWriter = (response, request) => { var buf = new byte[16]; // ReSharper disable once MustUseReturnValue - response.Content.ReadAsStream().Read(buf, 0, buf.Length); + response.Content.ReadAsStreamAsync().GetAwaiter().GetResult().Read(buf, 0, buf.Length); tag = Encoding.ASCII.GetString(buf, 6, 4); return new RestResponse(request); } @@ -52,7 +52,7 @@ public async Task Handles_File_Download_Failure() { public async Task Handles_Binary_File_Download() { var request = new RestRequest(""); var response = await _client.DownloadDataAsync(request); - var expected = await File.ReadAllBytesAsync(Path.Combine(_path, Path.Combine(LocalPath.Split('/')))); + var expected = File.ReadAllBytes(Path.Combine(_path, Path.Combine(LocalPath.Split('/')))); Assert.Equal(expected, response); } @@ -73,8 +73,8 @@ public async Task Writes_Response_To_Stream() { Assert.Null(response); - var fromTemp = await File.ReadAllBytesAsync(tempFile); - var expected = await File.ReadAllBytesAsync(Path.Combine(_path, Path.Combine(LocalPath.Split('/')))); + var fromTemp = File.ReadAllBytes(tempFile); + var expected = File.ReadAllBytes(Path.Combine(_path, Path.Combine(LocalPath.Split('/')))); Assert.Equal(expected, fromTemp); } diff --git a/test/RestSharp.Tests.Integrated/Fixtures/CookieExtensions.cs b/test/RestSharp.Tests.Integrated/Fixtures/CookieExtensions.cs new file mode 100644 index 000000000..b6245d7c5 --- /dev/null +++ b/test/RestSharp.Tests.Integrated/Fixtures/CookieExtensions.cs @@ -0,0 +1,15 @@ +using System.Net; + +namespace RestSharp.Tests.Integrated.Fixtures; + +static class CookieExtensions { + public static Cookie? Find(this CookieCollection? cookieCollection, string name) { + if (cookieCollection == null) return null; + for (var i = 0; i < cookieCollection.Count; i++) { + var cookie = cookieCollection[i]; + if (cookie.Name == name) return cookie; + } + + return null; + } +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/HttpClientTests.cs b/test/RestSharp.Tests.Integrated/HttpClientTests.cs index 91a86f651..8f1ca4f2b 100644 --- a/test/RestSharp.Tests.Integrated/HttpClientTests.cs +++ b/test/RestSharp.Tests.Integrated/HttpClientTests.cs @@ -3,20 +3,21 @@ namespace RestSharp.Tests.Integrated; -[Collection(nameof(TestServerCollection))] -public class HttpClientTests(TestServerFixture fixture) { +public class HttpClientTests : IDisposable { + readonly WireMockServer _server = WireMockTestServer.StartTestServer(); + [Fact] public async Task ShouldUseBaseAddress() { using var httpClient = new HttpClient(); - httpClient.BaseAddress = fixture.Server.Url; + httpClient.BaseAddress = new Uri(_server.Url!); using var client = new RestClient(httpClient); var request = new RestRequest("success"); - var response = await client.ExecuteAsync(request); + var response = await client.ExecuteAsync(request); response.StatusCode.Should().Be(HttpStatusCode.OK); response.Data!.Message.Should().Be("Works!"); } - record Response(string Message); + public void Dispose() => _server.Dispose(); } diff --git a/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs b/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs index 8abf40c53..c943b4159 100644 --- a/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs +++ b/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs @@ -1,11 +1,14 @@ using System.Net; using RestSharp.Tests.Integrated.Server; +using WireMock.Util; namespace RestSharp.Tests.Integrated; -[Collection(nameof(TestServerCollection))] -public class HttpHeadersTests(TestServerFixture fixture) { - readonly RestClient _client = new(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = true }); +public class HttpHeadersTests : IDisposable { + readonly WireMockServer _server = WireMockTestServer.StartTestServer(); + readonly RestClient _client; + + public HttpHeadersTests() => _client = new RestClient(new RestClientOptions(_server.Url!) { ThrowOnAnyError = true }); [Fact] public async Task Ensure_headers_correctly_set_in_the_interceptor() { @@ -43,6 +46,7 @@ public async Task Ensure_headers_correctly_set_in_the_hook() { // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Data.Should().NotBeNull(); var header = response.Data!.First(x => x.Name == headerName); header.Should().NotBeNull(); header.Value.Should().Be(headerValue); @@ -75,7 +79,12 @@ record Header(string Name, string Value); class HeaderInterceptor(string headerName, string headerValue) : Interceptors.Interceptor { public override ValueTask BeforeHttpRequest(HttpRequestMessage requestMessage, CancellationToken cancellationToken) { requestMessage.Headers.Add(headerName, headerValue); - return ValueTask.CompletedTask; + return default; } } + + public void Dispose() { + _server.Dispose(); + _client.Dispose(); + } } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/Interceptor/InterceptorTests.cs b/test/RestSharp.Tests.Integrated/Interceptor/InterceptorTests.cs index ee70683b3..5183f4e09 100644 --- a/test/RestSharp.Tests.Integrated/Interceptor/InterceptorTests.cs +++ b/test/RestSharp.Tests.Integrated/Interceptor/InterceptorTests.cs @@ -2,8 +2,10 @@ namespace RestSharp.Tests.Integrated.Interceptor; -[Collection(nameof(TestServerCollection))] -public class InterceptorTests(TestServerFixture fixture) { +// [Collection(nameof(TestServerCollection))] +public class InterceptorTests : IDisposable { + readonly WireMockServer _server = WireMockTestServer.StartTestServer(); + [Fact] public async Task Should_call_client_interceptor() { // Arrange @@ -32,7 +34,7 @@ public async Task Should_call_request_interceptor() { // Arrange var request = CreateRequest(); - var client = new RestClient(fixture.Server.Url); + var client = new RestClient(_server.Url!); var interceptor = new TestInterceptor(); request.Interceptors = new List { interceptor }; @@ -79,6 +81,8 @@ public async Task ThrowExceptionIn_InterceptBeforeRequest() { interceptor.AfterHttpRequestCalled.Should().BeFalse(); interceptor.AfterRequestCalled.Should().BeFalse(); interceptor.BeforeDeserializationCalled.Should().BeFalse(); + + client.Dispose(); } [Fact] @@ -97,6 +101,8 @@ public async Task ThrowExceptionIn_InterceptBeforeHttpRequest() { interceptor.AfterHttpRequestCalled.Should().BeFalse(); interceptor.AfterRequestCalled.Should().BeFalse(); interceptor.BeforeDeserializationCalled.Should().BeFalse(); + + client.Dispose(); } [Fact] @@ -115,6 +121,8 @@ public async Task ThrowException_InInterceptAfterHttpRequest() { interceptor.AfterHttpRequestCalled.Should().BeTrue(); interceptor.AfterRequestCalled.Should().BeFalse(); interceptor.BeforeDeserializationCalled.Should().BeFalse(); + + client.Dispose(); } [Fact] @@ -133,13 +141,15 @@ public async Task ThrowExceptionIn_InterceptAfterRequest() { interceptor.AfterHttpRequestCalled.Should().BeTrue(); interceptor.AfterRequestCalled.Should().BeTrue(); interceptor.BeforeDeserializationCalled.Should().BeFalse(); + + client.Dispose(); } (RestClient client, TestInterceptor interceptor) SetupClient(Action? configureInterceptor = null) { var interceptor = new TestInterceptor(); configureInterceptor?.Invoke(interceptor); - var options = new RestClientOptions(fixture.Server.Url) { + var options = new RestClientOptions(_server.Url!) { Interceptors = new List { interceptor } }; return (new RestClient(options), interceptor); @@ -149,6 +159,8 @@ static RestRequest CreateRequest() { var body = new TestRequest("foo", 100); return new RestRequest("post/json").AddJsonBody(body); } + + public void Dispose() => _server.Dispose(); } static class InterceptorChecks { diff --git a/test/RestSharp.Tests.Integrated/JsonBodyTests.cs b/test/RestSharp.Tests.Integrated/JsonBodyTests.cs index bc7efb201..9e05ba6b8 100644 --- a/test/RestSharp.Tests.Integrated/JsonBodyTests.cs +++ b/test/RestSharp.Tests.Integrated/JsonBodyTests.cs @@ -5,7 +5,7 @@ namespace RestSharp.Tests.Integrated; #pragma warning disable xUnit1033 -public sealed class JsonBodyTests { +public sealed class JsonBodyTests : IDisposable { readonly WireMockServer _server = WireMockServer.Start(); readonly RestClient _client; @@ -61,4 +61,9 @@ public async Task Add_JSON_body_string() { capturer.ContentType.Should().Be("application/json; charset=utf-8"); capturer.Body.Should().Be(expected); } + + public void Dispose() { + _server.Dispose(); + _client.Dispose(); + } } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs b/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs index 93ae61130..c79f465b9 100644 --- a/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs +++ b/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs @@ -18,7 +18,10 @@ public MultipartFormDataTests(ITestOutputHelper output) { _client = new RestClient(options); } - public void Dispose() => _server.Dispose(); + public void Dispose() { + _server.Dispose(); + _client.Dispose(); + } const string LineBreak = "\r\n"; @@ -176,4 +179,30 @@ public async Task MultipartFormDataAsync() { _capturer.Body.Should().Be(expected); } + + [Fact] + public async Task MultipartFormData_Without_File_Creates_A_Valid_RequestBody() { + using var client = new RestClient(_server.Url!); + + var request = new RestRequest(RequestBodyCapturer.Resource, Method.Post) { + AlwaysMultipartFormData = true + }; + var capturer = _server.ConfigureBodyCapturer(Method.Post); + + const string bodyData = "abc123 foo bar baz BING!"; + const string multipartName = "mybody"; + + request.AddParameter(new BodyParameter(multipartName, bodyData, ContentType.Plain)); + + await client.ExecuteAsync(request); + + var expectedBody = new[] { + $"{KnownHeaders.ContentType}: {ContentType.Plain}", + $"{KnownHeaders.ContentDisposition}: form-data; name={multipartName}", + bodyData + }; + + var actual = capturer.Body!.Split('\r'); + actual.Should().Contain(expectedBody); + } } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs b/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs index d1f3caef3..a05fe1dd7 100644 --- a/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs +++ b/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs @@ -1,4 +1,6 @@ -namespace RestSharp.Tests.Integrated; +using RestSharp.Tests.Integrated.Server; + +namespace RestSharp.Tests.Integrated; public sealed class NonProtocolExceptionHandlingTests : IDisposable { public NonProtocolExceptionHandlingTests() @@ -54,7 +56,7 @@ public async Task Handles_Server_Timeout_Error() { public async Task Handles_Server_Timeout_Error_With_Deserializer() { var client = new RestClient(_server.Url!); var request = new RestRequest("timeout") { Timeout = 500 }; - var response = await client.ExecuteAsync(request); + var response = await client.ExecuteAsync(request); response.Data.Should().BeNull(); response.ErrorException.Should().BeOfType(); diff --git a/test/RestSharp.Tests.Integrated/NtlmTests.cs b/test/RestSharp.Tests.Integrated/NtlmTests.cs index 1a1dc7247..f1a4a1b87 100644 --- a/test/RestSharp.Tests.Integrated/NtlmTests.cs +++ b/test/RestSharp.Tests.Integrated/NtlmTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Runtime.InteropServices; using RestSharp.Tests.Integrated.Fixtures; using RestSharp.Tests.Shared.Fixtures; @@ -10,7 +11,7 @@ namespace RestSharp.Tests.Integrated; public class NtlmTests : CaptureFixture { [Fact] public async Task Does_Not_Pass_Default_Credentials_When_Server_Does_Not_Negotiate() { - if (OperatingSystem.IsLinux()) return; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return; using var server = SimpleServer.Create(Handlers.Generic()); @@ -29,7 +30,7 @@ public async Task Does_Not_Pass_Default_Credentials_When_Server_Does_Not_Negotia [Fact] public async Task Does_Not_Pass_Default_Credentials_When_UseDefaultCredentials_Is_False() { - if (OperatingSystem.IsLinux()) return; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return; using var server = SimpleServer.Create(Handlers.Generic(), AuthenticationSchemes.Negotiate); @@ -43,7 +44,7 @@ public async Task Does_Not_Pass_Default_Credentials_When_UseDefaultCredentials_I [Fact] public async Task Passes_Default_Credentials_When_UseDefaultCredentials_Is_True() { - if (OperatingSystem.IsLinux()) return; + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; using var server = SimpleServer.Create(Handlers.Generic(), AuthenticationSchemes.Negotiate); diff --git a/test/RestSharp.Tests.Integrated/PostTests.cs b/test/RestSharp.Tests.Integrated/PostTests.cs index 7dc4325e7..b695c80a5 100644 --- a/test/RestSharp.Tests.Integrated/PostTests.cs +++ b/test/RestSharp.Tests.Integrated/PostTests.cs @@ -3,9 +3,11 @@ namespace RestSharp.Tests.Integrated; -[Collection(nameof(TestServerCollection))] -public class PostTests(TestServerFixture fixture) { - readonly RestClient _client = new(fixture.Server.Url); +public class PostTests { + readonly WireMockServer _server = WireMockTestServer.StartTestServer(); + readonly RestClient _client; + + public PostTests() => _client = new RestClient(_server.Url!); [Fact] public async Task Should_post_json() { @@ -73,4 +75,4 @@ class Response { } record PostParameter(string Name, string Value); -} +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/ProxyTests.cs b/test/RestSharp.Tests.Integrated/ProxyTests.cs index 67cc0f50d..5562fe3fd 100644 --- a/test/RestSharp.Tests.Integrated/ProxyTests.cs +++ b/test/RestSharp.Tests.Integrated/ProxyTests.cs @@ -1,16 +1,12 @@ -using System.Net; -using RestSharp.Tests.Shared.Fixtures; - -namespace RestSharp.Tests.Integrated; +namespace RestSharp.Tests.Integrated; public class ProxyTests { [Fact] public async Task Set_Invalid_Proxy_Fails() { using var server = WireMockServer.Start(); + using var client = new RestClient(new RestClientOptions(server.Url!) { Proxy = new WebProxy("non_existent_proxy", false) }); - var client = new RestClient(new RestClientOptions(server.Url!) { Proxy = new WebProxy("non_existent_proxy", false) }); - var request = new RestRequest(); - + var request = new RestRequest(); var response = await client.ExecuteAsync(request); response.IsSuccessful.Should().BeFalse(); diff --git a/test/RestSharp.Tests.Integrated/PutTests.cs b/test/RestSharp.Tests.Integrated/PutTests.cs index 19b238d33..2bd2c9bd0 100644 --- a/test/RestSharp.Tests.Integrated/PutTests.cs +++ b/test/RestSharp.Tests.Integrated/PutTests.cs @@ -1,19 +1,22 @@ using System.Text.Json; using RestSharp.Tests.Integrated.Server; -using static RestSharp.Tests.Integrated.Server.HttpServer; -namespace RestSharp.Tests.Integrated; +// using static RestSharp.Tests.Integrated.Server.HttpServer; -[Collection(nameof(TestServerCollection))] -public class PutTests(TestServerFixture fixture) { - readonly RestClient _client = new(fixture.Server.Url); +namespace RestSharp.Tests.Integrated; + +public class PutTests : IDisposable { + readonly WireMockServer _server = WireMockTestServer.StartTestServer(); + readonly RestClient _client; + + public PutTests() => _client = new(_server.Url!); static readonly JsonSerializerOptions Options = new(JsonSerializerDefaults.Web); [Fact] public async Task Should_put_json_body() { - var body = new TestRequest("foo", 100); - var request = new RestRequest(ContentResource).AddJsonBody(body); + var body = new TestRequest("foo", 100); + var request = new RestRequest("/content").AddJsonBody(body); var response = await _client.PutAsync(request); @@ -24,14 +27,14 @@ public async Task Should_put_json_body() { [Fact] public async Task Should_put_json_body_using_extension() { var body = new TestRequest("foo", 100); - var response = await _client.PutJsonAsync(ContentResource, body); - + var response = await _client.PutJsonAsync("/content", body); + response.Should().BeEquivalentTo(body); } [Fact] public async Task Can_Timeout_PUT_Async() { - var request = new RestRequest(TimeoutResource, Method.Put).AddBody("Body_Content"); + var request = new RestRequest("/timeout", Method.Put).AddBody("Body_Content"); // Half the value of ResponseHandler.Timeout request.Timeout = 200; @@ -40,7 +43,11 @@ public async Task Can_Timeout_PUT_Async() { Assert.Equal(ResponseStatus.TimedOut, response.ResponseStatus); } - + + public void Dispose() { + _server.Dispose(); + _client.Dispose(); + } } public record TestRequest(string Data, int Number); \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/RedirectTests.cs b/test/RestSharp.Tests.Integrated/RedirectTests.cs index 0abadb358..7b00bb4a7 100644 --- a/test/RestSharp.Tests.Integrated/RedirectTests.cs +++ b/test/RestSharp.Tests.Integrated/RedirectTests.cs @@ -13,14 +13,15 @@ // limitations under the License. // -using System.Net; -using RestSharp.Tests.Integrated.Server; - namespace RestSharp.Tests.Integrated; -[Collection(nameof(TestServerCollection))] -public class RedirectTests(TestServerFixture fixture) { - readonly RestClient _client = new(new RestClientOptions(fixture.Server.Url) { FollowRedirects = true }); +using Server; + +public class RedirectTests : IDisposable { + readonly WireMockServer _server = WireMockTestServer.StartTestServer(); + readonly RestClient _client; + + public RedirectTests() => _client = new RestClient(new RestClientOptions(_server.Url!) { FollowRedirects = true }); [Fact] public async Task Can_Perform_GET_Async_With_Redirect() { @@ -28,12 +29,13 @@ public async Task Can_Perform_GET_Async_With_Redirect() { var request = new RestRequest("redirect"); - var response = await _client.ExecuteAsync(request); + var response = await _client.ExecuteAsync(request); response.StatusCode.Should().Be(HttpStatusCode.OK); response.Data!.Message.Should().Be(val); } - class Response { - public string? Message { get; set; } + public void Dispose() { + _server.Dispose(); + _client.Dispose(); } -} +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/RequestBodyTests.cs b/test/RestSharp.Tests.Integrated/RequestBodyTests.cs index 47ba86f04..3897d9321 100644 --- a/test/RestSharp.Tests.Integrated/RequestBodyTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestBodyTests.cs @@ -3,24 +3,23 @@ namespace RestSharp.Tests.Integrated; -public class RequestBodyTests { - const string NewLine = "\r\n"; +public class RequestBodyTests : IDisposable { + // const string NewLine = "\r\n"; - const string TextPlainContentType = "text/plain"; - const string ExpectedTextContentType = $"{TextPlainContentType}; charset=utf-8"; - const string ExpectedTextContentTypeNoCharset = TextPlainContentType; + static readonly string ExpectedTextContentType = $"{ContentType.Plain}; charset=utf-8"; + static readonly string ExpectedTextContentTypeNoCharset = ContentType.Plain; readonly WireMockServer _server = WireMockServer.Start(s => s.AllowBodyForAllHttpMethods = true); async Task AssertBody(Method method, bool disableCharset = false) { var options = new RestClientOptions(_server.Url!) { DisableCharset = disableCharset }; - var client = new RestClient(options); + using var client = new RestClient(options); var request = new RestRequest(RequestBodyCapturer.Resource, method); var capturer = _server.ConfigureBodyCapturer(method); const string bodyData = "abc123 foo bar baz BING!"; - request.AddBody(bodyData, TextPlainContentType); + request.AddBody(bodyData, ContentType.Plain); await client.ExecuteAsync(request); @@ -56,7 +55,7 @@ async Task AssertBody(Method method, bool disableCharset = false) { public async Task Can_Have_No_Body_Added_To_POST_Request() { const Method httpMethod = Method.Post; - var client = new RestClient(_server.Url!); + using var client = new RestClient(_server.Url!); var request = new RestRequest(RequestBodyCapturer.Resource, httpMethod); var capturer = _server.ConfigureBodyCapturer(httpMethod); @@ -71,31 +70,6 @@ public async Task Can_Have_No_Body_Added_To_POST_Request() { [Fact] public Task Can_Be_Added_To_HEAD_Request() => AssertBody(Method.Head); - [Fact] - public async Task MultipartFormData_Without_File_Creates_A_Valid_RequestBody() { - var client = new RestClient(_server.Url!); - - var request = new RestRequest(RequestBodyCapturer.Resource, Method.Post) { - AlwaysMultipartFormData = true - }; - var capturer = _server.ConfigureBodyCapturer(Method.Post); - - const string bodyData = "abc123 foo bar baz BING!"; - const string multipartName = "mybody"; - - request.AddParameter(new BodyParameter(multipartName, bodyData, TextPlainContentType)); - - await client.ExecuteAsync(request); - - var expectedBody = new[] { - $"{KnownHeaders.ContentType}: {ExpectedTextContentType}", - $"{KnownHeaders.ContentDisposition}: form-data; name={multipartName}", - bodyData - }; - - var actual = capturer.Body!.Split(NewLine); - actual.Should().Contain(expectedBody); - } static void AssertHasNoRequestBody(RequestBodyCapturer capturer) { capturer.ContentType.Should().BeNull(); @@ -108,4 +82,6 @@ static void AssertHasRequestBody(RequestBodyCapturer capturer, string contentTyp capturer.HasBody.Should().BeTrue(); capturer.Body.Should().Be(bodyData); } + + public void Dispose() => _server.Dispose(); } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/RequestFailureTests.cs b/test/RestSharp.Tests.Integrated/RequestFailureTests.cs index 26954e86e..1ff517fdc 100644 --- a/test/RestSharp.Tests.Integrated/RequestFailureTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestFailureTests.cs @@ -1,13 +1,14 @@ -using System.Net; -using RestSharp.Tests.Integrated.Server; - // ReSharper disable ClassNeverInstantiated.Local namespace RestSharp.Tests.Integrated; -[Collection(nameof(TestServerCollection))] -public class RequestFailureTests(TestServerFixture fixture) { - readonly RestClient _client = new(fixture.Server.Url); +using Server; + +public class RequestFailureTests : IDisposable { + readonly WireMockServer _server = WireMockTestServer.StartTestServer(); + readonly RestClient _client; + + public RequestFailureTests() => _client = new RestClient(_server.Url!); [Fact] public async Task Handles_GET_Request_Errors() { @@ -20,7 +21,7 @@ public async Task Handles_GET_Request_Errors() { [Fact] public async Task Handles_GET_Request_Errors_With_Response_Type() { var request = new RestRequest("status?code=404"); - var response = await _client.ExecuteAsync(request); + var response = await _client.ExecuteAsync(request); response.StatusCode.Should().Be(HttpStatusCode.NotFound); response.Data.Should().Be(null); @@ -28,10 +29,10 @@ public async Task Handles_GET_Request_Errors_With_Response_Type() { [Fact] public async Task Throws_on_unsuccessful_call() { - var client = new RestClient(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = true }); + using var client = new RestClient(new RestClientOptions(_server.Url!) { ThrowOnAnyError = true }); var request = new RestRequest("status?code=500"); - var task = () => client.ExecuteAsync(request); + var task = () => client.ExecuteAsync(request); await task.Should().ThrowExactlyAsync(); } @@ -56,7 +57,7 @@ public async Task GetAsync_completes_on_404() { public async Task GetAsync_generic_throws_on_unsuccessful_call() { var request = new RestRequest("status?code=500"); - var task = () => _client.GetAsync(request); + var task = () => _client.GetAsync(request); await task.Should().ThrowExactlyAsync(); } @@ -64,12 +65,12 @@ public async Task GetAsync_generic_throws_on_unsuccessful_call() { public async Task GetAsync_returns_null_on_404() { var request = new RestRequest("status?code=404"); - var response = await _client.GetAsync(request); + var response = await _client.GetAsync(request); response.Should().BeNull(); } - class Response { - // ReSharper disable once UnusedMember.Local - public string Message { get; set; } = null!; + public void Dispose() { + _server.Dispose(); + _client.Dispose(); } -} +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/RequestTests.cs b/test/RestSharp.Tests.Integrated/RequestTests.cs index a4f07b092..d80a52684 100644 --- a/test/RestSharp.Tests.Integrated/RequestTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestTests.cs @@ -3,13 +3,11 @@ namespace RestSharp.Tests.Integrated; -[Collection(nameof(TestServerCollection))] -public class AsyncTests(TestServerFixture fixture) { - readonly RestClient _client = new(fixture.Server.Url); - - class Response { - public string Message { get; set; } = null!; - } +public class AsyncTests : IDisposable { + readonly WireMockServer _server = WireMockTestServer.StartTestServer(); + readonly RestClient _client; + + public AsyncTests() => _client = new RestClient(_server.Url!); [Fact] public async Task Can_Handle_Exception_Thrown_By_Interceptor_BeforeDeserialization() { @@ -42,7 +40,7 @@ public async Task Can_Handle_Exception_Thrown_By_OnBeforeDeserialization_Handler [Fact] public async Task Can_Perform_ExecuteGetAsync_With_Response_Type() { var request = new RestRequest("success"); - var response = await _client.ExecuteAsync(request); + var response = await _client.ExecuteAsync(request); response.StatusCode.Should().Be(HttpStatusCode.OK); response.Data!.Message.Should().Be("Works!"); @@ -73,7 +71,7 @@ public async Task Can_Timeout_GET_Async() { [Fact] public async Task Can_Perform_Delete_With_Response_Type() { var request = new RestRequest("delete"); - var response = await _client.ExecuteAsync(request, Method.Delete); + var response = await _client.ExecuteAsync(request, Method.Delete); response.StatusCode.Should().Be(HttpStatusCode.OK); response.Data!.Message.Should().Be("Works!"); @@ -82,7 +80,7 @@ public async Task Can_Perform_Delete_With_Response_Type() { [Fact] public async Task Can_Delete_With_Response_Type_using_extension() { var request = new RestRequest("delete"); - var response = await _client.DeleteAsync(request); + var response = await _client.DeleteAsync(request); response!.Message.Should().Be("Works!"); } @@ -91,4 +89,9 @@ class ThrowingInterceptor(string errorMessage) : Interceptors.Interceptor { public override ValueTask BeforeDeserialization(RestResponse response, CancellationToken cancellationToken) => throw new Exception(errorMessage); } + + public void Dispose() { + _server.Dispose(); + _client.Dispose(); + } } \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs b/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs index dab4271c6..e046cf926 100644 --- a/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs +++ b/test/RestSharp.Tests.Integrated/ResourceStringParametersTests.cs @@ -17,7 +17,7 @@ public async Task Should_keep_to_parameters_with_the_same_name() { return new ResponseMessage(); })); - var client = new RestClient(_server.Url!); + using var client = new RestClient(_server.Url!); var request = new RestRequest(parameters); await client.GetAsync(request); diff --git a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj index 13982fddb..c1e098fe1 100644 --- a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj +++ b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj @@ -1,7 +1,7 @@ enable - + @@ -15,11 +15,13 @@ + - + - - + + + @@ -29,5 +31,6 @@ + \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/RootElementTests.cs b/test/RestSharp.Tests.Integrated/RootElementTests.cs index 3e9a0feba..0285558f5 100644 --- a/test/RestSharp.Tests.Integrated/RootElementTests.cs +++ b/test/RestSharp.Tests.Integrated/RootElementTests.cs @@ -1,4 +1,5 @@ using RestSharp.Serializers.Xml; +using RestSharp.Tests.Integrated.Server; namespace RestSharp.Tests.Integrated; @@ -20,7 +21,7 @@ public async Task Copy_RootElement_From_Request_To_IWithRootElement_Deserializer .Given(Request.Create().WithPath("/success")) .RespondWith(Response.Create().WithBody(xmlBody).WithHeader(KnownHeaders.ContentType, ContentType.Xml)); - var client = new RestClient(server.Url!, configureSerialization: cfg => cfg.UseXmlSerializer()); + using var client = new RestClient(server.Url!, configureSerialization: cfg => cfg.UseXmlSerializer()); var request = new RestRequest("success") { RootElement = "Success" }; diff --git a/test/RestSharp.Tests.Integrated/Server/Handlers/CookieHandlers.cs b/test/RestSharp.Tests.Integrated/Server/Handlers/CookieHandlers.cs index 8dd64962a..40cfc3f25 100644 --- a/test/RestSharp.Tests.Integrated/Server/Handlers/CookieHandlers.cs +++ b/test/RestSharp.Tests.Integrated/Server/Handlers/CookieHandlers.cs @@ -3,17 +3,17 @@ namespace RestSharp.Tests.Integrated.Server.Handlers; public static class CookieHandlers { - public static IResult HandleCookies(HttpContext ctx) { - var results = new List(); + // public static IResult HandleCookies(HttpContext ctx) { + // var results = new List(); + // + // foreach (var (key, value) in ctx.Request.Cookies) { + // results.Add($"{key}={value}"); + // } + // + // return Results.Ok(results); + // } - foreach (var (key, value) in ctx.Request.Cookies) { - results.Add($"{key}={value}"); - } - - return Results.Ok(results); - } - - public static IResult HandleSetCookies(HttpContext ctx) { + public static void HandleSetCookies(HttpContext ctx) { ctx.Response.Cookies.Append("cookie1", "value1"); ctx.Response.Cookies.Append( @@ -65,6 +65,6 @@ public static IResult HandleSetCookies(HttpContext ctx) { } ); - return Results.Content("success"); + // return Results.Content("success"); } } diff --git a/test/RestSharp.Tests.Integrated/Server/Handlers/FileHandlers.cs b/test/RestSharp.Tests.Integrated/Server/Handlers/FileHandlers.cs index 56740fc7f..88ec8fb02 100644 --- a/test/RestSharp.Tests.Integrated/Server/Handlers/FileHandlers.cs +++ b/test/RestSharp.Tests.Integrated/Server/Handlers/FileHandlers.cs @@ -1,39 +1,39 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using RestSharp.Extensions; - -namespace RestSharp.Tests.Integrated.Server.Handlers; - -[ApiController] -public class UploadController : ControllerBase { - [HttpPost] - [Route("upload")] - [SuppressMessage("Performance", "CA1822:Mark members as static")] - public async Task Upload([FromForm] FormFile formFile, [FromQuery] bool checkFile = true) { - var file = formFile.File; - - if (!checkFile) { - return Ok(new UploadResponse(file.FileName, file.Length, true)); - } - - var assetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets"); - - await using var stream = file.OpenReadStream(); - - var received = await stream.ReadAsBytes(default); - - try { - var expected = await System.IO.File.ReadAllBytesAsync(Path.Combine(assetPath, file.FileName)); - var response = new UploadResponse(file.FileName, file.Length, received.SequenceEqual(expected)); - return Ok(response); - } - catch (Exception e) { - return BadRequest(new { Message = e.Message, Filename = file.FileName }); - } - } -} - -public class FormFile { - public IFormFile File { get; set; } = null!; -} +// using System.Diagnostics.CodeAnalysis; +// using Microsoft.AspNetCore.Http; +// using Microsoft.AspNetCore.Mvc; +// using RestSharp.Extensions; +// +// namespace RestSharp.Tests.Integrated.Server.Handlers; +// +// [ApiController] +// public class UploadController : ControllerBase { +// [HttpPost] +// [Route("upload")] +// [SuppressMessage("Performance", "CA1822:Mark members as static")] +// public async Task Upload([FromForm] FormFile formFile, [FromQuery] bool checkFile = true) { +// var file = formFile.File; +// +// if (!checkFile) { +// return Ok(new UploadResponse(file.FileName, file.Length, true)); +// } +// +// var assetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets"); +// +// await using var stream = file.OpenReadStream(); +// +// var received = await stream.ReadAsBytes(default); +// +// try { +// var expected = await System.IO.File.ReadAllBytesAsync(Path.Combine(assetPath, file.FileName)); +// var response = new UploadResponse(file.FileName, file.Length, received.SequenceEqual(expected)); +// return Ok(response); +// } +// catch (Exception e) { +// return BadRequest(new { Message = e.Message, Filename = file.FileName }); +// } +// } +// } +// +// public class FormFile { +// public IFormFile File { get; set; } = null!; +// } diff --git a/test/RestSharp.Tests.Integrated/Server/Handlers/FormRequest.cs b/test/RestSharp.Tests.Integrated/Server/Handlers/FormRequest.cs index a70e88117..889151cc9 100644 --- a/test/RestSharp.Tests.Integrated/Server/Handlers/FormRequest.cs +++ b/test/RestSharp.Tests.Integrated/Server/Handlers/FormRequest.cs @@ -1,12 +1,12 @@ -using Microsoft.AspNetCore.Http; - -namespace RestSharp.Tests.Integrated.Server.Handlers; - -public static class FormRequestHandler { - public static IResult HandleForm(HttpContext ctx) { - var response = ctx.Request.Form.Select( - x => new TestServerResponse(x.Key, x.Value!) - ); - return Results.Ok(response); - } -} +// using Microsoft.AspNetCore.Http; +// +// namespace RestSharp.Tests.Integrated.Server.Handlers; +// +// public static class FormRequestHandler { +// public static IResult HandleForm(HttpContext ctx) { +// var response = ctx.Request.Form.Select( +// x => new TestServerResponse(x.Key, x.Value!) +// ); +// return Results.Ok(response); +// } +// } diff --git a/test/RestSharp.Tests.Integrated/Server/Handlers/HeaderHandlers.cs b/test/RestSharp.Tests.Integrated/Server/Handlers/HeaderHandlers.cs index d687be698..4df2a1f24 100644 --- a/test/RestSharp.Tests.Integrated/Server/Handlers/HeaderHandlers.cs +++ b/test/RestSharp.Tests.Integrated/Server/Handlers/HeaderHandlers.cs @@ -2,9 +2,9 @@ namespace RestSharp.Tests.Integrated.Server.Handlers; -public static class HeaderHandlers { - public static IResult HandleHeaders(HttpContext ctx) { - var response = ctx.Request.Headers.Select(x => new TestServerResponse(x.Key, x.Value!)); - return Results.Ok(response); - } -} +// public static class HeaderHandlers { +// public static IResult HandleHeaders(HttpContext ctx) { +// var response = ctx.Request.Headers.Select(x => new TestServerResponse(x.Key, x.Value!)); +// return Results.Ok(response); +// } +// } diff --git a/test/RestSharp.Tests.Integrated/Server/Models.cs b/test/RestSharp.Tests.Integrated/Server/Models.cs index 9fd14e186..7fa4a9226 100644 --- a/test/RestSharp.Tests.Integrated/Server/Models.cs +++ b/test/RestSharp.Tests.Integrated/Server/Models.cs @@ -2,4 +2,10 @@ namespace RestSharp.Tests.Integrated.Server; record TestServerResponse(string Name, string Value); -public record UploadResponse(string FileName, long Length, bool Equal); \ No newline at end of file +public record UploadResponse(string FileName, long Length, bool Equal); + +public record SuccessResponse(string Message); + +public class TestResponse { + public string Message { get; set; } = null!; +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/Server/TestServer.cs b/test/RestSharp.Tests.Integrated/Server/TestServer.cs index dd075532a..464bbbcd1 100644 --- a/test/RestSharp.Tests.Integrated/Server/TestServer.cs +++ b/test/RestSharp.Tests.Integrated/Server/TestServer.cs @@ -1,77 +1,77 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using RestSharp.Tests.Integrated.Server.Handlers; -using RestSharp.Tests.Shared.Extensions; -// ReSharper disable ConvertClosureToMethodGroup - -namespace RestSharp.Tests.Integrated.Server; - -public sealed class HttpServer { - readonly WebApplication _app; - - const string Address = "http://localhost:5151"; - - public const string ContentResource = "content"; - public const string TimeoutResource = "timeout"; - - public HttpServer(ITestOutputHelper? output = null) { - var builder = WebApplication.CreateBuilder(); - - if (output != null) builder.Logging.AddXunit(output, LogLevel.Debug); - - builder.Services.AddControllers().AddApplicationPart(typeof(UploadController).Assembly); - builder.WebHost.UseUrls(Address); - _app = builder.Build(); - - _app.MapControllers(); - - _app.MapGet("success", () => new TestResponse { Message = "Works!" }); - _app.MapGet("echo", (string msg) => msg); - _app.MapGet(TimeoutResource, async () => await Task.Delay(2000)); - _app.MapPut(TimeoutResource, async () => await Task.Delay(2000)); - _app.MapGet("status", (int code) => Results.StatusCode(code)); - _app.MapGet("headers", HeaderHandlers.HandleHeaders); - _app.MapGet("request-echo", async context => await context.Request.BodyReader.AsStream().CopyToAsync(context.Response.BodyWriter.AsStream())); - _app.MapDelete("delete", () => new TestResponse { Message = "Works!" }); - - // Cookies - _app.MapGet("get-cookies", CookieHandlers.HandleCookies); - _app.MapGet("set-cookies", CookieHandlers.HandleSetCookies); - _app.MapGet("redirect", () => Results.Redirect("/success", false, true)); - - // PUT - _app.MapPut( - ContentResource, - async context => { - var content = await context.Request.Body.StreamToStringAsync(); - await context.Response.WriteAsync(content); - } - ); - - // Upload file - // var assetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets"); - // _app.MapPost("/upload", ctx => FileHandlers.HandleUpload(assetPath, ctx.Request)); - - // POST - _app.MapPost("/post/json", (TestRequest request) => new TestResponse { Message = request.Data }); - - _app.MapPost( - "/post/form", - (HttpContext context) => new TestResponse { Message = $"Works! Length: {context.Request.Form["big_string"].ToString().Length}" } - ); - - _app.MapPost("/post/data", FormRequestHandler.HandleForm); - } - - public Uri Url => new(Address); - - public Task Start() => _app.StartAsync(); - - public async Task Stop() { - await _app.StopAsync(); - await _app.DisposeAsync(); - } -} +// using Microsoft.AspNetCore.Builder; +// using Microsoft.AspNetCore.Hosting; +// using Microsoft.AspNetCore.Http; +// using Microsoft.Extensions.DependencyInjection; +// using Microsoft.Extensions.Logging; +// using RestSharp.Tests.Integrated.Server.Handlers; +// using RestSharp.Tests.Shared.Extensions; +// // ReSharper disable ConvertClosureToMethodGroup +// +// namespace RestSharp.Tests.Integrated.Server; +// +// public sealed class HttpServer1 { +// readonly WebApplication _app; +// +// const string Address = "http://localhost:5151"; +// +// public const string ContentResource = "content"; +// public const string TimeoutResource = "timeout"; +// +// public HttpServer(ITestOutputHelper? output = null) { +// var builder = WebApplication.CreateBuilder(); +// +// if (output != null) builder.Logging.AddXunit(output, LogLevel.Debug); +// +// builder.Services.AddControllers().AddApplicationPart(typeof(UploadController).Assembly); +// builder.WebHost.UseUrls(Address); +// _app = builder.Build(); +// +// _app.MapControllers(); +// +// _app.MapGet("success", () => new TestResponse { Message = "Works!" }); +// _app.MapGet("echo", (string msg) => msg); +// _app.MapGet(TimeoutResource, async () => await Task.Delay(2000)); +// _app.MapPut(TimeoutResource, async () => await Task.Delay(2000)); +// _app.MapGet("status", (int code) => Results.StatusCode(code)); +// _app.MapGet("headers", HeaderHandlers.HandleHeaders); +// _app.MapGet("request-echo", async context => await context.Request.BodyReader.AsStream().CopyToAsync(context.Response.BodyWriter.AsStream())); +// _app.MapDelete("delete", () => new TestResponse { Message = "Works!" }); +// +// // Cookies +// _app.MapGet("get-cookies", CookieHandlers.HandleCookies); +// _app.MapGet("set-cookies", CookieHandlers.HandleSetCookies); +// _app.MapGet("redirect", () => Results.Redirect("/success", false, true)); +// +// // PUT +// _app.MapPut( +// ContentResource, +// async context => { +// var content = await context.Request.Body.StreamToStringAsync(); +// await context.Response.WriteAsync(content); +// } +// ); +// +// // Upload file +// // var assetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets"); +// // _app.MapPost("/upload", ctx => FileHandlers.HandleUpload(assetPath, ctx.Request)); +// +// // POST +// _app.MapPost("/post/json", (TestRequest request) => new TestResponse { Message = request.Data }); +// +// _app.MapPost( +// "/post/form", +// (HttpContext context) => new TestResponse { Message = $"Works! Length: {context.Request.Form["big_string"].ToString().Length}" } +// ); +// +// _app.MapPost("/post/data", FormRequestHandler.HandleForm); +// } +// +// public Uri Url => new(Address); +// +// public Task Start() => _app.StartAsync(); +// +// public async Task Stop() { +// await _app.StopAsync(); +// await _app.DisposeAsync(); +// } +// } diff --git a/test/RestSharp.Tests.Integrated/Server/TestServerFixture.cs b/test/RestSharp.Tests.Integrated/Server/TestServerFixture.cs index 44e6312af..84eb6d46c 100644 --- a/test/RestSharp.Tests.Integrated/Server/TestServerFixture.cs +++ b/test/RestSharp.Tests.Integrated/Server/TestServerFixture.cs @@ -1,13 +1,13 @@ namespace RestSharp.Tests.Integrated.Server; -public class TestServerFixture : IAsyncLifetime { - public HttpServer Server { get; } = new(); +// public class TestServerFixture1 : IAsyncLifetime { + // public HttpServer Server { get; } = new(); - public Task InitializeAsync() => Server.Start(); + // public Task InitializeAsync() => Server.Start(); - public Task DisposeAsync() => Server.Stop(); -} + // public Task DisposeAsync() => Server.Stop(); +// } -[CollectionDefinition(nameof(TestServerCollection))] -public class TestServerCollection : ICollectionFixture { } +// [CollectionDefinition(nameof(TestServerCollection))] +// public class TestServerCollection : ICollectionFixture { } diff --git a/test/RestSharp.Tests.Integrated/Server/WireMockTestServer.cs b/test/RestSharp.Tests.Integrated/Server/WireMockTestServer.cs new file mode 100644 index 000000000..0fdec591e --- /dev/null +++ b/test/RestSharp.Tests.Integrated/Server/WireMockTestServer.cs @@ -0,0 +1,135 @@ +using System.Text.Json; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Net.Http.Headers; +using WireMock.Types; +using WireMock.Util; + +namespace RestSharp.Tests.Integrated.Server; + +static class WireMockTestServer { + public static WireMockServer StartTestServer() { + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath("/echo")) + .RespondWith(Response.Create().WithCallback(EchoQuery)); + + server + .Given(Request.Create().WithPath("/success").UsingGet()) + .RespondWith(Response.Create().WithBodyAsJson(new SuccessResponse("Works!"))); + + server + .Given(Request.Create().WithPath("/delete").UsingDelete()) + .RespondWith(Response.Create().WithBodyAsJson(new SuccessResponse("Works!"))); + + server + .Given(Request.Create().WithPath("/content")) + .RespondWith(Response.Create().WithCallback(EchoJsonBody)); + + server + .Given(Request.Create().WithPath("/post/json").UsingPost()) + .RespondWith(Response.Create().WithCallback(WrapBody)); + + server + .Given(Request.Create().WithPath("/post/data").UsingPost()) + .RespondWith(Response.Create().WithCallback(HandleForm)); + + server + .Given(Request.Create().WithPath("/post/form").UsingPost()) + .RespondWith(Response.Create().WithCallback(WrapForm)); + + server + .Given(Request.Create().WithPath("/timeout")) + .RespondWith(Response.Create().WithDelay(1000)); + + server + .Given(Request.Create().WithPath("/redirect")) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.Redirect).WithHeader("Location", "/success")); + + server + .Given(Request.Create().WithPath("/status").UsingGet()) + .RespondWith(Response.Create().WithCallback(StatusCode)); + + server + .Given(Request.Create().WithPath("/headers")) + .RespondWith(Response.Create().WithCallback(EchoHeaders)); + + return server; + } + + static ResponseMessage WrapForm(IRequestMessage request) { + var response = request.BodyData!.BodyAsFormUrlEncoded!["big_string"].Length; + return CreateJson(new SuccessResponse($"Works! Length: {response}")); + } + + static readonly JsonSerializerOptions JsonOptions = new() { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true + }; + + static ResponseMessage HandleForm(IRequestMessage request) { + var result = request.BodyData!.BodyAsFormUrlEncoded!.Select(x => new TestServerResponse(x.Key, x.Value)); + return CreateJson(result); + } + + static ResponseMessage EchoQuery(IRequestMessage request) { + var query = request.Query!["msg"]; + var msg = query[0]; + + return new ResponseMessage { + BodyData = new BodyData { + DetectedBodyType = BodyType.String, + BodyAsString = msg + } + }; + } + + static ResponseMessage EchoHeaders(IRequestMessage request) { + var headers = request.Headers!.Select(x => new TestServerResponse(x.Key, x.Value.First())); + return CreateJson(headers); + } + + static ResponseMessage EchoJsonBody(IRequestMessage request) => CreateJson(request.BodyAsJson!); + + static ResponseMessage WrapBody(IRequestMessage request) { + var data = JsonSerializer.Deserialize(request.Body!, JsonOptions); + return CreateJson(new TestResponse { Message = data?.Data ?? "" }); + } + + static ResponseMessage StatusCode(IRequestMessage request) { + var query = request.Query!["code"]; + var statusCode = int.Parse(query[0]); + + return new ResponseMessage { + StatusCode = statusCode + }; + } + + public static ResponseMessage CreateJson(object response) + => new() { + BodyData = new BodyData { + BodyAsJson = response, + DetectedBodyType = BodyType.Json + } + }; + + public static async Task GetFileSection(this IRequestMessage request, string name) { + var headerValue = request.Headers![KnownHeaders.ContentType][0]; + var mediaType = MediaTypeHeaderValue.Parse(headerValue); + var boundary = mediaType.Boundary; + + using var stream = new MemoryStream(request.BodyAsBytes!); + var reader = new MultipartReader(boundary.Value!, stream); + + FileMultipartSection? fileSection = null; + while (true) { + var section = await reader.ReadNextSectionAsync(); + if (section == null) break; + fileSection = section.AsFileSection(); + if (fileSection == null) continue; + if (fileSection.Name == name) break; + } + + return fileSection; + } +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/StatusCodeTests.cs b/test/RestSharp.Tests.Integrated/StatusCodeTests.cs index 292e2522a..cd9fccadd 100644 --- a/test/RestSharp.Tests.Integrated/StatusCodeTests.cs +++ b/test/RestSharp.Tests.Integrated/StatusCodeTests.cs @@ -24,7 +24,10 @@ ResponseMessage CreateResponse(IRequestMessage request) { } } - public void Dispose() => _server.Dispose(); + public void Dispose() { + _server.Dispose(); + _client.Dispose(); + } readonly WireMockServer _server = WireMockServer.Start(); readonly RestClient _client; diff --git a/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs b/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs index 5cfd56d33..50c996598 100644 --- a/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs +++ b/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs @@ -17,7 +17,7 @@ class Person { } const string XmlContent = "Bob50"; - const string JsonContent = @"{ ""name"":""Bob"", ""age"":50 }"; + const string JsonContent = """{ "name":"Bob", "age":50 }"""; public StructuredSyntaxSuffixTests() { _server = WireMockServer.Start(); @@ -38,19 +38,13 @@ static ResponseMessage Handle(IRequestMessage request) { }; return response; } - - // static void HandleRequest(HttpListenerRequest request, HttpListenerResponse response, Dictionary p) { - // response.ContentType = request.QueryString["ct"]; - // response.OutputStream.WriteStringUtf8(request.QueryString["c"]); - // response.StatusCode = 200; - // } } public void Dispose() => _server.Dispose(); [Fact] public async Task By_default_application_json_content_type_should_deserialize_as_JSON() { - var client = new RestClient(_server.Url!); + using var client = new RestClient(_server.Url!); var request = new RestRequest() .AddParameter("ct", "application/json") @@ -64,7 +58,7 @@ public async Task By_default_application_json_content_type_should_deserialize_as [Fact] public async Task By_default_content_types_with_JSON_structured_syntax_suffix_should_deserialize_as_JSON() { - var client = new RestClient(_server.Url!); + using var client = new RestClient(_server.Url!); var request = new RestRequest() .AddParameter("ct", "application/vnd.somebody.something+json") @@ -78,7 +72,7 @@ public async Task By_default_content_types_with_JSON_structured_syntax_suffix_sh [Fact] public async Task By_default_content_types_with_XML_structured_syntax_suffix_should_deserialize_as_XML() { - var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseXmlSerializer()); + using var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseXmlSerializer()); var request = new RestRequest() .AddParameter("ct", "application/vnd.somebody.something+xml") @@ -92,7 +86,7 @@ public async Task By_default_content_types_with_XML_structured_syntax_suffix_sho [Fact] public async Task By_default_text_xml_content_type_should_deserialize_as_XML() { - var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseXmlSerializer()); + using var client = new RestClient(_server.Url!, configureSerialization: cfg => cfg.UseXmlSerializer()); var request = new RestRequest() .AddParameter("ct", "text/xml") diff --git a/test/RestSharp.Tests.Integrated/TestResponse.cs b/test/RestSharp.Tests.Integrated/TestResponse.cs index ccf918b32..b3b422c94 100644 --- a/test/RestSharp.Tests.Integrated/TestResponse.cs +++ b/test/RestSharp.Tests.Integrated/TestResponse.cs @@ -1,5 +1 @@ namespace RestSharp.Tests.Integrated; - -public class TestResponse { - public string Message { get; set; } = null!; -} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/UploadFileTests.cs b/test/RestSharp.Tests.Integrated/UploadFileTests.cs index 9e2bc1d79..4178e6006 100644 --- a/test/RestSharp.Tests.Integrated/UploadFileTests.cs +++ b/test/RestSharp.Tests.Integrated/UploadFileTests.cs @@ -1,23 +1,33 @@ -using System.Net; -using RestSharp.Tests.Integrated.Server; +// ReSharper disable MethodHasAsyncOverload + +using HttpMultipartParser; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Net.Http.Headers; +using RestSharp.Extensions; namespace RestSharp.Tests.Integrated; -[Collection(nameof(TestServerCollection))] +using Server; + public class UploadFileTests { readonly ITestOutputHelper _output; readonly RestClient _client; readonly string _basePath = AppDomain.CurrentDomain.BaseDirectory; readonly string _path; readonly UploadResponse _expected; + readonly WireMockServer _server = WireMockServer.Start(); const string Filename = "Koala.jpg"; - public UploadFileTests(TestServerFixture fixture, ITestOutputHelper output) { - _output = output; - _client = new RestClient(new RestClientOptions(fixture.Server.Url)); + public UploadFileTests(ITestOutputHelper output) { + _output = output; + _client = new RestClient(new RestClientOptions(_server.Url!)); _path = Path.Combine(_basePath, "Assets", Filename); _expected = new UploadResponse(Filename, new FileInfo(_path).Length, true); + + _server + .Given(Request.Create().WithPath("/upload")) + .RespondWith(Response.Create().WithCallback(HandleUpload)); } [Fact] @@ -33,7 +43,7 @@ public async Task Should_upload_from_file() { [Fact] public async Task Should_upload_from_bytes() { - var bytes = await File.ReadAllBytesAsync(_path); + var bytes = File.ReadAllBytes(_path); var request = new RestRequest("upload").AddFile("file", bytes, Filename); var response = await _client.ExecutePostAsync(request); @@ -55,7 +65,7 @@ public async Task Should_upload_from_stream() { public async Task Should_upload_from_stream_non_ascii() { const string nonAsciiFilename = "Präsentation_Export.zip"; - var options = new FileParameterOptions { DisableFilenameEncoding = true, DisableFilenameStar = false}; + var options = new FileParameterOptions { DisableFilenameEncoding = true, DisableFilenameStar = false }; var request = new RestRequest("upload") .AddFile("file", () => File.OpenRead(_path), nonAsciiFilename, options: options) @@ -65,4 +75,42 @@ public async Task Should_upload_from_stream_non_ascii() { _output.WriteLine(response.Content); response.Data.Should().BeEquivalentTo(new UploadResponse(nonAsciiFilename, new FileInfo(_path).Length, true)); } -} + + static async Task HandleUpload(IRequestMessage request) { + var response = new ResponseMessage(); + + var checkFile = request.Query == null || + request.Query.Count == 0 || + request.Query.ContainsKey("checkFile") && bool.Parse(request.Query["checkFile"][0]); + + using var stream = new MemoryStream(request.BodyAsBytes!); + var form = await MultipartFormDataParser.ParseAsync(stream); + if (form.Files.Count == 0) return response; + + var fileSection = form.Files[0]; + var fileLength = fileSection.Data.Length; + + // Doing this because MultipartFormDataParser doesn't understand filename* + var section = await request.GetFileSection("file"); + var fileName = section!.FileName; + + // ReSharper disable once InvertIf + if (checkFile) { + var assetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets"); + + try { + var expected = File.ReadAllBytes(Path.Combine(assetPath, fileName)); + fileSection.Data.Seek(0, SeekOrigin.Begin); + var received = await fileSection.Data.ReadAsBytes(default); + var equal = received.SequenceEqual(expected); + return WireMockTestServer.CreateJson(new UploadResponse(fileName, fileLength, equal)); + } + catch (Exception) { + return response; + } + } + + return WireMockTestServer.CreateJson(new UploadResponse(fileName, fileLength, true)); + + } +} \ No newline at end of file diff --git a/test/RestSharp.Tests.Integrated/XmlResponseTests.cs b/test/RestSharp.Tests.Integrated/XmlResponseTests.cs index a30f511c6..45cb687fc 100644 --- a/test/RestSharp.Tests.Integrated/XmlResponseTests.cs +++ b/test/RestSharp.Tests.Integrated/XmlResponseTests.cs @@ -1,6 +1,7 @@ using System.Net; using RestSharp.Interceptors; using RestSharp.Serializers.Xml; +using RestSharp.Tests.Integrated.Server; using WireMock; namespace RestSharp.Tests.Integrated; diff --git a/test/RestSharp.Tests.Integrated/OAuth1Tests.cs b/test/RestSharp.Tests/OAuth1Tests.cs similarity index 98% rename from test/RestSharp.Tests.Integrated/OAuth1Tests.cs rename to test/RestSharp.Tests/OAuth1Tests.cs index 04827b1f8..d8fad50d6 100644 --- a/test/RestSharp.Tests.Integrated/OAuth1Tests.cs +++ b/test/RestSharp.Tests/OAuth1Tests.cs @@ -2,9 +2,10 @@ using RestSharp.Authenticators; using RestSharp.Authenticators.OAuth; using RestSharp.Tests.Shared.Extensions; + #pragma warning disable CS8618 -namespace RestSharp.Tests.Integrated; +namespace RestSharp.Tests; public class OAuth1Tests { [XmlRoot("queue")] diff --git a/test/RestSharp.Tests/RestSharp.Tests.csproj b/test/RestSharp.Tests/RestSharp.Tests.csproj index 4fdfc1277..6c8d9fd1c 100644 --- a/test/RestSharp.Tests/RestSharp.Tests.csproj +++ b/test/RestSharp.Tests/RestSharp.Tests.csproj @@ -6,6 +6,7 @@ + From aac49210e7c632678c13014b1a63a95c54767735 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Fri, 5 Apr 2024 12:29:00 +0200 Subject: [PATCH 20/24] Add .NET Framework 4.8 target --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 3263cbade..09c1cf239 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,7 +1,7 @@ - netstandard2.0;net471;net6.0;net7.0;net8.0 + netstandard2.0;net471;net48;net6.0;net7.0;net8.0 restsharp.png Apache-2.0 https://restsharp.dev From 229550ab652a05cb3234612b8959bf4872833a31 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Fri, 5 Apr 2024 12:29:20 +0200 Subject: [PATCH 21/24] Add 4.8 to the matrix --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index ecbf59455..1daf73fbb 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -20,7 +20,7 @@ jobs: runs-on: windows-latest strategy: matrix: - dotnet: ['net472', 'net6.0', 'net7.0', 'net8.0'] + dotnet: ['net48', 'net6.0', 'net7.0', 'net8.0'] steps: - From 51abdef9c2483392ddfe16df857dc6efa552935b Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Fri, 5 Apr 2024 12:37:53 +0200 Subject: [PATCH 22/24] Fixing compilation --- src/Directory.Build.props | 2 +- src/RestSharp/RestSharp.csproj | 4 +--- test/Directory.Build.props | 2 +- .../Fixtures/RequestBodyCapturer.cs | 12 ++++++------ .../Fixtures/WireMockExtensions.cs | 1 - 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 09c1cf239..2fa59dd14 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -18,7 +18,7 @@ README.md - + true true diff --git a/src/RestSharp/RestSharp.csproj b/src/RestSharp/RestSharp.csproj index 719d6ceb9..89f15d0d3 100644 --- a/src/RestSharp/RestSharp.csproj +++ b/src/RestSharp/RestSharp.csproj @@ -5,11 +5,9 @@ - + - - diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 8bbc23b5b..9ad7f24e7 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -20,7 +20,7 @@ - + diff --git a/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs b/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs index 7dc65a7e3..bcb57764a 100644 --- a/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs +++ b/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs @@ -1,14 +1,14 @@ -namespace RestSharp.Tests.Integrated.Fixtures; +namespace RestSharp.Tests.Shared.Fixtures; public class RequestBodyCapturer { public const string Resource = "/capture"; - public string? ContentType { get; private set; } - public bool HasBody { get; private set; } - public string? Body { get; private set; } - public Uri? Url { get; private set; } + public string ContentType { get; private set; } + public bool HasBody { get; private set; } + public string Body { get; private set; } + public Uri Url { get; private set; } - public bool CaptureBody(string? content) { + public bool CaptureBody(string content) { Body = content; HasBody = !string.IsNullOrWhiteSpace(content); return true; diff --git a/test/RestSharp.Tests.Shared/Fixtures/WireMockExtensions.cs b/test/RestSharp.Tests.Shared/Fixtures/WireMockExtensions.cs index 922a0c207..2cabf015c 100644 --- a/test/RestSharp.Tests.Shared/Fixtures/WireMockExtensions.cs +++ b/test/RestSharp.Tests.Shared/Fixtures/WireMockExtensions.cs @@ -1,4 +1,3 @@ -using RestSharp.Tests.Integrated.Fixtures; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Server; From ce369ef086b1355d3bb434019054ad139406d946 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Fri, 5 Apr 2024 12:46:39 +0200 Subject: [PATCH 23/24] Content type fix --- test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs b/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs index c79f465b9..993c03637 100644 --- a/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs +++ b/test/RestSharp.Tests.Integrated/MultipartFormDataTests.cs @@ -185,7 +185,7 @@ public async Task MultipartFormData_Without_File_Creates_A_Valid_RequestBody() { using var client = new RestClient(_server.Url!); var request = new RestRequest(RequestBodyCapturer.Resource, Method.Post) { - AlwaysMultipartFormData = true + AlwaysMultipartFormData = true, }; var capturer = _server.ConfigureBodyCapturer(Method.Post); @@ -197,12 +197,12 @@ public async Task MultipartFormData_Without_File_Creates_A_Valid_RequestBody() { await client.ExecuteAsync(request); var expectedBody = new[] { - $"{KnownHeaders.ContentType}: {ContentType.Plain}", - $"{KnownHeaders.ContentDisposition}: form-data; name={multipartName}", + ContentTypeString, + $"{ContentDispositionString} name={multipartName}", bodyData }; - var actual = capturer.Body!.Split('\r'); + var actual = capturer.Body!.Replace("\n", string.Empty).Split('\r'); actual.Should().Contain(expectedBody); } } \ No newline at end of file From b80b25fa2d7976488d6f02a05cbe59d2c6dce149 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Fri, 5 Apr 2024 13:03:40 +0200 Subject: [PATCH 24/24] Disable non-ASCII upload test for .NET 6 --- .../Server/WireMockTestServer.cs | 1 + test/RestSharp.Tests.Integrated/UploadFileTests.cs | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/test/RestSharp.Tests.Integrated/Server/WireMockTestServer.cs b/test/RestSharp.Tests.Integrated/Server/WireMockTestServer.cs index 0fdec591e..59218df32 100644 --- a/test/RestSharp.Tests.Integrated/Server/WireMockTestServer.cs +++ b/test/RestSharp.Tests.Integrated/Server/WireMockTestServer.cs @@ -120,6 +120,7 @@ public static ResponseMessage CreateJson(object response) using var stream = new MemoryStream(request.BodyAsBytes!); var reader = new MultipartReader(boundary.Value!, stream); + reader.HeadersLengthLimit = int.MaxValue; FileMultipartSection? fileSection = null; while (true) { diff --git a/test/RestSharp.Tests.Integrated/UploadFileTests.cs b/test/RestSharp.Tests.Integrated/UploadFileTests.cs index 4178e6006..ad84e1ad2 100644 --- a/test/RestSharp.Tests.Integrated/UploadFileTests.cs +++ b/test/RestSharp.Tests.Integrated/UploadFileTests.cs @@ -61,6 +61,8 @@ public async Task Should_upload_from_stream() { response.Data.Should().BeEquivalentTo(_expected); } +#if !NET6_0 + // This test fails because MultipartFormDataParser doesn't understand filename* [Fact] public async Task Should_upload_from_stream_non_ascii() { const string nonAsciiFilename = "Präsentation_Export.zip"; @@ -75,6 +77,7 @@ public async Task Should_upload_from_stream_non_ascii() { _output.WriteLine(response.Content); response.Data.Should().BeEquivalentTo(new UploadResponse(nonAsciiFilename, new FileInfo(_path).Length, true)); } +#endif static async Task HandleUpload(IRequestMessage request) { var response = new ResponseMessage(); @@ -84,15 +87,19 @@ static async Task HandleUpload(IRequestMessage request) { request.Query.ContainsKey("checkFile") && bool.Parse(request.Query["checkFile"][0]); using var stream = new MemoryStream(request.BodyAsBytes!); - var form = await MultipartFormDataParser.ParseAsync(stream); + var form = await MultipartFormDataParser.ParseAsync(stream); if (form.Files.Count == 0) return response; var fileSection = form.Files[0]; - var fileLength = fileSection.Data.Length; + var fileLength = fileSection.Data.Length; +#if !NET6_0 // Doing this because MultipartFormDataParser doesn't understand filename* var section = await request.GetFileSection("file"); var fileName = section!.FileName; +#else + var fileName = fileSection.FileName; +#endif // ReSharper disable once InvertIf if (checkFile) { @@ -102,7 +109,7 @@ static async Task HandleUpload(IRequestMessage request) { var expected = File.ReadAllBytes(Path.Combine(assetPath, fileName)); fileSection.Data.Seek(0, SeekOrigin.Begin); var received = await fileSection.Data.ReadAsBytes(default); - var equal = received.SequenceEqual(expected); + var equal = received.SequenceEqual(expected); return WireMockTestServer.CreateJson(new UploadResponse(fileName, fileLength, equal)); } catch (Exception) { @@ -111,6 +118,5 @@ static async Task HandleUpload(IRequestMessage request) { } return WireMockTestServer.CreateJson(new UploadResponse(fileName, fileLength, true)); - } } \ No newline at end of file