From 6daccf51709708d00f7868978e9087bc08d9c948 Mon Sep 17 00:00:00 2001 From: CMK Date: Wed, 3 Feb 2021 18:52:47 +0800 Subject: [PATCH] feat: add home timeline api --- Mastodon.xcodeproj/project.pbxproj | 12 ++-- .../xcschemes/xcschememanagement.plist | 2 +- .../PublicTimelineViewModel.swift | 2 +- .../APIService/APIService+HomeTimeline.swift | 60 ++++++++++++++++ .../APIService+PublicTimeline.swift | 21 ++++-- .../Persist/APIService+Persist+Timeline.swift | 3 +- .../API/Mastodon+API+Timeline.swift | 57 +++++++++++++++ .../API/MastodonSDK+API+TimelineTests.swift | 71 +++++++++++++++++++ .../MastodonSDKTests/MastodonSDKTests.swift | 30 -------- 9 files changed, 212 insertions(+), 46 deletions(-) create mode 100644 Mastodon/Service/APIService/APIService+HomeTimeline.swift create mode 100644 MastodonSDK/Tests/MastodonSDKTests/API/MastodonSDK+API+TimelineTests.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index fb5c211e..8c109601 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -33,12 +33,10 @@ 2D927F0E25C7E9C9004F19B8 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0D25C7E9C9004F19B8 /* History.swift */; }; 2D927F1425C7EDD9004F19B8 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F1325C7EDD9004F19B8 /* Emoji.swift */; }; 2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF123A625C3B0210020F248 /* ActiveLabel.swift */; }; - 3533495136D843E85211E3E2 /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1B4523A7981F1044DE89C21 /* Pods_Mastodon_MastodonUITests.framework */; }; 45B49097460EDE530AD5AA72 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; }; 5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 5D526FE125BE9AC400460CB9 /* MastodonSDK */; }; 5E0DEC05797A7E6933788DDB /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */; }; 5E44BF88AD33646E64727BCF /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */; }; - 7A9135D4559750AF07CA9BE4 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 602D783BEC22881EBAD84419 /* Pods_Mastodon.framework */; }; DB01409625C40B6700F9F3CF /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB01409525C40B6700F9F3CF /* AuthenticationViewController.swift */; }; DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */; }; DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */; }; @@ -61,6 +59,7 @@ DB45FAED25CA7A9A005A8AC7 /* MastodonAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAEC25CA7A9A005A8AC7 /* MastodonAuthentication.swift */; }; DB45FAF925CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAF825CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift */; }; DB45FB0F25CA87D0005A8AC7 /* AuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FB0E25CA87D0005A8AC7 /* AuthenticationService.swift */; }; + DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */; }; DB89B9F725C10FD0008580ED /* CoreDataStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; }; DB89B9FE25C10FD0008580ED /* CoreDataStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89B9FD25C10FD0008580ED /* CoreDataStackTests.swift */; }; DB89BA0025C10FD0008580ED /* CoreDataStack.h in Headers */ = {isa = PBXBuildFile; fileRef = DB89B9F025C10FD0008580ED /* CoreDataStack.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -173,10 +172,8 @@ 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon_MastodonUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 459EA4F43058CAB47719E963 /* Pods-Mastodon-MastodonUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.debug.xcconfig"; sourceTree = ""; }; - 602D783BEC22881EBAD84419 /* Pods_Mastodon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 75E3471C898DDD9631729B6E /* Pods-Mastodon.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.release.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.release.xcconfig"; sourceTree = ""; }; 9780A4C98FFC65B32B50D1C0 /* Pods-MastodonTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.release.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.release.xcconfig"; sourceTree = ""; }; - A1B4523A7981F1044DE89C21 /* Pods_Mastodon_MastodonUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon_MastodonUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BB482D32A7B9825BF5327C4F /* Pods-Mastodon-MastodonUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.release.xcconfig"; sourceTree = ""; }; CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -207,6 +204,7 @@ DB45FAEC25CA7A9A005A8AC7 /* MastodonAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthentication.swift; sourceTree = ""; }; DB45FAF825CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+MastodonAuthentication.swift"; sourceTree = ""; }; DB45FB0E25CA87D0005A8AC7 /* AuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationService.swift; sourceTree = ""; }; + DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+HomeTimeline.swift"; sourceTree = ""; }; DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreDataStack.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DB89B9F025C10FD0008580ED /* CoreDataStack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreDataStack.h; sourceTree = ""; }; DB89B9F125C10FD0008580ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -254,7 +252,6 @@ 2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */, 5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */, 2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */, - 7A9135D4559750AF07CA9BE4 /* Pods_Mastodon.framework in Frameworks */, DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */, 45B49097460EDE530AD5AA72 /* Pods_Mastodon.framework in Frameworks */, ); @@ -273,7 +270,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3533495136D843E85211E3E2 /* Pods_Mastodon_MastodonUITests.framework in Frameworks */, 18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -400,8 +396,6 @@ 2D7631A625C1533800929FB9 /* TableviewCell */ = { isa = PBXGroup; children = ( - 602D783BEC22881EBAD84419 /* Pods_Mastodon.framework */, - A1B4523A7981F1044DE89C21 /* Pods_Mastodon_MastodonUITests.framework */, 2D7631A725C1535600929FB9 /* TimelinePostTableViewCell.swift */, ); path = TableviewCell; @@ -542,6 +536,7 @@ DB98337025C9443200AD9700 /* APIService+Authentication.swift */, DB98339B25C96DE600AD9700 /* APIService+Account.swift */, 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */, + DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */, ); path = APIService; sourceTree = ""; @@ -1032,6 +1027,7 @@ buildActionMask = 2147483647; files = ( DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */, + DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */, 2D7631B325C159F700929FB9 /* Item.swift in Sources */, 2D61335E25C1894B00CAE157 /* APIService.swift in Sources */, DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */, diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 997b42f4..24ba6665 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ Mastodon.xcscheme_^#shared#^_ orderHint - 0 + 1 SuppressBuildableAutocreation diff --git a/Mastodon/Scene/PublicTimeline/PublicTimelineViewModel.swift b/Mastodon/Scene/PublicTimeline/PublicTimelineViewModel.swift index 2150bb25..613c5126 100644 --- a/Mastodon/Scene/PublicTimeline/PublicTimelineViewModel.swift +++ b/Mastodon/Scene/PublicTimeline/PublicTimelineViewModel.swift @@ -90,6 +90,6 @@ class PublicTimelineViewModel: NSObject { extension PublicTimelineViewModel { func fetchLatest() -> AnyPublisher, Error> { - return context.apiService.publicTimeline(count: 20, domain: "mstdn.jp") + return context.apiService.publicTimeline(domain: "mstdn.jp") } } diff --git a/Mastodon/Service/APIService/APIService+HomeTimeline.swift b/Mastodon/Service/APIService/APIService+HomeTimeline.swift new file mode 100644 index 00000000..0486ebec --- /dev/null +++ b/Mastodon/Service/APIService/APIService+HomeTimeline.swift @@ -0,0 +1,60 @@ +// +// APIService+HomeTimeline.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021/2/3. +// + +import Foundation +import Combine +import CoreData +import CoreDataStack +import DateToolsSwift +import MastodonSDK + +extension APIService { + + func homeTimeline( + domain: String, + sinceID: Mastodon.Entity.Status.ID? = nil, + maxID: Mastodon.Entity.Status.ID? = nil, + limit: Int = 100, + authorizationBox: AuthenticationService.MastodonAuthenticationBox + ) -> AnyPublisher, Error> { + let authorization = authorizationBox.userAuthorization + let query = Mastodon.API.Timeline.HomeTimelineQuery( + maxID: maxID, + sinceID: sinceID, + minID: nil, // prefer sinceID + limit: limit, + local: nil // TODO: + ) + + return Mastodon.API.Timeline.home( + session: session, + domain: domain, + query: query, + authorization: authorization + ) + .flatMap { response -> AnyPublisher, Error> in + return APIService.Persist.persistTimeline( + domain: domain, + managedObjectContext: self.backgroundManagedObjectContext, + response: response, + persistType: .homeTimeline + ) + .setFailureType(to: Error.self) + .tryMap { result -> Mastodon.Response.Content<[Mastodon.Entity.Toot]> in + switch result { + case .success: + return response + case .failure(let error): + throw error + } + } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + +} diff --git a/Mastodon/Service/APIService/APIService+PublicTimeline.swift b/Mastodon/Service/APIService/APIService+PublicTimeline.swift index 3e75d246..454bde08 100644 --- a/Mastodon/Service/APIService/APIService+PublicTimeline.swift +++ b/Mastodon/Service/APIService/APIService+PublicTimeline.swift @@ -17,21 +17,32 @@ extension APIService { static let publicTimelineRequestWindowInSec: TimeInterval = 15 * 60 func publicTimeline( - count: Int = 20, - domain: String + domain: String, + sinceID: Mastodon.Entity.Status.ID? = nil, + maxID: Mastodon.Entity.Status.ID? = nil, + limit: Int = 100 ) -> AnyPublisher, Error> { - + let query = Mastodon.API.Timeline.PublicTimelineQuery( + local: nil, + remote: nil, + onlyMedia: nil, + maxID: maxID, + sinceID: sinceID, + minID: nil, // prefer sinceID + limit: limit + ) + return Mastodon.API.Timeline.public( session: session, domain: domain, - query: Mastodon.API.Timeline.PublicTimelineQuery() + query: query ) .flatMap { response -> AnyPublisher, Error> in return APIService.Persist.persistTimeline( domain: domain, managedObjectContext: self.backgroundManagedObjectContext, response: response, - persistType: Persist.PersistTimelineType.publicHomeTimeline + persistType: Persist.PersistTimelineType.publicTimeline ) .setFailureType(to: Error.self) .tryMap { result -> Mastodon.Response.Content<[Mastodon.Entity.Toot]> in diff --git a/Mastodon/Service/APIService/Persist/APIService+Persist+Timeline.swift b/Mastodon/Service/APIService/Persist/APIService+Persist+Timeline.swift index d3e9ed5e..f2c65dc7 100644 --- a/Mastodon/Service/APIService/Persist/APIService+Persist+Timeline.swift +++ b/Mastodon/Service/APIService/Persist/APIService+Persist+Timeline.swift @@ -14,7 +14,8 @@ import MastodonSDK extension APIService.Persist { enum PersistTimelineType { - case publicHomeTimeline + case publicTimeline + case homeTimeline } static func persistTimeline( domain: String, diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Timeline.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Timeline.swift index 95f912b4..2ee3e8e8 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Timeline.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Timeline.swift @@ -13,6 +13,9 @@ extension Mastodon.API.Timeline { static func publicTimelineEndpointURL(domain: String) -> URL { return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("timelines/public") } + static func homeTimelineEndpointURL(domain: String) -> URL { + return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("timelines/home") + } public static func `public`( session: URLSession, @@ -32,9 +35,29 @@ extension Mastodon.API.Timeline { .eraseToAnyPublisher() } + public static func home( + session: URLSession, + domain: String, + query: HomeTimelineQuery, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: homeTimelineEndpointURL(domain: domain), + query: query, + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: [Mastodon.Entity.Toot].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + } extension Mastodon.API.Timeline { + public struct PublicTimelineQuery: Codable, GetQuery { public let local: Bool? @@ -76,4 +99,38 @@ extension Mastodon.API.Timeline { return items } } + + public struct HomeTimelineQuery: Codable, GetQuery { + public let maxID: Mastodon.Entity.Toot.ID? + public let sinceID: Mastodon.Entity.Toot.ID? + public let minID: Mastodon.Entity.Toot.ID? + public let limit: Int? + public let local: Bool? + + public init( + maxID: Mastodon.Entity.Toot.ID? = nil, + sinceID: Mastodon.Entity.Toot.ID? = nil, + minID: Mastodon.Entity.Toot.ID? = nil, + limit: Int? = nil, + local: Bool? = nil + ) { + self.maxID = maxID + self.sinceID = sinceID + self.minID = minID + self.limit = limit + self.local = local + } + + var queryItems: [URLQueryItem]? { + var items: [URLQueryItem] = [] + maxID.flatMap { items.append(URLQueryItem(name: "max_id", value: $0)) } + sinceID.flatMap { items.append(URLQueryItem(name: "since_id", value: $0)) } + minID.flatMap { items.append(URLQueryItem(name: "min_id", value: $0)) } + limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) } + local.flatMap { items.append(URLQueryItem(name: "local", value: $0.queryItemValue)) } + guard !items.isEmpty else { return nil } + return items + } + } + } diff --git a/MastodonSDK/Tests/MastodonSDKTests/API/MastodonSDK+API+TimelineTests.swift b/MastodonSDK/Tests/MastodonSDKTests/API/MastodonSDK+API+TimelineTests.swift new file mode 100644 index 00000000..ec6cb849 --- /dev/null +++ b/MastodonSDK/Tests/MastodonSDKTests/API/MastodonSDK+API+TimelineTests.swift @@ -0,0 +1,71 @@ +// +// MastodonSDK+API+TimelineTests.swift +// +// +// Created by MainasuK Cirno on 2021/2/3. +// + +import os.log +import XCTest +import Combine +@testable import MastodonSDK + +extension MastodonSDKTests { + + func testPublicTimeline() throws { + try _testPublicTimeline(domain: domain) + } + + private func _testPublicTimeline(domain: String) throws { + let theExpectation = expectation(description: "Fetch Public Timeline") + + let query = Mastodon.API.Timeline.PublicTimelineQuery() + Mastodon.API.Timeline.public(session: session, domain: domain, query: query) + .receive(on: DispatchQueue.main) + .sink { completion in + switch completion { + case .failure(let error): + XCTFail(error.localizedDescription) + case .finished: + break + } + } receiveValue: { response in + XCTAssert(!response.value.isEmpty) + theExpectation.fulfill() + } + .store(in: &disposeBag) + + wait(for: [theExpectation], timeout: 10.0) + } + +} + +extension MastodonSDKTests { + + func testHomeTimeline() { + let domain = "" + let accessToken = "" + guard !domain.isEmpty, !accessToken.isEmpty else { return } + + let query = Mastodon.API.Timeline.HomeTimelineQuery() + let authorization = Mastodon.API.OAuth.Authorization(accessToken: accessToken) + let theExpectation = expectation(description: "Fetch Home Timeline") + Mastodon.API.Timeline.home(session: session, domain: domain, query: query, authorization: authorization) + .receive(on: DispatchQueue.main) + .sink { completion in + switch completion { + case .failure(let error): + XCTFail(error.localizedDescription) + case .finished: + break + } + } receiveValue: { response in + XCTAssert(!response.value.isEmpty) + theExpectation.fulfill() + } + .store(in: &disposeBag) + + wait(for: [theExpectation], timeout: 10.0) + } + +} diff --git a/MastodonSDK/Tests/MastodonSDKTests/MastodonSDKTests.swift b/MastodonSDK/Tests/MastodonSDKTests/MastodonSDKTests.swift index 8996d1aa..b32261c3 100644 --- a/MastodonSDK/Tests/MastodonSDKTests/MastodonSDKTests.swift +++ b/MastodonSDK/Tests/MastodonSDKTests/MastodonSDKTests.swift @@ -14,33 +14,3 @@ final class MastodonSDKTests: XCTestCase { } } - -extension MastodonSDKTests { - - func testPublicTimeline() throws { - try _testPublicTimeline(domain: domain) - } - - private func _testPublicTimeline(domain: String) throws { - let theExpectation = expectation(description: "Fetch Public Timeline") - - let query = Mastodon.API.Timeline.PublicTimelineQuery() - Mastodon.API.Timeline.public(session: session, domain: domain, query: query) - .receive(on: DispatchQueue.main) - .sink { completion in - switch completion { - case .failure(let error): - XCTFail(error.localizedDescription) - case .finished: - break - } - } receiveValue: { response in - XCTAssert(!response.value.isEmpty) - theExpectation.fulfill() - } - .store(in: &disposeBag) - - wait(for: [theExpectation], timeout: 10.0) - } - -}