diff --git a/RestSharp.Lib/RestSharp.Lib.csproj b/RestSharp.Lib/RestSharp.Lib.csproj new file mode 100644 index 000000000..8cda2c3ef --- /dev/null +++ b/RestSharp.Lib/RestSharp.Lib.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + disable + disable + RestSharp + RestSharp + FRAMEWORK + false + CS0618;CS0672;CS0162;CS0168;CS0219;CS8600;CS8601;CS8602;CS8603;CS8604;CS8625;CS0108;CS0114;CS0579;CS0436 + + + + + + + + + + + + + + + + diff --git a/RestSharp.Tests.Coverage/AdditionalCoverageTests.cs b/RestSharp.Tests.Coverage/AdditionalCoverageTests.cs new file mode 100644 index 000000000..118762dd6 --- /dev/null +++ b/RestSharp.Tests.Coverage/AdditionalCoverageTests.cs @@ -0,0 +1,523 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using RestSharp; +using RestSharp.Deserializers; +using RestSharp.Extensions; +using RestSharp.Serializers; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + /// + /// Additional tests targeting remaining uncovered lines across the codebase. + /// + + public class XmlDeserializerAdditionalCoverageTests + { + [Fact] + public void Can_Deserialize_Uri() + { + var deserializer = new XmlDeserializer(); + var response = new RestResponse + { + Content = "http://example.com" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("http://example.com/", result.Link.ToString()); + } + + [Fact] + public void Can_Deserialize_Float() + { + var deserializer = new XmlDeserializer(); + var response = new RestResponse + { + Content = "1.5" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(1.5f, result.Rate, 1); + } + + [Fact] + public void Can_Deserialize_Int64() + { + var deserializer = new XmlDeserializer(); + var response = new RestResponse + { + Content = "9223372036854775807" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(long.MaxValue, result.BigNum); + } + + [Fact] + public void Can_Deserialize_DateFormat_Custom() + { + var deserializer = new XmlDeserializer(); + deserializer.DateFormat = "yyyy-MM-dd"; + var response = new RestResponse + { + Content = "2023-12-25" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(new DateTime(2023, 12, 25), result.Date); + } + + [Fact] + public void Can_Deserialize_With_Culture() + { + var deserializer = new XmlDeserializer(); + deserializer.Culture = CultureInfo.InvariantCulture; + var response = new RestResponse + { + Content = "19.95" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(19.95m, result.Price); + } + + [Fact] + public void Can_Deserialize_With_Namespace_And_Prefix() + { + var deserializer = new XmlDeserializer(); + deserializer.Namespace = "http://example.com/ns"; + var response = new RestResponse + { + Content = "ns_value" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("ns_value", result.Name); + } + + [Fact] + public void Can_Deserialize_Nullable_DateTime() + { + var deserializer = new XmlDeserializer(); + var response = new RestResponse + { + Content = "2023-06-15T00:00:00" + }; + var result = deserializer.Deserialize(response); + Assert.NotNull(result.When); + Assert.Equal(new DateTime(2023, 6, 15), result.When.Value); + } + + [Fact] + public void Can_Deserialize_Nested_Object() + { + var deserializer = new XmlDeserializer(); + var response = new RestResponse + { + Content = "child_name10" + }; + var result = deserializer.Deserialize(response); + Assert.NotNull(result.Child); + Assert.Equal("child_name", result.Child.Name); + Assert.Equal(10, result.Child.Value); + } + + [Fact] + public void Can_Deserialize_Nullable_Bool() + { + var deserializer = new XmlDeserializer(); + var response = new RestResponse + { + Content = "true" + }; + var result = deserializer.Deserialize(response); + Assert.True(result.Active); + } + + [Fact] + public void Can_Deserialize_Nullable_Bool_Missing() + { + var deserializer = new XmlDeserializer(); + var response = new RestResponse + { + Content = "" + }; + var result = deserializer.Deserialize(response); + Assert.Null(result.Active); + } + + [Fact] + public void RootElement_Property() + { + var deserializer = new XmlDeserializer(); + deserializer.RootElement = "Root"; + Assert.Equal("Root", deserializer.RootElement); + } + + [Fact] + public void XmlNamespace_Property() + { + var deserializer = new XmlDeserializer(); + deserializer.Namespace = "http://ns.example.com"; + Assert.Equal("http://ns.example.com", deserializer.Namespace); + } + } + + public class XmlSerializerAdditionalCoverageTests + { + [Fact] + public void Serialize_With_RootElement() + { + var serializer = new XmlSerializer(); + serializer.RootElement = "CustomRoot"; + var result = serializer.Serialize(new SimpleSerObj { Name = "test" }); + Assert.Contains("CustomRoot", result); + } + + [Fact] + public void Serialize_With_DateFormat() + { + var serializer = new XmlSerializer(); + serializer.DateFormat = "yyyy-MM-dd"; + var result = serializer.Serialize(new ObjWithDateTime { When = new DateTime(2023, 6, 15) }); + Assert.Contains("2023-06-15", result); + } + + [Fact] + public void Serialize_Null_Value_Skipped() + { + var serializer = new XmlSerializer(); + var result = serializer.Serialize(new ObjWithNullableString { Name = null }); + Assert.DoesNotContain("", result); + } + + [Fact] + public void Serialize_With_Int_Property() + { + var serializer = new XmlSerializer(); + var obj = new ObjWithInt { Count = 42 }; + var result = serializer.Serialize(obj); + Assert.Contains("42", result); + } + + [Fact] + public void Serialize_Nested_Object() + { + var serializer = new XmlSerializer(); + var result = serializer.Serialize(new ObjWithChild { Child = new SimpleSerObj { Name = "nested" } }); + Assert.Contains("nested", result); + } + + [Fact] + public void Namespace_Property() + { + var serializer = new XmlSerializer(); + serializer.Namespace = "http://ns.example.com"; + Assert.Equal("http://ns.example.com", serializer.Namespace); + } + + [Fact] + public void RootElement_Property() + { + var serializer = new XmlSerializer(); + serializer.RootElement = "Root"; + Assert.Equal("Root", serializer.RootElement); + } + } + + public class RestRequestAdditionalCoverageTests + { + [Fact] + public void AddBody_With_XmlNamespace() + { + var request = new RestRequest(); + request.XmlSerializer = new XmlSerializer(); + request.RequestFormat = DataFormat.Xml; + request.AddBody(new SimpleSerObj { Name = "test" }, "http://example.com/ns"); + var bodyParam = request.Parameters.FirstOrDefault(p => p.Type == ParameterType.RequestBody); + Assert.NotNull(bodyParam); + } + + [Fact] + public void AddBody_Json_Format() + { + var request = new RestRequest(); + request.RequestFormat = DataFormat.Json; + request.AddBody(new SimpleSerObj { Name = "json" }); + var bodyParam = request.Parameters.FirstOrDefault(p => p.Type == ParameterType.RequestBody); + Assert.NotNull(bodyParam); + Assert.Contains("json", bodyParam.Value.ToString()); + } + + [Fact] + public void AddObject_With_Whitelist() + { + var request = new RestRequest(); + var obj = new ObjWithMultipleProps { Name = "test", Age = 25, Email = "test@test.com" }; + request.AddObject(obj, "Name", "Age"); + Assert.Contains(request.Parameters, p => p.Name == "Name" && p.Value.ToString() == "test"); + Assert.Contains(request.Parameters, p => p.Name == "Age" && p.Value.ToString() == "25"); + Assert.DoesNotContain(request.Parameters, p => p.Name == "Email"); + } + + [Fact] + public void AddFile_With_Writer() + { + var request = new RestRequest(); + Action writer = s => s.Write(new byte[] { 1, 2, 3 }, 0, 3); + request.AddFile("file", writer, "test.bin", "application/octet-stream"); + Assert.Single(request.Files); + Assert.Equal("file", request.Files[0].Name); + } + + [Fact] + public void AddFile_With_Bytes() + { + var request = new RestRequest(); + request.AddFile("file", new byte[] { 1, 2, 3 }, "test.bin", "application/octet-stream"); + Assert.Single(request.Files); + } + + [Fact] + public void IncreaseNumAttempts() + { + var request = new RestRequest(); + Assert.Equal(0, request.Attempts); + request.IncreaseNumAttempts(); + Assert.Equal(1, request.Attempts); + } + + [Fact] + public void Credentials_Property() + { + var request = new RestRequest(); + var creds = new NetworkCredential("user", "pass"); + request.Credentials = creds; + Assert.Equal(creds, request.Credentials); + } + + [Fact] + public void RequestFormat_Default_Is_Xml() + { + var request = new RestRequest(); + Assert.Equal(DataFormat.Xml, request.RequestFormat); + } + + [Fact] + public void Method_Property() + { + var request = new RestRequest(Method.POST); + Assert.Equal(Method.POST, request.Method); + } + } + + public class RestClientAdditionalCoverageTests + { + [Fact] + public void GetHandler_Returns_Default_For_Unknown_ContentType() + { + var client = new RestClient("http://example.com"); + client.ClearHandlers(); + // After clearing, handlers should be empty + // Exercise the handler management + client.AddHandler("application/json", new RestSharp.Deserializers.JsonDeserializer()); + client.RemoveHandler("application/json"); + } + + [Fact] + public void Execute_With_Authenticator() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + client.Authenticator = new HttpBasicAuthenticator("user", "pass"); + + var request = new RestRequest("api", Method.GET); + var response = client.Execute(request); + + Assert.Contains(fakeHttp.Headers, h => h.Name == "Authorization"); + } + + [Fact] + public void Execute_Generic_Deserializes() + { + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactory(new HttpResponse + { + RawBytes = Encoding.UTF8.GetBytes("{\"Name\":\"test\",\"Value\":42}"), + ContentType = "application/json", + StatusCode = HttpStatusCode.OK, + ResponseStatus = ResponseStatus.Completed + }); + + var response = client.Execute(new RestRequest("api", Method.GET)); + Assert.NotNull(response.Data); + Assert.Equal("test", response.Data.Name); + } + + [Fact] + public void DownloadData_Returns_Bytes() + { + var rawBytes = Encoding.UTF8.GetBytes("raw data"); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactory(new HttpResponse + { + RawBytes = rawBytes, + StatusCode = HttpStatusCode.OK, + ResponseStatus = ResponseStatus.Completed + }); + + var result = client.DownloadData(new RestRequest("api", Method.GET)); + Assert.Equal(rawBytes, result); + } + + [Fact] + public void UseSynchronizationContext_Can_Be_Set() + { + var client = new RestClient("http://example.com"); + client.UseSynchronizationContext = true; + Assert.True(client.UseSynchronizationContext); + } + + [Fact] + public void Timeout_Property() + { + var client = new RestClient("http://example.com"); + client.Timeout = 5000; + Assert.Equal(5000, client.Timeout); + } + } + + public class ReflectionExtensionAdditionalTests + { + [Fact] + public void ChangeType_Handles_Null() + { + var result = ((object)null).ChangeType(typeof(string), CultureInfo.InvariantCulture); + Assert.Null(result); + } + + [Fact] + public void FindEnumValue_Case_Insensitive() + { + var result = typeof(Method).FindEnumValue("post", CultureInfo.InvariantCulture); + Assert.Equal(Method.POST, result); + } + + [Fact] + public void FindEnumValue_Exact_Name() + { + var result = typeof(Method).FindEnumValue("GET", CultureInfo.InvariantCulture); + Assert.Equal(Method.GET, result); + } + } + + public class StringExtensionAdditionalTests + { + [Fact] + public void ParseJsonDate_Unix_Timestamp() + { + // Unix timestamp (seconds since epoch) + var result = "1234567890".ParseJsonDate(CultureInfo.InvariantCulture); + Assert.NotEqual(default(DateTime), result); + Assert.Equal(2009, result.Year); + } + + [Fact] + public void ParseJsonDate_ISO8601() + { + var result = "2023-06-15T12:30:00Z".ParseJsonDate(CultureInfo.InvariantCulture); + Assert.Equal(2023, result.Year); + Assert.Equal(6, result.Month); + Assert.Equal(15, result.Day); + } + + [Fact] + public void ParseJsonDate_NewDate_Format() + { + var result = "new Date(1234567890000)".ParseJsonDate(CultureInfo.InvariantCulture); + Assert.NotEqual(default(DateTime), result); + } + + [Fact] + public void GetNameVariants_With_Underscores() + { + var variants = "SomeLongName".GetNameVariants(CultureInfo.InvariantCulture).ToList(); + Assert.Contains("some_long_name", variants); + Assert.Contains("some-long-name", variants); + } + + [Fact] + public void AddUnderscores_Single_Word() + { + var result = "Hello".AddUnderscores(); + Assert.Equal("Hello", result); + } + + [Fact] + public void AddDashes_Single_Word() + { + var result = "Hello".AddDashes(); + Assert.Equal("Hello", result); + } + + [Fact] + public void AddUnderscores_With_Consecutive_Uppercase() + { + var result = "XMLParser".AddUnderscores(); + Assert.Contains("_", result); + } + + [Fact] + public void ToCamelCase_Single_Word() + { + var result = "Hello".ToCamelCase(CultureInfo.InvariantCulture); + Assert.Equal("hello", result); + } + + [Fact] + public void MakeInitialLowerCase_SingleChar() + { + var result = "A".MakeInitialLowerCase(); + Assert.Equal("a", result); + } + + [Fact] + public void UrlEncode_Empty_Returns_Empty() + { + var result = "".UrlEncode(); + Assert.Equal("", result); + } + + [Fact] + public void HasValue_Returns_True_For_Whitespace() + { + Assert.True(" ".HasValue()); + } + } + + // Model classes for tests + public class XmlUriItem { public Uri Link { get; set; } } + public class XmlFloatItem2 { public float Rate { get; set; } } + public class XmlLong2 { public long BigNum { get; set; } } + public class XmlNullDateItem { public DateTime? When { get; set; } } + public class XmlDateFmtItem { public DateTime Date { get; set; } } + public class XmlCultureItem { public decimal Price { get; set; } } + public class XmlSimpleNs { public string Name { get; set; } } + + public class XmlNestedItem { public XmlChildItem Child { get; set; } } + public class XmlChildItem { public string Name { get; set; } public int Value { get; set; } } + public class XmlNullBoolItem { public bool? Active { get; set; } } + + public class SimpleSerObj { public string Name { get; set; } } + public class ObjWithDateTime { public DateTime When { get; set; } } + public class ObjWithNullableString { public string Name { get; set; } } + public class ObjWithUriProp { public Uri Link { get; set; } } + public class ObjWithInt { public int Count { get; set; } } + public class ObjWithChild { public SimpleSerObj Child { get; set; } } + public class ObjWithMultipleProps + { + public string Name { get; set; } + public int Age { get; set; } + public string Email { get; set; } + } +} diff --git a/RestSharp.Tests.Coverage/AsyncAndExtensionTests.cs b/RestSharp.Tests.Coverage/AsyncAndExtensionTests.cs new file mode 100644 index 000000000..e963325f0 --- /dev/null +++ b/RestSharp.Tests.Coverage/AsyncAndExtensionTests.cs @@ -0,0 +1,768 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using Newtonsoft.Json.Linq; +using RestSharp; +using RestSharp.Extensions; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class RestClientAsyncTests + { + [Fact] + public void ExecuteAsync_GET_Returns_Handle() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + RestResponse receivedResponse = null; + var handle = client.ExecuteAsync(new RestRequest("api", Method.GET), (response, asyncHandle) => + { + receivedResponse = response; + }); + + Assert.NotNull(handle); + Assert.NotNull(receivedResponse); + } + + [Fact] + public void ExecuteAsync_POST_Returns_Handle() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + RestResponse receivedResponse = null; + var handle = client.ExecuteAsync(new RestRequest("api", Method.POST), (response, asyncHandle) => + { + receivedResponse = response; + }); + + Assert.NotNull(handle); + Assert.Equal("POST", fakeHttp.LastMethod); + } + + [Fact] + public void ExecuteAsync_PUT_Returns_Handle() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var handle = client.ExecuteAsync(new RestRequest("api", Method.PUT), (r, h) => { }); + Assert.NotNull(handle); + Assert.Equal("PUT", fakeHttp.LastMethod); + } + + [Fact] + public void ExecuteAsync_DELETE_Returns_Handle() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var handle = client.ExecuteAsync(new RestRequest("api", Method.DELETE), (r, h) => { }); + Assert.NotNull(handle); + Assert.Equal("DELETE", fakeHttp.LastMethod); + } + + [Fact] + public void ExecuteAsync_HEAD_Returns_Handle() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var handle = client.ExecuteAsync(new RestRequest("api", Method.HEAD), (r, h) => { }); + Assert.NotNull(handle); + Assert.Equal("HEAD", fakeHttp.LastMethod); + } + + [Fact] + public void ExecuteAsync_OPTIONS_Returns_Handle() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var handle = client.ExecuteAsync(new RestRequest("api", Method.OPTIONS), (r, h) => { }); + Assert.NotNull(handle); + Assert.Equal("OPTIONS", fakeHttp.LastMethod); + } + + [Fact] + public void ExecuteAsync_PATCH_Returns_Handle() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var handle = client.ExecuteAsync(new RestRequest("api", Method.PATCH), (r, h) => { }); + Assert.NotNull(handle); + Assert.Equal("PATCH", fakeHttp.LastMethod); + } + + [Fact] + public void ExecuteAsync_Generic_Returns_Deserialized_Data() + { + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactory(new HttpResponse + { + RawBytes = Encoding.UTF8.GetBytes("{\"Name\":\"test\",\"Value\":42}"), + ContentType = "application/json", + StatusCode = HttpStatusCode.OK, + ResponseStatus = ResponseStatus.Completed + }); + + RestResponse receivedResponse = null; + var handle = client.ExecuteAsync(new RestRequest("api", Method.GET), + (response, asyncHandle) => { receivedResponse = response; }); + + Assert.NotNull(receivedResponse); + Assert.NotNull(receivedResponse.Data); + Assert.Equal("test", receivedResponse.Data.Name); + } + + [Fact] + public void ExecuteAsync_Adds_Accept_Header_From_Handlers() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var handle = client.ExecuteAsync(new RestRequest("api", Method.GET), (r, h) => { }); + + Assert.Contains(fakeHttp.Headers, h => h.Name == "Accept"); + } + } + + public class SimpleAsyncData + { + public string Name { get; set; } + public int Value { get; set; } + } + + public class RestClientExtensionTests + { + [Fact] + public void ExecuteAsync_Extension_WithSimpleCallback() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + RestResponse receivedResponse = null; + var handle = RestClientExtensions.ExecuteAsync(client, new RestRequest("api", Method.GET), + response => { receivedResponse = response; }); + + Assert.NotNull(handle); + Assert.NotNull(receivedResponse); + } + + [Fact] + public void ExecuteAsync_Generic_Extension_WithSimpleCallback() + { + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactory(new HttpResponse + { + RawBytes = Encoding.UTF8.GetBytes("{\"Name\":\"ext\",\"Value\":99}"), + ContentType = "application/json", + StatusCode = HttpStatusCode.OK, + ResponseStatus = ResponseStatus.Completed + }); + + RestResponse receivedResponse = null; + var handle = RestClientExtensions.ExecuteAsync(client, new RestRequest("api", Method.GET), + response => { receivedResponse = response; }); + + Assert.NotNull(receivedResponse); + Assert.NotNull(receivedResponse.Data); + Assert.Equal("ext", receivedResponse.Data.Name); + } + } + + public class MiscExtensionAdditionalTests + { + [Fact] + public void CopyTo_Copies_Stream_Contents() + { + var source = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); + var dest = new MemoryStream(); + + source.CopyTo(dest); + + Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, dest.ToArray()); + } + + [Fact] + public void CopyTo_Empty_Stream_Does_Nothing() + { + var source = new MemoryStream(); + var dest = new MemoryStream(); + + source.CopyTo(dest); + + Assert.Empty(dest.ToArray()); + } + + [Fact] + public void JToken_AsString_Returns_String_Value() + { + var token = JToken.FromObject("hello"); + var result = token.AsString(); + Assert.Equal("hello", result); + } + + [Fact] + public void JToken_AsString_Returns_ToString_For_NonString() + { + var token = JToken.FromObject(42); + var result = token.AsString(); + Assert.Equal("42", result); + } + + [Fact] + public void JToken_AsString_With_Culture_Returns_String() + { + var token = JToken.FromObject("world"); + var result = token.AsString(System.Globalization.CultureInfo.InvariantCulture); + Assert.Equal("world", result); + } + + [Fact] + public void JToken_AsString_With_Culture_NonString_Returns_Converted() + { + var token = JToken.FromObject(3.14); + var result = token.AsString(System.Globalization.CultureInfo.InvariantCulture); + Assert.Contains("3.14", result); + } + + [Fact] + public void AsString_Bytes_Returns_Empty_For_Null() + { + byte[] buffer = null; + var result = buffer.AsString(); + Assert.Equal("", result); + } + + [Fact] + public void ReadAsBytes_Large_Stream() + { + var data = new byte[100000]; + for (int i = 0; i < data.Length; i++) data[i] = (byte)(i % 256); + using (var stream = new MemoryStream(data)) + { + var result = stream.ReadAsBytes(); + Assert.Equal(data.Length, result.Length); + Assert.Equal(data[0], result[0]); + Assert.Equal(data[99999], result[99999]); + } + } + } + + public class RestRequestAsyncHandleAdditionalTests + { + [Fact] + public void WebRequest_Can_Be_Set() + { + var handle = new RestRequestAsyncHandle(); + // Can't easily create a real HttpWebRequest, just verify default is null + Assert.Null(handle.WebRequest); + } + } + + public class HttpInternalMethodTests + { + [Fact] + public void EncodeParameters_Returns_Encoded_String() + { + var http = new TestableHttpForEncoding(); + http.Parameters.Add(new HttpParameter { Name = "key1", Value = "value1" }); + http.Parameters.Add(new HttpParameter { Name = "key2", Value = "value 2" }); + + var result = http.TestEncodeParameters(); + Assert.Contains("key1", result); + Assert.Contains("value1", result); + Assert.Contains("key2", result); + } + + [Fact] + public void GetMultipartFormContentType_Returns_Correct_Format() + { + var result = TestableHttpForEncoding.TestGetMultipartFormContentType(); + Assert.Contains("multipart/form-data", result); + Assert.Contains("boundary", result); + } + + [Fact] + public void GetMultipartFileHeader_Returns_Correct_Format() + { + var file = new HttpFile { Name = "upload", FileName = "test.txt", ContentType = "text/plain" }; + var result = TestableHttpForEncoding.TestGetMultipartFileHeader(file); + Assert.Contains("upload", result); + Assert.Contains("test.txt", result); + Assert.Contains("text/plain", result); + } + + [Fact] + public void GetMultipartFormData_Returns_Correct_Format() + { + var param = new HttpParameter { Name = "key", Value = "value" }; + var result = TestableHttpForEncoding.TestGetMultipartFormData(param); + Assert.Contains("key", result); + Assert.Contains("value", result); + } + + [Fact] + public void GetMultipartFooter_Returns_Footer() + { + var result = TestableHttpForEncoding.TestGetMultipartFooter(); + Assert.Contains("--", result); + } + + [Fact] + public void WriteMultipartFormData_Writes_Parameters_And_Files() + { + var http = new TestableHttpForEncoding(); + http.Parameters.Add(new HttpParameter { Name = "key", Value = "value" }); + http.Files.Add(new HttpFile { Name = "file", FileName = "test.txt", ContentType = "text/plain", + Writer = s => { var bytes = Encoding.UTF8.GetBytes("file content"); s.Write(bytes, 0, bytes.Length); } }); + + using (var stream = new MemoryStream()) + { + http.TestWriteMultipartFormData(stream); + var content = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Contains("key", content); + Assert.Contains("value", content); + Assert.Contains("file content", content); + } + } + + [Fact] + public void PreparePostBody_Sets_ContentType_With_Parameters() + { + var http = new TestableHttpForEncoding(); + http.Parameters.Add(new HttpParameter { Name = "key", Value = "val" }); + + var webRequest = WebRequest.CreateHttp("http://example.com/test"); + webRequest.Method = "POST"; + http.TestPreparePostBody(webRequest); + + Assert.Equal("application/x-www-form-urlencoded", webRequest.ContentType); + Assert.Contains("key", http.RequestBody); + } + + [Fact] + public void PreparePostBody_Sets_ContentType_With_Body() + { + var http = new TestableHttpForEncoding(); + http.RequestBody = "{\"test\":1}"; + http.RequestContentType = "application/json"; + + var webRequest = WebRequest.CreateHttp("http://example.com/test"); + webRequest.Method = "POST"; + http.TestPreparePostBody(webRequest); + + Assert.Equal("application/json", webRequest.ContentType); + } + } + + public class TestableHttpForEncoding : Http + { + public string TestEncodeParameters() + { + var sb = new StringBuilder(); + foreach (var p in Parameters) + { + if (sb.Length > 1) sb.Append("&"); + sb.AppendFormat("{0}={1}", p.Name.UrlEncode(), p.Value.UrlEncode()); + } + return sb.ToString(); + } + + public static string TestGetMultipartFormContentType() + { + return string.Format("multipart/form-data; boundary={0}", "-----------------------------28947758029299"); + } + + public static string TestGetMultipartFileHeader(HttpFile file) + { + return string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n", + "-----------------------------28947758029299", file.Name, file.FileName, file.ContentType ?? "application/octet-stream"); + } + + public static string TestGetMultipartFormData(HttpParameter param) + { + return string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n", + "-----------------------------28947758029299", param.Name, param.Value); + } + + public static string TestGetMultipartFooter() + { + return string.Format("--{0}--\r\n", "-----------------------------28947758029299"); + } + + public void TestWriteMultipartFormData(Stream requestStream) + { + var encoding = Encoding.UTF8; + foreach (var param in Parameters) + { + var data = TestGetMultipartFormData(param); + var bytes = encoding.GetBytes(data); + requestStream.Write(bytes, 0, bytes.Length); + } + + foreach (var file in Files) + { + var header = TestGetMultipartFileHeader(file); + var bytes = encoding.GetBytes(header); + requestStream.Write(bytes, 0, bytes.Length); + file.Writer(requestStream); + var lineBreak = encoding.GetBytes("\r\n"); + requestStream.Write(lineBreak, 0, lineBreak.Length); + } + + var footer = TestGetMultipartFooter(); + var footerBytes = encoding.GetBytes(footer); + requestStream.Write(footerBytes, 0, footerBytes.Length); + } + + public void TestPreparePostBody(HttpWebRequest webRequest) + { + if (HasFiles) + { + webRequest.ContentType = TestGetMultipartFormContentType(); + } + else if (HasParameters) + { + webRequest.ContentType = "application/x-www-form-urlencoded"; + RequestBody = TestEncodeParameters(); + } + else if (HasBody) + { + webRequest.ContentType = RequestContentType; + } + } + } + + public class XmlAttributeDeserializerCoverageTests + { + [Fact] + public void Can_Deserialize_Simple_Xml() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "test42" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("test", result.Name); + Assert.Equal(42, result.Value); + } + + [Fact] + public void Can_Deserialize_With_RootElement() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + deserializer.RootElement = "Item"; + var response = new RestResponse + { + Content = "nested10" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("nested", result.Name); + Assert.Equal(10, result.Value); + } + + [Fact] + public void Can_Deserialize_With_Namespace() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + deserializer.Namespace = "http://example.com/ns"; + var response = new RestResponse + { + Content = "ns_test7" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("ns_test", result.Name); + } + + [Fact] + public void Can_Deserialize_Boolean_Values() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "truefalse" + }; + var result = deserializer.Deserialize(response); + Assert.True(result.Active); + Assert.False(result.Disabled); + } + + [Fact] + public void Can_Deserialize_DateTime() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "2023-06-15T00:00:00" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(new DateTime(2023, 6, 15), result.Created); + } + + [Fact] + public void Can_Deserialize_Guid() + { + var guid = Guid.NewGuid(); + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = string.Format("{0}", guid) + }; + var result = deserializer.Deserialize(response); + Assert.Equal(guid, result.Id); + } + + [Fact] + public void Can_Deserialize_Decimal() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "19.95" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(19.95m, result.Price); + } + + [Fact] + public void Can_Deserialize_Long() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "9223372036854775807" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(long.MaxValue, result.BigNum); + } + + [Fact] + public void Can_Deserialize_Enum_Value() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "POST" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(Method.POST, result.Status); + } + + [Fact] + public void Can_Deserialize_Nullable_Int() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "42" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(42, result.Value); + } + + [Fact] + public void Can_Deserialize_Nullable_With_Missing_Value() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "" + }; + var result = deserializer.Deserialize(response); + Assert.Null(result.Value); + } + + [Fact] + public void Can_Deserialize_List() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "a1b2" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(2, result.Items.Count); + Assert.Equal("a", result.Items[0].Name); + Assert.Equal("b", result.Items[1].Name); + } + + [Fact] + public void Can_Deserialize_Double() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "3.14159" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(3.14159, result.Rate, 4); + } + + [Fact] + public void Can_Deserialize_With_Underscore_Names() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "underscored5" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("underscored", result.TheName); + } + + [Fact] + public void Can_Deserialize_With_Dashed_Names() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "dashed" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("dashed", result.TheName); + } + + [Fact] + public void Can_Deserialize_Xml_Attribute() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "attr_test" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("attr_test", result.Name); + } + + [Fact] + public void Can_Deserialize_Float() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "1.5" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(1.5f, result.Weight, 1); + } + + [Fact] + public void Can_Deserialize_Empty_Content_Returns_Default() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + var response = new RestResponse { Content = "" }; + var result = deserializer.Deserialize(response); + Assert.Null(result.Name); + } + + [Fact] + public void DateFormat_Can_Be_Set() + { + var deserializer = new RestSharp.Deserializers.XmlAttributeDeserializer(); + deserializer.DateFormat = "yyyy-MM-dd"; + Assert.Equal("yyyy-MM-dd", deserializer.DateFormat); + } + } + + public class XmlItem + { + public string Name { get; set; } + public int Value { get; set; } + } + + public class XmlBoolItem + { + public bool Active { get; set; } + public bool Disabled { get; set; } + } + + public class XmlDateItem + { + public DateTime Created { get; set; } + } + + public class XmlGuidItem + { + public Guid Id { get; set; } + } + + public class XmlDecimalItem + { + public decimal Price { get; set; } + } + + public class XmlLongItem + { + public long BigNum { get; set; } + } + + public class XmlEnumItem + { + public Method Status { get; set; } + } + + public class XmlNullableItem + { + public int? Value { get; set; } + } + + public class XmlListItem + { + public List Items { get; set; } + } + + public class XmlDoubleItem + { + public double Rate { get; set; } + } + + public class XmlUnderscoreItem + { + public string TheName { get; set; } + public int TheValue { get; set; } + } + + public class XmlDashedItem + { + public string TheName { get; set; } + } + + public class XmlAttrItem + { + public string Name { get; set; } + } + + public class XmlBytesItem + { + public byte[] Data { get; set; } + } + + public class XmlTimeSpanItem + { + public TimeSpan Duration { get; set; } + } + + public class XmlFloatItem + { + public float Weight { get; set; } + } +} diff --git a/RestSharp.Tests.Coverage/AuthenticatorTests.cs b/RestSharp.Tests.Coverage/AuthenticatorTests.cs new file mode 100644 index 000000000..082c16f69 --- /dev/null +++ b/RestSharp.Tests.Coverage/AuthenticatorTests.cs @@ -0,0 +1,185 @@ +using System; +using System.Linq; +using System.Text; +using RestSharp; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class HttpBasicAuthenticatorTests + { + [Fact] + public void Authenticate_Adds_Authorization_Header() + { + var auth = new HttpBasicAuthenticator("user", "pass"); + var client = new RestClient("http://example.com"); + var request = new RestRequest(); + + auth.Authenticate(client, request); + + var header = request.Parameters.FirstOrDefault(p => + p.Name.Equals("Authorization", StringComparison.OrdinalIgnoreCase)); + Assert.NotNull(header); + Assert.Equal(ParameterType.HttpHeader, header.Type); + + var expected = Convert.ToBase64String(Encoding.UTF8.GetBytes("user:pass")); + Assert.Equal(string.Format("Basic {0}", expected), header.Value); + } + + [Fact] + public void Authenticate_Does_Not_Duplicate_Authorization_Header() + { + var auth = new HttpBasicAuthenticator("user", "pass"); + var client = new RestClient("http://example.com"); + var request = new RestRequest(); + + request.AddHeader("Authorization", "Existing"); + auth.Authenticate(client, request); + + var headers = request.Parameters.Where(p => + p.Name.Equals("Authorization", StringComparison.OrdinalIgnoreCase)).ToList(); + Assert.Single(headers); + Assert.Equal("Existing", headers[0].Value); + } + + [Fact] + public void Authenticate_With_Empty_Username_And_Password() + { + var auth = new HttpBasicAuthenticator("", ""); + var client = new RestClient("http://example.com"); + var request = new RestRequest(); + + auth.Authenticate(client, request); + + var header = request.Parameters.FirstOrDefault(p => + p.Name.Equals("Authorization", StringComparison.OrdinalIgnoreCase)); + Assert.NotNull(header); + var expected = Convert.ToBase64String(Encoding.UTF8.GetBytes(":")); + Assert.Equal(string.Format("Basic {0}", expected), header.Value); + } + + [Fact] + public void Authenticate_With_Special_Characters() + { + var auth = new HttpBasicAuthenticator("user@domain.com", "p@ss:word!"); + var client = new RestClient("http://example.com"); + var request = new RestRequest(); + + auth.Authenticate(client, request); + + var header = request.Parameters.FirstOrDefault(p => + p.Name.Equals("Authorization", StringComparison.OrdinalIgnoreCase)); + Assert.NotNull(header); + var expected = Convert.ToBase64String(Encoding.UTF8.GetBytes("user@domain.com:p@ss:word!")); + Assert.Equal(string.Format("Basic {0}", expected), header.Value); + } + } + + public class SimpleAuthenticatorTests + { + [Fact] + public void Authenticate_Adds_Username_And_Password_Parameters() + { + var auth = new SimpleAuthenticator("username", "admin", "password", "secret"); + var client = new RestClient("http://example.com"); + var request = new RestRequest(); + + auth.Authenticate(client, request); + + var userParam = request.Parameters.FirstOrDefault(p => p.Name == "username"); + var passParam = request.Parameters.FirstOrDefault(p => p.Name == "password"); + + Assert.NotNull(userParam); + Assert.Equal("admin", userParam.Value); + Assert.NotNull(passParam); + Assert.Equal("secret", passParam.Value); + } + + [Fact] + public void Authenticate_Uses_GetOrPost_ParameterType() + { + var auth = new SimpleAuthenticator("user", "admin", "pass", "secret"); + var client = new RestClient("http://example.com"); + var request = new RestRequest(); + + auth.Authenticate(client, request); + + foreach (var param in request.Parameters) + { + Assert.Equal(ParameterType.GetOrPost, param.Type); + } + } + } + + public class OAuth2AuthenticatorTests + { + [Fact] + public void UriQueryParameter_Adds_Token_As_GetOrPost_Parameter() + { + var auth = new OAuth2UriQueryParameterAuthenticator("my_token_123"); + var client = new RestClient("http://example.com"); + var request = new RestRequest(); + + auth.Authenticate(client, request); + + var param = request.Parameters.FirstOrDefault(p => p.Name == "oauth_token"); + Assert.NotNull(param); + Assert.Equal("my_token_123", param.Value); + Assert.Equal(ParameterType.GetOrPost, param.Type); + } + + [Fact] + public void AuthorizationRequestHeader_Adds_Bearer_Header() + { + var auth = new OAuth2AuthorizationRequestHeaderAuthenticator("my_token_123"); + var client = new RestClient("http://example.com"); + var request = new RestRequest(); + + auth.Authenticate(client, request); + + var header = request.Parameters.FirstOrDefault(p => + p.Name.Equals("Authorization", StringComparison.OrdinalIgnoreCase)); + Assert.NotNull(header); + Assert.Equal("OAuth my_token_123", header.Value); + Assert.Equal(ParameterType.HttpHeader, header.Type); + } + + [Fact] + public void AuthorizationRequestHeader_Does_Not_Duplicate_Header() + { + var auth = new OAuth2AuthorizationRequestHeaderAuthenticator("my_token_123"); + var client = new RestClient("http://example.com"); + var request = new RestRequest(); + + request.AddHeader("Authorization", "Existing"); + auth.Authenticate(client, request); + + var headers = request.Parameters.Where(p => + p.Name.Equals("Authorization", StringComparison.OrdinalIgnoreCase)).ToList(); + Assert.Single(headers); + Assert.Equal("Existing", headers[0].Value); + } + + [Fact] + public void UriQueryParameter_AccessToken_Property() + { + var auth = new OAuth2UriQueryParameterAuthenticator("token_abc"); + Assert.Equal("token_abc", auth.AccessToken); + } + } + + public class NtlmAuthenticatorTests + { + [Fact] + public void Authenticate_Sets_Default_Credentials() + { + var auth = new NtlmAuthenticator(); + var client = new RestClient("http://example.com"); + var request = new RestRequest(); + + auth.Authenticate(client, request); + + Assert.Equal(System.Net.CredentialCache.DefaultCredentials, request.Credentials); + } + } +} diff --git a/RestSharp.Tests.Coverage/ExtensionTests.cs b/RestSharp.Tests.Coverage/ExtensionTests.cs new file mode 100644 index 000000000..9054e5b8b --- /dev/null +++ b/RestSharp.Tests.Coverage/ExtensionTests.cs @@ -0,0 +1,370 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using RestSharp.Extensions; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class StringExtensionTests + { + [Fact] + public void HasValue_Returns_True_For_NonEmpty_String() + { + Assert.True("hello".HasValue()); + } + + [Fact] + public void HasValue_Returns_False_For_Null() + { + string s = null; + Assert.False(s.HasValue()); + } + + [Fact] + public void HasValue_Returns_False_For_Empty() + { + Assert.False("".HasValue()); + } + + [Fact] + public void RemoveUnderscoresAndDashes_Removes_Underscores() + { + Assert.Equal("helloworld", "hello_world".RemoveUnderscoresAndDashes()); + } + + [Fact] + public void RemoveUnderscoresAndDashes_Removes_Dashes() + { + Assert.Equal("helloworld", "hello-world".RemoveUnderscoresAndDashes()); + } + + [Fact] + public void ToPascalCase_Converts_Underscore_Separated() + { + var result = "some_text_here".ToPascalCase(CultureInfo.InvariantCulture); + Assert.Equal("SomeTextHere", result); + } + + [Fact] + public void ToPascalCase_Returns_Null_For_Null() + { + string s = null; + var result = s.ToPascalCase(CultureInfo.InvariantCulture); + Assert.Null(result); + } + + [Fact] + public void ToPascalCase_Returns_Empty_For_Empty() + { + var result = "".ToPascalCase(CultureInfo.InvariantCulture); + Assert.Equal("", result); + } + + [Fact] + public void ToPascalCase_With_Single_Lowercase_Word() + { + var result = "hello".ToPascalCase(CultureInfo.InvariantCulture); + Assert.Equal("Hello", result); + } + + [Fact] + public void ToPascalCase_With_All_Uppercase_Word() + { + var result = "HELLO".ToPascalCase(CultureInfo.InvariantCulture); + Assert.Equal("Hello", result); + } + + [Fact] + public void ToPascalCase_Without_Removing_Underscores() + { + var result = "some_text".ToPascalCase(false, CultureInfo.InvariantCulture); + Assert.Contains("_", result); + } + + [Fact] + public void ToCamelCase_Converts_Underscore_Separated() + { + var result = "some_text_here".ToCamelCase(CultureInfo.InvariantCulture); + Assert.Equal("someTextHere", result); + } + + [Fact] + public void MakeInitialLowerCase_Converts_First_Char() + { + Assert.Equal("hello", "Hello".MakeInitialLowerCase()); + } + + [Fact] + public void IsUpperCase_Returns_True_For_All_Upper() + { + Assert.True("HELLO".IsUpperCase()); + } + + [Fact] + public void IsUpperCase_Returns_False_For_Mixed_Case() + { + Assert.False("Hello".IsUpperCase()); + } + + [Fact] + public void IsUpperCase_Returns_False_For_Lower() + { + Assert.False("hello".IsUpperCase()); + } + + [Fact] + public void AddUnderscores_Converts_PascalCase() + { + Assert.Equal("Some_Text_Here", "SomeTextHere".AddUnderscores()); + } + + [Fact] + public void AddDashes_Converts_PascalCase() + { + Assert.Equal("Some-Text-Here", "SomeTextHere".AddDashes()); + } + + [Fact] + public void Matches_Returns_True_For_Matching_Pattern() + { + Assert.True("hello123".Matches(@"\d+")); + } + + [Fact] + public void Matches_Returns_False_For_Non_Matching() + { + Assert.False("hello".Matches(@"^\d+$")); + } + + [Fact] + public void GetNameVariants_Returns_Multiple_Variants() + { + var variants = "SomeName".GetNameVariants(CultureInfo.InvariantCulture).ToList(); + Assert.True(variants.Count >= 7); + Assert.Contains("SomeName", variants); + Assert.Contains("someName", variants); + Assert.Contains("somename", variants); + } + + [Fact] + public void GetNameVariants_Returns_Empty_For_Null() + { + string s = null; + var variants = s.GetNameVariants(CultureInfo.InvariantCulture).ToList(); + Assert.Empty(variants); + } + + [Fact] + public void GetNameVariants_Returns_Empty_For_Empty_String() + { + var variants = "".GetNameVariants(CultureInfo.InvariantCulture).ToList(); + Assert.Empty(variants); + } + + [Fact] + public void UrlEncode_Encodes_Special_Characters() + { + var result = "hello world&foo=bar".UrlEncode(); + Assert.DoesNotContain(" ", result); + Assert.Contains("%26", result); + } + + [Fact] + public void UrlDecode_Decodes_Encoded_String() + { + var result = "hello%20world".UrlDecode(); + Assert.Equal("hello world", result); + } + + [Fact] + public void HtmlEncode_Encodes_Special_Characters() + { + var result = "
test
".HtmlEncode(); + Assert.DoesNotContain("<", result); + } + + [Fact] + public void HtmlDecode_Decodes_Encoded_String() + { + var result = "<div>test</div>".HtmlDecode(); + Assert.Equal("
test
", result); + } + + [Fact] + public void RemoveSurroundingQuotes_Removes_Double_Quotes() + { + Assert.Equal("hello", "\"hello\"".RemoveSurroundingQuotes()); + } + + [Fact] + public void RemoveSurroundingQuotes_Leaves_Unquoted() + { + Assert.Equal("hello", "hello".RemoveSurroundingQuotes()); + } + + [Fact] + public void ParseJsonDate_Parses_Iso8601_Date() + { + var result = "2023-01-15T10:30:00Z".ParseJsonDate(CultureInfo.InvariantCulture); + Assert.NotEqual(default(DateTime), result); + Assert.Equal(2023, result.Year); + Assert.Equal(1, result.Month); + Assert.Equal(15, result.Day); + } + + [Fact] + public void ParseJsonDate_Parses_Millisecond_Timestamp() + { + // The escaped JSON format: \/Date(...)\/ + var result = "\\/Date(1234567890000)\\/".ParseJsonDate(CultureInfo.InvariantCulture); + Assert.NotEqual(default(DateTime), result); + } + + [Fact] + public void ParseJsonDate_Parses_Millisecond_Timestamp_With_Offset() + { + var result = "\\/Date(1234567890000+0530)\\/".ParseJsonDate(CultureInfo.InvariantCulture); + Assert.NotEqual(default(DateTime), result); + } + + [Fact] + public void ParseJsonDate_Parses_Millisecond_Timestamp_With_Negative_Offset() + { + var result = "\\/Date(1234567890000-0500)\\/".ParseJsonDate(CultureInfo.InvariantCulture); + Assert.NotEqual(default(DateTime), result); + } + + [Fact] + public void ParseJsonDate_Handles_New_Date_Format() + { + var result = "new Date(1234567890000)".ParseJsonDate(CultureInfo.InvariantCulture); + Assert.NotEqual(default(DateTime), result); + } + + [Fact] + public void AddUnderscores_Handles_Consecutive_Uppercase() + { + // e.g. XMLParser -> XML_Parser + var result = "XMLParser".AddUnderscores(); + Assert.Contains("_", result); + } + + [Fact] + public void AddDashes_Handles_Spaces() + { + var result = "Hello World".AddDashes(); + Assert.Contains("-", result); + } + } + + public class MiscExtensionTests + { + [Fact] + public void ReadAsBytes_Reads_Stream_To_Bytes() + { + var data = new byte[] { 1, 2, 3, 4, 5 }; + using (var stream = new MemoryStream(data)) + { + var result = stream.ReadAsBytes(); + Assert.Equal(data, result); + } + } + + [Fact] + public void SaveAs_Writes_Bytes_To_File() + { + var data = new byte[] { 10, 20, 30 }; + var tempFile = Path.GetTempFileName(); + try + { + data.SaveAs(tempFile); + var readBack = File.ReadAllBytes(tempFile); + Assert.Equal(data, readBack); + } + finally + { + File.Delete(tempFile); + } + } + + [Fact] + public void AsString_Converts_Bytes_To_String() + { + var bytes = System.Text.Encoding.UTF8.GetBytes("hello"); + var result = bytes.AsString(); + Assert.Equal("hello", result); + } + } + + public class XmlExtensionTests + { + [Fact] + public void AsNamespaced_Without_Namespace_Returns_Name() + { + var result = "element".AsNamespaced(""); + Assert.Equal("element", result.LocalName); + } + + [Fact] + public void AsNamespaced_With_Namespace_Returns_Namespaced_Name() + { + var result = "element".AsNamespaced("http://example.com/ns"); + Assert.Equal("element", result.LocalName); + Assert.Equal("http://example.com/ns", result.NamespaceName); + } + } + + public class ReflectionExtensionTests + { + [Fact] + public void IsSubclassOfRawGeneric_Returns_True_For_Derived_Generic() + { + Assert.True(typeof(System.Collections.Generic.List) + .IsSubclassOfRawGeneric(typeof(System.Collections.Generic.List<>))); + } + + [Fact] + public void IsSubclassOfRawGeneric_Returns_False_For_Non_Derived() + { + Assert.False(typeof(string).IsSubclassOfRawGeneric(typeof(System.Collections.Generic.List<>))); + } + + [Fact] + public void ChangeType_Converts_String_To_Int() + { + var result = "42".ChangeType(typeof(int)); + Assert.Equal(42, result); + } + + [Fact] + public void ChangeType_With_Culture_Converts_String_To_Double() + { + var result = "3.14".ChangeType(typeof(double), CultureInfo.InvariantCulture); + Assert.Equal(3.14, result); + } + + [Fact] + public void FindEnumValue_Finds_By_Name() + { + var result = typeof(Method).FindEnumValue("GET", CultureInfo.InvariantCulture); + Assert.Equal(Method.GET, result); + } + + [Fact] + public void FindEnumValue_Finds_CaseInsensitive() + { + var result = typeof(Method).FindEnumValue("get", CultureInfo.InvariantCulture); + Assert.Equal(Method.GET, result); + } + + [Fact] + public void GetAttribute_Returns_Null_When_Not_Present() + { + var attr = typeof(string).GetAttribute(); + Assert.Null(attr); + } + } +} diff --git a/RestSharp.Tests.Coverage/Final20Tests.cs b/RestSharp.Tests.Coverage/Final20Tests.cs new file mode 100644 index 000000000..23dab77ba --- /dev/null +++ b/RestSharp.Tests.Coverage/Final20Tests.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using RestSharp; +using RestSharp.Contrib; +using RestSharp.Deserializers; +using RestSharp.Extensions; +using RestSharp.Serializers; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class RestRequestAddFilePathTests + { + [Fact] + public void AddFile_With_Path_Creates_FileParameter() + { + var request = new RestRequest(); + var tmpFile = Path.GetTempFileName(); + try + { + File.WriteAllBytes(tmpFile, new byte[] { 1, 2, 3 }); + request.AddFile("upload", tmpFile); + Assert.Single(request.Files); + Assert.Equal("upload", request.Files[0].Name); + // Verify the writer can produce the data + using (var ms = new MemoryStream()) + { + request.Files[0].Writer(ms); + Assert.Equal(3, ms.Length); + } + } + finally + { + File.Delete(tmpFile); + } + } + + [Fact] + public void AddFile_With_Action_No_ContentType() + { + var request = new RestRequest(); + Action writer = s => s.Write(new byte[] { 10, 20 }, 0, 2); + request.AddFile("f", writer, "data.bin"); + Assert.Single(request.Files); + Assert.Equal("data.bin", request.Files[0].FileName); + } + } + + public class XmlSerializerConstructorTests + { + [Fact] + public void Constructor_With_Namespace_Sets_Property() + { + var serializer = new XmlSerializer("http://myns.com"); + Assert.Equal("http://myns.com", serializer.Namespace); + } + + [Fact] + public void Serialize_With_Enum_Property() + { + var serializer = new XmlSerializer(); + var obj = new ObjWithEnumFinal { Status = Method.POST }; + var result = serializer.Serialize(obj); + Assert.Contains("POST", result); + } + } + + public class JsonSerializerConstructorTests + { + [Fact] + public void Constructor_With_Custom_JsonSerializer() + { + var jsonSerializer = new Newtonsoft.Json.JsonSerializer(); + jsonSerializer.NullValueHandling = NullValueHandling.Ignore; + var serializer = new RestSharp.Serializers.JsonSerializer(jsonSerializer); + Assert.NotNull(serializer.ContentType); + } + } + + public class RestRequestAsyncHandleConstructorTests + { + [Fact] + public void Constructor_With_WebRequest_Sets_Property() + { + var webReq = (HttpWebRequest)WebRequest.Create("http://example.com"); + var handle = new RestRequestAsyncHandle(webReq); + Assert.Same(webReq, handle.WebRequest); + } + } + + public class RestClientHandlerAndBuildUriTests + { + [Fact] + public void GetHandler_Matches_ContentType_With_Charset() + { + var client = new RestClient("http://example.com"); + // Should handle content-type with charset parameter + client.HttpFactory = new FakeHttpFactory(new HttpResponse + { + RawBytes = Encoding.UTF8.GetBytes("{\"Name\":\"test\"}"), + ContentType = "application/json; charset=utf-8", + StatusCode = HttpStatusCode.OK, + ResponseStatus = ResponseStatus.Completed + }); + var response = client.Execute(new RestRequest("api", Method.GET)); + Assert.NotNull(response); + Assert.Equal("test", response.Data.Name); + } + + [Fact] + public void BuildUri_With_BaseUrl_Query_String() + { + var client = new RestClient("http://example.com?key=value"); + var request = new RestRequest("api", Method.GET); + var uri = client.BuildUri(request); + Assert.Contains("key=value", uri.ToString()); + } + + [Fact] + public void ConfigureHttp_With_DefaultParameters() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + client.AddDefaultHeader("X-Custom", "custom-value"); + + var request = new RestRequest("api", Method.GET); + client.Execute(request); + + Assert.Contains(fakeHttp.Headers, h => h.Name == "X-Custom"); + } + } + + public class RestClientAsyncAdditionalTests + { + [Fact] + public void ExecuteAsync_Returns_Handle() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + RestResponse receivedResponse = null; + var handle = client.ExecuteAsync(request, (resp, hdl) => { receivedResponse = (RestResponse)resp; }); + Assert.NotNull(handle); + } + } + + public class StringExtensionHtmlAttrEncodeTests + { + [Fact] + public void HtmlAttributeEncode_Extension_Encodes_Quotes() + { + var result = "test\"value".HtmlAttributeEncode(); + Assert.Contains(""", result); + } + + [Fact] + public void ParseJsonDate_FormattedDate_Fallback() + { + // A date in standard DateTime format triggers ParseFormattedDate path + var result = "2023-06-15T12:30:00".ParseJsonDate(CultureInfo.InvariantCulture); + Assert.Equal(2023, result.Year); + Assert.Equal(6, result.Month); + } + } + + public class HttpUtilityFinalSmallTests + { + [Fact] + public void UrlDecode_With_Encoding_Percent_UFormat() + { + // %u format in UrlDecode with explicit encoding + var result = HttpUtility.UrlDecode("%u0041", Encoding.UTF8); + Assert.NotNull(result); + } + + [Fact] + public void HtmlAttributeEncode_TextWriter_Empty() + { + var writer = new StringWriter(); + HttpUtility.HtmlAttributeEncode("", writer); + Assert.Equal("", writer.ToString()); + } + + [Fact] + public void ParseQueryString_NoQuestion_EmptyString_Key() + { + var result = HttpUtility.ParseQueryString("key_only"); + Assert.NotNull(result); + } + + [Fact] + public void UrlEncodeToBytes_Returns_Correct_Bytes() + { + var result = HttpUtility.UrlEncodeToBytes("a b"); + Assert.NotNull(result); + Assert.True(result.Length > 0); + } + } + + public class JsonDeserializerCreateAndMapTests + { + [Fact] + public void Deserialize_Nested_Object_Triggers_CreateAndMap() + { + var deserializer = new JsonDeserializer(); + var response = new RestResponse + { + Content = "{\"Child\":{\"Name\":\"nested\",\"Value\":5}}" + }; + var result = deserializer.Deserialize(response); + Assert.NotNull(result.Child); + Assert.Equal("nested", result.Child.Name); + Assert.Equal(5, result.Child.Value); + } + + [Fact] + public void Deserialize_List_Of_Objects() + { + var deserializer = new JsonDeserializer(); + var response = new RestResponse + { + Content = "[{\"Name\":\"a\",\"Value\":1},{\"Name\":\"b\",\"Value\":2}]" + }; + var result = deserializer.Deserialize>(response); + Assert.Equal(2, result.Count); + Assert.Equal("a", result[0].Name); + } + } + + // Model classes + public class ObjWithEnumFinal { public Method Status { get; set; } } + public class JsonNestedObj { public JsonChildObj Child { get; set; } } + public class JsonChildObj { public string Name { get; set; } public int Value { get; set; } } +} diff --git a/RestSharp.Tests.Coverage/FinalCoverageTests.cs b/RestSharp.Tests.Coverage/FinalCoverageTests.cs new file mode 100644 index 000000000..a91cccfa5 --- /dev/null +++ b/RestSharp.Tests.Coverage/FinalCoverageTests.cs @@ -0,0 +1,577 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using RestSharp; +using RestSharp.Contrib; +using RestSharp.Deserializers; +using RestSharp.Extensions; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + /// + /// Final round of tests to push coverage above 80%. + /// Targets: HtmlEncoder, HttpUtility, XmlAttributeDeserializer, Http.cs internals, + /// RestClient.cs, RestRequest.cs remaining uncovered lines. + /// + + public class HtmlEncoderFinalTests + { + [Fact] + public void HtmlDecode_Named_Entity_nbsp() + { + var result = HttpUtility.HtmlDecode(" "); + Assert.NotNull(result); + Assert.Single(result); + } + + [Fact] + public void HtmlDecode_Named_Entity_copy() + { + var result = HttpUtility.HtmlDecode("©"); + Assert.Equal("\u00A9", result); + } + + [Fact] + public void HtmlDecode_Named_Entity_reg() + { + var result = HttpUtility.HtmlDecode("®"); + Assert.Equal("\u00AE", result); + } + + [Fact] + public void HtmlDecode_Named_Entity_lt_gt_amp_quot() + { + var result = HttpUtility.HtmlDecode("<>&""); + Assert.Equal("<>&\"", result); + } + + [Fact] + public void HtmlDecode_NumericEntity_Large_Value() + { + // Large Unicode codepoint + var result = HttpUtility.HtmlDecode("𐀀"); + Assert.NotNull(result); + } + + [Fact] + public void HtmlDecode_Hex_Entity_Uppercase() + { + var result = HttpUtility.HtmlDecode("A"); + Assert.Equal("A", result); + } + + [Fact] + public void HtmlDecode_InvalidEntity_Preserved() + { + var result = HttpUtility.HtmlDecode("&invalidEntity;"); + Assert.Contains("&", result); + } + + [Fact] + public void HtmlDecode_Incomplete_NumericEntity() + { + var result = HttpUtility.HtmlDecode("&#abc;"); + Assert.NotNull(result); + } + + [Fact] + public void HtmlDecode_Entity_Without_Semicolon() + { + var result = HttpUtility.HtmlDecode("< next"); + Assert.NotNull(result); + } + + [Fact] + public void HtmlEncode_Char_Below_160_Passes_Through() + { + var result = HttpUtility.HtmlEncode("abc123"); + Assert.Equal("abc123", result); + } + + [Fact] + public void HtmlEncode_HighUnicode_Roundtrips() + { + var input = "\u2603"; // snowman + var encoded = HttpUtility.HtmlEncode(input); + var decoded = HttpUtility.HtmlDecode(encoded); + Assert.Equal(input, decoded); + } + + [Fact] + public void HtmlEncode_AllSpecialChars() + { + var result = HttpUtility.HtmlEncode("<>&\""); + Assert.Equal("<>&"", result); + } + + [Fact] + public void UrlPathEncode_NonAscii_Gets_Encoded() + { + var result = HttpUtility.UrlPathEncode("café"); + Assert.DoesNotContain("é", result); + } + + [Fact] + public void UrlPathEncode_With_Hash() + { + var result = HttpUtility.UrlPathEncode("page#section"); + Assert.Contains("#", result); + } + + [Fact] + public void UrlEncode_Unicode_Gets_Percent_Encoded() + { + var result = HttpUtility.UrlEncode("\u00e9"); + Assert.Contains("%", result); + } + + [Fact] + public void UrlEncode_Control_Characters() + { + var result = HttpUtility.UrlEncode("\t\n"); + Assert.Contains("%", result); + } + + [Fact] + public void UrlDecode_Unicode_Escape_u() + { + // %uXXXX format - behavior depends on implementation + var result = HttpUtility.UrlDecode("%u0041"); + Assert.NotNull(result); + } + + [Fact] + public void UrlDecode_Mixed_Percent_And_Plus() + { + var result = HttpUtility.UrlDecode("hello+%77orld"); + Assert.Equal("hello world", result); + } + + [Fact] + public void UrlEncode_Dot_And_Hyphen_Pass_Through() + { + var result = HttpUtility.UrlEncode("a.b-c_d"); + Assert.Equal("a.b-c_d", result); + } + + [Fact] + public void UrlEncode_Star_Stays() + { + var result = HttpUtility.UrlEncode("a*b"); + Assert.Equal("a*b", result); + } + } + + public class XmlAttributeDeserializerFinalTests + { + [Fact] + public void Can_Deserialize_With_Lowercase_Element_Names() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "lower10" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("lower", result.Name); + Assert.Equal(10, result.Value); + } + + [Fact] + public void Can_Deserialize_Attribute_From_Element() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "test" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("test", result.Name); + Assert.Equal("99", result.Id); + Assert.Equal("active", result.Status); + } + + [Fact] + public void Can_Deserialize_List_Property() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "" + }; + var result = deserializer.Deserialize(response); + Assert.NotNull(result.Items); + Assert.Equal(2, result.Items.Count); + } + + [Fact] + public void Can_Deserialize_Nested_Object() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "deep" + }; + var result = deserializer.Deserialize(response); + Assert.NotNull(result.Inner); + Assert.Equal("deep", result.Inner.Name); + } + + [Fact] + public void Can_Deserialize_With_CamelCase_Element() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "John" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("John", result.FirstName); + } + + [Fact] + public void Can_Deserialize_DeserializeAs_Attribute() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "aliased" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("aliased", result.Name); + } + + [Fact] + public void Can_Deserialize_Uri_Property() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "http://example.com" + }; + var result = deserializer.Deserialize(response); + Assert.NotNull(result.Link); + Assert.Equal("http://example.com/", result.Link.ToString()); + } + + [Fact] + public void Can_Deserialize_With_DateFormat() + { + var deserializer = new XmlAttributeDeserializer(); + deserializer.DateFormat = "yyyy-MM-dd"; + var response = new RestResponse + { + Content = "2023-01-15" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(new DateTime(2023, 1, 15), result.Created); + } + + [Fact] + public void Can_Deserialize_Nullable_Decimal() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "9.99" + }; + var result = deserializer.Deserialize(response); + Assert.Equal(9.99m, result.Price); + } + + [Fact] + public void Can_Deserialize_Nullable_Decimal_Missing() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "" + }; + var result = deserializer.Deserialize(response); + Assert.Null(result.Price); + } + } + + public class RestClientFinalCoverageTests + { + [Fact] + public void Execute_With_Files_Sets_HasFiles() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.POST); + request.AddFile("file", new byte[] { 1, 2, 3 }, "test.txt", "text/plain"); + client.Execute(request); + + Assert.NotEmpty(fakeHttp.Files); + } + + [Fact] + public void Execute_With_Body_Sets_RequestBody() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.POST); + request.RequestFormat = DataFormat.Json; + request.AddBody(new { Name = "test" }); + client.Execute(request); + + Assert.NotNull(fakeHttp.RequestBody); + } + + [Fact] + public void Execute_Sets_UserAgent() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + client.UserAgent = "TestAgent/1.0"; + + client.Execute(new RestRequest("api", Method.GET)); + + Assert.Equal("TestAgent/1.0", fakeHttp.UserAgent); + } + + [Fact] + public void Execute_Sets_Credentials_From_Request() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + request.Credentials = new NetworkCredential("user", "pass"); + client.Execute(request); + + Assert.NotNull(fakeHttp.Credentials); + } + + [Fact] + public void Execute_Sets_FollowRedirects() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + client.FollowRedirects = true; + + client.Execute(new RestRequest("api", Method.GET)); + + Assert.True(fakeHttp.FollowRedirects); + } + + [Fact] + public void Execute_Sets_Proxy() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + client.Proxy = new WebProxy("http://proxy.example.com"); + + client.Execute(new RestRequest("api", Method.GET)); + + Assert.NotNull(fakeHttp.Proxy); + } + + [Fact] + public void Execute_Sets_MaxRedirects() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + client.MaxRedirects = 5; + + client.Execute(new RestRequest("api", Method.GET)); + + Assert.Equal(5, fakeHttp.MaxRedirects); + } + + [Fact] + public void Execute_Sets_CookieContainer() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + client.CookieContainer = new CookieContainer(); + + client.Execute(new RestRequest("api", Method.GET)); + + Assert.NotNull(fakeHttp.CookieContainer); + } + + [Fact] + public void Execute_Sets_Timeout_From_Request() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + request.Timeout = 3000; + client.Execute(request); + + Assert.Equal(3000, fakeHttp.Timeout); + } + + [Fact] + public void GetHandler_With_ContentType_Params_Returns_Handler() + { + var client = new RestClient("http://example.com"); + // Default JSON handler should handle application/json; charset=utf-8 + var fakeHttp = new FakeHttp(); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var response = client.Execute(new RestRequest("api", Method.GET)); + Assert.NotNull(response); + } + } + + public class MiscExtensionFinalTests + { + [Fact] + public void SaveAs_Writes_File() + { + var data = new byte[] { 72, 101, 108, 108, 111 }; + var path = Path.GetTempFileName(); + try + { + data.SaveAs(path); + var read = File.ReadAllBytes(path); + Assert.Equal(data, read); + } + finally + { + File.Delete(path); + } + } + + [Fact] + public void CopyTo_Large_Data() + { + var data = new byte[50000]; + new Random(42).NextBytes(data); + using (var src = new MemoryStream(data)) + using (var dst = new MemoryStream()) + { + src.CopyTo(dst); + Assert.Equal(data.Length, dst.ToArray().Length); + } + } + } + + public class RestRequestFinalTests + { + [Fact] + public void AddParameter_With_Type() + { + var request = new RestRequest(); + request.AddParameter("name", "value", ParameterType.HttpHeader); + Assert.Contains(request.Parameters, p => p.Name == "name" && p.Type == ParameterType.HttpHeader); + } + + [Fact] + public void AddParameter_Object_Overload() + { + var request = new RestRequest(); + var param = new Parameter { Name = "test", Value = "val", Type = ParameterType.GetOrPost }; + request.AddParameter(param); + Assert.Contains(request.Parameters, p => p.Name == "test"); + } + + [Fact] + public void AddObject_Without_Whitelist() + { + var request = new RestRequest(); + request.AddObject(new ObjWithMultipleProps { Name = "t", Age = 1, Email = "e" }); + Assert.True(request.Parameters.Count >= 3); + } + + [Fact] + public void Resource_With_Leading_Slash_Stripped() + { + var client = new RestClient("http://example.com"); + var request = new RestRequest("/api/test", Method.GET); + var uri = client.BuildUri(request); + Assert.DoesNotContain("//api", uri.ToString()); + } + + [Fact] + public void AddBody_Xml_Without_Namespace() + { + var request = new RestRequest(); + request.RequestFormat = DataFormat.Xml; + request.XmlSerializer = new RestSharp.Serializers.XmlSerializer(); + request.AddBody(new SimpleSerObj { Name = "xmlbody" }); + var bodyParam = request.Parameters.FirstOrDefault(p => p.Type == ParameterType.RequestBody); + Assert.NotNull(bodyParam); + } + } + + // Model classes + public class XmlLowercaseItem + { + public string Name { get; set; } + public int Value { get; set; } + } + + public class XmlAttrDesItem + { + public string Name { get; set; } + public string Id { get; set; } + public string Status { get; set; } + } + + public class XmlListContainer + { + public List Items { get; set; } + } + + public class XmlListEntry + { + public string Label { get; set; } + } + + public class XmlAttrNested + { + public XmlAttrInner Inner { get; set; } + } + + public class XmlAttrInner + { + public string Name { get; set; } + } + + public class XmlCamelItem + { + public string FirstName { get; set; } + } + + public class XmlDeserializeAsItem + { + [DeserializeAs(Name = "custom_name")] + public string Name { get; set; } + } + + public class XmlAttrUri + { + public Uri Link { get; set; } + } + + public class XmlAttrDate + { + public DateTime Created { get; set; } + } + + public class XmlAttrNullDec + { + public decimal? Price { get; set; } + } +} diff --git a/RestSharp.Tests.Coverage/HttpTests.cs b/RestSharp.Tests.Coverage/HttpTests.cs new file mode 100644 index 000000000..0e0df534d --- /dev/null +++ b/RestSharp.Tests.Coverage/HttpTests.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using RestSharp; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class HttpTests + { + [Fact] + public void Http_Default_Constructor_Initializes_Collections() + { + var http = new Http(); + Assert.NotNull(http.Headers); + Assert.NotNull(http.Files); + Assert.NotNull(http.Parameters); + Assert.NotNull(http.Cookies); + } + + [Fact] + public void Http_UserAgent_Can_Be_Set() + { + var http = new Http(); + http.UserAgent = "Test/1.0"; + Assert.Equal("Test/1.0", http.UserAgent); + } + + [Fact] + public void Http_Timeout_Can_Be_Set() + { + var http = new Http(); + http.Timeout = 5000; + Assert.Equal(5000, http.Timeout); + } + + [Fact] + public void Http_Credentials_Can_Be_Set() + { + var http = new Http(); + var creds = new NetworkCredential("user", "pass"); + http.Credentials = creds; + Assert.Equal(creds, http.Credentials); + } + + [Fact] + public void Http_CookieContainer_Can_Be_Set() + { + var http = new Http(); + var container = new CookieContainer(); + http.CookieContainer = container; + Assert.Equal(container, http.CookieContainer); + } + + [Fact] + public void Http_FollowRedirects_Can_Be_Set() + { + var http = new Http(); + http.FollowRedirects = true; + Assert.True(http.FollowRedirects); + } + + [Fact] + public void Http_MaxRedirects_Can_Be_Set() + { + var http = new Http(); + http.MaxRedirects = 5; + Assert.Equal(5, http.MaxRedirects); + } + + [Fact] + public void Http_Url_Can_Be_Set() + { + var http = new Http(); + http.Url = new Uri("http://example.com"); + Assert.Equal("http://example.com/", http.Url.ToString()); + } + + [Fact] + public void Http_RequestBody_Can_Be_Set() + { + var http = new Http(); + http.RequestBody = "body content"; + Assert.Equal("body content", http.RequestBody); + } + + [Fact] + public void Http_RequestContentType_Can_Be_Set() + { + var http = new Http(); + http.RequestContentType = "application/json"; + Assert.Equal("application/json", http.RequestContentType); + } + + [Fact] + public void Http_ClientCertificates_Can_Be_Set() + { + var http = new Http(); + var certs = new System.Security.Cryptography.X509Certificates.X509CertificateCollection(); + http.ClientCertificates = certs; + Assert.Equal(certs, http.ClientCertificates); + } + + [Fact] + public void Http_Proxy_Can_Be_Set() + { + var http = new Http(); + var proxy = new WebProxy("http://proxy.example.com:8080"); + http.Proxy = proxy; + Assert.Equal(proxy, http.Proxy); + } + + [Fact] + public void Http_Create_Returns_New_Instance() + { + var http = new Http(); + var created = http.Create(); + Assert.NotNull(created); + Assert.IsType(created); + } + + [Fact] + public void Http_HasParameters_Returns_False_When_Empty() + { + var http = new TestableHttp(); + Assert.False(http.TestHasParameters); + } + + [Fact] + public void Http_HasParameters_Returns_True_When_Parameters_Exist() + { + var http = new TestableHttp(); + http.Parameters.Add(new HttpParameter { Name = "q", Value = "test" }); + Assert.True(http.TestHasParameters); + } + + [Fact] + public void Http_HasCookies_Returns_False_When_Empty() + { + var http = new TestableHttp(); + Assert.False(http.TestHasCookies); + } + + [Fact] + public void Http_HasCookies_Returns_True_When_Cookies_Exist() + { + var http = new TestableHttp(); + http.Cookies.Add(new HttpCookie { Name = "session", Value = "abc" }); + Assert.True(http.TestHasCookies); + } + + [Fact] + public void Http_HasBody_Returns_False_When_Empty() + { + var http = new TestableHttp(); + Assert.False(http.TestHasBody); + } + + [Fact] + public void Http_HasBody_Returns_True_When_Body_Set() + { + var http = new TestableHttp(); + http.RequestBody = "some body"; + Assert.True(http.TestHasBody); + } + + [Fact] + public void Http_HasFiles_Returns_False_When_Empty() + { + var http = new TestableHttp(); + Assert.False(http.TestHasFiles); + } + + [Fact] + public void Http_HasFiles_Returns_True_When_Files_Exist() + { + var http = new TestableHttp(); + http.Files.Add(new HttpFile { Name = "file", FileName = "test.txt" }); + Assert.True(http.TestHasFiles); + } + + [Fact] + public void Http_Headers_Collection_Can_Be_Modified() + { + var http = new Http(); + http.Headers.Add(new HttpHeader { Name = "X-Test", Value = "123" }); + Assert.Single(http.Headers); + Assert.Equal("X-Test", http.Headers[0].Name); + } + + [Fact] + public void Http_Parameters_Collection_Can_Be_Modified() + { + var http = new Http(); + http.Parameters.Add(new HttpParameter { Name = "key", Value = "val" }); + Assert.Single(http.Parameters); + } + + [Fact] + public void Http_Cookies_Collection_Can_Be_Modified() + { + var http = new Http(); + http.Cookies.Add(new HttpCookie { Name = "c", Value = "v" }); + Assert.Single(http.Cookies); + } + + [Fact] + public void Http_Files_Collection_Can_Be_Modified() + { + var http = new Http(); + http.Files.Add(new HttpFile { Name = "f" }); + Assert.Single(http.Files); + } + } + + public class TestableHttp : Http + { + public bool TestHasParameters => HasParameters; + public bool TestHasCookies => HasCookies; + public bool TestHasBody => HasBody; + public bool TestHasFiles => HasFiles; + } + + public class IHttpFactoryTests + { + [Fact] + public void SimpleFactory_Creates_Instance() + { + var factory = new SimpleFactory(); + var http = factory.Create(); + Assert.NotNull(http); + Assert.IsType(http); + } + } +} diff --git a/RestSharp.Tests.Coverage/HttpUtilityExtendedTests.cs b/RestSharp.Tests.Coverage/HttpUtilityExtendedTests.cs new file mode 100644 index 000000000..7eaee2416 --- /dev/null +++ b/RestSharp.Tests.Coverage/HttpUtilityExtendedTests.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Specialized; +using System.IO; +using System.Text; +using RestSharp.Contrib; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class HttpUtilityExtendedTests + { + [Fact] + public void HtmlAttributeEncode_Encodes_Ampersand() + { + var result = HttpUtility.HtmlAttributeEncode("a&b"); + Assert.Contains("&", result); + } + + [Fact] + public void HtmlAttributeEncode_Encodes_Quote() + { + var result = HttpUtility.HtmlAttributeEncode("a\"b"); + Assert.Contains(""", result); + } + + [Fact] + public void HtmlAttributeEncode_Encodes_LessThan() + { + var result = HttpUtility.HtmlAttributeEncode("a", writer.ToString()); + } + + [Fact] + public void HtmlEncode_With_TextWriter() + { + var writer = new StringWriter(); + HttpUtility.HtmlEncode("
", writer); + Assert.Contains("<", writer.ToString()); + } + + [Fact] + public void HtmlAttributeEncode_With_TextWriter() + { + var writer = new StringWriter(); + HttpUtility.HtmlAttributeEncode("a&b\"c", writer); + var result = writer.ToString(); + Assert.Contains("&", result); + Assert.Contains(""", result); + } + + [Fact] + public void UrlDecode_Byte_Array_Overload() + { + var bytes = Encoding.UTF8.GetBytes("hello%20world"); + var result = HttpUtility.UrlDecode(bytes, Encoding.UTF8); + Assert.Equal("hello world", result); + } + + [Fact] + public void UrlDecode_Byte_Array_Null_Returns_Null() + { + var result = HttpUtility.UrlDecode((byte[])null, Encoding.UTF8); + Assert.Null(result); + } + + [Fact] + public void UrlEncode_Byte_Array_Overload() + { + var bytes = Encoding.UTF8.GetBytes("hello world"); + var result = HttpUtility.UrlEncode(bytes); + Assert.Contains("+", result); + } + + [Fact] + public void UrlEncode_Byte_Array_Null_Returns_Null() + { + var result = HttpUtility.UrlEncode((byte[])null); + Assert.Null(result); + } + + [Fact] + public void UrlEncode_Byte_Array_With_Offset_Length() + { + var bytes = Encoding.UTF8.GetBytes("hello world!"); + var result = HttpUtility.UrlEncode(bytes, 0, bytes.Length); + Assert.Contains("+", result); + } + + [Fact] + public void UrlEncodeToBytes_Byte_Array_Overload() + { + var bytes = Encoding.UTF8.GetBytes("hello world"); + var result = HttpUtility.UrlEncodeToBytes(bytes); + Assert.NotNull(result); + } + + [Fact] + public void UrlEncodeToBytes_Byte_Array_Null_Returns_Null() + { + var result = HttpUtility.UrlEncodeToBytes((byte[])null); + Assert.Null(result); + } + + [Fact] + public void UrlEncodeToBytes_With_Encoding() + { + var result = HttpUtility.UrlEncodeToBytes("hello world", Encoding.UTF8); + Assert.NotNull(result); + } + + [Fact] + public void UrlDecodeToBytes_Byte_Array_Overload() + { + var bytes = Encoding.UTF8.GetBytes("hello"); + var result = HttpUtility.UrlDecodeToBytes(bytes); + Assert.NotNull(result); + } + + [Fact] + public void UrlDecodeToBytes_String_With_Encoding() + { + var result = HttpUtility.UrlDecodeToBytes("hello+world", Encoding.UTF8); + Assert.NotNull(result); + } + + [Fact] + public void UrlDecodeToBytes_Byte_Array_With_Encoded() + { + var bytes = Encoding.ASCII.GetBytes("hello%20world%26test"); + var result = HttpUtility.UrlDecodeToBytes(bytes, 0, bytes.Length); + var str = Encoding.UTF8.GetString(result); + Assert.Equal("hello world&test", str); + } + + [Fact] + public void ParseQueryString_With_Question_Mark_Prefix() + { + var result = HttpUtility.ParseQueryString("?key=value"); + Assert.Equal("value", result["key"]); + } + + [Fact] + public void ParseQueryString_ToString_Returns_QueryString() + { + var result = HttpUtility.ParseQueryString("key1=value1&key2=value2"); + var str = result.ToString(); + Assert.Contains("key1", str); + Assert.Contains("value1", str); + } + + [Fact] + public void ParseQueryString_With_No_Value() + { + var result = HttpUtility.ParseQueryString("key&key2=val2"); + Assert.NotNull(result); + } + + [Fact] + public void HttpUtility_Constructor() + { + var util = new HttpUtility(); + Assert.NotNull(util); + } + + [Fact] + public void UrlDecode_With_Percent_UTF8_Multi_Byte() + { + // encode café and decode it back + var encoded = HttpUtility.UrlEncode("café", Encoding.UTF8); + var decoded = HttpUtility.UrlDecode(encoded, Encoding.UTF8); + Assert.Equal("café", decoded); + } + + [Fact] + public void HtmlDecode_With_Apos_Entity() + { + var result = HttpUtility.HtmlDecode("hello & world"); + Assert.Equal("hello & world", result); + } + + [Fact] + public void HtmlDecode_With_Incomplete_Entity() + { + var result = HttpUtility.HtmlDecode("a & b"); + Assert.Contains("&", result); + } + + [Fact] + public void UrlPathEncode_With_Query() + { + var result = HttpUtility.UrlPathEncode("path/file name?query=value"); + Assert.Contains("?query=value", result); + Assert.DoesNotContain(" ", result.Substring(0, result.IndexOf('?'))); + } + + [Fact] + public void UrlDecode_Complex_Encoded_Bytes() + { + var input = Encoding.ASCII.GetBytes("a%20b%2Bc"); + var result = HttpUtility.UrlDecode(input, 0, input.Length, Encoding.UTF8); + Assert.Equal("a b+c", result); + } + + [Fact] + public void HtmlEncode_Various_Special_Chars() + { + var result = HttpUtility.HtmlEncode("ac&d\"e"); + Assert.Contains("<", result); + Assert.Contains(">", result); + Assert.Contains("&", result); + Assert.Contains(""", result); + } + + [Fact] + public void HtmlDecode_Numeric_Hex_Large() + { + var result = HttpUtility.HtmlDecode("☺"); + Assert.NotNull(result); + Assert.True(result.Length >= 1); + } + + [Fact] + public void UrlEncode_Ascii_Safe_Characters() + { + var result = HttpUtility.UrlEncode("abc-_.~"); + // letters, digits, -, _, ., ~ should mostly pass through + Assert.Contains("abc", result); + } + + [Fact] + public void UrlDecodeToBytes_Empty_Returns_Empty() + { + var result = HttpUtility.UrlDecodeToBytes(""); + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public void HtmlEncode_Tab_And_Newlines() + { + var result = HttpUtility.HtmlEncode("line1\nline2\ttab"); + Assert.Contains("\n", result); + } + + [Fact] + public void UrlDecode_Percent_At_End_No_Chars() + { + var result = HttpUtility.UrlDecode("hello%"); + Assert.Contains("%", result); + } + + [Fact] + public void UrlDecode_Incomplete_Percent_Sequence() + { + var result = HttpUtility.UrlDecode("hello%2"); + Assert.NotNull(result); + } + + [Fact] + public void UrlEncode_All_NonAlphanumeric() + { + var result = HttpUtility.UrlEncode("!@#$%^&*()"); + Assert.NotEqual("!@#$%^&*()", result); + } + + [Fact] + public void ParseQueryString_Key_Without_Equals() + { + var result = HttpUtility.ParseQueryString("lonely"); + Assert.NotNull(result); + } + + [Fact] + public void ParseQueryString_Empty_Value() + { + var result = HttpUtility.ParseQueryString("key="); + Assert.Equal("", result["key"]); + } + + [Fact] + public void UrlDecode_Plus_In_Bytes() + { + var bytes = Encoding.ASCII.GetBytes("a+b"); + var result = HttpUtility.UrlDecode(bytes, 0, bytes.Length, Encoding.UTF8); + Assert.Equal("a b", result); + } + + [Fact] + public void UrlDecode_Bytes_Percent_Encoded() + { + var bytes = Encoding.ASCII.GetBytes("a%41b"); + var result = HttpUtility.UrlDecode(bytes, 0, bytes.Length, Encoding.UTF8); + Assert.Equal("aAb", result); + } + } + + public class HtmlEncoderTests + { + [Fact] + public void HtmlAttributeEncode_Encodes_Quote_And_Ampersand() + { + var result = HttpUtility.HtmlAttributeEncode("a\"b&c"); + Assert.Contains(""", result); + Assert.Contains("&", result); + } + + [Fact] + public void HtmlEncode_Unicode_Char_Above_159() + { + var result = HttpUtility.HtmlEncode("\u00A0 \u00FF \u2603"); + Assert.Contains("&#", result); + } + + [Fact] + public void HtmlDecode_NumericEntity_Decimal_Large() + { + var result = HttpUtility.HtmlDecode("☺"); + Assert.NotNull(result); + } + + [Fact] + public void HtmlDecode_With_Multiple_Mixed_Entities() + { + var result = HttpUtility.HtmlDecode("<b>bold</b> & A"); + Assert.Equal("bold & A", result); + } + } +} diff --git a/RestSharp.Tests.Coverage/HttpUtilityTests.cs b/RestSharp.Tests.Coverage/HttpUtilityTests.cs new file mode 100644 index 000000000..6b97a5375 --- /dev/null +++ b/RestSharp.Tests.Coverage/HttpUtilityTests.cs @@ -0,0 +1,406 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text; +using RestSharp.Contrib; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class HttpUtilityTests + { + [Fact] + public void UrlEncode_Basic_String() + { + var result = HttpUtility.UrlEncode("hello world"); + Assert.Contains("+", result); + } + + [Fact] + public void UrlEncode_Null_Returns_Null() + { + var result = HttpUtility.UrlEncode((string)null); + Assert.Null(result); + } + + [Fact] + public void UrlEncode_Empty_Returns_Empty() + { + var result = HttpUtility.UrlEncode(""); + Assert.Equal("", result); + } + + [Fact] + public void UrlEncode_Special_Characters() + { + var result = HttpUtility.UrlEncode("a&b=c?d#e"); + Assert.DoesNotContain("&", result); + Assert.DoesNotContain("=", result); + Assert.DoesNotContain("?", result); + } + + [Fact] + public void UrlEncode_Unicode_Characters() + { + var result = HttpUtility.UrlEncode("café"); + Assert.NotEqual("café", result); + Assert.Contains("%", result); + } + + [Fact] + public void UrlDecode_Basic_String() + { + var result = HttpUtility.UrlDecode("hello+world"); + Assert.Equal("hello world", result); + } + + [Fact] + public void UrlDecode_Null_Returns_Null() + { + var result = HttpUtility.UrlDecode(null); + Assert.Null(result); + } + + [Fact] + public void UrlDecode_Percent_Encoded() + { + var result = HttpUtility.UrlDecode("hello%20world"); + Assert.Equal("hello world", result); + } + + [Fact] + public void UrlDecode_With_Encoding() + { + var result = HttpUtility.UrlDecode("hello+world", Encoding.UTF8); + Assert.Equal("hello world", result); + } + + [Fact] + public void UrlEncode_Bytes() + { + var bytes = Encoding.UTF8.GetBytes("hello world"); + var result = HttpUtility.UrlEncode(bytes, 0, bytes.Length); + Assert.Contains("+", result); + } + + [Fact] + public void UrlEncode_With_Encoding() + { + var result = HttpUtility.UrlEncode("hello world", Encoding.UTF8); + Assert.Contains("+", result); + } + + [Fact] + public void UrlEncodeUnicode_Encodes_Unicode() + { + var result = HttpUtility.UrlEncodeUnicode("café"); + Assert.Contains("%", result); + } + + [Fact] + public void UrlEncodeUnicode_Null_Returns_Null() + { + var result = HttpUtility.UrlEncodeUnicode(null); + Assert.Null(result); + } + + [Fact] + public void UrlDecode_Bytes() + { + var bytes = Encoding.ASCII.GetBytes("hello+world"); + var result = HttpUtility.UrlDecode(bytes, 0, bytes.Length, Encoding.UTF8); + Assert.Equal("hello world", result); + } + + [Fact] + public void UrlDecode_Bytes_Null_Returns_Null() + { + var result = HttpUtility.UrlDecode(null, 0, 0, Encoding.UTF8); + Assert.Null(result); + } + + [Fact] + public void UrlDecode_Percent_Hex() + { + var result = HttpUtility.UrlDecode("%41%42%43"); + Assert.Equal("ABC", result); + } + + [Fact] + public void UrlDecode_Unicode_Percent_u() + { + var result = HttpUtility.UrlDecode("%u0041%u0042%u0043"); + Assert.Equal("ABC", result); + } + + [Fact] + public void HtmlEncode_Special_Characters() + { + var result = HttpUtility.HtmlEncode(""); + Assert.DoesNotContain("<", result); + Assert.DoesNotContain(">", result); + Assert.Contains("<", result); + Assert.Contains(">", result); + } + + [Fact] + public void HtmlEncode_Null_Returns_Null() + { + var result = HttpUtility.HtmlEncode(null); + Assert.Null(result); + } + + [Fact] + public void HtmlEncode_Ampersand() + { + var result = HttpUtility.HtmlEncode("a & b"); + Assert.Contains("&", result); + } + + [Fact] + public void HtmlEncode_Quotes() + { + var result = HttpUtility.HtmlEncode("say \"hello\""); + Assert.Contains(""", result); + } + + [Fact] + public void HtmlEncode_High_Unicode() + { + var result = HttpUtility.HtmlEncode("hello \u00e9"); + Assert.Contains("&#", result); + } + + [Fact] + public void HtmlDecode_Entities() + { + var result = HttpUtility.HtmlDecode("<div>test</div>"); + Assert.Equal("
test
", result); + } + + [Fact] + public void HtmlDecode_Null_Returns_Null() + { + var result = HttpUtility.HtmlDecode(null); + Assert.Null(result); + } + + [Fact] + public void HtmlDecode_Amp() + { + var result = HttpUtility.HtmlDecode("a & b"); + Assert.Equal("a & b", result); + } + + [Fact] + public void HtmlDecode_Numeric_Entity() + { + var result = HttpUtility.HtmlDecode("ABC"); + Assert.Equal("ABC", result); + } + + [Fact] + public void HtmlDecode_Hex_Entity() + { + var result = HttpUtility.HtmlDecode("ABC"); + Assert.Equal("ABC", result); + } + + [Fact] + public void HtmlDecode_Quot() + { + var result = HttpUtility.HtmlDecode(""hello""); + Assert.Equal("\"hello\"", result); + } + + [Fact] + public void ParseQueryString_Parses_Basic_QueryString() + { + var result = HttpUtility.ParseQueryString("key1=value1&key2=value2"); + Assert.Equal("value1", result["key1"]); + Assert.Equal("value2", result["key2"]); + } + + [Fact] + public void ParseQueryString_Empty_String() + { + var result = HttpUtility.ParseQueryString(""); + Assert.NotNull(result); + Assert.Equal(0, result.Count); + } + + [Fact] + public void ParseQueryString_Null_Throws() + { + Assert.Throws(() => HttpUtility.ParseQueryString(null)); + } + + [Fact] + public void ParseQueryString_With_Encoding() + { + var result = HttpUtility.ParseQueryString("name=test", Encoding.UTF8); + Assert.Equal("test", result["name"]); + } + + [Fact] + public void ParseQueryString_With_Encoded_Values() + { + var result = HttpUtility.ParseQueryString("name=hello+world&foo=a%26b"); + Assert.Equal("hello world", result["name"]); + Assert.Equal("a&b", result["foo"]); + } + + [Fact] + public void ParseQueryString_Multiple_Same_Keys() + { + var result = HttpUtility.ParseQueryString("a=1&a=2"); + var values = result["a"]; + Assert.Contains("1", values); + Assert.Contains("2", values); + } + + [Fact] + public void UrlDecode_Roundtrip() + { + var original = "hello world & goodbye=test?foo#bar"; + var encoded = HttpUtility.UrlEncode(original); + var decoded = HttpUtility.UrlDecode(encoded); + Assert.Equal(original, decoded); + } + + [Fact] + public void HtmlEncodeDecode_Roundtrip() + { + var original = "
Hello & world
"; + var encoded = HttpUtility.HtmlEncode(original); + var decoded = HttpUtility.HtmlDecode(encoded); + Assert.Equal(original, decoded); + } + + [Fact] + public void UrlEncode_Numbers_And_Letters_Unchanged() + { + var result = HttpUtility.UrlEncode("abc123XYZ"); + Assert.Equal("abc123XYZ", result); + } + + [Fact] + public void UrlDecode_Pass_Through_Non_Encoded() + { + var result = HttpUtility.UrlDecode("simple"); + Assert.Equal("simple", result); + } + + [Fact] + public void HtmlDecode_Named_Entity_Apos() + { + var result = HttpUtility.HtmlDecode("'"); + // apos is HTML5, might not be supported + Assert.NotNull(result); + } + + [Fact] + public void HtmlDecode_No_Entities_Returns_Same() + { + var result = HttpUtility.HtmlDecode("plain text"); + Assert.Equal("plain text", result); + } + + [Fact] + public void UrlEncode_Bytes_Null_Returns_Null() + { + var result = HttpUtility.UrlEncode(null, 0, 0); + Assert.Null(result); + } + + [Fact] + public void UrlEncode_Bytes_With_Offset() + { + var bytes = Encoding.UTF8.GetBytes("hello world!"); + var result = HttpUtility.UrlEncode(bytes, 6, 5); + Assert.Contains("world", result); + } + + [Fact] + public void UrlPathEncode_Encodes_Spaces() + { + var result = HttpUtility.UrlPathEncode("hello world/path"); + Assert.DoesNotContain(" ", result); + Assert.Contains("/", result); + } + + [Fact] + public void UrlPathEncode_Null_Returns_Null() + { + var result = HttpUtility.UrlPathEncode(null); + Assert.Null(result); + } + + [Fact] + public void UrlEncodeToBytes_Returns_Encoded_Bytes() + { + var result = HttpUtility.UrlEncodeToBytes("hello world"); + Assert.NotNull(result); + var str = Encoding.ASCII.GetString(result); + Assert.Contains("+", str); + } + + [Fact] + public void UrlEncodeToBytes_Null_Returns_Null() + { + var result = HttpUtility.UrlEncodeToBytes((string)null); + Assert.Null(result); + } + + [Fact] + public void UrlDecodeToBytes_Decodes_Bytes() + { + var result = HttpUtility.UrlDecodeToBytes("hello+world"); + Assert.NotNull(result); + var str = Encoding.UTF8.GetString(result); + Assert.Equal("hello world", str); + } + + [Fact] + public void UrlDecodeToBytes_Null_Returns_Null() + { + var result = HttpUtility.UrlDecodeToBytes((string)null); + Assert.Null(result); + } + + [Fact] + public void UrlEncodeUnicodeToBytes_Encodes() + { + var result = HttpUtility.UrlEncodeUnicodeToBytes("café"); + Assert.NotNull(result); + } + + [Fact] + public void UrlDecodeToBytes_Byte_Array() + { + var input = Encoding.ASCII.GetBytes("hello+world"); + var result = HttpUtility.UrlDecodeToBytes(input, 0, input.Length); + Assert.NotNull(result); + var str = Encoding.UTF8.GetString(result); + Assert.Equal("hello world", str); + } + + [Fact] + public void UrlEncodeToBytes_Byte_Array() + { + var input = Encoding.UTF8.GetBytes("hello world"); + var result = HttpUtility.UrlEncodeToBytes(input, 0, input.Length); + Assert.NotNull(result); + var str = Encoding.ASCII.GetString(result); + Assert.Contains("+", str); + } + + [Fact] + public void UrlEncode_Handles_Single_Char_Percent() + { + var result = HttpUtility.UrlEncode("%"); + Assert.Equal("%25", result); + } + } +} diff --git a/RestSharp.Tests.Coverage/Last50Tests.cs b/RestSharp.Tests.Coverage/Last50Tests.cs new file mode 100644 index 000000000..9d98ce59a --- /dev/null +++ b/RestSharp.Tests.Coverage/Last50Tests.cs @@ -0,0 +1,423 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using Newtonsoft.Json.Linq; +using RestSharp; +using RestSharp.Contrib; +using RestSharp.Deserializers; +using RestSharp.Extensions; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class HttpUtilityBytePathTests + { + [Fact] + public void UrlDecode_String_With_Encoding() + { + var result = HttpUtility.UrlDecode("hello+world", Encoding.UTF8); + Assert.Equal("hello world", result); + } + + [Fact] + public void UrlDecode_String_With_UTF8_MultiByte_Encoding() + { + // UTF-8 encoded é = %c3%a9 + var result = HttpUtility.UrlDecode("%c3%a9", Encoding.UTF8); + Assert.Equal("\u00e9", result); + } + + [Fact] + public void UrlDecode_Byte_Array_With_Percent_And_Offset() + { + var bytes = Encoding.ASCII.GetBytes("a%20b"); + var result = HttpUtility.UrlDecode(bytes, 0, bytes.Length, Encoding.UTF8); + Assert.Equal("a b", result); + } + + [Fact] + public void UrlDecode_Byte_Array_With_Plus() + { + var bytes = Encoding.ASCII.GetBytes("hello+world"); + var result = HttpUtility.UrlDecode(bytes, 0, bytes.Length, Encoding.UTF8); + Assert.Equal("hello world", result); + } + + [Fact] + public void UrlDecode_Byte_Array_With_Hex_Encoded_High() + { + // %c3%a9 = UTF-8 é + var bytes = Encoding.ASCII.GetBytes("%c3%a9"); + var result = HttpUtility.UrlDecode(bytes, 0, bytes.Length, Encoding.UTF8); + Assert.Equal("\u00e9", result); + } + + [Fact] + public void UrlDecodeToBytes_String_With_Encoding() + { + var result = HttpUtility.UrlDecodeToBytes("a%20b", Encoding.UTF8); + Assert.NotNull(result); + } + + [Fact] + public void UrlDecodeToBytes_Byte_Array_With_Offset() + { + var bytes = Encoding.ASCII.GetBytes("a%20b"); + var result = HttpUtility.UrlDecodeToBytes(bytes, 0, bytes.Length); + Assert.NotNull(result); + Assert.True(result.Length > 0); + } + + [Fact] + public void UrlDecodeToBytes_Byte_Array_NoOffset() + { + var bytes = Encoding.ASCII.GetBytes("a%20b"); + var result = HttpUtility.UrlDecodeToBytes(bytes); + Assert.NotNull(result); + } + + [Fact] + public void UrlEncode_Byte_Array_NoOffset() + { + var bytes = Encoding.UTF8.GetBytes("hello"); + var result = HttpUtility.UrlEncode(bytes); + Assert.Equal("hello", result); + } + + [Fact] + public void UrlEncode_Byte_Array_With_Offset() + { + var bytes = Encoding.UTF8.GetBytes("hello world"); + var result = HttpUtility.UrlEncode(bytes, 0, bytes.Length); + Assert.Contains("+", result); + } + + [Fact] + public void UrlEncodeToBytes_String_With_Encoding() + { + var result = HttpUtility.UrlEncodeToBytes("hello", Encoding.UTF8); + Assert.NotNull(result); + } + + [Fact] + public void UrlEncodeToBytes_Byte_Array_NoOffset() + { + var bytes = Encoding.UTF8.GetBytes("abc"); + var result = HttpUtility.UrlEncodeToBytes(bytes); + Assert.NotNull(result); + } + + [Fact] + public void UrlEncodeToBytes_Byte_Array_With_Offset() + { + var bytes = Encoding.UTF8.GetBytes("abc"); + var result = HttpUtility.UrlEncodeToBytes(bytes, 0, bytes.Length); + Assert.NotNull(result); + } + + [Fact] + public void UrlEncodeUnicodeToBytes_Returns_Bytes() + { + var result = HttpUtility.UrlEncodeUnicodeToBytes("test\u4e2d"); + Assert.NotNull(result); + Assert.True(result.Length > 0); + } + + [Fact] + public void HtmlDecode_To_TextWriter_Null_Source() + { + var writer = new StringWriter(); + HttpUtility.HtmlDecode(null, writer); + Assert.Equal("", writer.ToString()); + } + + [Fact] + public void HtmlEncode_To_TextWriter_Null_Source() + { + var writer = new StringWriter(); + HttpUtility.HtmlEncode(null, writer); + Assert.Equal("", writer.ToString()); + } + + [Fact] + public void HtmlAttributeEncode_To_TextWriter_Regular() + { + var writer = new StringWriter(); + HttpUtility.HtmlAttributeEncode("test\"value", writer); + var result = writer.ToString(); + Assert.Contains(""", result); + } + + [Fact] + public void ParseQueryString_With_Encoding_And_QuestionMark() + { + var result = HttpUtility.ParseQueryString("?a=1&b=2", Encoding.UTF8); + Assert.Equal("1", result["a"]); + Assert.Equal("2", result["b"]); + } + + [Fact] + public void HttpQSCollection_ToString_Returns_Formatted() + { + var coll = HttpUtility.ParseQueryString("a=1&b=2"); + var str = coll.ToString(); + Assert.Contains("a=1", str); + Assert.Contains("b=2", str); + } + } + + public class HtmlEncoderInternalPathTests + { + [Fact] + public void HtmlEncode_Empty_String_Returns_Empty() + { + var result = HttpUtility.HtmlEncode(""); + Assert.Equal("", result); + } + + [Fact] + public void HtmlDecode_Empty_Returns_Empty() + { + var result = HttpUtility.HtmlDecode(""); + Assert.Equal("", result); + } + + [Fact] + public void HtmlAttributeEncode_Empty_Returns_Empty() + { + var result = HttpUtility.HtmlAttributeEncode(""); + Assert.Equal("", result); + } + + [Fact] + public void HtmlDecode_Entity_With_Hex_Then_Invalid() + { + // hex entity with invalid hex digit triggers have_trailing_digits path + var result = HttpUtility.HtmlDecode("AG;"); + Assert.NotNull(result); + } + + [Fact] + public void UrlPathEncode_SpecialChars() + { + var result = HttpUtility.UrlPathEncode("/path/to file?q=1"); + Assert.Contains("%20", result); + Assert.Contains("?", result); + } + } + + public class RestClientDeserializeTests + { + [Fact] + public void Execute_With_Error_Response_Sets_ErrorMessage() + { + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactory(new HttpResponse + { + RawBytes = Encoding.UTF8.GetBytes("error"), + ContentType = "text/plain", + StatusCode = HttpStatusCode.InternalServerError, + ResponseStatus = ResponseStatus.Error, + ErrorMessage = "Something failed", + ErrorException = new Exception("boom") + }); + + var response = client.Execute(new RestRequest("api", Method.GET)); + Assert.Equal(ResponseStatus.Error, response.ResponseStatus); + } + + [Fact] + public void Execute_Generic_With_Error_Returns_Default_Data() + { + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactory(new HttpResponse + { + RawBytes = Encoding.UTF8.GetBytes(""), + ContentType = "application/json", + StatusCode = HttpStatusCode.InternalServerError, + ResponseStatus = ResponseStatus.Error, + ErrorMessage = "Something failed", + ErrorException = new Exception("boom") + }); + + var response = client.Execute(new RestRequest("api", Method.GET)); + Assert.Equal(ResponseStatus.Error, response.ResponseStatus); + } + + [Fact] + public void Execute_Generic_Deserialize_With_Handler_Error() + { + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactory(new HttpResponse + { + RawBytes = Encoding.UTF8.GetBytes("not valid json"), + ContentType = "application/json", + StatusCode = HttpStatusCode.OK, + ResponseStatus = ResponseStatus.Completed + }); + + var response = client.Execute(new RestRequest("api", Method.GET)); + // Either the data is null due to deserialization error, or an error is caught + Assert.NotNull(response); + } + + [Fact] + public void BuildUri_With_UrlSegment_Substitution() + { + var client = new RestClient("http://example.com"); + var request = new RestRequest("api/{id}", Method.GET); + request.AddUrlSegment("id", "42"); + var uri = client.BuildUri(request); + Assert.Contains("42", uri.ToString()); + Assert.DoesNotContain("{id}", uri.ToString()); + } + } + + public class RestRequestFileTests + { + [Fact] + public void AddFile_With_ContentType_Inferred() + { + var request = new RestRequest(); + var tmpFile = Path.GetTempFileName(); + try + { + File.WriteAllText(tmpFile, "test content"); + request.AddFile("file", tmpFile); + Assert.Single(request.Files); + Assert.Equal("file", request.Files[0].Name); + } + finally + { + File.Delete(tmpFile); + } + } + + [Fact] + public void AddFile_With_Explicit_ContentType() + { + var request = new RestRequest(); + var tmpFile = Path.GetTempFileName(); + try + { + File.WriteAllText(tmpFile, "test content"); + request.AddFile("file", Encoding.UTF8.GetBytes("test content"), "test.txt", "text/plain"); + Assert.Single(request.Files); + } + finally + { + File.Delete(tmpFile); + } + } + + [Fact] + public void UserState_Property() + { + var request = new RestRequest(); + Assert.Null(request.UserState); + } + } + + public class MiscCopyToTests + { + [Fact] + public void CopyTo_Stream_Extension_Works() + { + var src = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); + var dst = new MemoryStream(); + MiscExtensions.CopyTo(src, dst); + Assert.Equal(5, dst.Length); + } + } + + public class JTokenAsStringTests + { + [Fact] + public void AsString_With_Culture() + { + var token = JToken.Parse("3.14"); + var result = token.AsString(CultureInfo.InvariantCulture); + Assert.Equal("3.14", result); + } + } + + public class XmlAttributeDeserializerMapTests + { + [Fact] + public void Can_Deserialize_With_Underscore_Names() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "JohnDoe" + }; + var result = deserializer.Deserialize(response); + // XmlAttributeDeserializer might not do underscore-to-camelCase mapping + Assert.NotNull(result); + } + + [Fact] + public void Can_Deserialize_With_Dashed_Names() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "JohnDoe" + }; + var result = deserializer.Deserialize(response); + Assert.NotNull(result); + } + + [Fact] + public void GetElementByName_With_LowerCase() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "test" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("test", result.TheName); + } + + [Fact] + public void GetAttributeByName_With_LowerCase() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("99", result.Id); + } + } + + // Model classes + public class XmlAttrUnderscoreItem + { + public string FirstName { get; set; } + public string LastName { get; set; } + } + + public class XmlAttrDashedItem + { + public string FirstName { get; set; } + public string LastName { get; set; } + } + + public class XmlAttrMixed + { + public string TheName { get; set; } + } + + public class XmlAttrById + { + public string Id { get; set; } + } +} diff --git a/RestSharp.Tests.Coverage/PushTo80Tests.cs b/RestSharp.Tests.Coverage/PushTo80Tests.cs new file mode 100644 index 000000000..63170bec5 --- /dev/null +++ b/RestSharp.Tests.Coverage/PushTo80Tests.cs @@ -0,0 +1,441 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using RestSharp; +using RestSharp.Contrib; +using RestSharp.Deserializers; +using RestSharp.Extensions; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class HtmlEncoderEdgeCaseTests + { + [Fact] + public void HtmlEncode_FullwidthLessThan() + { + // \uff1c is fullwidth < and \uff1e is fullwidth > + var result = HttpUtility.HtmlEncode("\uff1c\uff1e"); + Assert.Contains("65308", result); + Assert.Contains("65310", result); + } + + [Fact] + public void HtmlDecode_Consecutive_Ampersands() + { + // Test state transition when & appears inside an entity + var result = HttpUtility.HtmlDecode("&<"); + Assert.Equal("&<", result); + } + + [Fact] + public void HtmlDecode_Ampersand_Then_Another_Ampersand() + { + // This should trigger the c == '&' in state != 0 code + var result = HttpUtility.HtmlDecode("&invalid&"); + Assert.Contains("&", result); + } + + [Fact] + public void HtmlDecode_Hash_Semicolon_Directly() + { + // &#; should be treated as invalid + var result = HttpUtility.HtmlDecode("&#;test"); + Assert.NotNull(result); + } + + [Fact] + public void HtmlDecode_Numeric_Entity_With_Trailing_Text() + { + // Aabc; - digits followed by non-digit in numeric entity + var result = HttpUtility.HtmlDecode("Aabc;"); + Assert.NotNull(result); + Assert.True(result.Length > 0); + } + + [Fact] + public void HtmlDecode_Hex_Entity_Invalid_Chars() + { + var result = HttpUtility.HtmlDecode("&#xZZZ;"); + Assert.NotNull(result); + } + + [Fact] + public void HtmlDecode_Named_Entity_euro() + { + var result = HttpUtility.HtmlDecode("€"); + Assert.NotNull(result); + } + + [Fact] + public void HtmlDecode_Named_Entity_trade() + { + var result = HttpUtility.HtmlDecode("™"); + Assert.NotNull(result); + } + + [Fact] + public void HtmlDecode_Long_Named_Entity() + { + var result = HttpUtility.HtmlDecode(" "); + Assert.NotNull(result); + } + + [Fact] + public void HtmlDecode_Entity_At_End_Without_Semicolon() + { + var result = HttpUtility.HtmlDecode("text&"); + Assert.NotNull(result); + } + + [Fact] + public void HtmlDecode_Entity_Number_At_End() + { + var result = HttpUtility.HtmlDecode("A"); + Assert.NotNull(result); + } + + [Fact] + public void UrlEncodeUnicode_HighChar() + { + // This should trigger the c > 255 path in UrlEncodeChar + var result = HttpUtility.UrlEncodeUnicode("\u4e2d"); // Chinese character + Assert.Contains("%u", result); + } + + [Fact] + public void UrlEncodeUnicode_MixedContent() + { + var result = HttpUtility.UrlEncodeUnicode("hello\u4e16\u754c"); + Assert.Contains("hello", result); + Assert.Contains("%u", result); + } + + [Fact] + public void UrlPathEncode_Space_Is_Percent20() + { + // UrlPathEncode should encode space as %20 not + + var result = HttpUtility.UrlPathEncode("hello world"); + Assert.Contains("%20", result); + } + + [Fact] + public void UrlPathEncode_Control_Char() + { + var result = HttpUtility.UrlPathEncode("hello\x01world"); + Assert.Contains("%", result); + } + } + + public class XmlAttributeDeserializerEdgeTests + { + [Fact] + public void Can_Deserialize_With_RootElement_And_Attributes() + { + var deserializer = new XmlAttributeDeserializer(); + deserializer.RootElement = "Data"; + var response = new RestResponse + { + Content = "rooted5" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("rooted", result.Name); + } + + [Fact] + public void Can_Deserialize_Attribute_Values() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("attr_name", result.Name); + Assert.Equal("active", result.Status); + } + + [Fact] + public void Can_Deserialize_Attribute_CaseInsensitive() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "" + }; + var result = deserializer.Deserialize(response); + Assert.NotNull(result); + } + + [Fact] + public void Can_Deserialize_With_Namespace_And_Attributes() + { + var deserializer = new XmlAttributeDeserializer(); + deserializer.Namespace = "http://test.com"; + var response = new RestResponse + { + Content = "ns_attr3" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("ns_attr", result.Name); + } + + [Fact] + public void Can_Deserialize_With_DeserializeAs() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "mapped" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("mapped", result.TheName); + } + + [Fact] + public void Can_Deserialize_With_DeserializeAs_Attribute_Flag() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "test" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("42", result.Id); + Assert.Equal("test", result.Name); + } + + [Fact] + public void RemoveNamespace_Strips_Namespace() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "stripped" + }; + var result = deserializer.Deserialize(response); + Assert.Equal("stripped", result.Name); + } + + [Fact] + public void Can_Deserialize_List_Of_Nested() + { + var deserializer = new XmlAttributeDeserializer(); + var response = new RestResponse + { + Content = "firstsecond" + }; + var result = deserializer.Deserialize(response); + Assert.NotNull(result.Items); + Assert.Equal(2, result.Items.Count); + Assert.Equal("first", result.Items[0].Text); + } + } + + public class HttpUtilityEdgeCaseTests + { + [Fact] + public void UrlDecode_Byte_Array_With_Percent_Sequence() + { + var bytes = Encoding.ASCII.GetBytes("a%41%42%43z"); + var result = HttpUtility.UrlDecode(bytes, 0, bytes.Length, Encoding.UTF8); + Assert.Equal("aABCz", result); + } + + [Fact] + public void UrlDecodeToBytes_Byte_Array_Full() + { + var bytes = Encoding.ASCII.GetBytes("a%20b"); + var result = HttpUtility.UrlDecodeToBytes(bytes); + Assert.NotNull(result); + } + + [Fact] + public void ParseQueryString_Complex() + { + var result = HttpUtility.ParseQueryString("a=1&b=2&c=hello+world&d=%26"); + Assert.Equal("1", result["a"]); + Assert.Equal("2", result["b"]); + Assert.Equal("hello world", result["c"]); + Assert.Equal("&", result["d"]); + } + + [Fact] + public void UrlEncode_Byte_Array_Full() + { + var bytes = Encoding.UTF8.GetBytes("hello world!"); + var result = HttpUtility.UrlEncode(bytes); + Assert.Contains("+", result); + // ! might or might not be encoded depending on implementation + Assert.NotNull(result); + } + + [Fact] + public void UrlEncodeToBytes_Byte_Array_Full() + { + var bytes = Encoding.UTF8.GetBytes("hello world"); + var result = HttpUtility.UrlEncodeToBytes(bytes); + Assert.NotNull(result); + Assert.True(result.Length > 0); + } + + [Fact] + public void HtmlDecode_To_TextWriter_With_Entities() + { + var writer = new StringWriter(); + HttpUtility.HtmlDecode("<b>hello</b>", writer); + Assert.Equal("hello", writer.ToString()); + } + + [Fact] + public void HtmlEncode_To_TextWriter_With_Entities() + { + var writer = new StringWriter(); + HttpUtility.HtmlEncode("hello", writer); + var result = writer.ToString(); + Assert.Contains("<", result); + Assert.Contains(">", result); + } + + [Fact] + public void HtmlAttributeEncode_To_TextWriter_Null() + { + var writer = new StringWriter(); + HttpUtility.HtmlAttributeEncode(null, writer); + Assert.Equal("", writer.ToString()); + } + } + + public class RestRequestAsyncHandleFinalTests + { + [Fact] + public void Abort_When_WebRequest_Is_Not_Null() + { + var handle = new RestRequestAsyncHandle(); + // WebRequest is null, Abort should not throw + handle.Abort(); + Assert.Null(handle.WebRequest); + } + } + + public class MiscExtensionsFinalTests + { + [Fact] + public void AsString_NonNull_Bytes() + { + var bytes = Encoding.UTF8.GetBytes("test string"); + var result = bytes.AsString(); + Assert.Equal("test string", result); + } + + [Fact] + public void AsString_Empty_Bytes() + { + var bytes = new byte[0]; + var result = bytes.AsString(); + Assert.Equal("", result); + } + + [Fact] + public void ReadAsBytes_Empty_Stream() + { + using (var stream = new MemoryStream()) + { + var result = stream.ReadAsBytes(); + Assert.Empty(result); + } + } + } + + public class JsonSerializerFinalTests + { + [Fact] + public void Serialize_With_Null_Property() + { + var serializer = new RestSharp.Serializers.JsonSerializer(); + var result = serializer.Serialize(new ObjWithNullableField { Name = null, Count = 42 }); + Assert.Contains("42", result); + } + + [Fact] + public void DateFormat_Can_Be_Set() + { + var serializer = new RestSharp.Serializers.JsonSerializer(); + serializer.DateFormat = "yyyy-MM-dd"; + Assert.Equal("yyyy-MM-dd", serializer.DateFormat); + } + + [Fact] + public void RootElement_Can_Be_Set() + { + var serializer = new RestSharp.Serializers.JsonSerializer(); + serializer.RootElement = "root"; + Assert.Equal("root", serializer.RootElement); + } + + [Fact] + public void Namespace_Can_Be_Set() + { + var serializer = new RestSharp.Serializers.JsonSerializer(); + serializer.Namespace = "ns"; + Assert.Equal("ns", serializer.Namespace); + } + } + + // Model classes + public class XmlAttrSimple + { + public string Name { get; set; } + public int Value { get; set; } + } + + public class XmlWithAttr + { + public string Name { get; set; } + public int Value { get; set; } + } + + public class XmlWithAttrStr + { + public string Name { get; set; } + public string Status { get; set; } + } + + public class XmlNsSimple + { + public string Name { get; set; } + } + + public class XmlWithDeserializeAs + { + [DeserializeAs(Name = "actual_name")] + public string TheName { get; set; } + } + + public class XmlWithDeserializeAsAttr + { + [DeserializeAs(Name = "id", Attribute = true)] + public string Id { get; set; } + public string Name { get; set; } + } + + public class XmlAttrListContainer + { + public List Items { get; set; } + } + + public class XmlAttrListItem + { + public string Text { get; set; } + } + + public class ObjWithNullableField + { + public string Name { get; set; } + public int Count { get; set; } + } +} diff --git a/RestSharp.Tests.Coverage/ResponseAndModelTests.cs b/RestSharp.Tests.Coverage/ResponseAndModelTests.cs new file mode 100644 index 000000000..e7bcce07c --- /dev/null +++ b/RestSharp.Tests.Coverage/ResponseAndModelTests.cs @@ -0,0 +1,485 @@ +using System; +using System.Collections.Generic; +using System.Net; +using RestSharp; +using RestSharp.Deserializers; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class RestResponseTests + { + [Fact] + public void RestResponse_Has_Default_Values() + { + var response = new RestResponse(); + Assert.NotNull(response.Headers); + Assert.NotNull(response.Cookies); + Assert.Equal(ResponseStatus.None, response.ResponseStatus); + } + + [Fact] + public void RestResponse_Content_Can_Be_Set() + { + var response = new RestResponse(); + response.Content = "{\"name\": \"test\"}"; + Assert.Equal("{\"name\": \"test\"}", response.Content); + } + + [Fact] + public void RestResponse_StatusCode_Can_Be_Set() + { + var response = new RestResponse(); + response.StatusCode = HttpStatusCode.OK; + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public void RestResponse_StatusDescription_Can_Be_Set() + { + var response = new RestResponse(); + response.StatusDescription = "OK"; + Assert.Equal("OK", response.StatusDescription); + } + + [Fact] + public void RestResponse_ContentType_Can_Be_Set() + { + var response = new RestResponse(); + response.ContentType = "application/json"; + Assert.Equal("application/json", response.ContentType); + } + + [Fact] + public void RestResponse_ContentLength_Can_Be_Set() + { + var response = new RestResponse(); + response.ContentLength = 1024; + Assert.Equal(1024, response.ContentLength); + } + + [Fact] + public void RestResponse_ContentEncoding_Can_Be_Set() + { + var response = new RestResponse(); + response.ContentEncoding = "utf-8"; + Assert.Equal("utf-8", response.ContentEncoding); + } + + [Fact] + public void RestResponse_RawBytes_Can_Be_Set() + { + var response = new RestResponse(); + var data = new byte[] { 1, 2, 3 }; + response.RawBytes = data; + Assert.Equal(data, response.RawBytes); + } + + [Fact] + public void RestResponse_ResponseUri_Can_Be_Set() + { + var response = new RestResponse(); + var uri = new Uri("http://example.com"); + response.ResponseUri = uri; + Assert.Equal(uri, response.ResponseUri); + } + + [Fact] + public void RestResponse_Server_Can_Be_Set() + { + var response = new RestResponse(); + response.Server = "nginx"; + Assert.Equal("nginx", response.Server); + } + + [Fact] + public void RestResponse_ErrorMessage_Can_Be_Set() + { + var response = new RestResponse(); + response.ErrorMessage = "Connection failed"; + Assert.Equal("Connection failed", response.ErrorMessage); + } + + [Fact] + public void RestResponse_ErrorException_Can_Be_Set() + { + var response = new RestResponse(); + var ex = new Exception("test error"); + response.ErrorException = ex; + Assert.Equal(ex, response.ErrorException); + } + + [Fact] + public void RestResponse_Request_Can_Be_Set() + { + var response = new RestResponse(); + var request = new RestRequest(); + response.Request = request; + Assert.Equal(request, response.Request); + } + + [Fact] + public void RestResponse_ResponseStatus_Can_Be_Set() + { + var response = new RestResponse(); + response.ResponseStatus = ResponseStatus.Completed; + Assert.Equal(ResponseStatus.Completed, response.ResponseStatus); + + response.ResponseStatus = ResponseStatus.Error; + Assert.Equal(ResponseStatus.Error, response.ResponseStatus); + + response.ResponseStatus = ResponseStatus.TimedOut; + Assert.Equal(ResponseStatus.TimedOut, response.ResponseStatus); + + response.ResponseStatus = ResponseStatus.Aborted; + Assert.Equal(ResponseStatus.Aborted, response.ResponseStatus); + } + + [Fact] + public void GenericRestResponse_Has_Data_Property() + { + var response = new RestResponse(); + response.Data = new TestData { Name = "test", Value = 42 }; + Assert.Equal("test", response.Data.Name); + Assert.Equal(42, response.Data.Value); + } + + [Fact] + public void GenericRestResponse_Inherits_Base_Properties() + { + var response = new RestResponse(); + response.Content = "content"; + response.StatusCode = HttpStatusCode.NotFound; + Assert.Equal("content", response.Content); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + private class TestData + { + public string Name { get; set; } + public int Value { get; set; } + } + } + + public class ParameterTests + { + [Fact] + public void Parameter_Properties_Can_Be_Set() + { + var param = new Parameter(); + param.Name = "test"; + param.Value = "value"; + param.Type = ParameterType.GetOrPost; + + Assert.Equal("test", param.Name); + Assert.Equal("value", param.Value); + Assert.Equal(ParameterType.GetOrPost, param.Type); + } + + [Fact] + public void Parameter_ToString_Returns_Name_Equals_Value() + { + var param = new Parameter(); + param.Name = "key"; + param.Value = "val"; + param.Type = ParameterType.GetOrPost; + + var str = param.ToString(); + Assert.Equal("key=val", str); + } + + [Fact] + public void Parameter_Types_Cover_All_Enum_Values() + { + Assert.Equal(0, (int)ParameterType.Cookie); + Assert.Equal(1, (int)ParameterType.GetOrPost); + Assert.Equal(2, (int)ParameterType.UrlSegment); + Assert.Equal(3, (int)ParameterType.HttpHeader); + Assert.Equal(4, (int)ParameterType.RequestBody); + } + } + + public class FileParameterTests + { + [Fact] + public void Create_From_Bytes_Sets_Properties() + { + var data = new byte[] { 1, 2, 3, 4 }; + var fp = FileParameter.Create("file", data, "test.bin", "application/octet-stream"); + + Assert.Equal("file", fp.Name); + Assert.Equal("test.bin", fp.FileName); + Assert.Equal("application/octet-stream", fp.ContentType); + Assert.Equal(4, fp.ContentLength); + Assert.NotNull(fp.Writer); + } + + [Fact] + public void Create_From_Bytes_Default_ContentType() + { + var data = new byte[] { 1, 2, 3 }; + var fp = FileParameter.Create("file", data, "test.bin"); + + Assert.Null(fp.ContentType); + } + + [Fact] + public void Create_From_Bytes_3_Params() + { + var data = new byte[] { 1, 2, 3 }; + var fp = FileParameter.Create("file", data, "test.bin"); + + Assert.Equal("file", fp.Name); + Assert.Equal("test.bin", fp.FileName); + Assert.Equal(3, fp.ContentLength); + } + + [Fact] + public void Writer_Writes_Correct_Data() + { + var data = new byte[] { 10, 20, 30 }; + var fp = FileParameter.Create("file", data, "test.bin"); + + using (var stream = new System.IO.MemoryStream()) + { + fp.Writer(stream); + Assert.Equal(data, stream.ToArray()); + } + } + + [Fact] + public void FileParameter_Properties_Can_Be_Set() + { + var fp = new FileParameter(); + Action writer = s => { }; + fp.Name = "test"; + fp.FileName = "file.txt"; + fp.ContentType = "text/plain"; + fp.ContentLength = 100; + fp.Writer = writer; + + Assert.Equal("test", fp.Name); + Assert.Equal("file.txt", fp.FileName); + Assert.Equal("text/plain", fp.ContentType); + Assert.Equal(100, fp.ContentLength); + Assert.Equal(writer, fp.Writer); + } + } + + public class HttpResponseTests + { + [Fact] + public void HttpResponse_Has_Default_Values() + { + var response = new HttpResponse(); + Assert.NotNull(response.Headers); + Assert.NotNull(response.Cookies); + Assert.Equal(ResponseStatus.None, response.ResponseStatus); + } + + [Fact] + public void HttpResponse_Properties_Can_Be_Set() + { + var response = new HttpResponse(); + response.ContentEncoding = "utf-8"; + response.ContentLength = 4; + response.ContentType = "text/plain"; + response.ErrorMessage = "error"; + response.ErrorException = new Exception("ex"); + response.RawBytes = System.Text.Encoding.UTF8.GetBytes("body"); + response.ResponseUri = new Uri("http://example.com"); + response.Server = "Apache"; + response.StatusCode = HttpStatusCode.OK; + response.StatusDescription = "OK"; + response.ResponseStatus = ResponseStatus.Completed; + + Assert.Equal("body", response.Content); + Assert.Equal("utf-8", response.ContentEncoding); + Assert.Equal(4, response.ContentLength); + Assert.Equal("text/plain", response.ContentType); + Assert.Equal("error", response.ErrorMessage); + Assert.NotNull(response.ErrorException); + Assert.Equal(4, response.RawBytes.Length); + Assert.Equal("http://example.com/", response.ResponseUri.ToString()); + Assert.Equal("Apache", response.Server); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("OK", response.StatusDescription); + Assert.Equal(ResponseStatus.Completed, response.ResponseStatus); + } + + [Fact] + public void HttpResponse_Content_Is_Lazy_Loaded_From_RawBytes() + { + var response = new HttpResponse(); + response.RawBytes = System.Text.Encoding.UTF8.GetBytes("hello world"); + Assert.Equal("hello world", response.Content); + } + + [Fact] + public void HttpResponse_Content_Returns_Empty_When_RawBytes_Null() + { + var response = new HttpResponse(); + // When RawBytes is null, AsString() returns empty string + var content = response.Content; + Assert.True(content == null || content == ""); + } + } + + public class HttpCookieTests + { + [Fact] + public void HttpCookie_Properties_Can_Be_Set() + { + var cookie = new HttpCookie(); + cookie.Comment = "test cookie"; + cookie.CommentUri = new Uri("http://example.com"); + cookie.Discard = true; + cookie.Domain = "example.com"; + cookie.Expired = false; + cookie.Expires = new DateTime(2025, 1, 1); + cookie.HttpOnly = true; + cookie.Name = "session"; + cookie.Path = "/"; + cookie.Port = "80"; + cookie.Secure = true; + cookie.TimeStamp = DateTime.Now; + cookie.Value = "abc123"; + cookie.Version = 1; + + Assert.Equal("test cookie", cookie.Comment); + Assert.Equal("example.com", cookie.Domain); + Assert.True(cookie.Discard); + Assert.False(cookie.Expired); + Assert.True(cookie.HttpOnly); + Assert.Equal("session", cookie.Name); + Assert.Equal("/", cookie.Path); + Assert.True(cookie.Secure); + Assert.Equal("abc123", cookie.Value); + Assert.Equal(1, cookie.Version); + } + } + + public class RestResponseCookieTests + { + [Fact] + public void RestResponseCookie_Properties_Can_Be_Set() + { + var cookie = new RestResponseCookie(); + cookie.Comment = "test"; + cookie.CommentUri = new Uri("http://example.com"); + cookie.Discard = true; + cookie.Domain = "example.com"; + cookie.Expired = true; + cookie.Expires = new DateTime(2024, 6, 1); + cookie.HttpOnly = true; + cookie.Name = "auth"; + cookie.Path = "/api"; + cookie.Port = "443"; + cookie.Secure = true; + cookie.TimeStamp = DateTime.UtcNow; + cookie.Value = "token123"; + cookie.Version = 2; + + Assert.Equal("test", cookie.Comment); + Assert.Equal("example.com", cookie.Domain); + Assert.True(cookie.Discard); + Assert.True(cookie.Expired); + Assert.True(cookie.HttpOnly); + Assert.Equal("auth", cookie.Name); + Assert.Equal("/api", cookie.Path); + Assert.Equal("443", cookie.Port); + Assert.True(cookie.Secure); + Assert.Equal("token123", cookie.Value); + Assert.Equal(2, cookie.Version); + } + } + + public class HttpHeaderTests + { + [Fact] + public void HttpHeader_Properties_Can_Be_Set() + { + var header = new HttpHeader(); + header.Name = "Content-Type"; + header.Value = "application/json"; + + Assert.Equal("Content-Type", header.Name); + Assert.Equal("application/json", header.Value); + } + } + + public class HttpParameterTests + { + [Fact] + public void HttpParameter_Properties_Can_Be_Set() + { + var param = new HttpParameter(); + param.Name = "q"; + param.Value = "search term"; + + Assert.Equal("q", param.Name); + Assert.Equal("search term", param.Value); + } + } + + public class HttpFileTests + { + [Fact] + public void HttpFile_Properties_Can_Be_Set() + { + var file = new HttpFile(); + file.Name = "upload"; + file.FileName = "document.pdf"; + file.ContentType = "application/pdf"; + file.ContentLength = 1024; + file.Writer = s => { }; + + Assert.Equal("upload", file.Name); + Assert.Equal("document.pdf", file.FileName); + Assert.Equal("application/pdf", file.ContentType); + Assert.Equal(1024, file.ContentLength); + Assert.NotNull(file.Writer); + } + } + + public class RestRequestAsyncHandleTests + { + [Fact] + public void Default_Constructor_Creates_Handle() + { + var handle = new RestRequestAsyncHandle(); + Assert.Null(handle.WebRequest); + } + + [Fact] + public void Abort_Does_Not_Throw_When_WebRequest_Is_Null() + { + var handle = new RestRequestAsyncHandle(); + handle.Abort(); // should not throw + } + } + + public class DeserializeAsAttributeTests + { + [Fact] + public void DeserializeAsAttribute_Can_Be_Created() + { + var attr = new DeserializeAsAttribute(); + Assert.NotNull(attr); + } + + [Fact] + public void DeserializeAsAttribute_Name_Can_Be_Set() + { + var attr = new DeserializeAsAttribute { Name = "custom" }; + Assert.Equal("custom", attr.Name); + } + + [Fact] + public void DeserializeAsAttribute_Attribute_Can_Be_Set() + { + var attr = new DeserializeAsAttribute { Attribute = true }; + Assert.True(attr.Attribute); + } + } +} diff --git a/RestSharp.Tests.Coverage/RestClientIntegrationTests.cs b/RestSharp.Tests.Coverage/RestClientIntegrationTests.cs new file mode 100644 index 000000000..efffa9030 --- /dev/null +++ b/RestSharp.Tests.Coverage/RestClientIntegrationTests.cs @@ -0,0 +1,664 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using RestSharp; +using RestSharp.Deserializers; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + /// + /// Tests that exercise RestClient's internal methods (ConfigureHttp, ConvertToRestResponse, etc.) + /// through the Execute method with a fake HTTP implementation. + /// + public class RestClientIntegrationTests + { + [Fact] + public void Execute_With_FakeHttp_Returns_Response() + { + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactory(new HttpResponse + { + RawBytes = Encoding.UTF8.GetBytes("{\"name\":\"test\"}"), + ContentType = "application/json", + StatusCode = HttpStatusCode.OK, + StatusDescription = "OK", + ContentEncoding = "utf-8", + ContentLength = 15, + ResponseUri = new Uri("http://example.com/api"), + Server = "TestServer", + ResponseStatus = ResponseStatus.Completed + }); + + var request = new RestRequest("api", Method.GET); + var response = client.Execute(request); + + Assert.Equal("{\"name\":\"test\"}", response.Content); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("OK", response.StatusDescription); + Assert.Equal("TestServer", response.Server); + Assert.Equal(ResponseStatus.Completed, response.ResponseStatus); + } + + [Fact] + public void Execute_With_POST_Uses_PostMethod() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.POST); + request.AddParameter("data", "value"); + var response = client.Execute(request); + + Assert.Equal("POST", fakeHttp.LastMethod); + } + + [Fact] + public void Execute_With_PUT_Uses_PutMethod() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.PUT); + client.Execute(request); + + Assert.Equal("PUT", fakeHttp.LastMethod); + } + + [Fact] + public void Execute_With_DELETE_Uses_DeleteMethod() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.DELETE); + client.Execute(request); + + Assert.Equal("DELETE", fakeHttp.LastMethod); + } + + [Fact] + public void Execute_With_HEAD_Uses_HeadMethod() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.HEAD); + client.Execute(request); + + Assert.Equal("HEAD", fakeHttp.LastMethod); + } + + [Fact] + public void Execute_With_OPTIONS_Uses_OptionsMethod() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.OPTIONS); + client.Execute(request); + + Assert.Equal("OPTIONS", fakeHttp.LastMethod); + } + + [Fact] + public void Execute_With_PATCH_Uses_PatchMethod() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.PATCH); + client.Execute(request); + + Assert.Equal("PATCH", fakeHttp.LastMethod); + } + + [Fact] + public void Execute_Configures_Headers_From_Request() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + request.AddHeader("X-Custom", "CustomValue"); + client.Execute(request); + + Assert.Contains(fakeHttp.Headers, h => h.Name == "X-Custom" && h.Value == "CustomValue"); + } + + [Fact] + public void Execute_Configures_Cookies_From_Request() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + request.AddCookie("session", "abc"); + client.Execute(request); + + Assert.Contains(fakeHttp.Cookies, c => c.Name == "session" && c.Value == "abc"); + } + + [Fact] + public void Execute_Configures_Parameters_From_Request() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.POST); + request.AddParameter("key", "val"); + client.Execute(request); + + Assert.Contains(fakeHttp.Parameters, p => p.Name == "key" && p.Value == "val"); + } + + [Fact] + public void Execute_Configures_RequestBody_From_Request() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.POST); + request.RequestFormat = DataFormat.Json; + request.AddBody(new { Name = "test" }); + client.Execute(request); + + Assert.NotNull(fakeHttp.RequestBody); + Assert.Contains("test", fakeHttp.RequestBody); + } + + [Fact] + public void Execute_Sets_UserAgent() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.UserAgent = "TestAgent/1.0"; + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + client.Execute(request); + + Assert.Equal("TestAgent/1.0", fakeHttp.UserAgent); + } + + [Fact] + public void Execute_Sets_Timeout_From_Request() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.Timeout = 5000; + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + request.Timeout = 3000; + client.Execute(request); + + Assert.Equal(3000, fakeHttp.Timeout); + } + + [Fact] + public void Execute_Sets_Timeout_From_Client_When_Request_Zero() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.Timeout = 5000; + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + client.Execute(request); + + Assert.Equal(5000, fakeHttp.Timeout); + } + + [Fact] + public void Execute_Merges_DefaultParameters() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.AddDefaultHeader("X-Default", "default-value"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + client.Execute(request); + + Assert.Contains(fakeHttp.Headers, h => h.Name == "X-Default" && h.Value == "default-value"); + } + + [Fact] + public void Execute_Request_Overrides_DefaultParameters() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.AddDefaultHeader("X-Custom", "default"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + request.AddHeader("X-Custom", "override"); + client.Execute(request); + + var customHeaders = fakeHttp.Headers.Where(h => h.Name == "X-Custom").ToList(); + Assert.Single(customHeaders); + Assert.Equal("override", customHeaders[0].Value); + } + + [Fact] + public void Execute_With_Authenticator_Authenticates_Request() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.Authenticator = new HttpBasicAuthenticator("user", "pass"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + client.Execute(request); + + Assert.Contains(fakeHttp.Headers, h => h.Name == "Authorization"); + } + + [Fact] + public void Execute_Increments_Request_Attempts() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + Assert.Equal(0, request.Attempts); + client.Execute(request); + Assert.Equal(1, request.Attempts); + } + + [Fact] + public void Execute_Handles_Exception_Sets_Error_Status() + { + var client = new RestClient("http://example.com"); + client.HttpFactory = new ThrowingHttpFactory(); + + var request = new RestRequest("api", Method.GET); + var response = client.Execute(request); + + Assert.Equal(ResponseStatus.Error, response.ResponseStatus); + Assert.NotNull(response.ErrorMessage); + Assert.NotNull(response.ErrorException); + } + + [Fact] + public void Execute_Generic_Returns_Deserialized_Data() + { + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactory(new HttpResponse + { + RawBytes = Encoding.UTF8.GetBytes("{\"Name\":\"test\",\"Value\":42}"), + ContentType = "application/json", + StatusCode = HttpStatusCode.OK, + ResponseStatus = ResponseStatus.Completed + }); + + var request = new RestRequest("api", Method.GET); + var response = client.Execute(request); + + Assert.NotNull(response.Data); + Assert.Equal("test", response.Data.Name); + Assert.Equal(42, response.Data.Value); + } + + [Fact] + public void ConvertToRestResponse_Copies_Headers() + { + var client = new RestClient("http://example.com"); + var fakeResponse = new HttpResponse + { + RawBytes = Encoding.UTF8.GetBytes("body"), + ContentType = "text/plain", + StatusCode = HttpStatusCode.OK, + ResponseStatus = ResponseStatus.Completed + }; + fakeResponse.Headers.Add(new HttpHeader { Name = "X-Test", Value = "123" }); + + client.HttpFactory = new FakeHttpFactory(fakeResponse); + var request = new RestRequest("api", Method.GET); + var response = client.Execute(request); + + Assert.Contains(response.Headers, h => h.Name == "X-Test" && h.Value.ToString() == "123"); + } + + [Fact] + public void ConvertToRestResponse_Copies_Cookies() + { + var client = new RestClient("http://example.com"); + var fakeResponse = new HttpResponse + { + RawBytes = Encoding.UTF8.GetBytes("body"), + ContentType = "text/plain", + StatusCode = HttpStatusCode.OK, + ResponseStatus = ResponseStatus.Completed + }; + fakeResponse.Cookies.Add(new HttpCookie + { + Name = "session", + Value = "abc", + Domain = "example.com", + Path = "/", + HttpOnly = true, + Secure = true + }); + + client.HttpFactory = new FakeHttpFactory(fakeResponse); + var request = new RestRequest("api", Method.GET); + var response = client.Execute(request); + + Assert.Single(response.Cookies); + Assert.Equal("session", response.Cookies[0].Name); + Assert.Equal("abc", response.Cookies[0].Value); + Assert.Equal("example.com", response.Cookies[0].Domain); + Assert.True(response.Cookies[0].HttpOnly); + Assert.True(response.Cookies[0].Secure); + } + + [Fact] + public void Execute_Sets_FollowRedirects() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.FollowRedirects = false; + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + client.Execute(request); + + Assert.False(fakeHttp.FollowRedirects); + } + + [Fact] + public void Execute_Sets_MaxRedirects() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.MaxRedirects = 3; + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + client.Execute(request); + + Assert.Equal(3, fakeHttp.MaxRedirects); + } + + [Fact] + public void Execute_Sets_CookieContainer() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + var container = new CookieContainer(); + client.CookieContainer = container; + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + client.Execute(request); + + Assert.Equal(container, fakeHttp.CookieContainer); + } + + [Fact] + public void Execute_Sets_Proxy() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + var proxy = new WebProxy("http://proxy.example.com"); + client.Proxy = proxy; + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + client.Execute(request); + + Assert.Equal(proxy, fakeHttp.Proxy); + } + + [Fact] + public void Execute_Sets_Credentials_From_Request() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.GET); + var creds = new NetworkCredential("user", "pass"); + request.Credentials = creds; + client.Execute(request); + + Assert.Equal(creds, fakeHttp.Credentials); + } + + [Fact] + public void Execute_Configures_Files_From_Request() + { + var fakeHttp = new FakeHttp(); + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactoryInstance(fakeHttp); + + var request = new RestRequest("api", Method.POST); + request.AddFile("file", new byte[] { 1, 2, 3 }, "test.bin"); + client.Execute(request); + + Assert.Single(fakeHttp.Files); + Assert.Equal("file", fakeHttp.Files[0].Name); + } + + [Fact] + public void DownloadData_Returns_RawBytes() + { + var rawData = new byte[] { 10, 20, 30 }; + var client = new RestClient("http://example.com"); + client.HttpFactory = new FakeHttpFactory(new HttpResponse + { + ContentType = "application/octet-stream", + RawBytes = rawData, + StatusCode = HttpStatusCode.OK, + ResponseStatus = ResponseStatus.Completed + }); + + var request = new RestRequest("data", Method.GET); + var result = client.DownloadData(request); + + Assert.Equal(rawData, result); + } + + public class SimpleData + { + public string Name { get; set; } + public int Value { get; set; } + } + } + + #region Fake HTTP implementations + + public class FakeHttp : IHttp + { + public string LastMethod { get; private set; } + public CookieContainer CookieContainer { get; set; } + public ICredentials Credentials { get; set; } + public string UserAgent { get; set; } + public int Timeout { get; set; } + public bool FollowRedirects { get; set; } + public System.Security.Cryptography.X509Certificates.X509CertificateCollection ClientCertificates { get; set; } + public int? MaxRedirects { get; set; } + public IList Headers { get; private set; } + public IList Parameters { get; private set; } + public IList Files { get; private set; } + public IList Cookies { get; private set; } + public string RequestBody { get; set; } + public string RequestContentType { get; set; } + public Uri Url { get; set; } + public IWebProxy Proxy { get; set; } + + public FakeHttp() + { + Headers = new List(); + Parameters = new List(); + Files = new List(); + Cookies = new List(); + } + + private HttpResponse MakeResponse(string method) + { + LastMethod = method; + return new HttpResponse { ResponseStatus = ResponseStatus.Completed, StatusCode = HttpStatusCode.OK, RawBytes = new byte[0] }; + } + + public HttpResponse Delete() => MakeResponse("DELETE"); + public HttpResponse Get() => MakeResponse("GET"); + public HttpResponse Head() => MakeResponse("HEAD"); + public HttpResponse Options() => MakeResponse("OPTIONS"); + public HttpResponse Post() => MakeResponse("POST"); + public HttpResponse Put() => MakeResponse("PUT"); + public HttpResponse Patch() => MakeResponse("PATCH"); + + public HttpWebRequest DeleteAsync(Action action) { action(MakeResponse("DELETE")); return null; } + public HttpWebRequest GetAsync(Action action) { action(MakeResponse("GET")); return null; } + public HttpWebRequest HeadAsync(Action action) { action(MakeResponse("HEAD")); return null; } + public HttpWebRequest OptionsAsync(Action action) { action(MakeResponse("OPTIONS")); return null; } + public HttpWebRequest PostAsync(Action action) { action(MakeResponse("POST")); return null; } + public HttpWebRequest PutAsync(Action action) { action(MakeResponse("PUT")); return null; } + public HttpWebRequest PatchAsync(Action action) { action(MakeResponse("PATCH")); return null; } + } + + public class FakeHttpFactory : IHttpFactory + { + private readonly HttpResponse _response; + + public FakeHttpFactory(HttpResponse response) + { + _response = response; + } + + public IHttp Create() + { + return new FakeHttpWithResponse(_response); + } + } + + public class FakeHttpWithResponse : IHttp + { + private readonly HttpResponse _response; + + public FakeHttpWithResponse(HttpResponse response) + { + _response = response; + Headers = new List(); + Parameters = new List(); + Files = new List(); + Cookies = new List(); + } + + public CookieContainer CookieContainer { get; set; } + public ICredentials Credentials { get; set; } + public string UserAgent { get; set; } + public int Timeout { get; set; } + public bool FollowRedirects { get; set; } + public System.Security.Cryptography.X509Certificates.X509CertificateCollection ClientCertificates { get; set; } + public int? MaxRedirects { get; set; } + public IList Headers { get; private set; } + public IList Parameters { get; private set; } + public IList Files { get; private set; } + public IList Cookies { get; private set; } + public string RequestBody { get; set; } + public string RequestContentType { get; set; } + public Uri Url { get; set; } + public IWebProxy Proxy { get; set; } + + public HttpResponse Delete() => _response; + public HttpResponse Get() => _response; + public HttpResponse Head() => _response; + public HttpResponse Options() => _response; + public HttpResponse Post() => _response; + public HttpResponse Put() => _response; + public HttpResponse Patch() => _response; + + public HttpWebRequest DeleteAsync(Action action) { action(_response); return null; } + public HttpWebRequest GetAsync(Action action) { action(_response); return null; } + public HttpWebRequest HeadAsync(Action action) { action(_response); return null; } + public HttpWebRequest OptionsAsync(Action action) { action(_response); return null; } + public HttpWebRequest PostAsync(Action action) { action(_response); return null; } + public HttpWebRequest PutAsync(Action action) { action(_response); return null; } + public HttpWebRequest PatchAsync(Action action) { action(_response); return null; } + } + + public class FakeHttpFactoryInstance : IHttpFactory + { + private readonly IHttp _instance; + + public FakeHttpFactoryInstance(IHttp instance) + { + _instance = instance; + } + + public IHttp Create() + { + return _instance; + } + } + + public class ThrowingHttpFactory : IHttpFactory + { + public IHttp Create() + { + return new ThrowingHttp(); + } + } + + public class ThrowingHttp : IHttp + { + public CookieContainer CookieContainer { get; set; } + public ICredentials Credentials { get; set; } + public string UserAgent { get; set; } + public int Timeout { get; set; } + public bool FollowRedirects { get; set; } + public System.Security.Cryptography.X509Certificates.X509CertificateCollection ClientCertificates { get; set; } + public int? MaxRedirects { get; set; } + public IList Headers { get; private set; } + public IList Parameters { get; private set; } + public IList Files { get; private set; } + public IList Cookies { get; private set; } + public string RequestBody { get; set; } + public string RequestContentType { get; set; } + public Uri Url { get; set; } + public IWebProxy Proxy { get; set; } + + public ThrowingHttp() + { + Headers = new List(); + Parameters = new List(); + Files = new List(); + Cookies = new List(); + } + + public HttpResponse Delete() => throw new Exception("Test exception"); + public HttpResponse Get() => throw new Exception("Test exception"); + public HttpResponse Head() => throw new Exception("Test exception"); + public HttpResponse Options() => throw new Exception("Test exception"); + public HttpResponse Post() => throw new Exception("Test exception"); + public HttpResponse Put() => throw new Exception("Test exception"); + public HttpResponse Patch() => throw new Exception("Test exception"); + + public HttpWebRequest DeleteAsync(Action action) => throw new Exception("Test exception"); + public HttpWebRequest GetAsync(Action action) => throw new Exception("Test exception"); + public HttpWebRequest HeadAsync(Action action) => throw new Exception("Test exception"); + public HttpWebRequest OptionsAsync(Action action) => throw new Exception("Test exception"); + public HttpWebRequest PostAsync(Action action) => throw new Exception("Test exception"); + public HttpWebRequest PutAsync(Action action) => throw new Exception("Test exception"); + public HttpWebRequest PatchAsync(Action action) => throw new Exception("Test exception"); + } + + #endregion +} diff --git a/RestSharp.Tests.Coverage/RestClientTests.cs b/RestSharp.Tests.Coverage/RestClientTests.cs new file mode 100644 index 000000000..087dff31d --- /dev/null +++ b/RestSharp.Tests.Coverage/RestClientTests.cs @@ -0,0 +1,288 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using RestSharp; +using RestSharp.Deserializers; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class RestClientTests + { + [Fact] + public void Default_Constructor_Sets_Default_UserAgent() + { + var client = new RestClient(); + Assert.NotNull(client.UserAgent); + Assert.StartsWith("RestSharp", client.UserAgent); + } + + [Fact] + public void Default_Constructor_Sets_FollowRedirects_True() + { + var client = new RestClient(); + Assert.True(client.FollowRedirects); + } + + [Fact] + public void Constructor_With_BaseUrl_Sets_BaseUrl() + { + var client = new RestClient("http://example.com"); + Assert.Equal("http://example.com", client.BaseUrl); + } + + [Fact] + public void BaseUrl_Strips_Trailing_Slash() + { + var client = new RestClient(); + client.BaseUrl = "http://example.com/"; + Assert.Equal("http://example.com", client.BaseUrl); + } + + [Fact] + public void DefaultParameters_Is_Not_Null() + { + var client = new RestClient(); + Assert.NotNull(client.DefaultParameters); + } + + [Fact] + public void AddDefaultParameter_Adds_To_DefaultParameters() + { + var client = new RestClient(); + client.AddDefaultParameter("key", "value"); + Assert.Single(client.DefaultParameters); + Assert.Equal("key", client.DefaultParameters[0].Name); + } + + [Fact] + public void AddDefaultParameter_With_Type_Adds_Correct_Type() + { + var client = new RestClient(); + client.AddDefaultParameter("X-Custom", "value", ParameterType.HttpHeader); + Assert.Equal(ParameterType.HttpHeader, client.DefaultParameters[0].Type); + } + + [Fact] + public void AddDefaultParameter_RequestBody_Throws() + { + var client = new RestClient(); + var p = new Parameter { Name = "body", Value = "data", Type = ParameterType.RequestBody }; + Assert.Throws(() => client.AddDefaultParameter(p)); + } + + [Fact] + public void AddDefaultHeader_Adds_HttpHeader() + { + var client = new RestClient(); + client.AddDefaultHeader("Authorization", "Bearer token"); + var param = client.DefaultParameters.First(); + Assert.Equal("Authorization", param.Name); + Assert.Equal(ParameterType.HttpHeader, param.Type); + } + + [Fact] + public void AddDefaultUrlSegment_Adds_UrlSegment() + { + var client = new RestClient(); + client.AddDefaultUrlSegment("version", "v2"); + var param = client.DefaultParameters.First(); + Assert.Equal("version", param.Name); + Assert.Equal(ParameterType.UrlSegment, param.Type); + } + + [Fact] + public void AddHandler_Registers_Deserializer() + { + var client = new RestClient(); + client.ClearHandlers(); + client.AddHandler("text/csv", new JsonDeserializer()); + // Verify by building a URI (handlers are used internally) + Assert.NotNull(client); + } + + [Fact] + public void RemoveHandler_Removes_Handler() + { + var client = new RestClient(); + client.RemoveHandler("application/json"); + // Should not throw and handler should be removed + Assert.NotNull(client); + } + + [Fact] + public void ClearHandlers_Removes_All_Handlers() + { + var client = new RestClient(); + client.ClearHandlers(); + Assert.NotNull(client); + } + + [Fact] + public void Timeout_Can_Be_Set() + { + var client = new RestClient(); + client.Timeout = 30000; + Assert.Equal(30000, client.Timeout); + } + + [Fact] + public void Authenticator_Can_Be_Set() + { + var client = new RestClient(); + var auth = new HttpBasicAuthenticator("user", "pass"); + client.Authenticator = auth; + Assert.Equal(auth, client.Authenticator); + } + + [Fact] + public void CookieContainer_Can_Be_Set() + { + var client = new RestClient(); + var container = new CookieContainer(); + client.CookieContainer = container; + Assert.Equal(container, client.CookieContainer); + } + + [Fact] + public void MaxRedirects_Can_Be_Set() + { + var client = new RestClient(); + client.MaxRedirects = 10; + Assert.Equal(10, client.MaxRedirects); + } + + [Fact] + public void UserAgent_Can_Be_Set() + { + var client = new RestClient(); + client.UserAgent = "Custom/1.0"; + Assert.Equal("Custom/1.0", client.UserAgent); + } + + [Fact] + public void UseSynchronizationContext_Default_Is_False() + { + var client = new RestClient(); + Assert.False(client.UseSynchronizationContext); + } + + [Fact] + public void BuildUri_With_BaseUrl_And_Resource() + { + var client = new RestClient("http://example.com"); + var request = new RestRequest("api/users"); + var uri = client.BuildUri(request); + Assert.Equal("http://example.com/api/users", uri.ToString()); + } + + [Fact] + public void BuildUri_With_Empty_Resource() + { + var client = new RestClient("http://example.com"); + var request = new RestRequest(); + var uri = client.BuildUri(request); + Assert.Equal("http://example.com/", uri.ToString()); + } + + [Fact] + public void BuildUri_With_QueryString_For_GET() + { + var client = new RestClient("http://example.com"); + var request = new RestRequest("api/search", Method.GET); + request.AddParameter("q", "test"); + var uri = client.BuildUri(request); + Assert.Contains("q=test", uri.ToString()); + } + + [Fact] + public void BuildUri_Does_Not_Add_QueryString_For_POST() + { + var client = new RestClient("http://example.com"); + var request = new RestRequest("api/users", Method.POST); + request.AddParameter("name", "test"); + var uri = client.BuildUri(request); + Assert.DoesNotContain("?", uri.ToString()); + } + + [Fact] + public void BuildUri_With_UrlSegment_Replaces_Token() + { + var client = new RestClient("http://example.com"); + var request = new RestRequest("users/{id}"); + request.AddUrlSegment("id", "42"); + var uri = client.BuildUri(request); + Assert.Equal("http://example.com/users/42", uri.ToString()); + } + + [Fact] + public void BuildUri_Strips_Leading_Slash_From_Resource() + { + var client = new RestClient("http://example.com"); + var request = new RestRequest("/api/users"); + var uri = client.BuildUri(request); + Assert.Equal("http://example.com/api/users", uri.ToString()); + } + + [Fact] + public void BuildUri_Multiple_QueryString_Params() + { + var client = new RestClient("http://example.com"); + var request = new RestRequest("search", Method.GET); + request.AddParameter("q", "test"); + request.AddParameter("page", "1"); + var uri = client.BuildUri(request); + Assert.Contains("q=test", uri.ToString()); + Assert.Contains("page=1", uri.ToString()); + } + + [Fact] + public void BuildUri_With_DELETE_Includes_QueryString() + { + var client = new RestClient("http://example.com"); + var request = new RestRequest("items", Method.DELETE); + request.AddParameter("id", "5"); + var uri = client.BuildUri(request); + Assert.Contains("id=5", uri.ToString()); + } + + [Fact] + public void BuildUri_With_HEAD_Includes_QueryString() + { + var client = new RestClient("http://example.com"); + var request = new RestRequest("items", Method.HEAD); + request.AddParameter("id", "5"); + var uri = client.BuildUri(request); + Assert.Contains("id=5", uri.ToString()); + } + + [Fact] + public void BuildUri_With_OPTIONS_Includes_QueryString() + { + var client = new RestClient("http://example.com"); + var request = new RestRequest("items", Method.OPTIONS); + request.AddParameter("id", "5"); + var uri = client.BuildUri(request); + Assert.Contains("id=5", uri.ToString()); + } + + [Fact] + public void ClientCertificates_Can_Be_Set() + { + var client = new RestClient(); + var certs = new System.Security.Cryptography.X509Certificates.X509CertificateCollection(); + client.ClientCertificates = certs; + Assert.Equal(certs, client.ClientCertificates); + } + + [Fact] + public void Proxy_Can_Be_Set() + { + var client = new RestClient(); + var proxy = new WebProxy("http://proxy.example.com:8080"); + client.Proxy = proxy; + Assert.Equal(proxy, client.Proxy); + } + } +} diff --git a/RestSharp.Tests.Coverage/RestRequestTests.cs b/RestSharp.Tests.Coverage/RestRequestTests.cs new file mode 100644 index 000000000..1776c4aff --- /dev/null +++ b/RestSharp.Tests.Coverage/RestRequestTests.cs @@ -0,0 +1,343 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using RestSharp; +using RestSharp.Serializers; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class RestRequestTests + { + [Fact] + public void Default_Constructor_Sets_Method_To_GET() + { + var request = new RestRequest(); + Assert.Equal(Method.GET, request.Method); + } + + [Fact] + public void Constructor_With_Method_Sets_Method() + { + var request = new RestRequest(Method.POST); + Assert.Equal(Method.POST, request.Method); + } + + [Fact] + public void Constructor_With_Resource_Sets_Resource() + { + var request = new RestRequest("resource/path"); + Assert.Equal("resource/path", request.Resource); + } + + [Fact] + public void Constructor_With_Resource_And_Method_Sets_Both() + { + var request = new RestRequest("resource/path", Method.PUT); + Assert.Equal("resource/path", request.Resource); + Assert.Equal(Method.PUT, request.Method); + } + + [Fact] + public void Constructor_With_Uri_Sets_Resource() + { + var uri = new Uri("resource/path", UriKind.Relative); + var request = new RestRequest(uri); + Assert.Equal("resource/path", request.Resource); + } + + [Fact] + public void Constructor_With_Uri_And_Method_Sets_Both() + { + var uri = new Uri("resource/path", UriKind.Relative); + var request = new RestRequest(uri, Method.DELETE); + Assert.Equal("resource/path", request.Resource); + Assert.Equal(Method.DELETE, request.Method); + } + + [Fact] + public void Default_Request_Format_Is_Xml() + { + var request = new RestRequest(); + Assert.Equal(DataFormat.Xml, request.RequestFormat); + } + + [Fact] + public void AddParameter_String_Name_Value_Adds_GetOrPost() + { + var request = new RestRequest(); + request.AddParameter("name", "value"); + var param = request.Parameters.First(); + + Assert.Equal("name", param.Name); + Assert.Equal("value", param.Value); + Assert.Equal(ParameterType.GetOrPost, param.Type); + } + + [Fact] + public void AddParameter_With_Type_Adds_Correct_Type() + { + var request = new RestRequest(); + request.AddParameter("name", "value", ParameterType.HttpHeader); + var param = request.Parameters.First(); + + Assert.Equal(ParameterType.HttpHeader, param.Type); + } + + [Fact] + public void AddParameter_Object_Adds_To_Collection() + { + var request = new RestRequest(); + var p = new Parameter { Name = "test", Value = "val", Type = ParameterType.Cookie }; + request.AddParameter(p); + + Assert.Contains(p, request.Parameters); + } + + [Fact] + public void AddHeader_Adds_HttpHeader_Parameter() + { + var request = new RestRequest(); + request.AddHeader("Content-Type", "application/json"); + + var param = request.Parameters.First(); + Assert.Equal("Content-Type", param.Name); + Assert.Equal("application/json", param.Value); + Assert.Equal(ParameterType.HttpHeader, param.Type); + } + + [Fact] + public void AddCookie_Adds_Cookie_Parameter() + { + var request = new RestRequest(); + request.AddCookie("session", "abc123"); + + var param = request.Parameters.First(); + Assert.Equal("session", param.Name); + Assert.Equal("abc123", param.Value); + Assert.Equal(ParameterType.Cookie, param.Type); + } + + [Fact] + public void AddUrlSegment_Adds_UrlSegment_Parameter() + { + var request = new RestRequest("users/{id}"); + request.AddUrlSegment("id", "42"); + + var param = request.Parameters.First(); + Assert.Equal("id", param.Name); + Assert.Equal("42", param.Value); + Assert.Equal(ParameterType.UrlSegment, param.Type); + } + + [Fact] + public void AddBody_Json_Adds_RequestBody_Parameter() + { + var request = new RestRequest(); + request.RequestFormat = DataFormat.Json; + request.AddBody(new { Name = "test", Value = 42 }); + + var body = request.Parameters.FirstOrDefault(p => p.Type == ParameterType.RequestBody); + Assert.NotNull(body); + Assert.Contains("test", body.Value.ToString()); + } + + [Fact] + public void AddBody_Xml_Adds_RequestBody_Parameter() + { + var request = new RestRequest(); + request.RequestFormat = DataFormat.Xml; + request.AddBody(new TestObj { Name = "test" }); + + var body = request.Parameters.FirstOrDefault(p => p.Type == ParameterType.RequestBody); + Assert.NotNull(body); + Assert.Contains("test", body.Value.ToString()); + } + + [Fact] + public void AddBody_With_Namespace_Sets_Xml_Namespace() + { + var request = new RestRequest(); + request.RequestFormat = DataFormat.Xml; + request.AddBody(new TestObj { Name = "test" }, "http://example.com/ns"); + + var body = request.Parameters.FirstOrDefault(p => p.Type == ParameterType.RequestBody); + Assert.NotNull(body); + } + + [Fact] + public void AddObject_Adds_Public_Properties_As_Parameters() + { + var request = new RestRequest(); + var obj = new { Name = "John", Age = 30 }; + request.AddObject(obj); + + Assert.Equal(2, request.Parameters.Count); + Assert.Equal("Name", request.Parameters[0].Name); + Assert.Equal("John", request.Parameters[0].Value); + Assert.Equal("Age", request.Parameters[1].Name); + Assert.Equal(30, request.Parameters[1].Value); + } + + [Fact] + public void AddObject_With_Whitelist_Only_Adds_Listed_Properties() + { + var request = new RestRequest(); + var obj = new TestObj { Name = "John", Value = 30 }; + request.AddObject(obj, "Name"); + + Assert.Single(request.Parameters); + Assert.Equal("Name", request.Parameters[0].Name); + } + + [Fact] + public void AddObject_Skips_Null_Values() + { + var request = new RestRequest(); + var obj = new TestObj { Name = null, Value = 42 }; + request.AddObject(obj); + + Assert.DoesNotContain(request.Parameters, p => p.Name == "Name"); + Assert.Contains(request.Parameters, p => p.Name == "Value"); + } + + [Fact] + public void AddFile_From_Bytes_Adds_To_Files_Collection() + { + var request = new RestRequest(); + request.AddFile("file", new byte[] { 1, 2, 3 }, "test.txt"); + + Assert.Single(request.Files); + Assert.Equal("file", request.Files[0].Name); + Assert.Equal("test.txt", request.Files[0].FileName); + } + + [Fact] + public void AddFile_From_Bytes_With_ContentType_Sets_ContentType() + { + var request = new RestRequest(); + request.AddFile("file", new byte[] { 1, 2, 3 }, "test.txt", "text/plain"); + + Assert.Equal("text/plain", request.Files[0].ContentType); + } + + [Fact] + public void IncreaseNumAttempts_Increments_Attempts() + { + var request = new RestRequest(); + Assert.Equal(0, request.Attempts); + + request.IncreaseNumAttempts(); + Assert.Equal(1, request.Attempts); + + request.IncreaseNumAttempts(); + Assert.Equal(2, request.Attempts); + } + + [Fact] + public void Request_Has_Empty_Parameters_By_Default() + { + var request = new RestRequest(); + Assert.NotNull(request.Parameters); + Assert.Empty(request.Parameters); + } + + [Fact] + public void Request_Has_Empty_Files_By_Default() + { + var request = new RestRequest(); + Assert.NotNull(request.Files); + Assert.Empty(request.Files); + } + + [Fact] + public void OnBeforeDeserialization_Can_Be_Set_And_Invoked() + { + var request = new RestRequest(); + bool callbackInvoked = false; + request.OnBeforeDeserialization = r => { callbackInvoked = true; }; + + var response = new RestResponse { Content = "test" }; + request.OnBeforeDeserialization(response); + + Assert.True(callbackInvoked); + } + + [Fact] + public void Request_Timeout_Can_Be_Set() + { + var request = new RestRequest(); + request.Timeout = 5000; + Assert.Equal(5000, request.Timeout); + } + + [Fact] + public void Request_DateFormat_Can_Be_Set() + { + var request = new RestRequest(); + request.DateFormat = "yyyy-MM-dd"; + Assert.Equal("yyyy-MM-dd", request.DateFormat); + } + + [Fact] + public void Request_XmlNamespace_Can_Be_Set() + { + var request = new RestRequest(); + request.XmlNamespace = "http://example.com"; + Assert.Equal("http://example.com", request.XmlNamespace); + } + + [Fact] + public void Request_RootElement_Can_Be_Set() + { + var request = new RestRequest(); + request.RootElement = "data"; + Assert.Equal("data", request.RootElement); + } + + [Fact] + public void Request_Credentials_Can_Be_Set() + { + var request = new RestRequest(); + var creds = new System.Net.NetworkCredential("user", "pass"); + request.Credentials = creds; + Assert.Equal(creds, request.Credentials); + } + + [Fact] + public void Request_JsonSerializer_Has_Default() + { + var request = new RestRequest(); + Assert.NotNull(request.JsonSerializer); + } + + [Fact] + public void Request_XmlSerializer_Has_Default() + { + var request = new RestRequest(); + Assert.NotNull(request.XmlSerializer); + } + + [Fact] + public void AddObject_With_Array_Property_Joins_Values() + { + var request = new RestRequest(); + var obj = new ObjectWithArray { Tags = new[] { "a", "b", "c" } }; + request.AddObject(obj); + + var param = request.Parameters.First(p => p.Name == "Tags"); + Assert.Equal("a,b,c", param.Value); + } + + private class TestObj + { + public string Name { get; set; } + public int Value { get; set; } + } + + private class ObjectWithArray + { + public string[] Tags { get; set; } + } + } +} diff --git a/RestSharp.Tests.Coverage/RestSharp.Tests.Coverage.csproj b/RestSharp.Tests.Coverage/RestSharp.Tests.Coverage.csproj new file mode 100644 index 000000000..01733cd61 --- /dev/null +++ b/RestSharp.Tests.Coverage/RestSharp.Tests.Coverage.csproj @@ -0,0 +1,37 @@ + + + + net7.0 + disable + disable + FRAMEWORK + CS0618;CS0672;CS0162;CS0168;CS0219;CS8600;CS8601;CS8602;CS8603;CS8604;CS8625;CS0108;CS0114;CS0579;CS0436;CS8981 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RestSharp.Tests.Coverage/SerializerCoverageTests.cs b/RestSharp.Tests.Coverage/SerializerCoverageTests.cs new file mode 100644 index 000000000..5692a1d44 --- /dev/null +++ b/RestSharp.Tests.Coverage/SerializerCoverageTests.cs @@ -0,0 +1,475 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using RestSharp.Serializers; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class JsonSerializerTests + { + [Fact] + public void Serialize_Simple_Object() + { + var serializer = new JsonSerializer(); + var result = serializer.Serialize(new { Name = "test", Value = 42 }); + Assert.Contains("test", result); + Assert.Contains("42", result); + } + + [Fact] + public void ContentType_Defaults_To_ApplicationJson() + { + var serializer = new JsonSerializer(); + Assert.Equal("application/json", serializer.ContentType); + } + + [Fact] + public void ContentType_Can_Be_Set() + { + var serializer = new JsonSerializer(); + serializer.ContentType = "text/json"; + Assert.Equal("text/json", serializer.ContentType); + } + + [Fact] + public void Serialize_List_Object() + { + var serializer = new JsonSerializer(); + var result = serializer.Serialize(new List { "a", "b", "c" }); + Assert.Contains("a", result); + Assert.Contains("b", result); + } + + [Fact] + public void Serialize_Null_Returns_Null_Json() + { + var serializer = new JsonSerializer(); + var result = serializer.Serialize(null); + Assert.Equal("null", result); + } + + [Fact] + public void Serialize_Complex_Object() + { + var serializer = new JsonSerializer(); + var obj = new SerializableObj + { + Name = "Foo", + Age = 25, + Items = new List { "x", "y" } + }; + var result = serializer.Serialize(obj); + Assert.Contains("Foo", result); + Assert.Contains("25", result); + } + + [Fact] + public void RootElement_Can_Be_Set() + { + var serializer = new JsonSerializer(); + serializer.RootElement = "data"; + Assert.Equal("data", serializer.RootElement); + } + + [Fact] + public void Namespace_Can_Be_Set() + { + var serializer = new JsonSerializer(); + serializer.Namespace = "ns"; + Assert.Equal("ns", serializer.Namespace); + } + + [Fact] + public void DateFormat_Can_Be_Set() + { + var serializer = new JsonSerializer(); + serializer.DateFormat = "yyyy-MM-dd"; + Assert.Equal("yyyy-MM-dd", serializer.DateFormat); + } + } + + public class XmlSerializerCoverageTests + { + [Fact] + public void Default_ContentType_Is_ApplicationXml() + { + var serializer = new XmlSerializer(); + Assert.Equal("text/xml", serializer.ContentType); + } + + [Fact] + public void Serialize_Simple_Object() + { + var serializer = new XmlSerializer(); + var result = serializer.Serialize(new SimpleXmlObj { Name = "test", Value = 42 }); + Assert.Contains("test", result); + Assert.Contains("42", result); + } + + [Fact] + public void RootElement_Override() + { + var serializer = new XmlSerializer(); + serializer.RootElement = "CustomRoot"; + var result = serializer.Serialize(new SimpleXmlObj { Name = "test" }); + Assert.Contains("CustomRoot", result); + } + + [Fact] + public void Namespace_Is_Applied() + { + var serializer = new XmlSerializer(); + serializer.Namespace = "http://example.com/ns"; + var result = serializer.Serialize(new SimpleXmlObj { Name = "test" }); + Assert.Contains("http://example.com/ns", result); + } + + [Fact] + public void Serialize_With_Nullable_Property() + { + var serializer = new XmlSerializer(); + var result = serializer.Serialize(new ObjWithNullable { Value = null, Name = "test" }); + Assert.Contains("test", result); + } + + [Fact] + public void Serialize_With_Nullable_Property_Set() + { + var serializer = new XmlSerializer(); + var result = serializer.Serialize(new ObjWithNullable { Value = 42, Name = "test" }); + Assert.Contains("42", result); + } + + [Fact] + public void Serialize_With_DateTime() + { + var serializer = new XmlSerializer(); + var result = serializer.Serialize(new ObjWithDate { Date = new DateTime(2023, 6, 15) }); + Assert.Contains("2023", result); + } + + [Fact] + public void Serialize_With_DateFormat() + { + var serializer = new XmlSerializer(); + serializer.DateFormat = "yyyy-MM-dd"; + var result = serializer.Serialize(new ObjWithDate { Date = new DateTime(2023, 6, 15) }); + Assert.Contains("2023-06-15", result); + } + + [Fact] + public void Serialize_With_Enum() + { + var serializer = new XmlSerializer(); + var result = serializer.Serialize(new ObjWithEnum { Status = Method.POST }); + Assert.Contains("POST", result); + } + + [Fact] + public void Serialize_With_Guid() + { + var serializer = new XmlSerializer(); + var guid = Guid.NewGuid(); + var result = serializer.Serialize(new ObjWithGuid { Id = guid }); + Assert.Contains(guid.ToString(), result); + } + + [Fact] + public void Serialize_With_Boolean() + { + var serializer = new XmlSerializer(); + var result = serializer.Serialize(new ObjWithBool { Active = true, Disabled = false }); + Assert.Contains("true", result.ToLower()); + Assert.Contains("false", result.ToLower()); + } + + [Fact] + public void Serialize_With_List() + { + var serializer = new XmlSerializer(); + var obj = new ObjWithList + { + Name = "parent", + Items = new List + { + new SimpleXmlObj { Name = "child1", Value = 1 }, + new SimpleXmlObj { Name = "child2", Value = 2 } + } + }; + var result = serializer.Serialize(obj); + Assert.Contains("child1", result); + Assert.Contains("child2", result); + } + + [Fact] + public void Serialize_With_Decimal() + { + var serializer = new XmlSerializer(); + var result = serializer.Serialize(new ObjWithDecimal { Price = 19.95m }); + Assert.Contains("19.95", result); + } + + [Fact] + public void Serialize_With_Double() + { + var serializer = new XmlSerializer(); + var result = serializer.Serialize(new ObjWithDouble { Rate = 3.14 }); + Assert.Contains("3.14", result); + } + + [Fact] + public void Serialize_With_Long() + { + var serializer = new XmlSerializer(); + var result = serializer.Serialize(new ObjWithLong { BigNum = long.MaxValue }); + Assert.Contains(long.MaxValue.ToString(), result); + } + + [Fact] + public void Serialize_With_TimeSpan() + { + var serializer = new XmlSerializer(); + var result = serializer.Serialize(new ObjWithTimeSpan { Duration = TimeSpan.FromHours(1) }); + Assert.NotNull(result); + } + + [Fact] + public void ContentType_Can_Be_Set() + { + var serializer = new XmlSerializer(); + serializer.ContentType = "application/xml"; + Assert.Equal("application/xml", serializer.ContentType); + } + } + + public class DotNetXmlSerializerTests + { + [Fact] + public void Default_ContentType_Is_ApplicationXml() + { + var serializer = new DotNetXmlSerializer(); + Assert.Equal("application/xml", serializer.ContentType); + } + + [Fact] + public void Constructor_With_Namespace() + { + var serializer = new DotNetXmlSerializer("http://example.com"); + Assert.Equal("http://example.com", serializer.Namespace); + } + + [Fact] + public void Serialize_Simple_Object() + { + var serializer = new DotNetXmlSerializer(); + var result = serializer.Serialize(new SimpleXmlObj { Name = "test", Value = 42 }); + Assert.Contains("test", result); + Assert.Contains("42", result); + } + + [Fact] + public void Encoding_Defaults_To_UTF8() + { + var serializer = new DotNetXmlSerializer(); + Assert.Equal(System.Text.Encoding.UTF8, serializer.Encoding); + } + + [Fact] + public void Properties_Can_Be_Set() + { + var serializer = new DotNetXmlSerializer(); + serializer.RootElement = "Root"; + serializer.DateFormat = "yyyy-MM-dd"; + serializer.ContentType = "text/xml"; + + Assert.Equal("Root", serializer.RootElement); + Assert.Equal("yyyy-MM-dd", serializer.DateFormat); + Assert.Equal("text/xml", serializer.ContentType); + } + } + + public class DotNetXmlDeserializerTests + { + [Fact] + public void Deserialize_Simple_Object() + { + var deserializer = new RestSharp.Deserializers.DotNetXmlDeserializer(); + var xml = "test42"; + var response = new RestResponse { Content = xml }; + var result = deserializer.Deserialize(response); + + Assert.Equal("test", result.Name); + Assert.Equal(42, result.Value); + } + + [Fact] + public void Deserialize_Empty_Content_Returns_Default() + { + var deserializer = new RestSharp.Deserializers.DotNetXmlDeserializer(); + var response = new RestResponse { Content = "" }; + var result = deserializer.Deserialize(response); + + Assert.Null(result); + } + + [Fact] + public void Deserialize_Null_Content_Returns_Default() + { + var deserializer = new RestSharp.Deserializers.DotNetXmlDeserializer(); + var response = new RestResponse { Content = null }; + var result = deserializer.Deserialize(response); + + Assert.Null(result); + } + + [Fact] + public void Properties_Can_Be_Set() + { + var deserializer = new RestSharp.Deserializers.DotNetXmlDeserializer(); + deserializer.DateFormat = "yyyy-MM-dd"; + deserializer.Namespace = "http://example.com"; + deserializer.RootElement = "data"; + + Assert.Equal("yyyy-MM-dd", deserializer.DateFormat); + Assert.Equal("http://example.com", deserializer.Namespace); + Assert.Equal("data", deserializer.RootElement); + } + } + + public class SerializeAsAttributeTests + { + [Fact] + public void Default_NameStyle_Is_AsIs() + { + var attr = new SerializeAsAttribute(); + Assert.Equal(NameStyle.AsIs, attr.NameStyle); + } + + [Fact] + public void Default_Index_Is_MaxValue() + { + var attr = new SerializeAsAttribute(); + Assert.Equal(int.MaxValue, attr.Index); + } + + [Fact] + public void TransformName_AsIs_Returns_Input() + { + var attr = new SerializeAsAttribute(); + Assert.Equal("TestName", attr.TransformName("TestName")); + } + + [Fact] + public void TransformName_CamelCase_Transforms() + { + var attr = new SerializeAsAttribute { NameStyle = NameStyle.CamelCase }; + var result = attr.TransformName("TestName"); + Assert.Equal("testName", result); + } + + [Fact] + public void TransformName_PascalCase_Transforms() + { + var attr = new SerializeAsAttribute { NameStyle = NameStyle.PascalCase }; + var result = attr.TransformName("test_name"); + Assert.Equal("TestName", result); + } + + [Fact] + public void TransformName_LowerCase_Transforms() + { + var attr = new SerializeAsAttribute { NameStyle = NameStyle.LowerCase }; + var result = attr.TransformName("TestName"); + Assert.Equal("testname", result); + } + + [Fact] + public void TransformName_With_Custom_Name() + { + var attr = new SerializeAsAttribute { Name = "custom", NameStyle = NameStyle.LowerCase }; + var result = attr.TransformName("OriginalName"); + Assert.Equal("custom", result); + } + + [Fact] + public void Attribute_Property_Can_Be_Set() + { + var attr = new SerializeAsAttribute { Attribute = true }; + Assert.True(attr.Attribute); + } + + [Fact] + public void Culture_Defaults_To_InvariantCulture() + { + var attr = new SerializeAsAttribute(); + Assert.Equal(CultureInfo.InvariantCulture, attr.Culture); + } + } + + public class SimpleXmlObj + { + public string Name { get; set; } + public int Value { get; set; } + } + + public class ObjWithNullable + { + public string Name { get; set; } + public int? Value { get; set; } + } + + public class ObjWithDate + { + public DateTime Date { get; set; } + } + + public class ObjWithEnum + { + public Method Status { get; set; } + } + + public class ObjWithGuid + { + public Guid Id { get; set; } + } + + public class ObjWithBool + { + public bool Active { get; set; } + public bool Disabled { get; set; } + } + + public class ObjWithList + { + public string Name { get; set; } + public List Items { get; set; } + } + + public class ObjWithDecimal + { + public decimal Price { get; set; } + } + + public class ObjWithDouble + { + public double Rate { get; set; } + } + + public class ObjWithLong + { + public long BigNum { get; set; } + } + + public class ObjWithTimeSpan + { + public TimeSpan Duration { get; set; } + } + + public class SerializableObj + { + public string Name { get; set; } + public int Age { get; set; } + public List Items { get; set; } + } +} diff --git a/RestSharp.Tests.Coverage/SmokeTest.cs b/RestSharp.Tests.Coverage/SmokeTest.cs new file mode 100644 index 000000000..0094dd4a3 --- /dev/null +++ b/RestSharp.Tests.Coverage/SmokeTest.cs @@ -0,0 +1,14 @@ +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class SmokeTest + { + [Fact] + public void Smoke_RestClient_Can_Be_Created() + { + var client = new RestClient("http://example.com"); + Assert.Equal("http://example.com", client.BaseUrl); + } + } +} diff --git a/RestSharp.Tests.Coverage/ValidationTests.cs b/RestSharp.Tests.Coverage/ValidationTests.cs new file mode 100644 index 000000000..9aaa11468 --- /dev/null +++ b/RestSharp.Tests.Coverage/ValidationTests.cs @@ -0,0 +1,85 @@ +using System; +using RestSharp.Validation; +using Xunit; + +namespace RestSharp.Tests.Coverage +{ + public class ValidateTests + { + [Fact] + public void IsBetween_Does_Not_Throw_For_Valid_Value() + { + Validate.IsBetween(5, 1, 10); + } + + [Fact] + public void IsBetween_At_Min_Boundary_Does_Not_Throw() + { + Validate.IsBetween(1, 1, 10); + } + + [Fact] + public void IsBetween_At_Max_Boundary_Does_Not_Throw() + { + Validate.IsBetween(10, 1, 10); + } + + [Fact] + public void IsBetween_Below_Min_Throws() + { + Assert.Throws(() => Validate.IsBetween(0, 1, 10)); + } + + [Fact] + public void IsBetween_Above_Max_Throws() + { + Assert.Throws(() => Validate.IsBetween(11, 1, 10)); + } + + [Fact] + public void IsValidLength_Does_Not_Throw_For_Valid_String() + { + Validate.IsValidLength("hello", 10); + } + + [Fact] + public void IsValidLength_At_Max_Does_Not_Throw() + { + Validate.IsValidLength("hello", 5); + } + + [Fact] + public void IsValidLength_Over_Max_Throws() + { + Assert.Throws(() => Validate.IsValidLength("hello world", 5)); + } + + [Fact] + public void IsValidLength_Null_Does_Not_Throw() + { + Validate.IsValidLength(null, 5); + } + } + + public class RequireTests + { + [Fact] + public void Argument_Does_Not_Throw_For_NonNull() + { + Require.Argument("param", "value"); + } + + [Fact] + public void Argument_Throws_For_Null() + { + Assert.Throws(() => Require.Argument("param", null)); + } + + [Fact] + public void Argument_Throws_With_Correct_ParamName() + { + var ex = Assert.Throws(() => Require.Argument("myParam", null)); + Assert.Contains("myParam", ex.ParamName); + } + } +}