@@ -15,7 +15,7 @@ final class AvatarPickerViewModelTests {
15
15
}
16
16
17
17
static func createModel(
18
- session: URLSessionAvatarPickerMock = . init ( ) ,
18
+ session: URLSessionProtocol = URLSessionAvatarPickerMock ( ) ,
19
19
imageDownloader: ImageDownloader = TestImageFetcher ( result: . success)
20
20
) -> AvatarPickerViewModel {
21
21
. init(
@@ -209,7 +209,7 @@ final class AvatarPickerViewModelTests {
209
209
210
210
@Test
211
211
func testUploadErrorTooLarge( ) async throws {
212
- model = Self . createModel ( session: . init ( returnErrorCode: HTTPStatus . payloadTooLarge. rawValue) )
212
+ model = Self . createModel ( session: URLSessionAvatarPickerMock ( returnErrorCode: HTTPStatus . payloadTooLarge. rawValue) )
213
213
model. grid. setAvatars ( [ ] )
214
214
215
215
await confirmation { confirmation in
@@ -277,7 +277,7 @@ final class AvatarPickerViewModelTests {
277
277
@Test ( " Test success deletion when the response is a 404 error " )
278
278
func testDeleteError404( ) async throws {
279
279
let avatarToDelete = Self . createImageModel ( id: " 1 " , source: . remote( url: " " ) )
280
- model = Self . createModel ( session: . init ( returnErrorCode: HTTPStatus . notFound. rawValue) )
280
+ model = Self . createModel ( session: URLSessionAvatarPickerMock ( returnErrorCode: HTTPStatus . notFound. rawValue) )
281
281
model. grid. setAvatars ( [ avatarToDelete] )
282
282
283
283
#expect( await model. delete ( avatarToDelete) )
@@ -287,7 +287,7 @@ final class AvatarPickerViewModelTests {
287
287
@Test ( " Test success deletion of selected avatar when the response is a 404 error " )
288
288
func testDeleteSelectedAvatarError404( ) async throws {
289
289
let avatarToDelete = Self . createImageModel ( id: " 1 " , source: . remote( url: " " ) , isSelected: true )
290
- model = Self . createModel ( session: . init ( returnErrorCode: HTTPStatus . notFound. rawValue) )
290
+ model = Self . createModel ( session: URLSessionAvatarPickerMock ( returnErrorCode: HTTPStatus . notFound. rawValue) )
291
291
model. grid. setAvatars ( [ avatarToDelete] )
292
292
#expect( model. grid. selectedAvatar != nil )
293
293
@@ -308,7 +308,7 @@ final class AvatarPickerViewModelTests {
308
308
@Test ( " Test error deletion when the response is an error different to 404 " )
309
309
func testDeleteErrorFails( ) async throws {
310
310
let avatarToDelete = Self . createImageModel ( id: " 1 " , source: . remote( url: " " ) )
311
- model = Self . createModel ( session: . init ( returnErrorCode: HTTPStatus . unauthorized. rawValue) )
311
+ model = Self . createModel ( session: URLSessionAvatarPickerMock ( returnErrorCode: HTTPStatus . unauthorized. rawValue) )
312
312
model. grid. setAvatars ( [ avatarToDelete] )
313
313
314
314
#expect( await model. delete ( avatarToDelete) == false , " Delete request should fail " )
@@ -345,7 +345,7 @@ final class AvatarPickerViewModelTests {
345
345
)
346
346
func changeAvatarRatingReturnsError( httpStatus: HTTPStatus ) async throws {
347
347
let testAvatarID = " 991a7b71cf9f34... "
348
- model = Self . createModel ( session: . init ( returnErrorCode: httpStatus. rawValue) )
348
+ model = Self . createModel ( session: URLSessionAvatarPickerMock ( returnErrorCode: httpStatus. rawValue) )
349
349
350
350
await model. refresh ( )
351
351
let avatar = try #require( model. grid. avatars. first ( where: { $0. id == testAvatarID } ) , " No avatar found " )
@@ -381,12 +381,30 @@ final class AvatarPickerViewModelTests {
381
381
#expect( updatedAvatar. altText == newAltText)
382
382
}
383
383
384
+ @Test
385
+ func testNewAccountProfileRefresh( ) async throws {
386
+ let session = URLSessionAvatarPickerMockNewAccount ( )
387
+ model = Self . createModel ( session: session)
388
+ await model. refresh ( )
389
+ let task = try #require( model. compensatingFetchProfileTask, " No profile task found " )
390
+ // `await confirmation { ... }` doesn't await the unstructured `Task` so we need to await it.
391
+ // For context: https://forums.swift.org/t/testing-closure-based-asynchronous-apis/73705/9
392
+ await task. value
393
+ let isProfileFetched = switch model. profileResult {
394
+ case . success:
395
+ true
396
+ default :
397
+ false
398
+ }
399
+ #expect( isProfileFetched)
400
+ }
401
+
384
402
@Test (
385
403
" Handle avatar alt text change: Failure " ,
386
404
arguments: [ HTTPStatus . unauthorized, . forbidden]
387
405
)
388
406
func testUpdateAltTextError( httpStatus: HTTPStatus ) async throws {
389
- model = Self . createModel ( session: . init ( returnErrorCode: httpStatus. rawValue) )
407
+ model = Self . createModel ( session: URLSessionAvatarPickerMock ( returnErrorCode: httpStatus. rawValue) )
390
408
await model. refresh ( )
391
409
let avatar = model. grid. avatars [ 0 ]
392
410
let originalAltText = avatar. altText
@@ -540,3 +558,29 @@ extension Data? {
540
558
return ( try ? decoder. decode ( T . self, from: self ) ) != nil
541
559
}
542
560
}
561
+
562
+ // Simulates profile creation at the BE side on the very first avatar request.
563
+ actor URLSessionAvatarPickerMockNewAccount : URLSessionProtocol {
564
+ func data( for request: URLRequest ) async throws -> ( Data , URLResponse ) {
565
+ if request. isProfilesRequest {
566
+ if profileRequestCount == 0 {
567
+ profileRequestCount += 1
568
+ // profile is missing on the first call
569
+ return ( " { \" error \" : \" error \" " . data ( using: . utf8) !, HTTPURLResponse . errorResponse ( code: 404 ) )
570
+ } else {
571
+ return ( Bundle . fullProfileJsonData, HTTPURLResponse . successResponse ( ) ) // Profile data
572
+ }
573
+ } else if request. isAvatarsRequest == true {
574
+ return ( Bundle . getAvatarsJsonData, HTTPURLResponse . successResponse ( ) ) // Avatars data
575
+ }
576
+ throw TestURLSessionError ( message: " Unexpected request " )
577
+ }
578
+
579
+ func upload( for request: URLRequest , from bodyData: Data ) async throws -> ( Data , URLResponse ) {
580
+ ( Bundle . postAvatarUploadJsonData, HTTPURLResponse . successResponse ( ) )
581
+ }
582
+
583
+ private var profileRequestCount : Int = 0
584
+
585
+ init ( ) { }
586
+ }
0 commit comments