From e9f5a449f393078d040b148941fcf355e42c733f Mon Sep 17 00:00:00 2001 From: pmosk Date: Fri, 17 Apr 2026 15:22:40 +0300 Subject: [PATCH 1/4] Add comprehensive contract/http API tests and harden HttpApi --- src/Api.Contract/Api.Contract.csproj | 10 +- src/Api.Contract/Input/HttpSendIn.cs | 22 +- src/Api.Contract/Input/HttpVerb.cs | 22 +- src/Api.Test/Api.Test.csproj | 12 +- src/Api.Test/Test.HttpApi/HttpApiTest.cs | 87 ++++++++ src/Api.Test/Test.HttpApi/Test.Constructor.cs | 34 +++ src/Api.Test/Test.HttpApi/Test.Send.cs | 209 ++++++++++++++++++ src/Api.Test/Test.HttpBody/HttpBodyTest.cs | 75 +++++++ .../Test.HttpBody/Test.DeserializeFromJson.cs | 65 ++++++ .../Test.HttpBody/Test.Equality.Equals.cs | 24 ++ .../Test.Equality.EqualsWithObject.cs | 40 ++++ .../Test.Equality.GetHashCode.cs | 28 +++ .../Test.Equality.Operator.Equality.cs | 24 ++ .../Test.Equality.Operator.Inequality.cs | 24 ++ .../Test.HttpBody/Test.SerializeAsJson.cs | 52 +++++ src/Api.Test/Test.HttpBody/Test.ToString.cs | 62 ++++++ .../Test.HttpBodyType/HttpBodyTypeTest.cs | 38 ++++ .../Test.HttpBodyType/Test.Constructor.cs | 24 ++ .../Test.HttpBodyType/Test.Equality.Equals.cs | 24 ++ .../Test.Equality.EqualsWithObject.cs | 40 ++++ .../Test.Equality.GetHashCode.cs | 28 +++ .../Test.Equality.Operator.Equality.cs | 24 ++ .../Test.Equality.Operator.Inequality.cs | 24 ++ .../Test.HttpBodyType/Test.IsJsonMediaType.cs | 42 ++++ .../HttpSendFailureTest.cs | 115 ++++++++++ .../Test.Equality.Equals.cs | 24 ++ .../Test.Equality.EqualsWithObject.cs | 40 ++++ .../Test.Equality.GetHashCode.cs | 17 ++ .../Test.Equality.Operator.Equality.cs | 24 ++ .../Test.Equality.Operator.Inequality.cs | 24 ++ .../Test.ToStandardFailure.cs | 75 +++++++ .../Test.HttpSendIn/HttpSendInTest.cs | 100 ++++++--- .../Test.HttpSendIn/Test.Constructor.cs | 30 +++ .../Test.HttpSendIn/Test.Equality.Equals.cs | 25 ++- .../Test.Equality.Operator.Equality.cs | 32 ++- .../Test.Equality.Operator.Inequality.cs | 32 ++- src/Api.Test/Test.HttpVerb/HttpVerbTest.cs | 40 ++++ .../Test.HttpVerb/Test.Equality.Equals.cs | 33 +++ .../Test.Equality.EqualsWithObject.cs | 31 +++ .../Test.Equality.GetHashCode.cs | 17 ++ .../Test.Equality.Operator.Equality.cs | 42 ++++ .../Test.Equality.Operator.Inequality.cs | 42 ++++ src/Api.Test/Test.HttpVerb/Test.From.cs | 27 +++ src/Api.Test/Test.HttpVerb/Test.ToString.cs | 18 ++ src/Api/Api.csproj | 17 +- src/Api/Api/Api.Send.cs | 28 +-- src/Api/Api/HttpApi.cs | 5 +- src/Api/HttpApiDependency.cs | 19 +- 48 files changed, 1782 insertions(+), 109 deletions(-) create mode 100644 src/Api.Test/Test.HttpApi/HttpApiTest.cs create mode 100644 src/Api.Test/Test.HttpApi/Test.Constructor.cs create mode 100644 src/Api.Test/Test.HttpApi/Test.Send.cs create mode 100644 src/Api.Test/Test.HttpBody/HttpBodyTest.cs create mode 100644 src/Api.Test/Test.HttpBody/Test.DeserializeFromJson.cs create mode 100644 src/Api.Test/Test.HttpBody/Test.Equality.Equals.cs create mode 100644 src/Api.Test/Test.HttpBody/Test.Equality.EqualsWithObject.cs create mode 100644 src/Api.Test/Test.HttpBody/Test.Equality.GetHashCode.cs create mode 100644 src/Api.Test/Test.HttpBody/Test.Equality.Operator.Equality.cs create mode 100644 src/Api.Test/Test.HttpBody/Test.Equality.Operator.Inequality.cs create mode 100644 src/Api.Test/Test.HttpBody/Test.SerializeAsJson.cs create mode 100644 src/Api.Test/Test.HttpBody/Test.ToString.cs create mode 100644 src/Api.Test/Test.HttpBodyType/HttpBodyTypeTest.cs create mode 100644 src/Api.Test/Test.HttpBodyType/Test.Constructor.cs create mode 100644 src/Api.Test/Test.HttpBodyType/Test.Equality.Equals.cs create mode 100644 src/Api.Test/Test.HttpBodyType/Test.Equality.EqualsWithObject.cs create mode 100644 src/Api.Test/Test.HttpBodyType/Test.Equality.GetHashCode.cs create mode 100644 src/Api.Test/Test.HttpBodyType/Test.Equality.Operator.Equality.cs create mode 100644 src/Api.Test/Test.HttpBodyType/Test.Equality.Operator.Inequality.cs create mode 100644 src/Api.Test/Test.HttpBodyType/Test.IsJsonMediaType.cs create mode 100644 src/Api.Test/Test.HttpSendFailure/HttpSendFailureTest.cs create mode 100644 src/Api.Test/Test.HttpSendFailure/Test.Equality.Equals.cs create mode 100644 src/Api.Test/Test.HttpSendFailure/Test.Equality.EqualsWithObject.cs create mode 100644 src/Api.Test/Test.HttpSendFailure/Test.Equality.GetHashCode.cs create mode 100644 src/Api.Test/Test.HttpSendFailure/Test.Equality.Operator.Equality.cs create mode 100644 src/Api.Test/Test.HttpSendFailure/Test.Equality.Operator.Inequality.cs create mode 100644 src/Api.Test/Test.HttpSendFailure/Test.ToStandardFailure.cs create mode 100644 src/Api.Test/Test.HttpSendIn/Test.Constructor.cs create mode 100644 src/Api.Test/Test.HttpVerb/HttpVerbTest.cs create mode 100644 src/Api.Test/Test.HttpVerb/Test.Equality.Equals.cs create mode 100644 src/Api.Test/Test.HttpVerb/Test.Equality.EqualsWithObject.cs create mode 100644 src/Api.Test/Test.HttpVerb/Test.Equality.GetHashCode.cs create mode 100644 src/Api.Test/Test.HttpVerb/Test.Equality.Operator.Equality.cs create mode 100644 src/Api.Test/Test.HttpVerb/Test.Equality.Operator.Inequality.cs create mode 100644 src/Api.Test/Test.HttpVerb/Test.From.cs create mode 100644 src/Api.Test/Test.HttpVerb/Test.ToString.cs diff --git a/src/Api.Contract/Api.Contract.csproj b/src/Api.Contract/Api.Contract.csproj index 454b11d..299b65c 100644 --- a/src/Api.Contract/Api.Contract.csproj +++ b/src/Api.Contract/Api.Contract.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0 + net10.0 disable enable true @@ -9,15 +9,15 @@ $(NoWarn);IDE0130;CA1859 GarageGroup.Infra GarageGroup.Infra.Http.Api.Contract - 0.3.0 + 1.0.0 - + - - + + \ No newline at end of file diff --git a/src/Api.Contract/Input/HttpSendIn.cs b/src/Api.Contract/Input/HttpSendIn.cs index d0837ff..e3abbbe 100644 --- a/src/Api.Contract/Input/HttpSendIn.cs +++ b/src/Api.Contract/Input/HttpSendIn.cs @@ -109,13 +109,19 @@ public override string ToString() return builder.Append('\n').Append('\n').Append(body).ToString(); } - public static bool operator ==(HttpSendIn left, HttpSendIn right) - => - left.Equals(right); - - public static bool operator !=(HttpSendIn left, HttpSendIn right) - => - left.Equals(right) is not true; + public static bool operator ==(HttpSendIn? left, HttpSendIn? right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + return left?.Equals(right) is true; + } + + public static bool operator !=(HttpSendIn? left, HttpSendIn? right) + => + (left == right) is false; private static FlatArray> GetOrderedHeaders(FlatArray> source) { @@ -163,4 +169,4 @@ static string GetKey(KeyValuePair> kv) => kv.Key; } -} \ No newline at end of file +} diff --git a/src/Api.Contract/Input/HttpVerb.cs b/src/Api.Contract/Input/HttpVerb.cs index efd7ece..a2e5191 100644 --- a/src/Api.Contract/Input/HttpVerb.cs +++ b/src/Api.Contract/Input/HttpVerb.cs @@ -80,11 +80,17 @@ public bool Equals(HttpVerb? other) return StringComparer.InvariantCultureIgnoreCase.Equals(Name, other.Name); } - public static bool operator ==(HttpVerb left, HttpVerb right) - => - left.Equals(right); - - public static bool operator !=(HttpVerb left, HttpVerb right) - => - left.Equals(right) is not true; -} \ No newline at end of file + public static bool operator ==(HttpVerb? left, HttpVerb? right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + return left?.Equals(right) is true; + } + + public static bool operator !=(HttpVerb? left, HttpVerb? right) + => + (left == right) is false; +} diff --git a/src/Api.Test/Api.Test.csproj b/src/Api.Test/Api.Test.csproj index 1a10d5f..3a03c9a 100644 --- a/src/Api.Test/Api.Test.csproj +++ b/src/Api.Test/Api.Test.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0 + net10.0 disable enable true @@ -17,16 +17,16 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - \ No newline at end of file + diff --git a/src/Api.Test/Test.HttpApi/HttpApiTest.cs b/src/Api.Test/Test.HttpApi/HttpApiTest.cs new file mode 100644 index 0000000..bfff8c2 --- /dev/null +++ b/src/Api.Test/Test.HttpApi/HttpApiTest.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +public static partial class HttpApiTest +{ + public static TheoryData SuccessCaseTestData + => + new() + { + { HttpSuccessType.Default, 201, "Created", true, true }, + { HttpSuccessType.OnlyHeaders, 200, "OK", true, false }, + { HttpSuccessType.OnlyStatusCode, 204, "No Content", false, false } + }; + + public static TheoryData FailureCaseTestData + => + new() + { + { 404, "Not Found", "{\"error\":\"not found\"}", "application/json", "utf-8" }, + { 503, "Service Unavailable", "temporary outage", "text/plain", "us-ascii" } + }; + + private static HttpApi CreateApi( + Func> sendAsync, + HttpApiOption option = default) + { + if (option.BaseAddress is null) + { + option = option with + { + BaseAddress = new("https://example.com/") + }; + } + + return new(new DelegateHttpMessageHandler(sendAsync), option); + } + + private sealed class DelegateHttpMessageHandler( + Func> sendAsync) : HttpMessageHandler + { + protected override Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) + => + sendAsync(request, cancellationToken); + } + + private static HttpResponseMessage CreateResponse( + int statusCode, + string? reasonPhrase, + IEnumerable> headers, + string? body, + string? mediaType = null, + string? charSet = null) + { + var response = new HttpResponseMessage((System.Net.HttpStatusCode)statusCode) + { + ReasonPhrase = reasonPhrase + }; + + foreach (var header in headers) + { + response.Headers.Add(header.Key, header.Value); + } + + if (body is null) + { + return response; + } + + response.Content = new StringContent(body); + if (string.IsNullOrEmpty(mediaType) is false) + { + response.Content.Headers.ContentType = new(mediaType) + { + CharSet = charSet + }; + } + + return response; + } +} diff --git a/src/Api.Test/Test.HttpApi/Test.Constructor.cs b/src/Api.Test/Test.HttpApi/Test.Constructor.cs new file mode 100644 index 0000000..c137e8f --- /dev/null +++ b/src/Api.Test/Test.HttpApi/Test.Constructor.cs @@ -0,0 +1,34 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpApiTest +{ + [Fact] + public static async Task Constructor_BaseAddressProvided_ExpectRequestUriCombined() + { + Uri? actualUri = null; + + var source = CreateApi( + sendAsync: (request, _) => + { + actualUri = request.RequestUri; + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); + }, + option: new() + { + BaseAddress = new("https://example.com/root/") + }); + + _ = await source.SendAsync( + new(HttpVerb.Get, "products"), + TestContext.Current.CancellationToken); + + Assert.Equal(new("https://example.com/root/products"), actualUri); + } +} diff --git a/src/Api.Test/Test.HttpApi/Test.Send.cs b/src/Api.Test/Test.HttpApi/Test.Send.cs new file mode 100644 index 0000000..9f5aec8 --- /dev/null +++ b/src/Api.Test/Test.HttpApi/Test.Send.cs @@ -0,0 +1,209 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpApiTest +{ + [Theory] + [MemberData(nameof(SuccessCaseTestData))] + public static async Task SendAsync_ResponseIsSuccess_ExpectMappedSuccess( + HttpSuccessType successType, + int statusCode, + string? reasonPhrase, + bool hasHeaders, + bool hasBody) + { + var source = CreateApi( + sendAsync: (_, _) => + Task.FromResult( + CreateResponse( + statusCode: statusCode, + reasonPhrase: reasonPhrase, + headers: [new("x-request-id", "abc")], + body: "{\"id\":1}", + mediaType: "application/json", + charSet: "utf-8"))); + + var actual = await source.SendAsync( + new(HttpVerb.Post, "/users") + { + SuccessType = successType + }, + TestContext.Current.CancellationToken); + + Assert.True(actual.IsSuccess); + var success = actual.SuccessOrThrow(); + + Assert.Equal((HttpSuccessCode)(statusCode - 200), success.StatusCode); + Assert.Equal(reasonPhrase, success.ReasonPhrase); + + if (hasHeaders) + { + Assert.Equal(1, success.Headers.Length); + Assert.True(string.Equals(success.Headers[0].Key, "x-request-id", StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal("abc", success.Headers[0].Value); + } + else + { + Assert.Equal(default, success.Headers); + } + + if (hasBody) + { + Assert.Equal("application/json", success.Body.Type.MediaType); + Assert.Equal("utf-8", success.Body.Type.CharSet); + Assert.Equal("{\"id\":1}", success.Body.Content?.ToString()); + } + else + { + Assert.Equal(default, success.Body); + } + } + + [Theory] + [MemberData(nameof(FailureCaseTestData))] + public static async Task SendAsync_ResponseIsFailure_ExpectMappedFailure( + int statusCode, + string? reasonPhrase, + string? body, + string? mediaType, + string? charSet) + { + var source = CreateApi( + sendAsync: (_, _) => + Task.FromResult( + CreateResponse( + statusCode: statusCode, + reasonPhrase: reasonPhrase, + headers: [new("x-request-id", "abc")], + body: body, + mediaType: mediaType, + charSet: charSet))); + + var actual = await source.SendAsync( + new(HttpVerb.Get, "/missing") + { + SuccessType = HttpSuccessType.OnlyStatusCode + }, + TestContext.Current.CancellationToken); + + Assert.True(actual.IsFailure); + var failure = actual.FailureOrThrow(); + + Assert.Equal((HttpFailureCode)statusCode, failure.StatusCode); + Assert.Equal(reasonPhrase, failure.ReasonPhrase); + Assert.Equal(1, failure.Headers.Length); + Assert.True(string.Equals(failure.Headers[0].Key, "x-request-id", StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal("abc", failure.Headers[0].Value); + + if (body is null) + { + Assert.Equal(default, failure.Body); + return; + } + + Assert.Equal(mediaType, failure.Body.Type.MediaType); + Assert.Equal(charSet, failure.Body.Type.CharSet); + Assert.Equal(body, failure.Body.Content?.ToString()); + } + + [Fact] + public static async Task SendAsync_InputIsNull_ExpectArgumentNullException() + { + var source = CreateApi( + sendAsync: (_, _) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))); + + await Assert.ThrowsAsync( + () => source.SendAsync(null!, TestContext.Current.CancellationToken).AsTask()); + } + + [Fact] + public static async Task SendAsync_InputHasHeadersAndBody_ExpectRequestMapped() + { + HttpMethod? actualMethod = null; + string? actualHeaderValue = null; + string? actualContentType = null; + string? actualBody = null; + + var source = CreateApi( + sendAsync: async (request, cancellationToken) => + { + actualMethod = request.Method; + actualHeaderValue = request.Headers.GetValues("x-request-id").SingleOrDefault(); + actualContentType = request.Content?.Headers.ContentType?.ToString(); + actualBody = request.Content is null + ? null + : await request.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + + return new HttpResponseMessage(HttpStatusCode.OK); + }); + + _ = await source.SendAsync( + new(HttpVerb.Patch, "/users/1") + { + Headers = [new("x-request-id", "abc")], + Body = new() + { + Type = new("application/json", "utf-8"), + Content = new("{\"firstName\":\"John\"}") + } + }, + TestContext.Current.CancellationToken); + + Assert.Equal(HttpMethod.Patch, actualMethod); + Assert.Equal("abc", actualHeaderValue); + Assert.Equal("application/json; charset=utf-8", actualContentType); + Assert.Equal("{\"firstName\":\"John\"}", actualBody); + } + + [Fact] + public static async Task SendAsync_InputHasDuplicateHeaders_ExpectAllValuesMapped() + { + string[] actualHeaderValues = []; + var source = CreateApi( + sendAsync: (request, _) => + { + actualHeaderValues = request.Headers.GetValues("x-request-id").ToArray(); + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); + }); + + _ = await source.SendAsync( + new(HttpVerb.Get, "/users") + { + Headers = + [ + new("x-request-id", "abc"), + new("X-Request-ID", "def") + ] + }, + TestContext.Current.CancellationToken); + + Assert.Equal(["abc", "def"], actualHeaderValues); + } + + [Fact] + public static async Task SendAsync_InputBodyIsEmpty_ExpectRequestWithoutContent() + { + var hasContent = true; + var source = CreateApi( + sendAsync: (request, _) => + { + hasContent = request.Content is not null; + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); + }); + + _ = await source.SendAsync( + new(HttpVerb.Get, "/users") + { + Body = default + }, + TestContext.Current.CancellationToken); + + Assert.False(hasContent); + } +} diff --git a/src/Api.Test/Test.HttpBody/HttpBodyTest.cs b/src/Api.Test/Test.HttpBody/HttpBodyTest.cs new file mode 100644 index 0000000..c298c60 --- /dev/null +++ b/src/Api.Test/Test.HttpBody/HttpBodyTest.cs @@ -0,0 +1,75 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +public static partial class HttpBodyTest +{ + public static TheoryData EqualTestData + => + new() + { + { + new() + { + Type = new("application/json") + }, + new() + { + Type = new("text/plain") + } + }, + { + new() + { + Type = new("application/json", "utf-8"), + Content = new("{\"value\":1}") + }, + new() + { + Type = new("application/json", "utf-8"), + Content = new("{\"value\":1}") + } + } + }; + + public static TheoryData UnequalTestData + => + new() + { + { + new() + { + Type = new("application/json") + }, + new() + { + Type = new("application/json"), + Content = new("{}") + } + }, + { + new() + { + Type = new("application/json", "utf-8"), + Content = new("{\"value\":1}") + }, + new() + { + Type = new("application/json", "utf-16"), + Content = new("{\"value\":1}") + } + }, + { + new() + { + Type = new("application/json", "utf-8"), + Content = new("{\"value\":1}") + }, + new() + { + Type = new("application/json", "utf-8"), + Content = new("{\"value\":2}") + } + } + }; +} diff --git a/src/Api.Test/Test.HttpBody/Test.DeserializeFromJson.cs b/src/Api.Test/Test.HttpBody/Test.DeserializeFromJson.cs new file mode 100644 index 0000000..97f9410 --- /dev/null +++ b/src/Api.Test/Test.HttpBody/Test.DeserializeFromJson.cs @@ -0,0 +1,65 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTest +{ + [Fact] + public static void DeserializeFromJson_ContentIsNull_ExpectDefault() + { + var source = new HttpBody + { + Type = new("application/json", "utf-8") + }; + + var result = source.DeserializeFromJson(); + Assert.Null(result); + } + + [Fact] + public static void DeserializeFromJson_ExpectModel() + { + var source = new HttpBody + { + Type = new("application/json", "utf-8"), + Content = new("{\"firstName\":\"John\",\"age\":18}") + }; + + var result = source.DeserializeFromJson(); + + Assert.NotNull(result); + Assert.Equal("John", result?.FirstName); + Assert.Equal(18, result?.Age); + } + + [Fact] + public static void DeserializeFromJson_CustomSerializerOptions_ExpectAppliedOptions() + { + var source = new HttpBody + { + Type = new("application/json", "utf-8"), + Content = new("{\"firstName\":\"John\",\"age\":\"18\"}") + }; + + var serializerOptions = new JsonSerializerOptions + { + NumberHandling = JsonNumberHandling.AllowReadingFromString, + PropertyNameCaseInsensitive = true + }; + + var result = source.DeserializeFromJson(serializerOptions); + + Assert.NotNull(result); + Assert.Equal("John", result?.FirstName); + Assert.Equal(18, result?.Age); + } + + private sealed record class DeserializeModel + { + public string? FirstName { get; init; } + + public int Age { get; init; } + } +} diff --git a/src/Api.Test/Test.HttpBody/Test.Equality.Equals.cs b/src/Api.Test/Test.HttpBody/Test.Equality.Equals.cs new file mode 100644 index 0000000..ea2b576 --- /dev/null +++ b/src/Api.Test/Test.HttpBody/Test.Equality.Equals.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void Equals_SourceIsEqualToOther_ExpectTrue( + HttpBody source, HttpBody other) + { + var result = source.Equals(other); + Assert.True(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void Equals_SourceIsNotEqualToOther_ExpectFalse( + HttpBody source, HttpBody other) + { + var result = source.Equals(other); + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpBody/Test.Equality.EqualsWithObject.cs b/src/Api.Test/Test.HttpBody/Test.Equality.EqualsWithObject.cs new file mode 100644 index 0000000..fa90d74 --- /dev/null +++ b/src/Api.Test/Test.HttpBody/Test.Equality.EqualsWithObject.cs @@ -0,0 +1,40 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void EqualsWithObject_SourceIsEqualToOther_ExpectTrue( + HttpBody source, HttpBody other) + { + var result = source.Equals((object)other); + Assert.True(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void EqualsWithObject_SourceIsNotEqualToOther_ExpectFalse( + HttpBody source, HttpBody other) + { + var result = source.Equals((object)other); + Assert.False(result); + } + + [Fact] + public static void EqualsWithObject_OtherIsNull_ExpectFalse() + { + object? other = null; + + var result = default(HttpBody).Equals(other); + Assert.False(result); + } + + [Fact] + public static void EqualsWithObject_OtherHasAnotherType_ExpectFalse() + { + var result = default(HttpBody).Equals(new object()); + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpBody/Test.Equality.GetHashCode.cs b/src/Api.Test/Test.HttpBody/Test.Equality.GetHashCode.cs new file mode 100644 index 0000000..0eb065a --- /dev/null +++ b/src/Api.Test/Test.HttpBody/Test.Equality.GetHashCode.cs @@ -0,0 +1,28 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void GetHashCode_SourceIsEqualToOther_ExpectEqualHashCodes( + HttpBody source, HttpBody other) + { + var sourceHashCode = source.GetHashCode(); + var otherHashCode = other.GetHashCode(); + + Assert.Equal(sourceHashCode, otherHashCode); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void GetHashCode_SourceIsNotEqualToOther_ExpectUnequalHashCodes( + HttpBody source, HttpBody other) + { + var sourceHashCode = source.GetHashCode(); + var otherHashCode = other.GetHashCode(); + + Assert.NotEqual(sourceHashCode, otherHashCode); + } +} diff --git a/src/Api.Test/Test.HttpBody/Test.Equality.Operator.Equality.cs b/src/Api.Test/Test.HttpBody/Test.Equality.Operator.Equality.cs new file mode 100644 index 0000000..2e6d69f --- /dev/null +++ b/src/Api.Test/Test.HttpBody/Test.Equality.Operator.Equality.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void EqualityOperator_SourceIsEqualToOther_ExpectTrue( + HttpBody source, HttpBody other) + { + var result = source == other; + Assert.True(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void EqualityOperator_SourceIsNotEqualToOther_ExpectFalse( + HttpBody source, HttpBody other) + { + var result = source == other; + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpBody/Test.Equality.Operator.Inequality.cs b/src/Api.Test/Test.HttpBody/Test.Equality.Operator.Inequality.cs new file mode 100644 index 0000000..6a03e2f --- /dev/null +++ b/src/Api.Test/Test.HttpBody/Test.Equality.Operator.Inequality.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void InequalityOperator_SourceIsEqualToOther_ExpectFalse( + HttpBody source, HttpBody other) + { + var result = source != other; + Assert.False(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void InequalityOperator_SourceIsNotEqualToOther_ExpectTrue( + HttpBody source, HttpBody other) + { + var result = source != other; + Assert.True(result); + } +} diff --git a/src/Api.Test/Test.HttpBody/Test.SerializeAsJson.cs b/src/Api.Test/Test.HttpBody/Test.SerializeAsJson.cs new file mode 100644 index 0000000..643cb13 --- /dev/null +++ b/src/Api.Test/Test.HttpBody/Test.SerializeAsJson.cs @@ -0,0 +1,52 @@ +using System.Text.Json; +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTest +{ + [Fact] + public static void SerializeAsJson_ExpectJsonTypeAndContent() + { + var source = SerializeModel.Create(); + + var result = HttpBody.SerializeAsJson(source); + + Assert.Equal("application/json", result.Type.MediaType); + Assert.Equal("utf-8", result.Type.CharSet); + Assert.NotNull(result.Content); + + var json = result.Content?.ToString(); + Assert.Contains("\"firstName\":\"John\"", json); + } + + [Fact] + public static void SerializeAsJson_CustomSerializerOptions_ExpectAppliedOptions() + { + var source = SerializeModel.Create(); + var serializerOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = null + }; + + var result = HttpBody.SerializeAsJson(source, serializerOptions); + + var json = result.Content?.ToString(); + Assert.Contains("\"FirstName\":\"John\"", json); + } + + private sealed record class SerializeModel + { + public string? FirstName { get; init; } + + public int Age { get; init; } + + public static SerializeModel Create() + => + new() + { + FirstName = "John", + Age = 18 + }; + } +} diff --git a/src/Api.Test/Test.HttpBody/Test.ToString.cs b/src/Api.Test/Test.HttpBody/Test.ToString.cs new file mode 100644 index 0000000..447a7ba --- /dev/null +++ b/src/Api.Test/Test.HttpBody/Test.ToString.cs @@ -0,0 +1,62 @@ +using System; +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTest +{ + [Fact] + public static void ToString_ContentIsNull_ExpectNull() + { + var source = new HttpBody + { + Type = new("application/json", "utf-8") + }; + + var result = source.ToString(); + Assert.Null(result); + } + + [Fact] + public static void ToString_ContentTypeIsEmpty_ExpectBodyOnly() + { + var source = new HttpBody + { + Type = default, + Content = new("{\"value\":1}") + }; + + var result = source.ToString(); + Assert.Equal("{\"value\":1}", result); + } + + [Fact] + public static void ToString_ContentTypeHasMediaType_ExpectHeaderAndBody() + { + var source = new HttpBody + { + Type = new("application/json"), + Content = new("{\"value\":1}") + }; + + var result = source.ToString(); + var expected = $"Content-Type: application/json{Environment.NewLine}{{\"value\":1}}"; + + Assert.Equal(expected, result); + } + + [Fact] + public static void ToString_ContentTypeHasMediaTypeAndCharSet_ExpectHeaderAndBody() + { + var source = new HttpBody + { + Type = new("application/json", "utf-8"), + Content = new("{\"value\":1}") + }; + + var result = source.ToString(); + var expected = $"Content-Type: application/json; charset=utf-8{Environment.NewLine}{{\"value\":1}}"; + + Assert.Equal(expected, result); + } +} diff --git a/src/Api.Test/Test.HttpBodyType/HttpBodyTypeTest.cs b/src/Api.Test/Test.HttpBodyType/HttpBodyTypeTest.cs new file mode 100644 index 0000000..925260a --- /dev/null +++ b/src/Api.Test/Test.HttpBodyType/HttpBodyTypeTest.cs @@ -0,0 +1,38 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +public static partial class HttpBodyTypeTest +{ + public static TheoryData EqualTestData + => + new() + { + { + new(mediaType: null!, charSet: null!), + new(mediaType: "\n\r", charSet: " ") + }, + { + new(mediaType: "application/json", charSet: "utf-8"), + new(mediaType: "application/json", charSet: "utf-8") + } + }; + + public static TheoryData UnequalTestData + => + new() + { + { + new(mediaType: "application/json", charSet: "utf-8"), + new(mediaType: "text/plain", charSet: "utf-8") + }, + { + new(mediaType: "application/json", charSet: "utf-8"), + new(mediaType: "application/json", charSet: "utf-16") + }, + { + new(mediaType: "application/json", charSet: null!), + new(mediaType: "application/json", charSet: "utf-8") + } + }; +} diff --git a/src/Api.Test/Test.HttpBodyType/Test.Constructor.cs b/src/Api.Test/Test.HttpBodyType/Test.Constructor.cs new file mode 100644 index 0000000..fd32dc1 --- /dev/null +++ b/src/Api.Test/Test.HttpBodyType/Test.Constructor.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTypeTest +{ + [Fact] + public static void Constructor_MediaTypeIsWhiteSpace_ExpectMediaTypeNull() + { + var result = new HttpBodyType(mediaType: " \n\r ", charSet: "utf-8"); + + Assert.Null(result.MediaType); + Assert.Equal("utf-8", result.CharSet); + } + + [Fact] + public static void Constructor_CharSetIsWhiteSpace_ExpectCharSetNull() + { + var result = new HttpBodyType(mediaType: "application/json", charSet: " "); + + Assert.Equal("application/json", result.MediaType); + Assert.Null(result.CharSet); + } +} diff --git a/src/Api.Test/Test.HttpBodyType/Test.Equality.Equals.cs b/src/Api.Test/Test.HttpBodyType/Test.Equality.Equals.cs new file mode 100644 index 0000000..16443a0 --- /dev/null +++ b/src/Api.Test/Test.HttpBodyType/Test.Equality.Equals.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTypeTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void Equals_SourceIsEqualToOther_ExpectTrue( + HttpBodyType source, HttpBodyType other) + { + var result = source.Equals(other); + Assert.True(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void Equals_SourceIsNotEqualToOther_ExpectFalse( + HttpBodyType source, HttpBodyType other) + { + var result = source.Equals(other); + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpBodyType/Test.Equality.EqualsWithObject.cs b/src/Api.Test/Test.HttpBodyType/Test.Equality.EqualsWithObject.cs new file mode 100644 index 0000000..646b9de --- /dev/null +++ b/src/Api.Test/Test.HttpBodyType/Test.Equality.EqualsWithObject.cs @@ -0,0 +1,40 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTypeTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void EqualsWithObject_SourceIsEqualToOther_ExpectTrue( + HttpBodyType source, HttpBodyType other) + { + var result = source.Equals((object)other); + Assert.True(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void EqualsWithObject_SourceIsNotEqualToOther_ExpectFalse( + HttpBodyType source, HttpBodyType other) + { + var result = source.Equals((object)other); + Assert.False(result); + } + + [Fact] + public static void EqualsWithObject_ObjectIsNull_ExpectFalse() + { + object? other = null; + + var result = default(HttpBodyType).Equals(other); + Assert.False(result); + } + + [Fact] + public static void EqualsWithObject_ObjectHasAnotherType_ExpectFalse() + { + var result = default(HttpBodyType).Equals(new object()); + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpBodyType/Test.Equality.GetHashCode.cs b/src/Api.Test/Test.HttpBodyType/Test.Equality.GetHashCode.cs new file mode 100644 index 0000000..08092f9 --- /dev/null +++ b/src/Api.Test/Test.HttpBodyType/Test.Equality.GetHashCode.cs @@ -0,0 +1,28 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTypeTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void GetHashCode_SourceIsEqualToOther_ExpectEqualHashCodes( + HttpBodyType source, HttpBodyType other) + { + var sourceHashCode = source.GetHashCode(); + var otherHashCode = other.GetHashCode(); + + Assert.Equal(sourceHashCode, otherHashCode); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void GetHashCode_SourceIsNotEqualToOther_ExpectUnequalHashCodes( + HttpBodyType source, HttpBodyType other) + { + var sourceHashCode = source.GetHashCode(); + var otherHashCode = other.GetHashCode(); + + Assert.NotEqual(sourceHashCode, otherHashCode); + } +} diff --git a/src/Api.Test/Test.HttpBodyType/Test.Equality.Operator.Equality.cs b/src/Api.Test/Test.HttpBodyType/Test.Equality.Operator.Equality.cs new file mode 100644 index 0000000..b132346 --- /dev/null +++ b/src/Api.Test/Test.HttpBodyType/Test.Equality.Operator.Equality.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTypeTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void EqualityOperator_SourceIsEqualToOther_ExpectTrue( + HttpBodyType source, HttpBodyType other) + { + var result = source == other; + Assert.True(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void EqualityOperator_SourceIsNotEqualToOther_ExpectFalse( + HttpBodyType source, HttpBodyType other) + { + var result = source == other; + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpBodyType/Test.Equality.Operator.Inequality.cs b/src/Api.Test/Test.HttpBodyType/Test.Equality.Operator.Inequality.cs new file mode 100644 index 0000000..6b2c545 --- /dev/null +++ b/src/Api.Test/Test.HttpBodyType/Test.Equality.Operator.Inequality.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTypeTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void InequalityOperator_SourceIsEqualToOther_ExpectFalse( + HttpBodyType source, HttpBodyType other) + { + var result = source != other; + Assert.False(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void InequalityOperator_SourceIsNotEqualToOther_ExpectTrue( + HttpBodyType source, HttpBodyType other) + { + var result = source != other; + Assert.True(result); + } +} diff --git a/src/Api.Test/Test.HttpBodyType/Test.IsJsonMediaType.cs b/src/Api.Test/Test.HttpBodyType/Test.IsJsonMediaType.cs new file mode 100644 index 0000000..eee77be --- /dev/null +++ b/src/Api.Test/Test.HttpBodyType/Test.IsJsonMediaType.cs @@ -0,0 +1,42 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpBodyTypeTest +{ + [Fact] + public static void IsJsonMediaType_MediaTypeIsNull_ExpectFalse() + { + var source = new HttpBodyType(mediaType: null!, charSet: null!); + + var result = source.IsJsonMediaType(isApplicationJsonStrict: true); + Assert.False(result); + } + + [Fact] + public static void IsJsonMediaType_MediaTypeIsApplicationJson_ExpectTrue() + { + var source = new HttpBodyType(mediaType: "APPLICATION/JSON", charSet: "utf-8"); + + var result = source.IsJsonMediaType(isApplicationJsonStrict: true); + Assert.True(result); + } + + [Fact] + public static void IsJsonMediaType_MediaTypeContainsJsonAndStrictIsFalse_ExpectTrue() + { + var source = new HttpBodyType(mediaType: "application/problem+json", charSet: "utf-8"); + + var result = source.IsJsonMediaType(isApplicationJsonStrict: false); + Assert.True(result); + } + + [Fact] + public static void IsJsonMediaType_MediaTypeContainsJsonAndStrictIsTrue_ExpectFalse() + { + var source = new HttpBodyType(mediaType: "application/problem+json", charSet: "utf-8"); + + var result = source.IsJsonMediaType(isApplicationJsonStrict: true); + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpSendFailure/HttpSendFailureTest.cs b/src/Api.Test/Test.HttpSendFailure/HttpSendFailureTest.cs new file mode 100644 index 0000000..16ddaf5 --- /dev/null +++ b/src/Api.Test/Test.HttpSendFailure/HttpSendFailureTest.cs @@ -0,0 +1,115 @@ +using System.Collections.Generic; +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +public static partial class HttpSendFailureTest +{ + public static TheoryData EqualTestData + => + new() + { + { + default, + default + }, + { + new() + { + StatusCode = HttpFailureCode.BadGateway, + ReasonPhrase = "Bad Gateway", + Headers = + [ + new("x-api-version", "2"), + new("retry-after", "5") + ], + Body = new() + { + Type = new("application/json", "utf-8"), + Content = new("{\"error\":\"proxy\"}") + } + }, + new() + { + StatusCode = HttpFailureCode.BadGateway, + ReasonPhrase = "Bad Gateway", + Headers = + [ + new("x-api-version", "2"), + new("retry-after", "5") + ], + Body = new() + { + Type = new("application/json", "utf-8"), + Content = new("{\"error\":\"proxy\"}") + } + } + } + }; + + public static TheoryData UnequalTestData + => + new() + { + { + new() + { + StatusCode = HttpFailureCode.BadRequest + }, + new() + { + StatusCode = HttpFailureCode.NotFound + } + }, + { + new() + { + StatusCode = HttpFailureCode.BadRequest, + ReasonPhrase = "Bad Request" + }, + new() + { + StatusCode = HttpFailureCode.BadRequest, + ReasonPhrase = "Request Failed" + } + }, + { + new() + { + StatusCode = HttpFailureCode.BadGateway, + Headers = + [ + new("x-api-version", "2") + ] + }, + new() + { + StatusCode = HttpFailureCode.BadGateway, + Headers = + [ + new("x-api-version", "3") + ] + } + }, + { + new() + { + StatusCode = HttpFailureCode.BadGateway, + Body = new() + { + Type = new("application/json", "utf-8"), + Content = new("{\"error\":\"proxy\"}") + } + }, + new() + { + StatusCode = HttpFailureCode.BadGateway, + Body = new() + { + Type = new("application/json", "utf-8"), + Content = new("{\"error\":\"timeout\"}") + } + } + } + }; +} diff --git a/src/Api.Test/Test.HttpSendFailure/Test.Equality.Equals.cs b/src/Api.Test/Test.HttpSendFailure/Test.Equality.Equals.cs new file mode 100644 index 0000000..cd5798c --- /dev/null +++ b/src/Api.Test/Test.HttpSendFailure/Test.Equality.Equals.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpSendFailureTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void Equals_SourceIsEqualToOther_ExpectTrue( + HttpSendFailure source, HttpSendFailure other) + { + var result = source.Equals(other); + Assert.True(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void Equals_SourceIsNotEqualToOther_ExpectFalse( + HttpSendFailure source, HttpSendFailure other) + { + var result = source.Equals(other); + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpSendFailure/Test.Equality.EqualsWithObject.cs b/src/Api.Test/Test.HttpSendFailure/Test.Equality.EqualsWithObject.cs new file mode 100644 index 0000000..adfd616 --- /dev/null +++ b/src/Api.Test/Test.HttpSendFailure/Test.Equality.EqualsWithObject.cs @@ -0,0 +1,40 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpSendFailureTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void EqualsWithObject_SourceIsEqualToOther_ExpectTrue( + HttpSendFailure source, HttpSendFailure other) + { + var result = source.Equals((object)other); + Assert.True(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void EqualsWithObject_SourceIsNotEqualToOther_ExpectFalse( + HttpSendFailure source, HttpSendFailure other) + { + var result = source.Equals((object)other); + Assert.False(result); + } + + [Fact] + public static void EqualsWithObject_ObjectIsNull_ExpectFalse() + { + object? other = null; + + var result = default(HttpSendFailure).Equals(other); + Assert.False(result); + } + + [Fact] + public static void EqualsWithObject_ObjectHasAnotherType_ExpectFalse() + { + var result = default(HttpSendFailure).Equals(new object()); + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpSendFailure/Test.Equality.GetHashCode.cs b/src/Api.Test/Test.HttpSendFailure/Test.Equality.GetHashCode.cs new file mode 100644 index 0000000..0facfdf --- /dev/null +++ b/src/Api.Test/Test.HttpSendFailure/Test.Equality.GetHashCode.cs @@ -0,0 +1,17 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpSendFailureTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void GetHashCode_SourceIsEqualToOther_ExpectEqualHashCodes( + HttpSendFailure source, HttpSendFailure other) + { + var sourceHashCode = source.GetHashCode(); + var otherHashCode = other.GetHashCode(); + + Assert.Equal(sourceHashCode, otherHashCode); + } +} diff --git a/src/Api.Test/Test.HttpSendFailure/Test.Equality.Operator.Equality.cs b/src/Api.Test/Test.HttpSendFailure/Test.Equality.Operator.Equality.cs new file mode 100644 index 0000000..e35b4ce --- /dev/null +++ b/src/Api.Test/Test.HttpSendFailure/Test.Equality.Operator.Equality.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpSendFailureTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void EqualityOperator_SourceIsEqualToOther_ExpectTrue( + HttpSendFailure source, HttpSendFailure other) + { + var result = source == other; + Assert.True(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void EqualityOperator_SourceIsNotEqualToOther_ExpectFalse( + HttpSendFailure source, HttpSendFailure other) + { + var result = source == other; + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpSendFailure/Test.Equality.Operator.Inequality.cs b/src/Api.Test/Test.HttpSendFailure/Test.Equality.Operator.Inequality.cs new file mode 100644 index 0000000..a98e28c --- /dev/null +++ b/src/Api.Test/Test.HttpSendFailure/Test.Equality.Operator.Inequality.cs @@ -0,0 +1,24 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpSendFailureTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void InequalityOperator_SourceIsEqualToOther_ExpectFalse( + HttpSendFailure source, HttpSendFailure other) + { + var result = source != other; + Assert.False(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void InequalityOperator_SourceIsNotEqualToOther_ExpectTrue( + HttpSendFailure source, HttpSendFailure other) + { + var result = source != other; + Assert.True(result); + } +} diff --git a/src/Api.Test/Test.HttpSendFailure/Test.ToStandardFailure.cs b/src/Api.Test/Test.HttpSendFailure/Test.ToStandardFailure.cs new file mode 100644 index 0000000..181eabb --- /dev/null +++ b/src/Api.Test/Test.HttpSendFailure/Test.ToStandardFailure.cs @@ -0,0 +1,75 @@ +using System; +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpSendFailureTest +{ + [Fact] + public static void ToStandardFailure_BaseMessageIsDefaultNoReasonNoBody_ExpectMessageWithStatusCode() + { + var source = new HttpSendFailure + { + StatusCode = HttpFailureCode.BadRequest + }; + + var actual = source.ToStandardFailure(); + var expected = Failure.Create(HttpFailureCode.BadRequest, "An unexpected http failure occured: 400."); + + Assert.Equal(expected, actual); + } + + [Fact] + public static void ToStandardFailure_BaseMessageIsWhiteSpaceAndReasonPresentNoBody_ExpectMessageWithoutBaseMessage() + { + var source = new HttpSendFailure + { + StatusCode = HttpFailureCode.NotFound, + ReasonPhrase = "Not Found" + }; + + var actual = source.ToStandardFailure(" \n\r "); + var expected = Failure.Create(HttpFailureCode.NotFound, "404 Not Found."); + + Assert.Equal(expected, actual); + } + + [Fact] + public static void ToStandardFailure_BaseMessageAndReasonAndBodyPresent_ExpectMessageWithBody() + { + var source = new HttpSendFailure + { + StatusCode = HttpFailureCode.BadGateway, + ReasonPhrase = "Bad Gateway", + Body = new() + { + Type = new("application/json", "utf-8"), + Content = new("{\"error\":\"proxy\"}") + } + }; + + var actual = source.ToStandardFailure("Failed to execute request:"); + var expected = Failure.Create(HttpFailureCode.BadGateway, "Failed to execute request: 502 Bad Gateway.\n{\"error\":\"proxy\"}"); + + Assert.Equal(expected, actual); + } + + [Fact] + public static void ToStandardFailure_ReasonIsWhiteSpaceBodyPresent_ExpectMessageWithoutReason() + { + var source = new HttpSendFailure + { + StatusCode = HttpFailureCode.ServiceUnavailable, + ReasonPhrase = " ", + Body = new() + { + Content = new("Some error body") + } + }; + + var actual = source.ToStandardFailure("Request failed:"); + var expected = Failure.Create(HttpFailureCode.ServiceUnavailable, "Request failed: 503.\nSome error body"); + + Assert.Equal(expected, actual); + } +} diff --git a/src/Api.Test/Test.HttpSendIn/HttpSendInTest.cs b/src/Api.Test/Test.HttpSendIn/HttpSendInTest.cs index f4851d1..3d5bb0c 100644 --- a/src/Api.Test/Test.HttpSendIn/HttpSendInTest.cs +++ b/src/Api.Test/Test.HttpSendIn/HttpSendInTest.cs @@ -16,10 +16,10 @@ public static TheoryData EqualTestData method: HttpVerb.Get, requestUri: null!) }, - { - new( - method: HttpVerb.Post, - requestUri: "https://www.example.com/about") + { + new( + method: HttpVerb.Post, + requestUri: "https://www.example.com/about") { SuccessType = HttpSuccessType.OnlyHeaders }, @@ -31,10 +31,10 @@ public static TheoryData EqualTestData } }, { - new( - method: HttpVerb.Head, - requestUri: "/services/web-development") - { + new( + method: HttpVerb.Head, + requestUri: "/services/web-development") + { Headers = [ new("x-ms-date", "Wed, 03 Jul 2024 14:41:12 GMT"), @@ -52,12 +52,33 @@ public static TheoryData EqualTestData new("x-ms-version", "2019-02-02"), new("accept", "application/json;odata=nometadata") ] - } - }, - { - new( - method: HttpVerb.Delete, - requestUri: "http://www.example.com/blog/post-title") + } + }, + { + new( + method: HttpVerb.Get, + requestUri: "https://www.example.com/about") + { + Headers = + [ + new(" ", "some value"), + new("x-ms-date", "Wed, 03 Jul 2024 14:41:12 GMT") + ] + }, + new( + method: HttpVerb.Get, + requestUri: "https://www.example.com/about") + { + Headers = + [ + new("x-ms-date", "Wed, 03 Jul 2024 14:41:12 GMT") + ] + } + }, + { + new( + method: HttpVerb.Delete, + requestUri: "http://www.example.com/blog/post-title") { Headers = [ @@ -170,12 +191,20 @@ public static TheoryData UnequalTestData method: HttpVerb.Get, requestUri: "https://www.example.com/About") }, - { - new( - method: HttpVerb.Post, - requestUri: "https://www.example.com/about"), - new( - method: HttpVerb.Post, + { + new( + method: HttpVerb.Get, + requestUri: "https://www.example.com/about"), + new( + method: HttpVerb.Post, + requestUri: "https://www.example.com/about") + }, + { + new( + method: HttpVerb.Post, + requestUri: "https://www.example.com/about"), + new( + method: HttpVerb.Post, requestUri: "https://www.example.com/about") { SuccessType = HttpSuccessType.OnlyHeaders @@ -264,8 +293,29 @@ public static TheoryData UnequalTestData { Type = new("SomeType", "Some CharSet"), Content = new("Some text") - } - } - } - }; -} \ No newline at end of file + } + } + } + }; + + public static TheoryData NullableEqualTestData + => + new() + { + { null, null } + }; + + public static TheoryData NullableUnequalTestData + => + new() + { + { + null, + new(HttpVerb.Get, "https://www.example.com/about") + }, + { + new(HttpVerb.Get, "https://www.example.com/about"), + null + } + }; +} diff --git a/src/Api.Test/Test.HttpSendIn/Test.Constructor.cs b/src/Api.Test/Test.HttpSendIn/Test.Constructor.cs new file mode 100644 index 0000000..a7748ad --- /dev/null +++ b/src/Api.Test/Test.HttpSendIn/Test.Constructor.cs @@ -0,0 +1,30 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpSendInTest +{ + [Fact] + public static void Constructor_ExpectMethodAssigned() + { + var source = new HttpSendIn(HttpVerb.Post, "https://www.example.com/about"); + + Assert.Equal(HttpVerb.Post, source.Method); + } + + [Fact] + public static void Constructor_RequestUriIsNull_ExpectEmptyString() + { + var source = new HttpSendIn(HttpVerb.Get, null!); + + Assert.Equal(string.Empty, source.RequestUri); + } + + [Fact] + public static void Constructor_RequestUriHasSurroundingWhiteSpace_ExpectTrimmed() + { + var source = new HttpSendIn(HttpVerb.Get, " /api/items "); + + Assert.Equal("/api/items", source.RequestUri); + } +} diff --git a/src/Api.Test/Test.HttpSendIn/Test.Equality.Equals.cs b/src/Api.Test/Test.HttpSendIn/Test.Equality.Equals.cs index 0bba596..ebaa09f 100644 --- a/src/Api.Test/Test.HttpSendIn/Test.Equality.Equals.cs +++ b/src/Api.Test/Test.HttpSendIn/Test.Equality.Equals.cs @@ -2,13 +2,22 @@ namespace GarageGroup.Infra.Http.Api.Test; -partial class HttpSendInTest -{ - [Fact] - public static void Equals_OtherIsNull_ExpectFalse() - { - var source = new HttpSendIn(HttpVerb.Get, "https://www.example.com/about"); - var result = source.Equals(null); +partial class HttpSendInTest +{ + [Fact] + public static void Equals_OtherHasSameReference_ExpectTrue() + { + var source = new HttpSendIn(HttpVerb.Get, "https://www.example.com/about"); + + var result = source.Equals(source); + Assert.True(result); + } + + [Fact] + public static void Equals_OtherIsNull_ExpectFalse() + { + var source = new HttpSendIn(HttpVerb.Get, "https://www.example.com/about"); + var result = source.Equals(null); Assert.False(result); } @@ -30,4 +39,4 @@ public static void Equals_SourceIsNotEqualToOther_ExpectFalse( var result = source.Equals(other); Assert.False(result); } -} \ No newline at end of file +} diff --git a/src/Api.Test/Test.HttpSendIn/Test.Equality.Operator.Equality.cs b/src/Api.Test/Test.HttpSendIn/Test.Equality.Operator.Equality.cs index bedf651..bfaa752 100644 --- a/src/Api.Test/Test.HttpSendIn/Test.Equality.Operator.Equality.cs +++ b/src/Api.Test/Test.HttpSendIn/Test.Equality.Operator.Equality.cs @@ -15,10 +15,28 @@ public static void EqualityOperator_SourceIsEqualToOther_ExpectTrue( [Theory] [MemberData(nameof(UnequalTestData))] - public static void EqualityOperator_SourceIsNotEqualToOther_ExpectFalse( - HttpSendIn source, HttpSendIn other) - { - var result = source == other; - Assert.False(result); - } -} \ No newline at end of file + public static void EqualityOperator_SourceIsNotEqualToOther_ExpectFalse( + HttpSendIn source, HttpSendIn other) + { + var result = source == other; + Assert.False(result); + } + + [Theory] + [MemberData(nameof(NullableEqualTestData))] + public static void EqualityOperator_SourceIsNullEqualToOther_ExpectTrue( + HttpSendIn? source, HttpSendIn? other) + { + var result = source == other; + Assert.True(result); + } + + [Theory] + [MemberData(nameof(NullableUnequalTestData))] + public static void EqualityOperator_SourceIsNullNotEqualToOther_ExpectFalse( + HttpSendIn? source, HttpSendIn? other) + { + var result = source == other; + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpSendIn/Test.Equality.Operator.Inequality.cs b/src/Api.Test/Test.HttpSendIn/Test.Equality.Operator.Inequality.cs index a6fcf1f..5a19a03 100644 --- a/src/Api.Test/Test.HttpSendIn/Test.Equality.Operator.Inequality.cs +++ b/src/Api.Test/Test.HttpSendIn/Test.Equality.Operator.Inequality.cs @@ -15,10 +15,28 @@ public static void InequalityOperator_SourceIsEqualToOther_ExpectFalse( [Theory] [MemberData(nameof(UnequalTestData))] - public static void InequalityOperator_SourceIsNotEqualToOther_ExpectTrue( - HttpSendIn source, HttpSendIn other) - { - var result = source != other; - Assert.True(result); - } -} \ No newline at end of file + public static void InequalityOperator_SourceIsNotEqualToOther_ExpectTrue( + HttpSendIn source, HttpSendIn other) + { + var result = source != other; + Assert.True(result); + } + + [Theory] + [MemberData(nameof(NullableEqualTestData))] + public static void InequalityOperator_SourceIsNullEqualToOther_ExpectFalse( + HttpSendIn? source, HttpSendIn? other) + { + var result = source != other; + Assert.False(result); + } + + [Theory] + [MemberData(nameof(NullableUnequalTestData))] + public static void InequalityOperator_SourceIsNullNotEqualToOther_ExpectTrue( + HttpSendIn? source, HttpSendIn? other) + { + var result = source != other; + Assert.True(result); + } +} diff --git a/src/Api.Test/Test.HttpVerb/HttpVerbTest.cs b/src/Api.Test/Test.HttpVerb/HttpVerbTest.cs new file mode 100644 index 0000000..549b67d --- /dev/null +++ b/src/Api.Test/Test.HttpVerb/HttpVerbTest.cs @@ -0,0 +1,40 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +public static partial class HttpVerbTest +{ + public static TheoryData EqualTestData + => + new() + { + { HttpVerb.Get, HttpVerb.From("GET") }, + { HttpVerb.Get, HttpVerb.From("get") }, + { HttpVerb.Patch, HttpVerb.From("patch") }, + { HttpVerb.Get, HttpVerb.From("\n\r") } + }; + + public static TheoryData UnequalTestData + => + new() + { + { HttpVerb.Get, HttpVerb.Post }, + { HttpVerb.Head, HttpVerb.Delete }, + { HttpVerb.From("custom"), HttpVerb.Get } + }; + + public static TheoryData NullableEqualTestData + => + new() + { + { null, null } + }; + + public static TheoryData NullableUnequalTestData + => + new() + { + { null, HttpVerb.Get }, + { HttpVerb.Post, null } + }; +} diff --git a/src/Api.Test/Test.HttpVerb/Test.Equality.Equals.cs b/src/Api.Test/Test.HttpVerb/Test.Equality.Equals.cs new file mode 100644 index 0000000..3b6fca0 --- /dev/null +++ b/src/Api.Test/Test.HttpVerb/Test.Equality.Equals.cs @@ -0,0 +1,33 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpVerbTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void Equals_SourceIsEqualToOther_ExpectTrue( + HttpVerb source, HttpVerb other) + { + var result = source.Equals(other); + Assert.True(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void Equals_SourceIsNotEqualToOther_ExpectFalse( + HttpVerb source, HttpVerb other) + { + var result = source.Equals(other); + Assert.False(result); + } + + [Fact] + public static void Equals_OtherIsNull_ExpectFalse() + { + HttpVerb? other = null; + + var result = HttpVerb.Get.Equals(other); + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpVerb/Test.Equality.EqualsWithObject.cs b/src/Api.Test/Test.HttpVerb/Test.Equality.EqualsWithObject.cs new file mode 100644 index 0000000..c43c96e --- /dev/null +++ b/src/Api.Test/Test.HttpVerb/Test.Equality.EqualsWithObject.cs @@ -0,0 +1,31 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpVerbTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void EqualsWithObject_SourceIsEqualToOther_ExpectTrue( + HttpVerb source, HttpVerb other) + { + var result = source.Equals((object)other); + Assert.True(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void EqualsWithObject_SourceIsNotEqualToOther_ExpectFalse( + HttpVerb source, HttpVerb other) + { + var result = source.Equals((object)other); + Assert.False(result); + } + + [Fact] + public static void EqualsWithObject_OtherHasAnotherType_ExpectFalse() + { + var result = HttpVerb.Get.Equals(new object()); + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpVerb/Test.Equality.GetHashCode.cs b/src/Api.Test/Test.HttpVerb/Test.Equality.GetHashCode.cs new file mode 100644 index 0000000..9e0359b --- /dev/null +++ b/src/Api.Test/Test.HttpVerb/Test.Equality.GetHashCode.cs @@ -0,0 +1,17 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpVerbTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void GetHashCode_SourceIsEqualToOther_ExpectEqualHashes( + HttpVerb source, HttpVerb other) + { + var sourceHash = source.GetHashCode(); + var otherHash = other.GetHashCode(); + + Assert.Equal(sourceHash, otherHash); + } +} diff --git a/src/Api.Test/Test.HttpVerb/Test.Equality.Operator.Equality.cs b/src/Api.Test/Test.HttpVerb/Test.Equality.Operator.Equality.cs new file mode 100644 index 0000000..7c879c9 --- /dev/null +++ b/src/Api.Test/Test.HttpVerb/Test.Equality.Operator.Equality.cs @@ -0,0 +1,42 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpVerbTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void EqualityOperator_SourceIsEqualToOther_ExpectTrue( + HttpVerb source, HttpVerb other) + { + var result = source == other; + Assert.True(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void EqualityOperator_SourceIsNotEqualToOther_ExpectFalse( + HttpVerb source, HttpVerb other) + { + var result = source == other; + Assert.False(result); + } + + [Theory] + [MemberData(nameof(NullableEqualTestData))] + public static void EqualityOperator_SourceIsNullEqualToOther_ExpectTrue( + HttpVerb? source, HttpVerb? other) + { + var result = source == other; + Assert.True(result); + } + + [Theory] + [MemberData(nameof(NullableUnequalTestData))] + public static void EqualityOperator_SourceIsNullNotEqualToOther_ExpectFalse( + HttpVerb? source, HttpVerb? other) + { + var result = source == other; + Assert.False(result); + } +} diff --git a/src/Api.Test/Test.HttpVerb/Test.Equality.Operator.Inequality.cs b/src/Api.Test/Test.HttpVerb/Test.Equality.Operator.Inequality.cs new file mode 100644 index 0000000..18cae7a --- /dev/null +++ b/src/Api.Test/Test.HttpVerb/Test.Equality.Operator.Inequality.cs @@ -0,0 +1,42 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpVerbTest +{ + [Theory] + [MemberData(nameof(EqualTestData))] + public static void InequalityOperator_SourceIsEqualToOther_ExpectFalse( + HttpVerb source, HttpVerb other) + { + var result = source != other; + Assert.False(result); + } + + [Theory] + [MemberData(nameof(UnequalTestData))] + public static void InequalityOperator_SourceIsNotEqualToOther_ExpectTrue( + HttpVerb source, HttpVerb other) + { + var result = source != other; + Assert.True(result); + } + + [Theory] + [MemberData(nameof(NullableEqualTestData))] + public static void InequalityOperator_SourceIsNullEqualToOther_ExpectFalse( + HttpVerb? source, HttpVerb? other) + { + var result = source != other; + Assert.False(result); + } + + [Theory] + [MemberData(nameof(NullableUnequalTestData))] + public static void InequalityOperator_SourceIsNullNotEqualToOther_ExpectTrue( + HttpVerb? source, HttpVerb? other) + { + var result = source != other; + Assert.True(result); + } +} diff --git a/src/Api.Test/Test.HttpVerb/Test.From.cs b/src/Api.Test/Test.HttpVerb/Test.From.cs new file mode 100644 index 0000000..3b9e6b3 --- /dev/null +++ b/src/Api.Test/Test.HttpVerb/Test.From.cs @@ -0,0 +1,27 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpVerbTest +{ + [Fact] + public static void From_NameIsNull_ExpectNameIsGet() + { + var result = HttpVerb.From(null!); + Assert.Equal(HttpVerb.Get.Name, result.Name); + } + + [Fact] + public static void From_NameIsWhiteSpace_ExpectNameIsGet() + { + var result = HttpVerb.From(" \n\r "); + Assert.Equal(HttpVerb.Get.Name, result.Name); + } + + [Fact] + public static void From_NameHasLowerCase_ExpectNameIsUpperCase() + { + var result = HttpVerb.From("test"); + Assert.Equal("TEST", result.Name); + } +} diff --git a/src/Api.Test/Test.HttpVerb/Test.ToString.cs b/src/Api.Test/Test.HttpVerb/Test.ToString.cs new file mode 100644 index 0000000..10efcc2 --- /dev/null +++ b/src/Api.Test/Test.HttpVerb/Test.ToString.cs @@ -0,0 +1,18 @@ +using Xunit; + +namespace GarageGroup.Infra.Http.Api.Test; + +partial class HttpVerbTest +{ + [Theory] + [InlineData("GET")] + [InlineData("POST")] + [InlineData("PATCH")] + public static void ToString_ExpectName(string verbName) + { + var source = HttpVerb.From(verbName.ToLowerInvariant()); + + var result = source.ToString(); + Assert.Equal(verbName, result); + } +} diff --git a/src/Api/Api.csproj b/src/Api/Api.csproj index b22cf66..fe3f522 100644 --- a/src/Api/Api.csproj +++ b/src/Api/Api.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0 + net10.0 disable enable true @@ -9,12 +9,21 @@ $(NoWarn);IDE0130;CA1859 GarageGroup.Infra GarageGroup.Infra.Http.Api - 0.3.0 + 1.0.0 - - + + + + + + <_Parameter1>GarageGroup.Infra.Http.Api.Test + + + + + diff --git a/src/Api/Api/Api.Send.cs b/src/Api/Api/Api.Send.cs index 8c2a3e3..f6823bd 100644 --- a/src/Api/Api/Api.Send.cs +++ b/src/Api/Api/Api.Send.cs @@ -7,20 +7,10 @@ namespace GarageGroup.Infra; partial class HttpApi { - public ValueTask> SendAsync(HttpSendIn input, CancellationToken cancellationToken) + public async ValueTask> SendAsync(HttpSendIn input, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(input); - if (cancellationToken.IsCancellationRequested) - { - return ValueTask.FromCanceled>(cancellationToken); - } - - return InnerSendAsync(input, cancellationToken); - } - - private async ValueTask> InnerSendAsync(HttpSendIn input, CancellationToken cancellationToken) - { using var httpRequest = BuildHttpRequest(input); using var httpResponse = await httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); @@ -37,16 +27,14 @@ private async ValueTask> InnerSendAsync(Htt Body = response.Body }; } - else + + return new HttpSendFailure { - return new HttpSendFailure - { - StatusCode = (HttpFailureCode)response.StatusCode, - ReasonPhrase = response.ReasonPhrase, - Headers = response.Headers, - Body = response.Body - }; - } + StatusCode = (HttpFailureCode)response.StatusCode, + ReasonPhrase = response.ReasonPhrase, + Headers = response.Headers, + Body = response.Body + }; } private static HttpRequestMessage BuildHttpRequest(HttpSendIn input) diff --git a/src/Api/Api/HttpApi.cs b/src/Api/Api/HttpApi.cs index 9d016f3..109c245 100644 --- a/src/Api/Api/HttpApi.cs +++ b/src/Api/Api/HttpApi.cs @@ -42,9 +42,10 @@ private static void SetHeaders(HttpRequestMessage httpRequest, FlatArray ReadBodyAsync(HttpContent httpContent, Cance } private sealed record class HttpOut(int StatusCode, string? ReasonPhrase, FlatArray> Headers, HttpBody Body); -} \ No newline at end of file +} diff --git a/src/Api/HttpApiDependency.cs b/src/Api/HttpApiDependency.cs index db9ff61..9396efe 100644 --- a/src/Api/HttpApiDependency.cs +++ b/src/Api/HttpApiDependency.cs @@ -1,7 +1,8 @@ -using System; -using System.Net.Http; -using Microsoft.Extensions.Configuration; -using PrimeFuncPack; +using System; +using System.Globalization; +using System.Net.Http; +using Microsoft.Extensions.Configuration; +using PrimeFuncPack; namespace GarageGroup.Infra; @@ -86,11 +87,11 @@ HttpApi ResolveApi(IServiceProvider serviceProvider, HttpMessageHandler httpMess return null; } - if (TimeSpan.TryParse(value, out var result)) - { - return result; - } + if (TimeSpan.TryParse(value, CultureInfo.InvariantCulture, out var result)) + { + return result; + } throw new InvalidOperationException($"Section '{section.Path}' key '{key}' value '{value}' must be a valid TimeSpan value."); } -} \ No newline at end of file +} From 2c8651cf0b786d246ff2fd0ee5d2732588d005bd Mon Sep 17 00:00:00 2001 From: pmosk Date: Fri, 17 Apr 2026 15:23:01 +0300 Subject: [PATCH 2/4] Update CI workflow and migrate solution file to slnx --- .github/workflows/dotnet.yml | 53 ++++++++---------------------------- src/Infra.Http.Api.sln | 37 ------------------------- src/Infra.Http.Api.slnx | 5 ++++ 3 files changed, 16 insertions(+), 79 deletions(-) delete mode 100644 src/Infra.Http.Api.sln create mode 100644 src/Infra.Http.Api.slnx diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 3fad790..8098dc7 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -15,53 +15,22 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '9.0.x' - - # Add NuGet Sources + dotnet-version: '10.0.x' - name: Create Local NuGet Directory run: mkdir ~/nuget - - name: Add Local Nuget Source - run: dotnet nuget add source ~/nuget - - - name: Add Garage Group NuGet Source - run: > - dotnet nuget add source ${{ vars.GG_NUGET_SOURCE_URL }} - -n garage - -u ${{ secrets.GG_NUGET_SOURCE_USER_NAME }} - -p ${{ secrets.GG_NUGET_SOURCE_USER_PASSWORD }} - --store-password-in-clear-text - - # Api.Contract - - - name: Restore Api.Contract - run: dotnet restore ./src/*/Api.Contract.csproj - - - name: Build Api.Contract - run: dotnet build ./src/*/Api.Contract.csproj --no-restore -c Release - - - name: Pack Api.Contract - run: dotnet pack ./src/*/Api.Contract.csproj --no-restore -o ~/nuget -c Release - - # Api - - - name: Restore Api - run: dotnet restore ./src/*/Api.csproj - - - name: Build Api - run: dotnet build ./src/*/Api.csproj --no-restore -c Release - - - name: Pack Api - run: dotnet pack ./src/*/Api.csproj --no-restore -o ~/nuget -c Release + - name: Restore Infra.Http.Api + run: dotnet restore ./Infra.Http.Api.slnx - # Api.Test + - name: Build Infra.Http.Api + run: dotnet build ./Infra.Http.Api.slnx --no-restore -c Release - - name: Restore Api.Test - run: dotnet restore ./src/*/Api.Test.csproj + - name: Test Infra.Http.Api + run: dotnet test ./Infra.Http.Api.slnx --no-restore -c Release - - name: Test Api.Test - run: dotnet test ./src/*/Api.Test.csproj --no-restore -c Release + - name: Pack Infra.Http.Api + run: dotnet pack ./Infra.Http.Api.slnx --no-build -o ~/nuget -c Release # Push @@ -69,6 +38,6 @@ jobs: if: ${{ github.ref == 'refs/heads/main' }} run: > dotnet nuget push "../../../nuget/*.nupkg" - -s ${{ vars.GG_NUGET_SOURCE_URL }} - -k ${{ secrets.GG_NUGET_SOURCE_USER_PASSWORD }} + -s https://api.nuget.org/v3/index.json + -k ${{ secrets.NUGET_SECRET }} --skip-duplicate diff --git a/src/Infra.Http.Api.sln b/src/Infra.Http.Api.sln deleted file mode 100644 index f286654..0000000 --- a/src/Infra.Http.Api.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.002.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Api.Test", "Api.Test\Api.Test.csproj", "{5CE0128F-B405-4F9C-874B-C8355A6CD6BA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Api.Contract", "Api.Contract\Api.Contract.csproj", "{71E36871-9D43-4CC5-A191-3CF9B1DDAD0B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Api", "Api\Api.csproj", "{DD25FFE8-2BFD-4358-A1A2-92D89D799628}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5CE0128F-B405-4F9C-874B-C8355A6CD6BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5CE0128F-B405-4F9C-874B-C8355A6CD6BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5CE0128F-B405-4F9C-874B-C8355A6CD6BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5CE0128F-B405-4F9C-874B-C8355A6CD6BA}.Release|Any CPU.Build.0 = Release|Any CPU - {71E36871-9D43-4CC5-A191-3CF9B1DDAD0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {71E36871-9D43-4CC5-A191-3CF9B1DDAD0B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {71E36871-9D43-4CC5-A191-3CF9B1DDAD0B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {71E36871-9D43-4CC5-A191-3CF9B1DDAD0B}.Release|Any CPU.Build.0 = Release|Any CPU - {DD25FFE8-2BFD-4358-A1A2-92D89D799628}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DD25FFE8-2BFD-4358-A1A2-92D89D799628}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DD25FFE8-2BFD-4358-A1A2-92D89D799628}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DD25FFE8-2BFD-4358-A1A2-92D89D799628}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {6EC80F57-CE6F-4372-8624-BA75FB6F0732} - EndGlobalSection -EndGlobal diff --git a/src/Infra.Http.Api.slnx b/src/Infra.Http.Api.slnx new file mode 100644 index 0000000..cdf48ee --- /dev/null +++ b/src/Infra.Http.Api.slnx @@ -0,0 +1,5 @@ + + + + + From af68183579a650d0c4246ebf379448ed2f39328c Mon Sep 17 00:00:00 2001 From: pmosk Date: Fri, 17 Apr 2026 15:25:03 +0300 Subject: [PATCH 3/4] Minimal improvements --- src/Api.Contract/Input/HttpSendIn.cs | 34 ++++++++++++++++------------ src/Api.Test/Api.Test.csproj | 10 ++++---- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/Api.Contract/Input/HttpSendIn.cs b/src/Api.Contract/Input/HttpSendIn.cs index e3abbbe..84bbf63 100644 --- a/src/Api.Contract/Input/HttpSendIn.cs +++ b/src/Api.Contract/Input/HttpSendIn.cs @@ -109,19 +109,25 @@ public override string ToString() return builder.Append('\n').Append('\n').Append(body).ToString(); } - public static bool operator ==(HttpSendIn? left, HttpSendIn? right) - { - if (ReferenceEquals(left, right)) - { - return true; - } - - return left?.Equals(right) is true; - } - - public static bool operator !=(HttpSendIn? left, HttpSendIn? right) - => - (left == right) is false; + public static bool operator ==(HttpSendIn? left, HttpSendIn? right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + return left?.Equals(right) is true; + } + + public static bool operator !=(HttpSendIn? left, HttpSendIn? right) + { + if (ReferenceEquals(left, right)) + { + return false; + } + + return left?.Equals(right) is not true; + } private static FlatArray> GetOrderedHeaders(FlatArray> source) { @@ -169,4 +175,4 @@ static string GetKey(KeyValuePair> kv) => kv.Key; } -} +} diff --git a/src/Api.Test/Api.Test.csproj b/src/Api.Test/Api.Test.csproj index 3a03c9a..af40a5f 100644 --- a/src/Api.Test/Api.Test.csproj +++ b/src/Api.Test/Api.Test.csproj @@ -17,16 +17,16 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + \ No newline at end of file From 1bc81ce6a12f774cd9a41ca289a30a895213efda Mon Sep 17 00:00:00 2001 From: pmosk Date: Fri, 17 Apr 2026 15:26:40 +0300 Subject: [PATCH 4/4] Fix dontet.yml --- .github/workflows/dotnet.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 8098dc7..245c5a6 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -21,16 +21,16 @@ jobs: run: mkdir ~/nuget - name: Restore Infra.Http.Api - run: dotnet restore ./Infra.Http.Api.slnx + run: dotnet restore ./*/Infra.Http.Api.slnx - name: Build Infra.Http.Api - run: dotnet build ./Infra.Http.Api.slnx --no-restore -c Release + run: dotnet build ./*/Infra.Http.Api.slnx --no-restore -c Release - name: Test Infra.Http.Api - run: dotnet test ./Infra.Http.Api.slnx --no-restore -c Release + run: dotnet test ./*/Infra.Http.Api.slnx --no-restore -c Release - name: Pack Infra.Http.Api - run: dotnet pack ./Infra.Http.Api.slnx --no-build -o ~/nuget -c Release + run: dotnet pack ./*/Infra.Http.Api.slnx --no-build -o ~/nuget -c Release # Push