Skip to content
7 changes: 0 additions & 7 deletions Modules/Sources/Storage/Tools/StorageType+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -832,13 +832,6 @@ public extension StorageType {
return allObjects(ofType: SystemPlugin.self, matching: predicate, sortedBy: [descriptor])
}

/// Returns a system plugin with a specified `siteID` and `name`
///
func loadSystemPlugin(siteID: Int64, name: String) -> SystemPlugin? {
let predicate = \SystemPlugin.siteID == siteID && \SystemPlugin.name == name
return firstObject(ofType: SystemPlugin.self, matching: predicate)
}

/// Returns a system plugin with a specified `siteID` and `path`.
///
/// - Parameters:
Expand Down
4 changes: 0 additions & 4 deletions Modules/Sources/Yosemite/Actions/SystemStatusAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ public enum SystemStatusAction: Action {
///
case fetchSystemPlugin(siteID: Int64, systemPluginName: String, onCompletion: (SystemPlugin?) -> Void)

/// Fetch an specific systemPlugin by siteID and name list.
///
case fetchSystemPluginListWithNameList(siteID: Int64, systemPluginNameList: [String], onCompletion: (SystemPlugin?) -> Void)

/// Fetch a specific systemPlugin by path.
///
case fetchSystemPluginWithPath(siteID: Int64, pluginPath: String, onCompletion: (SystemPlugin?) -> Void)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ struct MockSystemStatusActionHandler: MockActionHandler {
let filteredSystemPlugin = systemPlugins.first { $0.name == systemPluginName }
let matchingPlugin = systemPlugins.first { $0.name == systemPluginName && $0.active } ?? filteredSystemPlugin
onCompletion(matchingPlugin)
case .fetchSystemPluginListWithNameList(let siteID, let systemPluginNameList, let onCompletion):
let systemPlugins = objectGraph.systemPlugins(for: siteID)
let filteredSystemPlugins = systemPlugins.first { systemPluginNameList.contains($0.name) }
onCompletion(filteredSystemPlugins)
case .fetchSystemPluginWithPath(let siteID, let pluginPath, let onCompletion):
let systemPlugins = objectGraph.systemPlugins(for: siteID)
let matchingPlugin = systemPlugins.first { $0.plugin == pluginPath }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,20 @@ public struct POSPluginAndFeatureInfo {
public final class POSSystemStatusService: POSSystemStatusServiceProtocol {
private let remote: SystemStatusRemote
private let storageManager: StorageManagerType
private let pluginsService: PluginsServiceProtocol

public init(credentials: Credentials?, storageManager: StorageManagerType) {
let network = AlamofireNetwork(credentials: credentials)
self.remote = SystemStatusRemote(network: network)
self.storageManager = storageManager
self.pluginsService = PluginsService(storageManager: storageManager)
}

/// Test-friendly initializer that accepts a network implementation.
init(network: Network, storageManager: StorageManagerType) {
self.remote = SystemStatusRemote(network: network)
self.storageManager = storageManager
self.pluginsService = PluginsService(storageManager: storageManager)
}

@MainActor
Expand All @@ -54,9 +57,7 @@ public final class POSSystemStatusService: POSSystemStatusServiceProtocol {
})

// Loads WooCommerce plugin from storage.
guard let wcPlugin = storageManager.viewStorage.loadSystemPlugin(siteID: siteID,
fileNameWithoutExtension: Constants.wcPluginFileNameWithoutExtension,
active: true)?.toReadOnly() else {
guard let wcPlugin = pluginsService.loadPluginInStorage(siteID: siteID, plugin: .wooCommerce, isActive: true) else {
return POSPluginAndFeatureInfo(wcPlugin: nil, featureValue: nil)
}

Expand All @@ -66,12 +67,6 @@ public final class POSSystemStatusService: POSSystemStatusServiceProtocol {
}
}

private extension POSSystemStatusService {
enum Constants {
static let wcPluginFileNameWithoutExtension = "woocommerce"
}
}

// MARK: - Network Response Structs

private struct POSPluginEligibilitySystemStatus: Decodable {
Expand Down
2 changes: 0 additions & 2 deletions Modules/Sources/Yosemite/Stores/SystemStatusStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ public final class SystemStatusStore: Store {
synchronizeSystemInformation(siteID: siteID, completionHandler: onCompletion)
case .fetchSystemPlugin(let siteID, let systemPluginName, let onCompletion):
fetchSystemPlugin(siteID: siteID, systemPluginNameList: [systemPluginName], completionHandler: onCompletion)
case .fetchSystemPluginListWithNameList(let siteID, let systemPluginNameList, let onCompletion):
fetchSystemPlugin(siteID: siteID, systemPluginNameList: systemPluginNameList, completionHandler: onCompletion)
case .fetchSystemPluginWithPath(let siteID, let pluginPath, let onCompletion):
fetchSystemPluginWithPath(siteID: siteID,
pluginPath: pluginPath,
Expand Down
23 changes: 23 additions & 0 deletions Modules/Sources/Yosemite/Tools/Plugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Foundation

public enum Plugin: Equatable, CaseIterable {
case wooCommerce
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea to make an enum for consistency 👍

case wooSubscriptions
case wooShipmentTracking
case wooSquare

/// File name without extension in the plugin path.
/// Full plugin path is like `woocommerce/woocommerce.php`.
var fileNameWithoutExtension: String {
switch self {
case .wooCommerce:
return "woocommerce"
case .wooSubscriptions:
return "woocommerce-subscriptions"
case .wooShipmentTracking:
return "woocommerce-shipment-tracking"
case .wooSquare:
return "woocommerce-square"
}
}
}
21 changes: 21 additions & 0 deletions Modules/Sources/Yosemite/Tools/Plugins/PluginsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ public protocol PluginsServiceProtocol {
/// - isActive: Whether the plugin is active or not.
/// - Returns: The SystemPlugin when found in storage.
func waitForPluginInStorage(siteID: Int64, pluginPath: String, isActive: Bool) async -> SystemPlugin

/// Loads a specific plugin from storage synchronously.
/// - Parameters:
/// - siteID: The site ID to search for the plugin.
/// - plugin: The plugin to load.
/// - isActive: Whether the plugin is active, inactive, or nil for any state.
/// - Returns: The SystemPlugin if found in storage, nil otherwise.
func loadPluginInStorage(siteID: Int64, plugin: Plugin, isActive: Bool?) -> SystemPlugin?
}

public extension PluginsServiceProtocol {
func isPluginActiveInStorage(siteID: Int64, plugin: Plugin) -> Bool {
let plugin = loadPluginInStorage(siteID: siteID, plugin: plugin, isActive: true)
return plugin != nil && plugin?.active == true
}
}

public class PluginsService: PluginsServiceProtocol {
Expand Down Expand Up @@ -49,4 +64,10 @@ public class PluginsService: PluginsServiceProtocol {
}
}
}

public func loadPluginInStorage(siteID: Int64, plugin: Plugin, isActive: Bool?) -> SystemPlugin? {
storageManager.viewStorage.loadSystemPlugin(siteID: siteID,
fileNameWithoutExtension: plugin.fileNameWithoutExtension,
active: isActive)?.toReadOnly()
}
}
17 changes: 0 additions & 17 deletions Modules/Tests/StorageTests/Tools/StorageTypeExtensionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1432,23 +1432,6 @@ final class StorageTypeExtensionsTests: XCTestCase {
XCTAssertEqual(storedSystemPlugins, [systemPlugin1, systemPlugin4])
}

func test_loadSystemPlugin_by_siteID_and_name() throws {
// Given
let systemPlugin1 = storage.insertNewObject(ofType: SystemPlugin.self)
systemPlugin1.name = "Plugin 1"
systemPlugin1.siteID = sampleSiteID

let systemPlugin2 = storage.insertNewObject(ofType: SystemPlugin.self)
systemPlugin2.name = "Plugin 2"
systemPlugin2.siteID = sampleSiteID

// When
let foundSystemPlugin = try XCTUnwrap(storage.loadSystemPlugin(siteID: sampleSiteID, name: "Plugin 2"))

// Then
XCTAssertEqual(foundSystemPlugin, systemPlugin2)
}

func test_loadSystemPlugin_by_siteID_and_path() throws {
// Given
let systemPlugin1 = storage.insertNewObject(ofType: SystemPlugin.self)
Expand Down
31 changes: 3 additions & 28 deletions Modules/Tests/YosemiteTests/Stores/SystemStatusStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ final class SystemStatusStoreTests: XCTestCase {

func test_synchronizeSystemInformation_removes_stale_systemPlugins_correctly() {
// Given
let staleSystemPluginName = "Stale System Plugin"
let staleSystemPlugin = SystemPlugin.fake().copy(siteID: sampleSiteID, name: staleSystemPluginName)
let staleSystemPluginPath = "folder/stale-plugin.php"
let staleSystemPlugin = SystemPlugin.fake().copy(siteID: sampleSiteID, plugin: staleSystemPluginPath)
let storedStaleSystemPlugin = viewStorage.insertNewObject(ofType: StorageSystemPlugin.self)
storedStaleSystemPlugin.update(with: staleSystemPlugin)
XCTAssertEqual(viewStorage.countObjects(ofType: StorageSystemPlugin.self), 1)
Expand All @@ -98,7 +98,7 @@ final class SystemStatusStoreTests: XCTestCase {
// Then
XCTAssertTrue(result.isSuccess)
XCTAssertEqual(viewStorage.countObjects(ofType: StorageSystemPlugin.self), 6) // number of systemPlugins in json file
XCTAssertNil(viewStorage.loadSystemPlugin(siteID: sampleSiteID, name: staleSystemPluginName))
XCTAssertNil(viewStorage.loadSystemPlugin(siteID: sampleSiteID, fileNameWithoutExtension: "stale-plugin"))
}

func test_fetchSystemPlugins_return_systemPlugins_correctly() {
Expand Down Expand Up @@ -194,31 +194,6 @@ final class SystemStatusStoreTests: XCTestCase {
XCTAssertNil(fetchedPlugin)
}

func test_fetchSystemPluginsList_return_systemPlugins_correctly() {
// Given
let systemPlugin1 = viewStorage.insertNewObject(ofType: SystemPlugin.self)
systemPlugin1.name = "Plugin 1"
systemPlugin1.siteID = sampleSiteID

let systemPlugin3 = viewStorage.insertNewObject(ofType: SystemPlugin.self)
systemPlugin3.name = "Plugin 3"
systemPlugin3.siteID = sampleSiteID

let store = SystemStatusStore(dispatcher: dispatcher, storageManager: storageManager, network: network)

// When
let systemPluginResult: Yosemite.SystemPlugin? = waitFor { promise in
let action = SystemStatusAction.fetchSystemPluginListWithNameList(siteID: self.sampleSiteID,
systemPluginNameList: ["Plugin 2", "Plugin 3"]) { result in
promise(result)
}
store.onAction(action)
}

// Then
XCTAssertEqual(systemPluginResult?.name, "Plugin 3")
}

func test_fetchSystemPluginWithPath_returns_plugin_when_matching_plugin_is_in_storage() {
// Given
let systemPlugin1 = viewStorage.insertNewObject(ofType: SystemPlugin.self)
Expand Down
108 changes: 90 additions & 18 deletions Modules/Tests/YosemiteTests/Tools/Plugins/PluginsServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ struct PluginsServiceTests {
@Test func waitForPluginInStorage_returns_plugin_when_already_in_storage() async {
// Given
await storageManager.reset()
storageManager.insertWCPlugin(siteID: siteID, isActive: true, version: "1.0.0")
storageManager.insertPlugin(siteID: siteID, plugin: .wooCommerce, isActive: true, version: "1.0.0")

// When
let result = await sut.waitForPluginInStorage(siteID: siteID, pluginPath: PluginConstants.plugin, isActive: true)
let result = await sut.waitForPluginInStorage(siteID: siteID, pluginPath: "woocommerce/woocommerce.php", isActive: true)

// Then
#expect(result.siteID == siteID)
#expect(result.plugin == PluginConstants.plugin)
#expect(result.plugin == "woocommerce/woocommerce.php")
#expect(result.active == true)
#expect(result.version == "1.0.0")
}
Expand All @@ -33,31 +33,103 @@ struct PluginsServiceTests {
await storageManager.reset()

// When
async let plugin = sut.waitForPluginInStorage(siteID: siteID, pluginPath: PluginConstants.plugin, isActive: true)
async let plugin = sut.waitForPluginInStorage(siteID: siteID, pluginPath: "woocommerce/woocommerce.php", isActive: true)
#expect(storageManager.viewStorage.loadSystemPlugins(siteID: siteID).count == 0)
storageManager.insertWCPlugin(siteID: siteID, isActive: true, version: "2.0.0")
storageManager.insertPlugin(siteID: siteID, plugin: .wooCommerce, isActive: true, version: "2.0.0")
#expect(storageManager.viewStorage.loadSystemPlugins(siteID: siteID).count == 1)

// Then
let result = await plugin
#expect(result.siteID == siteID)
#expect(result.plugin == PluginConstants.plugin)
#expect(result.name == PluginConstants.pluginName)
#expect(result.plugin == "woocommerce/woocommerce.php")
#expect(result.active == true)
#expect(result.version == "2.0.0")
}

// MARK: - `loadPluginInStorage`

@Test(arguments: [(Plugin.wooCommerce, true, "1.5.0"),
(Plugin.wooCommerce, false, "2.1.0"),
(Plugin.wooSubscriptions, true, "3.0.0"),
(Plugin.wooShipmentTracking, false, "3.0.0")])
func loadPluginInStorage_returns_plugin_when_exists_in_storage(plugin: Plugin, isActive: Bool, version: String) async throws {
// Given
await storageManager.reset()
storageManager.insertPlugin(siteID: siteID, plugin: plugin, isActive: isActive, version: version)

// When
let result = sut.loadPluginInStorage(siteID: siteID, plugin: plugin, isActive: isActive)

// Then
let unwrappedResult = try #require(result)
#expect(unwrappedResult.siteID == siteID)
#expect(unwrappedResult.plugin == plugin.pluginPath)
#expect(unwrappedResult.active == isActive)
#expect(unwrappedResult.version == version)
}

@Test func loadPluginInStorage_returns_plugin_when_exists_in_storage_with_any_active_state() async throws {
// Given
await storageManager.reset()
storageManager.insertPlugin(siteID: siteID, plugin: .wooCommerce, isActive: true, version: "3.0.0")

// When
let result = sut.loadPluginInStorage(siteID: siteID, plugin: .wooCommerce, isActive: nil)

// Then
let unwrappedResult = try #require(result)
#expect(unwrappedResult.siteID == siteID)
#expect(unwrappedResult.plugin == "woocommerce/woocommerce.php")
#expect(unwrappedResult.active == true)
#expect(unwrappedResult.version == "3.0.0")
}

@Test func loadPluginInStorage_returns_nil_when_plugin_does_not_exist() async {
// Given
await storageManager.reset()

// When
let result = sut.loadPluginInStorage(siteID: siteID, plugin: .wooCommerce, isActive: true)

// Then
#expect(result == nil)
}

@Test func loadPluginInStorage_returns_nil_when_plugin_exists_but_active_state_does_not_match() async {
// Given
await storageManager.reset()
storageManager.insertPlugin(siteID: siteID, plugin: .wooCommerce, isActive: true, version: "1.0.0")

// When
let result = sut.loadPluginInStorage(siteID: siteID, plugin: .wooCommerce, isActive: false)

// Then
#expect(result == nil)
}

@Test func loadPluginInStorage_returns_nil_when_plugin_exists_for_different_site() async {
// Given
await storageManager.reset()
let differentSiteID: Int64 = 999
storageManager.insertPlugin(siteID: differentSiteID, plugin: .wooCommerce, isActive: true, version: "1.0.0")

// When
let result = sut.loadPluginInStorage(siteID: siteID, plugin: .wooCommerce, isActive: true)

// Then
#expect(result == nil)
}
}

private extension MockStorageManager {
func insertWCPlugin(siteID: Int64, isActive: Bool, version: String? = nil) {
func insertPlugin(siteID: Int64, plugin: Plugin, isActive: Bool, version: String? = nil) {
performAndSave({ storage in
let plugin = SystemPlugin.fake().copy(siteID: siteID,
plugin: PluginConstants.plugin,
name: PluginConstants.pluginName,
version: version,
active: isActive)
let systemPlugin = SystemPlugin.fake().copy(siteID: siteID,
plugin: plugin.pluginPath,
version: version,
active: isActive)
let newPlugin = storage.insertNewObject(ofType: StorageSystemPlugin.self)
newPlugin.update(with: plugin)
newPlugin.update(with: systemPlugin)
}, completion: nil, on: .main)
}

Expand All @@ -70,8 +142,8 @@ private extension MockStorageManager {
}
}

// MARK: - Constants
private enum PluginConstants {
static let plugin = "example-plugin/example-plugin.php"
static let pluginName = "Example Plugin"
extension Plugin {
var pluginPath: String {
"\(fileNameWithoutExtension)/\(fileNameWithoutExtension).php"
}
}
2 changes: 0 additions & 2 deletions WooCommerce/Classes/Extensions/SitePlugin+Woo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import Yosemite
///
extension SitePlugin {
enum SupportedPlugin {
public static let WCTracking = "WooCommerce Shipment Tracking"
public static let WCSubscriptions = ["WooCommerce Subscriptions", "Woo Subscriptions"]
public static let WCProductBundles = ["WooCommerce Product Bundles", "Woo Product Bundles"]
public static let WCCompositeProducts = "WooCommerce Composite Products"
public static let square = "WooCommerce Square"
public static let WCGiftCards = ["WooCommerce Gift Cards", "Woo Gift Cards"]
public static let GoogleForWooCommerce = ["Google Listings and Ads", "Google for WooCommerce"]
}
Expand Down
Loading