Skip to content

Commit 097bccf

Browse files
committed
Add POSSiteSettingService with a protocol for setting POS feature switch through API.
1 parent 874f6ae commit 097bccf

File tree

4 files changed

+122
-0
lines changed

4 files changed

+122
-0
lines changed

Modules/Sources/Networking/Remote/SiteSettingsRemote.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import Foundation
22

3+
/// Protocol for SiteSettingsRemote to enable testing.
4+
public protocol SiteSettingsRemoteProtocol {
5+
func setFeature(for siteID: Int64, feature: SiteSettingsFeature, enabled: Bool) async throws -> Bool
6+
}
7+
38
/// Features that can be enabled/disabled in core, under WC Settings > Advanced > Features.
49
public enum SiteSettingsFeature {
510
case pointOfSale
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Foundation
2+
import Networking
3+
4+
/// Protocol for POS site setting service that manages WooCommerce feature flags.
5+
public protocol POSSiteSettingServiceProtocol {
6+
/// Enables or disables a specific feature in the site WC settings.
7+
/// - Parameters:
8+
/// - siteID: The site ID for which to update the feature setting.
9+
/// - feature: The feature to enable or disable.
10+
/// - enabled: Whether the feature should be enabled (true) or disabled (false).
11+
/// - Returns: A boolean indicating the updated feature status.
12+
/// - Throws: Error if the request fails or the setting cannot be updated.
13+
func setFeature(siteID: Int64, feature: SiteSettingsFeature, enabled: Bool) async throws -> Bool
14+
}
15+
16+
/// Service for managing Point of Sale related site settings.
17+
public final class POSSiteSettingService: POSSiteSettingServiceProtocol {
18+
private let remote: SiteSettingsRemoteProtocol
19+
20+
public init(credentials: Credentials?) {
21+
let network = AlamofireNetwork(credentials: credentials)
22+
self.remote = SiteSettingsRemote(network: network)
23+
}
24+
25+
/// Test-friendly initializer that accepts a remote implementation.
26+
/// - Parameter remote: The remote service implementation to use for network requests.
27+
init(remote: SiteSettingsRemoteProtocol) {
28+
self.remote = remote
29+
}
30+
31+
public func setFeature(siteID: Int64, feature: SiteSettingsFeature, enabled: Bool) async throws -> Bool {
32+
try await remote.setFeature(for: siteID, feature: feature, enabled: enabled)
33+
}
34+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Networking
2+
3+
final class MockSiteSettingsRemote: SiteSettingsRemoteProtocol {
4+
var setFeatureCalled: Bool = false
5+
var spySetFeatureSiteID: Int64?
6+
var spySetFeatureFeature: SiteSettingsFeature?
7+
var spySetFeatureEnabled: Bool?
8+
var setFeatureResult: Result<Bool, Error> = .success(true)
9+
10+
func setFeature(for siteID: Int64, feature: SiteSettingsFeature, enabled: Bool) async throws -> Bool {
11+
setFeatureCalled = true
12+
spySetFeatureSiteID = siteID
13+
spySetFeatureFeature = feature
14+
spySetFeatureEnabled = enabled
15+
16+
switch setFeatureResult {
17+
case .success(let result):
18+
return result
19+
case .failure(let error):
20+
throw error
21+
}
22+
}
23+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import Foundation
2+
import Testing
3+
@testable import Yosemite
4+
@testable import Networking
5+
6+
struct POSSiteSettingServiceTests {
7+
private let sut: POSSiteSettingService
8+
private let mockRemote: MockSiteSettingsRemote
9+
private let sampleSiteID: Int64 = 123
10+
11+
init() {
12+
let mockRemote = MockSiteSettingsRemote()
13+
self.mockRemote = mockRemote
14+
self.sut = POSSiteSettingService(remote: mockRemote)
15+
}
16+
17+
@Test(arguments: [true, false])
18+
func setFeature_calls_remote_with_correct_parameters(featureEnabled: Bool) async throws {
19+
// Given
20+
let feature = SiteSettingsFeature.pointOfSale
21+
mockRemote.setFeatureResult = .success(true)
22+
23+
// When
24+
let result = try await sut.setFeature(siteID: sampleSiteID, feature: feature, enabled: featureEnabled)
25+
26+
// Then
27+
#expect(mockRemote.setFeatureCalled == true)
28+
#expect(mockRemote.spySetFeatureSiteID == sampleSiteID)
29+
#expect(mockRemote.spySetFeatureFeature == feature)
30+
#expect(mockRemote.spySetFeatureEnabled == featureEnabled)
31+
#expect(result == true)
32+
}
33+
34+
@Test(arguments: [true, false])
35+
func setFeature_returns_false_when_remote_returns_false(remoteFeatureEnabled: Bool) async throws {
36+
// Given
37+
let feature = SiteSettingsFeature.pointOfSale
38+
mockRemote.setFeatureResult = .success(remoteFeatureEnabled)
39+
40+
// When
41+
let result = try await sut.setFeature(siteID: sampleSiteID, feature: feature, enabled: true)
42+
43+
// Then
44+
#expect(result == remoteFeatureEnabled)
45+
}
46+
47+
@Test func setFeature_throws_error_when_remote_throws_error() async throws {
48+
// Given
49+
let feature = SiteSettingsFeature.pointOfSale
50+
let expectedError = SiteSettingsRemoteError.invalidResponse
51+
mockRemote.setFeatureResult = .failure(expectedError)
52+
53+
// When/Then
54+
await #expect(performing: {
55+
try await sut.setFeature(siteID: sampleSiteID, feature: feature, enabled: true)
56+
}, throws: { error in
57+
return error as? SiteSettingsRemoteError == expectedError
58+
})
59+
}
60+
}

0 commit comments

Comments
 (0)