From ad37f3e6017537dbd5a32a4c646c0966a99f05cb Mon Sep 17 00:00:00 2001 From: Yvan Brunel <41630728+YBTopaz8@users.noreply.github.com> Date: Sun, 2 Feb 2025 11:12:36 -0500 Subject: [PATCH 1/4] This Aims at fixing all issues with Relations. - Changed the return time of `ConvertToJSON` to return "object" as we now use that one method to pass both primitive and complex datatypes like Dictionnary for Relations. - Implemented ConvertToJSON in ParseRelationOperations which now fixes Relations completely (Before, the related object would save but its pointer was never referenced) --- Parse.Tests/EncoderTests.cs | 7 - Parse.Tests/RelationTests.cs | 350 +++++++++++++++++- .../Infrastructure/IJsonConvertible.cs | 3 +- .../Control/ParseAddOperation.cs | 2 +- .../Control/ParseAddUniqueOperation.cs | 2 +- .../Control/ParseDeleteOperation.cs | 2 +- .../Control/ParseIncrementOperation.cs | 2 +- .../Control/ParseRelationOperation.cs | 58 ++- .../Control/ParseRemoveOperation.cs | 2 +- .../Control/ParseSetOperation.cs | 4 +- .../Execution/ParseCommandRunner.cs | 6 +- .../Execution/UniversalWebClient.cs | 64 ++-- .../Configuration/ParseConfiguration.cs | 2 +- .../ParseCurrentConfigurationController.cs | 5 +- Parse/Platform/Files/ParseFile.cs | 2 +- Parse/Platform/Location/ParseGeoPoint.cs | 2 +- Parse/Platform/Relations/ParseRelation.cs | 2 +- Parse/Platform/Security/ParseACL.cs | 2 +- 18 files changed, 430 insertions(+), 87 deletions(-) diff --git a/Parse.Tests/EncoderTests.cs b/Parse.Tests/EncoderTests.cs index 6cd103a3..ea390dc9 100644 --- a/Parse.Tests/EncoderTests.cs +++ b/Parse.Tests/EncoderTests.cs @@ -76,13 +76,6 @@ public void TestEncodeBytes() Assert.AreEqual(Convert.ToBase64String(new byte[] { 1, 2, 3, 4 }), value["base64"]); } - [TestMethod] - public void TestEncodeParseObjectWithNoObjectsEncoder() - { - ParseObject obj = new ParseObject("Corgi"); - - Assert.ThrowsException(() => NoObjectsEncoder.Instance.Encode(obj, Client)); - } [TestMethod] public void TestEncodeParseObjectWithPointerOrLocalIdEncoder() diff --git a/Parse.Tests/RelationTests.cs b/Parse.Tests/RelationTests.cs index c1a362cf..b8bc3f0b 100644 --- a/Parse.Tests/RelationTests.cs +++ b/Parse.Tests/RelationTests.cs @@ -1,13 +1,85 @@ +using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Internal; +using Parse.Abstractions.Platform.Objects; using Parse.Infrastructure; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Parse.Platform.Objects; +using System.Threading; +using Parse.Abstractions.Platform.Users; namespace Parse.Tests; [TestClass] public class RelationTests { + [ParseClassName("TestObject")] + private class TestObject : ParseObject { } + + [ParseClassName("Friend")] + private class Friend : ParseObject { } + + private ParseClient Client { get; set; } + + [TestInitialize] + public void SetUp() + { + // Initialize the client and ensure the instance is set + Client = new ParseClient(new ServerConnectionData { Test = true }); + Client.Publicize(); + + // Register the test classes + Client.RegisterSubclass(typeof(TestObject)); + Client.RegisterSubclass(typeof(Friend)); + Client.RegisterSubclass(typeof(ParseUser)); + Client.RegisterSubclass(typeof(ParseSession)); + Client.RegisterSubclass(typeof(ParseUser)); + + // **--- Mocking Setup ---** + var hub = new MutableServiceHub(); // Use MutableServiceHub for mocking + var mockUserController = new Mock(); + var mockObjectController = new Mock(); + + // **Mock SignUpAsync for ParseUser:** + mockUserController + .Setup(controller => controller.SignUpAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(new MutableObjectState { ObjectId = "some0neTol4v4" }); // Predefined ObjectId for User + + // **Mock SaveAsync for ParseObject (Friend objects):** + int objectSaveCounter = 1; // Counter for Friend ObjectIds + mockObjectController + .Setup(controller => controller.SaveAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(() => // Use a lambda to generate different ObjectIds for each Friend + { + return new MutableObjectState { ObjectId = $"mockFriendObjectId{objectSaveCounter++}" }; + }); + + // **Inject Mocks into ServiceHub:** + hub.UserController = mockUserController.Object; + hub.ObjectController = mockObjectController.Object; + //(Client.Services as ServiceHub)..ReplaceWith(hub); // Replace the Client's ServiceHub with the MutableServiceHub + + } + + [TestCleanup] + public void TearDown() => (Client.Services as ServiceHub).Reset(); + [TestMethod] public void TestRelationQuery() { @@ -24,4 +96,280 @@ public void TestRelationQuery() Assert.AreEqual("child", encoded["redirectClassNameForKey"]); } -} \ No newline at end of file + + [TestMethod] + [Description("Tests AddRelationToUserAsync throws exception when user is null")] // Mock difficulty: 1 + public async Task AddRelationToUserAsync_ThrowsException_WhenUserIsNull() + { + + var relatedObjects = new List + { + new ParseObject("Friend", Client.Services) { ["name"] = "Friend1" } + }; + + await Assert.ThrowsExceptionAsync(() => UserManagement.AddRelationToUserAsync(null, "friends", relatedObjects)); + + } + [TestMethod] + [Description("Tests AddRelationToUserAsync throws exception when relationfield is null")] // Mock difficulty: 1 + public async Task AddRelationToUserAsync_ThrowsException_WhenRelationFieldIsNull() + { + var user = new ParseUser() { Username = "TestUser", Password = "TestPass", Services = Client.Services }; + await user.SignUpAsync(); + var relatedObjects = new List + { + new ParseObject("Friend", Client.Services) { ["name"] = "Friend1" } + }; + await Assert.ThrowsExceptionAsync(() => UserManagement.AddRelationToUserAsync(user, null, relatedObjects)); + } + + [TestMethod] + [Description("Tests UpdateUserRelationAsync throws exception when user is null")] // Mock difficulty: 1 + public async Task UpdateUserRelationAsync_ThrowsException_WhenUserIsNull() + { + var relatedObjectsToAdd = new List + { + new ParseObject("Friend", Client.Services) { ["name"] = "Friend1" } + }; + var relatedObjectsToRemove = new List + { + new ParseObject("Friend", Client.Services) { ["name"] = "Friend2" } + }; + + + await Assert.ThrowsExceptionAsync(() => UserManagement.UpdateUserRelationAsync(null, "friends", relatedObjectsToAdd, relatedObjectsToRemove)); + } + [TestMethod] + [Description("Tests UpdateUserRelationAsync throws exception when relationfield is null")] // Mock difficulty: 1 + public async Task UpdateUserRelationAsync_ThrowsException_WhenRelationFieldIsNull() + { + var user = new ParseUser() { Username = "TestUser", Password = "TestPass", Services = Client.Services }; + await user.SignUpAsync(); + + var relatedObjectsToAdd = new List + { + new ParseObject("Friend", Client.Services) { ["name"] = "Friend1" } + }; + var relatedObjectsToRemove = new List + { + new ParseObject("Friend", Client.Services) { ["name"] = "Friend2" } + }; + + + await Assert.ThrowsExceptionAsync(() => UserManagement.UpdateUserRelationAsync(user, null, relatedObjectsToAdd, relatedObjectsToRemove)); + } + [TestMethod] + [Description("Tests DeleteUserRelationAsync throws exception when user is null")] // Mock difficulty: 1 + public async Task DeleteUserRelationAsync_ThrowsException_WhenUserIsNull() + { + await Assert.ThrowsExceptionAsync(() => UserManagement.DeleteUserRelationAsync(null, "friends")); + } + [TestMethod] + [Description("Tests DeleteUserRelationAsync throws exception when relationfield is null")] // Mock difficulty: 1 + public async Task DeleteUserRelationAsync_ThrowsException_WhenRelationFieldIsNull() + { + var user = new ParseUser() { Username = "TestUser", Password = "TestPass", Services = Client.Services }; + await user.SignUpAsync(); + + await Assert.ThrowsExceptionAsync(() => UserManagement.DeleteUserRelationAsync(user, null)); + } + [TestMethod] + [Description("Tests GetUserRelationsAsync throws exception when user is null")] // Mock difficulty: 1 + public async Task GetUserRelationsAsync_ThrowsException_WhenUserIsNull() + { + await Assert.ThrowsExceptionAsync(() => UserManagement.GetUserRelationsAsync(null, "friends")); + } + [TestMethod] + [Description("Tests GetUserRelationsAsync throws exception when relationfield is null")] // Mock difficulty: 1 + public async Task GetUserRelationsAsync_ThrowsException_WhenRelationFieldIsNull() + { + var user = new ParseUser() { Username = "TestUser", Password = "TestPass", Services = Client.Services }; + await user.SignUpAsync(); + + await Assert.ThrowsExceptionAsync(() => UserManagement.GetUserRelationsAsync(user, null)); + } + + + + [TestMethod] + [Description("Tests that AddRelationToUserAsync throws when a related object is unsaved")] + public async Task AddRelationToUserAsync_ThrowsException_WhenRelatedObjectIsUnsaved() + { + // Arrange: Create and sign up a test user. + var user = new ParseUser() { Username = "TestUser", Password = "TestPass", Services = Client.Services }; + await user.SignUpAsync(); + + // Create an unsaved Friend object (do NOT call SaveAsync). + var unsavedFriend = new ParseObject("Friend", Client.Services) { ["name"] = "UnsavedFriend" }; + var relatedObjects = new List { unsavedFriend }; + + // Act & Assert: Expect an exception when trying to add an unsaved object. + await Assert.ThrowsExceptionAsync(() => + UserManagement.AddRelationToUserAsync(user, "friends", relatedObjects)); + } + + + +} + +public static class UserManagement +{ + public static async Task AddRelationToUserAsync(ParseUser user, string relationField, IList relatedObjects) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user), "User must not be null."); + } + + if (string.IsNullOrEmpty(relationField)) + { + throw new ArgumentException("Relation field must not be null or empty.", nameof(relationField)); + } + + if (relatedObjects == null || relatedObjects.Count == 0) + { + Debug.WriteLine("No objects provided to add to the relation."); + return; + } + + var relation = user.GetRelation(relationField); + + foreach (var obj in relatedObjects) + { + relation.Add(obj); + } + + await user.SaveAsync(); + Debug.WriteLine($"Added {relatedObjects.Count} objects to the '{relationField}' relation for user '{user.Username}'."); + } + public static async Task UpdateUserRelationAsync(ParseUser user, string relationField, IList toAdd, IList toRemove) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user), "User must not be null."); + } + + if (string.IsNullOrEmpty(relationField)) + { + throw new ArgumentException("Relation field must not be null or empty.", nameof(relationField)); + } + + var relation = user.GetRelation(relationField); + + // Add objects to the relation + if (toAdd != null && toAdd.Count > 0) + { + foreach (var obj in toAdd) + { + relation.Add(obj); + } + Debug.WriteLine($"Added {toAdd.Count} objects to the '{relationField}' relation."); + } + + // Remove objects from the relation + if (toRemove != null && toRemove.Count > 0) + { + + foreach (var obj in toRemove) + { + relation.Remove(obj); + } + Debug.WriteLine($"Removed {toRemove.Count} objects from the '{relationField}' relation."); + } + + await user.SaveAsync(); + } + public static async Task DeleteUserRelationAsync(ParseUser user, string relationField) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user), "User must not be null."); + } + + if (string.IsNullOrEmpty(relationField)) + { + throw new ArgumentException("Relation field must not be null or empty.", nameof(relationField)); + } + + var relation = user.GetRelation(relationField); + var relatedObjects = await relation.Query.FindAsync(); + + + foreach (var obj in relatedObjects) + { + relation.Remove(obj); + } + + await user.SaveAsync(); + Debug.WriteLine($"Removed all objects from the '{relationField}' relation for user '{user.Username}'."); + } + public static async Task ManageUserRelationsAsync(ParseClient client) + { + // Get the current user + var user = await ParseClient.Instance.GetCurrentUser(); + + if (user == null) + { + Debug.WriteLine("No user is currently logged in."); + return; + } + + const string relationField = "friends"; // Example relation field name + + // Create related objects to add + var relatedObjectsToAdd = new List + { + new ParseObject("Friend", client.Services) { ["name"] = "Alice" }, + new ParseObject("Friend", client.Services) { ["name"] = "Bob" } + }; + + // Save related objects to the server before adding to the relation + foreach (var obj in relatedObjectsToAdd) + { + await obj.SaveAsync(); + } + + // Add objects to the relation + await AddRelationToUserAsync(user, relationField, relatedObjectsToAdd); + + // Query the relation + var relatedObjects = await GetUserRelationsAsync(user, relationField); + + // Update the relation (add and remove objects) + var relatedObjectsToRemove = new List { relatedObjects[0] }; // Remove the first related object + var newObjectsToAdd = new List + { + new ParseObject("Friend", client.Services) { ["name"] = "Charlie" } + }; + + foreach (var obj in newObjectsToAdd) + { + await obj.SaveAsync(); + } + + await UpdateUserRelationAsync(user, relationField, newObjectsToAdd, relatedObjectsToRemove); + + // Delete the relation + // await DeleteUserRelationAsync(user, relationField); + } + public static async Task> GetUserRelationsAsync(ParseUser user, string relationField) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user), "User must not be null."); + } + + if (string.IsNullOrEmpty(relationField)) + { + throw new ArgumentException("Relation field must not be null or empty.", nameof(relationField)); + } + + var relation = user.GetRelation(relationField); + + var results = await relation.Query.FindAsync(); + Debug.WriteLine($"Retrieved {results.Count()} objects from the '{relationField}' relation for user '{user.Username}'."); + return results.ToList(); + } + +} + diff --git a/Parse/Abstractions/Infrastructure/IJsonConvertible.cs b/Parse/Abstractions/Infrastructure/IJsonConvertible.cs index 954f626e..e5d0db0a 100644 --- a/Parse/Abstractions/Infrastructure/IJsonConvertible.cs +++ b/Parse/Abstractions/Infrastructure/IJsonConvertible.cs @@ -11,5 +11,6 @@ public interface IJsonConvertible /// Converts the object to a data structure that can be converted to JSON. /// /// An object to be JSONified. - IDictionary ConvertToJSON(IServiceHub serviceHub=default); + + object ConvertToJSON(IServiceHub serviceHub=default); } diff --git a/Parse/Infrastructure/Control/ParseAddOperation.cs b/Parse/Infrastructure/Control/ParseAddOperation.cs index 164503b5..7f62a142 100644 --- a/Parse/Infrastructure/Control/ParseAddOperation.cs +++ b/Parse/Infrastructure/Control/ParseAddOperation.cs @@ -49,7 +49,7 @@ public object Apply(object oldValue, string key) return result; } - public IDictionary ConvertToJSON(IServiceHub serviceHub = default) + public object ConvertToJSON(IServiceHub serviceHub = default) { // Convert the data into JSON-compatible structures var encodedObjects = Data.Select(EncodeForParse).ToList(); diff --git a/Parse/Infrastructure/Control/ParseAddUniqueOperation.cs b/Parse/Infrastructure/Control/ParseAddUniqueOperation.cs index 3c6cad30..a63c9133 100644 --- a/Parse/Infrastructure/Control/ParseAddUniqueOperation.cs +++ b/Parse/Infrastructure/Control/ParseAddUniqueOperation.cs @@ -55,7 +55,7 @@ public object Apply(object oldValue, string key) return result; } - public IDictionary ConvertToJSON(IServiceHub serviceHub = default) + public object ConvertToJSON(IServiceHub serviceHub = default) { // Converts the data into JSON-compatible structures var encodedObjects = Data.Select(EncodeForParse).ToList(); diff --git a/Parse/Infrastructure/Control/ParseDeleteOperation.cs b/Parse/Infrastructure/Control/ParseDeleteOperation.cs index c074e616..56e12649 100644 --- a/Parse/Infrastructure/Control/ParseDeleteOperation.cs +++ b/Parse/Infrastructure/Control/ParseDeleteOperation.cs @@ -18,7 +18,7 @@ public class ParseDeleteOperation : IParseFieldOperation private ParseDeleteOperation() { } // Replaced Encode with ConvertToJSON - public IDictionary ConvertToJSON(IServiceHub serviceHub = default) + public object ConvertToJSON(IServiceHub serviceHub = default) { return new Dictionary { diff --git a/Parse/Infrastructure/Control/ParseIncrementOperation.cs b/Parse/Infrastructure/Control/ParseIncrementOperation.cs index 036ef5ae..f2271e87 100644 --- a/Parse/Infrastructure/Control/ParseIncrementOperation.cs +++ b/Parse/Infrastructure/Control/ParseIncrementOperation.cs @@ -97,7 +97,7 @@ static ParseIncrementOperation() public ParseIncrementOperation(object amount) => Amount = amount; // Updated Encode to ConvertToJSON - public IDictionary ConvertToJSON(IServiceHub serviceHub = default) + public object ConvertToJSON(IServiceHub serviceHub = default) { // Updated to produce a JSON-compatible structure return new Dictionary diff --git a/Parse/Infrastructure/Control/ParseRelationOperation.cs b/Parse/Infrastructure/Control/ParseRelationOperation.cs index 7960bfbd..c489a592 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 @@ -88,8 +61,8 @@ public object Apply(object oldValue, string key) } public string TargetClassName { get; } - - public object Value => throw new NotImplementedException(); + + public object Value => Additions.ToList(); IEnumerable GetIdsFromObjects(IEnumerable objects) { @@ -109,5 +82,30 @@ IEnumerable GetIdsFromObjects(IEnumerable objects) return objects.Select(entity => entity.ObjectId).Distinct(); } - public IDictionary ConvertToJSON(IServiceHub serviceHub = null) => throw new NotImplementedException(); + public object 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/ParseRemoveOperation.cs b/Parse/Infrastructure/Control/ParseRemoveOperation.cs index 899c6002..0f280cd8 100644 --- a/Parse/Infrastructure/Control/ParseRemoveOperation.cs +++ b/Parse/Infrastructure/Control/ParseRemoveOperation.cs @@ -39,7 +39,7 @@ public object Apply(object oldValue, string key) : new List { }; // Return empty list if no previous value } - public IDictionary ConvertToJSON(IServiceHub serviceHub = default) + public object ConvertToJSON(IServiceHub serviceHub = default) { // Convert data to a JSON-compatible structure var encodedObjects = Data.Select(obj => PointerOrLocalIdEncoder.Instance.Encode(obj, serviceHub)).ToList(); diff --git a/Parse/Infrastructure/Control/ParseSetOperation.cs b/Parse/Infrastructure/Control/ParseSetOperation.cs index 4a15b367..d6c1eae1 100644 --- a/Parse/Infrastructure/Control/ParseSetOperation.cs +++ b/Parse/Infrastructure/Control/ParseSetOperation.cs @@ -14,7 +14,7 @@ public ParseSetOperation(object value) } // Replace Encode with ConvertToJSON - public IDictionary ConvertToJSON(IServiceHub serviceHub = default) + public object ConvertToJSON(IServiceHub serviceHub = default) { if (serviceHub == null) { @@ -26,7 +26,7 @@ public IDictionary ConvertToJSON(IServiceHub serviceHub = defaul // For simple values, return them directly (avoid unnecessary __op) if (Value != null && (Value.GetType().IsPrimitive || Value is string)) { - return new Dictionary { ["value"] = Value }; + return Value ; } // If the encoded value is a dictionary, return it directly diff --git a/Parse/Infrastructure/Execution/ParseCommandRunner.cs b/Parse/Infrastructure/Execution/ParseCommandRunner.cs index 630ed81a..4b616f1f 100644 --- a/Parse/Infrastructure/Execution/ParseCommandRunner.cs +++ b/Parse/Infrastructure/Execution/ParseCommandRunner.cs @@ -75,13 +75,17 @@ public async Task>> RunCommand var responseCode = (int) statusCode; - if (responseCode == 200) + if (responseCode == 200) //action successfully done { } else if (responseCode == 201) { } + else if(responseCode == 400) // Bad Request + { + //throw new ParseFailureException(ParseFailureException.ErrorCode., content); + } else if (responseCode == 404) { throw new ParseFailureException(ParseFailureException.ErrorCode.ERROR404, "Error 404"); diff --git a/Parse/Infrastructure/Execution/UniversalWebClient.cs b/Parse/Infrastructure/Execution/UniversalWebClient.cs index 656af000..4720ce59 100644 --- a/Parse/Infrastructure/Execution/UniversalWebClient.cs +++ b/Parse/Infrastructure/Execution/UniversalWebClient.cs @@ -38,6 +38,7 @@ public UniversalWebClient() : this(new BCLWebClient { }) { } public UniversalWebClient(BCLWebClient client) => Client = client; BCLWebClient Client { get; set; } + /// public async Task> ExecuteAsync( WebRequest httpRequest, IProgress uploadProgress, @@ -79,53 +80,48 @@ public async Task> ExecuteAsync( HttpResponseMessage response = await Client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken); uploadProgress.Report(new DataTransferLevel { Amount = 1 }); - Stream responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); - - + long? totalLength = response.Content.Headers.ContentLength; MemoryStream resultStream = new MemoryStream { }; - int bufferSize = 4096, bytesRead = 0; - byte[] buffer = new byte[bufferSize]; - long totalLength = -1, readSoFar = 0; - try { - totalLength = responseStream.Length; - } - catch - { - Console.WriteLine("Unsupported length..."); - }; + using (var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken)) + { + byte[] buffer = new byte[4096]; + int bytesRead; + long readSoFar = 0; + while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + await resultStream.WriteAsync(buffer, 0, bytesRead, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + readSoFar += bytesRead; - while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) - { - cancellationToken.ThrowIfCancellationRequested(); - await resultStream.WriteAsync(buffer, 0, bytesRead, cancellationToken); - cancellationToken.ThrowIfCancellationRequested(); - readSoFar += bytesRead; + if (totalLength.HasValue && totalLength > 0) + { + downloadProgress.Report(new DataTransferLevel { Amount = (double) readSoFar / totalLength.Value }); + } + - if (totalLength > -1) + } + } + + if (!totalLength.HasValue || totalLength <= 0) { - downloadProgress.Report(new DataTransferLevel { Amount = (double) readSoFar / totalLength }); + downloadProgress.Report(new DataTransferLevel { Amount = 1.0 }); // Report completion if total length is unknown } - } - responseStream.Dispose(); - if (totalLength == -1) + byte[] resultAsArray = resultStream.ToArray(); + string resultString = Encoding.UTF8.GetString(resultAsArray, 0, resultAsArray.Length); + //think of throwing better error when login fails for non verified + return new Tuple(response.StatusCode, resultString); + } + finally { - downloadProgress.Report(new DataTransferLevel { Amount = 1.0 }); + resultStream.Dispose(); } - - byte[] resultAsArray = resultStream.ToArray(); - resultStream.Dispose(); - - // Assume UTF-8 encoding. - string resultString = Encoding.UTF8.GetString(resultAsArray, 0, resultAsArray.Length); - - return new Tuple(response.StatusCode, resultString); } - } diff --git a/Parse/Platform/Configuration/ParseConfiguration.cs b/Parse/Platform/Configuration/ParseConfiguration.cs index 7cc76950..356bfd1e 100644 --- a/Parse/Platform/Configuration/ParseConfiguration.cs +++ b/Parse/Platform/Configuration/ParseConfiguration.cs @@ -106,7 +106,7 @@ public bool TryGetValue(string key, out T result) /// The value for the key. virtual public object this[string key] => Properties[key]; - public IDictionary ConvertToJSON(IServiceHub serviceHub = default) + public object ConvertToJSON(IServiceHub serviceHub = default) { return new Dictionary { diff --git a/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs b/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs index 70717e75..8ece93d6 100644 --- a/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs +++ b/Parse/Platform/Configuration/ParseCurrentConfigurationController.cs @@ -2,6 +2,7 @@ using Parse.Abstractions.Infrastructure.Data; using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Platform.Configuration; +using System.Collections.Generic; namespace Parse.Platform.Configuration; @@ -42,7 +43,9 @@ public async Task SetCurrentConfigAsync(ParseConfiguration target) _currentConfiguration = target; var data = await _storageController.LoadAsync(); - await data.AddAsync(CurrentConfigurationKey, ParseClient.SerializeJsonString(((IJsonConvertible) target).ConvertToJSON())); + var jsonData = ((IJsonConvertible) target).ConvertToJSON() as IDictionary; + + await data.AddAsync(CurrentConfigurationKey, ParseClient.SerializeJsonString(jsonData)); } public async Task ClearCurrentConfigAsync() diff --git a/Parse/Platform/Files/ParseFile.cs b/Parse/Platform/Files/ParseFile.cs index 3559512c..f86a113f 100644 --- a/Parse/Platform/Files/ParseFile.cs +++ b/Parse/Platform/Files/ParseFile.cs @@ -165,7 +165,7 @@ public ParseFile(string name, Stream data, string mimeType = null) #endregion - public IDictionary ConvertToJSON(IServiceHub serviceHub = default) + public object ConvertToJSON(IServiceHub serviceHub = default) { if (IsDirty) { diff --git a/Parse/Platform/Location/ParseGeoPoint.cs b/Parse/Platform/Location/ParseGeoPoint.cs index e9aa5ead..18216f84 100644 --- a/Parse/Platform/Location/ParseGeoPoint.cs +++ b/Parse/Platform/Location/ParseGeoPoint.cs @@ -91,7 +91,7 @@ public ParseGeoDistance DistanceTo(ParseGeoPoint point) return new ParseGeoDistance(2 * Math.Asin(Math.Sqrt(a))); } - public IDictionary ConvertToJSON(IServiceHub serviceHub = default) + public object ConvertToJSON(IServiceHub serviceHub = default) { return new Dictionary { {"__type", "GeoPoint"}, diff --git a/Parse/Platform/Relations/ParseRelation.cs b/Parse/Platform/Relations/ParseRelation.cs index b0d65ee2..aca8f0d7 100644 --- a/Parse/Platform/Relations/ParseRelation.cs +++ b/Parse/Platform/Relations/ParseRelation.cs @@ -70,7 +70,7 @@ internal void Remove(ParseObject entity) TargetClassName = change.TargetClassName; } - public IDictionary ConvertToJSON(IServiceHub serviceHub = default) + public object ConvertToJSON(IServiceHub serviceHub = default) { return new Dictionary { diff --git a/Parse/Platform/Security/ParseACL.cs b/Parse/Platform/Security/ParseACL.cs index 30d3376e..3154a51a 100644 --- a/Parse/Platform/Security/ParseACL.cs +++ b/Parse/Platform/Security/ParseACL.cs @@ -105,7 +105,7 @@ public ParseACL(ParseUser owner) SetWriteAccess(owner, true); } - public IDictionary ConvertToJSON(IServiceHub serviceHub = default) + public object ConvertToJSON(IServiceHub serviceHub = default) { Dictionary result = new Dictionary(); foreach (string user in readers.Union(writers)) From edaf3b5ad6c2e6b5d540cee8344ae4e944b66a14 Mon Sep 17 00:00:00 2001 From: Yvan Brunel <41630728+YBTopaz8@users.noreply.github.com> Date: Sun, 2 Feb 2025 11:22:49 -0500 Subject: [PATCH 2/4] Reverted changes done to UWC file as they are not linked to Relations nor this PR --- .../Execution/UniversalWebClient.cs | 68 ++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/Parse/Infrastructure/Execution/UniversalWebClient.cs b/Parse/Infrastructure/Execution/UniversalWebClient.cs index 4720ce59..21f682cd 100644 --- a/Parse/Infrastructure/Execution/UniversalWebClient.cs +++ b/Parse/Infrastructure/Execution/UniversalWebClient.cs @@ -38,7 +38,6 @@ public UniversalWebClient() : this(new BCLWebClient { }) { } public UniversalWebClient(BCLWebClient client) => Client = client; BCLWebClient Client { get; set; } - /// public async Task> ExecuteAsync( WebRequest httpRequest, IProgress uploadProgress, @@ -51,7 +50,7 @@ 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 (data != null || httpRequest.Method.Equals("POST", StringComparison.OrdinalIgnoreCase)) { message.Content = new StreamContent(data ?? new MemoryStream(new byte[0])); } @@ -80,48 +79,53 @@ public async Task> ExecuteAsync( HttpResponseMessage response = await Client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken); uploadProgress.Report(new DataTransferLevel { Amount = 1 }); - long? totalLength = response.Content.Headers.ContentLength; + Stream responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); + + MemoryStream resultStream = new MemoryStream { }; + int bufferSize = 4096, bytesRead = 0; + byte[] buffer = new byte[bufferSize]; + long totalLength = -1, readSoFar = 0; + try { - using (var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken)) - { - byte[] buffer = new byte[4096]; - int bytesRead; - long readSoFar = 0; - while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) - { - cancellationToken.ThrowIfCancellationRequested(); - - await resultStream.WriteAsync(buffer, 0, bytesRead, cancellationToken); - cancellationToken.ThrowIfCancellationRequested(); - readSoFar += bytesRead; - + totalLength = responseStream.Length; + } + catch + { + Console.WriteLine("Unsupported length..."); + }; - if (totalLength.HasValue && totalLength > 0) - { - downloadProgress.Report(new DataTransferLevel { Amount = (double) readSoFar / totalLength.Value }); - } + while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) + { + cancellationToken.ThrowIfCancellationRequested(); - } - } + await resultStream.WriteAsync(buffer, 0, bytesRead, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + readSoFar += bytesRead; - if (!totalLength.HasValue || totalLength <= 0) + if (totalLength > -1) { - downloadProgress.Report(new DataTransferLevel { Amount = 1.0 }); // Report completion if total length is unknown + downloadProgress.Report(new DataTransferLevel { Amount = (double) readSoFar / totalLength }); } + } + responseStream.Dispose(); - byte[] resultAsArray = resultStream.ToArray(); - string resultString = Encoding.UTF8.GetString(resultAsArray, 0, resultAsArray.Length); - //think of throwing better error when login fails for non verified - return new Tuple(response.StatusCode, resultString); - } - finally + if (totalLength == -1) { - resultStream.Dispose(); + downloadProgress.Report(new DataTransferLevel { Amount = 1.0 }); } + + byte[] resultAsArray = resultStream.ToArray(); + resultStream.Dispose(); + + // Assume UTF-8 encoding. + string resultString = Encoding.UTF8.GetString(resultAsArray, 0, resultAsArray.Length); + + return new Tuple(response.StatusCode, resultString); } -} + +} \ No newline at end of file From 9cb2bfe9098500dadd6f0508b79f95b247e6ad1b Mon Sep 17 00:00:00 2001 From: Yvan Brunel <41630728+YBTopaz8@users.noreply.github.com> Date: Sun, 2 Feb 2025 15:29:52 -0500 Subject: [PATCH 3/4] Update ParseCommandRunner.cs --- .../Execution/ParseCommandRunner.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Parse/Infrastructure/Execution/ParseCommandRunner.cs b/Parse/Infrastructure/Execution/ParseCommandRunner.cs index 4b616f1f..5f937332 100644 --- a/Parse/Infrastructure/Execution/ParseCommandRunner.cs +++ b/Parse/Infrastructure/Execution/ParseCommandRunner.cs @@ -73,20 +73,8 @@ public async Task>> RunCommand var statusCode = response.Item1; var content = response.Item2; var responseCode = (int) statusCode; - - - if (responseCode == 200) //action successfully done - { - - } - else if (responseCode == 201) - { - } - else if(responseCode == 400) // Bad Request - { - //throw new ParseFailureException(ParseFailureException.ErrorCode., content); - } - else if (responseCode == 404) + + if (responseCode == 404) { throw new ParseFailureException(ParseFailureException.ErrorCode.ERROR404, "Error 404"); } From 0d07332082c9ef142b9c32cbe13a31daecde980b Mon Sep 17 00:00:00 2001 From: Yvan Brunel <41630728+YBTopaz8@users.noreply.github.com> Date: Sun, 2 Feb 2025 15:30:58 -0500 Subject: [PATCH 4/4] Update RelationTests.cs --- Parse.Tests/RelationTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Parse.Tests/RelationTests.cs b/Parse.Tests/RelationTests.cs index b8bc3f0b..c550e582 100644 --- a/Parse.Tests/RelationTests.cs +++ b/Parse.Tests/RelationTests.cs @@ -73,7 +73,6 @@ public void SetUp() // **Inject Mocks into ServiceHub:** hub.UserController = mockUserController.Object; hub.ObjectController = mockObjectController.Object; - //(Client.Services as ServiceHub)..ReplaceWith(hub); // Replace the Client's ServiceHub with the MutableServiceHub } @@ -349,8 +348,6 @@ public static async Task ManageUserRelationsAsync(ParseClient client) await UpdateUserRelationAsync(user, relationField, newObjectsToAdd, relatedObjectsToRemove); - // Delete the relation - // await DeleteUserRelationAsync(user, relationField); } public static async Task> GetUserRelationsAsync(ParseUser user, string relationField) {