1+ using System ;
12using System . Collections . Generic ;
3+ using System . Diagnostics ;
4+ using System . Linq ;
5+ using System . Threading . Tasks ;
26using Microsoft . VisualStudio . TestTools . UnitTesting ;
7+ using Moq ;
8+ using Parse . Abstractions . Infrastructure . Control ;
9+ using Parse . Abstractions . Infrastructure ;
310using Parse . Abstractions . Internal ;
11+ using Parse . Abstractions . Platform . Objects ;
412using Parse . Infrastructure ;
13+ using Microsoft . VisualStudio . TestPlatform . ObjectModel ;
14+ using Parse . Platform . Objects ;
15+ using System . Threading ;
16+ using Parse . Abstractions . Platform . Users ;
517
618namespace Parse . Tests ;
719
820[ TestClass ]
921public class RelationTests
1022{
23+ [ ParseClassName ( "TestObject" ) ]
24+ private class TestObject : ParseObject { }
25+
26+ [ ParseClassName ( "Friend" ) ]
27+ private class Friend : ParseObject { }
28+
29+ private ParseClient Client { get ; set ; }
30+
31+ [ TestInitialize ]
32+ public void SetUp ( )
33+ {
34+ // Initialize the client and ensure the instance is set
35+ Client = new ParseClient ( new ServerConnectionData { Test = true } ) ;
36+ Client . Publicize ( ) ;
37+
38+ // Register the test classes
39+ Client . RegisterSubclass ( typeof ( TestObject ) ) ;
40+ Client . RegisterSubclass ( typeof ( Friend ) ) ;
41+ Client . RegisterSubclass ( typeof ( ParseUser ) ) ;
42+ Client . RegisterSubclass ( typeof ( ParseSession ) ) ;
43+ Client . RegisterSubclass ( typeof ( ParseUser ) ) ;
44+
45+ // **--- Mocking Setup ---**
46+ var hub = new MutableServiceHub ( ) ; // Use MutableServiceHub for mocking
47+ var mockUserController = new Mock < IParseUserController > ( ) ;
48+ var mockObjectController = new Mock < IParseObjectController > ( ) ;
49+
50+ // **Mock SignUpAsync for ParseUser:**
51+ mockUserController
52+ . Setup ( controller => controller . SignUpAsync (
53+ It . IsAny < IObjectState > ( ) ,
54+ It . IsAny < IDictionary < string , IParseFieldOperation > > ( ) ,
55+ It . IsAny < IServiceHub > ( ) ,
56+ It . IsAny < CancellationToken > ( ) ) )
57+ . ReturnsAsync ( new MutableObjectState { ObjectId = "some0neTol4v4" } ) ; // Predefined ObjectId for User
58+
59+ // **Mock SaveAsync for ParseObject (Friend objects):**
60+ int objectSaveCounter = 1 ; // Counter for Friend ObjectIds
61+ mockObjectController
62+ . Setup ( controller => controller . SaveAsync (
63+ It . IsAny < IObjectState > ( ) ,
64+ It . IsAny < IDictionary < string , IParseFieldOperation > > ( ) ,
65+ It . IsAny < string > ( ) ,
66+ It . IsAny < IServiceHub > ( ) ,
67+ It . IsAny < CancellationToken > ( ) ) )
68+ . ReturnsAsync ( ( ) => // Use a lambda to generate different ObjectIds for each Friend
69+ {
70+ return new MutableObjectState { ObjectId = $ "mockFriendObjectId{ objectSaveCounter ++ } " } ;
71+ } ) ;
72+
73+ // **Inject Mocks into ServiceHub:**
74+ hub . UserController = mockUserController . Object ;
75+ hub . ObjectController = mockObjectController . Object ;
76+ //(Client.Services as ServiceHub)..ReplaceWith(hub); // Replace the Client's ServiceHub with the MutableServiceHub
77+
78+ }
79+
80+ [ TestCleanup ]
81+ public void TearDown ( ) => ( Client . Services as ServiceHub ) . Reset ( ) ;
82+
1183 [ TestMethod ]
1284 public void TestRelationQuery ( )
1385 {
@@ -24,4 +96,280 @@ public void TestRelationQuery()
2496
2597 Assert . AreEqual ( "child" , encoded [ "redirectClassNameForKey" ] ) ;
2698 }
27- }
99+
100+ [ TestMethod ]
101+ [ Description ( "Tests AddRelationToUserAsync throws exception when user is null" ) ] // Mock difficulty: 1
102+ public async Task AddRelationToUserAsync_ThrowsException_WhenUserIsNull ( )
103+ {
104+
105+ var relatedObjects = new List < ParseObject >
106+ {
107+ new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "Friend1" }
108+ } ;
109+
110+ await Assert . ThrowsExceptionAsync < ArgumentNullException > ( ( ) => UserManagement . AddRelationToUserAsync ( null , "friends" , relatedObjects ) ) ;
111+
112+ }
113+ [ TestMethod ]
114+ [ Description ( "Tests AddRelationToUserAsync throws exception when relationfield is null" ) ] // Mock difficulty: 1
115+ public async Task AddRelationToUserAsync_ThrowsException_WhenRelationFieldIsNull ( )
116+ {
117+ var user = new ParseUser ( ) { Username = "TestUser" , Password = "TestPass" , Services = Client . Services } ;
118+ await user . SignUpAsync ( ) ;
119+ var relatedObjects = new List < ParseObject >
120+ {
121+ new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "Friend1" }
122+ } ;
123+ await Assert . ThrowsExceptionAsync < ArgumentException > ( ( ) => UserManagement . AddRelationToUserAsync ( user , null , relatedObjects ) ) ;
124+ }
125+
126+ [ TestMethod ]
127+ [ Description ( "Tests UpdateUserRelationAsync throws exception when user is null" ) ] // Mock difficulty: 1
128+ public async Task UpdateUserRelationAsync_ThrowsException_WhenUserIsNull ( )
129+ {
130+ var relatedObjectsToAdd = new List < ParseObject >
131+ {
132+ new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "Friend1" }
133+ } ;
134+ var relatedObjectsToRemove = new List < ParseObject >
135+ {
136+ new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "Friend2" }
137+ } ;
138+
139+
140+ await Assert . ThrowsExceptionAsync < ArgumentNullException > ( ( ) => UserManagement . UpdateUserRelationAsync ( null , "friends" , relatedObjectsToAdd , relatedObjectsToRemove ) ) ;
141+ }
142+ [ TestMethod ]
143+ [ Description ( "Tests UpdateUserRelationAsync throws exception when relationfield is null" ) ] // Mock difficulty: 1
144+ public async Task UpdateUserRelationAsync_ThrowsException_WhenRelationFieldIsNull ( )
145+ {
146+ var user = new ParseUser ( ) { Username = "TestUser" , Password = "TestPass" , Services = Client . Services } ;
147+ await user . SignUpAsync ( ) ;
148+
149+ var relatedObjectsToAdd = new List < ParseObject >
150+ {
151+ new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "Friend1" }
152+ } ;
153+ var relatedObjectsToRemove = new List < ParseObject >
154+ {
155+ new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "Friend2" }
156+ } ;
157+
158+
159+ await Assert . ThrowsExceptionAsync < ArgumentException > ( ( ) => UserManagement . UpdateUserRelationAsync ( user , null , relatedObjectsToAdd , relatedObjectsToRemove ) ) ;
160+ }
161+ [ TestMethod ]
162+ [ Description ( "Tests DeleteUserRelationAsync throws exception when user is null" ) ] // Mock difficulty: 1
163+ public async Task DeleteUserRelationAsync_ThrowsException_WhenUserIsNull ( )
164+ {
165+ await Assert . ThrowsExceptionAsync < ArgumentNullException > ( ( ) => UserManagement . DeleteUserRelationAsync ( null , "friends" ) ) ;
166+ }
167+ [ TestMethod ]
168+ [ Description ( "Tests DeleteUserRelationAsync throws exception when relationfield is null" ) ] // Mock difficulty: 1
169+ public async Task DeleteUserRelationAsync_ThrowsException_WhenRelationFieldIsNull ( )
170+ {
171+ var user = new ParseUser ( ) { Username = "TestUser" , Password = "TestPass" , Services = Client . Services } ;
172+ await user . SignUpAsync ( ) ;
173+
174+ await Assert . ThrowsExceptionAsync < ArgumentException > ( ( ) => UserManagement . DeleteUserRelationAsync ( user , null ) ) ;
175+ }
176+ [ TestMethod ]
177+ [ Description ( "Tests GetUserRelationsAsync throws exception when user is null" ) ] // Mock difficulty: 1
178+ public async Task GetUserRelationsAsync_ThrowsException_WhenUserIsNull ( )
179+ {
180+ await Assert . ThrowsExceptionAsync < ArgumentNullException > ( ( ) => UserManagement . GetUserRelationsAsync ( null , "friends" ) ) ;
181+ }
182+ [ TestMethod ]
183+ [ Description ( "Tests GetUserRelationsAsync throws exception when relationfield is null" ) ] // Mock difficulty: 1
184+ public async Task GetUserRelationsAsync_ThrowsException_WhenRelationFieldIsNull ( )
185+ {
186+ var user = new ParseUser ( ) { Username = "TestUser" , Password = "TestPass" , Services = Client . Services } ;
187+ await user . SignUpAsync ( ) ;
188+
189+ await Assert . ThrowsExceptionAsync < ArgumentException > ( ( ) => UserManagement . GetUserRelationsAsync ( user , null ) ) ;
190+ }
191+
192+
193+
194+ [ TestMethod ]
195+ [ Description ( "Tests that AddRelationToUserAsync throws when a related object is unsaved" ) ]
196+ public async Task AddRelationToUserAsync_ThrowsException_WhenRelatedObjectIsUnsaved ( )
197+ {
198+ // Arrange: Create and sign up a test user.
199+ var user = new ParseUser ( ) { Username = "TestUser" , Password = "TestPass" , Services = Client . Services } ;
200+ await user . SignUpAsync ( ) ;
201+
202+ // Create an unsaved Friend object (do NOT call SaveAsync).
203+ var unsavedFriend = new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "UnsavedFriend" } ;
204+ var relatedObjects = new List < ParseObject > { unsavedFriend } ;
205+
206+ // Act & Assert: Expect an exception when trying to add an unsaved object.
207+ await Assert . ThrowsExceptionAsync < ArgumentException > ( ( ) =>
208+ UserManagement . AddRelationToUserAsync ( user , "friends" , relatedObjects ) ) ;
209+ }
210+
211+
212+
213+ }
214+
215+ public static class UserManagement
216+ {
217+ public static async Task AddRelationToUserAsync ( ParseUser user , string relationField , IList < ParseObject > relatedObjects )
218+ {
219+ if ( user == null )
220+ {
221+ throw new ArgumentNullException ( nameof ( user ) , "User must not be null." ) ;
222+ }
223+
224+ if ( string . IsNullOrEmpty ( relationField ) )
225+ {
226+ throw new ArgumentException ( "Relation field must not be null or empty." , nameof ( relationField ) ) ;
227+ }
228+
229+ if ( relatedObjects == null || relatedObjects . Count == 0 )
230+ {
231+ Debug . WriteLine ( "No objects provided to add to the relation." ) ;
232+ return ;
233+ }
234+
235+ var relation = user . GetRelation < ParseObject > ( relationField ) ;
236+
237+ foreach ( var obj in relatedObjects )
238+ {
239+ relation . Add ( obj ) ;
240+ }
241+
242+ await user . SaveAsync ( ) ;
243+ Debug . WriteLine ( $ "Added { relatedObjects . Count } objects to the '{ relationField } ' relation for user '{ user . Username } '.") ;
244+ }
245+ public static async Task UpdateUserRelationAsync ( ParseUser user , string relationField , IList < ParseObject > toAdd , IList < ParseObject > toRemove )
246+ {
247+ if ( user == null )
248+ {
249+ throw new ArgumentNullException ( nameof ( user ) , "User must not be null." ) ;
250+ }
251+
252+ if ( string . IsNullOrEmpty ( relationField ) )
253+ {
254+ throw new ArgumentException ( "Relation field must not be null or empty." , nameof ( relationField ) ) ;
255+ }
256+
257+ var relation = user . GetRelation < ParseObject > ( relationField ) ;
258+
259+ // Add objects to the relation
260+ if ( toAdd != null && toAdd . Count > 0 )
261+ {
262+ foreach ( var obj in toAdd )
263+ {
264+ relation . Add ( obj ) ;
265+ }
266+ Debug . WriteLine ( $ "Added { toAdd . Count } objects to the '{ relationField } ' relation.") ;
267+ }
268+
269+ // Remove objects from the relation
270+ if ( toRemove != null && toRemove . Count > 0 )
271+ {
272+
273+ foreach ( var obj in toRemove )
274+ {
275+ relation . Remove ( obj ) ;
276+ }
277+ Debug . WriteLine ( $ "Removed { toRemove . Count } objects from the '{ relationField } ' relation.") ;
278+ }
279+
280+ await user . SaveAsync ( ) ;
281+ }
282+ public static async Task DeleteUserRelationAsync ( ParseUser user , string relationField )
283+ {
284+ if ( user == null )
285+ {
286+ throw new ArgumentNullException ( nameof ( user ) , "User must not be null." ) ;
287+ }
288+
289+ if ( string . IsNullOrEmpty ( relationField ) )
290+ {
291+ throw new ArgumentException ( "Relation field must not be null or empty." , nameof ( relationField ) ) ;
292+ }
293+
294+ var relation = user . GetRelation < ParseObject > ( relationField ) ;
295+ var relatedObjects = await relation . Query . FindAsync ( ) ;
296+
297+
298+ foreach ( var obj in relatedObjects )
299+ {
300+ relation . Remove ( obj ) ;
301+ }
302+
303+ await user . SaveAsync ( ) ;
304+ Debug . WriteLine ( $ "Removed all objects from the '{ relationField } ' relation for user '{ user . Username } '.") ;
305+ }
306+ public static async Task ManageUserRelationsAsync ( ParseClient client )
307+ {
308+ // Get the current user
309+ var user = await ParseClient . Instance . GetCurrentUser ( ) ;
310+
311+ if ( user == null )
312+ {
313+ Debug . WriteLine ( "No user is currently logged in." ) ;
314+ return ;
315+ }
316+
317+ const string relationField = "friends" ; // Example relation field name
318+
319+ // Create related objects to add
320+ var relatedObjectsToAdd = new List < ParseObject >
321+ {
322+ new ParseObject ( "Friend" , client . Services ) { [ "name" ] = "Alice" } ,
323+ new ParseObject ( "Friend" , client . Services ) { [ "name" ] = "Bob" }
324+ } ;
325+
326+ // Save related objects to the server before adding to the relation
327+ foreach ( var obj in relatedObjectsToAdd )
328+ {
329+ await obj . SaveAsync ( ) ;
330+ }
331+
332+ // Add objects to the relation
333+ await AddRelationToUserAsync ( user , relationField , relatedObjectsToAdd ) ;
334+
335+ // Query the relation
336+ var relatedObjects = await GetUserRelationsAsync ( user , relationField ) ;
337+
338+ // Update the relation (add and remove objects)
339+ var relatedObjectsToRemove = new List < ParseObject > { relatedObjects [ 0 ] } ; // Remove the first related object
340+ var newObjectsToAdd = new List < ParseObject >
341+ {
342+ new ParseObject ( "Friend" , client . Services ) { [ "name" ] = "Charlie" }
343+ } ;
344+
345+ foreach ( var obj in newObjectsToAdd )
346+ {
347+ await obj . SaveAsync ( ) ;
348+ }
349+
350+ await UpdateUserRelationAsync ( user , relationField , newObjectsToAdd , relatedObjectsToRemove ) ;
351+
352+ // Delete the relation
353+ // await DeleteUserRelationAsync(user, relationField);
354+ }
355+ public static async Task < IList < ParseObject > > GetUserRelationsAsync ( ParseUser user , string relationField )
356+ {
357+ if ( user == null )
358+ {
359+ throw new ArgumentNullException ( nameof ( user ) , "User must not be null." ) ;
360+ }
361+
362+ if ( string . IsNullOrEmpty ( relationField ) )
363+ {
364+ throw new ArgumentException ( "Relation field must not be null or empty." , nameof ( relationField ) ) ;
365+ }
366+
367+ var relation = user . GetRelation < ParseObject > ( relationField ) ;
368+
369+ var results = await relation . Query . FindAsync ( ) ;
370+ Debug . WriteLine ( $ "Retrieved { results . Count ( ) } objects from the '{ relationField } ' relation for user '{ user . Username } '.") ;
371+ return results . ToList ( ) ;
372+ }
373+
374+ }
375+
0 commit comments