diff --git a/Parse.Tests/ACLTests.cs b/Parse.Tests/ACLTests.cs index e6bcda16..1d3ec014 100644 --- a/Parse.Tests/ACLTests.cs +++ b/Parse.Tests/ACLTests.cs @@ -1,12 +1,12 @@ -using System; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; // Add Moq for mocking if not already added -using Parse.Infrastructure; -using Parse.Platform.Objects; +using Moq; using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Platform.Objects; - -namespace Parse.Tests; +using Parse.Infrastructure; +using Parse.Platform.Objects; +using Parse; +using System.Collections.Generic; +using System; [TestClass] public class ACLTests @@ -35,6 +35,7 @@ public void Initialize() return user; }); + // Set up ParseClient with the mocked ServiceHub Client = new ParseClient(new ServerConnectionData { Test = true }) { @@ -47,13 +48,23 @@ public void Initialize() // Add valid classes to the client Client.AddValidClass(); Client.AddValidClass(); + Client.AddValidClass(); } [TestCleanup] public void Clean() => (Client.Services as ServiceHub)?.Reset(); [TestMethod] - public void TestCheckPermissionsWithParseUserConstructor() + [Description("Tests if default ParseACL is created without errors.")] + public void TestParseACLDefaultConstructor() // 1 + { + var acl = new ParseACL(); + Assert.IsNotNull(acl); + + } + [TestMethod] + [Description("Tests ACL creation using ParseUser constructor.")] + public void TestCheckPermissionsWithParseUserConstructor() // 1 { // Arrange ParseUser owner = GenerateUser("OwnerUser"); @@ -70,7 +81,8 @@ public void TestCheckPermissionsWithParseUserConstructor() } [TestMethod] - public void TestReadWriteMutationWithParseUserConstructor() + [Description("Tests that users permission change accordingly")] + public void TestReadWriteMutationWithParseUserConstructor()// 1 { // Arrange ParseUser owner = GenerateUser("OwnerUser"); @@ -93,7 +105,8 @@ public void TestReadWriteMutationWithParseUserConstructor() } [TestMethod] - public void TestParseACLCreationWithNullObjectIdParseUser() + [Description("Tests if throws if try to instantiate using a ParseUser without objectId.")] + public void TestParseACLCreationWithNullObjectIdParseUser() // 1 { // Assert Assert.ThrowsException(() => new ParseACL(GenerateUser(default))); @@ -102,22 +115,25 @@ public void TestParseACLCreationWithNullObjectIdParseUser() ParseUser GenerateUser(string objectID) { // Use the mock to simulate generating a ParseUser - var state = new MutableObjectState { ObjectId = objectID }; + var state = new MutableObjectState { ObjectId = objectID, ClassName = "_User" }; return Client.GenerateObjectFromState(state, "_User"); + } [TestMethod] - public void TestGenerateObjectFromState() + [Description("Tests to create a ParseUser via IParseClassController, that is set when calling Bind.")] + public void TestGenerateObjectFromState() // 1 { // Arrange var state = new MutableObjectState { ObjectId = "123", ClassName = null }; var defaultClassName = "_User"; + var serviceHubMock = new Mock(); var classControllerMock = new Mock(); classControllerMock.Setup(controller => controller.Instantiate(It.IsAny(), It.IsAny())) - .Returns((className, hub) => new ParseUser()); + .Returns((className, hub) => new ParseUser()); // Act var user = classControllerMock.Object.GenerateObjectFromState(state, defaultClassName, serviceHubMock.Object); @@ -126,5 +142,137 @@ public void TestGenerateObjectFromState() Assert.IsNotNull(user); Assert.AreEqual(defaultClassName, user.ClassName); } + [TestMethod] + [Description("Tests for public read and write access values.")] + public void TestPublicReadWriteAccessValues() // 1 + { + var acl = new ParseACL(); + Assert.IsFalse(acl.PublicReadAccess); + Assert.IsFalse(acl.PublicWriteAccess); + + acl.PublicReadAccess = true; + acl.PublicWriteAccess = true; + Assert.IsTrue(acl.PublicReadAccess); + Assert.IsTrue(acl.PublicWriteAccess); + } + + [TestMethod] + [Description("Tests that sets and gets properly for string UserIds.")] + public void TestSetGetAccessWithStringId() // 1 + { + var acl = new ParseACL(); + var testUser = GenerateUser("test"); + acl.SetReadAccess(testUser.ObjectId, true); + acl.SetWriteAccess(testUser.ObjectId, true); + + Assert.IsTrue(acl.GetReadAccess(testUser.ObjectId)); + Assert.IsTrue(acl.GetWriteAccess(testUser.ObjectId)); + + acl.SetReadAccess(testUser.ObjectId, false); + acl.SetWriteAccess(testUser.ObjectId, false); + + Assert.IsFalse(acl.GetReadAccess(testUser.ObjectId)); + Assert.IsFalse(acl.GetWriteAccess(testUser.ObjectId)); + } + + [TestMethod] + [Description("Tests that methods thow exceptions if user id is null.")] + public void SetGetAccessThrowsForNull() // 1 + { + var acl = new ParseACL(); + + Assert.ThrowsException(() => acl.SetReadAccess(userId:null, false)); + Assert.ThrowsException(() => acl.SetWriteAccess(userId: null, false)); + Assert.ThrowsException(() => acl.GetReadAccess(userId:null)); + Assert.ThrowsException(() => acl.GetWriteAccess(userId:null)); + + } + [TestMethod] + [Description("Tests that a Get access using a ParseUser is correct.")] + public void TestSetGetAccessWithParseUser() // 1 + { + var acl = new ParseACL(); + ParseUser test = GenerateUser("test"); + + acl.SetReadAccess(test, true); + acl.SetWriteAccess(test, true); + Assert.IsTrue(acl.GetReadAccess(test)); + Assert.IsTrue(acl.GetWriteAccess(test)); + + acl.SetReadAccess(test, false); + acl.SetWriteAccess(test, false); + + Assert.IsFalse(acl.GetReadAccess(test)); + Assert.IsFalse(acl.GetWriteAccess(test)); + + } + + [TestMethod] + [Description("Tests that the default ParseACL returns correct roles for read/write")] + public void TestDefaultRolesForReadAndWriteAccess() // 1 + { + var acl = new ParseACL(); + Assert.IsFalse(acl.GetRoleReadAccess("role")); + Assert.IsFalse(acl.GetRoleWriteAccess("role")); + + } -} + [TestMethod] + [Description("Tests role read/write access with role names correctly and get methods.")] + public void TestSetGetRoleReadWriteAccessWithRoleName() // 1 + { + var acl = new ParseACL(); + acl.SetRoleReadAccess("test", true); + acl.SetRoleWriteAccess("test", true); + Assert.IsTrue(acl.GetRoleReadAccess("test")); + Assert.IsTrue(acl.GetRoleWriteAccess("test")); + + acl.SetRoleReadAccess("test", false); + acl.SetRoleWriteAccess("test", false); + Assert.IsFalse(acl.GetRoleReadAccess("test")); + Assert.IsFalse(acl.GetRoleWriteAccess("test")); + } + + [TestMethod] + [Description("Tests ACL can use and correctly convert to JSON object via ConvertToJSON.")] + public void TestConvertToJSON() // 3 + { + var acl = new ParseACL(); + ParseUser user = GenerateUser("test"); + + acl.SetReadAccess(user, true); + acl.SetWriteAccess(user, false); + acl.SetRoleReadAccess("test", true); + var json = (acl as IJsonConvertible).ConvertToJSON(); + Assert.IsInstanceOfType(json, typeof(IDictionary)); + + var jsonObject = json as IDictionary; + Assert.IsTrue(jsonObject.ContainsKey(user.ObjectId)); + Assert.IsTrue(jsonObject.ContainsKey("role:test")); + var test = jsonObject[user.ObjectId] as Dictionary; + Assert.AreEqual(1, test.Count); + } + + + [TestMethod] + [Description("Tests that ProcessAclData can handle invalid values for public key.")] + public void TestProcessAclData_HandlesInvalidDataForPublic() // 1 + { + var aclData = new Dictionary { { "*", 123 } }; + var acl = new ParseACL(aclData); + Assert.IsFalse(acl.PublicReadAccess); + Assert.IsFalse(acl.PublicWriteAccess); + } + [TestMethod] + [Description("Tests if ACL skips keys that don't represent valid JSON data dictionaries")] + public void TestProcessAclData_SkipsInvalidKeys() // 1 + { + var aclData = new Dictionary { + {"userId", 123 } + }; + var acl = new ParseACL(aclData); + + Assert.IsFalse(acl.GetReadAccess("userId")); + Assert.IsFalse(acl.GetWriteAccess("userId")); + } +} \ No newline at end of file diff --git a/Parse.Tests/AnalyticsTests.cs b/Parse.Tests/AnalyticsTests.cs index 730fa8f1..c8963c20 100644 --- a/Parse.Tests/AnalyticsTests.cs +++ b/Parse.Tests/AnalyticsTests.cs @@ -14,45 +14,9 @@ namespace Parse.Tests; public class AnalyticsTests { - private Mock _mockAnalyticsController; - private Mock _mockCurrentUserController; - private MutableServiceHub _hub; - private ParseClient _client; - - - [TestInitialize] - public void Initialize() - { - _mockAnalyticsController = new Mock(); - _mockCurrentUserController = new Mock(); - - _mockCurrentUserController - .Setup(controller => controller.GetCurrentSessionTokenAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync("sessionToken"); - - - _hub = new MutableServiceHub - { - AnalyticsController = _mockAnalyticsController.Object, - CurrentUserController = _mockCurrentUserController.Object - }; - _client = new ParseClient(new ServerConnectionData { Test = true }, _hub); - } - - [TestCleanup] - public void Cleanup() - { - _mockAnalyticsController = null; - _mockCurrentUserController = null; - _hub = null; - _client = null; - } - - [TestMethod] public async Task TestTrackEvent() { - // Arrange var hub = new MutableServiceHub(); var client = new ParseClient(new ServerConnectionData { Test = true }, hub); diff --git a/Parse.Tests/AttributeTests.cs b/Parse.Tests/AttributeTests.cs new file mode 100644 index 00000000..0853224e --- /dev/null +++ b/Parse.Tests/AttributeTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Infrastructure.Control; +using Parse.Infrastructure.Utilities; + +namespace Parse.Tests; + + +[TestClass] +public class AttributeTests +{ + [TestMethod] + [Description("Tests that PreserveAttribute can set its boolean properties correctly.")] + public void PreserveAttribute_SetPropertiesCorrectly() + { + var preserve = new PreserveAttribute { AllMembers = true, Conditional = true }; + Assert.IsTrue(preserve.AllMembers); + Assert.IsTrue(preserve.Conditional); + preserve.AllMembers = false; + preserve.Conditional = false; + Assert.IsFalse(preserve.AllMembers); + Assert.IsFalse(preserve.Conditional); + } + [TestMethod] + [Description("Test LinkerSafe attribute and ensures there is not exceptions on constructor.")] + public void LinkerSafeAttribute_CanBeCreatedWithoutErrors() + { + var safe = new LinkerSafeAttribute(); + Assert.IsNotNull(safe); + } + [TestMethod] + [Description("Tests that the PreserveWrapperTypes class has the Preserve attribute")] + public void PreserveWrapperTypes_HasPreserveAttribute() + { + var attribute = typeof(PreserveWrapperTypes).GetTypeInfo().GetCustomAttribute(true); + Assert.IsNotNull(attribute); + Assert.IsTrue(attribute.AllMembers); + } + + [TestMethod] + [Description("Test that types exists in the AOTPreservations List with correct types.")] + public void PreserveWrapperTypes_HasCorrectlyAOTTypesRegistered()// 1 + { + var property = typeof(PreserveWrapperTypes).GetTypeInfo().GetDeclaredProperty("AOTPreservations"); + var list = property.GetValue(null) as List; + + Assert.IsNotNull(list); + Assert.IsTrue(list.Any(p => p.Equals(typeof(FlexibleListWrapper)))); + Assert.IsTrue(list.Any(p => p.Equals(typeof(FlexibleListWrapper)))); + + Assert.IsTrue(list.Any(p => p.Equals(typeof(FlexibleDictionaryWrapper)))); + Assert.IsTrue(list.Any(p => p.Equals(typeof(FlexibleDictionaryWrapper)))); + } + + +} \ No newline at end of file diff --git a/Parse.Tests/CloudControllerTests.cs b/Parse.Tests/CloudControllerTests.cs index c26a5863..f25da4ac 100644 --- a/Parse.Tests/CloudControllerTests.cs +++ b/Parse.Tests/CloudControllerTests.cs @@ -1,4 +1,3 @@ - using System; using System.Collections.Generic; using System.Net; @@ -14,48 +13,30 @@ namespace Parse.Tests; +#warning Class refactoring requires completion. + [TestClass] public class CloudControllerTests { - private Mock _mockRunner; - private ParseCloudCodeController _cloudCodeController; - private ParseClient Client { get; set; } + ParseClient Client { get; set; } [TestInitialize] - public void SetUp() - { - Client = new ParseClient(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); - _mockRunner = new Mock(); - } - - [TestCleanup] - public void Cleanup() - { - _mockRunner = null; - _cloudCodeController = null; - Client = null; - } - + public void SetUp() => Client = new ParseClient(new ServerConnectionData { ApplicationID = "", Key = "", Test = true }); [TestMethod] public async Task TestEmptyCallFunction() { - // Arrange: Setup mock runner and controller - - _mockRunner.Setup(obj => obj.RunCommandAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny>(), - It.IsAny() - )).Returns(Task.FromResult(new Tuple>(HttpStatusCode.Accepted, null))); - - _cloudCodeController = new ParseCloudCodeController(_mockRunner.Object, Client.Decoder); + // Arrange: Create a mock runner that simulates a response with an accepted status but no data + var mockRunner = CreateMockRunner( + new Tuple>(HttpStatusCode.Accepted, null) + ); + var controller = new ParseCloudCodeController(mockRunner.Object, Client.Decoder); // Act & Assert: Call the function and verify the task faults as expected try { - await _cloudCodeController.CallFunctionAsync("someFunction", null, null, Client, CancellationToken.None); + await controller.CallFunctionAsync("someFunction", null, null, Client, CancellationToken.None); Assert.Fail("Expected the task to fault, but it succeeded."); } catch (ParseFailureException ex) @@ -63,26 +44,22 @@ public async Task TestEmptyCallFunction() Assert.AreEqual(ParseFailureException.ErrorCode.OtherCause, ex.Code); Assert.AreEqual("Cloud function returned no data.", ex.Message); } + } [TestMethod] public async Task TestCallFunction() { - // Arrange: Setup mock runner and controller with a response + // Arrange: Create a mock runner with a predefined response var responseDict = new Dictionary { ["result"] = "gogo" }; - _mockRunner.Setup(obj => obj.RunCommandAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny>(), - It.IsAny() - )).Returns(Task.FromResult(new Tuple>(HttpStatusCode.Accepted, responseDict))); - - _cloudCodeController = new ParseCloudCodeController(_mockRunner.Object, Client.Decoder); + var response = new Tuple>(HttpStatusCode.Accepted, responseDict); + var mockRunner = CreateMockRunner(response); + var cloudCodeController = new ParseCloudCodeController(mockRunner.Object, Client.Decoder); // Act: Call the function and capture the result - var result = await _cloudCodeController.CallFunctionAsync( + var result = await cloudCodeController.CallFunctionAsync( "someFunction", parameters: null, sessionToken: null, @@ -99,29 +76,24 @@ public async Task TestCallFunction() [TestMethod] public async Task TestCallFunctionWithComplexType() { - // Arrange: Setup mock runner and controller with a complex type response + // Arrange: Create a mock runner with a complex type response var complexResponse = new Dictionary { { "result", new Dictionary - { - { "fosco", "ben" }, - { "list", new List { 1, 2, 3 } } - } + { + { "fosco", "ben" }, + { "list", new List { 1, 2, 3 } } + } } }; + var mockRunner = CreateMockRunner( + new Tuple>(HttpStatusCode.Accepted, complexResponse) + ); - _mockRunner.Setup(obj => obj.RunCommandAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny>(), - It.IsAny() - )).Returns(Task.FromResult(new Tuple>(HttpStatusCode.Accepted, complexResponse))); - - _cloudCodeController = new ParseCloudCodeController(_mockRunner.Object, Client.Decoder); - + var cloudCodeController = new ParseCloudCodeController(mockRunner.Object, Client.Decoder); // Act: Call the function with a complex return type - var result = await _cloudCodeController.CallFunctionAsync>( + var result = await cloudCodeController.CallFunctionAsync>( "someFunction", parameters: null, sessionToken: null, @@ -135,32 +107,25 @@ public async Task TestCallFunctionWithComplexType() Assert.AreEqual("ben", result["fosco"]); Assert.IsInstanceOfType(result["list"], typeof(IList)); } - [TestMethod] public async Task TestCallFunctionWithWrongType() { // a mock runner with a response that doesn't match the expected type - var wrongTypeResponse = new Dictionary - { - { "result", "gogo" } - }; - - _mockRunner.Setup(obj => obj.RunCommandAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny>(), - It.IsAny() - )).Returns(Task.FromResult(new Tuple>(HttpStatusCode.Accepted, wrongTypeResponse))); - + { + { "result", "gogo" } + }; + var mockRunner = CreateMockRunner( + new Tuple>(HttpStatusCode.Accepted, wrongTypeResponse) + ); - _cloudCodeController = new ParseCloudCodeController(_mockRunner.Object, Client.Decoder); + var cloudCodeController = new ParseCloudCodeController(mockRunner.Object, Client.Decoder); // Act & Assert: Expect the call to fail with a ParseFailureException || This is fun! await Assert.ThrowsExceptionAsync(async () => { - await _cloudCodeController.CallFunctionAsync( + await cloudCodeController.CallFunctionAsync( "someFunction", parameters: null, sessionToken: null, @@ -169,4 +134,20 @@ await _cloudCodeController.CallFunctionAsync( ); }); } -} \ No newline at end of file + + + + private Mock CreateMockRunner(Tuple> response) + { + var mockRunner = new Mock(); + mockRunner.Setup(obj => obj.RunCommandAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny>(), + It.IsAny() + )).Returns(Task.FromResult(response)); + + return mockRunner; + } + +} diff --git a/Parse.Tests/CloudTests.cs b/Parse.Tests/CloudTests.cs index 3348934a..4dd64e8a 100644 --- a/Parse.Tests/CloudTests.cs +++ b/Parse.Tests/CloudTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -20,78 +19,80 @@ namespace Parse.Tests; [TestClass] public class CloudTests { - private Mock _commandRunnerMock; - private Mock _decoderMock; - private MutableServiceHub _hub; - private ParseClient _client; +#warning Skipped post-test-evaluation cleaning method may be needed. - [TestInitialize] - public void Initialize() - { - _commandRunnerMock = new Mock(); - _decoderMock = new Mock(); - } - - [TestCleanup] - public void Cleanup() + // [TestCleanup] + // public void TearDown() => ParseCorePlugins.Instance.Reset(); + [TestMethod] + public async Task TestCloudFunctionsMissingResultAsync() { - _commandRunnerMock = null; - _decoderMock = null; - _hub = null; - _client = null; - - } - - + // Arrange + var commandRunnerMock = new Mock(); + var decoderMock = new Mock(); - private void SetupMocksForMissingResult() - { - _commandRunnerMock + // Mock CommandRunner + commandRunnerMock .Setup(runner => runner.RunCommandAsync( It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny() )) - .ReturnsAsync(new Tuple>( - HttpStatusCode.OK, + .ReturnsAsync(new Tuple>( + System.Net.HttpStatusCode.OK, new Dictionary { ["unexpectedKey"] = "unexpectedValue" // Missing "result" key })); - _decoderMock - .Setup(decoder => decoder.Decode(It.IsAny(), It.IsAny())) + // Mock Decoder + decoderMock + .Setup(decoder => decoder.Decode(It.IsAny())) .Returns(new Dictionary { ["unexpectedKey"] = "unexpectedValue" }); - } - - - - [TestMethod] - public async Task TestCloudFunctionsMissingResultAsync() - { - // Arrange - SetupMocksForMissingResult(); - _hub = new MutableServiceHub + // Set up service hub + var hub = new MutableServiceHub { - CommandRunner = _commandRunnerMock.Object, - CloudCodeController = new ParseCloudCodeController(_commandRunnerMock.Object, _decoderMock.Object) + CommandRunner = commandRunnerMock.Object, + CloudCodeController = new ParseCloudCodeController(commandRunnerMock.Object, decoderMock.Object) }; - _client = new ParseClient(new ServerConnectionData { Test = true }, _hub); + var client = new ParseClient(new ServerConnectionData { Test = true }, hub); // Act & Assert await Assert.ThrowsExceptionAsync(async () => - await _client.CallCloudCodeFunctionAsync>("someFunction", null, CancellationToken.None)); + await client.CallCloudCodeFunctionAsync>("someFunction", null, CancellationToken.None)); } [TestMethod] public async Task TestParseCloudCodeControllerMissingResult() { - //Arrange - SetupMocksForMissingResult(); - var controller = new ParseCloudCodeController(_commandRunnerMock.Object, _decoderMock.Object); + // Arrange + var commandRunnerMock = new Mock(); + var decoderMock = new Mock(); + + // Mock the CommandRunner response + commandRunnerMock + .Setup(runner => runner.RunCommandAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny>(), + It.IsAny() + )) + .ReturnsAsync(new Tuple>( + System.Net.HttpStatusCode.OK, // Simulated HTTP status code + new Dictionary + { + ["unexpectedKey"] = "unexpectedValue" // Missing "result" key + })); + + // Mock the Decoder response + decoderMock + .Setup(decoder => decoder.Decode(It.IsAny())) + .Returns(new Dictionary { ["unexpectedKey"] = "unexpectedValue" }); + + // Initialize the controller + var controller = new ParseCloudCodeController(commandRunnerMock.Object, decoderMock.Object); // Act & Assert await Assert.ThrowsExceptionAsync(async () => @@ -102,4 +103,7 @@ await controller.CallFunctionAsync>( null, CancellationToken.None)); } -} \ No newline at end of file + + + +} diff --git a/Parse.Tests/ConfigTests.cs b/Parse.Tests/ConfigTests.cs index c93eeb8d..7e4c6639 100644 --- a/Parse.Tests/ConfigTests.cs +++ b/Parse.Tests/ConfigTests.cs @@ -66,7 +66,7 @@ public void SetUp() => [TestMethod] [Description("Tests TestCurrentConfig Returns the right config")] - public async Task TestCurrentConfig()// Mock difficulty: 1 + public async Task TestCurrentConfig()// 1 { var config = await Client.GetCurrentConfiguration(); @@ -76,7 +76,7 @@ public async Task TestCurrentConfig()// Mock difficulty: 1 [TestMethod] [Description("Tests the conversion of properties to json objects")] - public async Task TestToJSON() // Mock difficulty: 1 + public async Task TestToJSON() // 1 { var expectedJson = new Dictionary { @@ -90,7 +90,7 @@ public async Task TestToJSON() // Mock difficulty: 1 [TestMethod] [Description("Tests the fetching of a new config with an IServiceHub instance.")] - public async Task TestGetConfigAsync()// Mock difficulty: 1 + public async Task TestGetConfigAsync()// 1 { var config = await Client.GetConfigurationAsync(); @@ -100,7 +100,7 @@ public async Task TestGetConfigAsync()// Mock difficulty: 1 [TestMethod] [Description("Tests fetching of config is cancelled when requested via a cancellation token.")] - public async Task TestGetConfigCancelAsync() // Mock difficulty: 1 + public async Task TestGetConfigCancelAsync() // 1 { var tokenSource = new CancellationTokenSource(); tokenSource.Cancel(); @@ -114,20 +114,20 @@ 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 Get method throws an exception if key is not found")] + public void Get_ThrowsExceptionNotFound() // 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 + public void Create_BuildsConfigurationFromDictionary() // 3 { var mockDecoder = new Mock(); var mockServices = new Mock(); @@ -135,7 +135,7 @@ public void Create_BuildsConfigurationFromDictionary() // Mock difficulty: 3 { ["params"] = new Dictionary { { "test", 1 } }, }; - mockDecoder.Setup(d => d.Decode(It.IsAny(), It.IsAny())).Returns(new Dictionary { { "test", 1 } }); + mockDecoder.Setup(d => d.Decode(It.IsAny())).Returns(new Dictionary { { "test", 1 } }); var config = ParseConfiguration.Create(dict, mockDecoder.Object, mockServices.Object); Assert.AreEqual(1, config["test"]); diff --git a/Parse.Tests/ConversionTests.cs b/Parse.Tests/ConversionTests.cs index 3bbff3f7..e91a0bc4 100644 --- a/Parse.Tests/ConversionTests.cs +++ b/Parse.Tests/ConversionTests.cs @@ -24,7 +24,7 @@ public void TestToWithConstructedNullablePrimitive() public void TestToWithConstructedNullableNonPrimitive() { // Test invalid conversion between two nullable value types - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => { Conversion.To(new DummyValueTypeB()); }); diff --git a/Parse.Tests/DecoderTests.cs b/Parse.Tests/DecoderTests.cs index 396edcb0..b902a592 100644 --- a/Parse.Tests/DecoderTests.cs +++ b/Parse.Tests/DecoderTests.cs @@ -1,8 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Parse.Abstractions.Internal; using Parse.Infrastructure; +using Parse.Infrastructure.Control; using Parse.Infrastructure.Data; namespace Parse.Tests; @@ -15,7 +19,7 @@ public class DecoderTests [TestMethod] public void TestParseDate() { - DateTime dateTime = (DateTime) Client.Decoder.Decode(ParseDataDecoder.ParseDate("1990-08-30T12:03:59.000Z"), Client); + DateTime dateTime = (DateTime) Client.Decoder.Decode(ParseDataDecoder.ParseDate("1990-08-30T12:03:59.000Z")); Assert.AreEqual(1990, dateTime.Year); Assert.AreEqual(8, dateTime.Month); @@ -29,21 +33,118 @@ public void TestParseDate() [TestMethod] public void TestDecodePrimitives() { - Assert.AreEqual(1, Client.Decoder.Decode(1, Client)); - Assert.AreEqual(0.3, Client.Decoder.Decode(0.3, Client)); - Assert.AreEqual("halyosy", Client.Decoder.Decode("halyosy", Client)); + Assert.AreEqual(1,Client.Decoder.Decode(1)); + Assert.AreEqual(0.3, Client.Decoder.Decode(0.3)); + Assert.AreEqual("halyosy", Client.Decoder.Decode("halyosy")); + + Assert.IsNull(Client.Decoder.Decode(default)); + } + + [TestMethod] + [Description("Tests that an Increment operation is decoded correctly from JSON.")] + public void TestDecodeFieldOperation_Increment() + { + // Arrange: The JSON data for an increment operation. + var incrementData = new Dictionary { { "__op", "Increment" }, { "amount", 322 } }; + + // Act: Decode the data. + var result = Client.Decoder.Decode(incrementData); + + // Assert: Verify that the result is a valid ParseIncrementOperation. + Assert.IsNotNull(result, "The decoded operation should not be null."); + Assert.IsInstanceOfType(result, typeof(ParseIncrementOperation), "The operation should be a ParseIncrementOperation."); + + var incrementOp = result as ParseIncrementOperation; + Assert.AreEqual(322, Convert.ToInt32(incrementOp.Amount), "The increment amount should be 322."); + } + + [TestMethod] + [Description("Tests that a Delete operation is decoded correctly from JSON.")] + public void TestDecodeFieldOperation_Delete() + { + // Arrange: The JSON data for a delete operation. + var deleteData = new Dictionary { { "__op", "Delete" } }; + + // Act: Decode the data. + var result = Client.Decoder.Decode(deleteData); + + // Assert: Verify that the result is the correct singleton instance. + Assert.IsNotNull(result); + Assert.IsInstanceOfType(result, typeof(ParseDeleteOperation)); + Assert.AreSame(ParseDeleteOperation.Instance, result, "Should be the singleton instance of ParseDeleteOperation."); + } + + [TestMethod] + [Description("Tests that an Add operation is decoded correctly from JSON.")] + public void TestDecodeFieldOperation_Add() + { + // Arrange: The JSON data for an add operation. + var addData = new Dictionary + { + { "__op", "Add" }, + { "objects", new List { "item1", 123, true } } + }; + + // Act: Decode the data. + var result = Client.Decoder.Decode(addData) as ParseAddOperation; + + // Assert + Assert.IsNotNull(result); + Assert.IsInstanceOfType(result, typeof(ParseAddOperation)); + Assert.AreEqual(3, result.Objects.Count()); + Assert.IsTrue(result.Objects.Contains("item1")); + Assert.IsTrue(result.Objects.Contains(123)); // JSON numbers are often parsed as long + Assert.IsTrue(result.Objects.Contains(true)); + } + - Assert.IsNull(Client.Decoder.Decode(default, Client)); + [TestMethod] + [Description("Tests that an AddUnique operation is decoded correctly from JSON.")] + public void TestDecodeFieldOperation_AddUnique() + { + // Arrange + var addUniqueData = new Dictionary + { + { "__op", "AddUnique" }, + { "objects", new List { "item1", "item2" } } + }; + + // Act + var result = Client.Decoder.Decode(addUniqueData) as ParseAddUniqueOperation; + + // Assert + Assert.IsNotNull(result); + Assert.IsInstanceOfType(result, typeof(ParseAddUniqueOperation)); + Assert.AreEqual(2, result.Objects.Count()); + Assert.IsTrue(result.Objects.Contains("item2")); } + [TestMethod] - // Decoding ParseFieldOperation is not supported on .NET now. We only need this for LDS. - public void TestDecodeFieldOperation() => Assert.ThrowsException(() => Client.Decoder.Decode(new Dictionary { { "__op", "Increment" }, { "amount", "322" } }, Client)); + [Description("Tests that a Remove operation is decoded correctly from JSON.")] + public void TestDecodeFieldOperation_Remove() + { + // Arrange + var removeData = new Dictionary + { + { "__op", "Remove" }, + { "objects", new List { "itemToRemove" } } + }; + + // Act + var result = Client.Decoder.Decode(removeData) as ParseRemoveOperation; + + // Assert + Assert.IsNotNull(result); + Assert.IsInstanceOfType(result, typeof(ParseRemoveOperation)); + Assert.AreEqual(1, result.Objects.Count()); + Assert.AreEqual("itemToRemove", result.Objects.First()); + } [TestMethod] public void TestDecodeDate() { - DateTime dateTime = (DateTime) Client.Decoder.Decode(new Dictionary { { "__type", "Date" }, { "iso", "1990-08-30T12:03:59.000Z" } }, Client); + DateTime dateTime = (DateTime) Client.Decoder.Decode(new Dictionary { { "__type", "Date" }, { "iso", "1990-08-30T12:03:59.000Z" } }); Assert.AreEqual(1990, dateTime.Year); Assert.AreEqual(8, dateTime.Month); @@ -61,7 +162,7 @@ public void TestDecodeImproperDate() for (int i = 0; i < 2; i++, value["iso"] = (value["iso"] as string).Substring(0, (value["iso"] as string).Length - 1) + "0Z") { - DateTime dateTime = (DateTime) Client.Decoder.Decode(value, Client); + DateTime dateTime = (DateTime) Client.Decoder.Decode(value); Assert.AreEqual(1990, dateTime.Year); Assert.AreEqual(8, dateTime.Month); @@ -74,12 +175,12 @@ public void TestDecodeImproperDate() } [TestMethod] - public void TestDecodeBytes() => Assert.AreEqual("This is an encoded string", System.Text.Encoding.UTF8.GetString(Client.Decoder.Decode(new Dictionary { { "__type", "Bytes" }, { "base64", "VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==" } }, Client) as byte[])); + public void TestDecodeBytes() => Assert.AreEqual("This is an encoded string", System.Text.Encoding.UTF8.GetString(Client.Decoder.Decode(new Dictionary { { "__type", "Bytes" }, { "base64", "VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==" } }) as byte[])); [TestMethod] public void TestDecodePointer() { - ParseObject obj = Client.Decoder.Decode(new Dictionary { ["__type"] = "Pointer", ["className"] = "Corgi", ["objectId"] = "lLaKcolnu" }, Client) as ParseObject; + ParseObject obj = Client.Decoder.Decode(new Dictionary { ["__type"] = "Pointer", ["className"] = "Corgi", ["objectId"] = "lLaKcolnu" }) as ParseObject; Assert.IsFalse(obj.IsDataAvailable); Assert.AreEqual("Corgi", obj.ClassName); @@ -90,25 +191,25 @@ public void TestDecodePointer() public void TestDecodeFile() { - ParseFile file1 = Client.Decoder.Decode(new Dictionary { ["__type"] = "File", ["name"] = "parsee.png", ["url"] = "https://user-images.githubusercontent.com/5673677/138278489-7d0cebc5-1e31-4d3c-8ffb-53efcda6f29d.png" }, Client) as ParseFile; + ParseFile file1 = Client.Decoder.Decode(new Dictionary { ["__type"] = "File", ["name"] = "parsee.png", ["url"] = "https://user-images.githubusercontent.com/5673677/138278489-7d0cebc5-1e31-4d3c-8ffb-53efcda6f29d.png" }) as ParseFile; Assert.AreEqual("parsee.png", file1.Name); Assert.AreEqual("https://user-images.githubusercontent.com/5673677/138278489-7d0cebc5-1e31-4d3c-8ffb-53efcda6f29d.png", file1.Url.AbsoluteUri); Assert.IsFalse(file1.IsDirty); - Assert.ThrowsException(() => Client.Decoder.Decode(new Dictionary { ["__type"] = "File", ["name"] = "Corgi.png" }, Client)); + Assert.ThrowsException(() => Client.Decoder.Decode(new Dictionary { ["__type"] = "File", ["name"] = "Corgi.png" })); } [TestMethod] public void TestDecodeGeoPoint() { - ParseGeoPoint point1 = (ParseGeoPoint) Client.Decoder.Decode(new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9, ["longitude"] = 0.3 }, Client); + ParseGeoPoint point1 = (ParseGeoPoint) Client.Decoder.Decode(new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9, ["longitude"] = 0.3 }); Assert.IsNotNull(point1); Assert.AreEqual(0.9, point1.Latitude); Assert.AreEqual(0.3, point1.Longitude); - Assert.ThrowsException(() => Client.Decoder.Decode(new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9 }, Client)); + Assert.ThrowsException(() => Client.Decoder.Decode(new Dictionary { ["__type"] = "GeoPoint", ["latitude"] = 0.9 })); } [TestMethod] @@ -123,7 +224,7 @@ public void TestDecodeObject() ["updatedAt"] = "2015-06-22T22:06:41.733Z" }; - ParseObject obj = Client.Decoder.Decode(value, Client) as ParseObject; + ParseObject obj = Client.Decoder.Decode(value) as ParseObject; Assert.IsTrue(obj.IsDataAvailable); Assert.AreEqual("Corgi", obj.ClassName); @@ -142,7 +243,7 @@ public void TestDecodeRelation() ["objectId"] = "lLaKcolnu" }; - ParseRelation relation = Client.Decoder.Decode(value, Client) as ParseRelation; + ParseRelation relation = Client.Decoder.Decode(value) as ParseRelation; Assert.IsNotNull(relation); Assert.AreEqual("Corgi", relation.GetTargetClassName()); @@ -154,7 +255,7 @@ public void TestDecodeDictionary() IDictionary value = new Dictionary() { ["megurine"] = "luka", - ["hatsune"] = new ParseObject("Miku"), + ["hatsune"] = new ParseObject("Miku",Client), ["decodedGeoPoint"] = new Dictionary { ["__type"] = "GeoPoint", @@ -172,7 +273,7 @@ public void TestDecodeDictionary() } }; - IDictionary dict = Client.Decoder.Decode(value, Client) as IDictionary; + IDictionary dict = Client.Decoder.Decode(value) as IDictionary; Assert.AreEqual("luka", dict["megurine"]); Assert.IsTrue(dict["hatsune"] is ParseObject); @@ -187,7 +288,7 @@ public void TestDecodeDictionary() [new ParseACL { }] = "lLaKcolnu" }; - IDictionary randomDict = Client.Decoder.Decode(randomValue, Client) as IDictionary; + IDictionary randomDict = Client.Decoder.Decode(randomValue) as IDictionary; Assert.AreEqual("elements", randomDict["ultimate"]); Assert.AreEqual(2, randomDict.Keys.Count); @@ -216,7 +317,7 @@ public void TestDecodeList() } }; - IList list = Client.Decoder.Decode(value, Client) as IList; + IList list = Client.Decoder.Decode(value) as IList; Assert.AreEqual(1, list[0]); Assert.IsTrue(list[1] is ParseACL); @@ -230,7 +331,7 @@ public void TestDecodeList() [TestMethod] public void TestDecodeArray() { - int[] value = new int[] { 1, 2, 3, 4 }, array = Client.Decoder.Decode(value, Client) as int[]; + int[] value = new int[] { 1, 2, 3, 4 }, array = Client.Decoder.Decode(value) as int[]; Assert.AreEqual(4, array.Length); Assert.AreEqual(1, array[0]); diff --git a/Parse.Tests/EncoderTests.cs b/Parse.Tests/EncoderTests.cs index ea390dc9..49caa04b 100644 --- a/Parse.Tests/EncoderTests.cs +++ b/Parse.Tests/EncoderTests.cs @@ -31,7 +31,7 @@ class ParseEncoderTestClass : ParseDataEncoder [TestMethod] public void TestIsValidType() { - ParseObject corgi = new ParseObject("Corgi"); + ParseObject corgi = new ParseObject("Corgi",Client); ParseRelation corgiRelation = corgi.GetRelation(nameof(corgi)); Assert.IsTrue(ParseDataEncoder.Validate(322)); @@ -49,9 +49,9 @@ public void TestIsValidType() Assert.IsTrue(ParseDataEncoder.Validate(new Dictionary { })); Assert.IsFalse(ParseDataEncoder.Validate(new ParseAddOperation(new List { }))); - Assert.IsFalse(ParseDataEncoder.Validate(Task.FromResult(new ParseObject("Corgi")))); - Assert.ThrowsException(() => ParseDataEncoder.Validate(new Dictionary { })); - Assert.ThrowsException(() => ParseDataEncoder.Validate(new Dictionary { })); + Assert.IsFalse(ParseDataEncoder.Validate(Task.FromResult(new ParseObject("Corgi", Client)))); + Assert.ThrowsExactly(() => ParseDataEncoder.Validate(new Dictionary { })); + Assert.ThrowsExactly(() => ParseDataEncoder.Validate(new Dictionary { })); } [TestMethod] @@ -76,6 +76,13 @@ public void TestEncodeBytes() Assert.AreEqual(Convert.ToBase64String(new byte[] { 1, 2, 3, 4 }), value["base64"]); } + [TestMethod] + public void TestEncodeParseObjectWithNoObjectsEncoder() + { + ParseObject obj = new ParseObject("Corgi", Client); + + Assert.ThrowsExactly(() => NoObjectsEncoder.Instance.Encode(obj, Client)); + } [TestMethod] public void TestEncodeParseObjectWithPointerOrLocalIdEncoder() @@ -96,7 +103,7 @@ public void TestEncodeParseFile() ParseFile file2 = new ParseFile(null, new MemoryStream(new byte[] { 1, 2, 3, 4 })); - Assert.ThrowsException(() => ParseEncoderTestClass.Instance.Encode(file2, Client)); + Assert.ThrowsExactly(() => ParseEncoderTestClass.Instance.Encode(file2, Client)); } [TestMethod] @@ -141,7 +148,7 @@ public void TestEncodeACL() [TestMethod] public void TestEncodeParseRelation() { - ParseObject obj = new ParseObject("Corgi"); + ParseObject obj = new ParseObject("Corgi", Client); ParseRelation relation = ParseRelationExtensions.Create(obj, "nano", "Husky"); IDictionary value = ParseEncoderTestClass.Instance.Encode(relation, Client) as IDictionary; @@ -240,10 +247,10 @@ public void TestEncodeDictionary() Assert.IsTrue(value["geo"] is IDictionary); Assert.IsTrue(value["validDict"] is IDictionary); - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => ParseEncoderTestClass.Instance.Encode(new Dictionary(), Client)); - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => ParseEncoderTestClass.Instance.Encode(new Dictionary { ["validDict"] = new Dictionary { [new ParseACL()] = "jbf" } diff --git a/Parse.Tests/GeoDistanceTest.cs b/Parse.Tests/GeoDistanceTest.cs new file mode 100644 index 00000000..3af4eba4 --- /dev/null +++ b/Parse.Tests/GeoDistanceTest.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Parse.Tests; +[TestClass] +public class ParseGeoDistanceTests +{ + [TestMethod] + [Description("Tests that ParseGeoDistance constructor sets the value in Radians.")] + public void Constructor_SetsRadians() + { + double radians = 2.5; + ParseGeoDistance distance = new ParseGeoDistance(radians); + Assert.AreEqual(radians, distance.Radians); + } + [TestMethod] + [Description("Tests the Miles conversion using a given Radians.")] + public void Miles_ReturnsCorrectValue() + { + double radians = 2.5; + ParseGeoDistance distance = new ParseGeoDistance(radians); + double expected = radians * 3958.8; + Assert.AreEqual(expected, distance.Miles); + } + + [TestMethod] + [Description("Tests the Kilometers conversion using a given Radians.")] + public void Kilometers_ReturnsCorrectValue() + { + double radians = 2.5; + ParseGeoDistance distance = new ParseGeoDistance(radians); + double expected = radians * 6371.0; + + Assert.AreEqual(expected, distance.Kilometers); + } + + [TestMethod] + [Description("Tests that FromMiles returns a correct ParseGeoDistance value.")] + public void FromMiles_ReturnsCorrectGeoDistance() + { + double miles = 100; + ParseGeoDistance distance = ParseGeoDistance.FromMiles(miles); + double expected = miles / 3958.8; + Assert.AreEqual(expected, distance.Radians); + } + + [TestMethod] + [Description("Tests that FromKilometers returns a correct ParseGeoDistance value.")] + public void FromKilometers_ReturnsCorrectGeoDistance() + { + double kilometers = 100; + ParseGeoDistance distance = ParseGeoDistance.FromKilometers(kilometers); + double expected = kilometers / 6371.0; + Assert.AreEqual(expected, distance.Radians); + } + + + [TestMethod] + [Description("Tests that FromRadians returns a correct ParseGeoDistance value.")] + public void FromRadians_ReturnsCorrectGeoDistance() + { + double radians = 100; + ParseGeoDistance distance = ParseGeoDistance.FromRadians(radians); + Assert.AreEqual(radians, distance.Radians); + } +} \ No newline at end of file diff --git a/Parse.Tests/GeoPointTests.cs b/Parse.Tests/GeoPointTests.cs index 5bb0fc55..a9e5f1eb 100644 --- a/Parse.Tests/GeoPointTests.cs +++ b/Parse.Tests/GeoPointTests.cs @@ -24,7 +24,7 @@ public void TestGeoPointCultureInvariantParsing() Thread.CurrentThread.CurrentCulture = culture; ParseGeoPoint point = new ParseGeoPoint(1.234, 1.234); - IDictionary deserialized = Client.Decoder.Decode(JsonUtilities.Parse(JsonUtilities.Encode(new Dictionary { [nameof(point)] = NoObjectsEncoder.Instance.Encode(point, Client) })), Client) as IDictionary; + IDictionary deserialized = Client.Decoder.Decode(JsonUtilities.Parse(JsonUtilities.Encode(new Dictionary { [nameof(point)] = NoObjectsEncoder.Instance.Encode(point, Client) }))) as IDictionary; ParseGeoPoint pointAgain = (ParseGeoPoint) deserialized[nameof(point)]; Assert.AreEqual(1.234, pointAgain.Latitude); diff --git a/Parse.Tests/InternalExtensionsTests.cs b/Parse.Tests/InternalExtensionsTests.cs new file mode 100644 index 00000000..1bc94582 --- /dev/null +++ b/Parse.Tests/InternalExtensionsTests.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Parse.Infrastructure.Utilities; + +namespace Parse.Tests +{ + [TestClass] + public class InternalExtensionsTests + { + [TestMethod] + [Description("Tests that Safe() on a null Task returns a completed task.")] + public void Safe_NullTask_ReturnsCompletedTask() + { + Task nullTask = null; + var safeTask = nullTask.Safe(); + Assert.IsNotNull(safeTask); + Assert.IsTrue(safeTask.IsCompletedSuccessfully); + } + + [TestMethod] + [Description("Tests that Safe() on a null Task returns a completed task with a default result.")] + public async Task Safe_NullGenericTask_ReturnsCompletedTaskWithDefaultResult() + { + Task nullTask = null; + var safeTask = nullTask.Safe(); + Assert.IsNotNull(safeTask); + Assert.IsTrue(safeTask.IsCompletedSuccessfully); + Assert.IsNull(await safeTask); // Default for string is null. + } + + [TestMethod] + [Description("Tests that GetOrDefault returns the correct value when the key exists.")] + public void GetOrDefault_KeyExists_ReturnsValue() + { + var dictionary = new Dictionary { { "apple", 5 } }; + var result = dictionary.GetOrDefault("apple", 10); + Assert.AreEqual(5, result); + } + + [TestMethod] + [Description("Tests that GetOrDefault returns the default value when the key does not exist.")] + public void GetOrDefault_KeyDoesNotExist_ReturnsDefaultValue() + { + var dictionary = new Dictionary { { "apple", 5 } }; + var result = dictionary.GetOrDefault("banana", 10); + Assert.AreEqual(10, result); + } + + [TestMethod] + [Description("Tests that CollectionsEqual correctly compares two equal collections.")] + public void CollectionsEqual_EqualCollections_ReturnsTrue() + { + var list1 = new List { 1, 2, 3 }; + var list2 = new List { 1, 2, 3 }; + Assert.IsTrue(list1.CollectionsEqual(list2)); + } + + [TestMethod] + [Description("Tests that CollectionsEqual correctly compares two unequal collections.")] + public void CollectionsEqual_UnequalCollections_ReturnsFalse() + { + var list1 = new List { 1, 2, 3 }; + var list2 = new List { 3, 2, 1 }; + Assert.IsFalse(list1.CollectionsEqual(list2)); + } + + [TestMethod] + [Description("Tests that OnSuccess executes the continuation for a successful task.")] + public async Task OnSuccess_SuccessfulTask_ExecutesContinuation() + { + var task = Task.CompletedTask; + bool continuationExecuted = false; + + await task.OnSuccess(t => + { + continuationExecuted = true; + return ; + }); + + Assert.IsTrue(continuationExecuted); + } + + [TestMethod] + [Description("Tests WhileAsync loops correctly based on the predicate.")] + public async Task WhileAsync_PredicateControlsLoop() + { + int counter = 0; + Func> predicate = () => Task.FromResult(counter < 3); + Func body = () => + { + counter++; + return Task.CompletedTask; + }; + + await InternalExtensions.WhileAsync(predicate, body); + + Assert.AreEqual(3, counter); + } + + + [TestMethod] + [Description("Tests that OnSuccess with Action executes the continuation for a successful task.")] + public async Task OnSuccess_Action_SuccessfulTask_ExecutesContinuation() + { + var task = Task.CompletedTask; + bool continuationExecuted = false; + + await task.OnSuccess(t => + { + continuationExecuted = true; + }); + + Assert.IsTrue(continuationExecuted); + } + + [TestMethod] + [Description("Tests that OnSuccess with Func> executes the continuation for a successful task.")] + public async Task OnSuccess_Func_SuccessfulTask_ExecutesContinuationAndReturnsResult() + { + var task = Task.CompletedTask; + bool continuationExecuted = false; + + var result = await task.OnSuccess(t => + { + continuationExecuted = true; + return Task.FromResult("Success"); + }); + + Assert.IsTrue(continuationExecuted); + Assert.AreEqual("Success", result); + } + + [TestMethod] + [Description("Tests that OnSuccess propagates a faulted task's exception.")] + public async Task OnSuccess_FaultedTask_ThrowsException() + { + // Arrange: Create a task that is already failed. + var faultedTask = Task.FromException(new InvalidOperationException("Test Exception")); + + // Act & Assert: We expect that calling OnSuccess on this task will immediately + // re-throw the original exception. + await Assert.ThrowsExceptionAsync(() => + // The continuation is a simple action that would run if the task succeeded. + // Since the task failed, this action will never be executed. + faultedTask.OnSuccess(task => { /* This code will not be reached */ }) + ); + } + + [TestMethod] + [Description("Tests that OnSuccess correctly handles a canceled task.")] + public async Task OnSuccess_CanceledTask_ReturnsCanceledTask() + { + var tcs = new TaskCompletionSource(); + tcs.SetCanceled(); + + var canceledTask = tcs.Task; + + var resultTask = canceledTask.OnSuccess(t => Task.FromResult("should not run")); + + await Assert.ThrowsExceptionAsync(async () => await resultTask); + } + + + } +} \ No newline at end of file diff --git a/Parse.Tests/ObjectCoderTests.cs b/Parse.Tests/ObjectCoderTests.cs index 9a391c51..1e740b28 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 @@ -46,4 +58,65 @@ public void TestACLCoding() Assert.IsFalse(resultACL.GetWriteAccess("*")); Assert.IsTrue(resultACL.GetReadAccess("*")); } + + public async Task FetchAsync_FetchesCorrectly() // 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>())).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() // 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..f4138124 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/ObjectStateTests.cs b/Parse.Tests/ObjectStateTests.cs index 10b16e77..07e67ed8 100644 --- a/Parse.Tests/ObjectStateTests.cs +++ b/Parse.Tests/ObjectStateTests.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Control; using Parse.Abstractions.Platform.Objects; using Parse.Infrastructure.Control; @@ -160,4 +162,146 @@ public void TestMutatedClone() Assert.IsNotNull(newState.CreatedAt); Assert.AreNotSame(state, newState); } -} + + + + [TestMethod] + [Description("Tests that MutableClone clones null values correctly.")] + public void MutatedClone_WithNullValues() // 1 + { + IObjectState state = new MutableObjectState + { + ObjectId = "testId" + }; + + IObjectState newState = state.MutatedClone(m => + { + m.ObjectId = null; + + }); + + Assert.IsNull(newState.ObjectId); + } + + + [TestMethod] + [Description("Tests that MutatedClone ignores exceptions")] + public void MutatedClone_IgnoresExceptions() // 1 + { + IObjectState state = new MutableObjectState + { + ClassName = "Test" + }; + + IObjectState newState = state.MutatedClone(m => + { + m.ClassName = "NewName"; + throw new Exception(); + }); + + Assert.AreEqual("NewName", newState.ClassName); + } + [TestMethod] + [Description("Tests that Decode correctly parses a Dictionary")] + public void Decode_ParsesDictionary() // 2 + { + var dict = new Dictionary + { + { "className", "TestClass" }, + { "objectId", "testId" }, + { "createdAt", DateTime.Now }, + { "updatedAt", DateTime.Now }, + { "isNew", true }, + { "test", 1} + }; + IServiceHub mockHub = new Mock().Object; + var state = MutableObjectState.Decode(dict, mockHub); + + Assert.IsNotNull(state); + Assert.AreEqual("TestClass", state.ClassName); + Assert.AreEqual("testId", state.ObjectId); + Assert.IsNotNull(state.CreatedAt); + Assert.IsNotNull(state.UpdatedAt); + Assert.IsTrue(state.IsNew); + Assert.AreEqual(1, state["test"]); + } + [TestMethod] + [Description("Tests that decode can gracefully handle invalid values.")] + public void Decode_HandlesInvalidValues() // 2 + { + var dict = new Dictionary + { + { "className", "TestClass" }, + { "objectId", "testId" }, + { "createdAt", "invalid date" }, + { "updatedAt", 123 }, + }; + IServiceHub mockHub = new Mock().Object; + + var state = MutableObjectState.Decode(dict, mockHub); + + Assert.IsNotNull(state); + Assert.IsNull(state.CreatedAt); + Assert.IsNull(state.UpdatedAt); + Assert.AreEqual("TestClass", state.ClassName); + Assert.AreEqual("testId", state.ObjectId); + } + [TestMethod] + [Description("Tests that Decode Returns null if the data is not a Dictionary.")] + public void Decode_ReturnsNullForInvalidData() // 1 + { + IServiceHub mockHub = new Mock().Object; + var state = MutableObjectState.Decode("invalidData", mockHub); + Assert.IsNull(state); + } + + [TestMethod] + [Description("Tests Apply method ignores exceptions on invalid keys")] + public void Apply_WithIncompatibleKey_SkipsKey() // 1 + { + var mockOp = new Mock(); + mockOp.Setup(op => op.Apply(It.IsAny(), It.IsAny())).Throws(new InvalidCastException()); + var operations = new Dictionary + { + { "InvalidKey", mockOp.Object } + }; + + IObjectState state = new MutableObjectState + { + ServerData = new Dictionary() { + { "testKey", 1 } + } + }; + + state = state.MutatedClone(m => m.Apply(operations)); + + Assert.AreEqual(1, state["testKey"]); + } + + [TestMethod] + [Description("Tests that when apply other state copies objectId, createdAt, updatedAt")] + public void Apply_OtherStateCopiesCorrectly() // 1 + { + DateTime now = DateTime.Now; + IObjectState state = new MutableObjectState + { + ClassName = "test" + }; + + IObjectState appliedState = new MutableObjectState + { + ObjectId = "testId", + CreatedAt = now, + UpdatedAt = now, + IsNew = true, + }; + + state = state.MutatedClone(mutableClone => mutableClone.Apply(appliedState)); + + Assert.AreEqual("testId", state.ObjectId); + Assert.AreEqual(now, state.CreatedAt); + Assert.AreEqual(now, state.UpdatedAt); + Assert.IsTrue(state.IsNew); + } + +} \ No newline at end of file diff --git a/Parse.Tests/ObjectTests.cs b/Parse.Tests/ObjectTests.cs index 6977e387..c7591741 100644 --- a/Parse.Tests/ObjectTests.cs +++ b/Parse.Tests/ObjectTests.cs @@ -29,7 +29,7 @@ class SubClass : ParseObject { } [ParseClassName(nameof(UnregisteredSubClass))] class UnregisteredSubClass : ParseObject { } - private ParseClient Client { get; set; } + private ParseClient Client { get; set; } [TestInitialize] public void SetUp() @@ -40,8 +40,8 @@ public void SetUp() // Register the valid classes Client.RegisterSubclass(typeof(ParseSession)); Client.RegisterSubclass(typeof(ParseUser)); - - + + } [TestCleanup] public void TearDown() => (Client.Services as ServiceHub).Reset(); @@ -728,15 +728,15 @@ public async Task TestFetch() [TestMethod] public void TestFetchAll() { - + } #region New Tests - + [TestMethod] [Description("Tests Bind method attach an IServiceHub object")] - public void Bind_AttachesServiceHubCorrectly() // Mock difficulty: 1 + public void Bind_AttachesServiceHubCorrectly() // 1 { var mockHub = new Mock(); var obj = new ParseObject("TestClass"); @@ -747,30 +747,30 @@ public void Bind_AttachesServiceHubCorrectly() // Mock difficulty: 1 } [TestMethod] [Description("Tests that accessing ACL returns default values if no ACL is set.")] - public void ACL_ReturnsDefaultValueWhenNotSet() // Mock difficulty: 1 + public void ACL_ReturnsDefaultValueWhenNotSet() // 1 { var obj = new ParseObject("TestClass", Client.Services); Assert.IsNull(obj.ACL); } [TestMethod] [Description("Tests that setting and getting the class name returns the correct value.")] - public void ClassName_ReturnsCorrectValue() // Mock difficulty: 1 + public void ClassName_ReturnsCorrectValue() // 1 { var obj = new ParseObject("TestClass", Client.Services); Assert.AreEqual("TestClass", obj.ClassName); } [TestMethod] [Description("Tests that CreatedAt and UpdatedAt returns null if they are not yet set.")] - public void CreatedAt_UpdatedAt_ReturnNullIfNotSet() // Mock difficulty: 1 + public void CreatedAt_UpdatedAt_ReturnNullIfNotSet() // 1 { var obj = new ParseObject("TestClass", Client.Services); Assert.IsNull(obj.CreatedAt); Assert.IsNull(obj.UpdatedAt); } - + [TestMethod] [Description("Tests that IsDirty is true after a value is set.")] - public void IsDirty_ReturnsTrueAfterSet() // Mock difficulty: 1 + public void IsDirty_ReturnsTrueAfterSet() // 1 { var obj = new ParseObject("TestClass", Client.Services); Assert.IsTrue(obj.IsDirty); @@ -780,7 +780,7 @@ public void IsDirty_ReturnsTrueAfterSet() // Mock difficulty: 1 [TestMethod] [Description("Tests that IsNew is true by default and changed when value is set")] - public void IsNew_ReturnsCorrectValue() // Mock difficulty: 1 + public void IsNew_ReturnsCorrectValue() // 1 { var obj = new ParseObject("TestClass", Client.Services); Assert.IsFalse(obj.IsNew); @@ -790,7 +790,7 @@ public void IsNew_ReturnsCorrectValue() // Mock difficulty: 1 [TestMethod] [Description("Tests that Keys returns a collection of strings.")] - public void Keys_ReturnsCollectionOfKeys() // Mock difficulty: 1 + public void Keys_ReturnsCollectionOfKeys() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj["test"] = "test"; @@ -799,7 +799,7 @@ public void Keys_ReturnsCollectionOfKeys() // Mock difficulty: 1 [TestMethod] [Description("Tests that objectId correctly stores data.")] - public void ObjectId_ReturnsCorrectValue() // Mock difficulty: 1 + public void ObjectId_ReturnsCorrectValue() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj.ObjectId = "testObjectId"; @@ -809,7 +809,7 @@ public void ObjectId_ReturnsCorrectValue() // Mock difficulty: 1 [TestMethod] [Description("Tests the [] indexer get and set operations")] - public void Indexer_GetSetOperations() // Mock difficulty: 1 + public void Indexer_GetSetOperations() // 1 { var obj = new ParseObject("TestClass", Client.Services); @@ -821,7 +821,7 @@ public void Indexer_GetSetOperations() // Mock difficulty: 1 [TestMethod] [Description("Tests the Add method correctly adds keys.")] - public void Add_AddsNewKey() // Mock difficulty: 1 + public void Add_AddsNewKey() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj.Add("newKey", "value"); @@ -831,7 +831,7 @@ public void Add_AddsNewKey() // Mock difficulty: 1 } [TestMethod] [Description("Tests that AddRangeToList adds values.")] - public void AddRangeToList_AddsValuesToList() // Mock difficulty: 1 + public void AddRangeToList_AddsValuesToList() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj.AddRangeToList("testList", new[] { 1, 2, 3 }); @@ -840,7 +840,7 @@ public void AddRangeToList_AddsValuesToList() // Mock difficulty: 1 [TestMethod] [Description("Tests that AddRangeUniqueToList adds unique values.")] - public void AddRangeUniqueToList_AddsUniqueValues() // Mock difficulty: 1 + public void AddRangeUniqueToList_AddsUniqueValues() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj.AddRangeUniqueToList("testList", new[] { 1, 2, 1, 3 }); @@ -848,7 +848,7 @@ public void AddRangeUniqueToList_AddsUniqueValues() // Mock difficulty: 1 } [TestMethod] [Description("Tests that AddToList adds a value to the list.")] - public void AddToList_AddsValueToList() // Mock difficulty: 1 + public void AddToList_AddsValueToList() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj.AddToList("testList", 1); @@ -857,7 +857,7 @@ public void AddToList_AddsValueToList() // Mock difficulty: 1 [TestMethod] [Description("Tests that AddUniqueToList adds a unique value to the list.")] - public void AddUniqueToList_AddsUniqueValueToList() // Mock difficulty: 1 + public void AddUniqueToList_AddsUniqueValueToList() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj.AddUniqueToList("testList", 1); @@ -868,7 +868,7 @@ public void AddUniqueToList_AddsUniqueValueToList() // Mock difficulty: 1 [TestMethod] [Description("Tests that ContainsKey returns true if the key exists.")] - public void ContainsKey_ReturnsCorrectly() // Mock difficulty: 1 + public void ContainsKey_ReturnsCorrectly() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj["test"] = "test"; @@ -878,7 +878,7 @@ public void ContainsKey_ReturnsCorrectly() // Mock difficulty: 1 [TestMethod] [Description("Tests Get method that attempts to convert to a type and throws exceptions")] - public void Get_ReturnsCorrectTypeOrThrows() // Mock difficulty: 1 + public void Get_ReturnsCorrectTypeOrThrows() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj["testInt"] = 1; @@ -890,11 +890,11 @@ public void Get_ReturnsCorrectTypeOrThrows() // Mock difficulty: 1 Assert.ThrowsException(() => obj.Get("testString")); } - + [TestMethod] [Description("Tests that HasSameId returns correctly")] - public void HasSameId_ReturnsCorrectly() // Mock difficulty: 1 + public void HasSameId_ReturnsCorrectly() // 1 { var obj1 = new ParseObject("TestClass", Client.Services); var obj2 = new ParseObject("TestClass", Client.Services); @@ -907,7 +907,7 @@ public void HasSameId_ReturnsCorrectly() // Mock difficulty: 1 } [TestMethod] [Description("Tests Increment method by 1.")] - public void Increment_IncrementsValueByOne() // Mock difficulty: 1 + public void Increment_IncrementsValueByOne() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj["testInt"] = 1; @@ -916,7 +916,7 @@ public void Increment_IncrementsValueByOne() // Mock difficulty: 1 } [TestMethod] [Description("Tests Increment by long value")] - public void Increment_IncrementsValueByLong() // Mock difficulty: 1 + public void Increment_IncrementsValueByLong() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj["testInt"] = 1; @@ -926,7 +926,7 @@ public void Increment_IncrementsValueByLong() // Mock difficulty: 1 [TestMethod] [Description("Tests increment by double value.")] - public void Increment_IncrementsValueByDouble() // Mock difficulty: 1 + public void Increment_IncrementsValueByDouble() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj["testDouble"] = 1.0; @@ -936,7 +936,7 @@ public void Increment_IncrementsValueByDouble() // Mock difficulty: 1 [TestMethod] [Description("Tests that IsKeyDirty correctly retrieves dirty keys")] - public void IsKeyDirty_ReturnsCorrectly() // Mock difficulty: 1 + public void IsKeyDirty_ReturnsCorrectly() // 1 { var obj = new ParseObject("TestClass", Client.Services); Assert.IsFalse(obj.IsKeyDirty("test")); @@ -945,7 +945,7 @@ public void IsKeyDirty_ReturnsCorrectly() // Mock difficulty: 1 } [TestMethod] [Description("Tests the Remove method from the object")] - public void Remove_RemovesKeyFromObject() // Mock difficulty: 1 + public void Remove_RemovesKeyFromObject() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj["test"] = "test"; @@ -956,7 +956,7 @@ public void Remove_RemovesKeyFromObject() // Mock difficulty: 1 [TestMethod] [Description("Tests the Revert method that discards all the changes")] - public void Revert_ClearsAllChanges() // Mock difficulty: 1 + public void Revert_ClearsAllChanges() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj["test"] = "test"; @@ -966,7 +966,7 @@ public void Revert_ClearsAllChanges() // Mock difficulty: 1 } [TestMethod] [Description("Tests TryGetValue returns correctly.")] - public void TryGetValue_ReturnsCorrectly() // Mock difficulty: 1 + public void TryGetValue_ReturnsCorrectly() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj["test"] = 1; @@ -974,11 +974,11 @@ public void TryGetValue_ReturnsCorrectly() // Mock difficulty: 1 Assert.AreEqual(1, result); Assert.IsFalse(obj.TryGetValue("nonExistantKey", out int result2)); } - - + + [TestMethod] [Description("Tests MergeFromObject copies the data of other ParseObject")] - public void MergeFromObject_CopiesDataFromOtherObject() // Mock difficulty: 2 + public void MergeFromObject_CopiesDataFromOtherObject() // 2 { var obj1 = new ParseObject("TestClass", Client.Services); var obj2 = new ParseObject("TestClass", Client.Services); @@ -989,7 +989,7 @@ public void MergeFromObject_CopiesDataFromOtherObject() // Mock difficulty: 2 } [TestMethod] [Description("Tests MutateState and checks if estimated data is updated after mutation")] - public void MutateState_UpdatesEstimatedData() // Mock difficulty: 1 + public void MutateState_UpdatesEstimatedData() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj["test"] = 1; @@ -1000,7 +1000,7 @@ public void MutateState_UpdatesEstimatedData() // Mock difficulty: 1 } [TestMethod] [Description("Tests that OnSettingValue Throws exceptions if key is null")] - public void OnSettingValue_ThrowsIfKeyIsNull() // Mock difficulty: 1 + public void OnSettingValue_ThrowsIfKeyIsNull() // 1 { var obj = new ParseObject("TestClass", Client.Services); string key = null; @@ -1013,7 +1013,7 @@ public void OnSettingValue_ThrowsIfKeyIsNull() // Mock difficulty: 1 } [TestMethod] [Description("Tests PerformOperation with ParseSetOperation correctly sets value")] - public void PerformOperation_SetsValueWithSetOperation() // Mock difficulty: 2 + public void PerformOperation_SetsValueWithSetOperation() // 2 { var obj = new ParseObject("TestClass", Client.Services); obj.PerformOperation("test", new ParseSetOperation("value")); @@ -1022,7 +1022,7 @@ public void PerformOperation_SetsValueWithSetOperation() // Mock difficulty: 2 } [TestMethod] [Description("Tests PerformOperation with ParseDeleteOperation deletes the value")] - public void PerformOperation_DeletesValueWithDeleteOperation() // Mock difficulty: 2 + public void PerformOperation_DeletesValueWithDeleteOperation() // 2 { var obj = new ParseObject("TestClass", Client.Services); obj["test"] = "test"; @@ -1032,7 +1032,7 @@ public void PerformOperation_DeletesValueWithDeleteOperation() // Mock difficult } [TestMethod] [Description("Tests the RebuildEstimatedData method rebuilds all data")] - public void RebuildEstimatedData_RebuildsData() // Mock difficulty: 1 + public void RebuildEstimatedData_RebuildsData() // 1 { var obj = new ParseObject("TestClass", Client.Services); obj["test"] = 1; @@ -1042,7 +1042,7 @@ public void RebuildEstimatedData_RebuildsData() // Mock difficulty: 1 [TestMethod] [Description("Tests set method validates the key/value and throws an Argument Exception")] - public void Set_ThrowsArgumentExceptionForInvalidType() // Mock difficulty: 1 + public void Set_ThrowsArgumentExceptionForInvalidType() // 1 { var obj = new ParseObject("TestClass", Client.Services); @@ -1050,7 +1050,7 @@ public void Set_ThrowsArgumentExceptionForInvalidType() // Mock difficulty: 1 } [TestMethod] [Description("Tests set method sets a new value")] - public void Set_SetsCorrectValue() // Mock difficulty: 1 + public void Set_SetsCorrectValue() // 1 { var obj = new ParseObject("TestClass", Client.Services); @@ -1062,7 +1062,7 @@ public void Set_SetsCorrectValue() // Mock difficulty: 1 #endregion [TestMethod] [Description("Tests that ParseObjectClass correctly extract properties and fields.")] - public void Constructor_ExtractsPropertiesCorrectly() // Mock difficulty: 1 + public void Constructor_ExtractsPropertiesCorrectly() // 1 { ConstructorInfo constructor = typeof(TestParseObject).GetConstructor(Type.EmptyTypes); ParseObjectClass obj = new ParseObjectClass(typeof(TestParseObject), constructor); @@ -1073,7 +1073,7 @@ public void Constructor_ExtractsPropertiesCorrectly() // Mock difficulty: 1 [TestMethod] [Description("Tests that Instantiate can correctly instatiate with parameterless constructor.")] - public void Instantiate_WithParameterlessConstructor_CreatesInstance() // Mock difficulty: 1 + public void Instantiate_WithParameterlessConstructor_CreatesInstance() // 1 { ConstructorInfo constructor = typeof(TestParseObject).GetConstructor(Type.EmptyTypes); ParseObjectClass obj = new ParseObjectClass(typeof(TestParseObject), constructor); @@ -1083,7 +1083,7 @@ public void Instantiate_WithParameterlessConstructor_CreatesInstance() // Mock d } [TestMethod] [Description("Tests that Instantiate can correctly instantiate with IServiceHub constructor.")] - public void Instantiate_WithServiceHubConstructor_CreatesInstance() // Mock difficulty: 1 + public void Instantiate_WithServiceHubConstructor_CreatesInstance() // 1 { ConstructorInfo constructor = typeof(ParseObject).GetConstructor(new[] { typeof(string), typeof(IServiceHub) }); ParseObjectClass obj = new ParseObjectClass(typeof(ParseObject), constructor); diff --git a/Parse.Tests/Parse.Tests.csproj b/Parse.Tests/Parse.Tests.csproj index 8998d2cc..e0bfa2d3 100644 --- a/Parse.Tests/Parse.Tests.csproj +++ b/Parse.Tests/Parse.Tests.csproj @@ -6,10 +6,15 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - - + + diff --git a/Parse.Tests/ParseOperationsTests.cs b/Parse.Tests/ParseOperationsTests.cs new file mode 100644 index 00000000..2ba5fe17 --- /dev/null +++ b/Parse.Tests/ParseOperationsTests.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Parse.Infrastructure.Control; + +namespace Parse.Tests; +[TestClass] +public class ParseOperationsTests +{ + #region ParseAddUniqueOperation Tests + [TestMethod] + [Description("Test MergeWithPrevious null with AddUniqueOperation should return itself.")] + public void AddUniqueOperation_MergeWithPreviousNull_ReturnsSelf() // 1 + { + var operation = new ParseAddUniqueOperation(new object[] { 1, 2 }); + var result = operation.MergeWithPrevious(null); + Assert.AreEqual(operation, result); + } + + [TestMethod] + [Description("Test MergeWithPrevious DeleteOperation with AddUniqueOperation returns a ParseSetOperation")] + public void AddUniqueOperation_MergeWithPreviousDelete_ReturnsSetOperation() // 1 + { + var operation = new ParseAddUniqueOperation(new object[] { 1, 2 }); + var result = operation.MergeWithPrevious(ParseDeleteOperation.Instance); + Assert.IsInstanceOfType(result, typeof(ParseSetOperation)); + Assert.IsTrue(new List { 1, 2 }.SequenceEqual(result.Value as List)); + + } + [TestMethod] + [Description("Test MergeWithPrevious SetOperation with AddUniqueOperation creates new ParseSetOperation with previous value")] + public void AddUniqueOperation_MergeWithPreviousSet_ReturnsSetOperation() // 2 + { + var operation = new ParseAddUniqueOperation(new object[] { 3, 4 }); + var setOp = new ParseSetOperation(new[] { 1, 2 }); + + var result = operation.MergeWithPrevious(setOp); + + Assert.IsInstanceOfType(result, typeof(ParseSetOperation)); + Assert.IsTrue(new List { 1, 2, 3, 4 }.SequenceEqual(result.Value as List)); + } + + [TestMethod] + [Description("Test Apply adds all the values correctly and skips existing.")] + public void AddUniqueOperation_Apply_AddsValuesAndSkipsExisting() // 1 + { + var operation = new ParseAddUniqueOperation(new object[] { 1, 2, 3 }); + object existingList = new List { 1, 4, 5 }; + var result = operation.Apply(existingList, "testKey"); + Assert.IsTrue(new List { 1, 4, 5, 2, 3 }.SequenceEqual(result as List)); + + + var operation2 = new ParseAddUniqueOperation(new object[] { 4, 6, 7 }); + var result2 = operation2.Apply(null, "testKey"); + + Assert.IsTrue(new List { 4, 6, 7 }.SequenceEqual(result2 as List)); + + } + + [TestMethod] + [Description("Tests the objects return the Data as an enumerable.")] + public void AddUniqueOperation_Objects_ReturnsEnumerableData() // 1 + { + var operation = new ParseAddUniqueOperation(new object[] { 1, 2 }); + Assert.AreEqual(2, operation.Objects.Count()); + + } + [TestMethod] + [Description("Test that value returns a new list of all the objects used in the ctor.")] + public void AddUniqueOperation_Value_ReturnsDataList() // 1 + { + var operation = new ParseAddUniqueOperation(new object[] { 1, 2 }); + var list = operation.Value as List; + Assert.AreEqual(2, list.Count); + Assert.IsTrue(new List { 1, 2 }.SequenceEqual(list)); + } + #endregion + + #region ParseAddOperation Tests + [TestMethod] + [Description("Tests if MergeWithPrevious handles null and returns this.")] + public void AddOperation_MergeWithPreviousNull_ReturnsSelf()// 1 + { + var operation = new ParseAddOperation(new object[] { 1, 2 }); + var result = operation.MergeWithPrevious(null); + Assert.AreEqual(operation, result); + } + [TestMethod] + [Description("Test if it replaces with a ParseSetOperation on a DeleteOperation.")] + public void AddOperation_MergeWithPreviousDelete_ReturnsSetOperation() // 1 + { + var operation = new ParseAddOperation(new object[] { 1, 2 }); + var result = operation.MergeWithPrevious(ParseDeleteOperation.Instance); + Assert.IsInstanceOfType(result, typeof(ParseSetOperation)); + Assert.IsTrue(new List { 1, 2 }.SequenceEqual(result.Value as List)); + + } + [TestMethod] + [Description("Tests that MergeWithPrevious with another set operator merges with previous value.")] + public void AddOperation_MergeWithPreviousSet_ReturnsSetOperation() // 2 + { + var operation = new ParseAddOperation(new object[] { 3, 4 }); + var setOp = new ParseSetOperation(new[] { 1, 2 }); + var result = operation.MergeWithPrevious(setOp) as ParseSetOperation; + + Assert.IsInstanceOfType(result, typeof(ParseSetOperation)); + Assert.IsTrue(new List { 1, 2, 3, 4 }.SequenceEqual(result.Value as List)); + + } + + [TestMethod] + [Description("Tests if Apply adds all the values to the given list")] + public void AddOperation_Apply_AddsValuesToList()// 1 + { + var operation = new ParseAddOperation(new object[] { 1, 2, 3 }); + object existingList = new List { 1, 4, 5 }; + var result = operation.Apply(existingList, "testKey"); + Assert.IsTrue(new List { 1, 4, 5, 2, 3 }.SequenceEqual(result as List)); + + var operation2 = new ParseAddOperation(new object[] { 1, 4, 5, 6 }); + var result2 = operation2.Apply(null, "testKey"); + + Assert.IsTrue(new List { 1, 4, 5, 6 }.SequenceEqual(result2 as List)); + } + + + [TestMethod] + [Description("Tests that Objects method Returns data as an enumerable")] + public void AddOperation_Objects_ReturnsDataAsEnumerable() // 1 + { + var operation = new ParseAddOperation(new object[] { 1, 2 }); + Assert.AreEqual(2, operation.Objects.Count()); + + } + #endregion + + #region ParseDeleteOperation Tests + [TestMethod] + [Description("Tests that MergeWithPrevious returns itself if previous was deleted.")] + public void DeleteOperation_MergeWithPrevious_ReturnsSelf() // 1 + { + var operation = ParseDeleteOperation.Instance; + var result = operation.MergeWithPrevious(new ParseSetOperation(1)); + Assert.AreEqual(operation, result); + + result = operation.MergeWithPrevious(ParseDeleteOperation.Instance); + Assert.AreEqual(operation, result); + result = operation.MergeWithPrevious(new ParseAddOperation(new List { 1 })); + Assert.AreEqual(operation, result); + + result = operation.MergeWithPrevious(new ParseAddUniqueOperation(new List { 1 })); + Assert.AreEqual(operation, result); + + result = operation.MergeWithPrevious(null); + Assert.AreEqual(operation, result); + } + + [TestMethod] + [Description("Tests that DeleteOperation ConvertsToJson correctly")] + public void DeleteOperation_ConvertToJSON_EncodeToJSONObjectCorrectly() // 1 + { + var operation = ParseDeleteOperation.Instance; + var json = operation.ConvertToJSON(); + + Assert.IsTrue(json.ContainsKey("__op")); + Assert.AreEqual("Delete", json["__op"]); + } + + [TestMethod] + [Description("Tests Apply, which always returns null.")] + public void DeleteOperation_Apply_ReturnsDeleteToken()// 1 + { + var operation = ParseDeleteOperation.Instance; + var result = operation.Apply(1, "test"); + + Assert.AreEqual(ParseDeleteOperation.Token, result); + } + [TestMethod] + [Description("Tests the value returns a null.")] + public void DeleteOperation_Value_ReturnsNull()// 1 + { + var operation = ParseDeleteOperation.Instance; + Assert.IsNull(operation.Value); + } + #endregion + #region ParseIncrementOperation Tests + + [TestMethod] + [Description("Tests if ParseIncrementOperation correctly increments by an int.")] + public void IncrementOperation_MergeWithPreviousNull_ReturnsSelf()// 1 + { + var operation = new ParseIncrementOperation(5); + var result = operation.MergeWithPrevious(null); + Assert.AreEqual(operation, result); + + } + + [TestMethod] + [Description("Test if merging delete returns set.")] + public void IncrementOperation_MergeWithPreviousDelete_ReturnsSetOperation()// 1 + { + var operation = new ParseIncrementOperation(1); + var result = operation.MergeWithPrevious(ParseDeleteOperation.Instance); + + Assert.IsInstanceOfType(result, typeof(ParseSetOperation)); + Assert.AreEqual(1, (int) result.Value); + + } + [TestMethod] + [Description("Tests If MergeWithPrevious with set merges correctly and returns type")] + public void IncrementOperation_MergeWithPreviousSet_ReturnsCorrectType()// 2 + { + var operation = new ParseIncrementOperation(5); + var setOp = new ParseSetOperation(5); + + var result = operation.MergeWithPrevious(setOp); + Assert.IsInstanceOfType(result, typeof(ParseSetOperation)); + Assert.AreEqual(10, (int) result.Value); + } + + [TestMethod] + [Description("Tests MergeWithPrevious throws exceptions when there are two different types")] + public void IncrementOperation_MergeWithPreviousSetNonNumber_ThrowsException() + { + var operation = new ParseIncrementOperation(5); + var setOp = new ParseSetOperation("test"); + + Assert.ThrowsException(() => operation.MergeWithPrevious(setOp)); + + } + [TestMethod] + [Description("Tests that MergeWithPrevious correctly increments on merge of 2 other increment operations.")] + public void IncrementOperation_MergeWithPreviousIncrement_IncrementsValues()// 1 + { + var operation1 = new ParseIncrementOperation(5); + var operation2 = new ParseIncrementOperation(10); + + var result = operation1.MergeWithPrevious(operation2) as ParseIncrementOperation; + + Assert.AreEqual(15, (int) result.Amount); + } + + [TestMethod] + [Description("Tests that Apply correctly handles existing numbers correctly.")] + public void IncrementOperation_Apply_IncrementsValue()// 1 + { + var operation = new ParseIncrementOperation(5); + object result1 = operation.Apply(10, "test"); + Assert.AreEqual(15, result1); + + object result2 = operation.Apply(10.2, "test"); + Assert.AreEqual(15.2, result2); + + object result3 = operation.Apply(null, "test"); + Assert.AreEqual(5, result3); + + } + [TestMethod] + [Description("Tests if Increment Operation correctly Converted To JSON.")] + public void IncrementOperation_ConvertToJSON_EncodedToJSONObjectCorrectly() // 1 + { + var operation = new ParseIncrementOperation(10); + var dict = operation.ConvertToJSON(); + + Assert.IsTrue(dict.ContainsKey("__op")); + Assert.AreEqual("Increment", dict["__op"]); + Assert.IsTrue(dict.ContainsKey("amount")); + Assert.AreEqual(10, dict["amount"]); + + } + + [TestMethod] + [Description("Tests the Value getter and it returns correctly")] + public void IncrementOperation_Value_ReturnsCorrectValue() // 1 + { + var operation = new ParseIncrementOperation(10); + Assert.AreEqual(10, operation.Value); + } + + [TestMethod] + [Description("Tests apply throws on non number types")] + public void IncrementOperation_ApplyNonNumberType_ThrowsException()// 1 + { + var operation = new ParseIncrementOperation(10); + Assert.ThrowsException(() => operation.Apply("test", "test")); + + } + #endregion +} \ No newline at end of file diff --git a/Parse.Tests/ParseQueryControllerTests.cs b/Parse.Tests/ParseQueryControllerTests.cs new file mode 100644 index 00000000..46ea3221 --- /dev/null +++ b/Parse.Tests/ParseQueryControllerTests.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Parse.Abstractions.Internal; +using Moq; + +using Parse.Abstractions.Infrastructure; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Infrastructure.Execution; +using Parse.Abstractions.Platform.Objects; +using Parse.Infrastructure; +using Parse.Infrastructure.Execution; +using Parse.Platform.Objects; +using Parse.Platform.Queries; + +namespace Parse.Tests; + +[TestClass] +public class ParseQueryControllerTests +{ + private Mock mockCommandRunner; + private IServiceHub serviceHub; + + [TestInitialize] + public void SetUp() + { + mockCommandRunner = new Mock(); + + var hub = new MutableServiceHub + { + CommandRunner = mockCommandRunner.Object + }; + hub.SetDefaults(); // This correctly initializes all dependencies, including ClassController. + + var client = new ParseClient(new ServerConnectionData { Test = true }, hub); + serviceHub = client.Services; + } + + [TestMethod] + [Description("Tests that FindAsync correctly decodes a list of objects.")] + public async Task FindAsync_WithResults_ReturnsDecodedStates() + { + // Arrange + var controller = new ParseQueryController(mockCommandRunner.Object, serviceHub.Decoder); + var query = new ParseQuery(serviceHub, "TestClass"); + var serverResponse = new Dictionary + { + ["results"] = new List + { + new Dictionary { ["__type"] = "Object", ["className"] = "TestClass", ["objectId"] = "obj1" }, + new Dictionary { ["__type"] = "Object", ["className"] = "TestClass", ["objectId"] = "obj2" } + } + }; + var tupleResponse = new Tuple>(System.Net.HttpStatusCode.OK, serverResponse); + + mockCommandRunner.Setup(r => r.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())) + .ReturnsAsync(tupleResponse); + + // Act + var result = await controller.FindAsync(query, null, CancellationToken.None); + + // Assert + Assert.AreEqual(2, result.Count()); + Assert.AreEqual("obj1", result.First().ObjectId); + } + + [TestMethod] + [Description("Tests that CountAsync returns the correct count from the server response.")] + public async Task CountAsync_ReturnsCorrectCount() + { + // Arrange + var controller = new ParseQueryController(mockCommandRunner.Object, serviceHub.Decoder); + var query = new ParseQuery(serviceHub, "TestClass"); + var serverResponse = new Dictionary { ["count"] = 150 }; + var tupleResponse = new Tuple>(System.Net.HttpStatusCode.OK, serverResponse); + + mockCommandRunner.Setup(r => r.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())) + .ReturnsAsync(tupleResponse); + + // Act + int count = await controller.CountAsync(query, null, CancellationToken.None); + + // Assert + Assert.AreEqual(150, count); + } + + [TestMethod] + [Description("Tests that FirstAsync returns the correctly decoded first object.")] + public async Task FirstAsync_ReturnsFirstObject() + { + // Arrange + var controller = new ParseQueryController(mockCommandRunner.Object, serviceHub.Decoder); + var query = new ParseQuery(serviceHub, "TestClass"); + var serverResponse = new Dictionary + { + ["results"] = new List { new Dictionary { ["__type"] = "Object", ["className"] = "TestClass", ["objectId"] = "theFirst" } } + }; + var tupleResponse = new Tuple>(System.Net.HttpStatusCode.OK, serverResponse); + + mockCommandRunner.Setup(r => r.RunCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())) + .ReturnsAsync(tupleResponse); + + // Act + var result = await controller.FirstAsync(query, null, CancellationToken.None); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual("theFirst", result.ObjectId); + } +} \ No newline at end of file diff --git a/Parse.Tests/ParseQueryTests.cs b/Parse.Tests/ParseQueryTests.cs new file mode 100644 index 00000000..7c704e95 --- /dev/null +++ b/Parse.Tests/ParseQueryTests.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +using Parse.Abstractions.Infrastructure; + +using Parse.Infrastructure; + +namespace Parse.Tests; + +[TestClass] +public class ParseQueryTests +{ + private ParseClient Client { get; set; } + Mock MockHub { get; set; } + + [TestInitialize] + public void SetUp() + { + Client = new ParseClient(new ServerConnectionData { Test = true }); + Client.Publicize(); + MockHub = new Mock(); + Client.Services = MockHub.Object; + } + [TestCleanup] + public void TearDown() + { + if (Client?.Services is OrchestrationServiceHub orchestration && orchestration.Default is ServiceHub serviceHub) + { + serviceHub.Reset(); + } + } + + [TestMethod] + [Description("Tests constructor, that classes are instantiated correctly.")] + public void Constructor_CreatesObjectCorrectly() // 1 + { + var query = new ParseQuery(MockHub.Object, "test"); + + Assert.IsNotNull(query.ClassName); + Assert.IsNotNull(query.Services); + Assert.ThrowsException(() => new ParseQuery(MockHub.Object, null)); + } + + [TestMethod] + [Description("Tests that ThenBy throws exception if there is no orderby set before hand.")] + public void ThenBy_ThrowsIfNotSetOrderBy()// 1 + { + var query = new ParseQuery(MockHub.Object, "test"); + Assert.ThrowsException(() => query.ThenBy("test")); + + } + + [TestMethod] + [Description("Tests that where contains correctly constructs the query for given values")] + public void WhereContains_SetsRegexSearchValue()// 1 + { + var query = new ParseQuery(MockHub.Object, "test").WhereContains("test", "test"); + var results = query.GetConstraint("test") as IDictionary; + Assert.IsTrue(results.ContainsKey("$regex")); + Assert.AreEqual("\\Qtest\\E", results["$regex"]); + } + + [TestMethod] + [Description("Tests WhereDoesNotExist correctly builds query")] + public void WhereDoesNotExist_SetsNewWhereWithDoesNotExist()// 1 + { + var query = new ParseQuery(MockHub.Object, "test").WhereDoesNotExist("test"); + var results = query.GetConstraint("test") as IDictionary; + Assert.IsTrue(results.ContainsKey("$exists")); + Assert.AreEqual(false, results["$exists"]); + + } + + + [TestMethod] + [Description("Test WhereEndsWith correctly set query.")] + public void WhereEndsWith_SetsCorrectRegexEnd()// 1 + { + var query = new ParseQuery(MockHub.Object, "test").WhereEndsWith("test", "test"); + var results = query.GetConstraint("test") as IDictionary; + Assert.IsTrue(results.ContainsKey("$regex")); + Assert.AreEqual("\\Qtest\\E$", results["$regex"]); + } + + [TestMethod] + [Description("Tests WhereEqualTo correctly builds the query.")] + public void WhereEqualTo_SetsKeyValueOnWhere() // 1 + { + var query = new ParseQuery(MockHub.Object, "test").WhereEqualTo("test", "value"); + Assert.AreEqual("value", query.GetConstraint("test")); + } + [TestMethod] + [Description("Tests WhereExists correctly builds query.")] + public void WhereExists_SetsKeyValueOnWhere()// 1 + { + var query = new ParseQuery(MockHub.Object, "test").WhereExists("test"); + var results = query.GetConstraint("test") as IDictionary; + Assert.IsTrue(results.ContainsKey("$exists")); + Assert.AreEqual(true, results["$exists"]); + } + + [TestMethod] + [Description("Tests WhereGreaterThan correctly builds the query.")] + public void WhereGreaterThan_SetsLowerBound()// 1 + { + var query = new ParseQuery(MockHub.Object, "test").WhereGreaterThan("test", 10); + var results = query.GetConstraint("test") as IDictionary; + Assert.IsTrue(results.ContainsKey("$gt")); + Assert.AreEqual(10, results["$gt"]); + } + + [TestMethod] + [Description("Tests where greater or equal than sets lower bound properly")] + public void WhereGreaterThanOrEqualTo_SetsLowerBound()// 1 + { + var query = new ParseQuery(MockHub.Object, "test").WhereGreaterThanOrEqualTo("test", 10); + var results = query.GetConstraint("test") as IDictionary; + Assert.IsTrue(results.ContainsKey("$gte")); + Assert.AreEqual(10, results["$gte"]); + } + [TestMethod] + [Description("Tests if WhereLessThan correctly build the query")] + public void WhereLessThan_SetsLowerBound()// 1 + { + var query = new ParseQuery(MockHub.Object, "test").WhereLessThan("test", 10); + var results = query.GetConstraint("test") as IDictionary; + Assert.IsTrue(results.ContainsKey("$lt")); + Assert.AreEqual(10, results["$lt"]); + + } + + [TestMethod] + [Description("Tests where less than or equal to sets query properly")] + public void WhereLessThanOrEqualTo_SetsLowerBound()// 1 + { + var query = new ParseQuery(MockHub.Object, "test").WhereLessThanOrEqualTo("test", 10); + var results = query.GetConstraint("test") as IDictionary; + Assert.IsTrue(results.ContainsKey("$lte")); + Assert.AreEqual(10, results["$lte"]); + } + [TestMethod] + [Description("Tests if WhereMatches builds query using regex and modifiers correctly")] + public void WhereMatches_SetsRegexAndModifiersCorrectly()// 1 + { + var regex = new Regex("test", RegexOptions.ECMAScript | RegexOptions.IgnoreCase); + var query = new ParseQuery(MockHub.Object, "test").WhereMatches("test", regex, "im"); + var results = query.GetConstraint("test") as IDictionary; + + Assert.IsTrue(results.ContainsKey("$regex")); + Assert.IsTrue(results.ContainsKey("$options")); + Assert.AreEqual("test", results["$regex"]); + Assert.AreEqual("im", results["$options"]); + } + + [TestMethod] + [Description("Tests if exception is throw on Regex doesn't have proper flags.")] + public void WhereMatches_RegexWithoutFlag_Throws()// 1 + { + var regex = new Regex("test"); + var query = new ParseQuery(MockHub.Object, "test"); + Assert.ThrowsException(() => query.WhereMatches("test", regex, null)); + + } + + [TestMethod] + [Description("Tests if WhereNear builds query with $nearSphere property.")] + public void WhereNear_CreatesQueryNearValue()// 1 + { + var point = new ParseGeoPoint(1, 2); + var query = new ParseQuery(MockHub.Object, "test").WhereNear("test", point); + var result = query.GetConstraint("test") as IDictionary; + Assert.IsTrue(result.ContainsKey("$nearSphere")); + Assert.AreEqual(point, result["$nearSphere"]); + + } + + [TestMethod] + [Description("Tests WhereNotEqualTo correctly builds the query.")] + public void WhereNotEqualTo_SetsValueOnWhere()// 1 + { + var query = new ParseQuery(MockHub.Object, "test").WhereNotEqualTo("test", "value"); + var results = query.GetConstraint("test") as IDictionary; + Assert.IsTrue(results.ContainsKey("$ne")); + Assert.AreEqual("value", results["$ne"]); + } + + [TestMethod] + [Description("Tests where starts with sets regex values")] + public void WhereStartsWith_SetsCorrectRegexValue()// 1 + { + var query = new ParseQuery(MockHub.Object, "test").WhereStartsWith("test", "test"); + var results = query.GetConstraint("test") as IDictionary; + Assert.IsTrue(results.ContainsKey("$regex")); + Assert.AreEqual("^\\Qtest\\E", results["$regex"]); + } + [TestMethod] + [Description("Tests if WhereWithinGeoBox builds query with the correct values")] + public void WhereWithinGeoBox_SetsWithingValues()// 1 + { + var point1 = new ParseGeoPoint(1, 2); + var point2 = new ParseGeoPoint(3, 4); + var query = new ParseQuery(MockHub.Object, "test").WhereWithinGeoBox("test", point1, point2); + var results = query.GetConstraint("test") as IDictionary; + Assert.IsTrue(results.ContainsKey("$within")); + var innerWithin = results["$within"] as IDictionary; + Assert.IsTrue(innerWithin.ContainsKey("$box")); + Assert.AreEqual(2, (innerWithin["$box"] as IEnumerable).Cast().Count()); + + + } + + +} \ No newline at end of file diff --git a/Parse.Tests/ProgressTests.cs b/Parse.Tests/ProgressTests.cs index 8242b4f9..abb4277f 100644 --- a/Parse.Tests/ProgressTests.cs +++ b/Parse.Tests/ProgressTests.cs @@ -6,30 +6,9 @@ namespace Parse.Tests; - [TestClass] public class ProgressTests { - private Mock> mockProgress; - private int _callbackCallCount; - - - [TestInitialize] - public void Initialize() - { - mockProgress = new Mock>(); - _callbackCallCount = 0; - mockProgress.Setup(obj => obj.Report(It.IsAny())) - .Callback(() => _callbackCallCount++); - - } - - [TestCleanup] - public void Cleanup() - { - mockProgress = null; // Ensure mock is released - } - [TestMethod] public void TestDownloadProgressEventGetterSetter() { @@ -53,6 +32,9 @@ public void TestUploadProgressEventGetterSetter() [TestMethod] public void TestObservingDownloadProgress() { + int called = 0; + Mock> mockProgress = new Mock>(); + mockProgress.Setup(obj => obj.Report(It.IsAny())).Callback(() => called++); IProgress progress = mockProgress.Object; progress.Report(new DataTransferLevel { Amount = 0.2f }); @@ -61,12 +43,15 @@ public void TestObservingDownloadProgress() progress.Report(new DataTransferLevel { Amount = 0.68f }); progress.Report(new DataTransferLevel { Amount = 0.88f }); - Assert.AreEqual(5, _callbackCallCount); + Assert.AreEqual(5, called); } [TestMethod] public void TestObservingUploadProgress() { + int called = 0; + Mock> mockProgress = new Mock>(); + mockProgress.Setup(obj => obj.Report(It.IsAny())).Callback(() => called++); IProgress progress = mockProgress.Object; progress.Report(new DataTransferLevel { Amount = 0.2f }); @@ -75,6 +60,6 @@ public void TestObservingUploadProgress() progress.Report(new DataTransferLevel { Amount = 0.68f }); progress.Report(new DataTransferLevel { Amount = 0.88f }); - Assert.AreEqual(5, _callbackCallCount); + Assert.AreEqual(5, called); } -} \ No newline at end of file +} diff --git a/Parse.Tests/RelationTests.cs b/Parse.Tests/RelationTests.cs index c550e582..c1a362cf 100644 --- a/Parse.Tests/RelationTests.cs +++ b/Parse.Tests/RelationTests.cs @@ -1,84 +1,13 @@ -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; - - } - - [TestCleanup] - public void TearDown() => (Client.Services as ServiceHub).Reset(); - [TestMethod] public void TestRelationQuery() { @@ -95,278 +24,4 @@ public void TestRelationQuery() Assert.AreEqual("child", encoded["redirectClassNameForKey"]); } - - [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); - - } - 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(); - } - -} - +} \ No newline at end of file diff --git a/Parse.Tests/TransientCacheControllerTests.cs b/Parse.Tests/TransientCacheControllerTests.cs new file mode 100644 index 00000000..60199cfc --- /dev/null +++ b/Parse.Tests/TransientCacheControllerTests.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Parse.Infrastructure; + +namespace Parse.Tests +{ + [TestClass] + public class TransientCacheControllerTests + { + [TestMethod] + [Description("Tests that the cache controller can add and then retrieve a value.")] + public async Task LoadAsync_AddAsync_CanRetrieveValue() + { + // Arrange + var cacheController = new TransientCacheController(); + var initialCache = await cacheController.LoadAsync(); + + // Act + await initialCache.AddAsync("testKey", "testValue"); + var finalCache = await cacheController.LoadAsync(); + finalCache.TryGetValue("testKey", out var result); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual("testValue", result); + } + + [TestMethod] + [Description("Tests that RemoveAsync correctly removes an item from the cache.")] + public async Task RemoveAsync_RemovesItemFromCache() + { + // Arrange + var cacheController = new TransientCacheController(); + var cache = await cacheController.LoadAsync(); + await cache.AddAsync("testKey", "testValue"); + + // Act + await cache.RemoveAsync("testKey"); + var finalCache = await cacheController.LoadAsync(); + bool keyExists = finalCache.ContainsKey("testKey"); + + // Assert + Assert.IsFalse(keyExists); + } + + [TestMethod] + [Description("Tests that Clear correctly empties the cache.")] + public async Task Clear_EmptiesTheCache() + { + // Arrange + var cacheController = new TransientCacheController(); + var cache = await cacheController.LoadAsync(); + await cache.AddAsync("key1", "value1"); + await cache.AddAsync("key2", "value2"); + + // Act + cacheController.Clear(); + var finalCache = await cacheController.LoadAsync(); + + // Assert + Assert.AreEqual(0, finalCache.Count); + } + + [TestMethod] + [Description("Tests that GetRelativeFile throws NotSupportedException as expected.")] + public void GetRelativeFile_ThrowsNotSupportedException() + { + var cacheController = new TransientCacheController(); + Assert.ThrowsException(() => cacheController.GetRelativeFile("some/path")); + } + } +} diff --git a/Parse.Tests/UserTests.cs b/Parse.Tests/UserTests.cs index 21a8895b..f6db9abd 100644 --- a/Parse.Tests/UserTests.cs +++ b/Parse.Tests/UserTests.cs @@ -1,19 +1,24 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using Parse.Abstractions.Infrastructure; -using Parse.Infrastructure; using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Infrastructure.Execution; using Parse.Abstractions.Platform.Objects; using Parse.Abstractions.Platform.Sessions; using Parse.Abstractions.Platform.Users; +using Parse.Infrastructure; +using Parse.Infrastructure.Execution; using Parse.Platform.Objects; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Net.Http; namespace Parse.Tests; @@ -32,20 +37,22 @@ public class UserTests [TestInitialize] public void SetUp() { + Client = new ParseClient(new ServerConnectionData { Test = true }); - Client.Publicize(); // Ensure the Client instance is globally available + Client.Publicize(); // Ensure the Clientinstance is globally available + Client.AddValidClass(); 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() { (Client.Services as ServiceHub)?.Reset(); - } /// @@ -54,14 +61,16 @@ public void CleanUp() private ParseUser CreateParseUser(MutableObjectState state) { var user = ParseObject.Create(); - + user.HandleFetchResult(state); user.Bind(Client); - + return user; } + + [TestMethod] public async Task TestSignUpWithInvalidServerDataAsync() { @@ -114,9 +123,9 @@ public async Task TestSignUpAsync() var user = CreateParseUser(state); user.Bind(client); - + await user.SignUpAsync(); - + // Verify SignUpAsync is invoked mockController.Verify( @@ -138,64 +147,81 @@ public async Task TestSignUpAsync() [TestMethod] public async Task TestLogOut() { - // Arrange: Create a mock service hub and user state - var state = new MutableObjectState + // Arrange + + // 1. Create mocks for the specific services we need to control. + var mockCommandRunner = new Mock(); + var mockCurrentUserController = new Mock(); + + // 2. Create a MUTABLE service hub and put our mocks inside. + // This hub has NO knowledge of the real services. + var mockedHub = new MutableServiceHub { - ServerData = new Dictionary - { - ["sessionToken"] = TestRevocableSessionToken - } + CommandRunner = mockCommandRunner.Object, + CurrentUserController = mockCurrentUserController.Object }; + // Let the mutable hub fill in any other dependencies it needs with defaults. + mockedHub.SetDefaults(); - var user = CreateParseUser(state); + // 3. Create a NEW ParseClient instance specifically for this test. + // We pass our MOCKED hub directly into its constructor. + var isolatedClient = new ParseClient(new ServerConnectionData { Test = true }, mockedHub); - // Mock CurrentUserController - var mockCurrentUserController = new Mock(); + // 4. Use THIS isolated client to create our user. + // This guarantees the user is constructed ONLY with our mocked services. + // It will never touch the static ParseClient.Instance. + var user = isolatedClient.GenerateObjectFromState(new MutableObjectState + { + ServerData = new Dictionary { ["sessionToken"] = TestRevocableSessionToken } + }, "_User"); - // Mock GetAsync to return the user as the current user - mockCurrentUserController - .Setup(obj => obj.GetAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(user); + // 5. Set up the expected behavior of our mocks. + mockCommandRunner.Setup(runner => runner.RunCommandAsync( + It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())) + .ReturnsAsync(new Tuple>(System.Net.HttpStatusCode.OK, new Dictionary())); - // Mock ClearFromDiskAsync to ensure it's called during LogOutAsync mockCurrentUserController - .Setup(obj => obj.ClearFromDiskAsync()) - .Returns(Task.CompletedTask); - - // Mock LogOutAsync to ensure it can execute its logic - mockCurrentUserController - .Setup(obj => obj.LogOutAsync(It.IsAny(), It.IsAny())) - .CallBase(); // Use the actual LogOutAsync implementation - - // Mock SessionController for session revocation - var mockSessionController = new Mock(); - mockSessionController - .Setup(c => c.RevokeAsync(It.IsAny(), It.IsAny())) + .Setup(c => c.LogOutAsync(It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); - // Create a ServiceHub and inject mocks - var hub = new MutableServiceHub - { - CurrentUserController = mockCurrentUserController.Object, - SessionController = mockSessionController.Object - }; + // Act + // Call LogOutAsync on the user object that is guaranteed to be isolated. + await user.LogOutAsync(CancellationToken.None); + mockCommandRunner.Verify(runner => runner.RunCommandAsync( + It.Is(cmd => + // Check the path + cmd.Path.Contains("logout") && + // Manually check the headers + HeadersContainSessionToken(cmd.Headers, TestRevocableSessionToken) + ), + It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Once); + + // Verify the local cache was told to clear. + mockCurrentUserController.Verify(c => + c.LogOutAsync(It.IsAny(), It.IsAny()), + Times.Once); - // Inject mocks into ParseClient - var client = new ParseClient(new ServerConnectionData { Test = true }, hub); + Assert.IsNull(user.SessionToken); + } - // Act: Perform logout - await client.LogOutAsync(CancellationToken.None); - // Assert: Verify the user's sessionToken is cleared - Assert.IsNull(user["sessionToken"], "Session token should be cleared after logout."); + private bool HeadersContainSessionToken(IEnumerable> headers, string expectedToken) + { + foreach (var header in headers) + { + if (header.Key == "X-Parse-Session-Token" && header.Value == expectedToken) + { + return true; // We found it! + } + } + return false; // We looped through all headers and didn't find it. } - [TestMethod] public async Task TestRequestPasswordResetAsync() { var hub = new MutableServiceHub(); - var Client = new ParseClient(new ServerConnectionData { Test = true }, hub); + var Client= new ParseClient(new ServerConnectionData { Test = true }, hub); var mockController = new Mock(); hub.UserController = mockController.Object; @@ -204,7 +230,10 @@ public async Task TestRequestPasswordResetAsync() mockController.Verify(obj => obj.RequestPasswordResetAsync(TestEmail, It.IsAny()), Times.Once); } - [TestMethod] + + + //I need to test the LinkWithAsync method, but it requires a valid authData dictionary and a valid service hub setup. + [Ignore] public async Task TestLinkAsync() { // Arrange @@ -330,7 +359,7 @@ public async Task TestSaveAsync_IsCalled() It.IsAny(), It.IsAny(), It.IsAny())) - + .Verifiable(); // Act @@ -346,5 +375,167 @@ public async Task TestSaveAsync_IsCalled() It.IsAny()), Times.Once); } + [TestMethod] + [Description("Tests that SignUpAsync throws when essential properties are missing.")] + public async Task SignUpAsync_MissingCredentials_ThrowsException() + { + var user = new ParseUser(); + await Assert.ThrowsExceptionAsync(() => user.SignUpAsync(), "Should throw for missing username."); + + user.Username = TestUsername; + await Assert.ThrowsExceptionAsync(() => user.SignUpAsync(), "Should throw for missing password."); + + user.Password = TestPassword; + user.ObjectId = TestObjectId; + await Assert.ThrowsExceptionAsync(() => user.SignUpAsync(), "Should throw for existing ObjectId."); + } + + //[TestMethod] + //[Description("Tests that IsAuthenticatedAsync returns true when the user is the current user.")] + //public async Task IsAuthenticatedAsync_WhenCurrentUserMatches_ReturnsTrue() + //{ + // // Arrange + // var mockCurrentUserController = new Mock(); + // var hub = new MutableServiceHub { CurrentUserController = mockCurrentUserController.Object }; + // hub.SetDefaults(); + // var client = new ParseClient(new ServerConnectionData { Test = true }, hub); + + // var user = client.GenerateObjectFromState(new MutableObjectState { ObjectId = TestObjectId, ServerData = new Dictionary { ["sessionToken"] = TestSessionToken } }, "_User"); + + // // Mock GetCurrentUserAsync to return the same user. + // mockCurrentUserController.Setup(c => c.GetCurrentUserAsync()).ReturnsAsync(user); + + // // Act + // var isAuthenticated = await user.IsAuthenticatedAsync(); + + // // Assert + // Assert.IsTrue(isAuthenticated); + //} + + [TestMethod] + [Description("Tests that IsAuthenticatedAsync returns false when there is no session token.")] + public async Task IsAuthenticatedAsync_WhenNoSessionToken_ReturnsFalse() + { + // Arrange + var user = new ParseUser { ObjectId = TestObjectId }; + + // Act + var isAuthenticated = await user.IsAuthenticatedAsync(); + + // Assert + Assert.IsFalse(isAuthenticated); + } + + [TestMethod] + [Description("Tests that removing the username key throws an exception.")] + public void Remove_Username_ThrowsInvalidOperationException() + { + var user = new ParseUser(); + Assert.ThrowsException(() => user.Remove("username")); + } + + //[TestMethod] + //[Description("Tests that setting the session token correctly updates state and saves the current user.")] + //public async Task SetSessionTokenAsync_SavesCurrentUser() + //{ + // // Arrange + // var mockCurrentUserController = new Mock(); + // var hub = new MutableServiceHub { CurrentUserController = mockCurrentUserController.Object }; + // hub.SetDefaults(); + // var client = new ParseClient(new ServerConnectionData { Test = true }, hub); + // var user = client.GenerateObjectFromState(new MutableObjectState(), "_User"); + + // // Act + // await user.SetSessionTokenAsync("new_token"); + + // // Assert + // Assert.AreEqual("new_token", user.SessionToken); + // mockCurrentUserController.Verify(c => c.SaveCurrentUserAsync(user, It.IsAny()), Times.Once); + //} + + [TestMethod] + [Description("Tests that SaveAsync on a new user throws an exception.")] + public async Task SaveAsync_NewUser_ThrowsInvalidOperationException() + { + var user = new ParseUser(); + await Assert.ThrowsExceptionAsync(() => user.SaveAsync()); + } + + //[TestMethod] + //[Description("Tests that SaveAsync on an existing user saves the current user if they match.")] + //public async Task SaveAsync_WhenIsCurrentUser_SavesCurrentUser() + //{ + // // Arrange + // var mockObjectController = new Mock(); + // var mockCurrentUserController = new Mock(); + // var hub = new MutableServiceHub + // { + // ObjectController = mockObjectController.Object, + // CurrentUserController = mockCurrentUserController.Object + // }; + // hub.SetDefaults(); + // var client = new ParseClient(new ServerConnectionData { Test = true }, hub); + // var user = client.GenerateObjectFromState(new MutableObjectState { ObjectId = TestObjectId }, "_User"); + + // mockCurrentUserController.Setup(c => c.IsCurrent(user)).Returns(true); + // mockObjectController.Setup(c => c.SaveAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) + // .ReturnsAsync(user.State); + + // // Act + // user.Email = "new@email.com"; // Make the user dirty + // await user.SaveAsync(); + + // // Assert + // mockCurrentUserController.Verify(c => c.SaveCurrentUserAsync(user, It.IsAny()), Times.Once); + //} + + [TestMethod] + [Description("Tests IsLinked returns true when auth data for the provider exists.")] + public void IsLinked_WithExistingAuthData_ReturnsTrue() + { + var user = new ParseUser(); + user.AuthData = new Dictionary> + { + ["facebook"] = new Dictionary { { "id", "123" } } + }; + + Assert.IsTrue(user.IsLinked("facebook")); + } + + [TestMethod] + [Description("Tests IsLinked returns false when auth data for the provider is null or missing.")] + public void IsLinked_WithMissingAuthData_ReturnsFalse() + { + var user = new ParseUser(); + user.AuthData = new Dictionary> + { + ["twitter"] = null + }; + + Assert.IsFalse(user.IsLinked("facebook")); + Assert.IsFalse(user.IsLinked("twitter")); + } + + [TestMethod] + [Description("Tests that HandleSave removes the password from the server data.")] + public void HandleSave_RemovesPasswordFromServerData() + { + // Arrange + var user = new ParseUser(); + var serverState = new MutableObjectState + { + ServerData = new Dictionary + { + ["username"] = TestUsername, + ["password"] = "some_hash_not_the_real_password" + } + }; + + // Act + user.HandleSave(serverState); + + // Assert + Assert.IsFalse(user.State.ContainsKey("password")); + } } diff --git a/Parse/Abstractions/Infrastructure/CustomServiceHub.cs b/Parse/Abstractions/Infrastructure/CustomServiceHub.cs index 554c91bb..1ea92a07 100644 --- a/Parse/Abstractions/Infrastructure/CustomServiceHub.cs +++ b/Parse/Abstractions/Infrastructure/CustomServiceHub.cs @@ -15,6 +15,7 @@ namespace Parse.Abstractions.Infrastructure; public abstract class CustomServiceHub : ICustomServiceHub { + public virtual IServiceHub Services { get; internal set; } public virtual IServiceHubCloner Cloner => Services.Cloner; @@ -46,7 +47,7 @@ public abstract class CustomServiceHub : ICustomServiceHub public virtual IParseUserController UserController => Services.UserController; public virtual IParseCurrentUserController CurrentUserController => Services.CurrentUserController; - + public virtual ParseUser CurrentUser => Services.CurrentUserController.CurrentUser; public virtual IParseAnalyticsController AnalyticsController => Services.AnalyticsController; public virtual IParseInstallationCoder InstallationCoder => Services.InstallationCoder; diff --git a/Parse/Abstractions/Infrastructure/Data/IParseDataDecoder.cs b/Parse/Abstractions/Infrastructure/Data/IParseDataDecoder.cs index 29a0b6ec..dcfa0aba 100644 --- a/Parse/Abstractions/Infrastructure/Data/IParseDataDecoder.cs +++ b/Parse/Abstractions/Infrastructure/Data/IParseDataDecoder.cs @@ -11,5 +11,5 @@ public interface IParseDataDecoder /// The target input data to decode. /// A implementation instance to use when instantiating s. /// A Parse SDK entity such as a . - object Decode(object data, IServiceHub serviceHub); + object Decode(object data); } \ No newline at end of file diff --git a/Parse/Abstractions/Infrastructure/IJsonConvertible.cs b/Parse/Abstractions/Infrastructure/IJsonConvertible.cs index e5d0db0a..b547d5ae 100644 --- a/Parse/Abstractions/Infrastructure/IJsonConvertible.cs +++ b/Parse/Abstractions/Infrastructure/IJsonConvertible.cs @@ -11,6 +11,6 @@ public interface IJsonConvertible /// Converts the object to a data structure that can be converted to JSON. /// /// An object to be JSONified. - - object ConvertToJSON(IServiceHub serviceHub=default); -} + + IDictionary ConvertToJSON(IServiceHub serviceHub = default); +} \ No newline at end of file diff --git a/Parse/Abstractions/Platform/Users/IParseCurrentUserController.cs b/Parse/Abstractions/Platform/Users/IParseCurrentUserController.cs index 97437fd0..2f4be7bb 100644 --- a/Parse/Abstractions/Platform/Users/IParseCurrentUserController.cs +++ b/Parse/Abstractions/Platform/Users/IParseCurrentUserController.cs @@ -7,6 +7,8 @@ namespace Parse.Abstractions.Platform.Users; public interface IParseCurrentUserController : IParseObjectCurrentController { + ParseUser CurrentUser { get; } + Task GetCurrentSessionTokenAsync(IServiceHub serviceHub, CancellationToken cancellationToken = default); Task LogOutAsync(IServiceHub serviceHub, CancellationToken cancellationToken = default); diff --git a/Parse/Infrastructure/Control/ParseAddOperation.cs b/Parse/Infrastructure/Control/ParseAddOperation.cs index 7f62a142..164503b5 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 object ConvertToJSON(IServiceHub serviceHub = default) + public IDictionary 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 a63c9133..3c6cad30 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 object ConvertToJSON(IServiceHub serviceHub = default) + public IDictionary 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 56e12649..c074e616 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 object ConvertToJSON(IServiceHub serviceHub = default) + public IDictionary ConvertToJSON(IServiceHub serviceHub = default) { return new Dictionary { diff --git a/Parse/Infrastructure/Control/ParseFieldOperations.cs b/Parse/Infrastructure/Control/ParseFieldOperations.cs index 9aeac840..2454c5d4 100644 --- a/Parse/Infrastructure/Control/ParseFieldOperations.cs +++ b/Parse/Infrastructure/Control/ParseFieldOperations.cs @@ -1,6 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; + using Parse.Abstractions.Infrastructure.Control; +using Parse.Abstractions.Infrastructure.Data; +using Parse.Abstractions.Platform.Objects; namespace Parse.Infrastructure.Control; @@ -30,22 +34,77 @@ public int GetHashCode(object p) static class ParseFieldOperations { - private static ParseObjectIdComparer comparer; + private static readonly ParseObjectIdComparer comparer = new(); + public static IEqualityComparer ParseObjectComparer => comparer; - public static IParseFieldOperation Decode(IDictionary json) + /// + /// The factory method for creating IParseFieldOperation instances from JSON. + /// + /// The JSON dictionary representing the operation. + /// The decoder to be used for nested objects. + /// A concrete IParseFieldOperation. + public static IParseFieldOperation Decode(IDictionary json, IParseDataDecoder decoder + , IParseObjectClassController classController) { - throw new NotImplementedException(); - } + string opName = json["__op"] as string; - public static IEqualityComparer ParseObjectComparer - { - get + switch (opName) { - if (comparer == null) - { - comparer = new ParseObjectIdComparer(); - } - return comparer; + case "Delete": + return ParseDeleteOperation.Instance; + + case "Increment": + return new ParseIncrementOperation(json["amount"]); + + case "Add": + case "AddUnique": + case "Remove": + var objects = (json["objects"] as IEnumerable) + .Select(item => decoder.Decode(item)) // Recursively decode each item + .ToList(); + return opName switch + { + "Add" => new ParseAddOperation(objects), + "AddUnique" => new ParseAddUniqueOperation(objects), + "Remove" => new ParseRemoveOperation(objects), + _ => null // Should not happen + }; + + case "AddRelation": + case "RemoveRelation": + var relationObjects = (json["objects"] as IEnumerable) + .Select(item => decoder.Decode(item) as ParseObject) + .ToList(); + string targetClass = relationObjects.FirstOrDefault()?.ClassName; + var adds = opName == "AddRelation" ? relationObjects : new List(); + var removes = opName == "RemoveRelation" ? relationObjects : new List(); + return new ParseRelationOperation(classController, adds, removes); + + case "Batch": + var allAdds = new List(); + var allRemoves = new List(); + foreach (var op in json["ops"] as IEnumerable) + { + var opJson = op as IDictionary; + string innerOpName = opJson["__op"] as string; + var innerObjects = (opJson["objects"] as IEnumerable) + .Select(item => decoder.Decode(item) as ParseObject) + .ToList(); + + if (innerOpName == "AddRelation") + allAdds.AddRange(innerObjects); + if (innerOpName == "RemoveRelation") + allRemoves.AddRange(innerObjects); + } + return new ParseRelationOperation(classController, allAdds, allRemoves); + + default: + throw new NotSupportedException($"Decoding for operation '{opName}' is not supported."); } } + + + + + } diff --git a/Parse/Infrastructure/Control/ParseIncrementOperation.cs b/Parse/Infrastructure/Control/ParseIncrementOperation.cs index f2271e87..f5c68c6c 100644 --- a/Parse/Infrastructure/Control/ParseIncrementOperation.cs +++ b/Parse/Infrastructure/Control/ParseIncrementOperation.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; + using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Control; @@ -97,7 +98,7 @@ static ParseIncrementOperation() public ParseIncrementOperation(object amount) => Amount = amount; // Updated Encode to ConvertToJSON - public object ConvertToJSON(IServiceHub serviceHub = default) + public IDictionary 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 c489a592..ae8a36e3 100644 --- a/Parse/Infrastructure/Control/ParseRelationOperation.cs +++ b/Parse/Infrastructure/Control/ParseRelationOperation.cs @@ -82,7 +82,7 @@ IEnumerable GetIdsFromObjects(IEnumerable objects) return objects.Select(entity => entity.ObjectId).Distinct(); } - public object ConvertToJSON(IServiceHub serviceHub = null) + 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(); diff --git a/Parse/Infrastructure/Control/ParseRemoveOperation.cs b/Parse/Infrastructure/Control/ParseRemoveOperation.cs index 0f280cd8..899c6002 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 object ConvertToJSON(IServiceHub serviceHub = default) + public IDictionary 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 d6c1eae1..a15b0449 100644 --- a/Parse/Infrastructure/Control/ParseSetOperation.cs +++ b/Parse/Infrastructure/Control/ParseSetOperation.cs @@ -1,5 +1,8 @@ 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; @@ -14,7 +17,7 @@ public ParseSetOperation(object value) } // Replace Encode with ConvertToJSON - public object ConvertToJSON(IServiceHub serviceHub = default) + public IDictionary ConvertToJSON(IServiceHub serviceHub = default) { if (serviceHub == null) { @@ -26,7 +29,7 @@ public object ConvertToJSON(IServiceHub serviceHub = default) // For simple values, return them directly (avoid unnecessary __op) if (Value != null && (Value.GetType().IsPrimitive || Value is string)) { - return Value ; + return new Dictionary { ["value"] = Value }; } // If the encoded value is a dictionary, return it directly @@ -39,8 +42,6 @@ public object ConvertToJSON(IServiceHub serviceHub = default) throw new ArgumentException($"Unsupported type for encoding: {Value?.GetType()?.FullName}"); } - - public IParseFieldOperation MergeWithPrevious(IParseFieldOperation previous) { // Set operation always overrides previous operations @@ -52,6 +53,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..182758e2 100644 --- a/Parse/Infrastructure/Data/ParseDataDecoder.cs +++ b/Parse/Infrastructure/Data/ParseDataDecoder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; + using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Data; using Parse.Abstractions.Platform.Objects; @@ -12,56 +13,57 @@ namespace Parse.Infrastructure.Data; public class ParseDataDecoder : IParseDataDecoder { - IParseObjectClassController ClassController { get; } + private IServiceHub Services { get; } + private IParseObjectClassController ClassController => Services.ClassController; - public ParseDataDecoder(IParseObjectClassController classController) => ClassController = classController; + public ParseDataDecoder(IServiceHub serviceHub) => Services = serviceHub ?? throw new ArgumentNullException(nameof(serviceHub)); static string[] Types { get; } = { "Date", "Bytes", "Pointer", "File", "GeoPoint", "Object", "Relation" }; - public object Decode(object data, IServiceHub serviceHub) + public object Decode(object data) { - return data switch + return data switch + { + null => default, + IDictionary { } dictionary when dictionary.ContainsKey("__op") => ParseFieldOperations.Decode(dictionary, this, ClassController), + + 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"), + this.Services), + + "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, this.Services), + dictionary.TryGetValue("className", out var objClassName) ? objClassName as string : throw new KeyNotFoundException("Missing 'className' for Object type"), + this.Services), + + "Relation" => this.Services.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)), + IList { } list => list.Select(item => Decode(item)).ToList(), + _ => data + }; + } - protected virtual object DecodePointer(string className, string objectId, IServiceHub serviceHub) => - ClassController.CreateObjectWithoutData(className, objectId, serviceHub); + protected virtual object DecodePointer(string className, string objectId, IServiceHub services) => + ClassController.CreateObjectWithoutData(className, objectId, this.Services); public static DateTime? ParseDate(string input) { diff --git a/Parse/Infrastructure/Data/ParseDataEncoder.cs b/Parse/Infrastructure/Data/ParseDataEncoder.cs index ef39b50f..336bb5c0 100644 --- a/Parse/Infrastructure/Data/ParseDataEncoder.cs +++ b/Parse/Infrastructure/Data/ParseDataEncoder.cs @@ -3,8 +3,11 @@ 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.Platform.Objects; using Parse.Infrastructure.Control; using Parse.Infrastructure.Utilities; @@ -17,12 +20,17 @@ namespace Parse.Infrastructure.Data; /// public abstract class ParseDataEncoder { + private IServiceHub Services { get; } + private IParseObjectClassController ClassController => Services.ClassController; + + + private static readonly string[] SupportedDateFormats = ParseClient.DateFormatStrings; 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 +57,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 +71,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 +119,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 +147,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 +173,7 @@ private object EncodeDictionary(IDictionary dictionary, IService /// private object EncodeList(IEnumerable list, IServiceHub serviceHub) { - + List encoded = new(); foreach (var item in list) @@ -231,19 +195,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/Data/ParseObjectCoder.cs b/Parse/Infrastructure/Data/ParseObjectCoder.cs index 04a58d1d..54b0e085 100644 --- a/Parse/Infrastructure/Data/ParseObjectCoder.cs +++ b/Parse/Infrastructure/Data/ParseObjectCoder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; + using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Control; using Parse.Abstractions.Infrastructure.Data; @@ -9,10 +10,6 @@ namespace Parse.Infrastructure.Data; -// TODO: (richardross) refactor entire parse coder interfaces. -// Done: (YB) though, I wonder why Encode is never used in the ParseObjectCoder class. Might update if I find a use case. -//Got it now. The Encode method is used in ParseObjectController.cs - /// /// Provides methods to encode and decode Parse objects. @@ -91,7 +88,7 @@ public IObjectState Decode(IDictionary data, IParseDataDecoder d if (pair.Key == "__type" || pair.Key == "className") continue; - serverData[pair.Key] = decoder.Decode(pair.Value, serviceHub); + serverData[pair.Key] = decoder.Decode(pair.Value); } // Populate server data with primary properties @@ -116,24 +113,24 @@ public IObjectState Decode(IDictionary data, IParseDataDecoder d /// Extracts a value from a dictionary and removes the key. /// private static T Extract(IDictionary data, string key, Func action) -{ - if (data.TryGetValue(key, out var value)) { - data.Remove(key); - return action(value); - } + if (data.TryGetValue(key, out var value)) + { + data.Remove(key); + return action(value); + } - return default; -} + return default; + } -/// -/// Populates server data with a value if not already present. -/// -private static void PopulateServerData(IDictionary serverData, string key, object value) -{ - if (value != null && !serverData.ContainsKey(key)) + /// + /// Populates server data with a value if not already present. + /// + private static void PopulateServerData(IDictionary serverData, string key, object value) { - serverData[key] = value; + if (value != null && !serverData.ContainsKey(key)) + { + serverData[key] = value; + } } } -} diff --git a/Parse/Infrastructure/Execution/ParseCommandRunner.cs b/Parse/Infrastructure/Execution/ParseCommandRunner.cs index 5f937332..7e671826 100644 --- a/Parse/Infrastructure/Execution/ParseCommandRunner.cs +++ b/Parse/Infrastructure/Execution/ParseCommandRunner.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Net; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; + using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Execution; using Parse.Abstractions.Platform.Installations; @@ -71,12 +73,41 @@ 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 == 404) + + + if (responseCode == 400) + { + ParseErrorPayload payload = null; + try + { + payload = JsonSerializer.Deserialize(content); + } + catch (JsonException) + { + throw new ParseFailureException( + ParseFailureException.ErrorCode.BadRequest, + "Bad Request: unable to parse error payload"); + } + + ParseFailureException.ErrorCode code = (ParseFailureException.ErrorCode) payload.code; + + + throw new ParseFailureException(code: code, payload.error); + + } + else if (responseCode == 401) + { + throw new ParseFailureException(ParseFailureException.ErrorCode.InvalidSessionToken, content); + } + else if (responseCode == 403) + { + throw new ParseFailureException(ParseFailureException.ErrorCode.OperationForbidden, content); + } + else if (responseCode == 404) { - throw new ParseFailureException(ParseFailureException.ErrorCode.ERROR404, "Error 404"); + throw new ParseFailureException(ParseFailureException.ErrorCode.ERROR404, "Error 404 " +response.Item1+ response.Item2); } if (responseCode == 410) { @@ -193,7 +224,6 @@ async Task PrepareCommand(ParseCommand command) return newCommand; - //by the way, The original installationFetchTask variable was removed, as the async/await pattern eliminates the need for it. } } diff --git a/Parse/Infrastructure/LateInitializedMutableServiceHub.cs b/Parse/Infrastructure/LateInitializedMutableServiceHub.cs index b5c671f4..2eae2b31 100644 --- a/Parse/Infrastructure/LateInitializedMutableServiceHub.cs +++ b/Parse/Infrastructure/LateInitializedMutableServiceHub.cs @@ -150,7 +150,7 @@ public IParseCurrentInstallationController CurrentInstallationController public IParseDataDecoder Decoder { - get => LateInitializer.GetValue(() => new ParseDataDecoder(ClassController)); + get => LateInitializer.GetValue(() => new ParseDataDecoder(this)); set => LateInitializer.SetValue(value); } diff --git a/Parse/Infrastructure/MutableServiceHub.cs b/Parse/Infrastructure/MutableServiceHub.cs index 3cf50a0d..51be2d47 100644 --- a/Parse/Infrastructure/MutableServiceHub.cs +++ b/Parse/Infrastructure/MutableServiceHub.cs @@ -1,4 +1,5 @@ using System; + using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Data; using Parse.Abstractions.Infrastructure.Execution; @@ -79,8 +80,8 @@ public MutableServiceHub SetDefaults(IServerConnectionData connectionData = defa WebClient ??= new UniversalWebClient { }; CacheController ??= new CacheController { }; ClassController ??= new ParseObjectClassController { }; + Decoder ??= new ParseDataDecoder(this); - Decoder ??= new ParseDataDecoder(ClassController); InstallationController ??= new ParseInstallationController(CacheController); CommandRunner ??= new ParseCommandRunner(WebClient, InstallationController, MetadataController, ServerConnectionData, new Lazy(() => UserController)); diff --git a/Parse/Infrastructure/ParseFailureException.cs b/Parse/Infrastructure/ParseFailureException.cs index 086b7b33..9bc3b14d 100644 --- a/Parse/Infrastructure/ParseFailureException.cs +++ b/Parse/Infrastructure/ParseFailureException.cs @@ -254,6 +254,12 @@ public enum ErrorCode /// Twitter) is unsupported. /// UnsupportedService = 252, + + /// + /// Bad Request + /// + BadRequest = 400, + /// /// ERROR 404 /// @@ -267,3 +273,8 @@ public enum ErrorCode /// public ErrorCode Code { get; private set; } } +public class ParseErrorPayload +{ + public int code { get; set; } + public string error { get; set; } +} \ No newline at end of file diff --git a/Parse/Infrastructure/ServiceHub.cs b/Parse/Infrastructure/ServiceHub.cs index dbff4b24..60975a70 100644 --- a/Parse/Infrastructure/ServiceHub.cs +++ b/Parse/Infrastructure/ServiceHub.cs @@ -1,4 +1,5 @@ using System; + using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Data; using Parse.Abstractions.Infrastructure.Execution; @@ -45,7 +46,7 @@ public class ServiceHub : IServiceHub public ICacheController CacheController => LateInitializer.GetValue(() => new CacheController { }); public IParseObjectClassController ClassController => LateInitializer.GetValue(() => new ParseObjectClassController { }); - public IParseDataDecoder Decoder => LateInitializer.GetValue(() => new ParseDataDecoder(ClassController)); + public IParseDataDecoder Decoder { get; internal set; } public IParseInstallationController InstallationController => LateInitializer.GetValue(() => new ParseInstallationController(CacheController)); public IParseCommandRunner CommandRunner => LateInitializer.GetValue(() => new ParseCommandRunner(WebClient, InstallationController, MetadataController, ServerConnectionData, new Lazy(() => UserController))); diff --git a/Parse/Platform/Cloud/ParseCloudCodeController.cs b/Parse/Platform/Cloud/ParseCloudCodeController.cs index c1610d8f..c5b7ecd7 100644 --- a/Parse/Platform/Cloud/ParseCloudCodeController.cs +++ b/Parse/Platform/Cloud/ParseCloudCodeController.cs @@ -30,9 +30,6 @@ public async Task CallFunctionAsync( IProgress uploadProgress = null, IProgress downloadProgress = null) { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Function name cannot be null or empty.", nameof(name)); - try { // Prepare the command @@ -56,12 +53,7 @@ public async Task CallFunctionAsync( } // Decode the result - var decoded = Decoder.Decode(commandResult.Item2, serviceHub) as IDictionary; - - if (decoded == null) - { - throw new ParseFailureException(ParseFailureException.ErrorCode.OtherCause, "Failed to decode cloud function response."); - } + var decoded = Decoder.Decode(commandResult.Item2) as IDictionary; // Extract the result key if (decoded.TryGetValue("result", out var result)) @@ -85,11 +77,6 @@ public async Task CallFunctionAsync( // Rethrow known Parse exceptions throw; } - catch (Exception ex) - { - // Wrap unexpected exceptions - throw new ParseFailureException(ParseFailureException.ErrorCode.OtherCause, "An unexpected error occurred while calling the cloud function.", ex); - } } } diff --git a/Parse/Platform/Configuration/ParseConfiguration.cs b/Parse/Platform/Configuration/ParseConfiguration.cs index 356bfd1e..1211f393 100644 --- a/Parse/Platform/Configuration/ParseConfiguration.cs +++ b/Parse/Platform/Configuration/ParseConfiguration.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; + using Parse.Abstractions.Infrastructure; using Parse.Abstractions.Infrastructure.Data; using Parse.Infrastructure.Data; @@ -24,7 +25,7 @@ public class ParseConfiguration : IJsonConvertible internal static ParseConfiguration Create(IDictionary configurationData, IParseDataDecoder decoder, IServiceHub serviceHub) { - return new ParseConfiguration(decoder.Decode(configurationData["params"], serviceHub) as IDictionary, serviceHub); + return new ParseConfiguration(decoder.Decode(configurationData["params"]) as IDictionary, serviceHub); } /// @@ -106,7 +107,7 @@ public bool TryGetValue(string key, out T result) /// The value for the key. virtual public object this[string key] => Properties[key]; - public object ConvertToJSON(IServiceHub serviceHub = default) + public IDictionary ConvertToJSON(IServiceHub serviceHub = default) { return new Dictionary { diff --git a/Parse/Platform/Files/ParseFile.cs b/Parse/Platform/Files/ParseFile.cs index f86a113f..3559512c 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 object ConvertToJSON(IServiceHub serviceHub = default) + public IDictionary ConvertToJSON(IServiceHub serviceHub = default) { if (IsDirty) { diff --git a/Parse/Platform/Location/ParseGeoPoint.cs b/Parse/Platform/Location/ParseGeoPoint.cs index 18216f84..e9aa5ead 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 object ConvertToJSON(IServiceHub serviceHub = default) + public IDictionary ConvertToJSON(IServiceHub serviceHub = default) { return new Dictionary { {"__type", "GeoPoint"}, diff --git a/Parse/Platform/ParseClient.cs b/Parse/Platform/ParseClient.cs index 4e3b4a8b..5e0abac0 100644 --- a/Parse/Platform/ParseClient.cs +++ b/Parse/Platform/ParseClient.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; + using Parse.Abstractions.Infrastructure; -using Parse.Infrastructure.Utilities; using Parse.Infrastructure; +using Parse.Infrastructure.Data; +using Parse.Infrastructure.Utilities; #if DEBUG [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Parse.Tests")] @@ -94,6 +96,28 @@ public ParseClient(IServerConnectionData configuration, IServiceHub serviceHub = }; } + + FinalizeDecoder(Services); + void FinalizeDecoder(IServiceHub hubToFinalize) + { + if (hubToFinalize is OrchestrationServiceHub orchestrationHub) + { + FinalizeDecoder(orchestrationHub.Default); + FinalizeDecoder(orchestrationHub.Custom); + return; + } + + if (hubToFinalize is ServiceHub hub && hub.Decoder == null) + { + hub.Decoder = new ParseDataDecoder(hub); + } + + else if (hubToFinalize is MutableServiceHub mutableHub && mutableHub.Decoder == null) + { + mutableHub.Decoder = new ParseDataDecoder(mutableHub); + } + } + Services.ClassController.AddIntrinsic(); } diff --git a/Parse/Platform/Queries/ParseQuery.cs b/Parse/Platform/Queries/ParseQuery.cs index 4587c85b..ebe8af97 100644 --- a/Parse/Platform/Queries/ParseQuery.cs +++ b/Parse/Platform/Queries/ParseQuery.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; + using Parse.Abstractions.Infrastructure; using Parse.Infrastructure; using Parse.Infrastructure.Data; @@ -810,6 +811,31 @@ public Task GetAsync(string objectId) return GetAsync(objectId, CancellationToken.None); } + public static ParseQuery Or(params ParseQuery[] queries) + { + if (queries.Length == 0) + { + throw new ArgumentException("You must provide at least one query to Or."); + } + + string className = queries[0].ClassName; + var serviceHub = queries[0].Services; + + if (queries.Any(q => q.ClassName != className)) + { + throw new ArgumentException("All queries in an Or query must be for the same class."); + } + + var orClause = new Dictionary + { + ["$or"] = queries.Select(q => q.Filters).ToList() + }; + + var resultQuery = new ParseQuery(serviceHub, className); + return new ParseQuery(resultQuery, where: orClause); + } + + /// /// Constructs a ParseObject whose id is already known by fetching data /// from the server. @@ -908,7 +934,11 @@ public override bool Equals(object obj) /// A hash code for the current object. public override int GetHashCode() { - // TODO (richardross): Implement this. - return 0; + int hash = ClassName.GetHashCode(); + hash = (hash * 31) + (Filters?.GetHashCode() ?? 0); + hash = (hash * 31) + (Orderings?.GetHashCode() ?? 0); + hash = (hash * 31) + (SkipAmount?.GetHashCode() ?? 0); + hash = (hash * 31) + (LimitAmount?.GetHashCode() ?? 0); + return hash; } } diff --git a/Parse/Platform/Relations/ParseRelation.cs b/Parse/Platform/Relations/ParseRelation.cs index aca8f0d7..b0d65ee2 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 object ConvertToJSON(IServiceHub serviceHub = default) + public IDictionary ConvertToJSON(IServiceHub serviceHub = default) { return new Dictionary { diff --git a/Parse/Platform/Security/ParseACL.cs b/Parse/Platform/Security/ParseACL.cs index 3154a51a..30d3376e 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 object ConvertToJSON(IServiceHub serviceHub = default) + public IDictionary ConvertToJSON(IServiceHub serviceHub = default) { Dictionary result = new Dictionary(); foreach (string user in readers.Union(writers)) diff --git a/Parse/Platform/Users/ParseUser.cs b/Parse/Platform/Users/ParseUser.cs index f2e62cd8..dbe8d618 100644 --- a/Parse/Platform/Users/ParseUser.cs +++ b/Parse/Platform/Users/ParseUser.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; + using Parse.Abstractions.Platform.Authentication; using Parse.Abstractions.Platform.Objects; @@ -136,7 +137,6 @@ protected override async Task SaveAsync(Task toAwait, CancellationToken cancella internal override async Task FetchAsyncInternal(CancellationToken cancellationToken) { - //await toAwait.ConfigureAwait(false); var result = await base.FetchAsyncInternal(cancellationToken).ConfigureAwait(false); @@ -168,7 +168,6 @@ internal async Task UpgradeToRevocableSessionAsync(CancellationToken cancellatio var newSessionToken = await Services.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken).ConfigureAwait(false); await SetSessionTokenAsync(newSessionToken, cancellationToken).ConfigureAwait(false); } - //public string SessionToken => State.ContainsKey("sessionToken") ? State["sessionToken"] as string : null; public IDictionary> AuthData { diff --git a/Parse/Utilities/ParseQueryExtensions.cs b/Parse/Utilities/ParseQueryExtensions.cs index 7e34f29f..8592fd82 100644 --- a/Parse/Utilities/ParseQueryExtensions.cs +++ b/Parse/Utilities/ParseQueryExtensions.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; + using Parse.Infrastructure.Data; namespace Parse.Abstractions.Internal; @@ -268,25 +269,6 @@ protected override Expression VisitBinary(BinaryExpression node) /// protected override Expression VisitUnary(UnaryExpression node) { - // This is incorrect because control is supposed to be able to flow out of the binaryOperand case if the value of NodeType is not matched against an ExpressionType value, which it will not do. - // - // return node switch - // { - // { NodeType: ExpressionType.Not, Operand: var operand } => Visit(operand) switch - // { - // BinaryExpression { Left: var left, Right: var right, NodeType: var type } binaryOperand => type switch - // { - // ExpressionType.GreaterThan => Expression.LessThanOrEqual(left, right), - // ExpressionType.GreaterThanOrEqual => Expression.LessThan(left, right), - // ExpressionType.LessThan => Expression.GreaterThanOrEqual(left, right), - // ExpressionType.LessThanOrEqual => Expression.GreaterThan(left, right), - // ExpressionType.Equal => Expression.NotEqual(left, right), - // ExpressionType.NotEqual => Expression.Equal(left, right), - // }, - // _ => base.VisitUnary(node) - // }, - // _ => base.VisitUnary(node) - // }; // Normalizes inversion