diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 90ff6640fc95..9f787b243c65 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -1311,6 +1311,7 @@ 4AD5656F28E413160054C676 /* Blog+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AD5656E28E413160054C676 /* Blog+History.swift */; }; 4AD5657028E413160054C676 /* Blog+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AD5656E28E413160054C676 /* Blog+History.swift */; }; 4AD5657228E543A30054C676 /* BlogQueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AD5657128E543A30054C676 /* BlogQueryTests.swift */; }; + 4AD862E52AFAEF1700A07557 /* PostsListAPIStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AD862E42AFAEF1700A07557 /* PostsListAPIStub.swift */; }; 4AEF2DD929A84B2C00345734 /* ReaderSiteServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEF2DD829A84B2C00345734 /* ReaderSiteServiceTests.swift */; }; 4AFB1A812A9C08CE007CE165 /* StoppableProgressIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AFB1A802A9C08CE007CE165 /* StoppableProgressIndicatorView.swift */; }; 4AFB1A822A9C08CE007CE165 /* StoppableProgressIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AFB1A802A9C08CE007CE165 /* StoppableProgressIndicatorView.swift */; }; @@ -6924,6 +6925,7 @@ 4AD5656B28E3D0670054C676 /* ReaderPost+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReaderPost+Helper.swift"; sourceTree = ""; }; 4AD5656E28E413160054C676 /* Blog+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Blog+History.swift"; sourceTree = ""; }; 4AD5657128E543A30054C676 /* BlogQueryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogQueryTests.swift; sourceTree = ""; }; + 4AD862E42AFAEF1700A07557 /* PostsListAPIStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsListAPIStub.swift; sourceTree = ""; }; 4AEF2DD829A84B2C00345734 /* ReaderSiteServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderSiteServiceTests.swift; sourceTree = ""; }; 4AFB1A802A9C08CE007CE165 /* StoppableProgressIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoppableProgressIndicatorView.swift; sourceTree = ""; }; 4AFB8FBE2824999400A2F4B2 /* ContextManager+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ContextManager+Helpers.swift"; sourceTree = ""; }; @@ -13616,6 +13618,7 @@ FE6BB1452932289B001E5F7A /* ContentMigrationCoordinatorTests.swift */, 0C896DE62A3A832B00D7D4E7 /* SiteVisibilityTests.swift */, 0CA10FA42ADB286300CE75AC /* StringRankedSearchTests.swift */, + 4AD862E42AFAEF1700A07557 /* PostsListAPIStub.swift */, 93B853211B44165B0064FE72 /* Analytics */, DC06DFF727BD52A100969974 /* BackgroundTasks */, FE32E7EF284496F500744D80 /* Blogging Prompts */, @@ -23560,6 +23563,7 @@ D848CC0320FF04FA00A9038F /* FormattableUserContentTests.swift in Sources */, 5948AD111AB73D19006E8882 /* WPAppAnalyticsTests.m in Sources */, 0C8FC9AA2A8C57000059DCE4 /* ItemProviderMediaExporterTests.swift in Sources */, + 4AD862E52AFAEF1700A07557 /* PostsListAPIStub.swift in Sources */, 0A69300B28B5AA5E00E98DE1 /* FullScreenCommentReplyViewModelTests.swift in Sources */, 0CA10FA52ADB286300CE75AC /* StringRankedSearchTests.swift in Sources */, FF8032661EE9E22200861F28 /* MediaProgressCoordinatorTests.swift in Sources */, diff --git a/WordPress/WordPressTest/PostRepositoryPostsListTests.swift b/WordPress/WordPressTest/PostRepositoryPostsListTests.swift index 0f139d42c0ca..f0bf0e7db510 100644 --- a/WordPress/WordPressTest/PostRepositoryPostsListTests.swift +++ b/WordPress/WordPressTest/PostRepositoryPostsListTests.swift @@ -28,7 +28,7 @@ class PostRepositoryPostsListTests: CoreDataTestCase { func testPagination() async throws { // Given there are 15 published posts on the site - try await preparePostsList(type: "post", total: 15) + stubGetPostsList(type: "post", total: 15) // When fetching all of the posts let firstPage = try await repository.paginate(type: Post.self, statuses: [.publish], offset: 0, number: 10, in: blogID) @@ -44,7 +44,7 @@ class PostRepositoryPostsListTests: CoreDataTestCase { func testSearching() async throws { // Given there are 15 published posts on the site - try await preparePostsList(type: "post", total: 15) + stubGetPostsList(type: "post", total: 15) // When fetching all of the posts let _ = try await repository.paginate(type: Post.self, statuses: [.publish], offset: 0, number: 15, in: blogID) @@ -63,7 +63,7 @@ class PostRepositoryPostsListTests: CoreDataTestCase { } func testFetchingAllPages() async throws { - try await preparePostsList(type: "page", total: 1_120) + stubGetPostsList(type: "page", total: 1_120) let pages = try await repository.fetchAllPages(statuses: [], in: blogID).value XCTAssertEqual(pages.count, 1_120) @@ -75,116 +75,6 @@ class PostRepositoryPostsListTests: CoreDataTestCase { } -// MARK: - Tests that ensure the `preparePostsList` works as expected - -extension PostRepositoryPostsListTests { - - func testPostsListStubReturnPostsAsRequested() async throws { - try await preparePostsList(type: "post", total: 20) - - var result = try await repository.search(type: Post.self, input: nil, statuses: [], tag: nil, offset: 0, limit: 10, orderBy: .byDate, descending: true, in: blogID) - XCTAssertEqual(result.count, 10) - - result = try await repository.search(type: Post.self, input: nil, statuses: [], tag: nil, offset: 0, limit: 20, orderBy: .byDate, descending: true, in: blogID) - XCTAssertEqual(result.count, 20) - - result = try await repository.search(type: Post.self, input: nil, statuses: [], tag: nil, offset: 0, limit: 30, orderBy: .byDate, descending: true, in: blogID) - XCTAssertEqual(result.count, 20) - } - - func testPostsListStubReturnPostsAtCorrectPosition() async throws { - try await preparePostsList(type: "post", total: 20) - - let all = try await repository.search(type: Post.self, input: nil, statuses: [], tag: nil, offset: 0, limit: 30, orderBy: .byDate, descending: true, in: blogID) - - var result = try await repository.paginate(type: Post.self, statuses: [], offset: 0, number: 5, in: blogID) - XCTAssertEqual(result, Array(all[0..<5])) - - result = try await repository.paginate(type: Post.self, statuses: [], offset: 3, number: 2, in: blogID) - XCTAssertEqual(result, [all[3], all[4]]) - } - - func testPostsListStubReturnPostsSearch() async throws { - try await preparePostsList(type: "post", total: 10) - - let all = try await repository.search(type: Post.self, input: nil, statuses: [], tag: nil, offset: 0, limit: 30, orderBy: .byDate, descending: true, in: blogID) - - var result = try await repository.search(type: Post.self, input: "1", statuses: [], tag: nil, offset: 0, limit: 1, orderBy: .byDate, descending: true, in: blogID) - XCTAssertEqual(result, [all[0]]) - - result = try await repository.search(type: Post.self, input: "2", statuses: [], tag: nil, offset: 0, limit: 1, orderBy: .byDate, descending: true, in: blogID) - XCTAssertEqual(result, [all[1]]) - } - - func testPostsListStubReturnDefaultNumberOfPosts() async throws { - try await preparePostsList(type: "post", total: 100) - - let result = try await repository.search(type: Post.self, input: nil, statuses: [], tag: nil, offset: 0, limit: 0, orderBy: .byDate, descending: true, in: blogID) - XCTAssertEqual(result.count, 20) - } - -} - -private extension PostRepositoryPostsListTests { - /// This is a helper function to create HTTP stubs for fetching posts(GET /sites/%s/posts) requests. - /// - /// The returned fake posts have only basic properties. The stubs ensure post id is unique and starts from 1. - /// But it does not promise the returned posts match the request filters (like status). - /// - /// You can use the `update` closure to update the returned posts if needed. - /// - /// Here are the supported features: - /// - Pagination. The stubs simulates `total` number of posts in the site, to handle paginated request accordingly. - /// - Search, but limited. Search is based on title. All fake posts have a title like "Random Post - [post-id]", where post id starts from 1. So, search "1" returns the posts whose id has "1" in it (1, 1x, x1, and so on). - /// - /// Here are unsupported features: - /// - Order. The sorting related arguments are ignored. - /// - Filter by status. The status argument is ignored. - func preparePostsList(type: String, total: Int, update: ((inout [String: Any]) -> Void)? = nil) async throws { - let allPosts = (1...total).map { id -> [String: Any] in - [ - "ID": id, - "title": "Random Post - \(id)", - "content": "This is a test.", - "status": BasePost.Status.publish.rawValue, - "type": type - ] - } - - let siteID = try await contextManager.performQuery { try XCTUnwrap($0.existingObject(with: self.blogID).dotComID) } - stub(condition: isMethodGET() && pathMatches("/sites/\(siteID)/posts", options: [])) { request in - let queryItems = URLComponents(url: request.url!, resolvingAgainstBaseURL: true)?.queryItems ?? [] - - var result = allPosts - - if let search = queryItems.first(where: { $0.name == "search" })?.value { - result = result.filter { - let title = $0["title"] as? String - return title?.contains(search) == true - } - } - - var number = (queryItems.first { $0.name == "number" }?.value.flatMap { Int($0) }) ?? 0 - number = number == 0 ? 20 : number // The REST API uses the default value 20 when number is 0. - let offset = (queryItems.first { $0.name == "offset" }?.value.flatMap { Int($0) }) ?? 0 - let upperBound = number == 0 ? result.endIndex : max(offset, offset + number - 1) - let allowed = 0.. NSManagedObjectID { diff --git a/WordPress/WordPressTest/PostsListAPIStub.swift b/WordPress/WordPressTest/PostsListAPIStub.swift new file mode 100644 index 000000000000..e38bc6c9b4db --- /dev/null +++ b/WordPress/WordPressTest/PostsListAPIStub.swift @@ -0,0 +1,143 @@ +import Foundation +import XCTest +import OHHTTPStubs + +@testable import WordPress + +extension XCTestCase { + /// This is a helper function to create HTTP stubs for fetching posts(GET /sites/%s/posts) requests. + /// + /// The returned fake posts have only basic properties. The stubs ensure post id is unique and starts from 1. + /// But it does not promise the returned posts match the request filters (like status). + /// + /// You can use the `update` closure to update the returned posts if needed. + /// + /// Here are the supported features: + /// - Pagination. The stubs simulates `total` number of posts in the site, to handle paginated request accordingly. + /// - Search, but limited. Search is based on title. All fake posts have a title like "Random Post - [post-id]", where post id starts from 1. So, search "1" returns the posts whose id has "1" in it (1, 1x, x1, and so on). + /// + /// Here are unsupported features: + /// - Order. The sorting related arguments are ignored. + /// - Filter by status. The status argument is ignored. + func stubGetPostsList(type: String, total: Int, update: ((inout [String: Any]) -> Void)? = nil) { + let allPosts = (1...total).map { id -> [String: Any] in + [ + "ID": id, + "title": "Random Post - \(id)", + "content": "This is a test.", + "status": BasePost.Status.publish.rawValue, + "type": type + ] + } + + let handle = stub(condition: isMethodGET() && pathMatches(#"/sites/\d+/posts"#, options: [])) { request in + let queryItems = URLComponents(url: request.url!, resolvingAgainstBaseURL: true)?.queryItems ?? [] + + var result = allPosts + + if let search = queryItems.first(where: { $0.name == "search" })?.value { + result = result.filter { + let title = $0["title"] as? String + return title?.contains(search) == true + } + } + + var number = (queryItems.first { $0.name == "number" }?.value.flatMap { Int($0) }) ?? 0 + number = number == 0 ? 20 : number // The REST API uses the default value 20 when number is 0. + let offset = (queryItems.first { $0.name == "offset" }?.value.flatMap { Int($0) }) ?? 0 + let upperBound = number == 0 ? result.endIndex : max(offset, offset + number - 1) + let allowed = 0..! + + override func setUp() async throws { + repository = PostRepository(coreDataStack: contextManager) + + let loggedIn = try await signIn() + blogID = try await contextManager.performAndSave { + let blog = try BlogBuilder($0) + .with(dotComID: 42) + .withAccount(id: loggedIn) + .build() + return TaggedManagedObjectID(blog) + } + } + + func testPostsListStubReturnPostsAsRequested() async throws { + stubGetPostsList(type: "post", total: 20) + + var result = try await repository.search(type: Post.self, input: nil, statuses: [], tag: nil, offset: 0, limit: 10, orderBy: .byDate, descending: true, in: blogID) + XCTAssertEqual(result.count, 10) + + result = try await repository.search(type: Post.self, input: nil, statuses: [], tag: nil, offset: 0, limit: 20, orderBy: .byDate, descending: true, in: blogID) + XCTAssertEqual(result.count, 20) + + result = try await repository.search(type: Post.self, input: nil, statuses: [], tag: nil, offset: 0, limit: 30, orderBy: .byDate, descending: true, in: blogID) + XCTAssertEqual(result.count, 20) + } + + func testPostsListStubReturnPostsAtCorrectPosition() async throws { + stubGetPostsList(type: "post", total: 20) + + let all = try await repository.search(type: Post.self, input: nil, statuses: [], tag: nil, offset: 0, limit: 30, orderBy: .byDate, descending: true, in: blogID) + + var result = try await repository.paginate(type: Post.self, statuses: [], offset: 0, number: 5, in: blogID) + XCTAssertEqual(result, Array(all[0..<5])) + + result = try await repository.paginate(type: Post.self, statuses: [], offset: 3, number: 2, in: blogID) + XCTAssertEqual(result, [all[3], all[4]]) + } + + func testPostsListStubReturnPostsSearch() async throws { + stubGetPostsList(type: "post", total: 10) + + let all = try await repository.search(type: Post.self, input: nil, statuses: [], tag: nil, offset: 0, limit: 30, orderBy: .byDate, descending: true, in: blogID) + + var result = try await repository.search(type: Post.self, input: "1", statuses: [], tag: nil, offset: 0, limit: 1, orderBy: .byDate, descending: true, in: blogID) + XCTAssertEqual(result, [all[0]]) + + result = try await repository.search(type: Post.self, input: "2", statuses: [], tag: nil, offset: 0, limit: 1, orderBy: .byDate, descending: true, in: blogID) + XCTAssertEqual(result, [all[1]]) + } + + func testPostsListStubReturnDefaultNumberOfPosts() async throws { + stubGetPostsList(type: "post", total: 100) + + let result = try await repository.search(type: Post.self, input: nil, statuses: [], tag: nil, offset: 0, limit: 0, orderBy: .byDate, descending: true, in: blogID) + XCTAssertEqual(result.count, 20) + } + +}