@@ -81,17 +81,29 @@ class DataPointManagerTests: XCTestCase {
8181 user = nil
8282 }
8383
84- func testSynchronizesPoints ( ) async throws {
84+ func testSynchronizesPointsByRequestId ( ) async throws {
8585 let apiResponse = [
86- // Matching data point to be updated
86+ // Existing datapoint with requestId that should be updated
8787 [
88- " id " : " normal3 " ,
88+ " id " : " existing1 " ,
8989 " value " : 10 ,
9090 " daystamp " : " 20221201 " ,
91- " comment " : " Normal datapoint " ,
91+ " comment " : " Old comment " ,
9292 " updated_at " : 1000 ,
9393 " is_dummy " : false ,
94- " is_initial " : false
94+ " is_initial " : false ,
95+ " requestid " : " hk_workout_1 "
96+ ] ,
97+ // Existing datapoint without matching requestId that should be deleted
98+ [
99+ " id " : " obsolete1 " ,
100+ " value " : 20 ,
101+ " daystamp " : " 20221201 " ,
102+ " comment " : " Should be deleted " ,
103+ " updated_at " : 1001 ,
104+ " is_dummy " : false ,
105+ " is_initial " : false ,
106+ " requestid " : " hk_workout_old "
95107 ] ,
96108 // Metadata points which should be ignored
97109 [
@@ -102,60 +114,151 @@ class DataPointManagerTests: XCTestCase {
102114 " updated_at " : 2000 ,
103115 " is_dummy " : true ,
104116 " is_initial " : false
105- ] ,
106- [
107- " id " : " initial2 " ,
108- " value " : 30 ,
109- " daystamp " : " 20221203 " ,
110- " comment " : " Initial datapoint " ,
111- " updated_at " : 3000 ,
112- " is_dummy " : false ,
113- " is_initial " : true
114- ] ,
115- // Additional datapoint that will be deleted (multiple datapoints for same daystamp)
116- [
117- " id " : " duplicate1 " ,
118- " value " : 11 ,
119- " daystamp " : " 20221201 " ,
120- " comment " : " Duplicate datapoint to be deleted " ,
121- " updated_at " : 1001 ,
122- " is_dummy " : false ,
123- " is_initial " : false
124- ] ,
117+ ]
125118 ]
126119
127120 mockRequestManager. responses [ " api/v1/users/{username}/goals/test-goal/datapoints.json " ] = apiResponse
128121
129- let healthKitDatapoint = MockHealthKitDataPoint (
122+ let updatedHealthKitDatapoint = MockHealthKitDataPoint (
130123 daystamp: try Daystamp ( fromString: " 20221201 " ) ,
131124 value: NSNumber ( value: 15 ) ,
132- comment: " Updated from HealthKit " ,
133- requestid: " hk_test "
125+ comment: " Updated workout comment " ,
126+ requestid: " hk_workout_1 "
134127 )
135128
136129 let newHealthKitDatapoint = MockHealthKitDataPoint (
137- daystamp: try Daystamp ( fromString: " 20221204 " ) ,
130+ daystamp: try Daystamp ( fromString: " 20221201 " ) ,
138131 value: NSNumber ( value: 25 ) ,
139- comment: " New HealthKit datapoint " ,
140- requestid: " hk_new "
132+ comment: " New workout " ,
133+ requestid: " hk_workout_2 "
141134 )
142135
143- try ! await dataPointManager. updateToMatchDataPoints ( goalID: goal. objectID, healthKitDataPoints: [ healthKitDatapoint , newHealthKitDatapoint] )
136+ try ! await dataPointManager. updateToMatchDataPoints ( goalID: goal. objectID, healthKitDataPoints: [ updatedHealthKitDatapoint , newHealthKitDatapoint] )
144137
145- // Should update the matching data point
138+ // Should update the existing datapoint by requestId
146139 XCTAssertEqual ( mockRequestManager. putCalls. count, 1 )
147- XCTAssertTrue ( mockRequestManager. putCalls [ 0 ] . url. contains ( " normal3 " ) )
140+ XCTAssertTrue ( mockRequestManager. putCalls [ 0 ] . url. contains ( " existing1 " ) )
148141 XCTAssertEqual ( mockRequestManager. putCalls [ 0 ] . parameters [ " value " ] as? String , " 15 " )
142+ XCTAssertEqual ( mockRequestManager. putCalls [ 0 ] . parameters [ " comment " ] as? String , " Updated workout comment " )
149143
150- // Should delete the duplicate data point
144+ // Should delete the obsolete datapoint
151145 XCTAssertEqual ( mockRequestManager. deleteCalls. count, 1 )
152- XCTAssertTrue ( mockRequestManager. deleteCalls [ 0 ] . contains ( " duplicate1 " ) )
146+ XCTAssertTrue ( mockRequestManager. deleteCalls [ 0 ] . contains ( " obsolete1 " ) )
153147
154- // Should create a new data point
148+ // Should create a new datapoint
155149 XCTAssertEqual ( mockRequestManager. addDatapointCalls. count, 1 )
156- XCTAssertEqual ( mockRequestManager. addDatapointCalls [ 0 ] . urtext, " 4 25 \" New HealthKit datapoint \" " )
150+ XCTAssertEqual ( mockRequestManager. addDatapointCalls [ 0 ] . urtext, " 1 25 \" New workout \" " )
157151 XCTAssertEqual ( mockRequestManager. addDatapointCalls [ 0 ] . slug, " test-goal " )
158- XCTAssertEqual ( mockRequestManager. addDatapointCalls [ 0 ] . requestId, " hk_new " )
152+ XCTAssertEqual ( mockRequestManager. addDatapointCalls [ 0 ] . requestId, " hk_workout_2 " )
153+ }
154+
155+ func testDeletesRemovedWorkouts( ) async throws {
156+ let apiResponse = [
157+ [
158+ " id " : " workout1 " ,
159+ " value " : 30 ,
160+ " daystamp " : " 20221201 " ,
161+ " comment " : " Morning run " ,
162+ " updated_at " : 1000 ,
163+ " is_dummy " : false ,
164+ " is_initial " : false ,
165+ " requestid " : " hk_workout_uuid_1 "
166+ ] ,
167+ [
168+ " id " : " workout2 " ,
169+ " value " : 45 ,
170+ " daystamp " : " 20221201 " ,
171+ " comment " : " Evening bike " ,
172+ " updated_at " : 1001 ,
173+ " is_dummy " : false ,
174+ " is_initial " : false ,
175+ " requestid " : " hk_workout_uuid_2 "
176+ ]
177+ ]
178+
179+ mockRequestManager. responses [ " api/v1/users/{username}/goals/test-goal/datapoints.json " ] = apiResponse
180+
181+ // Only one workout remains in HealthKit
182+ let remainingWorkout = MockHealthKitDataPoint (
183+ daystamp: try Daystamp ( fromString: " 20221201 " ) ,
184+ value: NSNumber ( value: 30 ) ,
185+ comment: " Morning run " ,
186+ requestid: " hk_workout_uuid_1 "
187+ )
188+
189+ try ! await dataPointManager. updateToMatchDataPoints ( goalID: goal. objectID, healthKitDataPoints: [ remainingWorkout] )
190+
191+ // Should not update the matching workout (same value/comment)
192+ XCTAssertEqual ( mockRequestManager. putCalls. count, 0 )
193+
194+ // Should delete the removed workout
195+ XCTAssertEqual ( mockRequestManager. deleteCalls. count, 1 )
196+ XCTAssertTrue ( mockRequestManager. deleteCalls [ 0 ] . contains ( " workout2 " ) )
197+
198+ // Should not create any new datapoints
199+ XCTAssertEqual ( mockRequestManager. addDatapointCalls. count, 0 )
200+ }
201+
202+ func testMultipleDaysWithMultipleWorkouts( ) async throws {
203+ let apiResponse = [
204+ [
205+ " id " : " day1_workout1 " ,
206+ " value " : 30 ,
207+ " daystamp " : " 20221201 " ,
208+ " comment " : " Run " ,
209+ " updated_at " : 1000 ,
210+ " is_dummy " : false ,
211+ " is_initial " : false ,
212+ " requestid " : " uuid_1 "
213+ ] ,
214+ [
215+ " id " : " day2_workout1 " ,
216+ " value " : 45 ,
217+ " daystamp " : " 20221202 " ,
218+ " comment " : " Bike " ,
219+ " updated_at " : 1001 ,
220+ " is_dummy " : false ,
221+ " is_initial " : false ,
222+ " requestid " : " uuid_2 "
223+ ]
224+ ]
225+
226+ mockRequestManager. responses [ " api/v1/users/{username}/goals/test-goal/datapoints.json " ] = apiResponse
227+
228+ let day1Workouts = [
229+ MockHealthKitDataPoint (
230+ daystamp: try Daystamp ( fromString: " 20221201 " ) ,
231+ value: NSNumber ( value: 30 ) ,
232+ comment: " Run " ,
233+ requestid: " uuid_1 "
234+ ) ,
235+ MockHealthKitDataPoint (
236+ daystamp: try Daystamp ( fromString: " 20221201 " ) ,
237+ value: NSNumber ( value: 20 ) ,
238+ comment: " Yoga " ,
239+ requestid: " uuid_3 "
240+ )
241+ ]
242+
243+ let day2Workouts = [
244+ MockHealthKitDataPoint (
245+ daystamp: try Daystamp ( fromString: " 20221202 " ) ,
246+ value: NSNumber ( value: 60 ) ,
247+ comment: " Long bike ride " ,
248+ requestid: " uuid_4 "
249+ )
250+ ]
251+
252+ try ! await dataPointManager. updateToMatchDataPoints ( goalID: goal. objectID, healthKitDataPoints: day1Workouts + day2Workouts)
253+
254+ // Should delete day2 old workout, but not update day1 unchanged workout
255+ XCTAssertEqual ( mockRequestManager. deleteCalls. count, 1 )
256+ XCTAssertTrue ( mockRequestManager. deleteCalls [ 0 ] . contains ( " day2_workout1 " ) )
257+
258+ // Should create 2 new workouts (day1 yoga, day2 bike)
259+ XCTAssertEqual ( mockRequestManager. addDatapointCalls. count, 2 )
260+ XCTAssertTrue ( mockRequestManager. addDatapointCalls. contains { $0. requestId == " uuid_3 " } )
261+ XCTAssertTrue ( mockRequestManager. addDatapointCalls. contains { $0. requestId == " uuid_4 " } )
159262 }
160263
161264 private func createTestGoalJSON( ) -> JSON {
0 commit comments