diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b8f0e4d..a6a88e3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,14 +9,14 @@ on: jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 2.1.811 + dotnet-version: 3.1.426 - name: install altcover run: dotnet tool install --global altcover.visualizer --version 8.6.14 - name: Install dependencies @@ -24,7 +24,7 @@ jobs: - name: Build run: dotnet build --configuration Release --no-restore - name: Test - run: dotnet test TeaUnitTests/ /p:AltCover=true + run: dotnet test DarabonbaUnitTests/ /p:AltCover=true env: CA: ${{ secrets.CA }} - name: CodeCov diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 133af91..0000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: csharp -sudo: true -mono: none -dotnet: 2.2 -dist: xenial -branches: - only: - - master - -install: - - dotnet tool install --global altcover.visualizer - - dotnet restore - - dotnet build - -script: - - dotnet test TeaUnitTests/ /p:AltCover=true - -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/Tea/Attributes/NameInMapAttribute.cs b/Darabonba/Attributes/NameInMapAttribute.cs similarity index 91% rename from Tea/Attributes/NameInMapAttribute.cs rename to Darabonba/Attributes/NameInMapAttribute.cs index 43699f1..e7fb851 100644 --- a/Tea/Attributes/NameInMapAttribute.cs +++ b/Darabonba/Attributes/NameInMapAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Tea +namespace Darabonba { public class NameInMapAttribute : Attribute { diff --git a/Tea/Attributes/ValidationAttribute.cs b/Darabonba/Attributes/ValidationAttribute.cs similarity index 94% rename from Tea/Attributes/ValidationAttribute.cs rename to Darabonba/Attributes/ValidationAttribute.cs index 0673616..3184a43 100644 --- a/Tea/Attributes/ValidationAttribute.cs +++ b/Darabonba/Attributes/ValidationAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Tea +namespace Darabonba { public class ValidationAttribute : Attribute { diff --git a/Tea/TeaCore.cs b/Darabonba/Core.cs similarity index 55% rename from Tea/TeaCore.cs rename to Darabonba/Core.cs index 45b3c71..58be0a6 100644 --- a/Tea/TeaCore.cs +++ b/Darabonba/Core.cs @@ -10,21 +10,26 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Darabonba.RetryPolicy; +using Darabonba.Exceptions; +using Darabonba.Utils; -using Tea.Utils; - -namespace Tea +namespace Darabonba { - public class TeaCore + public class Core { private static readonly int bufferLength = 1024; + private static readonly int DefaultMaxAttempts = 3; private static List bodyMethod = new List { "POST", "PUT", "PATCH" }; + private static readonly TimeSpan DefaultMinDelay = TimeSpan.FromMilliseconds(100); + private static readonly TimeSpan DefaultMaxDelay = TimeSpan.FromSeconds(120); + - public static string ComposeUrl(TeaRequest request) + internal static string ComposeUrl(Request request) { var urlBuilder = new StringBuilder(""); - urlBuilder.Append(TeaConverter.StrToLower(request.Protocol)).Append("://"); + urlBuilder.Append(ConverterUtil.StrToLower(request.Protocol)).Append("://"); urlBuilder.Append(DictUtils.GetDicValue(request.Headers, "host")); if (request.Port > 0) { @@ -63,12 +68,12 @@ public static string ComposeUrl(TeaRequest request) return urlBuilder.ToString().TrimEnd('?').TrimEnd('&'); } - public static TeaResponse DoAction(TeaRequest request) + public static Response DoAction(Request request) { return DoAction(request, new Dictionary()); } - public static TeaResponse DoAction(TeaRequest request, Dictionary runtimeOptions) + public static Response DoAction(Request request, Dictionary runtimeOptions) { int timeout; var url = ComposeUrl(request); @@ -77,22 +82,22 @@ public static TeaResponse DoAction(TeaRequest request, Dictionary DoActionAsync(TeaRequest request) + public static async Task DoActionAsync(Request request) { return await DoActionAsync(request, new Dictionary()); } - public static async Task DoActionAsync(TeaRequest request, Dictionary runtimeOptions) + public static async Task DoActionAsync(Request request, Dictionary runtimeOptions) { int timeout; var url = ComposeUrl(request); @@ -103,17 +108,17 @@ public static async Task DoActionAsync(TeaRequest request, Dictiona { HttpClient httpClient = HttpClientUtils.GetOrAddHttpClient(request.Protocol, uri.Host, uri.Port, runtimeOptions); HttpResponseMessage response = await httpClient.SendAsync(req, new CancellationTokenSource(timeout).Token); - return new TeaResponse(response); + return new Response(response); } - catch (System.Threading.Tasks.TaskCanceledException) + catch (TaskCanceledException) { throw new WebException("operation is timeout"); } } - public static string GetResponseBody(TeaResponse response) + internal static string GetResponseBody(Response response) { - using(var ms = new MemoryStream()) + using (var ms = new MemoryStream()) { var buffer = new byte[bufferLength]; var stream = response.Body; @@ -140,39 +145,39 @@ public static string GetResponseBody(TeaResponse response) } } - public static Dictionary ConvertHeaders(WebHeaderCollection headers) + internal static Dictionary ConvertHeaders(WebHeaderCollection headers) { var result = new Dictionary(); for (int i = 0; i < headers.Count; i++) { - result.Add(TeaConverter.StrToLower(headers.GetKey(i)), headers.Get(i)); + result.Add(ConverterUtil.StrToLower(headers.GetKey(i)), headers.Get(i)); } return result; } - public static Dictionary ConvertHeaders(HttpResponseHeaders headers) + internal static Dictionary ConvertHeaders(HttpResponseHeaders headers) { var result = new Dictionary(); var enumerator = headers.GetEnumerator(); foreach (var item in headers) { - result.Add(TeaConverter.StrToLower(item.Key), item.Value.First()); + result.Add(ConverterUtil.StrToLower(item.Key), item.Value.First()); } return result; } - public static bool AllowRetry(IDictionary dict, int retryTimes, long now) + internal static bool AllowRetry(IDictionary dict, int retryTimes, long now) { - if(retryTimes == 0) + if (retryTimes == 0) { return true; } - if(!dict.Get("retryable").ToSafeBool(false)) + if (!dict.Get("retryable").ToSafeBool(false)) { return false; } - + int retry; if (dict == null) { @@ -191,7 +196,57 @@ public static bool AllowRetry(IDictionary dict, int retryTimes, long now) return retry >= retryTimes; } - public static int GetBackoffTime(IDictionary Idict, int retryTimes) + public static bool ShouldRetry(RetryOptions options, RetryPolicyContext ctx) + { + if (ctx.RetriesAttempted == 0) + { + return true; + } + if (options == null || options.Retryable == null || options.Retryable == false) + { + return false; + } + + if (ctx.Exception is DaraException) + { + var daraException = (DaraException)ctx.Exception; + List noRetryConditions = options.NoRetryCondition; + List retryConditions = options.RetryCondition; + + if (noRetryConditions != null) + { + foreach (var noRetryCondition in noRetryConditions) + { + if (DictUtils.Contains(noRetryCondition.Exception, daraException.Message) || + DictUtils.Contains(noRetryCondition.ErrorCode, daraException.Code)) + { + return false; + } + } + } + if (retryConditions != null) + { + foreach (var retryCondition in retryConditions) + { + if (!DictUtils.Contains(retryCondition.Exception, daraException.Message) && + !DictUtils.Contains(retryCondition.ErrorCode, daraException.Code)) + { + continue; + } + + if (ctx.RetriesAttempted > retryCondition.MaxAttempts) + { + return false; + } + + return true; + } + } + } + return false; + } + + internal static int GetBackoffTime(IDictionary Idict, int retryTimes) { int backOffTime = 0; Dictionary dict = Idict.Keys.Cast().ToDictionary(key => key, key => Idict[key]); @@ -212,6 +267,50 @@ public static int GetBackoffTime(IDictionary Idict, int retryTimes) return backOffTime; } + public static int GetBackoffDelay(RetryOptions options, RetryPolicyContext ctx) + { + if (ctx.RetriesAttempted == null || ctx.RetriesAttempted == 0) + { + return 0; + } + + if (ctx.Exception is DaraException) + { + if (options != null) + { + var daraException = (DaraException)ctx.Exception; + var retryConditions = options.RetryCondition; + if (retryConditions != null) + { + foreach (var retryCondition in retryConditions) + { + if (!DictUtils.Contains(retryCondition.Exception, daraException.Message) && !DictUtils.Contains(retryCondition.ErrorCode, daraException.Code)) + { + continue; + } + long maxDelay = retryCondition.MaxDelayTimeMillis ?? (long)DefaultMaxDelay.TotalMilliseconds; + if (daraException is DaraResponseException) + { + var responseException = (DaraResponseException)daraException; + if (responseException.RetryAfter != null) + { + return (int)Math.Min(responseException.RetryAfter.Value, maxDelay); + } + } + if (retryCondition.Backoff == null) + { + return (int)DefaultMinDelay.TotalMilliseconds; + } + var delayTimeMillis = retryCondition.Backoff.GetDelayTime(ctx); + long delayTime = delayTimeMillis != null ? (long)delayTimeMillis : (long)DefaultMinDelay.TotalMilliseconds; + return (int)Math.Min(delayTime, maxDelay); + } + } + } + } + return (int)DefaultMinDelay.TotalMilliseconds; + } + public static void Sleep(int backoffTime) { Thread.Sleep(backoffTime); @@ -219,28 +318,24 @@ public static void Sleep(int backoffTime) public static async Task SleepAsync(int backoffTime) { - await Task.Run(() => - { - Thread.Sleep(1000); - }); + await Task.Delay(backoffTime); } - public static bool IsRetryable(Exception e) + internal static bool IsRetryable(Exception e) { - return e is TeaRetryableException; + return e is DaraRetryableException; } - public static Stream BytesReadable(string str) + public static Dictionary ToObject(IDictionary dictionary) { - return BytesReadable(Encoding.UTF8.GetBytes(str)); - } + var result = new Dictionary(); - public static Stream BytesReadable(byte[] bytes) - { - MemoryStream stream = new MemoryStream(); - stream.Write(bytes, 0, bytes.Length); - stream.Seek(0, SeekOrigin.Begin); - return stream; + foreach (DictionaryEntry entry in dictionary) + { + result[entry.Key.ToString()] = entry.Value; // 将值存储为 object + } + + return result; } internal static string PercentEncode(string value) @@ -260,14 +355,14 @@ internal static string PercentEncode(string value) } else { - stringBuilder.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int) c)); + stringBuilder.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)c)); } } return stringBuilder.ToString(); } - internal static HttpRequestMessage GetRequestMessage(TeaRequest request, Dictionary runtimeOptions, out int timeout) + internal static HttpRequestMessage GetRequestMessage(Request request, Dictionary runtimeOptions, out int timeout) { var url = ComposeUrl(request); HttpRequestMessage req = new HttpRequestMessage(); @@ -311,5 +406,14 @@ internal static HttpRequestMessage GetRequestMessage(TeaRequest request, Diction return req; } + public static Exception ThrowException(RetryPolicyContext context) + { + if (context.Exception is IOException) + { + return new DaraUnRetryableException(context); + } + + return context.Exception; + } } } diff --git a/Tea/tea.csproj b/Darabonba/Darabonba.csproj similarity index 78% rename from Tea/tea.csproj rename to Darabonba/Darabonba.csproj index 43879c8..39091a7 100644 --- a/Tea/tea.csproj +++ b/Darabonba/Darabonba.csproj @@ -2,31 +2,23 @@ netstandard2.0;net45 - Tea + Darabonba Library Alibaba Cloud - Alibaba Aliyun Tea Core SDK + Alibaba Aliyun Dara Core SDK ©2009-present Alibaba Cloud https://github.com/aliyun/ https://www.alibabacloud.com/favicon.ico - Alibaba Cloud Tea Core SDK for .NET + Alibaba Cloud Dara Core SDK for .NET false false - Tea - 1.1.3 + Darabonba + 1.0.0-preview false 5 - - NETSTANDARD2_0 - - - - NET45 - - @@ -50,10 +42,13 @@ runtime; build; native; contentfiles; analyzers + + + + - diff --git a/Darabonba/Date.cs b/Darabonba/Date.cs new file mode 100644 index 0000000..5f89964 --- /dev/null +++ b/Darabonba/Date.cs @@ -0,0 +1,185 @@ +using System; +using Darabonba.Exceptions; + +namespace Darabonba +{ + public class Date + { + private static readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public DateTime DateTime { get; private set; } + + public Date(DateTime date) + { + DateTime = date; + } + + public Date(string dateStr) + { + long timestamp; + DateTimeOffset dateTimeOffset; + if (long.TryParse(dateStr, out timestamp)) + { + dateTimeOffset = Jan1st1970.AddSeconds(timestamp); + DateTime = dateTimeOffset.UtcDateTime; + } + // if no time zone, treat as local time as DateTimeOffset.Parse. + else if (DateTimeOffset.TryParse(dateStr, out dateTimeOffset)) + { + DateTime = dateTimeOffset.UtcDateTime; + } + else + { + throw new DaraException + { + Message = dateStr + "is not a valid time string." + }; + } + } + + public string Format(string layout) + { + layout = layout.Replace('Y', 'y') + .Replace('D', 'd') + .Replace('h', 'H'); + return DateTime.ToUniversalTime().ToString(layout); + } + + public long Unix() + { + return (long)(DateTime.ToUniversalTime() - Jan1st1970).TotalSeconds; + } + + public string UTC() + { + return DateTime.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss.ffffff '+0000 UTC'"); + } + + public Date Sub(string unit, int amount) + { + DateTime newDate; + switch (unit.ToLowerInvariant()) + { + case "millisecond": + newDate = DateTime.AddMilliseconds(-amount); + break; + case "second": + newDate = DateTime.AddSeconds(-amount); + break; + case "minute": + newDate = DateTime.AddMinutes(-amount); + break; + case "hour": + newDate = DateTime.AddHours(-amount); + break; + case "day": + newDate = DateTime.AddDays(-amount); + break; + case "month": + newDate = DateTime.AddMonths(-amount); + break; + case "year": + newDate = DateTime.AddYears(-amount); + break; + default: + throw new ArgumentException("Unsupported unit."); + } + return new Date(newDate); + } + + public Date Add(string unit, int amount) + { + DateTime newDate; + switch (unit.ToLowerInvariant()) + { + case "millisecond": + newDate = DateTime.AddMilliseconds(amount); + break; + case "second": + newDate = DateTime.AddSeconds(amount); + break; + case "minute": + newDate = DateTime.AddMinutes(amount); + break; + case "hour": + newDate = DateTime.AddHours(amount); + break; + case "day": + newDate = DateTime.AddDays(amount); + break; + case "month": + newDate = DateTime.AddMonths(amount); + break; + case "year": + newDate = DateTime.AddYears(amount); + break; + default: + throw new ArgumentException("Unsupported unit."); + } + return new Date(newDate); + } + + public int Diff(string unit, Date diffDate) + { + TimeSpan timeSpan = DateTime - diffDate.DateTime; + switch (unit.ToLowerInvariant()) + { + case "millisecond": + return timeSpan.Milliseconds; + case "second": + return timeSpan.Seconds; + case "minute": + return timeSpan.Minutes; + case "hour": + return timeSpan.Hours; + case "day": + return timeSpan.Days; + case "month": + return timeSpan.Days / 30; + case "year": + return timeSpan.Days / 365; + default: + throw new ArgumentException("Unsupported unit."); + } + } + + public int Hour() + { + return DateTime.Hour; + } + + public int Minute() + { + return DateTime.Minute; + } + + public int Second() + { + return DateTime.Second; + } + + public int Month() + { + return DateTime.Month; + } + + public int Year() + { + return DateTime.Year; + } + + public int DayOfMonth() + { + return DateTime.Day; + } + + public int DayOfWeek() + { + if (DateTime.DayOfWeek == 0) + { + return 7; + } + return (int)DateTime.DayOfWeek; + } + } +} \ No newline at end of file diff --git a/Darabonba/Exceptions/DaraException.cs b/Darabonba/Exceptions/DaraException.cs new file mode 100644 index 0000000..d7b15bd --- /dev/null +++ b/Darabonba/Exceptions/DaraException.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using Tea; + +namespace Darabonba.Exceptions +{ + public class DaraException : TeaException + { + private string customCode; + private string customMessage; + private Dictionary customDataResult; + private int customStatusCode; + private string customDescription; + private Dictionary customAccessDeniedDetail; + + public DaraException() : base(new Dictionary()) + { + } + + public new string Code + { + get { return customCode ?? base.Code; } + set { customCode = value; SetInternalField("code", value); } + } + + public new string Message + { + get { return customMessage ?? base.Message; } + set { customMessage = value; SetInternalField("message", value); } + } + + public new Dictionary DataResult + { + get { return customDataResult ?? base.DataResult; } + set { customDataResult = value; SetInternalField("data", value); } + } + + public new int StatusCode + { + get { return customStatusCode != 0 ? customStatusCode : base.StatusCode; } + set { customStatusCode = value; SetInternalField("statusCode", value); } + } + + public new string Description + { + get { return customDescription ?? base.Description; } + set { customDescription = value; SetInternalField("description", value); } + } + + public new Dictionary AccessDeniedDetail + { + get { return customAccessDeniedDetail ?? base.AccessDeniedDetail; } + set { customAccessDeniedDetail = value; SetInternalField("accessDeniedDetail", value); } + } + + // 使用反射设置TeaException中的私有字段 + private void SetInternalField(string fieldName, object value) + { + var field = typeof(TeaException).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + if (field != null) + { + field.SetValue(this, value); + } + } + + public DaraException(IDictionary dict) : base(dict) + { + Message = base.Message; + Code = base.Code; + StatusCode = base.StatusCode; + Description = base.Description; + DataResult = base.DataResult; + AccessDeniedDetail = base.AccessDeniedDetail; + } + } +} \ No newline at end of file diff --git a/Darabonba/Exceptions/DaraResponseException.cs b/Darabonba/Exceptions/DaraResponseException.cs new file mode 100644 index 0000000..17b2bcf --- /dev/null +++ b/Darabonba/Exceptions/DaraResponseException.cs @@ -0,0 +1,7 @@ +namespace Darabonba.Exceptions +{ + public class DaraResponseException : DaraException + { + public long? RetryAfter { get; set; } + } +} diff --git a/Darabonba/Exceptions/DaraRetryableException.cs b/Darabonba/Exceptions/DaraRetryableException.cs new file mode 100644 index 0000000..34433bf --- /dev/null +++ b/Darabonba/Exceptions/DaraRetryableException.cs @@ -0,0 +1,8 @@ +using System; + +namespace Darabonba.Exceptions +{ + public class DaraRetryableException : Exception + { + } +} diff --git a/Darabonba/Exceptions/DaraUnretryableException.cs b/Darabonba/Exceptions/DaraUnretryableException.cs new file mode 100644 index 0000000..80fe8d6 --- /dev/null +++ b/Darabonba/Exceptions/DaraUnretryableException.cs @@ -0,0 +1,17 @@ +using Darabonba.RetryPolicy; +using Tea; + +namespace Darabonba.Exceptions +{ + public class DaraUnRetryableException : TeaUnretryableException + { + public DaraUnRetryableException() + { + } + + public DaraUnRetryableException(RetryPolicyContext retryPolicyContext) : base(retryPolicyContext.Request, + retryPolicyContext.Exception) + { + } + } +} \ No newline at end of file diff --git a/Darabonba/File.cs b/Darabonba/File.cs new file mode 100644 index 0000000..9fa1f5e --- /dev/null +++ b/Darabonba/File.cs @@ -0,0 +1,166 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Darabonba +{ + public class File : IDisposable + { + public readonly string _path; + public FileInfo _fileInfo; + public FileStream _fileStream; + public long _position; + + + public File(string path) + { + _path = path; + _fileInfo = new FileInfo(path); + _position = 0; + _fileStream = new FileStream(_path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + } + + public void Dispose() + { + if (_fileStream != null) + { + _fileStream.Dispose(); + } + } + + public string Path() + { + return _path; + } + + private void EnsureFileInfoLoaded() + { + if (!_fileInfo.Exists) + { + _fileInfo = new FileInfo(_path); + } + } + + public Date CreateTime() + { + EnsureFileInfoLoaded(); + return new Date(_fileInfo.CreationTimeUtc); + } + + public async Task CreateTimeAsync() + { + return await Task.Run(() => + { + EnsureFileInfoLoaded(); + return new Date(_fileInfo.CreationTimeUtc); + }); + } + + public Date ModifyTime() + { + EnsureFileInfoLoaded(); + return new Date(_fileInfo.LastWriteTimeUtc); + } + + public async Task ModifyTimeAsync() + { + EnsureFileInfoLoaded(); + return new Date(_fileInfo.LastWriteTimeUtc); + } + + public int Length() + { + EnsureFileInfoLoaded(); + return (int)_fileInfo.Length; + } + + public async Task LengthAsync() + { + EnsureFileInfoLoaded(); + return (int)_fileInfo.Length; + } + + public byte[] Read(int size) + { + _fileStream.Seek(_position, SeekOrigin.Begin); + byte[] buffer = new byte[size]; + int bytesRead = _fileStream.Read(buffer, 0, size); + if (bytesRead == 0) + { + return null; + } + _position += bytesRead; + return buffer; + } + + public async Task ReadAsync(int size) + { + _fileStream.Seek(_position, SeekOrigin.Begin); + byte[] buffer = new byte[size]; + int bytesRead = await _fileStream.ReadAsync(buffer, 0, size); + if (bytesRead == 0) + { + return null; + } + + _position += bytesRead; + return buffer; + } + + public void Write(byte[] data) + { + _fileStream.Seek(0, SeekOrigin.End); + _fileStream.Write(data, 0, data.Length); + _fileStream.Flush(); + _fileInfo.Refresh(); + } + + public async Task WriteAsync(byte[] data) + { + _fileStream.Seek(0, SeekOrigin.End); + await _fileStream.WriteAsync(data, 0, data.Length); + await _fileStream.FlushAsync(); + _fileInfo.Refresh(); + } + + + public void Close() + { + if (_fileStream != null) + { + _fileStream.Flush(); + _fileStream.Close(); + _fileStream = null; + } + } + public async Task CloseAsync() + { + if (_fileStream != null) + { + await _fileStream.FlushAsync(); + _fileStream.Close(); + _fileStream = null; + } + } + + public static bool Exists(string path) + { + return System.IO.File.Exists(path); + } + + public static async Task ExistsAsync(string path) + { + return System.IO.File.Exists(path); + } + + public static FileStream CreateReadStream(string path) + { + return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None); + } + + public static FileStream CreateWriteStream(string path) + { + return new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.None); + } + } +} \ No newline at end of file diff --git a/Tea/TeaModel.cs b/Darabonba/Model.cs similarity index 95% rename from Tea/TeaModel.cs rename to Darabonba/Model.cs index f7f01a3..9ea3761 100644 --- a/Tea/TeaModel.cs +++ b/Darabonba/Model.cs @@ -3,12 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; - using Newtonsoft.Json; +using Tea; -namespace Tea +namespace Darabonba { - public class TeaModel + public class Model : TeaModel { public static T ToObject(IDictionary dict) where T : class, new() { @@ -90,7 +90,7 @@ private static object MapObj(Type propertyType, object value) } return list; } - else if (typeof(TeaModel).IsAssignableFrom(propertyType)) + else if (typeof(Model).IsAssignableFrom(propertyType)) { var v = Activator.CreateInstance(propertyType); Dictionary dicObj = ((IDictionary) value).Keys.Cast().ToDictionary(key => key, key => ((IDictionary) value) [key]); @@ -98,7 +98,7 @@ private static object MapObj(Type propertyType, object value) } else if (typeof(IDictionary).IsAssignableFrom(propertyType)) { - var dic = (IDictionary) value; + var dic = (IDictionary) value; if (dic.Count == 0) { return dic; diff --git a/Tea/TeaModelExtensions.cs b/Darabonba/ModelExtensions.cs similarity index 73% rename from Tea/TeaModelExtensions.cs rename to Darabonba/ModelExtensions.cs index 7362e0b..4b613a5 100644 --- a/Tea/TeaModelExtensions.cs +++ b/Darabonba/ModelExtensions.cs @@ -4,11 +4,11 @@ using System.IO; using System.Reflection; -namespace Tea +namespace Darabonba { - public static class TeaModelExtensions + public static class ModelExtensions { - public static Dictionary ToMap(this TeaModel model) + public static Dictionary ToMap(this Model model) { if (model == null) { @@ -22,7 +22,7 @@ public static Dictionary ToMap(this TeaModel model) { PropertyInfo propertyInfo = properties[i]; Type property = propertyInfo.PropertyType; - if(typeof(Stream).IsAssignableFrom(property)) + if (typeof(Stream).IsAssignableFrom(property)) { continue; } @@ -42,8 +42,8 @@ public static object ToMapFactory(Type type, object value) } if (typeof(IList).IsAssignableFrom(type) && !typeof(Array).IsAssignableFrom(type)) { - IList list = (IList) value; - Type listType = type.GetGenericArguments() [0]; + IList list = (IList)value; + Type listType = type.GetGenericArguments()[0]; List resultList = new List(); for (int j = 0; j < list.Count; j++) { @@ -60,7 +60,7 @@ public static object ToMapFactory(Type type, object value) } else if (typeof(IDictionary).IsAssignableFrom(type)) { - IDictionary dic = (IDictionary) value; + IDictionary dic = (IDictionary)value; IDictionary resultDic = new Dictionary(); foreach (DictionaryEntry keypair in dic) { @@ -76,16 +76,16 @@ public static object ToMapFactory(Type type, object value) } return resultDic; } - else if (typeof(TeaModel).IsAssignableFrom(type)) + else if (typeof(Model).IsAssignableFrom(type)) { - TeaModel teaModel = (TeaModel) value; - return teaModel.ToMap(); + Model model = (Model)value; + return model.ToMap(); } return value; } - public static void Validate(this TeaModel model) + public static void Validate(this Model model) { if (model == null) { @@ -99,26 +99,26 @@ public static void Validate(this TeaModel model) Type propertyType = p.PropertyType; object obj = p.GetValue(model); ValidationAttribute attribute = p.GetCustomAttribute(typeof(ValidationAttribute)) as ValidationAttribute; - TeaValidator teaValidator = new TeaValidator(attribute, p.Name); - teaValidator.ValidateRequired(obj); + Validator validator = new Validator(attribute, p.Name); + validator.ValidateRequired(obj); if (obj == null) { continue; } if (typeof(IList).IsAssignableFrom(propertyType) && !typeof(Array).IsAssignableFrom(propertyType)) { - IList list = (IList) obj; + IList list = (IList)obj; //validate list count - teaValidator.ValidateMaxLength(list); - teaValidator.ValidateMinLength(list); + validator.ValidateMaxLength(list); + validator.ValidateMinLength(list); - Type listType = propertyType.GetGenericArguments() [0]; - if (typeof(TeaModel).IsAssignableFrom(listType)) + Type listType = propertyType.GetGenericArguments()[0]; + if (typeof(Model).IsAssignableFrom(listType)) { for (int j = 0; j < list.Count; j++) { - ((TeaModel) list[j]).Validate(); + ((Model)list[j]).Validate(); } } else @@ -126,24 +126,24 @@ public static void Validate(this TeaModel model) for (int j = 0; j < list.Count; j++) { //validate pattern - teaValidator.ValidateRegex(list[j]); + validator.ValidateRegex(list[j]); } } } - else if (typeof(TeaModel).IsAssignableFrom(propertyType)) + else if (typeof(Model).IsAssignableFrom(propertyType)) { - ((TeaModel) obj).Validate(); + ((Model)obj).Validate(); } else { //validate pattern - teaValidator.ValidateRegex(obj); + validator.ValidateRegex(obj); //validate count - teaValidator.ValidateMaxLength(obj); - teaValidator.ValidateMinLength(obj); + validator.ValidateMaxLength(obj); + validator.ValidateMinLength(obj); //validate num - teaValidator.ValidateMaximum(obj); - teaValidator.ValidateMinimum(obj); + validator.ValidateMaximum(obj); + validator.ValidateMinimum(obj); } } } diff --git a/Darabonba/Models/ExtendsParameters.cs b/Darabonba/Models/ExtendsParameters.cs new file mode 100644 index 0000000..e5383e6 --- /dev/null +++ b/Darabonba/Models/ExtendsParameters.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; + +namespace Darabonba.Models +{ + public class ExtendsParameters : Model + { + public Dictionary Headers { get; set; } + public Dictionary Queries { get; set; } + + public new void Validate() {} + + public new ExtendsParameters Copy() + { + ExtendsParameters copy = FromMap(ToMap()); + return copy; + } + + public new ExtendsParameters CopyWithoutStream() + { + ExtendsParameters copy = FromMap(ToMap(true)); + return copy; + } + + public Dictionary ToMap(bool noStream = false) + { + var map = new Dictionary(); + + if (Headers != null) + { + map["headers"] = Headers; + } + if (Queries != null) + { + map["queries"] = Queries; + } + + return map; + } + + public static ExtendsParameters FromMap(IDictionary map) + { + var model = new ExtendsParameters(); + + if (map.ContainsKey("headers")) + { + model.Headers = map["headers"] as Dictionary; + } + if (map.ContainsKey("queries")) + { + model.Queries = map["queries"] as Dictionary; + } + + return model; + } + } +} \ No newline at end of file diff --git a/Darabonba/Models/RuntimeOptions.cs b/Darabonba/Models/RuntimeOptions.cs new file mode 100644 index 0000000..04d5370 --- /dev/null +++ b/Darabonba/Models/RuntimeOptions.cs @@ -0,0 +1,261 @@ +using System.Collections.Generic; +using Darabonba; +using Darabonba.RetryPolicy; + +namespace Darabonba.Models +{ + public class RuntimeOptions : Model + { + public RetryOptions RetryOptions { get; set; } + public bool? Autoretry { get; set; } + public bool? IgnoreSSL { get; set; } + public string Key { get; set; } + public string Cert { get; set; } + public string Ca { get; set; } + public int? MaxAttempts { get; set; } + public string BackoffPolicy { get; set; } + public int? BackoffPeriod { get; set; } + public int? ReadTimeout { get; set; } + public int? ConnectTimeout { get; set; } + public string HttpProxy { get; set; } + public string HttpsProxy { get; set; } + public string NoProxy { get; set; } + public int? MaxIdleConns { get; set; } + public string LocalAddr { get; set; } + public string Socks5Proxy { get; set; } + public string Socks5NetWork { get; set; } + public bool? KeepAlive { get; set; } + public ExtendsParameters ExtendsParameters { get; set; } + + public new void Validate() + { + } + + public new RuntimeOptions Copy() + { + RuntimeOptions copy = FromMap(ToMap()); + return copy; + } + + public new RuntimeOptions CopyWithoutStream() + { + RuntimeOptions copy = FromMap(ToMap(true)); + return copy; + } + + public Dictionary ToMap(bool noStream = false) + { + var map = new Dictionary(); + + if (RetryOptions != null) + { + map["retryOptions"] = RetryOptions; + } + + if (Autoretry != null) + { + map["autoretry"] = Autoretry; + } + + if (IgnoreSSL != null) + { + map["ignoreSSL"] = IgnoreSSL; + } + + if (Key != null) + { + map["key"] = Key; + } + + if (Cert != null) + { + map["cert"] = Cert; + } + + if (Ca != null) + { + map["ca"] = Ca; + } + + if (MaxAttempts != null) + { + map["max_attempts"] = MaxAttempts; + } + + if (BackoffPolicy != null) + { + map["backoff_policy"] = BackoffPolicy; + } + + if (BackoffPeriod != null) + { + map["backoff_period"] = BackoffPeriod; + } + + if (ReadTimeout != null) + { + map["readTimeout"] = ReadTimeout; + } + + if (ConnectTimeout != null) + { + map["connectTimeout"] = ConnectTimeout; + } + + if (HttpProxy != null) + { + map["httpProxy"] = HttpProxy; + } + + if (HttpsProxy != null) + { + map["httpsProxy"] = HttpsProxy; + } + + if (NoProxy != null) + { + map["noProxy"] = NoProxy; + } + + if (MaxIdleConns != null) + { + map["maxIdleConns"] = MaxIdleConns; + } + + if (LocalAddr != null) + { + map["localAddr"] = LocalAddr; + } + + if (Socks5Proxy != null) + { + map["socks5Proxy"] = Socks5Proxy; + } + + if (Socks5NetWork != null) + { + map["socks5NetWork"] = Socks5NetWork; + } + + if (KeepAlive != null) + { + map["keepAlive"] = KeepAlive; + } + + if (ExtendsParameters != null) + { + map["extendsParameters"] = ExtendsParameters != null ? ExtendsParameters.ToMap(noStream) : null; + } + + return map; + } + + public static RuntimeOptions FromMap(Dictionary map) + { + var model = new RuntimeOptions(); + + if (map.ContainsKey("retryOptions")) + { + model.RetryOptions = (RetryOptions)map["retryOptions"]; + } + + if (map.ContainsKey("autoretry")) + { + model.Autoretry = (bool?)map["autoretry"]; + } + + if (map.ContainsKey("ignoreSSL")) + { + model.IgnoreSSL = (bool?)map["ignoreSSL"]; + } + + if (map.ContainsKey("key")) + { + model.Key = (string)map["key"]; + } + + if (map.ContainsKey("cert")) + { + model.Cert = (string)map["cert"]; + } + + if (map.ContainsKey("ca")) + { + model.Ca = (string)map["ca"]; + } + + if (map.ContainsKey("max_attempts")) + { + model.MaxAttempts = (int?)map["max_attempts"]; + } + + if (map.ContainsKey("backoff_policy")) + { + model.BackoffPolicy = (string)map["backoff_policy"]; + } + + if (map.ContainsKey("backoff_period")) + { + model.BackoffPeriod = (int?)map["backoff_period"]; + } + + if (map.ContainsKey("readTimeout")) + { + model.ReadTimeout = (int?)map["readTimeout"]; + } + + if (map.ContainsKey("connectTimeout")) + { + model.ConnectTimeout = (int?)map["connectTimeout"]; + } + + if (map.ContainsKey("httpProxy")) + { + model.HttpProxy = (string)map["httpProxy"]; + } + + if (map.ContainsKey("httpsProxy")) + { + model.HttpsProxy = (string)map["httpsProxy"]; + } + + if (map.ContainsKey("noProxy")) + { + model.NoProxy = (string)map["noProxy"]; + } + + if (map.ContainsKey("maxIdleConns")) + { + model.MaxIdleConns = (int?)map["maxIdleConns"]; + } + + if (map.ContainsKey("localAddr")) + { + model.LocalAddr = (string)map["localAddr"]; + } + + if (map.ContainsKey("socks5Proxy")) + { + model.Socks5Proxy = (string)map["socks5Proxy"]; + } + + if (map.ContainsKey("socks5NetWork")) + { + model.Socks5NetWork = (string)map["socks5NetWork"]; + } + + if (map.ContainsKey("keepAlive")) + { + model.KeepAlive = (bool?)map["keepAlive"]; + } + + if (map.ContainsKey("extendsParameters")) + { + var temp = (Dictionary)map["extendsParameters"]; + model.ExtendsParameters = ExtendsParameters.FromMap(temp); + } + + return model; + } + } +} \ No newline at end of file diff --git a/Darabonba/Models/SSEEvent.cs b/Darabonba/Models/SSEEvent.cs new file mode 100644 index 0000000..0f25148 --- /dev/null +++ b/Darabonba/Models/SSEEvent.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using Darabonba; + +namespace Darabonba.Models +{ + public class SSEEvent : Model + { + public string Data { get; set; } + public string Id { get; set; } + public string Event { get; set; } + public int? Retry { get; set; } + + public SSEEvent Copy() + { + SSEEvent copy = FromMap(ToMap()); + return copy; + } + + public SSEEvent CopyWithoutStream() + { + SSEEvent copy = FromMap(ToMap(true)); + return copy; + } + + public Dictionary ToMap(bool noStream = false) + { + var map = new Dictionary(); + if (Data != null) + { + map["data"] = Data; + } + if (Id != null) + { + map["id"] = Id; + } + if (Event != null) + { + map["event"] = Event; + } + if (Retry != null) + { + map["retry"] = Retry; + } + return map; + } + + public static SSEEvent FromMap(Dictionary map) + { + var model = new SSEEvent(); + if (map.ContainsKey("data")) + { + model.Data = (string)map["data"]; + } + if (map.ContainsKey("id")) + { + model.Id = (string)map["id"]; + } + if (map.ContainsKey("event")) + { + model.Event = (string)map["event"]; + } + if (map.ContainsKey("retry")) + { + model.Retry = (int?)map["retry"]; + } + return model; + } + } +} \ No newline at end of file diff --git a/Tea/Properties/AssemblyInfo.cs b/Darabonba/Properties/AssemblyInfo.cs similarity index 85% rename from Tea/Properties/AssemblyInfo.cs rename to Darabonba/Properties/AssemblyInfo.cs index 0d687f8..1d2d0bc 100644 --- a/Tea/Properties/AssemblyInfo.cs +++ b/Darabonba/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -28,5 +29,8 @@ // Build Number // Revision // -[assembly: AssemblyVersion("1.1.3.0")] -[assembly: AssemblyFileVersion("1.1.3.0")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: InternalsVisibleTo("DaraUnitTests")] + diff --git a/Tea/TeaRequest.cs b/Darabonba/Request.cs similarity index 82% rename from Tea/TeaRequest.cs rename to Darabonba/Request.cs index eb1345b..665237c 100644 --- a/Tea/TeaRequest.cs +++ b/Darabonba/Request.cs @@ -1,15 +1,16 @@ using System.Collections.Generic; using System.IO; +using Tea; -namespace Tea +namespace Darabonba { - public class TeaRequest + public class Request : TeaRequest { private string _protocol; private string _method; private Dictionary _query; private Dictionary _headers; - public TeaRequest() + public Request() { Query = new Dictionary(); Headers = new Dictionary(); @@ -92,5 +93,17 @@ public Dictionary Headers } public Stream Body; + + public Dictionary ToMap(bool noStream = false) + { + var map = new Dictionary(); + return map; + } + + public static Request FromMap(Dictionary map) + { + var model = new Request(); + return model; + } } } diff --git a/Tea/TeaResponse.cs b/Darabonba/Response.cs similarity index 55% rename from Tea/TeaResponse.cs rename to Darabonba/Response.cs index 0d725a4..42f44a4 100644 --- a/Tea/TeaResponse.cs +++ b/Darabonba/Response.cs @@ -2,9 +2,9 @@ using System.IO; using System.Net.Http; -namespace Tea +namespace Darabonba { - public class TeaResponse + public class Response { private HttpResponseMessage _responseAsync { get; set; } @@ -29,15 +29,29 @@ public Stream Body } } - public TeaResponse(HttpResponseMessage response) + + public Response(HttpResponseMessage response) { if (response != null) { - StatusCode = (int) response.StatusCode; + StatusCode = (int)response.StatusCode; StatusMessage = ""; - Headers = TeaCore.ConvertHeaders(response.Headers); + Headers = Core.ConvertHeaders(response.Headers); _responseAsync = response; } } + + public Dictionary ToMap(bool noStream = false) + { + var map = new Dictionary(); + return map; + } + + public static Response FromMap(Dictionary map) + { + HttpResponseMessage httpResponseMessage = new HttpResponseMessage(); + var model = new Response(httpResponseMessage); + return model; + } } } diff --git a/Darabonba/RetryPolicy/BackoffPolicy.cs b/Darabonba/RetryPolicy/BackoffPolicy.cs new file mode 100644 index 0000000..2baf1d8 --- /dev/null +++ b/Darabonba/RetryPolicy/BackoffPolicy.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using Darabonba.Exceptions; + +namespace Darabonba.RetryPolicy +{ + public interface IBackoffPolicy + { + long? GetDelayTime(RetryPolicyContext ctx); + } + + public abstract class BackoffPolicy : IBackoffPolicy + { + protected string Policy { get; set; } + + public BackoffPolicy(string policy) + { + Policy = policy; + } + + public abstract long? GetDelayTime(RetryPolicyContext ctx); + + public static BackoffPolicy NewBackOffPolicy(Dictionary option) + { + var validPolicy = new List { "Fixed", "Random", "Exponential", "EqualJitter", "ExponentialWithEqualJitter", "FullJitter", "ExponentialWithFullJitter" }; + if (!option.ContainsKey("policy") || option["policy"] == null || !(option["policy"] is string)) + { + throw new DaraException + { + Message = "Invalid backoff policy" + }; + } + else + { + string policy = (string)option["policy"]; + if (!validPolicy.Contains(policy)) + { + throw new DaraException + { + Message = "Invalid backoff policy" + }; + } + if (!option.ContainsKey("period") || option["period"] == null || !(option["period"] is int)) + { + throw new DaraException { Message = "Period must be specified." }; + } + int period = (int)option["period"]; + switch (policy) + { + case "Fixed": + { + return new FixedBackoffPolicy(period); + } + case "Random": + { + var cap = option.ContainsKey("cap") && option["cap"] != null && option["cap"] is long ? (long)option["cap"] : 20000; + return new RandomBackoffPolicy(period, cap); + } + case "Exponential": + { + var cap = option.ContainsKey("cap") && option["cap"] != null && option["cap"] is long ? (long)option["cap"] : 3L * 24 * 60 * 60 * 1000; + return new ExponentialBackoffPolicy(period, cap); + } + case "EqualJitter": + case "ExponentialWithEqualJitter": + { + var cap = option.ContainsKey("cap") && option["cap"] != null && option["cap"] is long ? (long)option["cap"] : 3L * 24 * 60 * 60 * 1000; + return new EqualJitterBackoffPolicy(period, cap); + } + case "FullJitter": + case "ExponentialWithFullJitter": + { + var cap = option.ContainsKey("cap") && option["cap"] != null && option["cap"] is long ? (long)option["cap"] : 3L * 24 * 60 * 60 * 1000; + return new FullJitterBackoffPolicy(period, cap); + } + default: + throw new DaraException + { + Message = "Invalid backoff policy" + }; + } + } + } + } +} \ No newline at end of file diff --git a/Darabonba/RetryPolicy/EqualJitterBackoffPolicy.cs b/Darabonba/RetryPolicy/EqualJitterBackoffPolicy.cs new file mode 100644 index 0000000..9cb139f --- /dev/null +++ b/Darabonba/RetryPolicy/EqualJitterBackoffPolicy.cs @@ -0,0 +1,25 @@ +using System; + +namespace Darabonba.RetryPolicy +{ + public class EqualJitterBackoffPolicy : BackoffPolicy + { + private readonly int? Period; + private readonly long? Cap; + + public EqualJitterBackoffPolicy(int? period, long? cap = 3L * 24 * 60 * 60 * 1000) + : base(null) + { + Period = period; + Cap = cap; + } + + public override long? GetDelayTime(RetryPolicyContext ctx) + { + double ceil = Math.Min((double)Cap, (double)(Math.Pow(2.0, (double)ctx.RetriesAttempted) * Period)); + Random random = new Random(); + double delay = ceil / 2 + random.NextDouble() * (ceil / 2); + return (long?)delay; + } + } +} \ No newline at end of file diff --git a/Darabonba/RetryPolicy/ExponentialBackoffPolicy.cs b/Darabonba/RetryPolicy/ExponentialBackoffPolicy.cs new file mode 100644 index 0000000..3a39590 --- /dev/null +++ b/Darabonba/RetryPolicy/ExponentialBackoffPolicy.cs @@ -0,0 +1,23 @@ +using System; + +namespace Darabonba.RetryPolicy +{ + public class ExponentialBackoffPolicy : BackoffPolicy + { + private readonly int? Period; + public readonly long? Cap; + + public ExponentialBackoffPolicy(int? period, long? cap = 3L * 24 * 60 * 60 * 1000) + : base(null) + { + Period = period; + Cap = cap; + } + + public override long? GetDelayTime(RetryPolicyContext ctx) + { + double potentialTime = Math.Pow(2.0, (double)ctx.RetriesAttempted) * (double)Period; + return (long?)Math.Min((double)Cap, potentialTime); + } + } +} \ No newline at end of file diff --git a/Darabonba/RetryPolicy/FixedBackoffPolicy.cs b/Darabonba/RetryPolicy/FixedBackoffPolicy.cs new file mode 100644 index 0000000..f89888d --- /dev/null +++ b/Darabonba/RetryPolicy/FixedBackoffPolicy.cs @@ -0,0 +1,18 @@ +namespace Darabonba.RetryPolicy +{ + public class FixedBackoffPolicy : BackoffPolicy + { + private readonly int? Period; + + public FixedBackoffPolicy(int? period) + : base(null) + { + Period = period; + } + + public override long? GetDelayTime(RetryPolicyContext ctx) + { + return Period; + } + } +} \ No newline at end of file diff --git a/Darabonba/RetryPolicy/FullJitterBackoffPolicy.cs b/Darabonba/RetryPolicy/FullJitterBackoffPolicy.cs new file mode 100644 index 0000000..40154f7 --- /dev/null +++ b/Darabonba/RetryPolicy/FullJitterBackoffPolicy.cs @@ -0,0 +1,25 @@ +using System; + +namespace Darabonba.RetryPolicy +{ + public class FullJitterBackoffPolicy : BackoffPolicy + { + private readonly int? Period; + private readonly long? Cap; + + public FullJitterBackoffPolicy(int? period, long? cap = 3L * 24 * 60 * 60 * 1000) + : base(null) + { + Period = period; + Cap = cap; + } + + public override long? GetDelayTime(RetryPolicyContext ctx) + { + double ceil = Math.Min((double)Cap, (double)(Math.Pow(2.0, (double)ctx.RetriesAttempted) * Period)); + Random random = new Random(); + double delay = random.NextDouble() * ceil; + return (long?)delay; + } + } +} \ No newline at end of file diff --git a/Darabonba/RetryPolicy/RandomBackoffPolicy.cs b/Darabonba/RetryPolicy/RandomBackoffPolicy.cs new file mode 100644 index 0000000..f23126c --- /dev/null +++ b/Darabonba/RetryPolicy/RandomBackoffPolicy.cs @@ -0,0 +1,24 @@ +using System; + +namespace Darabonba.RetryPolicy +{ + public class RandomBackoffPolicy : BackoffPolicy + { + private readonly int? Period; + private readonly long? Cap; + + public RandomBackoffPolicy(int? period, long? cap = 2000) + : base(null) + { + Period = period; + Cap = cap; + } + + public override long? GetDelayTime(RetryPolicyContext ctx) + { + Random random = new Random(); + double randomTime = random.NextDouble() * Math.Min((double)Cap, (double)(Period * ctx.RetriesAttempted)); + return (long?)randomTime; + } + } +} \ No newline at end of file diff --git a/Darabonba/RetryPolicy/RetryCondition.cs b/Darabonba/RetryPolicy/RetryCondition.cs new file mode 100644 index 0000000..6d28102 --- /dev/null +++ b/Darabonba/RetryPolicy/RetryCondition.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Darabonba.RetryPolicy +{ + public class RetryCondition + { + public int? MaxAttempts { get; set; } + public long? MaxDelayTimeMillis { get; set; } + public BackoffPolicy Backoff { get; set; } + public List Exception { get; set; } + public List ErrorCode { get; set; } + } +} \ No newline at end of file diff --git a/Darabonba/RetryPolicy/RetryOptions.cs b/Darabonba/RetryPolicy/RetryOptions.cs new file mode 100644 index 0000000..6e7e0d7 --- /dev/null +++ b/Darabonba/RetryPolicy/RetryOptions.cs @@ -0,0 +1,23 @@ +using Darabonba; +using System; +using System.Collections.Generic; + +namespace Darabonba.RetryPolicy +{ + public class RetryOptions + { + public bool? Retryable { get; set; } + public List RetryCondition { get; set; } + public List NoRetryCondition { get; set; } + + public Dictionary ToMap(bool noStream = false) + { + return new Dictionary(); + } + + public static RetryOptions FromMap(Dictionary map) + { + return new RetryOptions(); + } + } +} \ No newline at end of file diff --git a/Darabonba/RetryPolicy/RetryPolicyContext.cs b/Darabonba/RetryPolicy/RetryPolicyContext.cs new file mode 100644 index 0000000..2359af7 --- /dev/null +++ b/Darabonba/RetryPolicy/RetryPolicyContext.cs @@ -0,0 +1,12 @@ +using System; + +namespace Darabonba.RetryPolicy +{ + public class RetryPolicyContext + { + public int? RetriesAttempted { get; set; } + public Request Request { get; set; } + public Response Response { get; set; } + public Exception Exception { get; set; } + } +} \ No newline at end of file diff --git a/Darabonba/URL.cs b/Darabonba/URL.cs new file mode 100644 index 0000000..c5e1528 --- /dev/null +++ b/Darabonba/URL.cs @@ -0,0 +1,119 @@ +using System; +using System.Globalization; +using System.Net; +using System.Text; + +namespace Darabonba +{ + public class URL + { + private Uri _uri; + + public URL(string str) + { + _uri = new Uri(str); + } + + public string Path() + { + return _uri.AbsolutePath + _uri.Query; + } + + public string Pathname() + { + return _uri.AbsolutePath; + } + + public string Protocol() + { + return _uri.Scheme; + } + + public string Hostname() + { + return _uri.Host; + } + + public string Host() + { + return _uri.Authority; + } + + public string Port() + { + return _uri.Port == -1 ? "" : _uri.Port.ToString(); + } + + public string Hash() + { + return string.IsNullOrWhiteSpace(_uri.Fragment) ? "" : _uri.Fragment.TrimStart('#'); + } + + public string Search() + { + return string.IsNullOrWhiteSpace(_uri.Query) ? "" : _uri.Query.TrimStart('?'); + } + + public string Href() + { + return _uri.ToString(); + } + + public string Auth() + { + return _uri.UserInfo; + } + + + public static URL Parse(string url) + { + return new URL(url); + } + + public static string UrlEncode(string url) + { + return url != null ? WebUtility.UrlEncode(url) : string.Empty; + } + + public static string PercentEncode(string value) + { + if (value == null) + { + return null; + } + var stringBuilder = new StringBuilder(); + var text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; + var bytes = Encoding.UTF8.GetBytes(value); + foreach (char c in bytes) + { + if (text.IndexOf(c) >= 0) + { + stringBuilder.Append(c); + } + else + { + stringBuilder.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)c)); + } + } + + return stringBuilder.ToString(); + } + + public static string PathEncode(string path) + { + if (string.IsNullOrEmpty(path) || path == "/") + { + return path; + } + var paths = path.Split('/'); + var stringBuilder = new StringBuilder(); + + foreach (var s in paths) + { + stringBuilder.Append('/').Append(PercentEncode(s)); + } + + return "/" + stringBuilder.ToString().TrimStart('/'); + } + } +} \ No newline at end of file diff --git a/Darabonba/Utils/BytesUtil.cs b/Darabonba/Utils/BytesUtil.cs new file mode 100644 index 0000000..c71e8f1 --- /dev/null +++ b/Darabonba/Utils/BytesUtil.cs @@ -0,0 +1,42 @@ +using System.Text; + +namespace Darabonba.Utils +{ + public class BytesUtil + { + public static byte[] From(string data, string type) + { + string lowerEncoding = type.ToLower(); + switch (lowerEncoding.ToLowerInvariant()) + { + case "ascii": + return Encoding.ASCII.GetBytes(data); + case "bigendianunicode": + return Encoding.BigEndianUnicode.GetBytes(data); + case "unicode": + return Encoding.Unicode.GetBytes(data); + case "utf32": + case "utf-32": + return Encoding.UTF32.GetBytes(data); + case "utf8": + case "utf-8": + return Encoding.UTF8.GetBytes(data); + default: + return Encoding.UTF8.GetBytes(data); + } + } + + public static string ToHex(byte[] raw) + { + if (raw == null) + { + return string.Empty; + } + + StringBuilder result = new StringBuilder(raw.Length * 2); + for (int i = 0; i < raw.Length; i++) + result.Append(raw[i].ToString("x2")); + return result.ToString(); + } + } +} \ No newline at end of file diff --git a/Darabonba/Utils/ConverterUtil.cs b/Darabonba/Utils/ConverterUtil.cs new file mode 100644 index 0000000..6ac470d --- /dev/null +++ b/Darabonba/Utils/ConverterUtil.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using Darabonba.Exceptions; + +namespace Darabonba.Utils +{ + public class ConverterUtil + { + public static Dictionary Merge(Dictionary dic1, Dictionary dic2) + { + object[] objs = new object[] { dic1, dic2 }; + return Merge(objs); + } + + public static Dictionary Merge(params object[] objs) + { + Dictionary dicResult = new Dictionary(); + if (objs == null) + { + return dicResult; + } + + foreach (object obj in objs) + { + if (obj == null) + { + continue; + } + Dictionary dicObj = new Dictionary(); + Type typeObj = obj.GetType(); + if (typeof(Model).IsAssignableFrom(typeObj)) + { + dicObj = ((Model)obj).ToMap(); + } + else if (obj is Dictionary) + { + dicObj = (Dictionary)obj; + } + else if (obj is Dictionary) + { + Dictionary dicString = (Dictionary)obj; + foreach (var keypair in dicString) + { + dicObj.Add(keypair.Key, keypair.Value); + } + } + else + { + throw new ArgumentException(" inparams only support Dictionary or Darabonba.Model. "); + } + + foreach (var keypair in dicObj) + { + T dicValue = (T)keypair.Value; + if (dicResult.ContainsKey(keypair.Key)) + { + dicResult[keypair.Key] = dicValue; + } + else + { + dicResult.Add(keypair.Key, dicValue); + } + } + } + return dicResult; + } + + public static string StrToLower(string str) + { + if (string.IsNullOrWhiteSpace(str)) + { + return string.Empty; + } + else + { + return str.ToLower(); + } + } + + public static int ParseInt(T data) + { + if (data == null) + { + throw new DaraException + { + Message = "Data is null." + }; + } + + return (int)double.Parse(data.ToString()); + } + + public static long ParseLong(T data) + { + if (data == null) + { + throw new DaraException + { + Message = "Data is null." + }; + } + + var str = new List(); + + + return (long)double.Parse(data.ToString()); + } + + public static float ParseFloat(T data) + { + if (data == null) + { + throw new DaraException + { + Message = "Data is null.." + }; + } + return (float)double.Parse(data.ToString()); + } + + public static bool ParseBool(T data) + { + if (data == null) + { + throw new DaraException + { + Message = "Data is null.." + }; + } + + var stringValue = data.ToString(); + + if (stringValue.Equals("true", StringComparison.OrdinalIgnoreCase) || + stringValue.Equals("1")) + { + return true; + } + if (stringValue.Equals("false", StringComparison.OrdinalIgnoreCase) || + stringValue.Equals("0")) + { + return false; + } + throw new DaraException + { + Message = "Cannot convert data to bool." + }; + } + } +} diff --git a/Tea/Utils/DictUtils.cs b/Darabonba/Utils/DictUtils.cs similarity index 57% rename from Tea/Utils/DictUtils.cs rename to Darabonba/Utils/DictUtils.cs index 108dcc7..198f5f0 100644 --- a/Tea/Utils/DictUtils.cs +++ b/Darabonba/Utils/DictUtils.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Tea.Utils +namespace Darabonba.Utils { internal class DictUtils { @@ -21,5 +21,21 @@ internal static string GetDicValue(Dictionary dic, string keyNam } return null; } + + internal static bool Contains(List list, string value) + { + if (list == null) + { + return false; + } + foreach (var item in list) + { + if (value != null && item != null && item == value) + { + return true; + } + } + return false; + } } } diff --git a/Tea/Utils/Extensions.cs b/Darabonba/Utils/Extensions.cs similarity index 92% rename from Tea/Utils/Extensions.cs rename to Darabonba/Utils/Extensions.cs index 0ee6b9d..267f407 100644 --- a/Tea/Utils/Extensions.cs +++ b/Darabonba/Utils/Extensions.cs @@ -2,10 +2,15 @@ using System.Collections; using System.Collections.Generic; -namespace Tea.Utils +namespace Darabonba.Utils { public static class Extensions { + public static bool IsNull(this T data) + { + return data == null; + } + public static string ToSafeString(this object obj, string defaultStr = null) { if (obj == null) diff --git a/Darabonba/Utils/FileFormStream.cs b/Darabonba/Utils/FileFormStream.cs new file mode 100644 index 0000000..f44baa3 --- /dev/null +++ b/Darabonba/Utils/FileFormStream.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Darabonba; + +namespace Darabonba.Utils +{ + public class FileField + { + [NameInMap("filename")] + public string Filename { get; set; } + + [NameInMap("contentType")] + public string ContentType { get; set; } + + [NameInMap("content")] + public Stream Content { get; set; } + } + + public class FileFormStream : Stream + { + + private Stream streamingStream { get; set; } + + private Dictionary form { get; set; } + + private string boundary; + + private List keys; + + private int index; + + private bool streaming; + + private long position; + + private long length; + + public FileFormStream(Dictionary form, string boundary) + { + this.form = form; + this.boundary = boundary; + index = 0; + keys = form.Keys.ToList(); + streaming = false; + } + + public override bool CanRead { get { return true; } } + + public override bool CanSeek { get { return false; } } + + public override bool CanWrite { get { return false; } } + + public override long Length { get { return length; } } + + public override long Position + { + get + { + return position; + } + set + { + position = value; + } + } + + public override void Flush() + { + throw new NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (streaming) + { + int bytesRLength; + if (streamingStream != null && (bytesRLength = streamingStream.Read(buffer, 0, buffer.Length)) != 0) + { + return bytesRLength; + } + else + { + streaming = false; + if (streamingStream != null && streamingStream.CanSeek) + { + streamingStream.Seek(0, SeekOrigin.Begin); + } + streamingStream = null; + byte[] bytesFileEnd = Encoding.UTF8.GetBytes("\r\n"); + for (int i = 0; i < bytesFileEnd.Length; i++) + { + buffer[i] = bytesFileEnd[i]; + } + index++; + return bytesFileEnd.Length; + } + } + + if (index < keys.Count) + { + string name = this.keys[this.index]; + object fieldValue = form[name]; + if (fieldValue is FileField) + { + FileField fileField = (FileField) fieldValue; + + streaming = true; + streamingStream = fileField.Content; + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append("--").Append(boundary).Append("\r\n"); + stringBuilder.Append("Content-Disposition: form-data; name=\"").Append(name).Append("\"; filename=\"").Append(fileField.Filename).Append("\"\r\n"); + stringBuilder.Append("Content-Type: ").Append(fileField.ContentType).Append("\r\n\r\n"); + byte[] startBytes = Encoding.UTF8.GetBytes(stringBuilder.ToString()); + for (int i = 0; i < startBytes.Length; i++) + { + buffer[i] = startBytes[i]; + } + return startBytes.Length; + } + else + { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append("--").Append(boundary).Append("\r\n"); + stringBuilder.Append("Content-Disposition: form-data; name=\"").Append(name).Append("\"\r\n\r\n"); + stringBuilder.Append(fieldValue.ToString()).Append("\r\n"); + byte[] formBytes = Encoding.UTF8.GetBytes(stringBuilder.ToString()); + for (int i = 0; i < formBytes.Length; i++) + { + buffer[i] = formBytes[i]; + } + index++; + return formBytes.Length; + } + } + else if (index == keys.Count) + { + string endStr = string.Format("--{0}--\r\n", boundary); + byte[] endBytes = Encoding.UTF8.GetBytes(endStr); + for (int i = 0; i < endBytes.Length; i++) + { + buffer[i] = endBytes[i]; + } + index++; + return endBytes.Length; + } + else + { + return 0; + } + } + + public new async Task ReadAsync(byte[] buffer, int offset, int count) + { + if (streaming) + { + int bytesRLength; + if (streamingStream != null && (bytesRLength = await streamingStream.ReadAsync(buffer, 0, buffer.Length)) != 0) + { + return bytesRLength; + } + else + { + streaming = false; + if (streamingStream != null) + { + streamingStream.Flush(); + streamingStream.Close(); + } + streamingStream = null; + byte[] bytesFileEnd = Encoding.UTF8.GetBytes("\r\n"); + for (int i = 0; i < bytesFileEnd.Length; i++) + { + buffer[i] = bytesFileEnd[i]; + } + index++; + return bytesFileEnd.Length; + } + } + + if (index < keys.Count) + { + string name = this.keys[this.index]; + object fieldValue = form[name]; + if (fieldValue is FileField) + { + FileField fileField = (FileField) fieldValue; + + streaming = true; + streamingStream = fileField.Content; + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append("--").Append(boundary).Append("\r\n"); + stringBuilder.Append("Content-Disposition: form-data; name=\"").Append(name).Append("\"; filename=\"").Append(fileField.Filename).Append("\"\r\n"); + stringBuilder.Append("Content-Type: ").Append(fileField.ContentType).Append("\r\n\r\n"); + byte[] startBytes = Encoding.UTF8.GetBytes(stringBuilder.ToString()); + for (int i = 0; i < startBytes.Length; i++) + { + buffer[i] = startBytes[i]; + } + return startBytes.Length; + } + else + { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append("--").Append(boundary).Append("\r\n"); + stringBuilder.Append("Content-Disposition: form-data; name=\"").Append(name).Append("\"\r\n\r\n"); + stringBuilder.Append(fieldValue.ToString()).Append("\r\n"); + byte[] formBytes = Encoding.UTF8.GetBytes(stringBuilder.ToString()); + for (int i = 0; i < formBytes.Length; i++) + { + buffer[i] = formBytes[i]; + } + index++; + return formBytes.Length; + } + } + else if (index == keys.Count) + { + string endStr = string.Format("--{0}--\r\n", boundary); + byte[] endBytes = Encoding.UTF8.GetBytes(endStr); + for (int i = 0; i < endBytes.Length; i++) + { + buffer[i] = endBytes[i]; + } + index++; + return endBytes.Length; + } + else + { + return 0; + } + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + length = value; + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + internal static string PercentEncode(string value) + { + if (value == null) + { + return null; + } + var stringBuilder = new StringBuilder(); + var text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; + var bytes = Encoding.UTF8.GetBytes(value); + foreach (char c in bytes) + { + if (text.IndexOf(c) >= 0) + { + stringBuilder.Append(c); + } + else + { + stringBuilder.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int) c)); + } + } + + return stringBuilder.ToString().Replace("+", "%20") + .Replace("*", "%2A").Replace("%7E", "~"); + } + } +} \ No newline at end of file diff --git a/Darabonba/Utils/FormUtil.cs b/Darabonba/Utils/FormUtil.cs new file mode 100644 index 0000000..2a1cd78 --- /dev/null +++ b/Darabonba/Utils/FormUtil.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Web; + +namespace Darabonba.Utils +{ + public class FormUtil + { + public static string ToFormString(Dictionary map) + { + if (map == null || map.Count <= 0) + { + return ""; + } + StringBuilder result = new StringBuilder(); + bool first = true; + foreach (var entry in map) + { + if (entry.Value == null) + { + continue; + } + if (first) + { + first = false; + } + else + { + result.Append("&"); + } + result.Append(HttpUtility.UrlEncode(entry.Key, Encoding.UTF8)); + result.Append("="); + result.Append(HttpUtility.UrlEncode(entry.Value.ToSafeString(""), Encoding.UTF8)); + } + return result.ToString(); + } + + public static string GetBoundary() + { + long num = (long)Math.Floor((new Random()).NextDouble() * 100000000000000D); ; + return num.ToString(); + } + + public static Stream ToFileForm(Dictionary form, string boundary) + { + return new FileFormStream(form, boundary); + } + } +} \ No newline at end of file diff --git a/Tea/Utils/HttpClientUtils.cs b/Darabonba/Utils/HttpClientUtils.cs similarity index 97% rename from Tea/Utils/HttpClientUtils.cs rename to Darabonba/Utils/HttpClientUtils.cs index 136b10b..cf758cd 100644 --- a/Tea/Utils/HttpClientUtils.cs +++ b/Darabonba/Utils/HttpClientUtils.cs @@ -9,7 +9,7 @@ using System.Security.Cryptography.X509Certificates; using System.Text; -namespace Tea.Utils +namespace Darabonba.Utils { public static class HttpClientUtils { diff --git a/Tea/Utils/HttpUtils.cs b/Darabonba/Utils/HttpUtils.cs similarity index 97% rename from Tea/Utils/HttpUtils.cs rename to Darabonba/Utils/HttpUtils.cs index 1c0674c..5f3aa23 100644 --- a/Tea/Utils/HttpUtils.cs +++ b/Darabonba/Utils/HttpUtils.cs @@ -1,6 +1,6 @@ using System.Net.Http; -namespace Tea.Utils +namespace Darabonba.Utils { internal static class HttpUtils { diff --git a/Darabonba/Utils/JSONUtil.cs b/Darabonba/Utils/JSONUtil.cs new file mode 100644 index 0000000..14a8a11 --- /dev/null +++ b/Darabonba/Utils/JSONUtil.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Darabonba.Utils +{ + public static class JSONUtil + { + public static string SerializeObject(object data) + { + if (data is string) + { + return data.ToString(); + } + return JsonConvert.SerializeObject(data); + } + + public static object Deserialize(object obj) + { + if (obj == null) + { + return null; + } + if (obj is JArray) + { + return DeserializeJArray((JArray) obj); + } + else if (obj is JObject) + { + return DeserializeJObject((JObject) obj); + } + else + { + return obj; + } + } + + public static Dictionary ParseToMap(object input) + { + if (input == null) + { + return null; + } + + var type = input.GetType(); + var map = (Dictionary)ModelExtensions.ToMapFactory(type, input); + + return map; + } + + private static Dictionary DeserializeJObject(JObject obj) + { + Dictionary dic = new Dictionary(); + Dictionary dicJObj = obj.ToObject>(); + foreach (var keypair in dicJObj) + { + dic.Add(keypair.Key, Deserialize(keypair.Value)); + } + return dic; + } + + private static List DeserializeJArray(JArray obj) + { + if (obj.Count == 0) + { + return new List(); + } + + if (obj[0].Type == JTokenType.Object) + { + List dicList = new List(); + List> dicObjList = obj.ToObject>>(); + foreach (Dictionary objItem in dicObjList) + { + Dictionary objDict = new Dictionary(); + foreach (var keypair in objItem) + { + objDict.Add(keypair.Key, Deserialize(keypair.Value)); + } + dicList.Add(objDict); + } + return dicList; + } + else if (obj[0].Type == JTokenType.Array) + { + List dicObjList = obj.ToObject>(); + List dicList = new List(); + foreach (var item in dicObjList) + { + dicList.Add(Deserialize((JArray) item)); + } + + return dicList; + } + else + { + List dicObjList = obj.ToObject>(); + return dicObjList; + } + } + + public static object ReadPath(object obj, string path) + { + object result; + string jsonStr; + if (obj is JObject) + { + jsonStr = JsonConvert.SerializeObject(obj); + result = JObject.Parse(jsonStr).SelectToken(path); + } + else + { + jsonStr = SerializeObject(ParseToMap(obj)); + result = JObject.Parse(jsonStr).SelectToken(path); + } + return ConvertNumber(result); + } + + private static object ConvertNumber(object input) + { + if (input == null) return null; + + var token = input as JToken; + if (token != null) + { + if (token.Type == JTokenType.Integer) + { + return token.ToObject(); + } + if (token.Type == JTokenType.Float) + { + return token.ToObject(); + } + if (token.Type == JTokenType.String) + { + return token.ToString(); + } + if (token.Type == JTokenType.Array) + { + return HandleList(token.Children()); + } + if (token.Type == JTokenType.Object) + { + return HandleMap(token.ToObject>()); + } + if (token.Type == JTokenType.Boolean) + { + return token.ToObject(); + } + } + + return input; + } + + private static object HandleList(IEnumerable list) + { + var convertedList = new List(); + foreach (var item in list) + { + convertedList.Add(ConvertNumber(item)); + } + return convertedList; + } + + private static object HandleMap(IDictionary map) + { + var convertedMap = new Dictionary(); + foreach (var entry in map) + { + convertedMap[entry.Key] = ConvertNumber(entry.Value); + } + return convertedMap; + } + } +} \ No newline at end of file diff --git a/Darabonba/Utils/ListUtil.cs b/Darabonba/Utils/ListUtil.cs new file mode 100644 index 0000000..a034c65 --- /dev/null +++ b/Darabonba/Utils/ListUtil.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using Darabonba.Exceptions; + +namespace Darabonba.Utils +{ + public class ListUtil + { + public static T Shift(List list) + { + if (list == null || list.Count == 0) + { + throw new DaraException + { + Message = "array is empty" + }; + } + T first = list[0]; + list.RemoveAt(0); + return first; + } + + public static int Unshift(List array, T data) + { + array.Insert(0, data); + return array.Count; + } + + + public static int Push(List array, T data) + { + array.Add(data); + return array.Count; + } + + public static T Pop(List list) + { + if (list == null || list.Count == 0) + { + throw new DaraException + { + Message = "array is empty" + }; + } + T last = list[list.Count - 1]; + list.RemoveAt(list.Count - 1); + return last; + } + + public static List Sort(List array, string order) where T : IComparable + { + if (order == "asc") + { + array.Sort(); + } + else if (order == "desc") + { + array.Sort((x, y) => y.CompareTo(x)); + } + return array; + } + + public static List Concat(List array1, List array2) + { + array1.AddRange(array2); + return array1; + } + } +} \ No newline at end of file diff --git a/Darabonba/Utils/MathUtil.cs b/Darabonba/Utils/MathUtil.cs new file mode 100644 index 0000000..73ed576 --- /dev/null +++ b/Darabonba/Utils/MathUtil.cs @@ -0,0 +1,64 @@ +using System; +using Darabonba.Exceptions; + +namespace Darabonba.Utils +{ + public class MathUtil + { + public static int Floor(double? num) + { + return (int)Math.Floor(num.Value); + } + + public static int Round(double? num) + { + return (int)Math.Round(num.Value, MidpointRounding.AwayFromZero); + } + + public static int ParseInt(T data) + { + if (data == null) + { + throw new DaraException + { + Message = "Data is null." + }; + } + return (int)double.Parse(data.ToString()); + } + + public static long ParseLong(T data) + { + if (data == null) + { + throw new DaraException + { + Message = "Data is null." + }; + } + return (long)double.Parse(data.ToString()); + } + + public static float ParseFloat(T data) + { + if (data == null) + { + throw new DaraException + { + Message = "Data is null." + }; + } + return (float)double.Parse(data.ToString()); + } + + public static T Min(T num1, T num2) where T : IComparable + { + return num1.CompareTo(num2) <= 0 ? num1 : num2; + } + + public static T Max(T num1, T num2) where T : IComparable + { + return num1.CompareTo(num2) <= 0 ? num2 : num1; + } + } +} \ No newline at end of file diff --git a/Darabonba/Utils/StreamUtil.cs b/Darabonba/Utils/StreamUtil.cs new file mode 100644 index 0000000..7d8e63c --- /dev/null +++ b/Darabonba/Utils/StreamUtil.cs @@ -0,0 +1,261 @@ +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; +using Newtonsoft.Json; +using System.Threading.Tasks; +using Darabonba.Models; + +namespace Darabonba.Utils +{ + public class StreamUtil + { + private const string DATA_PREFIX = "data:"; + private const string EVENT_PREFIX = "event:"; + private const string ID_PREFIX = "id:"; + private const string RETRY_PREFIX = "retry:"; + + public static Stream BytesReadable(string str) + { + return BytesReadable(Encoding.UTF8.GetBytes(str)); + } + + public static Stream BytesReadable(byte[] bytes) + { + MemoryStream stream = new MemoryStream(); + stream.Write(bytes, 0, bytes.Length); + stream.Seek(0, SeekOrigin.Begin); + return stream; + } + + public static string ToString(byte[] val) + { + return Encoding.UTF8.GetString(val); + } + + public static object ParseJSON(string val) + { + return JsonConvert.DeserializeObject(val); + } + + public static byte[] Read(Stream stream, int length) + { + byte[] data = new byte[length]; + stream.Read(data, 0, length); + return data; + } + + public static void Pipe(Stream readStream, Stream writeStream) + { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = readStream.Read(buffer, 0, buffer.Length)) > 0) + { + writeStream.Write(buffer, 0, bytesRead); + } + } + + public static Stream StreamFor(object data) + { + if (data is Stream) + { + Stream stream = data as Stream; + if (stream.CanRead) + { + Stream copy = new MemoryStream(); + stream.Position = 0; + stream.CopyTo(copy); + copy.Position = 0; + return copy; + } + throw new Exception("stream is not readable"); + } + if (data is string) + { + string str = data as string; + Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(str)); + return stream; + } + throw new Exception("data is not Stream or String"); + } + + public static byte[] ReadAsBytes(Stream stream) + { + int bufferLength = 4096; + using (var ms = new MemoryStream()) + { + var buffer = new byte[bufferLength]; + + while (true) + { + var length = stream.Read(buffer, 0, bufferLength); + if (length == 0) + { + break; + } + + ms.Write(buffer, 0, length); + } + + ms.Seek(0, SeekOrigin.Begin); + var bytes = new byte[ms.Length]; + ms.Read(bytes, 0, bytes.Length); + + stream.Close(); + stream.Dispose(); + + return bytes; + } + } + + public async static Task ReadAsBytesAsync(Stream stream) + { + int bufferLength = 4096; + using (var ms = new MemoryStream()) + { + var buffer = new byte[bufferLength]; + + while (true) + { + var length = await stream.ReadAsync(buffer, 0, bufferLength); + if (length == 0) + { + break; + } + + await ms.WriteAsync(buffer, 0, length); + } + + ms.Seek(0, SeekOrigin.Begin); + var bytes = new byte[ms.Length]; + await ms.ReadAsync(bytes, 0, bytes.Length); + + stream.Close(); + stream.Dispose(); + + return bytes; + } + } + + public static string ReadAsString(Stream stream) + { + return ToString(ReadAsBytes(stream)); + } + + public static async Task ReadAsStringAsync(Stream stream) + { + return ToString(await ReadAsBytesAsync(stream)); + } + + public static object ReadAsJSON(Stream stream) + { + object jResult = ParseJSON(ReadAsString(stream)); + object result = JSONUtil.Deserialize(jResult); + return result; + } + + public async static Task ReadAsJSONAsync(Stream stream) + { + object jResult = ParseJSON(await ReadAsStringAsync(stream)); + object result = JSONUtil.Deserialize(jResult); + return result; + } + + public class EventResult + { + public List Events { get; set; } + public string Remain { get; set; } + + public EventResult(List events, string remain) + { + Events = events; + Remain = remain; + } + } + + private static EventResult TryGetEvents(string head, string chunk) + { + string all = head + chunk; + var events = new List(); + var start = 0; + for (var i = 0; i < all.Length - 1; i++) + { + // message separated by \n\n + if (all[i] == '\n' && i + 1 < all.Length && all[i + 1] == '\n') + { + var rawEvent = all.Substring(start, i - start).Trim(); + var sseEvent = ParseEvent(rawEvent); + events.Add(sseEvent); + start = i + 2; + i++; + } + } + string remain = all.Substring(start); + return new EventResult(events, remain); + } + + private static SSEEvent ParseEvent(string rawEvent) + { + var sseEvent = new SSEEvent(); + var lines = rawEvent.Split('\n'); + + foreach (var line in lines) + { + if (line.StartsWith(DATA_PREFIX)) + { + sseEvent.Data = line.Substring(DATA_PREFIX.Length).Trim(); + } + else if (line.StartsWith(EVENT_PREFIX)) + { + sseEvent.Event = line.Substring(EVENT_PREFIX.Length).Trim(); + } + else if (line.StartsWith(ID_PREFIX)) + { + sseEvent.Id = line.Substring(ID_PREFIX.Length).Trim(); + } + else if (line.StartsWith(RETRY_PREFIX)) + { + var retryData = line.Substring(RETRY_PREFIX.Length).Trim(); + int retryValue; + if (int.TryParse(retryData, out retryValue)) + { + sseEvent.Retry = retryValue; + } + } + else if (line.StartsWith(":")) + { + // ignore the line + } + } + + return sseEvent; + } + + + public static IEnumerable ReadAsSSE(Stream stream) + { + using (var reader = new StreamReader(stream)) + { + var buffer = new char[4096]; + var rest = string.Empty; + int count; + + while ((count = reader.Read(buffer, 0, buffer.Length)) > 0) + { + var chunk = new string(buffer, 0, count); + + var eventResult = TryGetEvents(rest, chunk); + rest = eventResult.Remain; + + if (eventResult.Events != null && eventResult.Events.Count > 0) + { + foreach (var @event in eventResult.Events) + { + yield return @event; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Darabonba/Utils/StringUtil.cs b/Darabonba/Utils/StringUtil.cs new file mode 100644 index 0000000..13139bb --- /dev/null +++ b/Darabonba/Utils/StringUtil.cs @@ -0,0 +1,83 @@ +using System; +using System.Text.RegularExpressions; +using System.Globalization; +using Darabonba.Exceptions; + +namespace Darabonba.Utils +{ + public class StringUtil + { + public static string SubString(string str, int? start, int? end) + { + return str.Substring(start.Value, end.Value - start.Value); + } + + public static byte[] ToBytes(string data, string type) + { + return BytesUtil.From(data, type); + } + + private static string Replace(string data, string replacement, string pattern) + { + string regexPattern = @"\/(.*)\/([gi]*)$"; + Match match = Regex.Match(pattern, regexPattern); + if (match.Success) + { + string patternStr = match.Groups[1].Value; + string flags = match.Groups[2].Value; + if (flags == "g") + { + return Regex.Replace(data, patternStr, replacement, RegexOptions.None); + } + else if (flags == "gi") + { + return Regex.Replace(data, patternStr, replacement, RegexOptions.IgnoreCase); + } + else if (flags == "i") + { + Match matchFirst = Regex.Match(data, patternStr, RegexOptions.IgnoreCase); + if (matchFirst.Success) + { + return data.Remove(matchFirst.Index, matchFirst.Length).Insert(matchFirst.Index, replacement); + } + return data; + } + else if (flags == "") + { + Match matchFirst = Regex.Match(data, patternStr); + if (matchFirst.Success) + { + return data.Remove(matchFirst.Index, matchFirst.Length).Insert(matchFirst.Index, replacement); + } + return data; + } + } + try + { + return Regex.Replace(data, pattern, replacement, RegexOptions.None); + } + catch (Exception e) + { + throw new DaraException + { + Message = "Replace error occured: " + e.Message + }; + } + } + + public static int ParseInt(string data) + { + return (int)double.Parse(data, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.InvariantInfo); + } + + public static long ParseLong(string data) + { + return (long)double.Parse(data, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.InvariantInfo); + } + + public static float ParseFloat(string data) + { + return (float)double.Parse(data, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.InvariantInfo); + } + } +} \ No newline at end of file diff --git a/Darabonba/Utils/XmlUtil.cs b/Darabonba/Utils/XmlUtil.cs new file mode 100644 index 0000000..86f0988 --- /dev/null +++ b/Darabonba/Utils/XmlUtil.cs @@ -0,0 +1,362 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml; +using System.Xml.Linq; +using Darabonba; + +namespace Darabonba.Utils +{ + public class XmlUtil + { + public static Dictionary ParseXml(string body, Type response) + { + return DeserializeXml(body, response); + } + + public static string ToXML(Dictionary body) + { + return SerializeXml(body); + } + + internal static Dictionary DeserializeXml(string xmlStr, Type type) + { + Dictionary result = new Dictionary(); + XmlDocument contentXmlDoc = new XmlDocument(); + contentXmlDoc.LoadXml(xmlStr); + + XmlNodeList nodeList = contentXmlDoc.ChildNodes; + for (int i = 0; i < nodeList.Count; i++) + { + XmlNode root = nodeList.Item(i); + if (type != null) + { + PropertyInfo[] properties = type.GetProperties(); + foreach (PropertyInfo p in properties) + { + Type propertyType = p.PropertyType; + NameInMapAttribute attribute = p.GetCustomAttribute(typeof(NameInMapAttribute)) as NameInMapAttribute; + string realName = attribute == null ? p.Name : attribute.Name; + if (root.Name == realName) + { + if (!typeof(Model).IsAssignableFrom(propertyType)) + { + result.Add(realName, root.InnerText); + } + else + { + result.Add(realName, GetDictFromXml(root, propertyType)); + } + } + } + } + else + { + ElementToDict(root, result); + } + } + if (result.ContainsKey("xml") && result["xml"].ToString().Contains("version=\"1.0\"")) + { + result.Remove("xml"); + } + return result; + } + + private static Dictionary GetDictFromXml(XmlNode element, Type type) + { + Dictionary nodeDict = new Dictionary(); + PropertyInfo[] properties = type.GetProperties(); + for (int i = 0; i < properties.Length; i++) + { + PropertyInfo p = properties[i]; + Type propertyType = p.PropertyType; + NameInMapAttribute attribute = p.GetCustomAttribute(typeof(NameInMapAttribute)) as NameInMapAttribute; + string realName = attribute == null ? p.Name : attribute.Name; + XmlNodeList node = element.SelectNodes(realName); + + if (node != null && node.Count > 0) + { + int count = (node[0].OuterXml.Length - node[0].OuterXml.Replace(realName, "").Length) / realName.Length; + if (count > 1) + { + if (typeof(IList).IsAssignableFrom(propertyType)) + { + Type innerPropertyType = propertyType.GetGenericArguments()[0]; + if (typeof(Model).IsAssignableFrom(innerPropertyType)) + { + IList dicList = new List>(); + for (int j = 0; j < node.Count; j++) + { + dicList.Add(GetDictFromXml(node.Item(j), innerPropertyType)); + } + nodeDict.Add(realName, dicList); + } + else + { + var dicList = (IList)Activator.CreateInstance(propertyType); + for (int j = 0; j < node.Count; j++) + { + var value = mapObj(innerPropertyType, node.Item(j).InnerText); + dicList.Add(value); + } + nodeDict.Add(realName, dicList); + } + } + else if (typeof(Model).IsAssignableFrom(propertyType)) + { + nodeDict.Add(realName, GetDictFromXml(node.Item(0), propertyType)); + } + else + { + string value = node.Item(0).InnerText; + nodeDict.Add(realName, mapObj(propertyType, value)); + } + } + } + else + { + nodeDict.Add(realName, null); + } + } + return nodeDict; + } + + private static object mapObj(Type propertyType, string value) + { + if (value == null) + { + return null; + } + else if (propertyType == typeof(int?)) + { + return Convert.ToInt32(value); + } + else if (propertyType == typeof(long?)) + { + return Convert.ToInt64(value); + } + else if (propertyType == typeof(float?)) + { + return Convert.ToSingle(value); + } + else if (propertyType == typeof(double?)) + { + return Convert.ToDouble(value); + } + else if (propertyType == typeof(bool?)) + { + return Convert.ToBoolean(value); + } + else if (propertyType == typeof(short?)) + { + return Convert.ToInt16(value); + } + else if (propertyType == typeof(ushort?)) + { + return Convert.ToUInt16(value); + } + else if (propertyType == typeof(uint?)) + { + return Convert.ToUInt32(value); + } + else if (propertyType == typeof(ulong?)) + { + return Convert.ToUInt64(value); + } + else + { + return Convert.ChangeType(value, propertyType); + } + } + + private static object ElementToDict(XmlNode element, Dictionary nodeDict) + { + XmlNodeList elements = element.ChildNodes; + if (elements.Count == 0 || (elements.Count == 1 && !elements[0].HasChildNodes)) + { + string context = string.IsNullOrEmpty(element.InnerText.Trim()) ? null : element.InnerText.Trim(); + if (nodeDict != null) + { + nodeDict.Add(element.Name, context); + } + return context; + } + else + { + Dictionary subDict = new Dictionary(); + if (nodeDict != null) + { + nodeDict.Add(element.Name, subDict); + } + foreach (XmlNode subNode in elements) + { + if (subDict.ContainsKey(subNode.Name)) + { + object o = subDict[subNode.Name]; + Type type = o.GetType(); + if (typeof(IList).IsAssignableFrom(type)) + { + ((IList)o).Add(ElementToDict(subNode, null)); + } + else if (typeof(IDictionary).IsAssignableFrom(type)) + { + List list = new List(); + Dictionary remove = (Dictionary)subDict[subNode.Name]; + subDict.Remove(subNode.Name); + list.Add(remove); + list.Add(ElementToDict(subNode, null)); + subDict.Add(subNode.Name, list); + } + else + { + List list = new List(); + list.Add(o); + subDict.Remove(subNode.Name); + list.Add(ElementToDict(subNode, null)); + subDict.Add(subNode.Name, list); + } + } + else + { + ElementToDict(subNode, subDict); + } + } + return subDict; + } + } + + internal static string SerializeXml(object obj) + { + Type type = obj.GetType(); + if (typeof(Model).IsAssignableFrom(type)) + { + return SerializeXmlByModel((Model)obj); + } + else if (obj is Dictionary) + { + return SerializeXmlByDict((Dictionary)obj); + } + else + { + return string.Empty; + } + } + + internal static string SerializeXmlByModel(Model obj) + { + Type type = obj.GetType(); + PropertyInfo[] properties = type.GetProperties(); + if (obj == null || properties.Length == 0) + { + return string.Empty; + } + + PropertyInfo propertyInfo = properties[0]; + NameInMapAttribute attribute = propertyInfo.GetCustomAttribute(typeof(NameInMapAttribute)) as NameInMapAttribute; + string realName = attribute == null ? propertyInfo.Name : attribute.Name; + object rootObj = propertyInfo.GetValue(obj); + + XElement element = new XElement(realName); + GetXmlFactory(rootObj, element); + + return element.ToString(); + } + + private static string SerializeXmlByDict(Dictionary dict) + { + if (dict == null || dict.Count == 0) + { + return string.Empty; + } + + string nodeName = dict.Keys.ToList()[0]; + XElement element = new XElement(nodeName); + GetXmlFactory(dict[nodeName], element); + + return element.ToString(); + } + + private static void GetXmlFactory(object obj, XElement element, XElement xParent = null) + { + if (obj == null) + { + return; + } + Type type = obj.GetType(); + + if (typeof(IList).IsAssignableFrom(type)) + { + if (xParent == null) + { + throw new ArgumentException("unsupported nest list."); + } + IList list = (IList)obj; + string nodeName = element.Name.LocalName; + for (int j = 0; j < list.Count; j++) + { + XElement xNode = new XElement(nodeName); + GetXmlFactory(list[j], xNode); + xParent.Add(xNode); + } + return; + } + + if (typeof(Model).IsAssignableFrom(type)) + { + GetXml((Model)obj, element); + + } + else if (typeof(IDictionary).IsAssignableFrom(type)) + { + Dictionary newDict = CastDict((IDictionary)obj) + .ToDictionary(entry => (string)entry.Key, entry => entry.Value); + GetXml(newDict, element); + } + else + { + element.Add(obj); + } + + if (xParent != null) + { + xParent.Add(element); + } + + } + + private static IEnumerable CastDict(IDictionary dictionary) + { + foreach (DictionaryEntry entry in dictionary) + { + yield return entry; + } + } + + private static void GetXml(Dictionary dict, XElement element) + { + foreach (var keypair in dict) + { + XElement xNode = new XElement(keypair.Key); + GetXmlFactory(keypair.Value, xNode, element); + } + } + + private static void GetXml(Model model, XElement element) + { + Type type = model.GetType(); + PropertyInfo[] properties = type.GetProperties(); + for (int i = 0; i < properties.Length; i++) + { + PropertyInfo propertyInfo = properties[i]; + Type property = propertyInfo.PropertyType; + NameInMapAttribute attribute = propertyInfo.GetCustomAttribute(typeof(NameInMapAttribute)) as NameInMapAttribute; + string realName = attribute == null ? propertyInfo.Name : attribute.Name; + XElement node = new XElement(realName); + GetXmlFactory(propertyInfo.GetValue(model), node, element); + } + } + + } +} diff --git a/Tea/TeaValidator.cs b/Darabonba/Validator.cs similarity index 94% rename from Tea/TeaValidator.cs rename to Darabonba/Validator.cs index 3fdc84a..da2f090 100644 --- a/Tea/TeaValidator.cs +++ b/Darabonba/Validator.cs @@ -3,17 +3,17 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; -[assembly : InternalsVisibleTo("TeaUnitTests")] +[assembly : InternalsVisibleTo("DarabonbaUnitTests")] -namespace Tea +namespace Darabonba { - internal class TeaValidator + internal class Validator { public ValidationAttribute Attribute { get; set; } public string PropertyName { get; set; } - public TeaValidator(ValidationAttribute attribute, string propertyName) + public Validator(ValidationAttribute attribute, string propertyName) { Attribute = attribute; PropertyName = propertyName; diff --git a/DarabonbaUnitTests/CoreTest.cs b/DarabonbaUnitTests/CoreTest.cs new file mode 100644 index 0000000..1b4f55a --- /dev/null +++ b/DarabonbaUnitTests/CoreTest.cs @@ -0,0 +1,903 @@ +using System; +using System.Collections.Generic; +using System.Collections; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Darabonba; +using Darabonba.RetryPolicy; +using Darabonba.Exceptions; +using Darabonba.Utils; +using Xunit; + +namespace DaraUnitTests +{ + public class CoreTest + { + [Fact] + public void TestComposeUrl() + { + Request request = new Request(); + + var url = Core.ComposeUrl(request); + Assert.Equal("http://", url); + + request.Headers["host"] = "fake.domain.com"; + url = Core.ComposeUrl(request); + Assert.Equal("http://fake.domain.com", url); + + request.Port = 8080; + url = Core.ComposeUrl(request); + Assert.Equal("http://fake.domain.com:8080", url); + + request.Pathname = "/index.html"; + url = Core.ComposeUrl(request); + Assert.Equal("http://fake.domain.com:8080/index.html", url); + + request.Query["foo"] = ""; + url = Core.ComposeUrl(request); + Assert.Equal("http://fake.domain.com:8080/index.html?foo=", url); + + request.Query["foo"] = "bar"; + url = Core.ComposeUrl(request); + Assert.Equal("http://fake.domain.com:8080/index.html?foo=bar", url); + + request.Pathname = "/index.html?a=b"; + url = Core.ComposeUrl(request); + Assert.Equal("http://fake.domain.com:8080/index.html?a=b&foo=bar", url); + + request.Pathname = "/index.html?a=b&"; + url = Core.ComposeUrl(request); + Assert.Equal("http://fake.domain.com:8080/index.html?a=b&foo=bar", url); + + request.Query["fake"] = null; + url = Core.ComposeUrl(request); + Assert.Equal("http://fake.domain.com:8080/index.html?a=b&foo=bar", url); + + request.Query["fake"] = "val*"; + url = Core.ComposeUrl(request); + Assert.Equal("http://fake.domain.com:8080/index.html?a=b&foo=bar&fake=val%2A", url); + } + + [Fact] + public void TestDoAction() + { + Request request = new Request + { + Protocol = "http", + Method = "GET", + Headers = new Dictionary() + }; + request.Headers["host"] = "www.alibabacloud.com"; + request.Pathname = "/s/zh"; + request.Query = new Dictionary + { + { "k", "ecs" } + }; + Dictionary runtime = new Dictionary + { + { "readTimeout", 7000 }, + { "connectTimeout", 7000 }, + { "httpsProxy", "http://www.alibabacloud.com/s/zh?k=ecs" }, + { "ignoreSSL", true } + }; + + Response response = Core.DoAction(request, runtime); + Assert.NotNull(response); + + request.Protocol = "https"; + response = Core.DoAction(request); + Assert.NotNull(response); + + + string bodyStr = Core.GetResponseBody(response); + Assert.NotNull(bodyStr); + + request.Method = "POST"; + request.Body = new MemoryStream(Encoding.UTF8.GetBytes("test")); + request.Headers["content-test"] = "test"; + response = Core.DoAction(request, runtime); + Assert.NotNull(response); + + Request request404 = new Request + { + Protocol = "https", + Method = "GET", + Headers = new Dictionary() + }; + request404.Headers["host"] = "www.alibabacloud404.com"; + request404.Pathname = "/s/zh"; + request404.Query = new Dictionary + { + { "k", "ecs" } + }; + Dictionary runtime404 = new Dictionary + { + { "readTimeout", 7000 }, + { "connectTimeout", 7000 } + }; + Assert.Throws(() => { Core.DoAction(request404, runtime404); }); + + Request requestProxy = new Request(); + + + Request requestException = new Request + { + Protocol = "http", + Method = "GET", + Pathname = "/test" + }; + Dictionary runtimeException = new Dictionary(); + requestException.Headers["host"] = "www.aliyun.com"; + Response responseException = Core.DoAction(requestException, runtimeException); + Assert.NotNull(responseException); + } + + [Fact] + public async Task TestDoActionAsync() + { + Request request = new Request + { + Protocol = "https", + Method = "GET", + Headers = new Dictionary() + }; + request.Headers["host"] = "www.alibabacloud.com"; + request.Pathname = "/s/zh"; + request.Query = new Dictionary + { + { "k", "ecs" } + }; + + Response response = await Core.DoActionAsync(request); + Assert.NotNull(response); + + Dictionary runtime = new Dictionary + { + { "readTimeout", 4000 }, + { "connectTimeout", 0 } + }; + + response = await Core.DoActionAsync(request, runtime); + Assert.NotNull(response); + + string bodyStr = Core.GetResponseBody(response); + Assert.NotNull(bodyStr); + + request.Method = "POST"; + request.Body = new MemoryStream(Encoding.UTF8.GetBytes("test")); + response = await Core.DoActionAsync(request, runtime); + Assert.NotNull(response); + + Request request404 = new Request + { + Protocol = "https", + Method = "GET", + Headers = new Dictionary() + }; + request404.Headers["host"] = "www.alibabacloud404.com"; + request404.Pathname = "/s/zh"; + request404.Query = new Dictionary + { + { "k", "ecs" } + }; + Dictionary runtime404 = new Dictionary + { + { "readTimeout", 7000 }, + { "connectTimeout", 7000 } + }; + await Assert.ThrowsAsync(async () => { await Core.DoActionAsync(request404, runtime); }); + + Request requestException = new Request + { + Protocol = "http", + Method = "GET", + Pathname = "/test" + }; + Dictionary runtimeException = new Dictionary(); + requestException.Headers["host"] = "www.aliyun.com"; + Response responseException = await Core.DoActionAsync(requestException, runtimeException); + } + + [Fact] + public void TestConvertHeaders() + { + WebHeaderCollection headers = new WebHeaderCollection + { + { "testKey", "testValue" } + }; + Dictionary dic = Core.ConvertHeaders(headers); + Assert.NotNull(dic); + Assert.True(dic.ContainsKey("testkey")); + Assert.Equal("testValue", dic["testkey"]); + } + + [Fact] + public void TestAllowRetry() + { + long _now = DateTime.Now.Millisecond; + + Assert.True(Core.AllowRetry(null, 0, _now)); + + Assert.False(Core.AllowRetry(null, 3, _now)); + + Dictionary dic = new Dictionary(); + Assert.False(Core.AllowRetry(dic, 3, _now)); + + dic.Add("retryable", true); + dic.Add("maxAttempts", null); + Assert.False(Core.AllowRetry(dic, 3, _now)); + + dic["maxAttempts"] = 5; + Assert.True(Core.AllowRetry(dic, 3, _now)); + + Dictionary dicInt = new Dictionary(); + Assert.False(Core.AllowRetry(dicInt, 3, _now)); + } + + public class AException : DaraException + { + public AException() : base() + { + } + + public AException(IDictionary dict) : base(dict) + { + } + } + + public class BException : DaraException + { + public BException() : base() + { + } + + public BException(IDictionary dict) : base(dict) + { + } + } + + public class CException : DaraException + { + public CException() : base() + { + } + + public CException(IDictionary dict) : base(dict) + { + } + } + + [Fact] + public void TestShouldRetry() + { + var backoffPolicy = new ExponentialBackoffPolicy(2, 60 * 1000); + + var retryCondition1 = new RetryCondition + { + MaxAttempts = 1, + Backoff = backoffPolicy, + Exception = new List { "AException" }, + ErrorCode = new List { "BExceptionCode" } + }; + + var retryCondition2 = new RetryCondition + { + MaxAttempts = 2, + Backoff = backoffPolicy, + Exception = new List { "AException", "CException" } + }; + + var retryCondition3 = new RetryCondition + { + Exception = new List { "BException" } + }; + + var retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 0, + Exception = new AException + { + Message = "AException", + Code = "AExceptionCode" + } + }; + + Assert.True(Core.ShouldRetry(null, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new AException + { + Message = "AException", + Code = "AExceptionCode" + } + }; + + Assert.False(Core.ShouldRetry(null, retryPolicyContext)); + + var retryOptions = new RetryOptions + { + Retryable = false, + RetryCondition = null, + NoRetryCondition = null + }; + + Assert.False(Core.ShouldRetry(retryOptions, retryPolicyContext)); + + retryOptions = new RetryOptions + { + Retryable = true, + RetryCondition = null, + NoRetryCondition = null + }; + + Assert.False(Core.ShouldRetry(retryOptions, retryPolicyContext)); + + retryOptions = new RetryOptions + { + Retryable = true, + RetryCondition = new List { retryCondition1, retryCondition2 }, + NoRetryCondition = new List { retryCondition3 } + }; + + Assert.True(Core.ShouldRetry(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 2, + Exception = new AException + { + Message = "AException", + Code = "AExceptionCode" + } + }; + + Assert.False(Core.ShouldRetry(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 0, + Exception = new BException + { + Message = "BException", + Code = "BExceptionCode" + } + }; + + Assert.True(Core.ShouldRetry(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new BException + { + Message = "BException", + Code = "BExceptionCode" + } + }; + + Assert.False(Core.ShouldRetry(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new CException + { + Message = "CException", + Code = "CExceptionCode" + } + }; + + Assert.True(Core.ShouldRetry(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 2, + Exception = new CException + { + Message = "CException", + Code = "CExceptionCode" + } + }; + + Assert.True(Core.ShouldRetry(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 3, + Exception = new CException + { + Message = "CException", + Code = "CExceptionCode" + } + }; + + Assert.False(Core.ShouldRetry(retryOptions, retryPolicyContext)); + } + + [Fact] + public void TestGetBackoffTime() + { + Dictionary dic = new Dictionary(); + Assert.Equal(0, Core.GetBackoffTime(dic, 1)); + + dic.Add("policy", null); + Assert.Equal(0, Core.GetBackoffTime(dic, 1)); + + dic["policy"] = string.Empty; + Assert.Equal(0, Core.GetBackoffTime(dic, 1)); + + dic["policy"] = "no"; + Assert.Equal(0, Core.GetBackoffTime(dic, 1)); + + dic["policy"] = "yes"; + Assert.Equal(0, Core.GetBackoffTime(dic, 1)); + + dic.Add("period", null); + Assert.Equal(0, Core.GetBackoffTime(dic, 1)); + + dic["period"] = -1; + Assert.Equal(1, Core.GetBackoffTime(dic, 1)); + + dic["period"] = 1000; + Assert.Equal(1000, Core.GetBackoffTime(dic, 1)); + } + + [Fact] + public void TestGetBackoffDelay() + { + BackoffPolicy backoffPolicy = new ExponentialBackoffPolicy(200, 60 * 1000); + + RetryCondition retryCondition1 = new RetryCondition + { + MaxAttempts = 1, + Backoff = backoffPolicy, + Exception = new List { "AException" }, + ErrorCode = new List { "BExceptionCode" } + }; + + RetryCondition retryCondition2 = new RetryCondition + { + MaxAttempts = 2, + Backoff = backoffPolicy, + Exception = new List { "AException", "CException" } + }; + + RetryCondition retryCondition3 = new RetryCondition + { + Exception = new List { "BException" } + }; + + RetryPolicyContext retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 0, + Exception = new AException + { + Message = "AException", + Code = "AExceptionCode" + } + }; + + Assert.Equal(0, Core.GetBackoffDelay(null, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new AException + { + Message = "AException", + Code = "AExceptionCode" + } + }; + + Assert.Equal(100, Core.GetBackoffDelay(null, retryPolicyContext)); + + RetryOptions retryOptions = new RetryOptions + { + Retryable = false, + RetryCondition = null, + NoRetryCondition = null + }; + + Assert.Equal(100, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryOptions = new RetryOptions + { + Retryable = true, + RetryCondition = new List { retryCondition1, retryCondition2 }, + NoRetryCondition = new List { retryCondition3 } + }; + + Assert.Equal(400, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 2, + Exception = new AException + { + Message = "AException", + Code = "AExceptionCode" + } + }; + + Assert.Equal(800, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new BException + { + Message = "BException", + Code = "BExceptionCode" + } + }; + + Assert.Equal(400, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new CException + { + Message = "CException", + Code = "CExceptionCode" + } + }; + + Assert.Equal(400, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 2, + Exception = new CException + { + Message = "CException", + Code = "CExceptionCode" + } + }; + Assert.Equal(800, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 3, + Exception = new CException + { + Message = "CException", + Code = "CExceptionCode" + } + }; + Assert.Equal(1600, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + RetryCondition retryCondition4 = new RetryCondition + { + MaxAttempts = 20, + Backoff = backoffPolicy, + Exception = new List { "AException" } + }; + + retryOptions = new RetryOptions + { + RetryCondition = new List { retryCondition4 } + }; + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 10, + Exception = new AException + { + Message = "AException", + Code = "AExceptionCode" + } + }; + Assert.Equal(60000, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + backoffPolicy = new ExponentialBackoffPolicy(200, 180 * 1000); + + retryCondition4 = new RetryCondition + { + MaxAttempts = 20, + Backoff = backoffPolicy, + Exception = new List { "AException" } + }; + + retryOptions = new RetryOptions + { + Retryable = true, + RetryCondition = new List { retryCondition4 } + }; + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 10, + Exception = new AException + { + Message = "AException", + Code = "AExceptionCode" + } + }; + + Assert.Equal(120000, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 15, + Exception = new AException + { + Message = "AException", + Code = "AExceptionCode" + } + }; + Assert.Equal(120000, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryCondition4 = new RetryCondition + { + MaxAttempts = 20, + MaxDelayTimeMillis = 30 * 1000, + Backoff = backoffPolicy, + Exception = new List { "AException" } + }; + + retryOptions = new RetryOptions + { + Retryable = true, + RetryCondition = new List { retryCondition4 } + }; + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 10, + Exception = new AException + { + Message = "AException", + Code = "AExceptionCode" + } + }; + + Assert.Equal(30000, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 15, + Exception = new AException + { + Message = "AException", + Code = "AExceptionCode" + } + }; + + Assert.Equal(30000, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryCondition4 = new RetryCondition + { + MaxAttempts = 20, + Backoff = null, + Exception = new List { "AException" } + }; + + retryOptions = new RetryOptions + { + Retryable = true, + RetryCondition = new List { retryCondition4 } + }; + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 10, + Exception = new AException + { + Message = "AException", + Code = "AExceptionCode" + } + }; + Assert.Equal(100, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + } + + + public class ThrottlingException : DaraResponseException + { + public ThrottlingException() : base() + { + } + } + + [Fact] + public void TestThrottlingBackoffDelay() + { + BackoffPolicy backoffPolicy = new ExponentialBackoffPolicy(200, 60 * 1000); + RetryCondition retryCondition = new RetryCondition + { + MaxAttempts = 1, + Backoff = backoffPolicy, + Exception = new List { "ThrottlingException" } + }; + + RetryPolicyContext retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new ThrottlingException { } + }; + Assert.Equal(100, Core.GetBackoffDelay(null, retryPolicyContext)); + + RetryOptions retryOptions = new RetryOptions + { + Retryable = false, + RetryCondition = new List { retryCondition }, + NoRetryCondition = null + }; + Assert.Equal(100, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryOptions = new RetryOptions + { + Retryable = true, + RetryCondition = new List { retryCondition }, + NoRetryCondition = null + }; + Assert.Equal(100, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new ThrottlingException { + Message = "ThrottlingException" + } + }; + Assert.Equal(400, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new ThrottlingException { + // TODO retryable true + Message = "ThrottlingException", + RetryAfter = 2000L + } + }; + Assert.Equal(2000, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new ThrottlingException { + // TODO retryable true + Message = "ThrottlingException", + RetryAfter = 320 * 1000L + } + }; + Assert.Equal(120000, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryCondition = new RetryCondition + { + MaxAttempts = 1, + Backoff = backoffPolicy, + ErrorCode = new List { "Throttling", "Throttling.User", "Throttling.Api"} + }; + retryOptions = new RetryOptions + { + Retryable = true, + RetryCondition = new List { retryCondition }, + NoRetryCondition = null + }; + Assert.Equal(100, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new ThrottlingException { + // TODO retryable true + Code = "Throttling", + RetryAfter = 2000L + } + }; + Assert.Equal(2000, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new ThrottlingException { + // TODO retryable true + Code = "Throttling.User", + RetryAfter = 2000L + } + }; + Assert.Equal(2000, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + + retryPolicyContext = new RetryPolicyContext + { + RetriesAttempted = 1, + Exception = new ThrottlingException { + // TODO retryable true + Code = "Throttling.Api", + RetryAfter = 2000L + } + }; + Assert.Equal(2000, Core.GetBackoffDelay(retryOptions, retryPolicyContext)); + } + + [Fact] + public void TestSleep() + { + TimeSpan tsBefore = new TimeSpan(DateTime.Now.Ticks); + Core.Sleep(1000); + TimeSpan tsAfter = new TimeSpan(DateTime.Now.Ticks); + TimeSpan tsSubtract = tsBefore.Subtract(tsAfter).Duration(); + Assert.InRange(tsSubtract.TotalMilliseconds, 990, 1100); + } + + [Fact] + public async void TestSleepAsync() + { + TimeSpan tsBefore = new TimeSpan(DateTime.Now.Ticks); + await Core.SleepAsync(1000); + TimeSpan tsAfter = new TimeSpan(DateTime.Now.Ticks); + TimeSpan tsSubtract = tsBefore.Subtract(tsAfter).Duration(); + Assert.InRange(tsSubtract.TotalMilliseconds, 990, 1000000); + } + + [Fact] + public void TestIsRetryable() + { + Exception ex = new Exception(); + Assert.False(Core.IsRetryable(ex)); + + DaraRetryableException webEx = new DaraRetryableException(); + Assert.True(Core.IsRetryable(webEx)); + } + + [Fact] + public void TestBytesReadable() + { + string str = "test"; + Stream stream = StreamUtil.BytesReadable(str); + byte[] bytes = new byte[stream.Length]; + stream.Read(bytes, 0, bytes.Length); + string bytesStr = Encoding.UTF8.GetString(bytes); + Assert.Equal("test", bytesStr); + } + + [Fact] + public void Test_PercentEncode() + { + Assert.Null(Core.PercentEncode(null)); + + Assert.Equal("test%3D", Core.PercentEncode("test=")); + } + + [Fact] + public void Test_ThrowException() + { + var retryPolicyContext = new RetryPolicyContext + { + Request = new Request(), + Exception = new DaraException() + }; + try + { + throw Core.ThrowException(retryPolicyContext); + } + catch (Exception e) + { + Assert.Equal("Darabonba.Exceptions.DaraException", e.GetType().ToString()); + } + + retryPolicyContext = new RetryPolicyContext + { + Request = new Request(), + Exception = new IOException() + }; + try + { + throw Core.ThrowException(retryPolicyContext); + } + catch (Exception e) + { + Assert.Equal("Darabonba.Exceptions.DaraUnRetryableException", e.GetType().ToString()); + } + } + } +} diff --git a/TeaUnitTests/TeaUnitTests.csproj b/DarabonbaUnitTests/DarabonbaUnitTests.csproj similarity index 82% rename from TeaUnitTests/TeaUnitTests.csproj rename to DarabonbaUnitTests/DarabonbaUnitTests.csproj index e417cdc..8f0bb77 100644 --- a/TeaUnitTests/TeaUnitTests.csproj +++ b/DarabonbaUnitTests/DarabonbaUnitTests.csproj @@ -1,10 +1,10 @@  - netcoreapp2.0 + netcoreapp3.1 false false - TeaUnitTests + DaraUnitTests @@ -23,7 +23,7 @@ - + diff --git a/DarabonbaUnitTests/DateTest.cs b/DarabonbaUnitTests/DateTest.cs new file mode 100644 index 0000000..afb1628 --- /dev/null +++ b/DarabonbaUnitTests/DateTest.cs @@ -0,0 +1,74 @@ +using Darabonba; +using Xunit; +using System; + +namespace DaraUnitTests +{ + public class DateTest + { + Date dateLocal = new Date("2023-12-31 00:00:00.916000"); + Date dateUTC = new Date("2023-12-31 00:00:00.916000 +0000"); + + [Fact] + public void Test_TimestampStr() + { + Date date = new Date("1723081751"); + Assert.Equal("2024-08-08 01:49:11.000000 +0000 UTC", date.DateTime.ToString("yyyy-MM-dd HH:mm:ss.ffffff '+0000 UTC'")); + } + + [Fact] + public void Test_Init_NoTimeZone() + { + Assert.Equal("2023-12-31 00:00:00.916000", dateLocal.DateTime.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss.ffffff")); + } + + [Fact] + public void Test_Init_WithTimeZone() + { + DateTime expectedDate = DateTimeOffset.Parse("2023-12-31 00:00:00.916000 +0000").UtcDateTime; + Assert.Equal(expectedDate, dateUTC.DateTime); + } + + [Fact] + public void Test_Format() + { + Assert.Equal("2023-12-31 00:00:00.916", dateUTC.Format("yyyy-MM-dd HH:mm:ss.fff")); + Assert.Equal("2023-12-31 00:00:00", dateUTC.Format("yyyy-MM-dd HH:mm:ss")); + Assert.Equal("2023-12-31T00:00:00Z", dateUTC.Format("yyyy-MM-ddTHH:mm:ssZ")); + } + + [Fact] + public void Test_Unix() + { + Assert.Equal(1703980800, dateUTC.Unix()); + Assert.Equal(1703980800, dateLocal.Unix()); + } + + [Fact] + public void Test_UTC() + { + Assert.Equal("2023-12-31 00:00:00.916000 +0000 UTC", dateUTC.UTC()); + // Local time + Assert.Equal("2023-12-31 00:00:00.916000 +0000 UTC", dateLocal.UTC()); + } + + [Fact] + public void Test_Methods() + { + Date yesterday = dateUTC.Sub("day", 1); + Assert.Equal("2023-12-30 00:00:00.916", yesterday.Format("yyyy-MM-dd HH:mm:ss.fff")); + Assert.Equal(1, dateUTC.Diff("day", yesterday)); + Date tomorrow = dateUTC.Add("day", 1); + Assert.Equal(-1, dateUTC.Diff("day", tomorrow)); + Assert.Equal(2023, dateUTC.Year()); + Assert.Equal(2024, tomorrow.Year()); + Assert.Equal(1, tomorrow.Month()); + Assert.Equal(12, dateUTC.Month()); + Assert.Equal(0, dateUTC.Hour()); + Assert.Equal(0, dateUTC.Minute()); + Assert.Equal(0, dateUTC.Second()); + Assert.Equal(31, dateUTC.DayOfMonth()); + Assert.Equal(7, dateUTC.DayOfWeek()); + } + } +} \ No newline at end of file diff --git a/DarabonbaUnitTests/Exceptions/DaraExceptionTest.cs b/DarabonbaUnitTests/Exceptions/DaraExceptionTest.cs new file mode 100644 index 0000000..cf49d1e --- /dev/null +++ b/DarabonbaUnitTests/Exceptions/DaraExceptionTest.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Darabonba.Exceptions; +using Tea; +using Darabonba.Utils; +using Xunit; +using Xunit.Abstractions; + +namespace DaraUnitTests +{ + public class DaraExceptionTest + { + [Fact] + public void TestDaraException_new() + { + var daraException = new DaraException + { + Message = "message", + Code = "200", + DataResult = new Dictionary + { + { "test", "test" } + } + }; + Assert.NotNull(daraException); + Assert.Equal("200", daraException.Code); + Assert.Equal("message", daraException.Message); + Assert.NotNull(daraException.DataResult); + Assert.Null(daraException.AccessDeniedDetail); + + daraException = new DaraException + { + Message = "message", + Code = "200", + AccessDeniedDetail = new Dictionary + { + { "NoPermissionType", "ImplicitDeny" } + } + }; + Assert.NotNull(daraException); + Assert.Equal("200", daraException.Code); + Assert.Equal("message", daraException.Message); + Assert.Null(daraException.DataResult); + Assert.NotNull(daraException.AccessDeniedDetail); + Assert.Equal("ImplicitDeny", DictUtils.GetDicValue(daraException.AccessDeniedDetail, "NoPermissionType")); + } + + [Fact] + public void TestDaraException_compatible() + { + var daraException = new DaraException(new Dictionary + { + { "code", "200" }, + { "message", "message" }, + { "data", null } + }); + + Assert.NotNull(daraException); + Assert.Equal("200", daraException.Code); + Assert.Equal("message", daraException.Message); + Assert.Null(daraException.DataResult); + + daraException = new DaraException(new Dictionary + { + { "code", "200" }, + { "message", "message" }, + { + "data", + new Dictionary + { + { "test", "test" } + } + } + }); + Assert.NotNull(daraException); + Assert.NotNull(daraException.DataResult); + + daraException = new DaraException(new Dictionary + { + { "code", "200" }, + { "message", "message" }, + { + "data", + new + { + test = "test" + } + } + }); + Assert.NotNull(daraException); + Assert.NotNull(daraException.DataResult); + + daraException = new DaraException(new Dictionary + { + { "code", "200" } + }); + Assert.NotNull(daraException); + Assert.Equal("200", daraException.Code); + + daraException = new DaraException(new Dictionary + { + { "code", "code" }, + { "message", "message" }, + { "description", "description" }, + { + "data", + new Dictionary + { + { "test", "test" }, + { "statusCode", 200 } + } + }, + { + "accessDeniedDetail", + new Dictionary + { + { "NoPermissionType", "ImplicitDeny" } + } + } + }); + Assert.NotNull(daraException); + Assert.Equal("code", daraException.Code); + Assert.Equal("message", daraException.Message); + Assert.Equal("description", daraException.Description); + Assert.Equal(200, daraException.StatusCode); + Assert.Equal("ImplicitDeny", DictUtils.GetDicValue(daraException.AccessDeniedDetail, "NoPermissionType")); + + daraException = new DaraException(new Dictionary + { + { "code", "code" }, + { + "accessDeniedDetail", null + } + }); + Assert.NotNull(daraException); + Assert.Null(daraException.AccessDeniedDetail); + + daraException = new DaraException(new Dictionary + { + { "code", "code" }, + { + "accessDeniedDetail", "error type" + } + }); + Assert.NotNull(daraException); + Assert.Null(daraException.AccessDeniedDetail); + } + + [Fact] + public void Test_Throw_And_Catch() + { + try + { + throw new DaraException + { + Message = "msg", + Code = "200", + AccessDeniedDetail = new Dictionary + { + { "NoPermissionType", "ImplicitDeny" } + }, + DataResult = new Dictionary + { + { "test", "test" } + } + }; + } + catch (TeaException e) + { + Assert.Equal("msg", e.Message); + Assert.Equal("200", e.Code); + Assert.Equal("ImplicitDeny", DictUtils.GetDicValue(e.AccessDeniedDetail, "NoPermissionType")); + Assert.Equal("test", DictUtils.GetDicValue(e.DataResult, "test")); + Assert.Equal(0, e.StatusCode); + } + + try + { + throw new TestException + { + TestCode = 123, + Code = "400" + }; + } + catch (TestException e) + { + Assert.Null(e.Message); + Assert.Equal("400", e.Code); + Assert.Null(e.AccessDeniedDetail); + Assert.Null(e.DataResult); + Assert.Equal(123, e.TestCode); + } + + try + { + throw new TestException + { + Message = "message", + Code = "400" + }; + } + catch (DaraException e) + { + Assert.Equal("message", e.Message); + Assert.Equal("400", e.Code); + Assert.Null(e.AccessDeniedDetail); + Assert.Null(e.DataResult); + } + } + } +} + +internal class TestException : DaraException +{ + public int TestCode { get; set; } +} \ No newline at end of file diff --git a/DarabonbaUnitTests/Exceptions/DaraRetryableExceptionTest.cs b/DarabonbaUnitTests/Exceptions/DaraRetryableExceptionTest.cs new file mode 100644 index 0000000..d670eba --- /dev/null +++ b/DarabonbaUnitTests/Exceptions/DaraRetryableExceptionTest.cs @@ -0,0 +1,24 @@ +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text; +using Darabonba; +using Darabonba.Exceptions; +using Xunit; + +namespace DaraUnitTests.Exceptions +{ + public class DaraRetryableExceptionTest + { + [Fact] + public void TestDaraRetryableException() + { + HttpResponseMessage httpResponseMessage = new HttpResponseMessage(); + httpResponseMessage.StatusCode = HttpStatusCode.OK; + httpResponseMessage.Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("test"))); + Response response = new Response(httpResponseMessage); + DaraRetryableException daraRetryableException = new DaraRetryableException(); + Assert.NotNull(daraRetryableException); + } + } +} diff --git a/DarabonbaUnitTests/Exceptions/TeaUnretryableExceptionTest.cs b/DarabonbaUnitTests/Exceptions/TeaUnretryableExceptionTest.cs new file mode 100644 index 0000000..71d2a85 --- /dev/null +++ b/DarabonbaUnitTests/Exceptions/TeaUnretryableExceptionTest.cs @@ -0,0 +1,74 @@ +using System; +using Darabonba; +using Darabonba.Exceptions; +using Darabonba.RetryPolicy; +using Tea; +using Xunit; +using Xunit.Abstractions; + +namespace DaraUnitTests.Exceptions +{ + public class TeaUnretryableExceptionTest + { + private readonly ITestOutputHelper _testOutputHelper; + + public TeaUnretryableExceptionTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + [Fact] + public void TestDaraUnRetryableException() + { + var retryPolicyContext = new RetryPolicyContext + { + Request = new Request(), + Exception = new DaraException + { + Message = "Exception" + } + }; + try + { + throw new DaraUnRetryableException(retryPolicyContext); + } + catch (TeaUnretryableException e) + { + Assert.NotNull(e); + Assert.NotNull(e.InnerException); + Assert.Equal(" Retry failed : Exception", e.Message); + Assert.NotNull(e.LastRequest); + Assert.True(e.LastRequest != null); + } + + try + { + throw new TestUnRetryableException + { + TestCode = "200" + }; + } + catch (TestUnRetryableException e) + { + Assert.NotNull(e); + Assert.Null(e.InnerException); + Assert.Equal("200", e.TestCode); + } + + try + { + throw new DaraRetryableException(); + } + catch (DaraRetryableException e) + { + Assert.NotNull(e); + Assert.Equal("Exception of type 'Darabonba.Exceptions.DaraRetryableException' was thrown.", e.Message); + } + } + } +} + +internal class TestUnRetryableException : DaraUnRetryableException +{ + public string TestCode { get; set; } +} \ No newline at end of file diff --git a/DarabonbaUnitTests/FileTest.cs b/DarabonbaUnitTests/FileTest.cs new file mode 100644 index 0000000..0ac2559 --- /dev/null +++ b/DarabonbaUnitTests/FileTest.cs @@ -0,0 +1,180 @@ +using System.Text; +using Darabonba; +using Xunit; +using System; +using System.IO; +using System.Threading.Tasks; +using File = Darabonba.File; + +namespace DaraUnitTests +{ + public class FileTest : IAsyncLifetime + { + private File _file; + private FileInfo _fileInfo; + + private string tempTestFile = Path.GetTempFileName(); + + public async Task InitializeAsync() + { + System.IO.File.WriteAllText(tempTestFile, "Test For File"); + _file = new File(tempTestFile); + _fileInfo = new FileInfo(tempTestFile); + } + + public Task DisposeAsync() + { + _file.Close(); + System.IO.File.Delete(tempTestFile); + return Task.CompletedTask; + } + + [Fact] + public async Task Test_All() + { + TestPath(); + await TestCreateTimeAsync(); + await TestModifyTimeAsync(); + await TestLengthAsync(); + TestExists(); + await TestExistsAsync(); + TestRead(); + await TestReadAsync(); + TestWrite(); + await TestWriteAsync(); + TestCreateWriteStream(); + TestCreateReadStream(); + } + + private void TestPath() + { + Assert.Equal(tempTestFile, _file.Path()); + } + + private async Task TestCreateTimeAsync() + { + var createTime = await _file.CreateTimeAsync(); + Assert.Equal(_fileInfo.CreationTimeUtc.ToString("yyyy-MM-dd HH:mm:ssZ"), createTime.DateTime.ToString("yyyy-MM-dd HH:mm:ssZ")); + } + + private async Task TestModifyTimeAsync() + { + var modifyTime = await _file.ModifyTimeAsync(); + Assert.Equal(_fileInfo.LastWriteTimeUtc.ToString("yyyy-MM-dd HH:mm:ssZ"), modifyTime.DateTime.ToString("yyyy-MM-dd HH:mm:ssZ")); + } + + private async Task TestLengthAsync() + { + var length = await _file.LengthAsync(); + Assert.Equal(_fileInfo.Length, length); + string tempTestFile1 = Path.GetTempFileName(); + System.IO.File.WriteAllText(tempTestFile1, "Hello, World!"); + var newFile = new File(tempTestFile1); + var newLength = await newFile.LengthAsync(); + Assert.Equal(_fileInfo.Length, newLength); + await newFile.CloseAsync(); + } + + private void TestExists() + { + Assert.True(File.Exists(tempTestFile)); + Assert.False(File.Exists("../../../../TeaUnitTests/Fixtures/test1.txt")); + } + + private async Task TestExistsAsync() + { + Assert.True(await File.ExistsAsync(tempTestFile)); + Assert.False(await File.ExistsAsync("../../../../TeaUnitTests/Fixtures/test1.txt")); + } + + private void TestRead() + { + byte[] text1 = _file.Read(4); + Assert.Equal("Test", Encoding.UTF8.GetString(text1)); + byte[] text2 = _file.Read(4); + Assert.Equal(" For", Encoding.UTF8.GetString(text2)); + Assert.Equal(8, _file._position); + string tempEmptyFile = Path.GetTempFileName(); + File emptyFile = new File(tempEmptyFile); + byte[] empty = emptyFile.Read(10); + Assert.Null(empty); + emptyFile.Close(); + } + + private async Task TestReadAsync() + { + _file._position = 0; + byte[] text1 = await _file.ReadAsync(4); + Assert.Equal("Test", Encoding.UTF8.GetString(text1)); + byte[] text2 = await _file.ReadAsync(4); + Assert.Equal(" For", Encoding.UTF8.GetString(text2)); + Assert.Equal(8, _file._position); + string tempEmptyFile = Path.GetTempFileName(); + File emptyFile = new File(tempEmptyFile); + byte[] empty = await emptyFile.ReadAsync(10); + Assert.Null(empty); + await emptyFile.CloseAsync(); + } + + private void TestWrite() + { + var expectedLen = _fileInfo.Length; + _file.Write(Encoding.UTF8.GetBytes(" Test")); + Date modifyTime = _file.ModifyTime(); + int length = _file.Length(); + Assert.Equal(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ssZ"), modifyTime.DateTime.ToString("yyyy-MM-dd HH:mm:ssZ")); + Assert.Equal(expectedLen + 5, length); + string tempNewFile = Path.GetTempFileName(); + File newFile = new File(tempNewFile); + newFile.Write(Encoding.UTF8.GetBytes("Test")); + byte[] text = newFile.Read(4); + Assert.Equal("Test", Encoding.UTF8.GetString(text)); + newFile.Close(); + } + + private async Task TestWriteAsync() + { + var expectedLen = _fileInfo.Length; + await _file.WriteAsync(Encoding.UTF8.GetBytes(" Test")); + Date modifyTime = await _file.ModifyTimeAsync(); + int length = await _file.LengthAsync(); + Assert.Equal(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ssZ"), modifyTime.DateTime.ToString("yyyy-MM-dd HH:mm:ssZ")); + Assert.Equal(expectedLen + 10, length); + string tempNewFile = Path.GetTempFileName(); + File newFile = new File(tempNewFile); + await newFile.WriteAsync(Encoding.UTF8.GetBytes("Test")); + byte[] text = await newFile.ReadAsync(4); + Assert.Equal("Test", Encoding.UTF8.GetString(text)); + await newFile.CloseAsync(); + } + + private void TestCreateWriteStream() + { + string tempWriteFile = Path.GetTempFileName(); + using (FileStream stream = File.CreateWriteStream(tempWriteFile)) + { + Assert.NotNull(stream); + Assert.True(stream.CanWrite); + byte[] contentBytes = Encoding.UTF8.GetBytes("Test Write"); + stream.Write(contentBytes, 0, contentBytes.Length); + } + string finalContent = System.IO.File.ReadAllText(tempWriteFile); + Assert.EndsWith("Test Write", finalContent); + } + + private void TestCreateReadStream() + { + string tempReadFile = Path.GetTempFileName(); + System.IO.File.WriteAllText(tempReadFile, "Test For File"); + using (FileStream stream = File.CreateReadStream(tempReadFile)) + { + Assert.NotNull(stream); + Assert.True(stream.CanRead); + byte[] buffer = new byte[13]; + int bytesRead = stream.Read(buffer, 0, buffer.Length); + string actualContent = Encoding.UTF8.GetString(buffer, 0, bytesRead); + Assert.Equal("Test For File", actualContent); + } + } + } +} \ No newline at end of file diff --git a/DarabonbaUnitTests/Fixtures/test.json b/DarabonbaUnitTests/Fixtures/test.json new file mode 100644 index 0000000..715b02d --- /dev/null +++ b/DarabonbaUnitTests/Fixtures/test.json @@ -0,0 +1 @@ +{"key":"value"} \ No newline at end of file diff --git a/TeaUnitTests/TeaModelTest.cs b/DarabonbaUnitTests/ModelTest.cs similarity index 83% rename from TeaUnitTests/TeaModelTest.cs rename to DarabonbaUnitTests/ModelTest.cs index 3d34549..7b7dc06 100644 --- a/TeaUnitTests/TeaModelTest.cs +++ b/DarabonbaUnitTests/ModelTest.cs @@ -2,17 +2,16 @@ using System.Collections; using System.Collections.Generic; using System.Text; -using Tea; - -using TeaUnitTests.Models; +using Darabonba; +using DaraUnitTests.Models; using Xunit; -namespace TeaUnitTests +namespace DaraUnitTests { - public class TeaModelTest + public class ModelTest { - public class NestedTest : TeaModel + public class NestedTest : Model { [NameInMap("MapNestedMap")] public Dictionary> mapnestedMap { get; set; } @@ -25,17 +24,19 @@ public class NestedTest : TeaModel [Fact] public void TestToMap() { - TeaModel modelNull = null; + Model modelNull = null; Assert.Null(modelNull.ToMap()); - TestRegModel model = new TestRegModel(); - model.RequestId = "requestID"; - model.Items = new List { new TestRegSubModel { RequestId = "sub" }, null }; - model.NextMarker = "next"; - model.testNoAttr = "noAttr"; - model.subModel = new TestRegSubModel(); - model.testListStr = new List { "str" }; - model.bytes = Encoding.UTF8.GetBytes("test"); + TestRegModel model = new TestRegModel + { + RequestId = "requestID", + Items = new List { new TestRegSubModel { RequestId = "sub" }, null }, + NextMarker = "next", + testNoAttr = "noAttr", + subModel = new TestRegSubModel(), + testListStr = new List { "str" }, + bytes = Encoding.UTF8.GetBytes("test") + }; TestRegSubModel dicSubModel = new TestRegSubModel { RequestId = "requestDic" @@ -62,7 +63,7 @@ public void TestToMap() Dictionary dic = model.ToMap(); Assert.NotNull(dic); - var from = TeaModel.ToObject(dic); + var from = Model.ToObject(dic); Assert.Equal("test", from.listIDic[0][0]["test"]); Assert.Equal("requestDic", from.dicNestDic["map"]["subDic"].RequestId); @@ -121,7 +122,7 @@ public void TestToObject() nullValueDic.Add("testNullValueDic", new Dictionary()); dic.Add("Content", nullValueDic); - TestRegModel model = TeaModel.ToObject(dic); + TestRegModel model = Model.ToObject(dic); var testNullValueDic = model.Content["testNullValueDic"] as Dictionary; Assert.True(testNullValueDic.Count==0); Assert.NotNull(model); @@ -141,11 +142,11 @@ public void TestToObject() Assert.Null(model.testNull); Dictionary dicString = null; - Assert.Null(TeaModel.ToObject(dicString)); + Assert.Null(Model.ToObject(dicString)); dicString = new Dictionary(); dicString.Add("requestId", "test"); dicString.Add("count", "1"); - TestDicStringModel stringModel = TeaModel.ToObject(dicString); + TestDicStringModel stringModel = Model.ToObject(dicString); Assert.NotNull(stringModel); Assert.Equal("test", stringModel.RequestId); @@ -153,40 +154,40 @@ public void TestToObject() dicConvert.Add("requestId", dicSub); dicConvert.Add("count", "1"); - stringModel = TeaModel.ToObject(dicConvert); + stringModel = Model.ToObject(dicConvert); Assert.NotNull(stringModel); Assert.Equal("{\"requestId\":\"sub\",\"testInt\":100}", stringModel.RequestId); Assert.Equal(1, stringModel.Count); dicConvert.Remove("requestId"); dicConvert.Add("requestId", dicItems); - stringModel = TeaModel.ToObject(dicConvert); + stringModel = Model.ToObject(dicConvert); Assert.Equal("[{\"requestId\":\"sub\",\"testInt\":100},{\"requestId\":\"subRe\",\"testInt\":500},null]", stringModel.RequestId); dicConvert.Remove("requestId"); string[] array = new string[] { "a", "b" }; dicConvert.Add("requestId", array); - stringModel = TeaModel.ToObject(dicConvert); + stringModel = Model.ToObject(dicConvert); Assert.Equal("[\"a\",\"b\"]", stringModel.RequestId); dicConvert.Remove("requestId"); dicConvert.Add("requestId", 1.1); - stringModel = TeaModel.ToObject(dicConvert); + stringModel = Model.ToObject(dicConvert); Assert.Equal("1.1", stringModel.RequestId); dicConvert.Remove("requestId"); dicConvert.Add("requestId", 11111111111111111111L); - stringModel = TeaModel.ToObject(dicConvert); + stringModel = Model.ToObject(dicConvert); Assert.Equal("11111111111111111111", stringModel.RequestId); dicConvert.Remove("requestId"); dicConvert.Add("requestId", null); - stringModel = TeaModel.ToObject(dicConvert); + stringModel = Model.ToObject(dicConvert); Assert.Null(stringModel.RequestId); dicConvert.Remove("requestId"); dicConvert.Add("requestId", true); - stringModel = TeaModel.ToObject(dicConvert); + stringModel = Model.ToObject(dicConvert); Assert.Equal("True", stringModel.RequestId); } @@ -194,7 +195,7 @@ public void TestToObject() [Fact] public void TestValidator() { - TeaModel modelNull = null; + Model modelNull = null; Assert.Throws(() => { modelNull.Validate(); }); TestRegModel successModel = new TestRegModel(); diff --git a/DarabonbaUnitTests/Models/ListAllMyBucketsResult.cs b/DarabonbaUnitTests/Models/ListAllMyBucketsResult.cs new file mode 100644 index 0000000..cc78ed0 --- /dev/null +++ b/DarabonbaUnitTests/Models/ListAllMyBucketsResult.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using Darabonba; + +namespace DaraUnitTests.Models +{ + public class ListAllMyBucketsResult : Model + { + [NameInMap("Owner")] + public Owner owner { get; set; } + + [NameInMap("Buckets")] + public Buckets buckets { get; set; } + + [NameInMap("listStr")] + public List testStrList { get; set; } + + [NameInMap("Owners")] + public List owners { get; set; } + + public long? TestLong { get; set; } + + public short? TestShort { get; set; } + + public uint? TestUInt { get; set; } + + public ushort? TestUShort { get; set; } + + public ulong? TestULong { get; set; } + + public float? TestFloat { get; set; } + + public double? TestDouble { get; set; } + + public string TestNull { get; set; } + + public string TestString { get; set; } + + public bool? TestBool { get; set; } + + public Dictionary dict { get; set; } + + public List TestListNull { get; set; } + + public class Owner : Model + { + public int? ID { get; set; } + + public string DisplayName { get; set; } + } + + public class Buckets : Model + { + [NameInMap("Bucket")] + public List bucket { get; set; } + + public class Bucket : Model + { + public string CreationDate { get; set; } + + public string ExtranetEndpoint { get; set; } + + public string IntranetEndpoint { get; set; } + + public string Location { get; set; } + + public string Name { get; set; } + + public string StorageClass { get; set; } + } + } + } +} diff --git a/TeaUnitTests/Models/TestDicStringModel.cs b/DarabonbaUnitTests/Models/TestDicStringModel.cs similarity index 79% rename from TeaUnitTests/Models/TestDicStringModel.cs rename to DarabonbaUnitTests/Models/TestDicStringModel.cs index 84f6794..804f7a1 100644 --- a/TeaUnitTests/Models/TestDicStringModel.cs +++ b/DarabonbaUnitTests/Models/TestDicStringModel.cs @@ -1,6 +1,6 @@ -using Tea; +using Darabonba; -namespace TeaUnitTests.Models +namespace DaraUnitTests.Models { public class TestDicStringModel { diff --git a/TeaUnitTests/Models/TestRegModel.cs b/DarabonbaUnitTests/Models/TestRegModel.cs similarity index 91% rename from TeaUnitTests/Models/TestRegModel.cs rename to DarabonbaUnitTests/Models/TestRegModel.cs index 610022e..0519fb4 100644 --- a/TeaUnitTests/Models/TestRegModel.cs +++ b/DarabonbaUnitTests/Models/TestRegModel.cs @@ -1,11 +1,10 @@ using System.Collections; using System.Collections.Generic; +using Darabonba; -using Tea; - -namespace TeaUnitTests.Models +namespace DaraUnitTests.Models { - public class TestRegModel : TeaModel + public class TestRegModel : Model { [NameInMap("requestId")] [Validation(Pattern = "re", MaxLength = 0, Required = true)] @@ -23,8 +22,8 @@ public class TestRegModel : TeaModel [NameInMap("testListStr")] [Validation(Pattern = "listStr", MaxLength = 0)] - public List testListStr { get; set; } - + public List testListStr { get; set; } + [NameInMap("Content")] [Validation(Required = false)] public Dictionary Content diff --git a/TeaUnitTests/Models/TestRegSubModel.cs b/DarabonbaUnitTests/Models/TestRegSubModel.cs similarity index 69% rename from TeaUnitTests/Models/TestRegSubModel.cs rename to DarabonbaUnitTests/Models/TestRegSubModel.cs index 08e30ad..2b2e4a7 100644 --- a/TeaUnitTests/Models/TestRegSubModel.cs +++ b/DarabonbaUnitTests/Models/TestRegSubModel.cs @@ -1,8 +1,8 @@ -using Tea; +using Darabonba; -namespace TeaUnitTests.Models +namespace DaraUnitTests.Models { - public class TestRegSubModel : TeaModel + public class TestRegSubModel : Model { [NameInMap("requestId")] [Validation(Pattern = "r", MaxLength = 0, Required = true)] diff --git a/DarabonbaUnitTests/Models/ToBodyModel.cs b/DarabonbaUnitTests/Models/ToBodyModel.cs new file mode 100644 index 0000000..25f2a10 --- /dev/null +++ b/DarabonbaUnitTests/Models/ToBodyModel.cs @@ -0,0 +1,11 @@ +using Darabonba; + +namespace DaraUnitTests.Models +{ + public class ToBodyModel : Model + { + [NameInMap("ListAllMyBucketsResult")] + public ListAllMyBucketsResult listAllMyBucketsResult { get; set; } + + } +} diff --git a/DarabonbaUnitTests/RequestTest.cs b/DarabonbaUnitTests/RequestTest.cs new file mode 100644 index 0000000..0719e2f --- /dev/null +++ b/DarabonbaUnitTests/RequestTest.cs @@ -0,0 +1,28 @@ +using Darabonba; + +using Xunit; + +namespace DaraUnitTests +{ + public class RequestTest + { + [Fact] + public void TestDaraRequest() + { + Request request = new Request(); + Assert.NotNull(request); + Assert.NotNull(request.Headers); + Assert.NotNull(request.Query); + Assert.Equal("http", request.Protocol); + request.Headers = null; + Assert.NotNull(request.Headers); + Assert.Equal("GET", request.Method); + + request.Method = "POST"; + Assert.Equal("POST", request.Method); + + request.Query = null; + Assert.NotNull(request.Query); + } + } +} diff --git a/DarabonbaUnitTests/ResponseTest.cs b/DarabonbaUnitTests/ResponseTest.cs new file mode 100644 index 0000000..55600b0 --- /dev/null +++ b/DarabonbaUnitTests/ResponseTest.cs @@ -0,0 +1,31 @@ +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text; + +using Darabonba; + +using Xunit; + +namespace DaraUnitTests +{ + public class ResponseTest + { + [Fact] + public void TestDaraResponse() + { + HttpResponseMessage httpResponseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("test"))) + }; + Response response = new Response(httpResponseMessage); + Assert.NotNull(response); + Assert.Equal(200, response.StatusCode); + Assert.Equal("", response.StatusMessage); + + Response responseNull = new Response(null); + Assert.Null(responseNull.Body); + } + } +} diff --git a/DarabonbaUnitTests/RetryPolicy/BackoffPolicyTest.cs b/DarabonbaUnitTests/RetryPolicy/BackoffPolicyTest.cs new file mode 100644 index 0000000..5ca1f87 --- /dev/null +++ b/DarabonbaUnitTests/RetryPolicy/BackoffPolicyTest.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using Darabonba.Exceptions; +using Darabonba.RetryPolicy; +using Xunit; + +namespace DaraUnitTests.RetryPolicy +{ + public class BackoffPolicyTest + { + [Fact] + public void Test_BackoffPolicy() + { + var exception = Assert.Throws(() => + { + BackoffPolicy backoffPolicy = BackoffPolicy.NewBackOffPolicy(new Dictionary + { + { "policy", "Any" } + }); + }); + Assert.Equal("Invalid backoff policy", exception.Message); + + BackoffPolicy backoffPolicy = BackoffPolicy.NewBackOffPolicy(new Dictionary + { + { "policy", "Fixed" }, + { "period", 1000 } + }); + Assert.Equal("FixedBackoffPolicy", backoffPolicy.GetType().Name); + + backoffPolicy = BackoffPolicy.NewBackOffPolicy(new Dictionary + { + { "policy", "Random" }, + { "period", 2 }, + { "cap", 60000L } + }); + Assert.Equal("RandomBackoffPolicy", backoffPolicy.GetType().Name); + + backoffPolicy = BackoffPolicy.NewBackOffPolicy(new Dictionary + { + { "policy", "Exponential" }, + { "period", 2 }, + { "cap", 60000L } + }); + Assert.Equal("ExponentialBackoffPolicy", backoffPolicy.GetType().Name); + + backoffPolicy = BackoffPolicy.NewBackOffPolicy(new Dictionary + { + { "policy", "EqualJitter" }, + { "period", 2 }, + { "cap", 60000L } + }); + Assert.Equal("EqualJitterBackoffPolicy", backoffPolicy.GetType().Name); + + backoffPolicy = BackoffPolicy.NewBackOffPolicy(new Dictionary + { + { "policy", "ExponentialWithEqualJitter" }, + { "period", 2 }, + { "cap", 60000L } + }); + Assert.Equal("EqualJitterBackoffPolicy", backoffPolicy.GetType().Name); + + backoffPolicy = BackoffPolicy.NewBackOffPolicy(new Dictionary + { + { "policy", "FullJitter" }, + { "period", 2 }, + { "cap", 60000L } + }); + Assert.Equal("FullJitterBackoffPolicy", backoffPolicy.GetType().Name); + + backoffPolicy = BackoffPolicy.NewBackOffPolicy(new Dictionary + { + { "policy", "ExponentialWithFullJitter" }, + { "period", 2 }, + { "cap", 60000L } + }); + Assert.Equal("FullJitterBackoffPolicy", backoffPolicy.GetType().Name); + } + } +} \ No newline at end of file diff --git a/DarabonbaUnitTests/URLTest.cs b/DarabonbaUnitTests/URLTest.cs new file mode 100644 index 0000000..0208d39 --- /dev/null +++ b/DarabonbaUnitTests/URLTest.cs @@ -0,0 +1,49 @@ +using Darabonba; +using Xunit; + +namespace DaraUnitTests +{ + public class URLTest + { + [Fact] + public void Test_Parse() + { + string url = "https://sdk:test@ecs.aliyuncs.com:443/sdk/?api&ok=test#sddd"; + URL ret = URL.Parse(url); + Assert.Equal("/sdk/?api&ok=test", ret.Path()); + Assert.Equal("/sdk/", ret.Pathname()); + Assert.Equal("https", ret.Protocol()); + Assert.Equal("ecs.aliyuncs.com", ret.Hostname()); + Assert.Equal("ecs.aliyuncs.com", ret.Host()); + Assert.Equal("443", ret.Port()); + Assert.Equal("sddd", ret.Hash()); + Assert.Equal("api&ok=test", ret.Search()); + Assert.Equal("https://sdk:test@ecs.aliyuncs.com/sdk/?api&ok=test#sddd", ret.Href()); + Assert.Equal("sdk:test", ret.Auth()); + } + + [Fact] + public void Test_UrlEncode() + { + string result = URL.UrlEncode("https://www.baidu.com/"); + Assert.Equal("https%3A%2F%2Fwww.baidu.com%2F", result); + } + + [Fact] + public void Test_PercentEncode() + { + Assert.Null(URL.PercentEncode(null)); + Assert.Equal("test%3D", URL.PercentEncode("test=")); + + string result = URL.PercentEncode("https://www.bai+*~du.com/"); + Assert.Equal("https%3A%2F%2Fwww.bai%2B%2A~du.com%2F", result); + } + + [Fact] + public void Test_PathEncode() + { + string result = URL.PathEncode("/work_space/DARABONBA/GIT/darabonba-util/ts"); + Assert.Equal("/work_space/DARABONBA/GIT/darabonba-util/ts", result); + } + } +} \ No newline at end of file diff --git a/DarabonbaUnitTests/Utils/BytesUitlTest.cs b/DarabonbaUnitTests/Utils/BytesUitlTest.cs new file mode 100644 index 0000000..d840240 --- /dev/null +++ b/DarabonbaUnitTests/Utils/BytesUitlTest.cs @@ -0,0 +1,29 @@ +using System.Text; +using Xunit; +using Darabonba.Utils; + +namespace DaraUnitTests.Utils +{ + public class DaraBytesUtilTest + { + + [Fact] + public void Test_HexEncode() + { + byte[] test = Encoding.UTF8.GetBytes("test"); + var res = BytesUtil.ToHex(test); + Assert.Equal("74657374", res); + } + + [Fact] + public void Test_From() + { + string data = "test"; + Assert.Equal(new byte[] { 116, 101, 115, 116 }, BytesUtil.From(data, "utf8")); + Assert.Equal(new byte[] { 116, 101, 115, 116 }, BytesUtil.From(data, "ASCII")); + Assert.Equal(new byte[] { 0, 116, 0, 101, 0, 115, 0, 116 }, BytesUtil.From(data, "bigendianunicode")); + Assert.Equal(new byte[] { 116, 0, 101, 0, 115, 0, 116, 0 }, BytesUtil.From(data, "unicode")); + Assert.Equal(new byte[] { 116, 0, 0, 0, 101, 0, 0, 0, 115, 0, 0, 0, 116, 0, 0, 0 }, BytesUtil.From(data, "utf32")); + } + } +} \ No newline at end of file diff --git a/DarabonbaUnitTests/Utils/ConverterUtilTest.cs b/DarabonbaUnitTests/Utils/ConverterUtilTest.cs new file mode 100644 index 0000000..3f62997 --- /dev/null +++ b/DarabonbaUnitTests/Utils/ConverterUtilTest.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using Darabonba.Exceptions; +using Darabonba.Utils; +using DaraUnitTests.Models; +using Xunit; + +namespace DaraUnitTests.Utils +{ + public class TestObject + { + public string name { get; set; } + } + + public class ConverterUtilTests + { + [Fact] + public void TestMergeMap() + { + Dictionary dict1 = new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" } + }; + Dictionary dict2 = new Dictionary + { + { "key2", "value22" }, + { "key3", "value3" } + }; + Dictionary res = ConverterUtil.Merge(dict1, dict2); + Assert.Equal("value1", res["key1"]); + Assert.Equal("value22", res["key2"]); + Assert.Equal("value3", res["key3"]); + } + + [Fact] + public void TestMergeListMap() + { + Assert.Empty(ConverterUtil.Merge(null)); + + Dictionary dic = new Dictionary(); + Dictionary dicNull = null; + Dictionary dicMerge = new Dictionary(); + TestRegModel model = new TestRegModel(); + + dic.Add("testNull", null); + dic.Add("testExist", "testExist"); + dic.Add("test", "test"); + dicMerge.Add("testMerge", "testMerge"); + dicMerge.Add("testExist", "IsExist"); + Dictionary dicResult = ConverterUtil.Merge(dic, dicNull, dicMerge, null); + Assert.NotNull(dicResult); + Assert.Equal(4, dicResult.Count); + + Dictionary dicModelMerge = ConverterUtil.Merge(dic, dicNull, dicMerge, model); + Assert.NotNull(dicResult); + + Assert.Throws(() => { ConverterUtil.Merge(dic, 1); }); + } + + [Fact] + public void TestStrToLower() + { + Assert.Empty(ConverterUtil.StrToLower(null)); + + Assert.Equal("test", ConverterUtil.StrToLower("TEST")); + } + + [Fact] + public void Test_ParseMethods() + { + Assert.Equal(123, ConverterUtil.ParseInt("123")); + Assert.Equal(123, ConverterUtil.ParseInt("123.0123")); + Assert.Equal(123, ConverterUtil.ParseLong("123")); + Assert.Equal(123, ConverterUtil.ParseLong("123.0123")); + Assert.Equal(123.0123, Math.Round(ConverterUtil.ParseFloat("123.0123"), 4)); + var ex = Assert.Throws(() => { ConverterUtil.ParseLong((string)null); }); + Assert.Equal("Data is null.", ex.Message); + var ex1 = Assert.Throws(() => { ConverterUtil.ParseLong("test"); }); + Assert.Equal("Input string was not in a correct format.", ex1.Message); + } + } +} diff --git a/TeaUnitTests/Utils/DictUtilsTest.cs b/DarabonbaUnitTests/Utils/DictUtilsTest.cs similarity index 93% rename from TeaUnitTests/Utils/DictUtilsTest.cs rename to DarabonbaUnitTests/Utils/DictUtilsTest.cs index 849f0b5..ebaa5eb 100644 --- a/TeaUnitTests/Utils/DictUtilsTest.cs +++ b/DarabonbaUnitTests/Utils/DictUtilsTest.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; -using Tea.Utils; +using Darabonba.Utils; using Xunit; -namespace TeaUnitTests.Utils +namespace DaraUnitTests.Utils { public class DictUtilsTest { diff --git a/TeaUnitTests/Utils/ExtensionsTest.cs b/DarabonbaUnitTests/Utils/ExtensionsTest.cs similarity index 87% rename from TeaUnitTests/Utils/ExtensionsTest.cs rename to DarabonbaUnitTests/Utils/ExtensionsTest.cs index 67e787c..21666a3 100644 --- a/TeaUnitTests/Utils/ExtensionsTest.cs +++ b/DarabonbaUnitTests/Utils/ExtensionsTest.cs @@ -1,11 +1,23 @@ using System.Collections.Generic; -using Tea.Utils; +using Darabonba.Utils; using Xunit; -namespace TeaUnitTests.Utils +namespace DaraUnitTests.Utils { public class ExtensionsTest { + [Fact] + public void TestIsNull() + { + string str = null; + byte[] bytes = null; + Assert.True(str.IsNull()); + Assert.True(bytes.IsNull()); + + string nonNullStr = "not null"; + Assert.False(nonNullStr.IsNull()); + } + [Fact] public void TestToSafeString() { diff --git a/DarabonbaUnitTests/Utils/FileFormStreamTest.cs b/DarabonbaUnitTests/Utils/FileFormStreamTest.cs new file mode 100644 index 0000000..c9be13e --- /dev/null +++ b/DarabonbaUnitTests/Utils/FileFormStreamTest.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Darabonba.Utils; +using Xunit; + +namespace DaraUnitTests.Utils +{ + public class FileFormStreamTest + { + [Fact] + public void Test_FileField() + { + FileField fileField = new FileField(); + fileField.Content = new MemoryStream(); + fileField.ContentType = "contentType"; + fileField.Filename = "fileName"; + Assert.NotNull(fileField); + Assert.Equal("contentType", fileField.ContentType); + Assert.Equal("fileName", fileField.Filename); + Assert.NotNull(fileField.Content); + } + + [Fact] + public void Test_FileFormStream() + { + FileFormStream fileFormStream = new FileFormStream(new Dictionary(), ""); + Assert.True(fileFormStream.CanRead); + Assert.False(fileFormStream.CanSeek); + Assert.False(fileFormStream.CanWrite); + fileFormStream.Position = 1; + Assert.Equal(1, fileFormStream.Position); + fileFormStream.SetLength(2); + Assert.Equal(2, fileFormStream.Length); + Assert.Throws(() => { fileFormStream.Flush(); }); + Assert.Throws(() => { fileFormStream.Seek(0, System.IO.SeekOrigin.Begin); }); + Assert.Throws(() => { fileFormStream.Write(new byte[1024], 0, 1024); }); + } + + [Fact] + public void Test_Read() + { + FileFormStream fileFormStream = new FileFormStream(new Dictionary(), ""); + Assert.Equal(6, fileFormStream.Read(new byte[1024], 0, 1024)); + + FileField fileFieldNoContent = new FileField() + { + Filename = "noContent", + Content = null, + ContentType = "contentType" + }; + MemoryStream content = new MemoryStream(); + byte[] contentBytes = Encoding.UTF8.GetBytes("This is file test. This sentence must be long"); + content.Write(contentBytes, 0, contentBytes.Length); + content.Seek(0, SeekOrigin.Begin); + FileField fileField = new FileField() + { + Filename = "haveContent", + Content = content, + ContentType = "contentType" + }; + + Dictionary dict = new Dictionary(); + dict.Add("key", "value"); + dict.Add("testKey", "testValue"); + dict.Add("haveFile", fileField); + dict.Add("noFile", fileFieldNoContent); + MemoryStream StreamResult = new MemoryStream(); + byte[] bytes = new byte[1024]; + fileFormStream = new FileFormStream(dict, "testBoundary"); + int readNoStreamLength = 0; + while ((readNoStreamLength = fileFormStream.Read(bytes, 0, 1024)) != 0) + { + StreamResult.Write(bytes, 0, readNoStreamLength); + } + StreamResult.Seek(0, SeekOrigin.Begin); + byte[] bytesResult = new byte[StreamResult.Length]; + StreamResult.Read(bytesResult, 0, (int) StreamResult.Length); + string result = Encoding.UTF8.GetString(bytesResult); + Assert.Equal("--testBoundary\r\nContent-Disposition: form-data; name=\"key\"\r\n\r\nvalue\r\n--testBoundary\r\nContent-Disposition: form-data; name=\"testKey\"\r\n\r\ntestValue\r\n--testBoundary\r\nContent-Disposition: form-data; name=\"haveFile\"; filename=\"haveContent\"\r\nContent-Type: contentType\r\n\r\nThis is file test. This sentence must be long\r\n--testBoundary\r\nContent-Disposition: form-data; name=\"noFile\"; filename=\"noContent\"\r\nContent-Type: contentType\r\n\r\n\r\n--testBoundary--\r\n", result); + } + + [Fact] + public async Task Test_ReadAsync() + { + FileFormStream fileFormStream = new FileFormStream(new Dictionary(), ""); + Assert.Equal(6, await fileFormStream.ReadAsync(new byte[1024], 0, 1024)); + + FileField fileFieldNoContent = new FileField() + { + Filename = "noContent", + Content = null, + ContentType = "contentType" + }; + MemoryStream content = new MemoryStream(); + byte[] contentBytes = Encoding.UTF8.GetBytes("This is file test. This sentence must be long"); + content.Write(contentBytes, 0, contentBytes.Length); + content.Seek(0, SeekOrigin.Begin); + FileField fileField = new FileField() + { + Filename = "haveContent", + Content = content, + ContentType = "contentType" + }; + + Dictionary dict = new Dictionary(); + dict.Add("key", "value"); + dict.Add("testKey", "testValue"); + dict.Add("haveFile", fileField); + dict.Add("noFile", fileFieldNoContent); + MemoryStream StreamResult = new MemoryStream(); + byte[] bytes = new byte[1024]; + fileFormStream = new FileFormStream(dict, "testBoundary"); + int readNoStreamLength = 0; + while ((readNoStreamLength = await fileFormStream.ReadAsync(bytes, 0, 1024)) != 0) + { + StreamResult.Write(bytes, 0, readNoStreamLength); + } + StreamResult.Seek(0, SeekOrigin.Begin); + byte[] bytesResult = new byte[StreamResult.Length]; + StreamResult.Read(bytesResult, 0, (int) StreamResult.Length); + string result = Encoding.UTF8.GetString(bytesResult); + Assert.Equal("--testBoundary\r\nContent-Disposition: form-data; name=\"key\"\r\n\r\nvalue\r\n--testBoundary\r\nContent-Disposition: form-data; name=\"testKey\"\r\n\r\ntestValue\r\n--testBoundary\r\nContent-Disposition: form-data; name=\"haveFile\"; filename=\"haveContent\"\r\nContent-Type: contentType\r\n\r\nThis is file test. This sentence must be long\r\n--testBoundary\r\nContent-Disposition: form-data; name=\"noFile\"; filename=\"noContent\"\r\nContent-Type: contentType\r\n\r\n\r\n--testBoundary--\r\n", result); + } + + [Fact] + public void Test_PercentEncode() + { + Assert.Null(FileFormStream.PercentEncode(null)); + Assert.Equal("ab%3Dcd", FileFormStream.PercentEncode("ab=cd")); + } + } +} \ No newline at end of file diff --git a/DarabonbaUnitTests/Utils/FormUtilTest.cs b/DarabonbaUnitTests/Utils/FormUtilTest.cs new file mode 100644 index 0000000..26916a0 --- /dev/null +++ b/DarabonbaUnitTests/Utils/FormUtilTest.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using Darabonba.Utils; +using Xunit; + +namespace DaraUnitTests.Utils +{ + public class FormUtilTest + { + [Fact] + public void Test_ToFormString() + { + Assert.Empty(FormUtil.ToFormString(null)); + Assert.Empty(FormUtil.ToFormString(new Dictionary())); + + Dictionary dict = new Dictionary + { + { "form", "test" }, + { "param", "test" }, + { "testNull", null } + }; + Assert.Equal("form=test¶m=test", FormUtil.ToFormString(dict)); + } + + [Fact] + public void Test_GetBoundary() + { + Assert.Equal(14, FormUtil.GetBoundary().Length); + } + + [Fact] + public void Test_ToFileForm() + { + Stream fileFormStream = FormUtil.ToFileForm(new Dictionary(), "boundary"); + Assert.NotNull(fileFormStream); + + string formStr = GetFormStr(fileFormStream); + Assert.Equal("--boundary--\r\n", formStr); + + Dictionary dict = new Dictionary(); + dict.Add("stringkey", "string"); + fileFormStream = FormUtil.ToFileForm(dict, "boundary"); + formStr = GetFormStr(fileFormStream); + Assert.Equal("--boundary\r\n" + + "Content-Disposition: form-data; name=\"stringkey\"\r\n\r\n" + + "string\r\n" + + "--boundary--\r\n", formStr); + + string path = System.AppDomain.CurrentDomain.BaseDirectory; + FileStream file = File.OpenRead("../../../../DarabonbaUnitTests/Fixtures/test.json"); + FileField fileField = new FileField + { + Filename = "fakefilename", + ContentType = "application/json", + Content = file + }; + dict = new Dictionary + { + { "stringkey", "string" }, + { "filefield", fileField } + }; + fileFormStream = FormUtil.ToFileForm(dict, "boundary"); + formStr = GetFormStr(fileFormStream); + Assert.Equal("--boundary\r\n" + + "Content-Disposition: form-data; name=\"stringkey\"\r\n\r\n" + + "string\r\n" + + "--boundary\r\n" + + "Content-Disposition: form-data; name=\"filefield\"; filename=\"fakefilename\"\r\n" + + "Content-Type: application/json\r\n" + + "\r\n" + + "{\"key\":\"value\"}" + + "\r\n" + + "--boundary--\r\n", formStr); + } + + private string GetFormStr(Stream stream) + { + string formStr = string.Empty; + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0) + { + formStr += Encoding.UTF8.GetString(buffer, 0, bytesRead); + } + + return formStr; + } + } +} \ No newline at end of file diff --git a/TeaUnitTests/Utils/HttpClientUtilsTest.cs b/DarabonbaUnitTests/Utils/HttpClientUtilsTest.cs similarity index 96% rename from TeaUnitTests/Utils/HttpClientUtilsTest.cs rename to DarabonbaUnitTests/Utils/HttpClientUtilsTest.cs index e6e5861..04dd996 100644 --- a/TeaUnitTests/Utils/HttpClientUtilsTest.cs +++ b/DarabonbaUnitTests/Utils/HttpClientUtilsTest.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; using System.Text; -using Tea.Utils; +using Darabonba.Utils; using Xunit; -namespace TeaUnitTests.Utils +namespace DaraUnitTests.Utils { public class HttpClientUtilsTest { diff --git a/TeaUnitTests/Utils/HttpUtilsTest.cs b/DarabonbaUnitTests/Utils/HttpUtilsTest.cs similarity index 93% rename from TeaUnitTests/Utils/HttpUtilsTest.cs rename to DarabonbaUnitTests/Utils/HttpUtilsTest.cs index 7634f5c..6c4a720 100644 --- a/TeaUnitTests/Utils/HttpUtilsTest.cs +++ b/DarabonbaUnitTests/Utils/HttpUtilsTest.cs @@ -1,10 +1,10 @@ using System.Net.Http; -using Tea.Utils; +using Darabonba.Utils; using Xunit; -namespace TeaUnitTests.Utils +namespace DaraUnitTests.Utils { public class HttpUtilsTest { diff --git a/DarabonbaUnitTests/Utils/JSONUtilTest.cs b/DarabonbaUnitTests/Utils/JSONUtilTest.cs new file mode 100644 index 0000000..b4b4084 --- /dev/null +++ b/DarabonbaUnitTests/Utils/JSONUtilTest.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Darabonba; +using Darabonba.Utils; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; +using Xunit.Abstractions; + +namespace DaraUnitTests.Utils +{ + public class JSONUtilTest + { + private readonly ITestOutputHelper _testOutputHelper; + + public JSONUtilTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + [Fact] + public void Test_SerializeObject() + { + Dictionary dict = new Dictionary + { { "key", "value" } + }; + string jsonStr = JSONUtil.SerializeObject(dict); + Assert.NotNull(jsonStr); + Assert.NotEmpty(jsonStr); + Assert.Equal("{\"key\":\"value\"}", jsonStr); + Assert.Equal("{}", JSONUtil.SerializeObject(new Dictionary())); + Assert.Equal("test str", JSONUtil.SerializeObject("test str")); + Assert.Equal("1", JSONUtil.SerializeObject(1)); + Assert.Equal("true", JSONUtil.SerializeObject(true)); + Assert.Equal("null", JSONUtil.SerializeObject(null)); + Dictionary unicode = new Dictionary + { { "str", "test&<>://中文" } + }; + Assert.Equal("{\"key\":\"value\",\"map\":{\"str\":\"test&<>://中文\"},\"num\":1}", JSONUtil.SerializeObject( + new Dictionary + { + { "key", "value" }, + { "map", unicode }, + { "num", 1 } + })); + } + + [Fact] + public void TestDeserializeToDic() + { + Assert.Null(JSONUtil.Deserialize(null)); + + string jsonStr = "{\"arrayObj\":[[{\"itemName\":\"item\",\"itemInt\":1},{\"itemName\":\"item2\",\"itemInt\":2}],[{\"itemName\":\"item3\",\"itemInt\":3}]],\"arrayList\":[[[1,2],[3,4]],[[5,6],[7]],[]],\"listStr\":[1,2,3],\"items\":[{\"total_size\":18,\"partNumber\":1,\"tags\":[{\"aa\":\"11\"}]},{\"total_size\":20,\"partNumber\":2,\"tags\":[{\"aa\":\"22\"}]}],\"next_marker\":\"\",\"test\":{\"total_size\":19,\"partNumber\":1,\"tags\":[{\"aa\":\"11\"}]}}"; + JObject jObject = JObject.Parse(jsonStr); + Dictionary dic = (Dictionary) JSONUtil.Deserialize(jObject); + Assert.NotNull(dic); + List listResult = (List) dic["items"]; + Dictionary item1 = (Dictionary) listResult[0]; + Assert.Equal(18L, item1["total_size"]); + Assert.Empty((string) dic["next_marker"]); + Assert.Equal(2, ((List) dic["arrayObj"]).Count); + } + + [Fact] + public void TestReadPath() + { + var jsonStr = "{\"testBool\":true,\"arrayObj\":[[{\"itemName\":\"item\",\"itemInt\":1},{\"itemName\":\"item2\",\"itemInt\":2}],[{\"itemName\":\"item3\",\"itemInt\":3}]],\"arrayList\":[[[1,2],[3,4]],[[5,6],[7]],[]],\"listStr\":[1,2,3],\"items\":[{\"total_size\":18,\"partNumber\":1,\"tags\":[{\"aa\":\"11\"}]},{\"total_size\":20,\"partNumber\":2,\"tags\":[{\"aa\":\"22\"}]}],\"next_marker\":\"\",\"test\":{\"total_size\":19,\"partNumber\":1,\"tags\":[{\"aa\":\"11\"}]}}"; + var jObject = JObject.Parse(jsonStr); + _testOutputHelper.WriteLine("objjj----{0}", jObject); + var res = JSONUtil.ReadPath(jObject, "$.arrayObj[0]"); + Assert.Equal("[{\"itemName\":\"item\",\"itemInt\":1},{\"itemName\":\"item2\",\"itemInt\":2}]", JsonConvert.SerializeObject(res)); + res = JSONUtil.ReadPath(jObject, "$.arrayObj[0][0].itemInt"); + Assert.Equal(1L, res); + res = JSONUtil.ReadPath(jObject, "$.testBool"); + Assert.True((bool)res); + } + + [Fact] + public void Test_ReadPath() + { + var context = new Context + { + Str = "test", + TestBool = true, + ContextInteger = 123, + ContextLong = 123L, + ContextDouble = 1.123d, + ContextFloat = 3.456f, + ContextListLong = new List { 123L, 456L }, + ListList = new List> + { + new List { 789, 123 }, + new List { 8, 9 } + }, + IntegerListMap = new Dictionary> + { + { "integerList", new List { 123, 456 } } + } + }; + + Assert.Null(JSONUtil.ReadPath(context, "$.notExist")); + _testOutputHelper.WriteLine("bool----{0}", JSONUtil.ReadPath(context, "$.testBool")); + Assert.True(JSONUtil.ReadPath(context, "$.testBool") is bool); + Assert.True(JSONUtil.ReadPath(context, "$.listList") is List); + Assert.True(JSONUtil.ReadPath(context, "$.contextInteger") is long); + Assert.True(JSONUtil.ReadPath(context, "$.contextLong") is long); + Assert.True(JSONUtil.ReadPath(context, "$.contextDouble") is double); + Assert.True(JSONUtil.ReadPath(context, "$.contextFloat") is double); + Assert.True(JSONUtil.ReadPath(context, "$.contextListLong") is List); + Assert.True(JSONUtil.ReadPath(context, "$.integerListMap") is Dictionary); + + Assert.Equal(true, JSONUtil.ReadPath(context, "$.testBool")); + Assert.Equal("test", JSONUtil.ReadPath(context, "$.testStr")); + Assert.Equal(123L, JSONUtil.ReadPath(context, "$.contextLong")); + var listLong = JSONUtil.ReadPath(context, "$.contextListLong") as List; + Assert.Equal(123L, listLong[0]); + + var listList = JSONUtil.ReadPath(context, "$.listList") as List; + Assert.Equal(789L, (listList[0] as List)[0]); + + var map = JSONUtil.ReadPath(context, "$.integerListMap") as Dictionary; + Assert.Equal(123L, (map["integerList"] as List)[0]); + + var realListList = new List>(); + foreach (var itemList in listList) + { + var intList = itemList as List; + var nullableIntList = new List(); + if (intList != null) + { + foreach (var item in intList) + { + var intValue = (int?)(item as long?); + nullableIntList.Add(intValue); + } + } + + realListList.Add(nullableIntList); + } + + + var realIntegerListMap = new Dictionary>(); + foreach (var kvp in map) + { + string key = kvp.Key; + object value = kvp.Value; + + var intList = value as List; + var nullableIntList = new List(); + if (intList != null) + { + foreach (var item in intList) + { + nullableIntList.Add((int?)(item as long?)); + } + } + realIntegerListMap[key] = nullableIntList; + } + var context1 = new Context + { + ContextLong = JSONUtil.ReadPath(context, "$.contextLong") as long?, + ContextInteger = (int?)(JSONUtil.ReadPath(context, "$.contextInteger") as long?), + ContextFloat = (float?)(JSONUtil.ReadPath(context, "$.contextFloat") as double?), + ContextDouble = JSONUtil.ReadPath(context, "$.contextDouble") as double?, + ContextListLong = (JSONUtil.ReadPath(context, "$.contextListLong") as List) + .Select(item => item is long longValue ? longValue : (long?)null) + .ToList(), + ListList = realListList, + IntegerListMap = realIntegerListMap + }; + + Assert.Equal(123L, context1.ContextLong); + Assert.Equal(123, context1.ContextInteger); + Assert.Equal(3.456f, context1.ContextFloat); + Assert.Equal(1.123d, context1.ContextDouble); + Assert.Equal(new List { 123L, 456L }, context1.ContextListLong); + Assert.Equal(new List> + { + new List { 789, 123 }, + new List { 8, 9 } + }, context1.ListList); + Assert.Equal(123, (context1.IntegerListMap["integerList"] as List)[0]); + } + } + + public class Context : Model + { + [NameInMap("testStr")] + public string Str { get; set; } + + [NameInMap("testBool")] + public bool? TestBool { get; set; } + + [NameInMap("contextInteger")] + public int? ContextInteger { get; set; } + + [NameInMap("contextLong")] + public long? ContextLong { get; set; } + + [NameInMap("contextListLong")] + public List ContextListLong { get; set; } + + [NameInMap("listList")] + public List> ListList { get; set; } + + [NameInMap("contextDouble")] + public double? ContextDouble { get; set; } + + [NameInMap("contextFloat")] + public float? ContextFloat { get; set; } + + [NameInMap("integerListMap")] + public Dictionary> IntegerListMap { get; set; } + } +} diff --git a/DarabonbaUnitTests/Utils/ListUtilTest.cs b/DarabonbaUnitTests/Utils/ListUtilTest.cs new file mode 100644 index 0000000..d427872 --- /dev/null +++ b/DarabonbaUnitTests/Utils/ListUtilTest.cs @@ -0,0 +1,58 @@ +using Darabonba.Utils; +using Xunit; +using System.Collections.Generic; + +namespace DaraUnitTests.Utils +{ + public class ListUtilTest + { + + [Fact] + public void TestShift() + { + List array = new List { "a", "b", "c" }; + string first = ListUtil.Shift(array); + Assert.Equal(2, array.Count); + Assert.Equal("a", first); + Assert.Equal("b", array[0]); + } + + [Fact] + public void TestUnshift() + { + List array = new List { "a", "b", "c" }; + ListUtil.Unshift(array, "x"); + Assert.Equal(4, array.Count); + Assert.Equal("x", array[0]); + } + + [Fact] + public void TestPush() + { + List array = new List { "a", "b", "c" }; + ListUtil.Push(array, "x"); + Assert.Equal(4, array.Count); + Assert.Equal("x", array[3]); + } + + [Fact] + public void TestPop() + { + List array = new List { "a", "b", "c" }; + string last = ListUtil.Pop(array); + Assert.Equal(2, array.Count); + Assert.Equal("c", last); + Assert.Equal("b", array[1]); + } + + [Fact] + public void TestConcat() + { + List array1 = new List { "a", "b", "c" }; + List array2 = new List { "d", "e", "f" }; + ListUtil.Concat(array1, array2); + Assert.Equal(6, array1.Count); + Assert.Equal(new List { "a", "b", "c", "d", "e", "f" }, array1); + } + } +} \ No newline at end of file diff --git a/DarabonbaUnitTests/Utils/MathUtilTest.cs b/DarabonbaUnitTests/Utils/MathUtilTest.cs new file mode 100644 index 0000000..3cb1cff --- /dev/null +++ b/DarabonbaUnitTests/Utils/MathUtilTest.cs @@ -0,0 +1,76 @@ +using Darabonba.Utils; +using Xunit; + +namespace DaraUnitTests.Utils +{ + public class MathUtilTest + { + [Fact] + public void TestFloor() + { + float funm = 2.13f; + Assert.Equal(2, MathUtil.Floor(funm)); + double dunm = 2.13d; + Assert.Equal(2, MathUtil.Floor(dunm)); + } + + [Fact] + public void TestRound() + { + float funm = 2.49f; + Assert.Equal(2, MathUtil.Round(funm)); + double dunm = 2.51d; + Assert.Equal(3, MathUtil.Round(dunm)); + } + + [Fact] + public void TestParseInt() + { + float funm = 2.13f; + Assert.Equal(2, MathUtil.ParseInt(funm)); + double dunm = 2.13d; + Assert.Equal(2, MathUtil.ParseInt(dunm)); + } + + [Fact] + public void TestParseLong() + { + float funm = 2.13f; + Assert.Equal(2L, MathUtil.ParseLong(funm)); + double dunm = 2.13d; + Assert.Equal(2L, MathUtil.ParseLong(dunm)); + } + + [Fact] + public void TestParseFloat() + { + int iunm = 2; + Assert.Equal(2f, MathUtil.ParseFloat(iunm)); + float funm = 2.13f; + Assert.Equal(2.13f, MathUtil.ParseFloat(funm)); + double dunm = 2.13d; + Assert.Equal(2.13f, MathUtil.ParseFloat(dunm)); + } + + [Fact] + public void TestMin() + { + int inum = 2; + float fnum = 2.01f; + double dnum = 2.001d; + Assert.Equal(2, MathUtil.Min(inum, fnum)); + Assert.Equal(2.001d, MathUtil.Min(dnum, fnum)); + } + + [Fact] + public void TestMax() + { + int inum = 2; + float fnum = 2.01f; + double dnum = 2.02d; + Assert.Equal(2.01f, MathUtil.Max(inum, fnum)); + Assert.Equal(2.02d, MathUtil.Max(dnum, fnum)); + } + } + +} \ No newline at end of file diff --git a/DarabonbaUnitTests/Utils/StreamUtilTest.cs b/DarabonbaUnitTests/Utils/StreamUtilTest.cs new file mode 100644 index 0000000..8b8af87 --- /dev/null +++ b/DarabonbaUnitTests/Utils/StreamUtilTest.cs @@ -0,0 +1,627 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using System.Threading; +using Darabonba.Models; +using Darabonba.Utils; +using Xunit; +using System.Net; +using System.Net.Http; +using Newtonsoft.Json; +using System; + +namespace DaraUnitTests.Utils +{ + public class SseServer : IDisposable + { + private readonly HttpListener _httpListener; + private CancellationTokenSource _cancellationTokenSource; + + public SseServer(string uriPrefix) + { + _httpListener = new HttpListener(); + _httpListener.Prefixes.Add(uriPrefix); + } + + public void Start() + { + _cancellationTokenSource = new CancellationTokenSource(); + _httpListener.Start(); + Task.Run(() => HandleIncomingConnections(_cancellationTokenSource.Token)); + } + + private async Task HandleIncomingConnections(CancellationToken cancellationToken) + { + while (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + var context = await _httpListener.GetContextAsync().ConfigureAwait(false); + + if (context.Request.Url?.AbsolutePath == "/sse") + { + HandleSseResponse(context.Response); + } + else if (context.Request.Url?.AbsolutePath == "/sse_with_no_spaces") + { + HandleSseWithNoSpacesResponse(context.Response); + } + else if (context.Request.Url?.AbsolutePath == "/sse_invalid_retry") + { + HandleSseWithInvalidRetryResponse(context.Response); + } + else if (context.Request.Url?.AbsolutePath == "/sse_with_data_divided") + { + HandleSseWithDataDividedResponse(context.Response); + } + } + catch (HttpListenerException) when (cancellationToken.IsCancellationRequested) + { + throw new HttpListenerException(); + } + } + } + + private void HandleSseResponse(HttpListenerResponse response) + { + int count = 0; + Timer timer = null; + var cts = new CancellationTokenSource(); + var token = cts.Token; + timer = new Timer(_ => + { + if (token.IsCancellationRequested) + { + return; + } + + if (count >= 5) + { + cts.Cancel(); + timer.Dispose(); + response.Close(); + return; + } + + try + { + byte[] buffer = Encoding.UTF8.GetBytes(string.Format( + "data: {0}\nevent: flow\nid: sse-test\nretry: 3\n:heartbeat\n\n", + JsonConvert.SerializeObject(new { count = count }))); + response.OutputStream.Write(buffer, 0, buffer.Length); + response.OutputStream.Flush(); + count++; + } + catch (ObjectDisposedException ex) + { + Console.WriteLine($"ObjectDisposedException caught: {ex.Message}"); + } + }, null, 0, 100); + } + + private void HandleSseWithNoSpacesResponse(HttpListenerResponse response) + { + int count = 0; + Timer timer = null; + var cts = new CancellationTokenSource(); + var token = cts.Token; + timer = new Timer(_ => + { + if (token.IsCancellationRequested) + { + return; + } + + if (count >= 5) + { + cts.Cancel(); + timer.Dispose(); + response.Close(); + return; + } + + try + { + byte[] buffer = Encoding.UTF8.GetBytes(string.Format("data: {0}\nevent:flow\nid:sse-test\nretry:3\n\n", JsonConvert.SerializeObject(new { count = count }))); + response.OutputStream.Write(buffer, 0, buffer.Length); + response.OutputStream.Flush(); + count++; + } + catch (ObjectDisposedException ex) + { + Console.WriteLine($"ObjectDisposedException caught: {ex.Message}"); + } + }, null, 0, 100); + } + + private void HandleSseWithInvalidRetryResponse(HttpListenerResponse response) + { + int count = 0; + Timer timer = null; + var cts = new CancellationTokenSource(); + var token = cts.Token; + timer = new Timer(_ => + { + if (token.IsCancellationRequested) + { + return; + } + + if (count >= 5) + { + cts.Cancel(); + timer.Dispose(); + response.Close(); + return; + } + + try + { + byte[] buffer = Encoding.UTF8.GetBytes(string.Format("data: {0}\nevent:flow\nid:sse-test\nretry: abc\n\n", JsonConvert.SerializeObject(new { count = count }))); + response.OutputStream.Write(buffer, 0, buffer.Length); + response.OutputStream.Flush(); + count++; + } + catch (ObjectDisposedException ex) + { + Console.WriteLine($"ObjectDisposedException caught: {ex.Message}"); + } + }, null, 0, 100); + } + + private void HandleSseWithDataDividedResponse(HttpListenerResponse response) + { + int count = 0; + Timer timer = null; + var cts = new CancellationTokenSource(); + var token = cts.Token; + timer = new Timer(_ => + { + if (token.IsCancellationRequested) + { + return; + } + + if (count >= 5) + { + cts.Cancel(); + timer.Dispose(); + response.Close(); + return; + } + + if (count == 1) + { + byte[] buffer = Encoding.UTF8.GetBytes("data:{\"count\":"); + response.OutputStream.Write(buffer, 0, buffer.Length); + response.OutputStream.Flush(); + count++; + return; + } + + if (count == 2) + { + byte[] buffer = Encoding.UTF8.GetBytes(string.Format("{0},\"tag\":\"divided\"}}\nevent:flow\nid:sse-test\nretry:3\n\n", count++)); + response.OutputStream.Write(buffer, 0, buffer.Length); + response.OutputStream.Flush(); + return; + } + + try + { + byte[] buffer1 = Encoding.UTF8.GetBytes(string.Format("data: {0}\nevent:flow\nid:sse-test\nretry:3\n\n", JsonConvert.SerializeObject(new { count = count++ }))); + response.OutputStream.Write(buffer1, 0, buffer1.Length); + response.OutputStream.Flush(); + } + catch (ObjectDisposedException ex) + { + Console.WriteLine($"ObjectDisposedException caught: {ex.Message}"); + } + }, null, 0, 100); + } + + public void Stop() + { + _cancellationTokenSource.Cancel(); + _httpListener.Stop(); + _httpListener.Close(); + } + + public void Dispose() + { + Stop(); + ((IDisposable)_httpListener)?.Dispose(); + _cancellationTokenSource?.Dispose(); + } + } + + + public class StreamUtilTest : IAsyncLifetime + { + private SseServer server = new SseServer("http://localhost:8384/"); + + public async Task InitializeAsync() + { + server.Start(); + await Task.Delay(1000); + } + + public Task DisposeAsync() + { + server.Dispose(); + return Task.CompletedTask; + } + + [Fact] + public void Test_ReadAsString() + { + using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes("test"))) + { + Assert.Equal("test", StreamUtil.ReadAsString(stream)); + } + } + + [Fact] + public async void Test_ReadAsStringAsync() + { + using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes("test"))) + { + Assert.Equal("test", await StreamUtil.ReadAsStringAsync(stream)); + } + } + + [Fact] + public void Test_ReadAsJSON() + { + string jsonStr = "{\"arrayObj\":[[{\"itemName\":\"item\",\"itemInt\":1},{\"itemName\":\"item2\",\"itemInt\":2}],[{\"itemName\":\"item3\",\"itemInt\":3}]],\"arrayList\":[[[1,2],[3,4]],[[5,6],[7]],[]],\"listStr\":[1,2,3],\"items\":[{\"total_size\":18,\"partNumber\":1,\"tags\":[{\"aa\":\"11\"}]},{\"total_size\":20,\"partNumber\":2,\"tags\":[{\"aa\":\"22\"}]}],\"next_marker\":\"\",\"test\":{\"total_size\":19,\"partNumber\":1,\"tags\":[{\"aa\":\"11\"}]}}"; + byte[] array = Encoding.UTF8.GetBytes(jsonStr); + using (MemoryStream stream = new MemoryStream(array)) + { + Dictionary dic = (Dictionary)StreamUtil.ReadAsJSON(stream); + Assert.NotNull(dic); + List listResult = (List)dic["items"]; + Dictionary item1 = (Dictionary)listResult[0]; + Assert.Equal(18L, item1["total_size"]); + Assert.Empty((string)dic["next_marker"]); + Assert.Equal(2, ((List)dic["arrayObj"]).Count); + } + + jsonStr = "[{\"itemName\":\"item\",\"itemInt\":1},{\"itemName\":\"item2\",\"itemInt\":2}]"; + array = Encoding.UTF8.GetBytes(jsonStr); + using (MemoryStream stream = new MemoryStream(array)) + { + List listResult = (List)StreamUtil.ReadAsJSON(stream); + Assert.NotNull(listResult); + Dictionary item1 = (Dictionary)listResult[0]; + Assert.Equal("item", item1["itemName"]); + Assert.Equal(1L, item1["itemInt"]); + } + } + + [Fact] + public void Test_Read() + { + using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes("test"))) + { + Assert.NotNull(StreamUtil.Read(stream, 3)); + Assert.Equal(3, StreamUtil.Read(stream, 3).Length); + } + } + + [Fact] + public void Test_Pipe() + { + byte[] inputData = new byte[] {1, 2, 3, 4, 5}; + using (MemoryStream readStream = new MemoryStream(inputData)) + using (MemoryStream writeStream = new MemoryStream()) + { + StreamUtil.Pipe(readStream, writeStream); + byte[] outputData = writeStream.ToArray(); + Assert.Equal(inputData, outputData); + } + byte[] inputData1 = new byte[] { }; + using (MemoryStream readStream1 = new MemoryStream(inputData1)) + using (MemoryStream writeStream1 = new MemoryStream()) + { + StreamUtil.Pipe(readStream1, writeStream1); + byte[] outputData1 = writeStream1.ToArray(); + Assert.Empty(outputData1); + } + } + + [Fact] + public void Test_StreamFor() + { + byte[] data = Encoding.UTF8.GetBytes("test"); + using (MemoryStream stream = new MemoryStream(data)) + { + Stream copy = StreamUtil.StreamFor(stream); + Assert.NotNull(copy); + Assert.True(copy.CanRead); + string str = new StreamReader(copy).ReadToEnd(); + Assert.Equal("test", str); + + string data1 = "test1"; + Stream copy1 = StreamUtil.StreamFor(data1); + string str1 = new StreamReader(copy1).ReadToEnd(); + Assert.Equal("test1", str1); + + int data2 = 111; + Exception ex = Assert.Throws(() => StreamUtil.StreamFor(data2)); + Assert.Equal("data is not Stream or String", ex.Message); + } + } + + [Fact] + public async void Test_ReadAsJSONAsync() + { + string jsonStr = "{\"arrayObj\":[[{\"itemName\":\"item\",\"itemInt\":1},{\"itemName\":\"item2\",\"itemInt\":2}],[{\"itemName\":\"item3\",\"itemInt\":3}]],\"arrayList\":[[[1,2],[3,4]],[[5,6],[7]],[]],\"listStr\":[1,2,3],\"items\":[{\"total_size\":18,\"partNumber\":1,\"tags\":[{\"aa\":\"11\"}]},{\"total_size\":20,\"partNumber\":2,\"tags\":[{\"aa\":\"22\"}]}],\"next_marker\":\"\",\"test\":{\"total_size\":19,\"partNumber\":1,\"tags\":[{\"aa\":\"11\"}]}}"; + byte[] array = Encoding.UTF8.GetBytes(jsonStr); + using (MemoryStream stream = new MemoryStream(array)) + { + Dictionary dic = (Dictionary)await StreamUtil.ReadAsJSONAsync(stream); + Assert.NotNull(dic); + List listResult = (List)dic["items"]; + Dictionary item1 = (Dictionary)listResult[0]; + Assert.Equal(18L, item1["total_size"]); + Assert.Empty((string)dic["next_marker"]); + Assert.Equal(2, ((List)dic["arrayObj"]).Count); + } + + jsonStr = "[{\"itemName\":\"item\",\"itemInt\":1},{\"itemName\":\"item2\",\"itemInt\":2}]"; + array = Encoding.UTF8.GetBytes(jsonStr); + using (MemoryStream stream = new MemoryStream(array)) + { + List listResult = (List)await StreamUtil.ReadAsJSONAsync(stream); + Assert.NotNull(listResult); + Dictionary item1 = (Dictionary)listResult[0]; + Assert.Equal("item", item1["itemName"]); + Assert.Equal(1L, item1["itemInt"]); + } + } + + [Fact] + public void Test_ReadAsBytes() + { + using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes("test"))) + { + Assert.NotNull(StreamUtil.ReadAsBytes(stream)); + } + } + + [Fact] + public async void Test_ReadAsBytesAsync() + { + using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes("test"))) + { + Assert.NotNull(await StreamUtil.ReadAsBytesAsync(stream)); + } + } + + // [Fact] + // public async Task Test_ReadAsSSEAsync() + // { + // using (var client = new HttpClient()) + // { + // var response = await client.GetStreamAsync("http://localhost:8384/sse"); + // + // var events = new List(); + // + // await foreach (var sseEvent in StreamUtil.ReadAsSSEAsync(response)) + // { + // events.Add(sseEvent); + // } + // + // for (int i = 0; i < 5; i++) + // { + // Assert.Equal(JsonConvert.SerializeObject(new { count = i }), events[i].Data); + // Assert.Equal("sse-test", events[i].Id); + // Assert.Equal("flow", events[i].Event); + // Assert.Equal(3, events[i].Retry); + // } + // } + // } + // + // [Fact] + // public async Task Test_ReadAsSSEAsync_WithNoSpaces() + // { + // using (var client = new HttpClient()) + // { + // var response = await client.GetStreamAsync("http://localhost:8384/sse_with_no_spaces"); + // + // var events = new List(); + // + // await foreach (var sseEvent in StreamUtil.ReadAsSSEAsync(response)) + // { + // events.Add(sseEvent); + // } + // + // Assert.Equal(5, events.Count); + // + // for (int i = 0; i < 5; i++) + // { + // Assert.Equal(JsonConvert.SerializeObject(new { count = i }), events[i].Data); + // Assert.Equal("sse-test", events[i].Id); + // Assert.Equal("flow", events[i].Event); + // Assert.Equal(3, events[i].Retry); + // } + // } + // } + // + // [Fact] + // public async Task Test_ReadAsSSEAsync_WithInvalidRetry() + // { + // using (var client = new HttpClient()) + // { + // var response = await client.GetStreamAsync("http://localhost:8384/sse_invalid_retry"); + // + // var events = new List(); + // + // await foreach (var sseEvent in StreamUtil.ReadAsSSEAsync(response)) + // { + // events.Add(sseEvent); + // } + // + // Assert.Equal(5, events.Count); + // + // for (int i = 0; i < 5; i++) + // { + // Assert.Equal(JsonConvert.SerializeObject(new { count = i }), events[i].Data); + // Assert.Equal("sse-test", events[i].Id); + // Assert.Equal("flow", events[i].Event); + // Assert.Null(events[i].Retry); + // } + // } + // } + // + // [Fact] + // public async Task Test_ReadAsSSEAsync_WithDividedData() + // { + // using (var client = new HttpClient()) + // { + // var response = await client.GetStreamAsync("http://localhost:8384/sse_with_data_divided"); + // + // var events = new List(); + // + // await foreach (var sseEvent in StreamUtil.ReadAsSSEAsync(response)) + // { + // events.Add(sseEvent); + // } + // Assert.Equal(4, events.Count); + // Assert.Equal(JsonConvert.SerializeObject(new { count = 0 }), events[0].Data); + // Assert.Equal("sse-test", events[0].Id); + // Assert.Equal("flow", events[0].Event); + // Assert.Equal(3, events[0].Retry); + // + // Assert.Equal(JsonConvert.SerializeObject(new { count = 2, tag = "divided" }), events[1].Data); + // Assert.Equal("sse-test", events[1].Id); + // Assert.Equal("flow", events[1].Event); + // Assert.Equal(3, events[1].Retry); + // + // Assert.Equal(JsonConvert.SerializeObject(new { count = 3 }), events[2].Data); + // Assert.Equal("sse-test", events[2].Id); + // Assert.Equal("flow", events[2].Event); + // Assert.Equal(3, events[2].Retry); + // + // Assert.Equal(JsonConvert.SerializeObject(new { count = 4 }), events[3].Data); + // Assert.Equal("sse-test", events[3].Id); + // Assert.Equal("flow", events[3].Event); + // Assert.Equal(3, events[3].Retry); + // } + // } + + [Fact] + public void Test_ReadAsSSE() + { + using (var client = new HttpClient()) + { + var response = client.GetStreamAsync("http://localhost:8384/sse").Result; + var events = new List(); + + foreach (var sseEvent in StreamUtil.ReadAsSSE(response)) + { + events.Add(sseEvent); + } + + for (int i = 0; i < 5; i++) + { + Assert.Equal(JsonConvert.SerializeObject(new { count = i }), events[i].Data); + Assert.Equal("sse-test", events[i].Id); + Assert.Equal("flow", events[i].Event); + Assert.Equal(3, events[i].Retry); + } + } + } + + [Fact] + public async Task Test_ReadAsSSE_WithNoSpaces() + { + using (var client = new HttpClient()) + { + var response = await client.GetStreamAsync("http://localhost:8384/sse_with_no_spaces"); + + var events = new List(); + + foreach (var sseEvent in StreamUtil.ReadAsSSE(response)) + { + events.Add(sseEvent); + } + + Assert.Equal(5, events.Count); + + for (int i = 0; i < 5; i++) + { + Assert.Equal(JsonConvert.SerializeObject(new { count = i }), events[i].Data); + Assert.Equal("sse-test", events[i].Id); + Assert.Equal("flow", events[i].Event); + Assert.Equal(3, events[i].Retry); + } + } + } + + [Fact] + public async Task Test_ReadAsSSE_WithInvalidRetry() + { + using (var client = new HttpClient()) + { + var response = await client.GetStreamAsync("http://localhost:8384/sse_invalid_retry"); + + var events = new List(); + + foreach (var sseEvent in StreamUtil.ReadAsSSE(response)) + { + events.Add(sseEvent); + } + + Assert.Equal(5, events.Count); + + for (int i = 0; i < 5; i++) + { + Assert.Equal(JsonConvert.SerializeObject(new { count = i }), events[i].Data); + Assert.Equal("sse-test", events[i].Id); + Assert.Equal("flow", events[i].Event); + Assert.Null(events[i].Retry); + } + } + } + + [Fact] + public async Task Test_ReadAsSSE_WithDividedData() + { + using (var client = new HttpClient()) + { + var response = await client.GetStreamAsync("http://localhost:8384/sse_with_data_divided"); + + var events = new List(); + + foreach (var sseEvent in StreamUtil.ReadAsSSE(response)) + { + events.Add(sseEvent); + } + + Assert.Equal(4, events.Count); + Assert.Equal(JsonConvert.SerializeObject(new { count = 0 }), events[0].Data); + Assert.Equal("sse-test", events[0].Id); + Assert.Equal("flow", events[0].Event); + Assert.Equal(3, events[0].Retry); + + Assert.Equal(JsonConvert.SerializeObject(new { count = 2, tag = "divided" }), events[1].Data); + Assert.Equal("sse-test", events[1].Id); + Assert.Equal("flow", events[1].Event); + Assert.Equal(3, events[1].Retry); + + Assert.Equal(JsonConvert.SerializeObject(new { count = 3 }), events[2].Data); + Assert.Equal("sse-test", events[2].Id); + Assert.Equal("flow", events[2].Event); + Assert.Equal(3, events[2].Retry); + + Assert.Equal(JsonConvert.SerializeObject(new { count = 4 }), events[3].Data); + Assert.Equal("sse-test", events[3].Id); + Assert.Equal("flow", events[3].Event); + Assert.Equal(3, events[3].Retry); + } + } + } +} + diff --git a/DarabonbaUnitTests/Utils/StringUtilTest.cs b/DarabonbaUnitTests/Utils/StringUtilTest.cs new file mode 100644 index 0000000..b765615 --- /dev/null +++ b/DarabonbaUnitTests/Utils/StringUtilTest.cs @@ -0,0 +1,74 @@ +using System; +using Darabonba.Utils; +using Xunit; + +namespace DaraUnitTests.Utils +{ + public class StringUtilTest + { + [Fact] + public void TestSubString() + { + int? start = null; + int? end = null; + Assert.Throws(() => StringUtil.SubString("test", start, end)); + Assert.Equal("te", StringUtil.SubString("test", 0, 2)); + } + + [Fact] + public void TestToBytes() + { + string data = "test"; + Assert.Equal(new byte[] { 116, 101, 115, 116 }, BytesUtil.From(data, "utf8")); + Assert.Equal(new byte[] { 116, 101, 115, 116 }, BytesUtil.From(data, "ASCII")); + Assert.Equal(new byte[] { 0, 116, 0, 101, 0, 115, 0, 116 }, BytesUtil.From(data, "bigendianunicode")); + Assert.Equal(new byte[] { 116, 0, 101, 0, 115, 0, 116, 0 }, BytesUtil.From(data, "unicode")); + Assert.Equal(new byte[] { 116, 0, 0, 0, 101, 0, 0, 0, 115, 0, 0, 0, 116, 0, 0, 0 }, BytesUtil.From(data, "utf32")); + } + + // [Fact] + // public void TestReplace() + // { + // string pattern = "/beijing/"; + // string replacement = "chengdu"; + // string data = "Beijing city, hangzhou city, beijing city, another city, beijing city"; + // string res = StringUtil.Replace(data, replacement, pattern); + // Assert.Equal("Beijing city, hangzhou city, chengdu city, another city, beijing city", res); + // pattern = "/beijing/g"; + // res = StringUtil.Replace(data, replacement, pattern); + // Assert.Equal("Beijing city, hangzhou city, chengdu city, another city, chengdu city", res); + // pattern = "/beijing/gi"; + // res = StringUtil.Replace(data, replacement, pattern); + // Assert.Equal("chengdu city, hangzhou city, chengdu city, another city, chengdu city", res); + // pattern = "/beijing/i"; + // res = StringUtil.Replace(data, replacement, pattern); + // Assert.Equal("chengdu city, hangzhou city, beijing city, another city, beijing city", res); + // pattern = @"\S+(?=\s*city)"; + // res = StringUtil.Replace(data, replacement, pattern); + // Assert.Equal("chengdu city, chengdu city, chengdu city, chengdu city, chengdu city", res); + // } + + [Fact] + public void TestParse() + { + string numberStr = "1.3433"; + int intRes = StringUtil.ParseInt(numberStr); + Assert.Equal(1, intRes); + numberStr = "2"; + intRes = StringUtil.ParseInt(numberStr); + Assert.Equal(2, intRes); + numberStr = "1,682.80"; + intRes = StringUtil.ParseInt(numberStr); + Assert.Equal(1682, intRes); + numberStr = "1.682.80"; + Assert.Throws(() => StringUtil.ParseInt(numberStr)); + numberStr = "3.1415926"; + float floatRes = StringUtil.ParseFloat(numberStr); + Assert.Equal(3.1415926f, floatRes); + numberStr = "3.1415926"; + long longRes = StringUtil.ParseLong(numberStr); + Assert.Equal(3, longRes); + } + + } +} \ No newline at end of file diff --git a/DarabonbaUnitTests/Utils/XmlUtilTest.cs b/DarabonbaUnitTests/Utils/XmlUtilTest.cs new file mode 100644 index 0000000..dda4fae --- /dev/null +++ b/DarabonbaUnitTests/Utils/XmlUtilTest.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using Darabonba; +using Darabonba.Utils; +using Xunit; +using DaraUnitTests.Models; +using static DaraUnitTests.Models.ListAllMyBucketsResult; +using static DaraUnitTests.Models.ListAllMyBucketsResult.Buckets; + +namespace DaraUnitTests.Utils +{ + public class XmlUtilTest + { + ToBodyModel model; + public XmlUtilTest() + { + model = new ToBodyModel(); + ListAllMyBucketsResult result = new ListAllMyBucketsResult(); + Buckets buckets = new Buckets(); + buckets.bucket = new List(); + buckets.bucket.Add(new Bucket { CreationDate = "2015-12-17T18:12:43.000Z", ExtranetEndpoint = "oss-cn-shanghai.aliyuncs.com", IntranetEndpoint = "oss-cn-shanghai-internal.aliyuncs.com", Location = "oss-cn-shanghai", Name = "app-base-oss", StorageClass = "Standard" }); + buckets.bucket.Add(new Bucket { CreationDate = "2014-12-25T11:21:04.000Z", ExtranetEndpoint = "oss-cn-hangzhou.aliyuncs.com", IntranetEndpoint = "oss-cn-hangzhou-internal.aliyuncs.com", Location = "oss-cn-hangzhou", Name = "atestleo23", StorageClass = "IA" }); + buckets.bucket.Add(null); + result.buckets = buckets; + Owner owner = new Owner { ID = 512, DisplayName = "51264" }; + result.owner = owner; + model.listAllMyBucketsResult = result; + model.listAllMyBucketsResult.testStrList = new List { "1", "2" }; + model.listAllMyBucketsResult.owners = new List(); + model.listAllMyBucketsResult.owners.Add(owner); + model.listAllMyBucketsResult.TestDouble = 1; + model.listAllMyBucketsResult.TestFloat = 2; + model.listAllMyBucketsResult.TestLong = 3; + model.listAllMyBucketsResult.TestShort = 4; + model.listAllMyBucketsResult.TestUInt = 5; + model.listAllMyBucketsResult.TestULong = 6; + model.listAllMyBucketsResult.TestUShort = 7; + model.listAllMyBucketsResult.TestBool = true; + model.listAllMyBucketsResult.TestNull = null; + model.listAllMyBucketsResult.TestString = "string"; + model.listAllMyBucketsResult.TestListNull = null; + model.listAllMyBucketsResult.dict = new Dictionary { { "key", "value" } }; + } + + [Fact] + public void Test_ToXml() + { + Model modelNull = new Model(); + Assert.Empty(XmlUtil.ToXML(modelNull.ToMap())); + + ToBodyModel model = new ToBodyModel(); + ListAllMyBucketsResult result = new ListAllMyBucketsResult(); + Buckets buckets = new Buckets + { + bucket = new List + { + new Bucket { CreationDate = "2015-12-17T18:12:43.000Z", ExtranetEndpoint = "oss-cn-shanghai.aliyuncs.com", IntranetEndpoint = "oss-cn-shanghai-internal.aliyuncs.com", Location = "oss-cn-shanghai", Name = "app-base-oss", StorageClass = "Standard" }, + new Bucket { CreationDate = "2014-12-25T11:21:04.000Z", ExtranetEndpoint = "oss-cn-hangzhou.aliyuncs.com", IntranetEndpoint = "oss-cn-hangzhou-internal.aliyuncs.com", Location = "oss-cn-hangzhou", Name = "atestleo23", StorageClass = "IA" }, + null + } + }; + result.buckets = buckets; + Owner owner = new Owner { ID = 512, DisplayName = "51264" }; + result.owner = owner; + model.listAllMyBucketsResult = result; + model.listAllMyBucketsResult.testStrList = new List { "1", "2" }; + model.listAllMyBucketsResult.owners = new List + { + owner + }; + model.listAllMyBucketsResult.TestDouble = 1; + model.listAllMyBucketsResult.TestFloat = 2; + model.listAllMyBucketsResult.TestLong = 3; + model.listAllMyBucketsResult.TestShort = 4; + model.listAllMyBucketsResult.TestUInt = 5; + model.listAllMyBucketsResult.TestULong = 6; + model.listAllMyBucketsResult.TestUShort = 7; + model.listAllMyBucketsResult.TestBool = true; + model.listAllMyBucketsResult.TestNull = null; + model.listAllMyBucketsResult.TestListNull = null; + string xmlStr = XmlUtil.ToXML(model.ToMap()); + Assert.NotNull(xmlStr); + + Dictionary xmlBody = (Dictionary)XmlUtil.ParseXml(xmlStr, typeof(ToBodyModel)); + ToBodyModel teaModel = Model.ToObject(xmlBody); + Assert.NotNull(teaModel); + Assert.Equal(1, teaModel.listAllMyBucketsResult.TestDouble); + + string xml = "\n" + + "disNamekey"; + Dictionary map = XmlUtil.ParseXml(xml, null); + Assert.False(map.ContainsKey("xml")); + Assert.Single(map); + Assert.True(((Dictionary)map["body"]).ContainsKey("Contents")); + List list = (List)((Dictionary)map["body"])["Contents"]; + Assert.Equal(2, list.Count); + Assert.Equal("key", ((Dictionary)list[0])["Key"]); + Assert.Equal("disName", ((Dictionary)((Dictionary)list[0])["Owner"])["DisplayName"]); + Assert.Null((Dictionary)list[1]); + } + + [Fact] + public void TestXml() + { + string xmlStr = XmlUtil.SerializeXml(model); + Assert.NotNull(xmlStr); + Assert.NotEmpty(xmlStr); + + Assert.Equal(xmlStr, XmlUtil.SerializeXml(model.ToMap())); + + model.listAllMyBucketsResult.dict = null; + xmlStr = XmlUtil.SerializeXml(model); + Dictionary dict = XmlUtil.DeserializeXml(xmlStr, model.GetType()); + Assert.NotNull(dict); + } + } +} diff --git a/DarabonbaUnitTests/ValidatorTest.cs b/DarabonbaUnitTests/ValidatorTest.cs new file mode 100644 index 0000000..e4c39fb --- /dev/null +++ b/DarabonbaUnitTests/ValidatorTest.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using Darabonba; +using Xunit; + +namespace DaraUnitTests +{ + public class ValidatorTest + { + [Fact] + public void TestDaraValidator() + { + Validator validator = new Validator(null, "propertyName"); + validator.ValidateRequired("test"); + validator.ValidateRegex("test"); + Assert.NotNull(validator); + + ValidationAttribute attribute = new ValidationAttribute(); + attribute.Required = false; + validator.Attribute = attribute; + validator.ValidateRequired("test"); + Assert.NotNull(validator); + + attribute.Pattern = ""; + validator.ValidateRegex("test"); + Assert.NotNull(validator); + + attribute.Pattern = "pattern"; + validator.ValidateRegex(null); + Assert.NotNull(validator); + + validator.ValidateRegex("patternTest"); + Assert.NotNull(validator); + + Assert.Equal("propertyName is not match pattern", + Assert.Throws(() => { validator.ValidateRegex("test"); }).Message + ); + + attribute.Required = true; + Assert.Equal("propertyName is required.", + Assert.Throws(() => { validator.ValidateRequired(null); }).Message + ); + + attribute.MaxLength = 3; + validator.ValidateMaxLength("阿里"); + Assert.Equal("propertyName is exceed max-length: 3", + Assert.Throws(() => { validator.ValidateMaxLength("阿里test"); }).Message + ); + + List list = new List{ "1", "2","3","4" }; + validator.ValidateMaxLength("阿里"); + Assert.Equal("propertyName is exceed max-length: 3", + Assert.Throws(() => { validator.ValidateMaxLength(list); }).Message + ); + + attribute.MinLength = 2; + validator.ValidateMinLength("阿里"); + Assert.Equal("propertyName is less than min-length: 2", + Assert.Throws(() => { validator.ValidateMinLength("阿"); }).Message + ); + + attribute.Maximun = 1.5; + validator.ValidateMaximum("1"); + Assert.Equal("propertyName is exceed maximum: 1.5", + Assert.Throws(() => { validator.ValidateMaximum(2); }).Message + ); + + attribute.Minimum = 1; + validator.ValidateMinimum(1.5); + Assert.Equal("propertyName is less than Minimum: 1", + Assert.Throws(() => { validator.ValidateMinimum(-2); }).Message + ); + } + } +} diff --git a/TeaUnitTests/xunit.runner.json b/DarabonbaUnitTests/xunit.runner.json similarity index 100% rename from TeaUnitTests/xunit.runner.json rename to DarabonbaUnitTests/xunit.runner.json diff --git a/Tea/TeaConverter.cs b/Tea/TeaConverter.cs deleted file mode 100644 index c895ea8..0000000 --- a/Tea/TeaConverter.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Tea -{ - public class TeaConverter - { - public static Dictionary merge(params object[] objs) - { - Dictionary dicResult = new Dictionary(); - if (objs == null) - { - return dicResult; - } - - foreach (object obj in objs) - { - if (obj == null) - { - continue; - } - Dictionary dicObj = new Dictionary(); - Type typeObj = obj.GetType(); - if (typeof(TeaModel).IsAssignableFrom(typeObj)) - { - dicObj = ((TeaModel) obj).ToMap(); - } - else if (obj is Dictionary) - { - dicObj = (Dictionary) obj; - } - else if (obj is Dictionary) - { - Dictionary dicString = (Dictionary) obj; - foreach (var keypair in dicString) - { - dicObj.Add(keypair.Key, keypair.Value); - } - } - else - { - throw new ArgumentException(" inparams only support Dictionary or TeaModel. "); - } - - foreach (var keypair in dicObj) - { - T dicValue = (T) keypair.Value; - if (dicResult.ContainsKey(keypair.Key)) - { - dicResult[keypair.Key] = dicValue; - } - else - { - dicResult.Add(keypair.Key, dicValue); - } - } - } - return dicResult; - } - - public static string StrToLower(string str) - { - if (string.IsNullOrWhiteSpace(str)) - { - return string.Empty; - } - else - { - return str.ToLower(); - } - } - } -} diff --git a/Tea/TeaException.cs b/Tea/TeaException.cs deleted file mode 100644 index 555999b..0000000 --- a/Tea/TeaException.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -using Tea.Utils; - -namespace Tea -{ - public class TeaException : Exception - { - private string code; - private string message; - private Dictionary data; - private int statusCode; - private string description; - private Dictionary accessDeniedDetail; - - public string Code - { - get - { - return code; - } - } - - public override string Message - { - get - { - return message; - } - } - - public Dictionary DataResult - { - get - { - return data; - } - } - - public int StatusCode - { - get - { - return statusCode; - } - } - - - public string Description - { - get - { - return description; - } - } - - public Dictionary AccessDeniedDetail - { - get - { - return accessDeniedDetail; - } - } - - public TeaException(IDictionary dict) - { - Dictionary dicObj = dict.Keys.Cast().ToDictionary(key => key, key => dict[key]); - code = DictUtils.GetDicValue(dicObj, "code").ToSafeString(); - message = DictUtils.GetDicValue(dicObj, "message").ToSafeString(); - description = DictUtils.GetDicValue(dicObj, "description").ToSafeString(); - object obj = DictUtils.GetDicValue(dicObj, "accessDeniedDetail"); - if (obj != null) - { - if (typeof(IDictionary).IsAssignableFrom(obj.GetType())) - { - IDictionary dicDetail = (IDictionary) obj; - accessDeniedDetail = dicDetail.Keys.Cast().ToDictionary(key => key, key => dicDetail[key]); - } - } - obj = DictUtils.GetDicValue(dicObj, "data"); - if (obj == null) - { - return; - } - if (typeof(IDictionary).IsAssignableFrom(obj.GetType())) - { - IDictionary dicData = (IDictionary) obj; - data = dicData.Keys.Cast().ToDictionary(key => key, key => dicData[key]); - if (DictUtils.GetDicValue(data, "statusCode") != null) - { - statusCode = int.Parse(DictUtils.GetDicValue(data, "statusCode").ToSafeString()); - } - return; - } - - Dictionary filedsDict = new Dictionary(); - Type type = obj.GetType(); - PropertyInfo[] properties = type.GetProperties(); - for (int i = 0; i < properties.Length; i++) - { - PropertyInfo p = properties[i]; - filedsDict.Add(p.Name, p.GetValue(obj)); - } - data = filedsDict; - } - } -} diff --git a/Tea/TeaRetryableException.cs b/Tea/TeaRetryableException.cs deleted file mode 100644 index 7fe3f3e..0000000 --- a/Tea/TeaRetryableException.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Tea -{ - public class TeaRetryableException : Exception - { - public TeaRetryableException() - { - } - } -} diff --git a/Tea/TeaUnretryableException.cs b/Tea/TeaUnretryableException.cs deleted file mode 100644 index 0453fcf..0000000 --- a/Tea/TeaUnretryableException.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Tea -{ - public class TeaUnretryableException : Exception - { - private readonly TeaRequest _lastRequest; - - public TeaRequest LastRequest { get { return _lastRequest; } } - - public TeaUnretryableException() : base() - { - - } - - public TeaUnretryableException(TeaRequest lastRequest, Exception innerException) : base(" Retry failed : " + (innerException == null ? "" : innerException.Message), innerException) - { - _lastRequest = lastRequest; - } - } -} diff --git a/TeaUnitTests/TeaConverterTest.cs b/TeaUnitTests/TeaConverterTest.cs deleted file mode 100644 index bc6462c..0000000 --- a/TeaUnitTests/TeaConverterTest.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; - -using Tea; - -using TeaUnitTests.Models; - -using Xunit; - -namespace TeaUnitTests -{ - public class TestObject - { - public string name { get; set; } - } - - public class TeaConverterTests - { - [Fact] - public void TestMerge() - { - Assert.Empty(TeaConverter.merge(null)); - - Dictionary dic = new Dictionary(); - Dictionary dicNull = null; - Dictionary dicMerge = new Dictionary(); - TestRegModel model = new TestRegModel(); - - dic.Add("testNull", null); - dic.Add("testExist", "testExist"); - dic.Add("test", "test"); - dicMerge.Add("testMerge", "testMerge"); - dicMerge.Add("testExist", "IsExist"); - Dictionary dicResult = TeaConverter.merge(dic, dicNull, dicMerge, null); - Assert.NotNull(dicResult); - Assert.Equal(4, dicResult.Count); - - Dictionary dicModelMerge = TeaConverter.merge(dic, dicNull, dicMerge, model); - Assert.NotNull(dicResult); - - Assert.Throws(() => { TeaConverter.merge(dic, 1); }); - } - - [Fact] - public void TestStrToLower() - { - Assert.Empty(TeaConverter.StrToLower(null)); - - Assert.Equal("test", TeaConverter.StrToLower("TEST")); - } - - } -} diff --git a/TeaUnitTests/TeaCoreTest.cs b/TeaUnitTests/TeaCoreTest.cs deleted file mode 100644 index 6f7d707..0000000 --- a/TeaUnitTests/TeaCoreTest.cs +++ /dev/null @@ -1,289 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; - -using Tea; - -using Xunit; - -namespace TeaUnitTests -{ - public class TeaCoreTest - { - [Fact] - public void TestComposeUrl() - { - TeaRequest teaRequest = new TeaRequest(); - - var url = TeaCore.ComposeUrl(teaRequest); - Assert.Equal("http://", url); - - teaRequest.Headers["host"] = "fake.domain.com"; - url = TeaCore.ComposeUrl(teaRequest); - Assert.Equal("http://fake.domain.com", url); - - teaRequest.Port = 8080; - url = TeaCore.ComposeUrl(teaRequest); - Assert.Equal("http://fake.domain.com:8080", url); - - teaRequest.Pathname = "/index.html"; - url = TeaCore.ComposeUrl(teaRequest); - Assert.Equal("http://fake.domain.com:8080/index.html", url); - - teaRequest.Query["foo"] = ""; - url = TeaCore.ComposeUrl(teaRequest); - Assert.Equal("http://fake.domain.com:8080/index.html?foo=", url); - - teaRequest.Query["foo"] = "bar"; - url = TeaCore.ComposeUrl(teaRequest); - Assert.Equal("http://fake.domain.com:8080/index.html?foo=bar", url); - - teaRequest.Pathname = "/index.html?a=b"; - url = TeaCore.ComposeUrl(teaRequest); - Assert.Equal("http://fake.domain.com:8080/index.html?a=b&foo=bar", url); - - teaRequest.Pathname = "/index.html?a=b&"; - url = TeaCore.ComposeUrl(teaRequest); - Assert.Equal("http://fake.domain.com:8080/index.html?a=b&foo=bar", url); - - teaRequest.Query["fake"] = null; - url = TeaCore.ComposeUrl(teaRequest); - Assert.Equal("http://fake.domain.com:8080/index.html?a=b&foo=bar", url); - - teaRequest.Query["fake"] = "val*"; - url = TeaCore.ComposeUrl(teaRequest); - Assert.Equal("http://fake.domain.com:8080/index.html?a=b&foo=bar&fake=val%2A", url); - } - - [Fact] - public void TestDoAction() - { - TeaRequest teaRequest = new TeaRequest(); - teaRequest.Protocol = "http"; - teaRequest.Method = "GET"; - teaRequest.Headers = new Dictionary(); - teaRequest.Headers["host"] = "www.alibabacloud.com"; - teaRequest.Pathname = "/s/zh"; - teaRequest.Query = new Dictionary(); - teaRequest.Query.Add("k", "ecs"); - Dictionary runtime = new Dictionary(); - runtime.Add("readTimeout", 7000); - runtime.Add("connectTimeout", 7000); - runtime.Add("httpsProxy", "http://www.alibabacloud.com/s/zh?k=ecs"); - runtime.Add("ignoreSSL", true); - - TeaResponse teaResponse = TeaCore.DoAction(teaRequest, runtime); - Assert.NotNull(teaResponse); - - teaRequest.Protocol = "https"; - teaResponse = TeaCore.DoAction(teaRequest); - Assert.NotNull(teaResponse); - - - string bodyStr = TeaCore.GetResponseBody(teaResponse); - Assert.NotNull(bodyStr); - - teaRequest.Method = "POST"; - teaRequest.Body = new MemoryStream(Encoding.UTF8.GetBytes("test")); - teaRequest.Headers["content-test"] = "test"; - teaResponse = TeaCore.DoAction(teaRequest, runtime); - Assert.NotNull(teaResponse); - - TeaRequest teaRequest404 = new TeaRequest(); - teaRequest404.Protocol = "https"; - teaRequest404.Method = "GET"; - teaRequest404.Headers = new Dictionary(); - teaRequest404.Headers["host"] = "www.alibabacloud404.com"; - teaRequest404.Pathname = "/s/zh"; - teaRequest404.Query = new Dictionary(); - teaRequest404.Query.Add("k", "ecs"); - Dictionary runtime404 = new Dictionary(); - runtime404.Add("readTimeout", 7000); - runtime404.Add("connectTimeout", 7000); - Assert.Throws(() => { TeaCore.DoAction(teaRequest404, runtime404); }); - - TeaRequest teaRequestProxy = new TeaRequest(); - - - TeaRequest requestException = new TeaRequest - { - Protocol = "http", - Method = "GET", - Pathname = "/test" - }; - Dictionary runtimeException = new Dictionary(); - requestException.Headers["host"] = "www.aliyun.com"; - TeaResponse responseException = TeaCore.DoAction(requestException, runtimeException); - Assert.NotNull(responseException); - } - - [Fact] - public async Task TestDoActionAsync() - { - TeaRequest teaRequest = new TeaRequest(); - teaRequest.Protocol = "https"; - teaRequest.Method = "GET"; - teaRequest.Headers = new Dictionary(); - teaRequest.Headers["host"] = "www.alibabacloud.com"; - teaRequest.Pathname = "/s/zh"; - teaRequest.Query = new Dictionary(); - teaRequest.Query.Add("k", "ecs"); - - TeaResponse teaResponse = await TeaCore.DoActionAsync(teaRequest); - Assert.NotNull(teaResponse); - - Dictionary runtime = new Dictionary(); - runtime.Add("readTimeout", 4000); - runtime.Add("connectTimeout", 0); - - teaResponse = await TeaCore.DoActionAsync(teaRequest, runtime); - Assert.NotNull(teaResponse); - - string bodyStr = TeaCore.GetResponseBody(teaResponse); - Assert.NotNull(bodyStr); - - teaRequest.Method = "POST"; - teaRequest.Body = new MemoryStream(Encoding.UTF8.GetBytes("test")); - teaResponse = await TeaCore.DoActionAsync(teaRequest, runtime); - Assert.NotNull(teaResponse); - - TeaRequest teaRequest404 = new TeaRequest(); - teaRequest404.Protocol = "https"; - teaRequest404.Method = "GET"; - teaRequest404.Headers = new Dictionary(); - teaRequest404.Headers["host"] = "www.alibabacloud404.com"; - teaRequest404.Pathname = "/s/zh"; - teaRequest404.Query = new Dictionary(); - teaRequest404.Query.Add("k", "ecs"); - Dictionary runtime404 = new Dictionary(); - runtime404.Add("readTimeout", 7000); - runtime404.Add("connectTimeout", 7000); - await Assert.ThrowsAsync(async() => { await TeaCore.DoActionAsync(teaRequest404, runtime); }); - - TeaRequest requestException = new TeaRequest - { - Protocol = "http", - Method = "GET", - Pathname = "/test" - }; - Dictionary runtimeException = new Dictionary(); - requestException.Headers["host"] = "www.aliyun.com"; - TeaResponse responseException = await TeaCore.DoActionAsync(requestException, runtimeException); - } - - [Fact] - public void TestConvertHeaders() - { - WebHeaderCollection headers = new WebHeaderCollection(); - headers.Add("testKey", "testValue"); - Dictionary dic = TeaCore.ConvertHeaders(headers); - Assert.NotNull(dic); - Assert.True(dic.ContainsKey("testkey")); - Assert.Equal("testValue", dic["testkey"]); - } - - [Fact] - public void TestAllowRetry() - { - long _now = System.DateTime.Now.Millisecond; - - Assert.True(TeaCore.AllowRetry(null, 0, _now)); - - Assert.False(TeaCore.AllowRetry(null, 3, _now)); - - Dictionary dic = new Dictionary(); - Assert.False(TeaCore.AllowRetry(dic, 3, _now)); - - dic.Add("retryable", true); - dic.Add("maxAttempts", null); - Assert.False(TeaCore.AllowRetry(dic, 3, _now)); - - dic["maxAttempts"] = 5; - Assert.True(TeaCore.AllowRetry(dic, 3, _now)); - - Dictionary dicInt = new Dictionary(); - Assert.False(TeaCore.AllowRetry(dicInt, 3, _now)); - } - - [Fact] - public void TestGetBackoffTime() - { - Dictionary dic = new Dictionary(); - Assert.Equal(0, TeaCore.GetBackoffTime(dic, 1)); - - dic.Add("policy", null); - Assert.Equal(0, TeaCore.GetBackoffTime(dic, 1)); - - dic["policy"] = string.Empty; - Assert.Equal(0, TeaCore.GetBackoffTime(dic, 1)); - - dic["policy"] = "no"; - Assert.Equal(0, TeaCore.GetBackoffTime(dic, 1)); - - dic["policy"] = "yes"; - Assert.Equal(0, TeaCore.GetBackoffTime(dic, 1)); - - dic.Add("period", null); - Assert.Equal(0, TeaCore.GetBackoffTime(dic, 1)); - - dic["period"] = -1; - Assert.Equal(1, TeaCore.GetBackoffTime(dic, 1)); - - dic["period"] = 1000; - Assert.Equal(1000, TeaCore.GetBackoffTime(dic, 1)); - } - - [Fact] - public void TestSleep() - { - TimeSpan tsBefore = new TimeSpan(DateTime.Now.Ticks); - TeaCore.Sleep(1000); - TimeSpan tsAfter = new TimeSpan(DateTime.Now.Ticks); - TimeSpan tsSubtract = tsBefore.Subtract(tsAfter).Duration(); - Assert.InRange(tsSubtract.TotalMilliseconds, 990, 1100); - } - - [Fact] - public async void TestSleepAsync() - { - TimeSpan tsBefore = new TimeSpan(DateTime.Now.Ticks); - await TeaCore.SleepAsync(1000); - TimeSpan tsAfter = new TimeSpan(DateTime.Now.Ticks); - TimeSpan tsSubtract = tsBefore.Subtract(tsAfter).Duration(); - Assert.InRange(tsSubtract.TotalMilliseconds, 990, 1000000); - } - - [Fact] - public void TestIsRetryable() - { - Exception ex = new Exception(); - Assert.False(TeaCore.IsRetryable(ex)); - - TeaRetryableException webEx = new TeaRetryableException(); - Assert.True(TeaCore.IsRetryable(webEx)); - } - - [Fact] - public void TestBytesReadable() - { - string str = "test"; - Stream stream = TeaCore.BytesReadable(str); - byte[] bytes = new byte[stream.Length]; - stream.Read(bytes, 0, bytes.Length); - string bytesStr = Encoding.UTF8.GetString(bytes); - Assert.Equal("test", bytesStr); - } - - [Fact] - public void Test_PercentEncode() - { - Assert.Null(TeaCore.PercentEncode(null)); - - Assert.Equal("test%3D", TeaCore.PercentEncode("test=")); - } - } -} diff --git a/TeaUnitTests/TeaExceptionTest.cs b/TeaUnitTests/TeaExceptionTest.cs deleted file mode 100644 index 6e30024..0000000 --- a/TeaUnitTests/TeaExceptionTest.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.Collections.Generic; - -using Tea; -using Tea.Utils; - -using Xunit; - -namespace TeaUnitTests -{ - public class TeaExceptionTest - { - [Fact] - public void TestTeaException() - { - TeaException teaException = new TeaException(new Dictionary - { { "code", "200" }, - { "message", "message" }, - { "data", null } - }); - - Assert.NotNull(teaException); - Assert.Equal("200", teaException.Code); - Assert.Equal("message", teaException.Message); - Assert.Null(teaException.DataResult); - - teaException = new TeaException(new Dictionary - { { "code", "200" }, - { "message", "message" }, - { - "data", - new Dictionary - { { "test", "test" } - } - } - }); - Assert.NotNull(teaException); - Assert.NotNull(teaException.DataResult); - - teaException = new TeaException(new Dictionary - { { "code", "200" }, - { "message", "message" }, - { - "data", - new - { - test = "test" - } - } - }); - Assert.NotNull(teaException); - Assert.NotNull(teaException.DataResult); - - teaException = new TeaException(new Dictionary - { { "code", "200" } - }); - Assert.NotNull(teaException); - Assert.Equal("200", teaException.Code); - - teaException = new TeaException(new Dictionary - { { "code", "code" }, - { "message", "message" }, - { "description", "description" }, - { - "data", - new Dictionary - { { "test", "test" }, - {"statusCode", 200} - } - }, - { - "accessDeniedDetail", - new Dictionary - { { "NoPermissionType", "ImplicitDeny" } - } - } - }); - Assert.NotNull(teaException); - Assert.Equal("code", teaException.Code); - Assert.Equal("message", teaException.Message); - Assert.Equal("description", teaException.Description); - Assert.Equal(200, teaException.StatusCode); - Assert.Equal("ImplicitDeny", DictUtils.GetDicValue(teaException.AccessDeniedDetail, "NoPermissionType")); - - teaException = new TeaException(new Dictionary - { { "code", "code" }, - { - "accessDeniedDetail", null - } - }); - Assert.NotNull(teaException); - Assert.Null(teaException.AccessDeniedDetail); - - teaException = new TeaException(new Dictionary - { { "code", "code" }, - { - "accessDeniedDetail", "error type" - } - }); - Assert.NotNull(teaException); - Assert.Null(teaException.AccessDeniedDetail); - } - } -} diff --git a/TeaUnitTests/TeaRequestTest.cs b/TeaUnitTests/TeaRequestTest.cs deleted file mode 100644 index bee6cb6..0000000 --- a/TeaUnitTests/TeaRequestTest.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Tea; - -using Xunit; - -namespace TeaUnitTests -{ - public class TeaRequestTest - { - [Fact] - public void TestTeaRequest() - { - TeaRequest teaRequest = new TeaRequest(); - Assert.NotNull(teaRequest); - Assert.NotNull(teaRequest.Headers); - Assert.NotNull(teaRequest.Query); - Assert.Equal("http", teaRequest.Protocol); - teaRequest.Headers = null; - Assert.NotNull(teaRequest.Headers); - Assert.Equal("GET", teaRequest.Method); - - teaRequest.Method = "POST"; - Assert.Equal("POST", teaRequest.Method); - - teaRequest.Query = null; - Assert.NotNull(teaRequest.Query); - } - } -} diff --git a/TeaUnitTests/TeaResponseTest.cs b/TeaUnitTests/TeaResponseTest.cs deleted file mode 100644 index 6cf57a5..0000000 --- a/TeaUnitTests/TeaResponseTest.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.IO; -using System.Net; -using System.Net.Http; -using System.Text; - -using Tea; - -using Xunit; - -namespace TeaUnitTests -{ - public class TeaResponseTest - { - [Fact] - public void TestTeaResponse() - { - HttpResponseMessage httpResponseMessage = new HttpResponseMessage(); - httpResponseMessage.StatusCode = HttpStatusCode.OK; - httpResponseMessage.Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("test"))); - TeaResponse response = new TeaResponse(httpResponseMessage); - Assert.NotNull(response); - Assert.Equal(200, response.StatusCode); - Assert.Equal("", response.StatusMessage); - - TeaResponse teaResponseNull = new TeaResponse(null); - Assert.Null(teaResponseNull.Body); - } - } -} diff --git a/TeaUnitTests/TeaRetryableExceptionTest.cs b/TeaUnitTests/TeaRetryableExceptionTest.cs deleted file mode 100644 index 538feba..0000000 --- a/TeaUnitTests/TeaRetryableExceptionTest.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.IO; -using System.Net; -using System.Net.Http; -using System.Text; - -using Tea; - -using Xunit; - -namespace TeaUnitTests -{ - public class TeaRetryableExceptionTest - { - [Fact] - public void TestTeaRetryableException() - { - HttpResponseMessage httpResponseMessage = new HttpResponseMessage(); - httpResponseMessage.StatusCode = HttpStatusCode.OK; - httpResponseMessage.Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("test"))); - TeaResponse response = new TeaResponse(httpResponseMessage); - TeaRetryableException teaRetryableException = new TeaRetryableException(); - Assert.NotNull(teaRetryableException); - } - } -} diff --git a/TeaUnitTests/TeaUnretryableExceptionTest.cs b/TeaUnitTests/TeaUnretryableExceptionTest.cs deleted file mode 100644 index 37b117a..0000000 --- a/TeaUnitTests/TeaUnretryableExceptionTest.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -using Tea; - -using Xunit; - -namespace TeaUnitTests -{ - public class TeaUnretryableExceptionTest - { - [Fact] - public void TestTeaUnretryableException() - { - TeaUnretryableException teaUnretryableExceptionEmpty = new TeaUnretryableException(); - Assert.NotNull(teaUnretryableExceptionEmpty); - - TeaUnretryableException teaUnretryableException = new TeaUnretryableException(new TeaRequest(), new Exception("Exception")); - Assert.NotNull(teaUnretryableException); - Assert.Equal(" Retry failed : Exception", teaUnretryableException.Message); - Assert.NotNull(teaUnretryableException.LastRequest); - } - } -} diff --git a/TeaUnitTests/TeaValidatorTest.cs b/TeaUnitTests/TeaValidatorTest.cs deleted file mode 100644 index 7306c38..0000000 --- a/TeaUnitTests/TeaValidatorTest.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using Tea; - -using Xunit; - -namespace TeaUnitTests -{ - public class TeaValidatorTest - { - [Fact] - public void TestTeaValidator() - { - TeaValidator teaValidator = new TeaValidator(null, "propertyName"); - teaValidator.ValidateRequired("test"); - teaValidator.ValidateRegex("test"); - Assert.NotNull(teaValidator); - - ValidationAttribute attribute = new ValidationAttribute(); - attribute.Required = false; - teaValidator.Attribute = attribute; - teaValidator.ValidateRequired("test"); - Assert.NotNull(teaValidator); - - attribute.Pattern = ""; - teaValidator.ValidateRegex("test"); - Assert.NotNull(teaValidator); - - attribute.Pattern = "pattern"; - teaValidator.ValidateRegex(null); - Assert.NotNull(teaValidator); - - teaValidator.ValidateRegex("patternTest"); - Assert.NotNull(teaValidator); - - Assert.Equal("propertyName is not match pattern", - Assert.Throws(() => { teaValidator.ValidateRegex("test"); }).Message - ); - - attribute.Required = true; - Assert.Equal("propertyName is required.", - Assert.Throws(() => { teaValidator.ValidateRequired(null); }).Message - ); - - attribute.MaxLength = 3; - teaValidator.ValidateMaxLength("阿里"); - Assert.Equal("propertyName is exceed max-length: 3", - Assert.Throws(() => { teaValidator.ValidateMaxLength("阿里test"); }).Message - ); - - List list = new List{ "1", "2","3","4" }; - teaValidator.ValidateMaxLength("阿里"); - Assert.Equal("propertyName is exceed max-length: 3", - Assert.Throws(() => { teaValidator.ValidateMaxLength(list); }).Message - ); - - attribute.MinLength = 2; - teaValidator.ValidateMinLength("阿里"); - Assert.Equal("propertyName is less than min-length: 2", - Assert.Throws(() => { teaValidator.ValidateMinLength("阿"); }).Message - ); - - attribute.Maximun = 1.5; - teaValidator.ValidateMaximum("1"); - Assert.Equal("propertyName is exceed maximum: 1.5", - Assert.Throws(() => { teaValidator.ValidateMaximum(2); }).Message - ); - - attribute.Minimum = 1; - teaValidator.ValidateMinimum(1.5); - Assert.Equal("propertyName is less than Minimum: 1", - Assert.Throws(() => { teaValidator.ValidateMinimum(-2); }).Message - ); - } - } -} diff --git a/appveyor.yml b/appveyor.yml index 7c69097..711d34e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,7 +16,7 @@ build_script: - cmd: dotnet build test_script: # Dotnet Test - - dotnet test TeaUnitTests/ /p:AltCover=true + - dotnet test DarabonbaUnitTests/ /p:AltCover=true on_finish: - ps: | $env:PATH = 'C:\msys64\usr\bin;' + $env:PATH diff --git a/codecov.yml b/codecov.yml index f596fa0..1b183bf 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,2 +1,2 @@ ignore: - - "TeaUnitTests/" # TeaUnitTests + - "DaraUnitTests/" # DaraUnitTests diff --git a/tea-csharp.sln b/tea-csharp.sln index 637f9ff..010ea02 100644 --- a/tea-csharp.sln +++ b/tea-csharp.sln @@ -2,9 +2,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29306.81 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tea", "Tea\tea.csproj", "{BA7831AD-0335-437E-8F8F-EDB62B874D22}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Darabonba", "Darabonba\Darabonba.csproj", "{BA7831AD-0335-437E-8F8F-EDB62B874D22}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeaUnitTests", "TeaUnitTests\TeaUnitTests.csproj", "{9BC6DAB8-9222-4856-AE71-05E0AB1B8B61}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DarabonbaUnitTests", "DarabonbaUnitTests\DarabonbaUnitTests.csproj", "{9BC6DAB8-9222-4856-AE71-05E0AB1B8B61}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,4 +27,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2CF3B193-FBD0-4AA9-9A31-98B1E8B11BE8} EndGlobalSection -EndGlobal \ No newline at end of file +EndGlobal