Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import OHHTTPStubs
import OHHTTPStubsSwift
import XCTest

@testable import WordPress
@testable import WordPressData

final class WordPressClientFactoryTests: CoreDataTestCase {
override func setUp() {
super.setUp()
contextManager.useAsSharedInstance(untilTestFinished: self)
WordPressClientFactory.shared.reset()
HTTPStubs.removeAllStubs()
stub(condition: { _ in true }) { _ in
HTTPStubsResponse(
jsonObject: [:],
statusCode: 200,
headers: ["Content-Type": "application/json"]
)
}
}

override func tearDown() {
WordPressClientFactory.shared.reset()
HTTPStubs.removeAllStubs()
super.tearDown()
}

func testEvictInstanceIsIdempotentAndCreatesANewClient() throws {
let site = try makeSite(dotComID: 123)
let otherSite = try makeSite(dotComID: 456)
let original = WordPressClientFactory.shared.instance(for: site)
let otherOriginal = WordPressClientFactory.shared.instance(for: otherSite)

WordPressClientFactory.shared.evictInstance(for: site.blogId)
WordPressClientFactory.shared.evictInstance(for: site.blogId)

let replacement = WordPressClientFactory.shared.instance(for: site)
XCTAssertFalse(original === replacement)
XCTAssertTrue(replacement === WordPressClientFactory.shared.instance(for: site))
XCTAssertTrue(otherOriginal === WordPressClientFactory.shared.instance(for: otherSite))
}

func testResetCreatesANewClient() throws {
let site = try makeSite(dotComID: 123)
let original = WordPressClientFactory.shared.instance(for: site)

WordPressClientFactory.shared.reset()

XCTAssertFalse(original === WordPressClientFactory.shared.instance(for: site))
}

func testRemovingDefaultAccountEvictsBlogClient() throws {
let blog = makeBlog(dotComID: 123)
try mainContext.save()
let site = try WordPressSite(blog: blog)
let original = WordPressClientFactory.shared.instance(for: site)
let service = AccountService(coreDataStack: contextManager)
service.setDefaultWordPressComAccount(try XCTUnwrap(blog.account))

service.removeDefaultWordPressComAccount()

XCTAssertFalse(original === WordPressClientFactory.shared.instance(for: site))
}

func testRemovingBlogEvictsClient() throws {
let blog = makeBlog(dotComID: 123)
try mainContext.save()
let site = try WordPressSite(blog: blog)
let original = WordPressClientFactory.shared.instance(for: site)

BlogService(coreDataStack: contextManager).remove(blog)

XCTAssertFalse(original === WordPressClientFactory.shared.instance(for: site))
}

private func makeSite(dotComID: Int) throws -> WordPressSite {
try WordPressSite(blog: makeBlog(dotComID: dotComID))
}

private func makeBlog(dotComID: Int) -> Blog {
let blog = BlogBuilder(mainContext, dotComID: NSNumber(value: dotComID))
.with(url: "https://example.wordpress.com")
.isHostedAtWPcom()
.withAnAccount(username: "test-user", authToken: "test-token")
.build()
blog.account?.uuid = UUID().uuidString
return blog
}
}
6 changes: 6 additions & 0 deletions WordPress/Classes/Networking/WordPressClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ public final class WordPressClientFactory: Sendable {
}
}

public func evictInstance(for blogID: TaggedManagedObjectID<Blog>) {
instances.withLock { instances in
instances.removeValue(forKey: blogID)
}
}

