diff --git a/Assets/Plugins/CountlySDK/Helpers/Constants.cs b/Assets/Plugins/CountlySDK/Helpers/Constants.cs index 0aaa5603..936d6131 100644 --- a/Assets/Plugins/CountlySDK/Helpers/Constants.cs +++ b/Assets/Plugins/CountlySDK/Helpers/Constants.cs @@ -5,7 +5,7 @@ namespace Plugins.CountlySDK.Helpers { internal class Constants { - public const string SdkVersion = "20.11.4"; + public const string SdkVersion = "20.11.5"; #if UNITY_EDITOR public const string SdkName = "csharp-unity-editor"; diff --git a/Assets/Plugins/CountlySDK/Models/CountlyConfiguration.cs b/Assets/Plugins/CountlySDK/Models/CountlyConfiguration.cs index f5a3be13..f67cc128 100644 --- a/Assets/Plugins/CountlySDK/Models/CountlyConfiguration.cs +++ b/Assets/Plugins/CountlySDK/Models/CountlyConfiguration.cs @@ -75,6 +75,31 @@ public class CountlyConfiguration /// public int SessionDuration = 60; + /// + /// Maximum size of all string keys + /// + public int MaxKeyLength = 128; + + /// + /// Maximum size of all values in our key-value pairs + /// + public int MaxValueSize = 256; + + /// + /// Max amount of custom (dev provided) segmentation in one event + /// + public int MaxSegmentationValues = 30; + + /// + ///Limits how many stack trace lines would be recorded per thread + /// + public int MaxStackTraceLinesPerThread = 30; + + /// + ///Limits how many characters are allowed per stack trace line + /// + public int MaxStackTraceLineLength = 200; + /// /// Set threshold value for the number of events that can be stored locally. /// @@ -224,4 +249,4 @@ public void AddNotificationListener(INotificationListener listener) NotificationEventListeners.Add(listener); } } -} \ No newline at end of file +} diff --git a/Assets/Plugins/CountlySDK/Models/CountlyUserDetailsModel.cs b/Assets/Plugins/CountlySDK/Models/CountlyUserDetailsModel.cs index e35ebef8..4a64f9c7 100644 --- a/Assets/Plugins/CountlySDK/Models/CountlyUserDetailsModel.cs +++ b/Assets/Plugins/CountlySDK/Models/CountlyUserDetailsModel.cs @@ -28,7 +28,7 @@ public class CountlyUserDetailsModel [JsonProperty("custom")] //dots (.) and dollar signs ($) in key names will be stripped out. - internal Dictionary Custom { get; set; } + internal IDictionary Custom { get; set; } /// /// Initializes a new instance of User Model with the specified params diff --git a/Assets/Plugins/CountlySDK/Services/AbstractBaseService.cs b/Assets/Plugins/CountlySDK/Services/AbstractBaseService.cs index f377114f..4dfe0dae 100644 --- a/Assets/Plugins/CountlySDK/Services/AbstractBaseService.cs +++ b/Assets/Plugins/CountlySDK/Services/AbstractBaseService.cs @@ -1,5 +1,6 @@  using System; +using System.Collections; using System.Collections.Generic; using Plugins.CountlySDK.Enums; using Plugins.CountlySDK.Models; @@ -23,6 +24,102 @@ protected AbstractBaseService(CountlyConfiguration configuration, CountlyLogHelp _consentService = consentService; } + protected IDictionary RemoveSegmentInvalidDataTypes(IDictionary segments) + { + + if (segments == null || segments.Count == 0) { + return segments; + } + + string moduleName = GetType().Name; + int i = 0; + List toRemove = new List(); + foreach (KeyValuePair item in segments) { + if (++i > _configuration.MaxSegmentationValues) { + toRemove.Add(item.Key); + continue; + } + Type type = item.Value?.GetType(); + bool isValidDataType = item.Value != null + && (type == typeof(int) + || type == typeof(bool) + || type == typeof(float) + || type == typeof(double) + || type == typeof(string)); + + + if (!isValidDataType) { + toRemove.Add(item.Key); + Log.Warning("[" + moduleName + "] RemoveSegmentInvalidDataTypes: In segmentation Data type '" + type + "' of item '" + item.Key + "' isn't valid."); + } + } + + foreach (string k in toRemove) { + segments.Remove(k); + } + + return segments; + } + + protected string TrimKey(string k) + { + if (k.Length > _configuration.MaxKeyLength) { + Log.Warning("[" + GetType().Name + "] TrimKey : Max allowed key length is " + _configuration.MaxKeyLength + ". "+ k + " will be truncated."); + k = k.Substring(0, _configuration.MaxKeyLength); + } + + return k; + } + + protected string[] TrimValues(string[] values) + { + for (int i = 0; i < values.Length; ++i) { + if (values[i].Length > _configuration.MaxValueSize) { + Log.Warning("[" + GetType().Name + "] TrimKey : Max allowed value length is " + _configuration.MaxKeyLength + ". " + values[i] + " will be truncated."); + values[i] = values[i].Substring(0, _configuration.MaxValueSize); + } + } + + + return values; + } + + protected string TrimValue(string fieldName, string v) + { + if (v.Length > _configuration.MaxValueSize) { + Log.Warning("[" + GetType().Name + "] TrimValue : Max allowed '" + fieldName + "' length is " + _configuration.MaxValueSize + ". " + v + " will be truncated."); + v = v.Substring(0, _configuration.MaxValueSize); + } + + return v; + } + + protected IDictionary FixSegmentKeysAndValues(IDictionary segments) + { + if (segments == null || segments.Count == 0) { + return segments; + } + + IDictionary segmentation = new Dictionary(); + foreach (KeyValuePair item in segments) { + string k = item.Key; + object v = item.Value; + + if (k == null || v == null) { + continue; + } + + k = TrimKey(k); + + if (v.GetType() == typeof(string)) { + v = TrimValue(k, (string)v); + } + + segmentation.Add(k, v); + } + + return segmentation; + } internal virtual void OnInitializationCompleted() { } internal virtual void DeviceIdChanged(string deviceId, bool merged) { } internal virtual void ConsentChanged(List updatedConsents, bool newConsentValue) { } diff --git a/Assets/Plugins/CountlySDK/Services/CrashReportsCountlyService.cs b/Assets/Plugins/CountlySDK/Services/CrashReportsCountlyService.cs index 3a66efc2..bd01c88d 100644 --- a/Assets/Plugins/CountlySDK/Services/CrashReportsCountlyService.cs +++ b/Assets/Plugins/CountlySDK/Services/CrashReportsCountlyService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using Plugins.CountlySDK.Enums; @@ -22,6 +23,38 @@ internal CrashReportsCountlyService(CountlyConfiguration configuration, CountlyL _requestCountlyHelper = requestCountlyHelper; } + #region Helper Methods + private string ManipulateStackTrace(string stackTrace) + { + string result = null; + if (!string.IsNullOrEmpty(stackTrace)) { + string[] lines = stackTrace.Split('\n'); + + int limit = lines.Length; + + if (limit > _configuration.MaxStackTraceLinesPerThread) { + limit = _configuration.MaxStackTraceLinesPerThread; + } + + for (int i = 0; i < limit; ++i) { + string line = lines[i]; + + if (line.Length > _configuration.MaxStackTraceLineLength) { + line = line.Substring(0, _configuration.MaxStackTraceLineLength); + } + + if (i + 1 != limit) { + line += '\n'; + } + + result += line; + } + } + + return result; + } + #endregion + /// /// Called when there is an exception @@ -42,11 +75,12 @@ public async void LogCallback(string message, string stackTrace, LogType type) Log.Warning("[CrashReportsCountlyService] LogCallback : The parameter 'message' can't be null or empty"); return; } - CountlyExceptionDetailModel model = ExceptionDetailModel(message, stackTrace, false, null); + + CountlyExceptionDetailModel model = ExceptionDetailModel(message, ManipulateStackTrace(stackTrace), false, null); if (_configuration.EnableAutomaticCrashReporting && (type == LogType.Error || type == LogType.Exception)) { - _=SendCrashReportInternal(model); + _ = SendCrashReportInternal(model); } } } @@ -75,8 +109,11 @@ public async Task SendCrashReportAsync(string message, string stackTrace, LogTyp return; } - CountlyExceptionDetailModel model = ExceptionDetailModel(message, stackTrace, nonfatal, segments); - _=SendCrashReportInternal(model); + IDictionary segmentation = RemoveSegmentInvalidDataTypes(segments); + segmentation = FixSegmentKeysAndValues(segments); + + CountlyExceptionDetailModel model = ExceptionDetailModel(message, ManipulateStackTrace(stackTrace), nonfatal, segmentation); + _ = SendCrashReportInternal(model); } } @@ -100,8 +137,6 @@ internal async Task SendCrashReportInternal(CountlyExceptionDetailModel model) /// /// Adds string value to a list which is later sent over as logs whenever a cash is reported by system. - /// The length of a breadcrumb is limited to 1000 characters. Only first 1000 characters will be accepted in case the length is more - /// than 1000 characters. /// /// a bread crumb for the crash report public void AddBreadcrumbs(string value) @@ -116,7 +151,7 @@ public void AddBreadcrumbs(string value) return; } - string validBreadcrumb = value.Length > 1000 ? value.Substring(0, 1000) : value; + string validBreadcrumb = value.Length > _configuration.MaxValueSize ? value.Substring(0, _configuration.MaxValueSize) : value; if (_crashBreadcrumbs.Count == _configuration.TotalBreadcrumbsAllowed) { _crashBreadcrumbs.Dequeue(); diff --git a/Assets/Plugins/CountlySDK/Services/EventCountlyService.cs b/Assets/Plugins/CountlySDK/Services/EventCountlyService.cs index af65c3ce..131d5007 100644 --- a/Assets/Plugins/CountlySDK/Services/EventCountlyService.cs +++ b/Assets/Plugins/CountlySDK/Services/EventCountlyService.cs @@ -71,7 +71,6 @@ internal void AddEventsToRequestQueue() /// internal async Task RecordEventAsync(CountlyEventModel @event) { - Log.Debug("[EventCountlyService] RecordEventAsync : " + @event.ToString()); if (_configuration.EnableTestMode) { @@ -163,29 +162,15 @@ public async Task RecordEventAsync(string key, IDictionary segme return; } - if (segmentation != null) { - List toRemove = new List(); - - foreach (KeyValuePair item in segmentation) { - bool isValidDataType = item.Value != null - && (item.Value.GetType() == typeof(int) - || item.Value.GetType() == typeof(bool) - || item.Value.GetType() == typeof(float) - || item.Value.GetType() == typeof(double) - || item.Value.GetType() == typeof(string)); - - if (!isValidDataType) { - toRemove.Add(item.Key); - Log.Warning("[EventCountlyService] ReportCustomEventAsync : In segmentation Data type '" + (item.Value?.GetType()) + "' of item '" + item.Key + "' isn't valid."); - } - } - - foreach (string k in toRemove) { - segmentation.Remove(k); - } + if (key.Length > _configuration.MaxKeyLength) { + Log.Warning("[EventCountlyService] RecordEventAsync : Max allowed key length is " + _configuration.MaxKeyLength); + key = key.Substring(0, _configuration.MaxKeyLength); } - CountlyEventModel @event = new CountlyEventModel(key, segmentation, count, sum, duration); + IDictionary segments = RemoveSegmentInvalidDataTypes(segmentation); + segments = FixSegmentKeysAndValues(segments); + + CountlyEventModel @event = new CountlyEventModel(key, segments, count, sum, duration); _ = RecordEventAsync(@event); } @@ -219,29 +204,16 @@ public async Task ReportCustomEventAsync(string key, return; } + if (key.Length > _configuration.MaxKeyLength) { + Log.Warning("[EventCountlyService] ReportCustomEventAsync : Max allowed key length is " + _configuration.MaxKeyLength); + key = key.Substring(0, _configuration.MaxKeyLength); + } - if (segmentation != null) { - List toRemove = new List(); - - foreach (KeyValuePair item in segmentation) { - bool isValidDataType = item.Value.GetType() == typeof(int) - || item.Value.GetType() == typeof(bool) - || item.Value.GetType() == typeof(float) - || item.Value.GetType() == typeof(double) - || item.Value.GetType() == typeof(string); - - if (!isValidDataType) { - toRemove.Add(item.Key); - Log.Warning("[EventCountlyService] ReportCustomEventAsync : In segmentation Data type '" + (item.Value?.GetType()) + "' of item '" + item.Key + "'isn't valid."); - } - } - foreach (string k in toRemove) { - segmentation.Remove(k); - } - } + IDictionary segments = RemoveSegmentInvalidDataTypes(segmentation); + segments = FixSegmentKeysAndValues(segments); - CountlyEventModel @event = new CountlyEventModel(key, segmentation, count, sum, duration); + CountlyEventModel @event = new CountlyEventModel(key, segments, count, sum, duration); _ = RecordEventAsync(@event); } diff --git a/Assets/Plugins/CountlySDK/Services/UserDetailsCountlyService.cs b/Assets/Plugins/CountlySDK/Services/UserDetailsCountlyService.cs index 315198ac..71b9d489 100644 --- a/Assets/Plugins/CountlySDK/Services/UserDetailsCountlyService.cs +++ b/Assets/Plugins/CountlySDK/Services/UserDetailsCountlyService.cs @@ -25,40 +25,27 @@ internal UserDetailsCountlyService(CountlyConfiguration configuration, CountlyLo } /// - /// Modifies all user data. Custom data should be json string. - /// Deletes an already defined custom property from the Countly server, if it is supplied with a NULL value + /// Add user custom detail to request queue. /// - /// User's detail object /// - internal async Task UserDetailsAsync(CountlyUserDetailsModel userDetailsModel) + private void AddCustomDetailToRequestQueue(IDictionary segments) { - Log.Debug("[UserDetailsCountlyService] UserDetailsAsync : userDetails = " + (userDetailsModel != null)); + IDictionary customDetail = FixSegmentKeysAndValues(segments); - if (userDetailsModel == null) { - Log.Warning("[UserDetailsCountlyService] UserDetailsAsync : The parameter 'userDetailsModel' can't be null."); - return; - } - - await SetUserDetailsAsync(userDetailsModel); - } - - /// - /// Modifies custom user data only. Custom data should be json string. - /// Deletes an already defined custom property from the Countly server, if it is supplied with a NULL value - /// - /// User's custom detail object - /// - internal async Task UserCustomDetailsAsync(CountlyUserDetailsModel userDetailsModel) - { - Log.Debug("[UserDetailsCountlyService] UserCustomDetailsAsync " + (userDetailsModel != null)); - - if (userDetailsModel == null) { - Log.Warning("[UserDetailsCountlyService] UserCustomDetailsAsync : The parameter 'userDetailsModel' can't be null."); - return; - } - - await SetCustomUserDetailsAsync(userDetailsModel); + Dictionary requestParams = + new Dictionary + { + { "user_details", + JsonConvert.SerializeObject( + new Dictionary + { + { "custom", customDetail } + }) + } + }; + _requestCountlyHelper.AddToRequestQueue(requestParams); + _ = _requestCountlyHelper.ProcessQueue(); } /// @@ -84,6 +71,21 @@ public async Task SetUserDetailsAsync(CountlyUserDetailsModel userDetailsModel) throw new Exception("Accepted picture formats are .png, .gif and .jpeg"); } + + userDetailsModel.Name = TrimValue("Name", userDetailsModel.Name); + userDetailsModel.Phone = TrimValue("Phone", userDetailsModel.Phone); + userDetailsModel.Email = TrimValue("Email", userDetailsModel.Email); + userDetailsModel.Gender = TrimValue("Gender", userDetailsModel.Gender); + userDetailsModel.Username = TrimValue("Username", userDetailsModel.Username); + userDetailsModel.BirthYear = TrimValue("BirthYear", userDetailsModel.BirthYear); + userDetailsModel.Organization = TrimValue("Organization", userDetailsModel.Organization); + + if (userDetailsModel.PictureUrl.Length > 4096) { + Log.Warning("[" + GetType().Name + "] TrimValue : Max allowed length of 'PictureUrl' is " + _configuration.MaxValueSize); + userDetailsModel.PictureUrl = userDetailsModel.PictureUrl.Substring(0, 4096); + } + + userDetailsModel.Custom = FixSegmentKeysAndValues(userDetailsModel.Custom); Dictionary requestParams = new Dictionary { @@ -92,7 +94,7 @@ public async Task SetUserDetailsAsync(CountlyUserDetailsModel userDetailsModel) }; _requestCountlyHelper.AddToRequestQueue(requestParams); - _= _requestCountlyHelper.ProcessQueue(); + _ = _requestCountlyHelper.ProcessQueue(); } } @@ -122,19 +124,7 @@ public async Task SetCustomUserDetailsAsync(CountlyUserDetailsModel userDetailsM return; } - Dictionary requestParams = - new Dictionary - { - { "user_details", - JsonConvert.SerializeObject( - new Dictionary - { - { "custom", userDetailsModel.Custom } - }) - } - }; - _requestCountlyHelper.AddToRequestQueue(requestParams); - _= _requestCountlyHelper.ProcessQueue(); + AddCustomDetailToRequestQueue(userDetailsModel.Custom); } } @@ -155,7 +145,7 @@ public async Task SaveAsync() CountlyUserDetailsModel model = new CountlyUserDetailsModel(CustomDataProperties); CustomDataProperties = new Dictionary { }; - _= SetCustomUserDetailsAsync(model); + AddCustomDetailToRequestQueue(CustomDataProperties); } } @@ -167,10 +157,23 @@ public async Task SaveAsync() /// string with value for the property public void Set(string key, string value) { + + if (string.IsNullOrEmpty(key)) { + Log.Warning("[UserDetailsCountlyService] Set : key '" + key + "'isn't valid."); + + return; + } + + if (string.IsNullOrEmpty(value)) { + Log.Warning("[UserDetailsCountlyService] Set : value '" + value + "'isn't valid."); + + return; + } + lock (LockObj) { Log.Info("[UserDetailsCountlyService] Set : key = " + key + ", value = " + value); - AddToCustomData(key, value); + AddToCustomData(key, TrimValue(key, value)); } } @@ -181,10 +184,22 @@ public void Set(string key, string value) /// string value to set public void SetOnce(string key, string value) { + if (string.IsNullOrEmpty(value)) { + Log.Warning("[UserDetailsCountlyService] SetOnce : key '" + key + "'isn't valid."); + + return; + } + + if (string.IsNullOrEmpty(value)) { + Log.Warning("[UserDetailsCountlyService] SetOnce : value '" + value + "'isn't valid."); + + return; + } + lock (LockObj) { Log.Info("[UserDetailsCountlyService] SetOnce : key = " + key + ", value = " + value); - AddToCustomData(key, new Dictionary { { "$setOnce", value } }); + AddToCustomData(key, new Dictionary { { "$setOnce", TrimValue(key, value) } }); } } @@ -194,6 +209,12 @@ public void SetOnce(string key, string value) /// string with property name to increment public void Increment(string key) { + if (string.IsNullOrEmpty(key)) { + Log.Warning("[UserDetailsCountlyService] Increment : key '" + key + "'isn't valid."); + + return; + } + lock (LockObj) { Log.Info("[UserDetailsCountlyService] Increment : key = " + key); @@ -208,6 +229,11 @@ public void Increment(string key) /// double value by which to increment public void IncrementBy(string key, double value) { + if (string.IsNullOrEmpty(key)) { + Log.Warning("[UserDetailsCountlyService] IncrementBy : key '" + key + "'isn't valid."); + + return; + } lock (LockObj) { Log.Info("[UserDetailsCountlyService] IncrementBy : key = " + key + ", value = " + value); @@ -222,6 +248,12 @@ public void IncrementBy(string key, double value) /// double value by which to multiply public void Multiply(string key, double value) { + if (string.IsNullOrEmpty(key)) { + Log.Warning("[UserDetailsCountlyService] Multiply : key '" + key + "'isn't valid."); + + return; + } + lock (LockObj) { Log.Info("[UserDetailsCountlyService] Multiply : key = " + key + ", value = " + value); @@ -236,6 +268,12 @@ public void Multiply(string key, double value) /// double value to check for max public void Max(string key, double value) { + if (string.IsNullOrEmpty(key)) { + Log.Warning("[UserDetailsCountlyService] Max : key '" + key + "'isn't valid."); + + return; + } + lock (LockObj) { Log.Info("[UserDetailsCountlyService] Max : key = " + key + ", value = " + value); @@ -250,6 +288,12 @@ public void Max(string key, double value) /// double value to check for min public void Min(string key, double value) { + if (string.IsNullOrEmpty(key)) { + Log.Warning("[UserDetailsCountlyService] Min : key '" + key + "'isn't valid."); + + return; + } + lock (LockObj) { Log.Info("[UserDetailsCountlyService] Min : key = " + key + ", value = " + value); @@ -265,10 +309,22 @@ public void Min(string key, double value) /// array with values to add public void Push(string key, string[] value) { + if (string.IsNullOrEmpty(key)) { + Log.Warning("[UserDetailsCountlyService] Push : key '" + key + "'isn't valid."); + + return; + } + + if (value == null) { + Log.Warning("[UserDetailsCountlyService] Push : value '" + value + "'isn't valid."); + + return; + } + lock (LockObj) { Log.Info("[UserDetailsCountlyService] Push : key = " + key + ", value = " + value); - AddToCustomData(key, new Dictionary { { "$push", value } }); + AddToCustomData(key, new Dictionary { { "$push", TrimValues(value) } }); } } @@ -280,10 +336,21 @@ public void Push(string key, string[] value) /// array with values to add public void PushUnique(string key, string[] value) { + if (string.IsNullOrEmpty(key)) { + Log.Warning("[UserDetailsCountlyService] PushUnique : key '" + key + "'isn't valid."); + + return; + } + + if (value == null) { + Log.Warning("[UserDetailsCountlyService] PushUnique : value '" + value + "'isn't valid."); + + return; + } + lock (LockObj) { Log.Info("[UserDetailsCountlyService] PushUnique : key = " + key + ", value = " + value); - - AddToCustomData(key, new Dictionary { { "$addToSet", value } }); + AddToCustomData(key, new Dictionary { { "$addToSet", TrimValues(value) } }); } } @@ -294,9 +361,22 @@ public void PushUnique(string key, string[] value) /// array with values to remove from array public void Pull(string key, string[] value) { + if (string.IsNullOrEmpty(key)) { + Log.Warning("[UserDetailsCountlyService] Pull : key '" + key + "'isn't valid."); + + return; + } + + if (value == null) { + Log.Warning("[UserDetailsCountlyService] Pull : value '" + value + "'isn't valid."); + + return; + } + lock (LockObj) { Log.Info("[UserDetailsCountlyService] Pull : key = " + key + ", value = " + value); + value = TrimValues(value); AddToCustomData(key, new Dictionary { { "$pull", value } }); } } @@ -315,6 +395,8 @@ private void AddToCustomData(string key, object value) return; } + key = TrimKey(key); + if (CustomDataProperties.ContainsKey(key)) { string item = CustomDataProperties.Select(x => x.Key).FirstOrDefault(x => x.Equals(key, StringComparison.OrdinalIgnoreCase)); if (item != null) { diff --git a/Assets/Plugins/CountlySDK/Services/ViewCountlyService.cs b/Assets/Plugins/CountlySDK/Services/ViewCountlyService.cs index 654308ba..5b8c3a35 100644 --- a/Assets/Plugins/CountlySDK/Services/ViewCountlyService.cs +++ b/Assets/Plugins/CountlySDK/Services/ViewCountlyService.cs @@ -53,6 +53,11 @@ public async Task RecordOpenViewAsync(string name) return; } + if (name.Length > _configuration.MaxKeyLength) { + Log.Verbose("[ViewCountlyService] RecordOpenViewAsync : Max allowed key length is " + _configuration.MaxKeyLength); + name = name.Substring(0, _configuration.MaxKeyLength); + } + ViewSegment currentViewSegment = new ViewSegment { Name = name, @@ -105,6 +110,11 @@ public async Task RecordCloseViewAsync(string name) return; } + if (name.Length > _configuration.MaxKeyLength) { + Log.Verbose("[ViewCountlyService] RecordCloseViewAsync : Max allowed key length is " + _configuration.MaxKeyLength); + name = name.Substring(0, _configuration.MaxKeyLength); + } + ViewSegment currentViewSegment = new ViewSegment { Name = name, diff --git a/Assets/Tests/PlayModeTests/ConfigurationTests.cs b/Assets/Tests/PlayModeTests/ConfigurationTests.cs index f98e7e57..d89ef49e 100644 --- a/Assets/Tests/PlayModeTests/ConfigurationTests.cs +++ b/Assets/Tests/PlayModeTests/ConfigurationTests.cs @@ -34,6 +34,12 @@ public void TestSDKInitParams() EventQueueThreshold = 150, TotalBreadcrumbsAllowed = 200, + MaxValueSize = 4, + MaxKeyLength = 5, + MaxSegmentationValues = 6, + MaxStackTraceLineLength = 7, + MaxStackTraceLinesPerThread = 8, + NotificationMode = TestMode.AndroidTestToken }; @@ -72,6 +78,12 @@ public void TestSDKInitParams() Assert.AreEqual(Countly.Instance.Configuration.TotalBreadcrumbsAllowed, 200); Assert.AreEqual(Countly.Instance.Configuration.NotificationMode, TestMode.AndroidTestToken); + Assert.AreEqual(Countly.Instance.Configuration.MaxValueSize, 4); + Assert.AreEqual(Countly.Instance.Configuration.MaxKeyLength, 5); + Assert.AreEqual(Countly.Instance.Configuration.MaxSegmentationValues, 6); + Assert.AreEqual(Countly.Instance.Configuration.MaxStackTraceLineLength, 7); + Assert.AreEqual(Countly.Instance.Configuration.MaxStackTraceLinesPerThread, 8); + Assert.AreEqual(Countly.Instance.Configuration.Salt, "091355076ead"); Assert.AreEqual(Countly.Instance.Configuration.DeviceId, "device-xyz"); @@ -102,6 +114,13 @@ public void TestDefaultConfigValues() Assert.AreEqual(Countly.Instance.Configuration.TotalBreadcrumbsAllowed, 100); Assert.AreEqual(Countly.Instance.Configuration.NotificationMode, TestMode.None); + Assert.AreEqual(Countly.Instance.Configuration.MaxValueSize, 256); + Assert.AreEqual(Countly.Instance.Configuration.MaxKeyLength, 128); + Assert.AreEqual(Countly.Instance.Configuration.MaxSegmentationValues, 30); + Assert.AreEqual(Countly.Instance.Configuration.MaxStackTraceLineLength, 200); + Assert.AreEqual(Countly.Instance.Configuration.MaxStackTraceLinesPerThread, 30); + + Assert.AreEqual(Countly.Instance.Configuration.Salt, null); Assert.AreEqual(Countly.Instance.Configuration.DeviceId, null); Assert.AreEqual(Countly.Instance.Configuration.EnablePost, false); diff --git a/Assets/Tests/PlayModeTests/CrashTests.cs b/Assets/Tests/PlayModeTests/CrashTests.cs index 3060ed82..1b8261aa 100644 --- a/Assets/Tests/PlayModeTests/CrashTests.cs +++ b/Assets/Tests/PlayModeTests/CrashTests.cs @@ -67,6 +67,63 @@ public void TestCrashBreadCrumbs() } + /// + /// It validates the crash limits. + /// + [Test] + public async void TestCrashLimits() + { + CountlyConfiguration configuration = new CountlyConfiguration { + ServerUrl = _serverUrl, + AppKey = _appKey, + MaxValueSize = 5, + MaxKeyLength = 5, + MaxSegmentationValues = 2, + MaxStackTraceLineLength = 5, + MaxStackTraceLinesPerThread = 2 + }; + + Countly.Instance.Init(configuration); + Countly.Instance.ClearStorage(); + + Assert.IsNotNull(Countly.Instance.CrashReports); + Assert.AreEqual(0, Countly.Instance.CrashReports._requestCountlyHelper._requestRepo.Count); + + await Countly.Instance.CrashReports.SendCrashReportAsync("", "StackTrace", LogType.Exception, null); + Assert.AreEqual(0, Countly.Instance.CrashReports._requestCountlyHelper._requestRepo.Count); + + await Countly.Instance.CrashReports.SendCrashReportAsync(null, "StackTrace", LogType.Exception, null); + Assert.AreEqual(0, Countly.Instance.CrashReports._requestCountlyHelper._requestRepo.Count); + + await Countly.Instance.CrashReports.SendCrashReportAsync(" ", "StackTrace", LogType.Exception, null); + Assert.AreEqual(0, Countly.Instance.CrashReports._requestCountlyHelper._requestRepo.Count); + + + Dictionary seg = new Dictionary{ + { "Time", "1234455"}, + { "Retry Attempts", "10"}, + { "Temp", "100"} + }; + + await Countly.Instance.CrashReports.SendCrashReportAsync("message", "StackTrace_1\nStackTrace_2\nStackTrace_3", LogType.Exception, seg); + Assert.AreEqual(1, Countly.Instance.CrashReports._requestCountlyHelper._requestRepo.Count); + + CountlyRequestModel requestModel = Countly.Instance.CrashReports._requestCountlyHelper._requestRepo.Dequeue(); + string myUri = requestModel.RequestUrl; + string crash = HttpUtility.ParseQueryString(myUri).Get("crash"); + JObject json = JObject.Parse(crash); + Assert.AreEqual("message", json.GetValue("_name").ToString()); + Assert.AreEqual("True", json.GetValue("_nonfatal").ToString()); + Assert.AreEqual("Stack\nStack", json.GetValue("_error").ToString()); + + JObject custom = json["_custom"].ToObject(); + + Assert.AreEqual(2, custom.Count); + Assert.AreEqual("12344", custom.GetValue("Time").ToString()); + Assert.AreEqual("10", custom.GetValue("Retry").ToString()); + + } + /// /// It validates the functionality of 'SendCrashReportAsync'. /// @@ -154,7 +211,7 @@ public async void TestMethod_SendCrashReportInternalGetMethod() } }; - string url = + string url = Countly.Instance.CrashReports._requestCountlyHelper.BuildGetRequest(requestParams); int index = url.IndexOf("crash"); Assert.AreEqual(url.Substring(index), requestModel.RequestUrl.Substring(index)); @@ -233,7 +290,7 @@ public async void TestMethod_SendCrashReportInternalPostMethod() } /// - /// It validates the limit on bread crumbs length (limit = 1000). + /// It validates the maximum size of a bread crumb. /// [Test] public void TestCrashBreadCrumbsLength() @@ -255,10 +312,10 @@ public void TestCrashBreadCrumbsLength() Assert.AreEqual(1, Countly.Instance.CrashReports._crashBreadcrumbs.Count); string qBreadCrumbs = Countly.Instance.CrashReports._crashBreadcrumbs.Dequeue(); - Assert.AreEqual(1000, qBreadCrumbs.Length); + Assert.AreEqual(256, qBreadCrumbs.Length); - string validBreadcrumb = breadCrumbs.Length > 1000 ? breadCrumbs.Substring(0, 1000) : breadCrumbs; - Assert.AreEqual(validBreadcrumb, qBreadCrumbs); + string validBreadcrumb = breadCrumbs.Length > 256 ? breadCrumbs.Substring(0, 256) : breadCrumbs; + Assert.AreEqual(validBreadcrumb, qBreadCrumbs); } /// diff --git a/Assets/Tests/PlayModeTests/EventTests.cs b/Assets/Tests/PlayModeTests/EventTests.cs index abca70d3..5fc89b2a 100644 --- a/Assets/Tests/PlayModeTests/EventTests.cs +++ b/Assets/Tests/PlayModeTests/EventTests.cs @@ -162,6 +162,48 @@ public async void TestEventMethod_ReportCustomEventAsync() Assert.AreEqual("value2", model.Segmentation["key2"]); } + /// + /// It validates the the event limits. + /// + [Test] + public async void TestEventLimits() + { + CountlyConfiguration configuration = new CountlyConfiguration { + ServerUrl = _serverUrl, + AppKey = _appKey, + MaxKeyLength = 4, + MaxValueSize = 6, + MaxSegmentationValues = 2 + }; + + Countly.Instance.Init(configuration); + + Assert.IsNotNull(Countly.Instance.Events); + + Assert.AreEqual(0, Countly.Instance.Events._eventRepo.Count); + + + Dictionary segments = new Dictionary{ + { "key1", "value1"}, + { "key2_00", "value2_00"}, + { "key3_00", "value3"} + }; + + SegmentModel segmentModel = new SegmentModel(segments); + + await Countly.Instance.Events.RecordEventAsync("test_event", segmentation: segmentModel, sum: 23, duration: 5); + + CountlyEventModel model = Countly.Instance.Events._eventRepo.Dequeue(); + + Assert.AreEqual("test", model.Key); + Assert.AreEqual(23, model.Sum); + Assert.AreEqual(1, model.Count); + Assert.AreEqual(5, model.Duration); + Assert.AreEqual(2, model.Segmentation.Count); + Assert.AreEqual("value1", model.Segmentation["key1"]); + Assert.AreEqual("value2", model.Segmentation["key2"]); + } + /// /// It validates the data type of segment items. /// diff --git a/Assets/Tests/PlayModeTests/UserCustomDetailsTests.cs b/Assets/Tests/PlayModeTests/UserCustomDetailsTests.cs index 5b8579ce..80570191 100644 --- a/Assets/Tests/PlayModeTests/UserCustomDetailsTests.cs +++ b/Assets/Tests/PlayModeTests/UserCustomDetailsTests.cs @@ -70,6 +70,65 @@ public async void TestUserDetailMethod_SetUserDetailsAsync() Assert.AreEqual("5.9", userDetailJson["custom"]["Height"].ToString()); } + /// + /// It validate user profile fields limits. + /// + [Test] + public async void TestUserProfileFieldsLimits() + { + CountlyConfiguration configuration = new CountlyConfiguration { + ServerUrl = _serverUrl, + AppKey = _appKey, + MaxValueSize = 3, + EnablePost = true + }; + + Countly.Instance.Init(configuration); + Countly.Instance.ClearStorage(); + + Assert.IsNotNull(Countly.Instance.UserDetails); + Assert.AreEqual(0, Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Count); + + CountlyUserDetailsModel userDetails = null; + + await Countly.Instance.UserDetails.SetUserDetailsAsync(userDetails); + Assert.AreEqual(0, Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Count); + + userDetails = new CountlyUserDetailsModel("Full Name", "username", "useremail@email.com", "Organization", + "222-222-222", + "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890.png", + "M", "1986", + new Dictionary{ + { "Hair", "Black" }, + { "Height", "5.9" }, + }); + await Countly.Instance.UserDetails.SetUserDetailsAsync(userDetails); + Assert.AreEqual(1, Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Count); + + CountlyRequestModel requestModel = Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Dequeue(); + + string userDetailData = requestModel.RequestData; + JObject json = JObject.Parse(userDetailData); + JObject userDetailJson = JObject.Parse(json["user_details"].ToString()); + + Assert.AreEqual("Ful", userDetailJson["name"].ToString()); + Assert.AreEqual("use", userDetailJson["username"].ToString()); + Assert.AreEqual("use", userDetailJson["email"].ToString()); + Assert.AreEqual("Org", userDetailJson["organization"].ToString()); + Assert.AreEqual("222", userDetailJson["phone"].ToString()); + Assert.AreEqual(4096, userDetailJson["picture"].ToString().Length); + Assert.AreEqual("M", userDetailJson["gender"].ToString()); + Assert.AreEqual("198", userDetailJson["byear"].ToString()); + + Assert.AreEqual("Bla", userDetailJson["custom"]["Hair"].ToString()); + Assert.AreEqual("5.9", userDetailJson["custom"]["Height"].ToString()); + } + /// /// It check the working of method 'SetUserDetailsAsync'. /// @@ -114,6 +173,131 @@ public async void TestUserDetailMethod_SetCustomUserDetailsAsync() } + + /// + /// It validate user detail segments limits. + /// + [Test] + public async void TestUserDetailSegmentLimits() + { + CountlyConfiguration configuration = new CountlyConfiguration { + ServerUrl = _serverUrl, + AppKey = _appKey, + MaxKeyLength = 5, + MaxValueSize = 6, + EnablePost = true + }; + + Countly.Instance.Init(configuration); + Countly.Instance.ClearStorage(); + + Assert.IsNotNull(Countly.Instance.UserDetails); + Assert.AreEqual(0, Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Count); + + CountlyUserDetailsModel userDetails = null; + + await Countly.Instance.UserDetails.SetCustomUserDetailsAsync(userDetails); + Assert.AreEqual(0, Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Count); + + userDetails = new CountlyUserDetailsModel( + new Dictionary{ + { "Hair", "Black_000" }, + { "Height", "5.9" }, + }); + await Countly.Instance.UserDetails.SetCustomUserDetailsAsync(userDetails); + Assert.AreEqual(1, Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Count); + + CountlyRequestModel requestModel = Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Dequeue(); + + + string userDetailData = requestModel.RequestData; + JObject json = JObject.Parse(userDetailData); + string userDetail = json["user_details"].ToString(); + JObject custom = JObject.Parse(userDetail); + + Assert.AreEqual("Black_", custom["custom"]["Hair"].ToString()); + Assert.AreEqual("5.9", custom["custom"]["Heigh"].ToString()); + + } + + /// + /// It validate custom user detail segments key and value limits. + /// + [Test] + public void TestCustomUserDetailSegmentLimits() + { + CountlyConfiguration configuration = new CountlyConfiguration { + ServerUrl = _serverUrl, + AppKey = _appKey, + MaxKeyLength = 5, + MaxValueSize = 4, + EnablePost = true + }; + + Countly.Instance.Init(configuration); + Countly.Instance.ClearStorage(); + + Assert.IsNotNull(Countly.Instance.UserDetails); + Assert.AreEqual(0, Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Count); + + + Countly.Instance.UserDetails.IncrementBy("IncrementBy", 5); + Assert.IsTrue(Countly.Instance.UserDetails.CustomDataProperties.ContainsKey("Incre")); + Dictionary dic = Countly.Instance.UserDetails.CustomDataProperties["Incre"] as Dictionary; + Assert.AreEqual(5, dic["$inc"]); + + Countly.Instance.UserDetails.Increment("Increment"); + Assert.IsTrue(Countly.Instance.UserDetails.CustomDataProperties.ContainsKey("Incre")); + dic = Countly.Instance.UserDetails.CustomDataProperties["Incre"] as Dictionary; + Assert.AreEqual(1, dic["$inc"]); + + Countly.Instance.UserDetails.SetOnce("SetOnce", "100KM"); + Assert.IsTrue(Countly.Instance.UserDetails.CustomDataProperties.ContainsKey("SetOn")); + dic = Countly.Instance.UserDetails.CustomDataProperties["SetOn"] as Dictionary; + Assert.AreEqual("100K", dic["$setOnce"]); + + Countly.Instance.UserDetails.Set("Set", "6000.0"); + Assert.IsTrue(Countly.Instance.UserDetails.CustomDataProperties.ContainsKey("Set")); + string height = (string)Countly.Instance.UserDetails.CustomDataProperties["Set"]; + Assert.AreEqual("6000", height); + + Countly.Instance.UserDetails.Pull("Pull", new string[] { "50KM", "100KM" }); + Assert.IsTrue(Countly.Instance.UserDetails.CustomDataProperties.ContainsKey("Pull")); + dic = Countly.Instance.UserDetails.CustomDataProperties["Pull"] as Dictionary; + Assert.AreEqual("50KM", ((string[])dic["$pull"])[0]); + Assert.AreEqual("100K", ((string[])dic["$pull"])[1]); + + Countly.Instance.UserDetails.PushUnique("PushUnique", new string[] { "2900.0" }); + Assert.IsTrue(Countly.Instance.UserDetails.CustomDataProperties.ContainsKey("PushU")); + dic = Countly.Instance.UserDetails.CustomDataProperties["PushU"] as Dictionary; + Assert.AreEqual(new string[] { "2900" }, dic["$addToSet"]); + Countly.Instance.UserDetails.CustomDataProperties.Clear(); + + Countly.Instance.UserDetails.Push("Push", new string[] { "6000.0" }); + Assert.IsTrue(Countly.Instance.UserDetails.CustomDataProperties.ContainsKey("Push")); + dic = Countly.Instance.UserDetails.CustomDataProperties["Push"] as Dictionary; + Assert.AreEqual("6000", ((string[])dic["$push"])[0]); + Countly.Instance.UserDetails.CustomDataProperties.Clear(); + + Countly.Instance.UserDetails.Min("Min", 10.0); + Assert.IsTrue(Countly.Instance.UserDetails.CustomDataProperties.ContainsKey("Min")); + dic = Countly.Instance.UserDetails.CustomDataProperties["Min"] as Dictionary; + Assert.AreEqual(10.0, dic["$min"]); + Countly.Instance.UserDetails.CustomDataProperties.Clear(); + + Countly.Instance.UserDetails.Max("Max", 10000.0); + Assert.IsTrue(Countly.Instance.UserDetails.CustomDataProperties.ContainsKey("Max")); + dic = Countly.Instance.UserDetails.CustomDataProperties["Max"] as Dictionary; + Assert.AreEqual(10000.0, dic["$max"]); + + Countly.Instance.UserDetails.Multiply("Multiply", 10.0); + Assert.IsTrue(Countly.Instance.UserDetails.CustomDataProperties.ContainsKey("Multi")); + dic = Countly.Instance.UserDetails.CustomDataProperties["Multi"] as Dictionary; + Assert.AreEqual(10.0, dic["$mul"]); + Countly.Instance.UserDetails.CustomDataProperties.Clear(); + + } + /// /// It check the working of method 'UserCustomDetailsAsync'. /// @@ -132,11 +316,11 @@ public async void TestUserDetailMethod_UserCustomDetailsAsync() Assert.AreEqual(0, Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Count); CountlyUserDetailsModel InvalidUserDetails = null; - await Countly.Instance.UserDetails.UserCustomDetailsAsync(InvalidUserDetails); + await Countly.Instance.UserDetails.SetCustomUserDetailsAsync(InvalidUserDetails); Assert.AreEqual(0, Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Count); InvalidUserDetails = new CountlyUserDetailsModel(null); - await Countly.Instance.UserDetails.UserCustomDetailsAsync(InvalidUserDetails); + await Countly.Instance.UserDetails.SetCustomUserDetailsAsync(InvalidUserDetails); Assert.AreEqual(0, Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Count); Dictionary customDetail = new Dictionary{ @@ -144,7 +328,7 @@ public async void TestUserDetailMethod_UserCustomDetailsAsync() { "Mole", "Lower Left Cheek" } }; CountlyUserDetailsModel userDetails = new CountlyUserDetailsModel(customDetail); - await Countly.Instance.UserDetails.UserCustomDetailsAsync(userDetails); + await Countly.Instance.UserDetails.SetCustomUserDetailsAsync(userDetails); Assert.AreEqual(1, Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Count); } /// @@ -155,22 +339,25 @@ public void TestUserCustomProperty_SetOnceAndSet() { CountlyConfiguration configuration = new CountlyConfiguration { ServerUrl = _serverUrl, - AppKey = _appKey, + AppKey = _appKey }; Countly.Instance.Init(configuration); + Countly.Instance.ClearStorage(); Assert.IsNotNull(Countly.Instance.UserDetails); + Assert.AreEqual(0, Countly.Instance.UserDetails._requestCountlyHelper._requestRepo.Count); + - Countly.Instance.UserDetails.SetOnce("Distance", "10KM"); + Countly.Instance.UserDetails.SetOnce("Distance", "100KM"); Assert.IsTrue(Countly.Instance.UserDetails.CustomDataProperties.ContainsKey("Distance")); Dictionary dic = Countly.Instance.UserDetails.CustomDataProperties["Distance"] as Dictionary; - Assert.AreEqual("10KM", dic["$setOnce"]); + Assert.AreEqual("100KM", dic["$setOnce"]); - Countly.Instance.UserDetails.Set("Height", "6"); + Countly.Instance.UserDetails.Set("Height", "5.9125"); Assert.IsTrue(Countly.Instance.UserDetails.CustomDataProperties.ContainsKey("Height")); string height = (string)Countly.Instance.UserDetails.CustomDataProperties["Height"]; - Assert.AreEqual("6", height); + Assert.AreEqual("5.9125", height); } /// diff --git a/Assets/Tests/PlayModeTests/ViewsTests.cs b/Assets/Tests/PlayModeTests/ViewsTests.cs index de32c854..e5fef603 100644 --- a/Assets/Tests/PlayModeTests/ViewsTests.cs +++ b/Assets/Tests/PlayModeTests/ViewsTests.cs @@ -110,6 +110,47 @@ public async void TestViewsConsent() } + /// + /// It validates the limit of the view's name size. + /// + [Test] + public async void TestViewNameLimit() + { + CountlyConfiguration configuration = new CountlyConfiguration { + ServerUrl = _serverUrl, + AppKey = _appKey, + MaxKeyLength = 5 + }; + + Countly.Instance.Init(configuration); + + Countly.Instance.ClearStorage(); + Assert.IsNotNull(Countly.Instance.Views); + Assert.AreEqual(0, Countly.Instance.Views._eventService._eventRepo.Count); + + await Countly.Instance.Views.RecordCloseViewAsync("open_view"); + await Countly.Instance.Views.RecordCloseViewAsync("close_view"); + Assert.AreEqual(2, Countly.Instance.Views._eventService._eventRepo.Count); + + CountlyEventModel model = Countly.Instance.Views._eventService._eventRepo.Dequeue(); + + Assert.AreEqual(CountlyEventModel.ViewEvent, model.Key); + Assert.IsNull(model.Sum); + Assert.AreEqual(1, model.Count); + Assert.IsNull(model.Duration); + Assert.IsNotNull(model.Segmentation); + Assert.AreEqual("open_", model.Segmentation["name"]); + + model = Countly.Instance.Views._eventService._eventRepo.Dequeue(); + + Assert.AreEqual(CountlyEventModel.ViewEvent, model.Key); + Assert.IsNull(model.Sum); + Assert.AreEqual(1, model.Count); + Assert.IsNull(model.Duration); + Assert.IsNotNull(model.Segmentation); + Assert.AreEqual("close", model.Segmentation["name"]); + } + /// /// It validates functionality of method 'RecordCloseViewAsync'. /// diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cd9faaa..71f56828 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 20.11.5 +* Added new configuration fields to manipulate internal SDK value and key limits. +* Warning! This release will introduce configurable maximum size limits for values and keys throughout the SDK. If they would exceed the limits, they would be truncated. + ## 20.11.4 * Added "lock" checks for all public SDK methods. * Fixed race issue bug that occured when events are combined into a request.