diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b8f0e4d..203266a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: - 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 diff --git a/Tea/Streams/FileFormStream.cs b/Tea/Streams/FileFormStream.cs new file mode 100644 index 0000000..ba37b58 --- /dev/null +++ b/Tea/Streams/FileFormStream.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tea.Streams +{ + 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/Tea/TeaArray.cs b/Tea/TeaArray.cs new file mode 100644 index 0000000..1630b89 --- /dev/null +++ b/Tea/TeaArray.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace Tea +{ + public class TeaArray + { + 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 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/Tea/TeaBytes.cs b/Tea/TeaBytes.cs new file mode 100644 index 0000000..50890da --- /dev/null +++ b/Tea/TeaBytes.cs @@ -0,0 +1,42 @@ +using System.Text; + +namespace Tea +{ + public class TeaBytes + { + 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/Tea/TeaConverter.cs b/Tea/TeaConverter.cs index c895ea8..b99a9d3 100644 --- a/Tea/TeaConverter.cs +++ b/Tea/TeaConverter.cs @@ -23,15 +23,15 @@ public static Dictionary merge(params object[] objs) Type typeObj = obj.GetType(); if (typeof(TeaModel).IsAssignableFrom(typeObj)) { - dicObj = ((TeaModel) obj).ToMap(); + dicObj = ((TeaModel)obj).ToMap(); } else if (obj is Dictionary) { - dicObj = (Dictionary) obj; + dicObj = (Dictionary)obj; } else if (obj is Dictionary) { - Dictionary dicString = (Dictionary) obj; + Dictionary dicString = (Dictionary)obj; foreach (var keypair in dicString) { dicObj.Add(keypair.Key, keypair.Value); @@ -44,7 +44,7 @@ public static Dictionary merge(params object[] objs) foreach (var keypair in dicObj) { - T dicValue = (T) keypair.Value; + T dicValue = (T)keypair.Value; if (dicResult.ContainsKey(keypair.Key)) { dicResult[keypair.Key] = dicValue; @@ -69,5 +69,20 @@ public static string StrToLower(string str) return str.ToLower(); } } + + public static int ParseInt(T data) + { + return (int)Double.Parse(data.ToString()); + } + + public static long ParseLong(T data) + { + return (long)Double.Parse(data.ToString()); + } + + public static float ParseFLoat(T data) + { + return (float)Double.Parse(data.ToString()); + } } } diff --git a/Tea/TeaCore.cs b/Tea/TeaCore.cs index 45b3c71..71ace50 100644 --- a/Tea/TeaCore.cs +++ b/Tea/TeaCore.cs @@ -77,11 +77,11 @@ public static TeaResponse DoAction(TeaRequest request, Dictionary DoActionAsync(TeaRequest request, Dictiona HttpResponseMessage response = await httpClient.SendAsync(req, new CancellationTokenSource(timeout).Token); return new TeaResponse(response); } - catch (System.Threading.Tasks.TaskCanceledException) + catch (TaskCanceledException) { throw new WebException("operation is timeout"); } @@ -113,7 +113,7 @@ public static async Task DoActionAsync(TeaRequest request, Dictiona public static string GetResponseBody(TeaResponse response) { - using(var ms = new MemoryStream()) + using (var ms = new MemoryStream()) { var buffer = new byte[bufferLength]; var stream = response.Body; @@ -163,16 +163,16 @@ public static Dictionary ConvertHeaders(HttpResponseHeaders head public 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) { @@ -260,7 +260,7 @@ 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)); } } diff --git a/Tea/TeaDate.cs b/Tea/TeaDate.cs new file mode 100644 index 0000000..05a8d6b --- /dev/null +++ b/Tea/TeaDate.cs @@ -0,0 +1,176 @@ +using System; + +namespace Tea +{ + public class TeaDate + { + private static readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public DateTime Date { get; private set; } + + public TeaDate(DateTime date) + { + Date = date; + } + + public TeaDate(string dateString) + { + DateTime parseDate; + DateTimeOffset dateTimeOffset; + if (DateTimeOffset.TryParse(dateString, out dateTimeOffset)) + { + parseDate = dateTimeOffset.UtcDateTime; + } + else + { + parseDate = DateTime.SpecifyKind(DateTime.Parse(dateString), DateTimeKind.Local).ToUniversalTime(); + } + Date = parseDate; + } + + public string Format(string layout) + { + layout = layout.Replace('Y', 'y') + .Replace('D', 'd') + .Replace('h', 'H'); + return Date.ToUniversalTime().ToString(layout); + } + + public long Unix() + { + return (long)(Date.ToUniversalTime() - Jan1st1970).TotalSeconds; + } + + public string UTC() + { + return Date.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss.ffffff '+0000 UTC'"); + } + + public TeaDate Sub(string unit, int amount) + { + DateTime newDate; + switch (unit.ToLowerInvariant()) + { + case "millisecond": + newDate = Date.AddMilliseconds(-amount); + break; + case "second": + newDate = Date.AddSeconds(-amount); + break; + case "minute": + newDate = Date.AddMinutes(-amount); + break; + case "hour": + newDate = Date.AddHours(-amount); + break; + case "day": + newDate = Date.AddDays(-amount); + break; + case "month": + newDate = Date.AddMonths(-amount); + break; + case "year": + newDate = Date.AddYears(-amount); + break; + default: + throw new ArgumentException("Unsupported unit."); + } + return new TeaDate(newDate); + } + + public TeaDate Add(string unit, int amount) + { + DateTime newDate; + switch (unit.ToLowerInvariant()) + { + case "millisecond": + newDate = Date.AddMilliseconds(amount); + break; + case "second": + newDate = Date.AddSeconds(amount); + break; + case "minute": + newDate = Date.AddMinutes(amount); + break; + case "hour": + newDate = Date.AddHours(amount); + break; + case "day": + newDate = Date.AddDays(amount); + break; + case "month": + newDate = Date.AddMonths(amount); + break; + case "year": + newDate = Date.AddYears(amount); + break; + default: + throw new ArgumentException("Unsupported unit."); + } + return new TeaDate(newDate); + } + + public int Diff(string unit, TeaDate diffDate) + { + TimeSpan timeSpan = Date - diffDate.Date; + 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 Date.Hour; + } + + public int Minute() + { + return Date.Minute; + } + + public int Second() + { + return Date.Second; + } + + public int Month() + { + return Date.Month; + } + + public int Year() + { + return Date.Year; + } + + public int DayOfMonth() + { + return Date.Day; + } + + public int DayOfWeek() + { + if (Date.DayOfWeek == 0) + { + return 7; + } + return (int)Date.DayOfWeek; + } + } +} \ No newline at end of file diff --git a/Tea/TeaException.cs b/Tea/TeaException.cs index 555999b..38c5e42 100644 --- a/Tea/TeaException.cs +++ b/Tea/TeaException.cs @@ -10,75 +10,38 @@ 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 string Code { get; set; } + public new string Message { get; set; } + public new Dictionary Data { get; set; } + public int StatusCode { get; set; } + public string Description { get; set; } + public Dictionary AccessDeniedDetail { get; set; } public Dictionary DataResult { get { - return data; - } - } - - public int StatusCode - { - get - { - return statusCode; - } - } - - - public string Description - { - get - { - return description; + return Data; } } - public Dictionary AccessDeniedDetail + public TeaException() { - 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(); + 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]); + IDictionary dicDetail = (IDictionary)obj; + AccessDeniedDetail = dicDetail.Keys.Cast().ToDictionary(key => key, key => dicDetail[key]); } } obj = DictUtils.GetDicValue(dicObj, "data"); @@ -88,11 +51,11 @@ public TeaException(IDictionary dict) } 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) + 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()); + StatusCode = int.Parse(DictUtils.GetDicValue(Data, "statusCode").ToSafeString()); } return; } @@ -105,7 +68,7 @@ public TeaException(IDictionary dict) PropertyInfo p = properties[i]; filedsDict.Add(p.Name, p.GetValue(obj)); } - data = filedsDict; + Data = filedsDict; } } } diff --git a/Tea/TeaFile.cs b/Tea/TeaFile.cs new file mode 100644 index 0000000..f042072 --- /dev/null +++ b/Tea/TeaFile.cs @@ -0,0 +1,163 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Tea +{ + public class TeaFile : IDisposable + { + public readonly string _path; + public FileInfo _fileInfo; + public FileStream _fileStream; + public long _position; + + + public TeaFile(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 TeaDate CreateTime() + { + EnsureFileInfoLoaded(); + return new TeaDate(_fileInfo.CreationTimeUtc); + } + + public async Task CreateTimeAsync() + { + EnsureFileInfoLoaded(); + return new TeaDate(_fileInfo.CreationTimeUtc); + } + + public TeaDate ModifyTime() + { + EnsureFileInfoLoaded(); + return new TeaDate(_fileInfo.LastWriteTimeUtc); + } + + public async Task ModifyTimeAsync() + { + EnsureFileInfoLoaded(); + return new TeaDate(_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 File.Exists(path); + } + + public static async Task ExistsAsync(string path) + { + return 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/TeaForm.cs b/Tea/TeaForm.cs new file mode 100644 index 0000000..b92127f --- /dev/null +++ b/Tea/TeaForm.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Web; + +using Tea.Utils; +using Tea.Streams; + +namespace Tea +{ + public class TeaForm + { + 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/TeaFunc.cs b/Tea/TeaFunc.cs new file mode 100644 index 0000000..a9f113a --- /dev/null +++ b/Tea/TeaFunc.cs @@ -0,0 +1,14 @@ +namespace Tea +{ + public class TeaFunc + { + public static bool IsNull(T data) + { + if (data == null) + { + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/Tea/TeaJSON.cs b/Tea/TeaJSON.cs new file mode 100644 index 0000000..4d1cde9 --- /dev/null +++ b/Tea/TeaJSON.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Tea +{ + public class TeaJSON + { + public static string SerializeObject(object data) + { + if (data is string) + { + return data.ToString(); + } + return JsonConvert.SerializeObject(data); + } + } +} \ No newline at end of file diff --git a/Tea/TeaMap.cs b/Tea/TeaMap.cs new file mode 100644 index 0000000..09c4851 --- /dev/null +++ b/Tea/TeaMap.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Tea +{ + public class TeaMap + { + public static Dictionary Merge(Dictionary dic1, Dictionary dic2) + { + object[] objs = new object[] { dic1, dic2 }; + return TeaConverter.merge(objs); + } + } +} \ No newline at end of file diff --git a/Tea/TeaNumber.cs b/Tea/TeaNumber.cs new file mode 100644 index 0000000..dd5b7f1 --- /dev/null +++ b/Tea/TeaNumber.cs @@ -0,0 +1,45 @@ +using System; + +namespace Tea +{ + public class TeaNumber + { + public static int ParseInt(T data) + { + if (data == null) + { + // TODO + return 0; + } + return (int)Double.Parse(data.ToString()); + } + + public static long ParseLong(T data) + { + if (data == null) + { + return 0; + } + return (long)Double.Parse(data.ToString()); + } + + public static float ParseFloat(T data) + { + if (data == null) + { + return 0; + } + 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/Tea/TeaResponse.cs b/Tea/TeaResponse.cs index 0d725a4..47ee478 100644 --- a/Tea/TeaResponse.cs +++ b/Tea/TeaResponse.cs @@ -29,11 +29,12 @@ public Stream Body } } + public TeaResponse(HttpResponseMessage response) { if (response != null) { - StatusCode = (int) response.StatusCode; + StatusCode = (int)response.StatusCode; StatusMessage = ""; Headers = TeaCore.ConvertHeaders(response.Headers); _responseAsync = response; diff --git a/Tea/TeaStream.cs b/Tea/TeaStream.cs new file mode 100644 index 0000000..8ed1be6 --- /dev/null +++ b/Tea/TeaStream.cs @@ -0,0 +1,283 @@ +using System.IO; +using System.Text; +using System.Collections.Generic; +using Newtonsoft.Json; +using System.Threading.Tasks; + +using Tea.Utils; +using System; + +namespace Tea +{ + public class TeaStream + { + 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 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 = ReadJsonUtil.Deserialize(jResult); + return result; + } + + public async static Task ReadAsJSONAsync(Stream stream) + { + object jResult = ParseJSON(await ReadAsStringAsync(stream)); + object result = ReadJsonUtil.Deserialize(jResult); + return result; + } + + public class SSEEvent + { + public string Data { get; set; } + public string Id { get; set; } + public string Event { get; set; } + public int? Retry { get; set; } + } + + 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 之间以 \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; + } + } + } + } + } + + +#if NETSTANDARD2_1 || NETCOREAPP3_0 + public static async IAsyncEnumerable ReadAsSSEAsync(Stream stream) + { + using var reader = new StreamReader(stream); + var buffer = new char[4096]; + var rest = string.Empty; + + int count; + while ((count = await reader.ReadAsync(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; + } + } + } + } +#endif + } +} \ No newline at end of file diff --git a/Tea/TeaString.cs b/Tea/TeaString.cs new file mode 100644 index 0000000..90ce2f5 --- /dev/null +++ b/Tea/TeaString.cs @@ -0,0 +1,57 @@ +using System; +using System.Text; +using System.Globalization; + +namespace Tea +{ + public class TeaString + { + public static byte[] ToBytes(string data, string enCoding) + { + string lowerEncoding = enCoding.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 Replace(string data, string newStr, string regexStr) + { + if (regexStr.EndsWith("/g")) + { + // return data.Replace(regexStr.Replace("/g", ""), newStr, StringComparison.OrdinalIgnoreCase); + } + // TODO + return null; + } + + public static int ParseInt(string data) + { + // TODO 是否需要加上NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.InvariantInfo以支持千分号等格式 + 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/Tea/TeaURL.cs b/Tea/TeaURL.cs new file mode 100644 index 0000000..b855243 --- /dev/null +++ b/Tea/TeaURL.cs @@ -0,0 +1,119 @@ +using System; +using System.Globalization; +using System.Net; +using System.Text; + +namespace Tea +{ + public class TeaURL + { + private Uri _uri; + + public TeaURL(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; + } + + + internal static TeaURL Parse(string url) + { + return new TeaURL(url); + } + + internal static string UrlEncode(string url) + { + return url != null ? WebUtility.UrlEncode(url) : string.Empty; + } + + 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(); + } + + internal 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/Tea/TeaXML.cs b/Tea/TeaXML.cs new file mode 100644 index 0000000..506fab5 --- /dev/null +++ b/Tea/TeaXML.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using Tea.Utils; + + +namespace Tea +{ + public class TeaXML + { + + public static Dictionary ParseXml(string body, Type response) + { + return XmlUtil.DeserializeXml(body, response); + } + + public static string ToXML(Dictionary body) + { + return XmlUtil.SerializeXml(body); + } + + } +} diff --git a/Tea/Utils/ReadJsonUtil.cs b/Tea/Utils/ReadJsonUtil.cs new file mode 100644 index 0000000..faa5e3c --- /dev/null +++ b/Tea/Utils/ReadJsonUtil.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; + +using Newtonsoft.Json.Linq; + +namespace Tea.Utils +{ + internal static class ReadJsonUtil + { + internal 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; + } + } + + 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; + } + } + } +} \ No newline at end of file diff --git a/Tea/Utils/XmlUtil.cs b/Tea/Utils/XmlUtil.cs new file mode 100644 index 0000000..c7ee6aa --- /dev/null +++ b/Tea/Utils/XmlUtil.cs @@ -0,0 +1,351 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml; +using System.Xml.Linq; + +namespace Tea.Utils +{ + internal class XmlUtil + { + 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(TeaModel).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(TeaModel).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(TeaModel).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(TeaModel).IsAssignableFrom(type)) + { + return SerializeXmlByModel((TeaModel)obj); + } + else if (obj is Dictionary) + { + return SerializeXmlByDict((Dictionary)obj); + } + else + { + return string.Empty; + } + } + + internal static string SerializeXmlByModel(TeaModel 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(TeaModel).IsAssignableFrom(type)) + { + GetXml((TeaModel)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(TeaModel 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/tea.csproj b/Tea/tea.csproj index 43879c8..d355a0b 100644 --- a/Tea/tea.csproj +++ b/Tea/tea.csproj @@ -1,7 +1,7 @@  - netstandard2.0;net45 + netstandard2.0;net45;netstandard2.1;netcoreapp3.0 Tea Library Alibaba Cloud @@ -27,6 +27,16 @@ NET45 + + NETSTANDARD2_1 + 8 + + + + NETCOREAPP3_0 + 8 + + diff --git a/TeaUnitTests/Fixtures/test.json b/TeaUnitTests/Fixtures/test.json new file mode 100644 index 0000000..715b02d --- /dev/null +++ b/TeaUnitTests/Fixtures/test.json @@ -0,0 +1 @@ +{"key":"value"} \ No newline at end of file diff --git a/TeaUnitTests/Models/ListAllMyBucketsResult.cs b/TeaUnitTests/Models/ListAllMyBucketsResult.cs new file mode 100644 index 0000000..3fb86cb --- /dev/null +++ b/TeaUnitTests/Models/ListAllMyBucketsResult.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using Tea; + +namespace TeaUnitTests.Models +{ + public class ListAllMyBucketsResult : TeaModel + { + [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 : TeaModel + { + public int? ID { get; set; } + + public string DisplayName { get; set; } + } + + public class Buckets : TeaModel + { + [NameInMap("Bucket")] + public List bucket { get; set; } + + public class Bucket : TeaModel + { + 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/ToBodyModel.cs b/TeaUnitTests/Models/ToBodyModel.cs new file mode 100644 index 0000000..6f39648 --- /dev/null +++ b/TeaUnitTests/Models/ToBodyModel.cs @@ -0,0 +1,11 @@ +using Tea; + +namespace TeaUnitTests.Models +{ + public class ToBodyModel : TeaModel + { + [NameInMap("ListAllMyBucketsResult")] + public ListAllMyBucketsResult listAllMyBucketsResult { get; set; } + + } +} diff --git a/TeaUnitTests/Streams/FileFormStreamTest.cs b/TeaUnitTests/Streams/FileFormStreamTest.cs new file mode 100644 index 0000000..ed259a2 --- /dev/null +++ b/TeaUnitTests/Streams/FileFormStreamTest.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +using Tea.Streams; +using Xunit; + +namespace tests.Streams +{ + 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/TeaUnitTests/TeaArrayTest.cs b/TeaUnitTests/TeaArrayTest.cs new file mode 100644 index 0000000..9dd82b8 --- /dev/null +++ b/TeaUnitTests/TeaArrayTest.cs @@ -0,0 +1,37 @@ +using Tea; +using Xunit; +using System.Collections.Generic; + +namespace TeaUnitTests +{ + public class TeaArrayTest + { + [Fact] + public void Test_Unshift() + { + List array = new List { "a", "b", "c" }; + TeaArray.Unshift(array, "x"); + Assert.Equal(4, array.Count); + Assert.Equal("x", array[0]); + } + + [Fact] + public void Test_Push() + { + List array = new List { "a", "b", "c" }; + TeaArray.Push(array, "x"); + Assert.Equal(4, array.Count); + Assert.Equal("x", array[3]); + } + + [Fact] + public void Test_Concat() + { + List array1 = new List { "a", "b", "c" }; + List array2 = new List { "d", "e", "f" }; + TeaArray.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/TeaUnitTests/TeaBytesTest.cs b/TeaUnitTests/TeaBytesTest.cs new file mode 100644 index 0000000..1d2968c --- /dev/null +++ b/TeaUnitTests/TeaBytesTest.cs @@ -0,0 +1,31 @@ +using System.Text; +using Tea; +using System.Security.Cryptography; +using Xunit; +using System; + +namespace TeaUnitTests +{ + public class TeaBytesTest + { + + [Fact] + public void Test_HexEncode() + { + byte[] test = Encoding.UTF8.GetBytes("test"); + var res = TeaBytes.ToHex(test); + Assert.Equal("74657374", res); + } + + [Fact] + public void Test_From() + { + string data = "test"; + Assert.Equal(new byte[] { 116, 101, 115, 116 }, TeaBytes.From(data, "utf8")); + Assert.Equal(new byte[] { 116, 101, 115, 116 }, TeaBytes.From(data, "ASCII")); + Assert.Equal(new byte[] { 0, 116, 0, 101, 0, 115, 0, 116 }, TeaBytes.From(data, "bigendianunicode")); + Assert.Equal(new byte[] { 116, 0, 101, 0, 115, 0, 116, 0 }, TeaBytes.From(data, "unicode")); + Assert.Equal(new byte[] { 116, 0, 0, 0, 101, 0, 0, 0, 115, 0, 0, 0, 116, 0, 0, 0 }, TeaBytes.From(data, "utf32")); + } + } +} \ No newline at end of file diff --git a/TeaUnitTests/TeaConverterTest.cs b/TeaUnitTests/TeaConverterTest.cs index bc6462c..c61d28a 100644 --- a/TeaUnitTests/TeaConverterTest.cs +++ b/TeaUnitTests/TeaConverterTest.cs @@ -49,5 +49,14 @@ public void TestStrToLower() Assert.Equal("test", TeaConverter.StrToLower("TEST")); } + [Fact] + public void Test_ParseMethods() + { + Assert.Equal(123, TeaConverter.ParseInt("123")); + Assert.Equal(123, TeaConverter.ParseInt("123.0123")); + Assert.Equal(123, TeaConverter.ParseLong("123")); + Assert.Equal(123, TeaConverter.ParseLong("123.0123")); + Assert.Equal(123.0123, Math.Round(TeaConverter.ParseFLoat("123.0123"), 4)); + } } } diff --git a/TeaUnitTests/TeaDateTest.cs b/TeaUnitTests/TeaDateTest.cs new file mode 100644 index 0000000..4119b6b --- /dev/null +++ b/TeaUnitTests/TeaDateTest.cs @@ -0,0 +1,69 @@ +using System.Globalization; +using Tea; +using Xunit; +using System; + +namespace TeaUnitTests +{ + public class TeaDateTest + { + TeaDate dateLocal = new TeaDate("2023-12-31 00:00:00.916000"); + TeaDate dateUTC = new TeaDate("2023-12-31 00:00:00.916000 +0000"); + + [Fact] + public void Test_Init_NoTimeZone() + { + DateTime expectedDate = DateTime.SpecifyKind(DateTime.Parse("2023-12-31 00:00:00.916000"), DateTimeKind.Local).ToUniversalTime(); + Assert.Equal(expectedDate, dateLocal.Date); + } + + [Fact] + public void Test_Init_WithTimeZone() + { + DateTime expectedDate = DateTimeOffset.Parse("2023-12-31 00:00:00.916000 +0000").UtcDateTime; + Assert.Equal(expectedDate, dateUTC.Date); + } + + [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时间,但非北京时间 + Assert.Equal("2023-12-31 00:00:00.916000 +0000 UTC", dateLocal.UTC()); + } + + [Fact] + public void Test_Methods() + { + TeaDate 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)); + TeaDate 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/TeaUnitTests/TeaFileTest.cs b/TeaUnitTests/TeaFileTest.cs new file mode 100644 index 0000000..5b09ec7 --- /dev/null +++ b/TeaUnitTests/TeaFileTest.cs @@ -0,0 +1,178 @@ +using System.Text; +using Tea; +using Xunit; +using System; +using System.IO; +using System.Threading.Tasks; +namespace TeaUnitTests +{ + public class TeaFileTest : IAsyncLifetime + { + private TeaFile _file; + private FileInfo _fileInfo; + + private string tempTestFile = Path.GetTempFileName(); + + public async Task InitializeAsync() + { + File.WriteAllText(tempTestFile, "Test For File"); + _file = new TeaFile(tempTestFile); + _fileInfo = new FileInfo(tempTestFile); + } + + public Task DisposeAsync() + { + _file.Close(); + 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.Date.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.Date.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(); + File.WriteAllText(tempTestFile1, "Hello, World!"); + var newFile = new TeaFile(tempTestFile1); + var newLength = await newFile.LengthAsync(); + Assert.Equal(_fileInfo.Length, newLength); + await newFile.CloseAsync(); + } + + private void TestExists() + { + Assert.True(TeaFile.Exists(tempTestFile)); + Assert.False(TeaFile.Exists("../../../../TeaUnitTests/Fixtures/test1.txt")); + } + + private async Task TestExistsAsync() + { + Assert.True(await TeaFile.ExistsAsync(tempTestFile)); + Assert.False(await TeaFile.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(); + TeaFile emptyFile = new TeaFile(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(); + TeaFile emptyFile = new TeaFile(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")); + TeaDate modifyTime = _file.ModifyTime(); + int length = _file.Length(); + Assert.Equal(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ssZ"), modifyTime.Date.ToString("yyyy-MM-dd HH:mm:ssZ")); + Assert.Equal(expectedLen + 5, length); + string tempNewFile = Path.GetTempFileName(); + TeaFile newFile = new TeaFile(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")); + TeaDate modifyTime = await _file.ModifyTimeAsync(); + int length = await _file.LengthAsync(); + Assert.Equal(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ssZ"), modifyTime.Date.ToString("yyyy-MM-dd HH:mm:ssZ")); + Assert.Equal(expectedLen + 10, length); + string tempNewFile = Path.GetTempFileName(); + TeaFile newFile = new TeaFile(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 = TeaFile.CreateWriteStream(tempWriteFile)) + { + Assert.NotNull(stream); + Assert.True(stream.CanWrite); + byte[] contentBytes = Encoding.UTF8.GetBytes("Test Write"); + stream.Write(contentBytes, 0, contentBytes.Length); + } + string finalContent = File.ReadAllText(tempWriteFile); + Assert.EndsWith("Test Write", finalContent); + } + + private void TestCreateReadStream() + { + string tempReadFile = Path.GetTempFileName(); + File.WriteAllText(tempReadFile, "Test For File"); + using (FileStream stream = TeaFile.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/TeaUnitTests/TeaFormTest.cs b/TeaUnitTests/TeaFormTest.cs new file mode 100644 index 0000000..d95708c --- /dev/null +++ b/TeaUnitTests/TeaFormTest.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using Tea; +using Xunit; +using Tea.Streams; + +namespace TeaUnitTests +{ + public class TeaFormTest + { + [Fact] + public void Test_ToFormString() + { + Assert.Empty(TeaForm.ToFormString(null)); + Assert.Empty(TeaForm.ToFormString(new Dictionary())); + + Dictionary dict = new Dictionary(); + dict.Add("form", "test"); + dict.Add("param", "test"); + dict.Add("testNull", null); + Assert.Equal("form=test¶m=test", TeaForm.ToFormString(dict)); + } + + [Fact] + public void Test_GetBoundary() + { + Assert.Equal(14, TeaForm.GetBoundary().Length); + } + + [Fact] + public void Test_ToFileForm() + { + Stream fileFormStream = TeaForm.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 = TeaForm.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("../../../../TeaUnitTests/Fixtures/test.json"); + FileField fileField = new FileField + { + Filename = "fakefilename", + ContentType = "application/json", + Content = file + }; + dict = new Dictionary(); + dict.Add("stringkey", "string"); + dict.Add("filefield", fileField); + fileFormStream = TeaForm.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/TeaFuncTest.cs b/TeaUnitTests/TeaFuncTest.cs new file mode 100644 index 0000000..67ecaaa --- /dev/null +++ b/TeaUnitTests/TeaFuncTest.cs @@ -0,0 +1,15 @@ +using Tea; +using Xunit; + +namespace TeaUnitTests +{ + public class TeaFuncTest + { + [Fact] + public void Test_isNull() + { + Assert.True(TeaFunc.IsNull(null)); + Assert.False(TeaFunc.IsNull("test")); + } + } +} \ No newline at end of file diff --git a/TeaUnitTests/TeaJSONTest.cs b/TeaUnitTests/TeaJSONTest.cs new file mode 100644 index 0000000..d6ecac6 --- /dev/null +++ b/TeaUnitTests/TeaJSONTest.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Tea; +using Xunit; + +namespace TeaUnitTests +{ + public class TeaJSONTest + { + [Fact] + public void Test_SerializeObject() + { + Dictionary dict = new Dictionary + { { "key", "value" } + }; + string jsonStr = TeaJSON.SerializeObject(dict); + Assert.NotNull(jsonStr); + Assert.NotEmpty(jsonStr); + Assert.Equal("{\"key\":\"value\"}", jsonStr); + Assert.Equal("{}", TeaJSON.SerializeObject(new Dictionary())); + Assert.Equal("test str", TeaJSON.SerializeObject("test str")); + Assert.Equal("1", TeaJSON.SerializeObject(1)); + Assert.Equal("true", TeaJSON.SerializeObject(true)); + Assert.Equal("null", TeaJSON.SerializeObject(null)); + Dictionary unicode = new Dictionary + { { "str", "test&<>://中文" } + }; + Assert.Equal("{\"key\":\"value\",\"map\":{\"str\":\"test&<>://中文\"},\"num\":1}", TeaJSON.SerializeObject( + new Dictionary + { + { "key", "value" }, + { "map", unicode }, + { "num", 1 } + })); + } + } +} \ No newline at end of file diff --git a/TeaUnitTests/TeaMapTest.cs b/TeaUnitTests/TeaMapTest.cs new file mode 100644 index 0000000..be3e71f --- /dev/null +++ b/TeaUnitTests/TeaMapTest.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using Tea; +using Xunit; + +namespace TeaUnitTests +{ + public class TeaMapTest + { + [Fact] + public void Test_Merge() + { + Dictionary dict1 = new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" } + }; + Dictionary dict2 = new Dictionary + { + { "key2", "value22" }, + { "key3", "value3" } + }; + Dictionary res = TeaMap.Merge(dict1, dict2); + Assert.Equal("value1", res["key1"]); + Assert.Equal("value22", res["key2"]); + Assert.Equal("value3", res["key3"]); + } + } +} \ No newline at end of file diff --git a/TeaUnitTests/TeaStreamTest.cs b/TeaUnitTests/TeaStreamTest.cs new file mode 100644 index 0000000..c9e2663 --- /dev/null +++ b/TeaUnitTests/TeaStreamTest.cs @@ -0,0 +1,567 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Threading; + +using Tea; + +using Xunit; +using System.Net; +using System.Net.Http; +using Newtonsoft.Json; +using System; + +namespace tests +{ + 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; + timer = new Timer(_ => + { + if (count >= 5) + { + timer.Dispose(); + response.Close(); + return; + } + + 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++; + }, null, 0, 100); + } + + private void HandleSseWithNoSpacesResponse(HttpListenerResponse response) + { + int count = 0; + Timer timer = null; + timer = new Timer(_ => + { + if (count >= 5) + { + timer.Dispose(); + response.Close(); + return; + } + + 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++; + }, null, 0, 100); + } + + private void HandleSseWithInvalidRetryResponse(HttpListenerResponse response) + { + int count = 0; + Timer timer = null; + timer = new Timer(_ => + { + if (count >= 5) + { + timer.Dispose(); + response.Close(); + return; + } + + 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++; + }, null, 0, 100); + } + + private void HandleSseWithDataDividedResponse(HttpListenerResponse response) + { + int count = 0; + Timer timer = null; + timer = new Timer(_ => + { + if (count >= 5) + { + 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; + } + + 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(); + }, null, 0, 100); + } + + public void Stop() + { + _cancellationTokenSource.Cancel(); + _httpListener.Stop(); + _httpListener.Close(); + } + + public void Dispose() + { + Stop(); + ((IDisposable)_httpListener)?.Dispose(); + _cancellationTokenSource?.Dispose(); + } + } + + + public class TeaStreamTest : 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", TeaStream.ReadAsString(stream)); + } + } + + [Fact] + public async void Test_ReadAsStringAsync() + { + using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes("test"))) + { + Assert.Equal("test", await TeaStream.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)TeaStream.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)TeaStream.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(TeaStream.Read(stream, 3)); + Assert.Equal(3, TeaStream.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()) + { + TeaStream.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()) + { + TeaStream.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 = TeaStream.StreamFor(stream); + Assert.NotNull(copy); + Assert.True(copy.CanRead); + string str = new StreamReader(copy).ReadToEnd(); + Assert.Equal("test", str); + + string data1 = "test1"; + Stream copy1 = TeaStream.StreamFor(data1); + string str1 = new StreamReader(copy1).ReadToEnd(); + Assert.Equal("test1", str1); + + int data2 = 111; + Exception ex = Assert.Throws(() => TeaStream.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 TeaStream.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 TeaStream.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(TeaStream.ReadAsBytes(stream)); + } + } + + [Fact] + public async void Test_ReadAsBytesAsync() + { + using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes("test"))) + { + Assert.NotNull(await TeaStream.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 TeaStream.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 TeaStream.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 TeaStream.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 TeaStream.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 TeaStream.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 TeaStream.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 TeaStream.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 TeaStream.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/TeaUnitTests/TeaStringTest.cs b/TeaUnitTests/TeaStringTest.cs new file mode 100644 index 0000000..1ac0ec8 --- /dev/null +++ b/TeaUnitTests/TeaStringTest.cs @@ -0,0 +1,19 @@ +using Tea; +using Xunit; + +namespace TeaUnitTests +{ + public class TeaStringTest + { + [Fact] + public void Test_ToBytes() + { + string data = "test"; + Assert.Equal(new byte[] { 116, 101, 115, 116 }, TeaBytes.From(data, "utf8")); + Assert.Equal(new byte[] { 116, 101, 115, 116 }, TeaBytes.From(data, "ASCII")); + Assert.Equal(new byte[] { 0, 116, 0, 101, 0, 115, 0, 116 }, TeaBytes.From(data, "bigendianunicode")); + Assert.Equal(new byte[] { 116, 0, 101, 0, 115, 0, 116, 0 }, TeaBytes.From(data, "unicode")); + Assert.Equal(new byte[] { 116, 0, 0, 0, 101, 0, 0, 0, 115, 0, 0, 0, 116, 0, 0, 0 }, TeaBytes.From(data, "utf32")); + } + } +} \ No newline at end of file diff --git a/TeaUnitTests/TeaURLTest.cs b/TeaUnitTests/TeaURLTest.cs new file mode 100644 index 0000000..3435dc0 --- /dev/null +++ b/TeaUnitTests/TeaURLTest.cs @@ -0,0 +1,49 @@ +using Tea; +using Xunit; + +namespace TeaUnitTests +{ + public class TeaURLTest + { + [Fact] + public void Test_Parse() + { + string url = "https://sdk:test@ecs.aliyuncs.com:443/sdk/?api&ok=test#sddd"; + TeaURL ret = TeaURL.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 = TeaURL.UrlEncode("https://www.baidu.com/"); + Assert.Equal("https%3A%2F%2Fwww.baidu.com%2F", result); + } + + [Fact] + public void Test_PercentEncode() + { + Assert.Null(TeaURL.PercentEncode(null)); + Assert.Equal("test%3D", TeaURL.PercentEncode("test=")); + + string result = TeaURL.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 = TeaURL.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/TeaUnitTests/TeaUnitTests.csproj b/TeaUnitTests/TeaUnitTests.csproj index e417cdc..fa09575 100644 --- a/TeaUnitTests/TeaUnitTests.csproj +++ b/TeaUnitTests/TeaUnitTests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.0 + netcoreapp3.1 false false TeaUnitTests diff --git a/TeaUnitTests/TeaXMLTest.cs b/TeaUnitTests/TeaXMLTest.cs new file mode 100644 index 0000000..584ccb6 --- /dev/null +++ b/TeaUnitTests/TeaXMLTest.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using Tea; +using TeaUnitTests.Models; +using Xunit; +using static TeaUnitTests.Models.ListAllMyBucketsResult; +using static TeaUnitTests.Models.ListAllMyBucketsResult.Buckets; + +namespace TeaUnitTests +{ + public class TeaXMLTest + { + [Fact] + public void Test_ToBody() + { + TeaModel modelNull = new TeaModel(); + Assert.Empty(TeaXML.ToXML(modelNull.ToMap())); + + ToBodyModel 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.TestListNull = null; + string xmlStr = TeaXML.ToXML(model.ToMap()); + Assert.NotNull(xmlStr); + + Dictionary xmlBody = (Dictionary)TeaXML.ParseXml(xmlStr, typeof(ToBodyModel)); + ToBodyModel teaModel = TeaModel.ToObject(xmlBody); + Assert.NotNull(teaModel); + Assert.Equal(1, teaModel.listAllMyBucketsResult.TestDouble); + + string xml = "\n" + + "disNamekey"; + Dictionary map = TeaXML.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]); + } + } +} diff --git a/TeaUnitTests/Utils/ReadJsonUtilTest.cs b/TeaUnitTests/Utils/ReadJsonUtilTest.cs new file mode 100644 index 0000000..05c6951 --- /dev/null +++ b/TeaUnitTests/Utils/ReadJsonUtilTest.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +using Tea.Utils; + +using Newtonsoft.Json.Linq; + +using Xunit; + +namespace TeaUnitTests.Utils +{ + public class ReadJsonUtilTest + { + [Fact] + public void TestDeserializeToDic() + { + Assert.Null(ReadJsonUtil.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) ReadJsonUtil.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); + } + + } +} diff --git a/TeaUnitTests/Utils/XmlUtilTest.cs b/TeaUnitTests/Utils/XmlUtilTest.cs new file mode 100644 index 0000000..315d204 --- /dev/null +++ b/TeaUnitTests/Utils/XmlUtilTest.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using Tea; +using Tea.Utils; +using Xunit; +using TeaUnitTests.Models; +using static TeaUnitTests.Models.ListAllMyBucketsResult; +using static TeaUnitTests.Models.ListAllMyBucketsResult.Buckets; + +namespace TeaUnitTests.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 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); + } + } +}