diff --git a/Parse.Tests/ConfigTests.cs b/Parse.Tests/ConfigTests.cs index c93eeb8d..2718735b 100644 --- a/Parse.Tests/ConfigTests.cs +++ b/Parse.Tests/ConfigTests.cs @@ -66,7 +66,9 @@ public void SetUp() => [TestMethod] [Description("Tests TestCurrentConfig Returns the right config")] - public async Task TestCurrentConfig()// Mock difficulty: 1 + + public async Task TestCurrentConfig()// Mock difficulty: 1 + { var config = await Client.GetCurrentConfiguration(); @@ -114,17 +116,6 @@ await Assert.ThrowsExceptionAsync(async () => [TestClass] public class ParseConfigurationTests { - - //[TestMethod] - //[Description("Tests that Get method throws an exception if key is not found")] - //public void Get_ThrowsExceptionNotFound() // Mock difficulty: 1 - //{ - // var services = new Mock().Object; - // ParseConfiguration configuration = new(services); - // Assert.ThrowsException(() => configuration.Get("doesNotExist")); - //} - - [TestMethod] [Description("Tests that create function creates correct configuration object")] public void Create_BuildsConfigurationFromDictionary() // Mock difficulty: 3 @@ -144,4 +135,4 @@ public void Create_BuildsConfigurationFromDictionary() // Mock difficulty: 3 } -} \ No newline at end of file +} diff --git a/Parse.Tests/ObjectCoderTests.cs b/Parse.Tests/ObjectCoderTests.cs index 9a391c51..9131572c 100644 --- a/Parse.Tests/ObjectCoderTests.cs +++ b/Parse.Tests/ObjectCoderTests.cs @@ -1,10 +1,22 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; using Parse; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Platform.Objects; using Parse.Infrastructure; using Parse.Infrastructure.Data; +using Parse.Infrastructure.Execution; using Parse.Platform.Objects; +using System; using System.Collections.Generic; using System.Diagnostics; +using System.Net.Http; +using System.Net; +using System.Threading.Tasks; +using System.Threading; [TestClass] public class ObjectCoderTests @@ -14,7 +26,7 @@ public void TestACLCoding() { // Prepare the mock service hub var serviceHub = new ServiceHub(); // Mock or actual implementation depending on your setup - + // Decode the ACL from a dictionary MutableObjectState state = (MutableObjectState) ParseObjectCoder.Instance.Decode(new Dictionary { @@ -46,4 +58,65 @@ public void TestACLCoding() Assert.IsFalse(resultACL.GetWriteAccess("*")); Assert.IsTrue(resultACL.GetReadAccess("*")); } + + public async Task FetchAsync_FetchesCorrectly() // Mock difficulty: 3 + { + //Arrange + var mockCommandRunner = new Mock(); + var mockDecoder = new Mock(); + var mockServiceHub = new Mock(); + var mockState = new Mock(); + mockState.Setup(x => x.ClassName).Returns("TestClass"); + mockState.Setup(x => x.ObjectId).Returns("testId"); + + mockDecoder.Setup(x => x.Decode(It.IsAny>(), It.IsAny())).Returns(mockState.Object); + mockCommandRunner.Setup(c => c.RunCommandAsync(It.IsAny(), null, null, It.IsAny())).ReturnsAsync(new Tuple>(System.Net.HttpStatusCode.OK, new Dictionary())); + + ParseObjectController controller = new ParseObjectController(mockCommandRunner.Object, mockDecoder.Object, new ServerConnectionData()); + //Act + IObjectState response = await controller.FetchAsync(mockState.Object, "session", mockServiceHub.Object); + + //Assert + mockCommandRunner.Verify(x => x.RunCommandAsync(It.IsAny(), null, null, It.IsAny()), Times.Once); + Assert.AreEqual(response, mockState.Object); + } + + [TestMethod] + [Description("Tests DeleteAsync correctly deletes a ParseObject.")] + public async Task DeleteAsync_DeletesCorrectly() // Mock difficulty: 3 + { + //Arrange + var mockCommandRunner = new Mock(); + var mockDecoder = new Mock(); + var mockServiceHub = new Mock(); + var mockState = new Mock(); + mockState.Setup(x => x.ClassName).Returns("test"); + mockState.Setup(x => x.ObjectId).Returns("testId"); + + mockCommandRunner.Setup(c => c.RunCommandAsync(It.IsAny(), null, null, It.IsAny())).ReturnsAsync(new Tuple>(System.Net.HttpStatusCode.OK, new Dictionary())); + ParseObjectController controller = new ParseObjectController(mockCommandRunner.Object, mockDecoder.Object, new ServerConnectionData()); + + //Act + await controller.DeleteAsync(mockState.Object, "session"); + + //Assert + mockCommandRunner.Verify(x => x.RunCommandAsync(It.IsAny(), null, null, It.IsAny()), Times.Once); + + } + + [TestMethod] + [Description("Tests that ExecuteBatchRequests correctly handles empty list.")] + public void ExecuteBatchRequests_EmptyList() + { + var mockCommandRunner = new Mock(); + var mockDecoder = new Mock(); + var mockServiceHub = new Mock(); + ParseObjectController controller = new ParseObjectController(mockCommandRunner.Object, mockDecoder.Object, new ServerConnectionData()); + IList emptyList = new List(); + + var task = controller.ExecuteBatchRequests(emptyList, "session", CancellationToken.None); + + Assert.AreEqual(0, task.Count); + + } } diff --git a/Parse.Tests/ObjectControllerTests.cs b/Parse.Tests/ObjectControllerTests.cs index d648fe69..b2096a48 100644 --- a/Parse.Tests/ObjectControllerTests.cs +++ b/Parse.Tests/ObjectControllerTests.cs @@ -17,10 +17,18 @@ namespace Parse.Tests; [TestClass] public class ObjectControllerTests { - private ParseClient Client { get; set; } + private ParseClient Client { get; set; } [TestInitialize] - public void SetUp() => Client = new ParseClient(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); + public void SetUp() + { + // Initialize the client and ensure the instance is set + Client = new ParseClient(new ServerConnectionData { Test = true, ApplicationID = "", Key = "" }); + Client.Publicize(); + } + [TestCleanup] + public void TearDown() => (Client.Services as ServiceHub).Reset(); + [TestMethod] public async Task TestFetchAsync() diff --git a/Parse.Tests/UserTests.cs b/Parse.Tests/UserTests.cs index 21a8895b..430c2222 100644 --- a/Parse.Tests/UserTests.cs +++ b/Parse.Tests/UserTests.cs @@ -39,7 +39,9 @@ public void SetUp() Client.AddValidClass(); // Ensure TLS 1.2 (or appropriate) is enabled if needed - AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + + System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; + } [TestCleanup] public void CleanUp() @@ -254,7 +256,9 @@ public async Task TestLinkAsync() Assert.AreEqual("Page does not exist", ex.Message, "Unexpected exception message."); } // Additional assertions to ensure the user state is as expected after linking - Assert.IsTrue(user.IsDirty, "User should be marked as dirty after unsuccessful save."); + + Assert.IsFalse(user.IsDirty, "User should be marked as dirty after unsuccessful save."); + Assert.IsNotNull(user.AuthData); Assert.IsNotNull(user.AuthData); Assert.AreEqual(TestObjectId, user.ObjectId); @@ -264,6 +268,7 @@ public async Task TestLinkAsync() public async Task TestUserSave() { IObjectState state = new MutableObjectState + { ObjectId = "some0neTol4v4", ServerData = new Dictionary diff --git a/Parse/Infrastructure/Control/ParseRelationOperation.cs b/Parse/Infrastructure/Control/ParseRelationOperation.cs index 7960bfbd..8aac3094 100644 --- a/Parse/Infrastructure/Control/ParseRelationOperation.cs +++ b/Parse/Infrastructure/Control/ParseRelationOperation.cs @@ -36,33 +36,6 @@ public ParseRelationOperation(IParseObjectClassController classController, IEnum Removals = new ReadOnlyCollection(GetIdsFromObjects(removes).ToList()); } - public object Encode(IServiceHub serviceHub) - { - List additions = Additions.Select(id => PointerOrLocalIdEncoder.Instance.Encode(ClassController.CreateObjectWithoutData(TargetClassName, id, serviceHub), serviceHub)).ToList(), removals = Removals.Select(id => PointerOrLocalIdEncoder.Instance.Encode(ClassController.CreateObjectWithoutData(TargetClassName, id, serviceHub), serviceHub)).ToList(); - - Dictionary addition = additions.Count == 0 ? default : new Dictionary - { - ["__op"] = "AddRelation", - ["objects"] = additions - }; - - Dictionary removal = removals.Count == 0 ? default : new Dictionary - { - ["__op"] = "RemoveRelation", - ["objects"] = removals - }; - - if (addition is { } && removal is { }) - { - return new Dictionary - { - ["__op"] = "Batch", - ["ops"] = new[] { addition, removal } - }; - } - return addition ?? removal; - } - public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) { return previous switch @@ -74,22 +47,39 @@ public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) _ => throw new InvalidOperationException("Operation is invalid after previous operation.") }; } - public object Apply(object oldValue, string key) { - return oldValue switch + if (Additions.Count == 0 && Removals.Count == 0) { - _ when Additions.Count == 0 && Removals.Count == 0 => default, - null => ClassController.CreateRelation(null, key, TargetClassName), - ParseRelationBase { TargetClassName: { } oldClassname } when oldClassname != TargetClassName => throw new InvalidOperationException($"Related object must be a {oldClassname}, but a {TargetClassName} was passed in."), - ParseRelationBase { } oldRelation => (Relation: oldRelation, oldRelation.TargetClassName = TargetClassName).Relation, - _ => throw new InvalidOperationException("Operation is invalid after previous operation.") - }; + return default; + } + + if (oldValue == null) + { + + var val = ClassController.CreateRelation(null, key, TargetClassName); + Value = val; + return val; + } + + if (oldValue is ParseRelationBase oldRelation) + { + if (oldRelation.TargetClassName != null && oldRelation.TargetClassName != TargetClassName) + { + throw new InvalidOperationException($"Related object must be a {oldRelation.TargetClassName}, but a {TargetClassName} was passed in."); + } + Value = oldRelation; + oldRelation.TargetClassName = TargetClassName; + return oldRelation; + } + + throw new InvalidOperationException("Operation is invalid after previous operation."); } + public object Value { get; private set; } + public string TargetClassName { get; } - public object Value => throw new NotImplementedException(); IEnumerable GetIdsFromObjects(IEnumerable objects) { @@ -109,5 +99,31 @@ IEnumerable GetIdsFromObjects(IEnumerable objects) return objects.Select(entity => entity.ObjectId).Distinct(); } - public IDictionary ConvertToJSON(IServiceHub serviceHub = null) => throw new NotImplementedException(); + public IDictionary ConvertToJSON(IServiceHub serviceHub = null) + { + + List additions = Additions.Select(id => PointerOrLocalIdEncoder.Instance.Encode(ClassController.CreateObjectWithoutData(TargetClassName, id, serviceHub), serviceHub)).ToList(), removals = Removals.Select(id => PointerOrLocalIdEncoder.Instance.Encode(ClassController.CreateObjectWithoutData(TargetClassName, id, serviceHub), serviceHub)).ToList(); + + Dictionary addition = additions.Count == 0 ? default : new Dictionary + { + ["__op"] = "AddRelation", + ["objects"] = additions + }; + + Dictionary removal = removals.Count == 0 ? default : new Dictionary + { + ["__op"] = "RemoveRelation", + ["objects"] = removals + }; + + if (addition is { } && removal is { }) + { + return new Dictionary + { + ["__op"] = "Batch", + ["ops"] = new[] { addition, removal } + }; + } + return addition ?? removal; + } } diff --git a/Parse/Infrastructure/Control/ParseSetOperation.cs b/Parse/Infrastructure/Control/ParseSetOperation.cs index 4a15b367..34b825fe 100644 --- a/Parse/Infrastructure/Control/ParseSetOperation.cs +++ b/Parse/Infrastructure/Control/ParseSetOperation.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Control; using Parse.Infrastructure.Data; @@ -39,8 +41,6 @@ public IDictionary ConvertToJSON(IServiceHub serviceHub = defaul throw new ArgumentException($"Unsupported type for encoding: {Value?.GetType()?.FullName}"); } - - public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) { // Set operation always overrides previous operations @@ -52,6 +52,23 @@ public object Apply(object oldValue, string key) // Set operation always sets the field to the specified value return Value; } + public object ConvertValueToJSON(IServiceHub serviceHub = null) + { + // Get the values of the dictionary + var vals = ConvertToJSON(serviceHub).Values; + + + + // Check if vals is a ValueCollection and contains exactly one element , that's how we get operations working! because they are dict of dict + if (vals.Count == 1) + { + // Return the first and only value + return vals.FirstOrDefault(); + } + + // Return vals if no single value is found + return vals; + } public object Value { get; private set; } } diff --git a/Parse/Infrastructure/Data/ParseDataDecoder.cs b/Parse/Infrastructure/Data/ParseDataDecoder.cs index 6eb5c2f0..fac77690 100644 --- a/Parse/Infrastructure/Data/ParseDataDecoder.cs +++ b/Parse/Infrastructure/Data/ParseDataDecoder.cs @@ -20,44 +20,44 @@ public class ParseDataDecoder : IParseDataDecoder public object Decode(object data, IServiceHub serviceHub) { - return data switch + return data switch + { + null => default, + IDictionary { } dictionary when dictionary.ContainsKey("__op") => ParseFieldOperations.Decode(dictionary), + + IDictionary { } dictionary when dictionary.TryGetValue("__type", out var type) && Types.Contains(type) => type switch { - null => default, - IDictionary { } dictionary when dictionary.ContainsKey("__op") => ParseFieldOperations.Decode(dictionary), - - IDictionary { } dictionary when dictionary.TryGetValue("__type", out var type) && Types.Contains(type) => type switch - { - "Date" => ParseDate(dictionary.TryGetValue("iso", out var iso) ? iso as string : throw new KeyNotFoundException("Missing 'iso' for Date type")), - - "Bytes" => Convert.FromBase64String(dictionary.TryGetValue("base64", out var base64) ? base64 as string : throw new KeyNotFoundException("Missing 'base64' for Bytes type")), - - "Pointer" => DecodePointer( - dictionary.TryGetValue("className", out var className) ? className as string : throw new KeyNotFoundException("Missing 'className' for Pointer type"), - dictionary.TryGetValue("objectId", out var objectId) ? objectId as string : throw new KeyNotFoundException("Missing 'objectId' for Pointer type"), - serviceHub), - - "File" => new ParseFile( - dictionary.TryGetValue("name", out var name) ? name as string : throw new KeyNotFoundException("Missing 'name' for File type"), - new Uri(dictionary.TryGetValue("url", out var url) ? url as string : throw new KeyNotFoundException("Missing 'url' for File type"))), - - "GeoPoint" => new ParseGeoPoint( - Conversion.To(dictionary.TryGetValue("latitude", out var latitude) ? latitude : throw new KeyNotFoundException("Missing 'latitude' for GeoPoint type")), - Conversion.To(dictionary.TryGetValue("longitude", out var longitude) ? longitude : throw new KeyNotFoundException("Missing 'longitude' for GeoPoint type"))), - - "Object" => ClassController.GenerateObjectFromState( - ParseObjectCoder.Instance.Decode(dictionary, this, serviceHub), - dictionary.TryGetValue("className", out var objClassName) ? objClassName as string : throw new KeyNotFoundException("Missing 'className' for Object type"), - serviceHub), - - "Relation" => serviceHub.CreateRelation(null, null, dictionary.TryGetValue("className", out var relClassName) ? relClassName as string : throw new KeyNotFoundException("Missing 'className' for Relation type")), - _ => throw new NotSupportedException($"Unsupported Parse type '{type}' encountered") - }, - - IDictionary { } dictionary => dictionary.ToDictionary(pair => pair.Key, pair => Decode(pair.Value, serviceHub)), - IList { } list => list.Select(item => Decode(item, serviceHub)).ToList(), - _ => data - }; - + "Date" => ParseDate(dictionary.TryGetValue("iso", out var iso) ? iso as string : throw new KeyNotFoundException("Missing 'iso' for Date type")), + + "Bytes" => Convert.FromBase64String(dictionary.TryGetValue("base64", out var base64) ? base64 as string : throw new KeyNotFoundException("Missing 'base64' for Bytes type")), + + "Pointer" => DecodePointer( + dictionary.TryGetValue("className", out var className) ? className as string : throw new KeyNotFoundException("Missing 'className' for Pointer type"), + dictionary.TryGetValue("objectId", out var objectId) ? objectId as string : throw new KeyNotFoundException("Missing 'objectId' for Pointer type"), + serviceHub), + + "File" => new ParseFile( + dictionary.TryGetValue("name", out var name) ? name as string : throw new KeyNotFoundException("Missing 'name' for File type"), + new Uri(dictionary.TryGetValue("url", out var url) ? url as string : throw new KeyNotFoundException("Missing 'url' for File type"))), + + "GeoPoint" => new ParseGeoPoint( + Conversion.To(dictionary.TryGetValue("latitude", out var latitude) ? latitude : throw new KeyNotFoundException("Missing 'latitude' for GeoPoint type")), + Conversion.To(dictionary.TryGetValue("longitude", out var longitude) ? longitude : throw new KeyNotFoundException("Missing 'longitude' for GeoPoint type"))), + + "Object" => ClassController.GenerateObjectFromState( + ParseObjectCoder.Instance.Decode(dictionary, this, serviceHub), + dictionary.TryGetValue("className", out var objClassName) ? objClassName as string : throw new KeyNotFoundException("Missing 'className' for Object type"), + serviceHub), + + "Relation" => serviceHub.CreateRelation(null, null, dictionary.TryGetValue("className", out var relClassName) ? relClassName as string : throw new KeyNotFoundException("Missing 'className' for Relation type")), + _ => throw new NotSupportedException($"Unsupported Parse type '{type}' encountered") + }, + + IDictionary { } dictionary => dictionary.ToDictionary(pair => pair.Key, pair => Decode(pair.Value, serviceHub)), + IList { } list => list.Select(item => Decode(item, serviceHub)).ToList(), + _ => data + }; + } protected virtual object DecodePointer(string className, string objectId, IServiceHub serviceHub) => diff --git a/Parse/Infrastructure/Data/ParseDataEncoder.cs b/Parse/Infrastructure/Data/ParseDataEncoder.cs index ef39b50f..7caa7d22 100644 --- a/Parse/Infrastructure/Data/ParseDataEncoder.cs +++ b/Parse/Infrastructure/Data/ParseDataEncoder.cs @@ -3,8 +3,10 @@ using System.Diagnostics; using System.Globalization; using System.Linq; +using System.Runtime.ExceptionServices; using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Infrastructure.Data; using Parse.Infrastructure.Control; using Parse.Infrastructure.Utilities; @@ -22,7 +24,7 @@ public abstract class ParseDataEncoder public static bool Validate(object value) { return value is null || - value.GetType().IsPrimitive|| + value.GetType().IsPrimitive || value is string || value is ParseObject || value is ParseACL || @@ -49,9 +51,10 @@ public object Encode(object value, IServiceHub serviceHub) { if (value == null) return null; - return value switch { + // Primitive types or strings + _ when value.GetType().IsPrimitive || value is string => value, // DateTime encoding DateTime date => EncodeDate(date), @@ -62,25 +65,17 @@ public object Encode(object value, IServiceHub serviceHub) ParseObject entity => EncodeObject(entity), // JSON-convertible types + ParseSetOperation setOperation => setOperation.ConvertValueToJSON(serviceHub), IJsonConvertible jsonConvertible => jsonConvertible.ConvertToJSON(serviceHub), // Dictionary encoding IDictionary dictionary => EncodeDictionary(dictionary, serviceHub), - IDictionary dictionary => EncodeDictionary(dictionary, serviceHub), - IDictionary dictionary => EncodeDictionary(dictionary, serviceHub), - IDictionary dictionary => EncodeDictionary(dictionary, serviceHub), - IDictionary dictionary => EncodeDictionary(dictionary, serviceHub), - IDictionary dictionary => EncodeDictionary(dictionary, serviceHub), - + IDictionary> dictionary => EncodeDictionaryStringDict(dictionary, serviceHub), // List or array encoding IEnumerable list => EncodeList(list, serviceHub), Array array => EncodeList(array.Cast(), serviceHub), - // Parse field operations - - // Primitive types or strings - _ when value.GetType().IsPrimitive || value is string => value, // Unsupported types _ => throw new ArgumentException($"Unsupported type for encoding: {value?.GetType()?.FullName}") @@ -118,14 +113,13 @@ private static IDictionary EncodeBytes(byte[] bytes) }; } - //// /// Encodes a dictionary into a JSON-compatible structure. /// private object EncodeDictionary(IDictionary dictionary, IServiceHub serviceHub) { var encodedDictionary = new Dictionary(); - if (dictionary.Count<1) + if (dictionary.Count < 1) { return encodedDictionary; } @@ -147,59 +141,23 @@ private object EncodeDictionary(IDictionary dictionary, IService return encodedDictionary; } - - // Add a specialized method to handle string-only dictionaries - private object EncodeDictionary(IDictionary dictionary, IServiceHub serviceHub) - { - - return dictionary.ToDictionary( - pair => pair.Key, - pair => Encode(pair.Value, serviceHub) // Encode string values as object - ); - } - - // Add a specialized method to handle int-only dictionaries - private object EncodeDictionary(IDictionary dictionary, IServiceHub serviceHub) - { - - - return dictionary.ToDictionary( - pair => pair.Key, - pair => Encode(pair.Value, serviceHub) // Encode int values as object - ); - } - - // Add a specialized method to handle long-only dictionaries - private object EncodeDictionary(IDictionary dictionary, IServiceHub serviceHub) - { - - - return dictionary.ToDictionary( - pair => pair.Key, - pair => Encode(pair.Value, serviceHub) // Encode long values as object - ); - } - - // Add a specialized method to handle float-only dictionaries - private object EncodeDictionary(IDictionary dictionary, IServiceHub serviceHub) + // Add a specialized method to handle double-only dictionaries + private object EncodeDictionaryStringDict(IDictionary> dictionary, IServiceHub serviceHub) { - - return dictionary.ToDictionary( - pair => pair.Key, - pair => Encode(pair.Value, serviceHub) // Encode float values as object - ); - } + pair => pair.Key, + pair => + { + // If the value is another dictionary, recursively process it + if (pair.Value is IDictionary nestedDict) + { + return EncodeDictionary(nestedDict, serviceHub); + } - // Add a specialized method to handle double-only dictionaries - private object EncodeDictionary(IDictionary dictionary, IServiceHub serviceHub) - { - + // Return the actual value as-is + return pair.Value; + }); - return dictionary.ToDictionary( - pair => pair.Key, - pair => Encode(pair.Value, serviceHub) // Encode double values as object - ); } @@ -209,7 +167,7 @@ private object EncodeDictionary(IDictionary dictionary, IService /// private object EncodeList(IEnumerable list, IServiceHub serviceHub) { - + List encoded = new(); foreach (var item in list) @@ -231,19 +189,4 @@ private object EncodeList(IEnumerable list, IServiceHub serviceHub) return encoded; } - - - - /// - /// Encodes a field operation into a JSON-compatible structure. - /// - private object EncodeFieldOperation(IParseFieldOperation fieldOperation, IServiceHub serviceHub) - { - if (fieldOperation is IJsonConvertible jsonConvertible) - { - return jsonConvertible.ConvertToJSON(); - } - - throw new InvalidOperationException($"Cannot encode field operation of type {fieldOperation.GetType().Name}."); - } } diff --git a/Parse/Infrastructure/Execution/ParseCommand.cs b/Parse/Infrastructure/Execution/ParseCommand.cs index f2db1772..4e30c68d 100644 --- a/Parse/Infrastructure/Execution/ParseCommand.cs +++ b/Parse/Infrastructure/Execution/ParseCommand.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -20,11 +21,12 @@ public override Stream Data get { if (DataObject is { }) - return base.Data ??= (new MemoryStream(Encoding.UTF8.GetBytes(JsonUtilities.Encode(DataObject)))); - else - return base.Data ??= default; + { + // Dynamically generate the stream from DataObject on every access + return new MemoryStream(Encoding.UTF8.GetBytes(JsonUtilities.Encode(DataObject))); + } + return base.Data; } - set => base.Data = value; } @@ -41,12 +43,12 @@ public ParseCommand(string relativeUri, string method, string sessionToken = nul Data = stream; Headers = new List>(headers ?? Enumerable.Empty>()); - if (!String.IsNullOrEmpty(sessionToken)) + if (!string.IsNullOrEmpty(sessionToken)) { Headers.Add(new KeyValuePair("X-Parse-Session-Token", sessionToken)); } - if (!String.IsNullOrEmpty(contentType)) + if (!string.IsNullOrEmpty(contentType)) { Headers.Add(new KeyValuePair("Content-Type", contentType)); } diff --git a/Parse/Infrastructure/Execution/ParseCommandRunner.cs b/Parse/Infrastructure/Execution/ParseCommandRunner.cs index 630ed81a..f8305ce7 100644 --- a/Parse/Infrastructure/Execution/ParseCommandRunner.cs +++ b/Parse/Infrastructure/Execution/ParseCommandRunner.cs @@ -71,16 +71,13 @@ public async Task>> RunCommand IDictionary contentJson = null; // Extract response var statusCode = response.Item1; - var content = response.Item2; + var content = response.Item2; var responseCode = (int) statusCode; if (responseCode == 200) { - - } - else if (responseCode == 201) - { + } else if (responseCode == 404) { diff --git a/Parse/Infrastructure/Execution/UniversalWebClient.cs b/Parse/Infrastructure/Execution/UniversalWebClient.cs index 656af000..fcdade93 100644 --- a/Parse/Infrastructure/Execution/UniversalWebClient.cs +++ b/Parse/Infrastructure/Execution/UniversalWebClient.cs @@ -49,10 +49,11 @@ public async Task> ExecuteAsync( HttpRequestMessage message = new HttpRequestMessage(new HttpMethod(httpRequest.Method), httpRequest.Target); - Stream data = httpRequest.Data; - if (data != null || httpRequest.Method.Equals("POST", StringComparison.OrdinalIgnoreCase)) + if ((httpRequest.Data is null && httpRequest.Method.ToLower().Equals("post") + ? new MemoryStream(new byte[0]) + : httpRequest.Data) is Stream { } data) { - message.Content = new StreamContent(data ?? new MemoryStream(new byte[0])); + message.Content = new StreamContent(data); } if (httpRequest.Headers != null) diff --git a/Parse/Utilities/ObjectServiceExtensions.cs b/Parse/Utilities/ObjectServiceExtensions.cs index 4f7a8320..70e6d954 100644 --- a/Parse/Utilities/ObjectServiceExtensions.cs +++ b/Parse/Utilities/ObjectServiceExtensions.cs @@ -91,7 +91,7 @@ public static T CreateObject(this IServiceHub serviceHub) where T : ParseObje /// A new ParseObject for the given class name. public static T CreateObject(this IParseObjectClassController classController, IServiceHub serviceHub) where T : ParseObject { - + return (T) classController.Instantiate(classController.GetClassName(typeof(T)), serviceHub); } @@ -123,6 +123,8 @@ public static ParseObject CreateObjectWithoutData(this IParseObjectClassControll ParseObject.CreatingPointer.Value = true; try { + + ParseObject result = classController.Instantiate(className, serviceHub); result.ObjectId = objectId; @@ -130,9 +132,18 @@ public static ParseObject CreateObjectWithoutData(this IParseObjectClassControll result.IsDirty = false; if (result.IsDirty) + { throw new InvalidOperationException("A ParseObject subclass default constructor must not make changes to the object that cause it to be dirty."); + } else - return result; + return result; + + } + catch (Exception ex) + { + + Debug.WriteLine("Exception " + ex.Message); + throw new Exception(ex.Message); } finally { @@ -303,7 +314,7 @@ public static Task SaveObjectsAsync(this IServiceHub serviceHub, IEnumerable< /// The cancellation token. public static async Task SaveObjectsAsync(this IServiceHub serviceHub, IEnumerable objects, CancellationToken cancellationToken) where T : ParseObject { - _ = DeepSaveAsync(serviceHub, objects.ToList(),await serviceHub.GetCurrentSessionToken(), cancellationToken); + _ = DeepSaveAsync(serviceHub, objects.ToList(), await serviceHub.GetCurrentSessionToken(), cancellationToken); } /// @@ -337,21 +348,21 @@ internal static T GenerateObjectFromState( { throw new ArgumentNullException(nameof(state), "The state cannot be null."); } - + // Ensure the class name is determined or throw an exception string className = state.ClassName ?? defaultClassName; if (string.IsNullOrEmpty(className)) { - + throw new InvalidOperationException("Both state.ClassName and defaultClassName are null or empty. Unable to determine class name."); } - + // Create the object using the class controller T obj = classController.Instantiate(className, serviceHub) as T; - + if (obj == null) { - + throw new InvalidOperationException($"Failed to instantiate object of type {typeof(T).Name} for class {className}."); } @@ -361,20 +372,22 @@ internal static T GenerateObjectFromState( return obj; } - - internal static IDictionary GenerateJSONObjectForSaving(this IServiceHub serviceHub, IDictionary operations) + internal static IDictionary GenerateJSONObjectForSaving( + this IServiceHub serviceHub, IDictionary operations) { Dictionary result = new Dictionary(); foreach (KeyValuePair pair in operations) { - //result[pair.Key] = PointerOrLocalIdEncoder.Instance.Encode(pair.Value, serviceHub); - result[pair.Key] = PointerOrLocalIdEncoder.Instance.Encode(pair.Value.Value, serviceHub); + var s = PointerOrLocalIdEncoder.Instance.Encode(pair.Value, serviceHub); + + result[pair.Key] = s; } return result; } + /// /// Returns true if the given object can be serialized for saving as a value /// that is pointed to by a ParseObject. @@ -438,7 +451,7 @@ static void CollectDirtyChildren(this IServiceHub serviceHub, object node, IList { CollectDirtyChildren(serviceHub, node, dirtyChildren, new HashSet(new IdentityEqualityComparer()), new HashSet(new IdentityEqualityComparer())); } - + internal static async Task DeepSaveAsync(this IServiceHub serviceHub, object target, string sessionToken, CancellationToken cancellationToken) { // Collect dirty objects @@ -458,14 +471,14 @@ internal static async Task DeepSaveAsync(this IServiceHub serviceHub, object tar // Save remaining objects in batches var remaining = new List(uniqueObjects); - while (remaining.Count>0) + while (remaining.Count > 0) { // Partition objects into those that can be saved immediately and those that cannot var current = remaining.Where(item => item.CanBeSerialized).ToList(); var nextBatch = remaining.Where(item => !item.CanBeSerialized).ToList(); remaining = nextBatch; - if (current.Count<1) + if (current.Count < 1) { throw new InvalidOperationException("Unable to save a ParseObject with a relation to a cycle."); }