From 7ea950a96b580951e407d947db6936716015d226 Mon Sep 17 00:00:00 2001 From: Tim M <49349513+TimothyMakkison@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:30:13 +0100 Subject: [PATCH] feat: add `ReflectionTests` for `IUrlParameterFormatter` (#1888) * feat: add `ReflectionTests` * feat: add generic and enumerable tests --- Refit.Tests/ReflectionTests.cs | 380 +++++++++++++++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 Refit.Tests/ReflectionTests.cs diff --git a/Refit.Tests/ReflectionTests.cs b/Refit.Tests/ReflectionTests.cs new file mode 100644 index 000000000..6bf935ddc --- /dev/null +++ b/Refit.Tests/ReflectionTests.cs @@ -0,0 +1,380 @@ +using System.Net.Http; +using System.Reflection; +using RichardSzalay.MockHttp; +using Xunit; + +namespace Refit.Tests; + +public interface IBasicApi +{ + [Get("/{value}")] + Task GetParam(string value); + + [Get("/{value}")] + Task GetDerivedParam(BaseRecord value); + + [Get("/{value.PropValue}")] + Task GetPropertyParam(MyParams value); + + [Get("/{value}")] + Task GetGenericParam(T value); + + [Get("/")] + Task GetQuery(string queryKey); + + [Get("/")] + Task GetGenericQuery(T queryKey); + + [Get("/")] + Task GetPropertyQuery(BaseRecord queryKey); + + [Get("/")] + Task GetEnumerableQuery(IEnumerable enums); + + [Get("/")] + Task GetEnumerablePropertyQuery(MyEnumerableParams enums); + + [Get("/")] + Task GetDictionaryQuery(IDictionary dict); +} + +public record DerivedRecordWithProperty(string Name) : BaseRecord("value"); + +public record DerivedRecord(string Value) : BaseRecord(Value); + +public record BaseRecord(string Value); + +public record MyParams(string PropValue); + +public record MyEnumerableParams(int[] Enumerable); + +public class TestUrlFormatter : IUrlParameterFormatter +{ + private readonly ICustomAttributeProvider[] expectedAttributeProviders; + private readonly Type[] expectedTypes; + private int index; + + public TestUrlFormatter(ICustomAttributeProvider expectedAttributeProvider, Type expectedType) + { + expectedAttributeProviders = [expectedAttributeProvider]; + expectedTypes = [expectedType]; + } + + public TestUrlFormatter( + ICustomAttributeProvider[] expectedAttributeProviders, + Type[] expectedTypes + ) + { + this.expectedAttributeProviders = expectedAttributeProviders; + this.expectedTypes = expectedTypes; + } + + public string Format(object value, ICustomAttributeProvider attributeProvider, Type type) + { + Assert.Equal(expectedAttributeProviders[index], attributeProvider); + Assert.Equal(expectedTypes[index], type); + index++; + return value!.ToString(); + } + + public void AssertNoOutstandingAssertions() + { + Assert.Equal(expectedAttributeProviders.Length, index); + Assert.Equal(expectedTypes.Length, index); + } +} + +public sealed class ReflectionTests : IDisposable +{ + readonly MockHttpMessageHandler mockHandler = new(); + + [Fact] + public async Task UrlParameterShouldBeExpectedReflection() + { + mockHandler + .Expect(HttpMethod.Get, "https://foo/bar") + .Respond("application/json", nameof(IBasicApi.GetParam)); + + var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetParam))!; + var parameterInfo = methodInfo.GetParameters()[0]; + + var formatter = new TestUrlFormatter(parameterInfo, typeof(string)); + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => mockHandler, + UrlParameterFormatter = formatter + }; + var service = RestService.For("https://foo", settings); + + await service.GetParam("bar"); + formatter.AssertNoOutstandingAssertions(); + } + + [Fact] + public async Task DerivedUrlParameterShouldBeExpectedReflection() + { + mockHandler + .Expect(HttpMethod.Get, "https://foo/DerivedRecord%20%7B%20Value%20%3D%20Derived%20%7D") + .Respond("application/json", nameof(IBasicApi.GetDerivedParam)); + + var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetDerivedParam))!; + var parameterInfo = methodInfo.GetParameters()[0]; + + var formatter = new TestUrlFormatter(parameterInfo, typeof(BaseRecord)); + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => mockHandler, + UrlParameterFormatter = formatter + }; + var service = RestService.For("https://foo", settings); + + await service.GetDerivedParam(new DerivedRecord("Derived")); + formatter.AssertNoOutstandingAssertions(); + } + + [Fact] + public async Task PropertyParameterShouldBeExpectedReflection() + { + mockHandler + .Expect(HttpMethod.Get, "https://foo/propVal") + .Respond("application/json", nameof(IBasicApi.GetPropertyParam)); + + var propertyInfo = typeof(MyParams).GetProperties()[0]; + + var formatter = new TestUrlFormatter(propertyInfo, typeof(string)); + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => mockHandler, + UrlParameterFormatter = formatter + }; + var service = RestService.For("https://foo", settings); + + await service.GetPropertyParam(new MyParams("propVal")); + formatter.AssertNoOutstandingAssertions(); + } + + [Fact] + public async Task GenericParameterShouldBeExpectedReflection() + { + mockHandler + .Expect(HttpMethod.Get, "https://foo/genericVal") + .Respond("application/json", nameof(IBasicApi.GetGenericParam)); + + var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetGenericParam))!; + var stringMethod = methodInfo.MakeGenericMethod(typeof(string)); + var parameterInfo = stringMethod.GetParameters()[0]; + + var formatter = new TestUrlFormatter(parameterInfo, typeof(string)); + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => mockHandler, + UrlParameterFormatter = formatter + }; + var service = RestService.For("https://foo", settings); + + await service.GetGenericParam("genericVal"); + formatter.AssertNoOutstandingAssertions(); + } + + [Fact] + public async Task QueryParameterShouldBeExpectedReflection() + { + mockHandler + .Expect(HttpMethod.Get, "https://foo/") + .WithExactQueryString( + new[] { new KeyValuePair("queryKey", "queryValue"), } + ) + .Respond("application/json", nameof(IBasicApi.GetQuery)); + + var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetQuery))!; + var parameterInfo = methodInfo.GetParameters()[0]; + + var formatter = new TestUrlFormatter(parameterInfo, typeof(string)); + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => mockHandler, + UrlParameterFormatter = formatter + }; + var service = RestService.For("https://foo", settings); + + await service.GetQuery("queryValue"); + formatter.AssertNoOutstandingAssertions(); + } + + [Fact] + public async Task QueryPropertyParameterShouldBeExpectedReflection() + { + mockHandler + .Expect(HttpMethod.Get, "https://foo/") + .WithExactQueryString(new[] { new KeyValuePair("Value", "queryVal"), }) + .Respond("application/json", nameof(IBasicApi.GetPropertyQuery)); + + var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetPropertyQuery))!; + var parameterInfo = methodInfo.GetParameters()[0]; + + var formatter = new TestUrlFormatter(parameterInfo, typeof(BaseRecord)); + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => mockHandler, + UrlParameterFormatter = formatter + }; + var service = RestService.For("https://foo", settings); + + await service.GetPropertyQuery(new BaseRecord("queryVal")); + formatter.AssertNoOutstandingAssertions(); + } + + [Fact] + public async Task DerivedQueryPropertyParameterShouldBeExpectedReflection() + { + mockHandler + .Expect(HttpMethod.Get, "https://foo/") + .WithExactQueryString( + new[] + { + new KeyValuePair("Name", "queryName"), + new KeyValuePair("Value", "value"), + } + ) + .Respond("application/json", nameof(IBasicApi.GetPropertyQuery)); + + var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetPropertyQuery))!; + var parameterInfo = methodInfo.GetParameters()[0]; + + var formatter = new TestUrlFormatter( + [parameterInfo, parameterInfo], + [typeof(BaseRecord), typeof(BaseRecord)] + ); + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => mockHandler, + UrlParameterFormatter = formatter + }; + var service = RestService.For("https://foo", settings); + + await service.GetPropertyQuery(new DerivedRecordWithProperty("queryName")); + formatter.AssertNoOutstandingAssertions(); + } + + [Fact] + public async Task GenericQueryParameterShouldBeExpectedReflection() + { + mockHandler + .Expect(HttpMethod.Get, "https://foo/") + .WithExactQueryString( + new[] { new KeyValuePair("queryKey", "queryValue"), } + ) + .Respond("application/json", nameof(IBasicApi.GetGenericQuery)); + + var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetGenericQuery))!; + var stringMethod = methodInfo.MakeGenericMethod(typeof(string)); + var parameterInfo = stringMethod.GetParameters()[0]; + + var formatter = new TestUrlFormatter(parameterInfo, typeof(string)); + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => mockHandler, + UrlParameterFormatter = formatter + }; + var service = RestService.For("https://foo", settings); + + await service.GetGenericQuery("queryValue"); + formatter.AssertNoOutstandingAssertions(); + } + + [Fact] + public async Task EnumerableQueryParameterShouldBeExpectedReflection() + { + mockHandler + .Expect(HttpMethod.Get, "https://foo/") + .WithExactQueryString(new[] { new KeyValuePair("enums", "k0,k1"), }) + .Respond("application/json", nameof(IBasicApi.GetEnumerableQuery)); + + var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetEnumerableQuery))!; + var parameterInfo = methodInfo.GetParameters()[0]; + + var formatter = new TestUrlFormatter( + [parameterInfo, parameterInfo], + [typeof(IEnumerable), typeof(IEnumerable)] + ); + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => mockHandler, + UrlParameterFormatter = formatter + }; + var service = RestService.For("https://foo", settings); + + await service.GetEnumerableQuery(["k0", "k1"]); + formatter.AssertNoOutstandingAssertions(); + } + + [Fact] + public async Task EnumerablePropertyQueryParameterShouldBeExpectedReflection() + { + mockHandler + .Expect(HttpMethod.Get, "https://foo/") + .WithExactQueryString(new[] { new KeyValuePair("Enumerable", "0,1"), }) + .Respond("application/json", nameof(IBasicApi.GetEnumerablePropertyQuery)); + + var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetEnumerablePropertyQuery))!; + var parameterInfo = methodInfo.GetParameters()[0]; + var propertyInfo = typeof(MyEnumerableParams).GetProperties()[0]; + + var formatter = new TestUrlFormatter( + [propertyInfo, propertyInfo, parameterInfo], + [typeof(int[]), typeof(int[]), typeof(MyEnumerableParams)] + ); + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => mockHandler, + UrlParameterFormatter = formatter + }; + var service = RestService.For("https://foo", settings); + + await service.GetEnumerablePropertyQuery(new MyEnumerableParams([0, 1])); + formatter.AssertNoOutstandingAssertions(); + } + + [Fact] + public async Task QueryDictionaryParameterShouldBeExpectedReflection() + { + mockHandler + .Expect(HttpMethod.Get, "https://foo/") + .WithExactQueryString( + new[] + { + new KeyValuePair("key0", "1"), + new KeyValuePair("key1", "2"), + } + ) + .Respond("application/json", nameof(IBasicApi.GetDictionaryQuery)); + + var methodInfo = typeof(IBasicApi).GetMethod(nameof(IBasicApi.GetDictionaryQuery))!; + var parameterInfo = methodInfo.GetParameters()[0]; + + var formatter = new TestUrlFormatter( + [typeof(string), typeof(string), parameterInfo, parameterInfo], + [ + typeof(string), + typeof(string), + typeof(IDictionary), + typeof(IDictionary) + ] + ); + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => mockHandler, + UrlParameterFormatter = formatter + }; + var service = RestService.For("https://foo", settings); + + var dict = new Dictionary { { "key0", 1 }, { "key1", 2 } }; + await service.GetDictionaryQuery(dict); + formatter.AssertNoOutstandingAssertions(); + } + + public void Dispose() + { + mockHandler?.Dispose(); + } +}