public func reset() {
instances.withLock { dict in
dict.removeAll()
Expand Down
8 changes: 7 additions & 1 deletion WordPress/Classes/Services/AccountService+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ extension AccountService {
return
}

UserPersistentStoreFactory.instance().set(account.uuid, forKey: AccountService.defaultDotcomAccountUUIDDefaultsKey)
UserPersistentStoreFactory.instance()
.set(account.uuid, forKey: AccountService.defaultDotcomAccountUUIDDefaultsKey)

let objectID = TaggedManagedObjectID(account)
let notifyAccountChange = {
Expand Down Expand Up @@ -48,6 +49,11 @@ extension AccountService {
return
}

account.blogs?
.forEach {
WordPressClientFactory.shared.evictInstance(for: TaggedManagedObjectID($0))
}

let objectID = TaggedManagedObjectID(account)
coreDataStack.performAndSave { context in
do {
Expand Down
50 changes: 36 additions & 14 deletions WordPress/Classes/Services/BlogService+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import WordPressCore
import WordPressAPI

extension BlogService {
@objc(evictWordPressClientForBlog:)
public func evictWordPressClient(for blog: Blog) {
WordPressClientFactory.shared.evictInstance(for: TaggedManagedObjectID(blog))
}

@objc public func unscheduleBloggingReminders(for blog: Blog) {
do {
let scheduler = try ReminderScheduleCoordinator()
Expand All @@ -20,11 +25,12 @@ extension BlogService {

@objc public func updatePromptSettings(for blog: RemoteBlog?, context: NSManagedObjectContext) {
guard let blog,
let jsonSettings = blog.options["blogging_prompts_settings"] as? [String: Any],
let settingsValue = jsonSettings["value"] as? [String: Any],
JSONSerialization.isValidJSONObject(settingsValue),
let data = try? JSONSerialization.data(withJSONObject: settingsValue),
let remoteSettings = try? JSONDecoder().decode(RemoteBloggingPromptsSettings.self, from: data) else {
let jsonSettings = blog.options["blogging_prompts_settings"] as? [String: Any],
let settingsValue = jsonSettings["value"] as? [String: Any],
JSONSerialization.isValidJSONObject(settingsValue),
let data = try? JSONSerialization.data(withJSONObject: settingsValue),
let remoteSettings = try? JSONDecoder().decode(RemoteBloggingPromptsSettings.self, from: data)
else {
return
}

Expand Down Expand Up @@ -130,14 +136,19 @@ extension BlogService {

guard blog.isSelfHosted,
let xmlrpcApi = blog.xmlrpcApi,
let username = blog.username,
let password = blog.password else {
let username = blog.username,
let password = blog.password
else {

// Set isXMLRPCDisabled to false if the site is not a self-hosted site.
self.coreDataStack.performAndSave({ context in
guard let blog = try? context.existingObject(with: blogObjectID) as? Blog else { return }
blog.isXMLRPCDisabled = false
}, completion: failure, on: .main)
self.coreDataStack.performAndSave(
{ context in
guard let blog = try? context.existingObject(with: blogObjectID) as? Blog else { return }
blog.isXMLRPCDisabled = false
},
completion: failure,
on: .main
)

return
}
Expand Down Expand Up @@ -170,7 +181,10 @@ extension BlogService {
}
}

static func blog(with site: JetpackSiteRef, context: NSManagedObjectContext = ContextManager.shared.mainContext) -> Blog? {
static func blog(
with site: JetpackSiteRef,
context: NSManagedObjectContext = ContextManager.shared.mainContext
) -> Blog? {
let blog: Blog?

if site.isSelfHostedWithoutJetpack, let xmlRPC = site.xmlRPC {
Expand All @@ -184,7 +198,15 @@ extension BlogService {
}

private extension BlogService {
private func findBlogAuthor(with userId: NSNumber, and blog: Blog, in context: NSManagedObjectContext) -> BlogAuthor {
return context.entity(of: BlogAuthor.self, with: NSPredicate(format: "\(#keyPath(BlogAuthor.userID)) = %@ AND \(#keyPath(BlogAuthor.blog)) = %@", userId, blog))
private func findBlogAuthor(with userId: NSNumber, and blog: Blog, in context: NSManagedObjectContext) -> BlogAuthor
{
context.entity(
of: BlogAuthor.self,
with: NSPredicate(
format: "\(#keyPath(BlogAuthor.userID)) = %@ AND \(#keyPath(BlogAuthor.blog)) = %@",
userId,
blog
)
)
}
}
1 change: 1 addition & 0 deletions WordPress/Classes/Services/BlogService.m
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ - (void)removeBlog:(Blog *)blog
DDLogInfo(@"<Blog:%@> remove", blog.displayURL);
[blog.xmlrpcApi invalidateAndCancelTasks];
[self unscheduleBloggingRemindersFor:blog];
[self evictWordPressClientForBlog:blog];

WPAccount *account = blog.account;

Expand Down