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+
77+ }
78+
79+ [ TestCleanup ]
80+ public void TearDown ( ) => ( Client . Services as ServiceHub ) . Reset ( ) ;
81+
1182 [ TestMethod ]
1283 public void TestRelationQuery ( )
1384 {
@@ -24,4 +95,278 @@ public void TestRelationQuery()
2495
2596 Assert . AreEqual ( "child" , encoded [ "redirectClassNameForKey" ] ) ;
2697 }
27- }
98+
99+ [ TestMethod ]
100+ [ Description ( "Tests AddRelationToUserAsync throws exception when user is null" ) ] // Mock difficulty: 1
101+ public async Task AddRelationToUserAsync_ThrowsException_WhenUserIsNull ( )
102+ {
103+
104+ var relatedObjects = new List < ParseObject >
105+ {
106+ new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "Friend1" }
107+ } ;
108+
109+ await Assert . ThrowsExceptionAsync < ArgumentNullException > ( ( ) => UserManagement . AddRelationToUserAsync ( null , "friends" , relatedObjects ) ) ;
110+
111+ }
112+ [ TestMethod ]
113+ [ Description ( "Tests AddRelationToUserAsync throws exception when relationfield is null" ) ] // Mock difficulty: 1
114+ public async Task AddRelationToUserAsync_ThrowsException_WhenRelationFieldIsNull ( )
115+ {
116+ var user = new ParseUser ( ) { Username = "TestUser" , Password = "TestPass" , Services = Client . Services } ;
117+ await user . SignUpAsync ( ) ;
118+ var relatedObjects = new List < ParseObject >
119+ {
120+ new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "Friend1" }
121+ } ;
122+ await Assert . ThrowsExceptionAsync < ArgumentException > ( ( ) => UserManagement . AddRelationToUserAsync ( user , null , relatedObjects ) ) ;
123+ }
124+
125+ [ TestMethod ]
126+ [ Description ( "Tests UpdateUserRelationAsync throws exception when user is null" ) ] // Mock difficulty: 1
127+ public async Task UpdateUserRelationAsync_ThrowsException_WhenUserIsNull ( )
128+ {
129+ var relatedObjectsToAdd = new List < ParseObject >
130+ {
131+ new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "Friend1" }
132+ } ;
133+ var relatedObjectsToRemove = new List < ParseObject >
134+ {
135+ new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "Friend2" }
136+ } ;
137+
138+
139+ await Assert . ThrowsExceptionAsync < ArgumentNullException > ( ( ) => UserManagement . UpdateUserRelationAsync ( null , "friends" , relatedObjectsToAdd , relatedObjectsToRemove ) ) ;
140+ }
141+ [ TestMethod ]
142+ [ Description ( "Tests UpdateUserRelationAsync throws exception when relationfield is null" ) ] // Mock difficulty: 1
143+ public async Task UpdateUserRelationAsync_ThrowsException_WhenRelationFieldIsNull ( )
144+ {
145+ var user = new ParseUser ( ) { Username = "TestUser" , Password = "TestPass" , Services = Client . Services } ;
146+ await user . SignUpAsync ( ) ;
147+
148+ var relatedObjectsToAdd = new List < ParseObject >
149+ {
150+ new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "Friend1" }
151+ } ;
152+ var relatedObjectsToRemove = new List < ParseObject >
153+ {
154+ new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "Friend2" }
155+ } ;
156+
157+
158+ await Assert . ThrowsExceptionAsync < ArgumentException > ( ( ) => UserManagement . UpdateUserRelationAsync ( user , null , relatedObjectsToAdd , relatedObjectsToRemove ) ) ;
159+ }
160+ [ TestMethod ]
161+ [ Description ( "Tests DeleteUserRelationAsync throws exception when user is null" ) ] // Mock difficulty: 1
162+ public async Task DeleteUserRelationAsync_ThrowsException_WhenUserIsNull ( )
163+ {
164+ await Assert . ThrowsExceptionAsync < ArgumentNullException > ( ( ) => UserManagement . DeleteUserRelationAsync ( null , "friends" ) ) ;
165+ }
166+ [ TestMethod ]
167+ [ Description ( "Tests DeleteUserRelationAsync throws exception when relationfield is null" ) ] // Mock difficulty: 1
168+ public async Task DeleteUserRelationAsync_ThrowsException_WhenRelationFieldIsNull ( )
169+ {
170+ var user = new ParseUser ( ) { Username = "TestUser" , Password = "TestPass" , Services = Client . Services } ;
171+ await user . SignUpAsync ( ) ;
172+
173+ await Assert . ThrowsExceptionAsync < ArgumentException > ( ( ) => UserManagement . DeleteUserRelationAsync ( user , null ) ) ;
174+ }
175+ [ TestMethod ]
176+ [ Description ( "Tests GetUserRelationsAsync throws exception when user is null" ) ] // Mock difficulty: 1
177+ public async Task GetUserRelationsAsync_ThrowsException_WhenUserIsNull ( )
178+ {
179+ await Assert . ThrowsExceptionAsync < ArgumentNullException > ( ( ) => UserManagement . GetUserRelationsAsync ( null , "friends" ) ) ;
180+ }
181+ [ TestMethod ]
182+ [ Description ( "Tests GetUserRelationsAsync throws exception when relationfield is null" ) ] // Mock difficulty: 1
183+ public async Task GetUserRelationsAsync_ThrowsException_WhenRelationFieldIsNull ( )
184+ {
185+ var user = new ParseUser ( ) { Username = "TestUser" , Password = "TestPass" , Services = Client . Services } ;
186+ await user . SignUpAsync ( ) ;
187+
188+ await Assert . ThrowsExceptionAsync < ArgumentException > ( ( ) => UserManagement . GetUserRelationsAsync ( user , null ) ) ;
189+ }
190+
191+
192+
193+ [ TestMethod ]
194+ [ Description ( "Tests that AddRelationToUserAsync throws when a related object is unsaved" ) ]
195+ public async Task AddRelationToUserAsync_ThrowsException_WhenRelatedObjectIsUnsaved ( )
196+ {
197+ // Arrange: Create and sign up a test user.
198+ var user = new ParseUser ( ) { Username = "TestUser" , Password = "TestPass" , Services = Client . Services } ;
199+ await user . SignUpAsync ( ) ;
200+
201+ // Create an unsaved Friend object (do NOT call SaveAsync).
202+ var unsavedFriend = new ParseObject ( "Friend" , Client . Services ) { [ "name" ] = "UnsavedFriend" } ;
203+ var relatedObjects = new List < ParseObject > { unsavedFriend } ;
204+
205+ // Act & Assert: Expect an exception when trying to add an unsaved object.
206+ await Assert . ThrowsExceptionAsync < ArgumentException > ( ( ) =>
207+ UserManagement . AddRelationToUserAsync ( user , "friends" , relatedObjects ) ) ;
208+ }
209+
210+
211+
212+ }
213+
214+ public static class UserManagement
215+ {
216+ public static async Task AddRelationToUserAsync ( ParseUser user , string relationField , IList < ParseObject > relatedObjects )
217+ {
218+ if ( user == null )
219+ {
220+ throw new ArgumentNullException ( nameof ( user ) , "User must not be null." ) ;
221+ }
222+
223+ if ( string . IsNullOrEmpty ( relationField ) )
224+ {
225+ throw new ArgumentException ( "Relation field must not be null or empty." , nameof ( relationField ) ) ;
226+ }
227+
228+ if ( relatedObjects == null || relatedObjects . Count == 0 )
229+ {
230+ Debug . WriteLine ( "No objects provided to add to the relation." ) ;
231+ return ;
232+ }
233+
234+ var relation = user . GetRelation < ParseObject > ( relationField ) ;
235+
236+ foreach ( var obj in relatedObjects )
237+ {
238+ relation . Add ( obj ) ;
239+ }
240+
241+ await user . SaveAsync ( ) ;
242+ Debug . WriteLine ( $ "Added { relatedObjects . Count } objects to the '{ relationField } ' relation for user '{ user . Username } '.") ;
243+ }
244+ public static async Task UpdateUserRelationAsync ( ParseUser user , string relationField , IList < ParseObject > toAdd , IList < ParseObject > toRemove )
245+ {
246+ if ( user == null )
247+ {
248+ throw new ArgumentNullException ( nameof ( user ) , "User must not be null." ) ;
249+ }
250+
251+ if ( string . IsNullOrEmpty ( relationField ) )
252+ {
253+ throw new ArgumentException ( "Relation field must not be null or empty." , nameof ( relationField ) ) ;
254+ }
255+
256+ var relation = user . GetRelation < ParseObject > ( relationField ) ;
257+
258+ // Add objects to the relation
259+ if ( toAdd != null && toAdd . Count > 0 )
260+ {
261+ foreach ( var obj in toAdd )
262+ {
263+ relation . Add ( obj ) ;
264+ }
265+ Debug . WriteLine ( $ "Added { toAdd . Count } objects to the '{ relationField } ' relation.") ;
266+ }
267+
268+ // Remove objects from the relation
269+ if ( toRemove != null && toRemove . Count > 0 )
270+ {
271+
272+ foreach ( var obj in toRemove )
273+ {
274+ relation . Remove ( obj ) ;
275+ }
276+ Debug . WriteLine ( $ "Removed { toRemove . Count } objects from the '{ relationField } ' relation.") ;
277+ }
278+
279+ await user . SaveAsync ( ) ;
280+ }
281+ public static async Task DeleteUserRelationAsync ( ParseUser user , string relationField )
282+ {
283+ if ( user == null )
284+ {
285+ throw new ArgumentNullException ( nameof ( user ) , "User must not be null." ) ;
286+ }
287+
288+ if ( string . IsNullOrEmpty ( relationField ) )
289+ {
290+ throw new ArgumentException ( "Relation field must not be null or empty." , nameof ( relationField ) ) ;
291+ }
292+
293+ var relation = user . GetRelation < ParseObject > ( relationField ) ;
294+ var relatedObjects = await relation . Query . FindAsync ( ) ;
295+
296+
297+ foreach ( var obj in relatedObjects )
298+ {
299+ relation . Remove ( obj ) ;
300+ }
301+
302+ await user . SaveAsync ( ) ;
303+ Debug . WriteLine ( $ "Removed all objects from the '{ relationField } ' relation for user '{ user . Username } '.") ;
304+ }
305+ public static async Task ManageUserRelationsAsync ( ParseClient client )
306+ {
307+ // Get the current user
308+ var user = await ParseClient . Instance . GetCurrentUser ( ) ;
309+
310+ if ( user == null )
311+ {
312+ Debug . WriteLine ( "No user is currently logged in." ) ;
313+ return ;
314+ }
315+
316+ const string relationField = "friends" ; // Example relation field name
317+
318+ // Create related objects to add
319+ var relatedObjectsToAdd = new List < ParseObject >
320+ {
321+ new ParseObject ( "Friend" , client . Services ) { [ "name" ] = "Alice" } ,
322+ new ParseObject ( "Friend" , client . Services ) { [ "name" ] = "Bob" }
323+ } ;
324+
325+ // Save related objects to the server before adding to the relation
326+ foreach ( var obj in relatedObjectsToAdd )
327+ {
328+ await obj . SaveAsync ( ) ;
329+ }
330+
331+ // Add objects to the relation
332+ await AddRelationToUserAsync ( user , relationField , relatedObjectsToAdd ) ;
333+
334+ // Query the relation
335+ var relatedObjects = await GetUserRelationsAsync ( user , relationField ) ;
336+
337+ // Update the relation (add and remove objects)
338+ var relatedObjectsToRemove = new List < ParseObject > { relatedObjects [ 0 ] } ; // Remove the first related object
339+ var newObjectsToAdd = new List < ParseObject >
340+ {
341+ new ParseObject ( "Friend" , client . Services ) { [ "name" ] = "Charlie" }
342+ } ;
343+
344+ foreach ( var obj in newObjectsToAdd )
345+ {
346+ await obj . SaveAsync ( ) ;
347+ }
348+
349+ await UpdateUserRelationAsync ( user , relationField , newObjectsToAdd , relatedObjectsToRemove ) ;
350+
351+ }
352+ public static async Task < IList < ParseObject > > GetUserRelationsAsync ( ParseUser user , string relationField )
353+ {
354+ if ( user == null )
355+ {
356+ throw new ArgumentNullException ( nameof ( user ) , "User must not be null." ) ;
357+ }
358+
359+ if ( string . IsNullOrEmpty ( relationField ) )
360+ {
361+ throw new ArgumentException ( "Relation field must not be null or empty." , nameof ( relationField ) ) ;
362+ }
363+
364+ var relation = user . GetRelation < ParseObject > ( relationField ) ;
365+
366+ var results = await relation . Query . FindAsync ( ) ;
367+ Debug . WriteLine ( $ "Retrieved { results . Count ( ) } objects from the '{ relationField } ' relation for user '{ user . Username } '.") ;
368+ return results . ToList ( ) ;
369+ }
370+
371+ }
372+
0 commit comments