Granularize app features (#671)

Split .networkSettings and add .sharing for #668
This commit is contained in:
Davide 2024-10-03 12:13:03 +02:00 committed by GitHub
parent 63b0199a39
commit 0917e47ea3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 94 additions and 38 deletions

View File

@ -71,6 +71,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
0E06D18F2B87629100176E1D /* Passepartout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Passepartout.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0E06D18F2B87629100176E1D /* Passepartout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Passepartout.app; sourceTree = BUILT_PRODUCTS_DIR; };
0E7C3CCC2C9AF44600B72E69 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 0E7C3CCC2C9AF44600B72E69 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
0E7D0EAD2CAEA47700A2F28D /* Passepartout.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Passepartout.xctestplan; sourceTree = "<group>"; };
0E7E3D5B2B9345FD002BBDB4 /* App.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; }; 0E7E3D5B2B9345FD002BBDB4 /* App.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
0E7E3D5C2B9345FD002BBDB4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 0E7E3D5C2B9345FD002BBDB4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0E7E3D5F2B9345FD002BBDB4 /* PassepartoutApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassepartoutApp.swift; sourceTree = "<group>"; }; 0E7E3D5F2B9345FD002BBDB4 /* PassepartoutApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassepartoutApp.swift; sourceTree = "<group>"; };
@ -152,6 +153,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0E8D852F2C328CA1005493DE /* Config.xcconfig */, 0E8D852F2C328CA1005493DE /* Config.xcconfig */,
0E7D0EAD2CAEA47700A2F28D /* Passepartout.xctestplan */,
0E7E3D5A2B9345FD002BBDB4 /* App */, 0E7E3D5A2B9345FD002BBDB4 /* App */,
0EDE56E82CABE40D0082D21C /* Intents */, 0EDE56E82CABE40D0082D21C /* Intents */,
0E7E3D612B9345FD002BBDB4 /* Shared */, 0E7E3D612B9345FD002BBDB4 /* Shared */,

View File

@ -26,8 +26,13 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES">
shouldAutocreateTestPlan = "YES"> <TestPlans>
<TestPlanReference
reference = "container:Passepartout/Passepartout.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"

View File

@ -25,24 +25,33 @@
import Foundation import Foundation
public enum AppFeature: String, CaseIterable { public enum AppFeature: String {
case appleTV case appleTV
case interactiveLogin case dns
case networkSettings case httpProxy
case interactiveLogin
case onDemand case onDemand
case providers case providers
case routing
case sharing
case siri case siri
public static let allCases: [AppFeature] = [ public static let fullVersionFeaturesV2: [AppFeature] = [
.dns,
.httpProxy,
.interactiveLogin, .interactiveLogin,
.networkSettings,
.onDemand, .onDemand,
.providers, .providers,
.routing,
.sharing,
.siri .siri
] ]
} }

View File

@ -81,7 +81,7 @@ extension AppProduct: AppFeatureProviding {
return [.appleTV] return [.appleTV]
case .Features.networkSettings: case .Features.networkSettings:
return [.networkSettings] return [.dns, .httpProxy, .routing]
case .Features.siriShortcuts: case .Features.siriShortcuts:
return [.siri] return [.siri]
@ -90,18 +90,18 @@ extension AppProduct: AppFeatureProviding {
return [.onDemand] return [.onDemand]
case .Full.allPlatforms: case .Full.allPlatforms:
return AppFeature.allCases return AppFeature.fullVersionFeaturesV2
case .Full.iOS: case .Full.iOS:
#if os(iOS) #if os(iOS)
return AppFeature.allCases return AppFeature.fullVersionFeaturesV2
#else #else
return [] return []
#endif #endif
case .Full.macOS: case .Full.macOS:
#if os(macOS) #if os(macOS)
return AppFeature.allCases return AppFeature.fullVersionFeaturesV2
#else #else
return [] return []
#endif #endif

View File

@ -29,10 +29,10 @@ extension AppUserLevel: AppFeatureProviding {
var features: [AppFeature] { var features: [AppFeature] {
switch self { switch self {
case .fullVersion: case .fullVersion:
return AppFeature.allCases return AppFeature.fullVersionFeaturesV2
case .fullVersionPlusTV: case .fullVersionPlusTV:
var list = AppFeature.allCases var list = AppFeature.fullVersionFeaturesV2
list.append(.appleTV) list.append(.appleTV)
return list return list

View File

@ -103,11 +103,17 @@ private extension ProfileCoordinator {
private extension ProfileCoordinator { private extension ProfileCoordinator {
func onNewModule(_ moduleType: ModuleType) { func onNewModule(_ moduleType: ModuleType) {
switch moduleType { switch moduleType {
case .onDemand: case .dns:
break paywallReason = iapManager.paywallReason(forFeature: .dns)
default: case .httpProxy:
paywallReason = iapManager.paywallReason(forFeature: .networkSettings) paywallReason = iapManager.paywallReason(forFeature: .httpProxy)
case .ip:
paywallReason = iapManager.paywallReason(forFeature: .routing)
case .onDemand, .openVPN, .wireGuard:
break
} }
guard paywallReason == nil else { guard paywallReason == nil else {
return return

View File

@ -62,7 +62,7 @@ extension ConnectionObserverTests {
XCTAssertEqual(sut.dataCount, nil) XCTAssertEqual(sut.dataCount, nil)
try await tunnel.install(profile, connect: true, title: \.name) try await tunnel.install(profile, connect: true, title: \.name)
try await Task.sleep(for: .milliseconds(200)) try await Task.sleep(for: .milliseconds(300))
XCTAssertEqual(sut.dataCount, dataCount) XCTAssertEqual(sut.dataCount, dataCount)
} }
} }

View File

@ -52,7 +52,7 @@ extension IAPManagerTests {
return [] return []
} }
await sut.reloadReceipt() await sut.reloadReceipt()
XCTAssertTrue(sut.isEligible(for: AppFeature.allCases)) XCTAssertTrue(sut.isEligible(for: AppFeature.fullVersionFeaturesV2))
} }
func test_givenBuildProducts_whenNewer_thenFreeVersion() async { func test_givenBuildProducts_whenNewer_thenFreeVersion() async {
@ -65,7 +65,7 @@ extension IAPManagerTests {
return [] return []
} }
await sut.reloadReceipt() await sut.reloadReceipt()
XCTAssertFalse(sut.isEligible(for: AppFeature.allCases)) XCTAssertFalse(sut.isEligible(for: AppFeature.fullVersionFeaturesV2))
} }
// MARK: Eligibility // MARK: Eligibility
@ -74,13 +74,13 @@ extension IAPManagerTests {
let reader = MockReceiptReader() let reader = MockReceiptReader()
let sut = IAPManager(receiptReader: reader) let sut = IAPManager(receiptReader: reader)
XCTAssertFalse(sut.isEligible(for: AppFeature.allCases)) XCTAssertFalse(sut.isEligible(for: AppFeature.fullVersionFeaturesV2))
await reader.setReceipt(withBuild: defaultBuildNumber, products: [.Full.allPlatforms]) await reader.setReceipt(withBuild: defaultBuildNumber, products: [.Full.allPlatforms])
XCTAssertFalse(sut.isEligible(for: AppFeature.allCases)) XCTAssertFalse(sut.isEligible(for: AppFeature.fullVersionFeaturesV2))
await sut.reloadReceipt() await sut.reloadReceipt()
XCTAssertTrue(sut.isEligible(for: AppFeature.allCases)) XCTAssertTrue(sut.isEligible(for: AppFeature.fullVersionFeaturesV2))
} }
func test_givenPurchasedFeatures_thenIsOnlyEligibleForFeatures() async { func test_givenPurchasedFeatures_thenIsOnlyEligibleForFeatures() async {
@ -92,10 +92,13 @@ extension IAPManagerTests {
let sut = IAPManager(receiptReader: reader) let sut = IAPManager(receiptReader: reader)
await sut.reloadReceipt() await sut.reloadReceipt()
XCTAssertTrue(sut.isEligible(for: .siri)) XCTAssertTrue(sut.isEligible(for: .dns))
XCTAssertTrue(sut.isEligible(for: .networkSettings)) XCTAssertTrue(sut.isEligible(for: .httpProxy))
XCTAssertFalse(sut.isEligible(for: .onDemand)) XCTAssertFalse(sut.isEligible(for: .onDemand))
XCTAssertFalse(sut.isEligible(for: AppFeature.allCases)) XCTAssertTrue(sut.isEligible(for: .routing))
XCTAssertFalse(sut.isEligible(for: .sharing))
XCTAssertTrue(sut.isEligible(for: .siri))
XCTAssertFalse(sut.isEligible(for: AppFeature.fullVersionFeaturesV2))
} }
func test_givenPurchasedAndCancelledFeature_thenIsNotEligible() async { func test_givenPurchasedAndCancelledFeature_thenIsNotEligible() async {
@ -108,7 +111,7 @@ extension IAPManagerTests {
let sut = IAPManager(receiptReader: reader) let sut = IAPManager(receiptReader: reader)
await sut.reloadReceipt() await sut.reloadReceipt()
XCTAssertFalse(sut.isEligible(for: AppFeature.allCases)) XCTAssertFalse(sut.isEligible(for: AppFeature.fullVersionFeaturesV2))
} }
func test_givenFreeVersion_thenIsNotEligibleForAnyFeature() async { func test_givenFreeVersion_thenIsNotEligibleForAnyFeature() async {
@ -118,7 +121,7 @@ extension IAPManagerTests {
await sut.reloadReceipt() await sut.reloadReceipt()
XCTAssertFalse(sut.userLevel.isFullVersion) XCTAssertFalse(sut.userLevel.isFullVersion)
AppFeature.allCases.forEach { AppFeature.fullVersionFeaturesV2.forEach {
XCTAssertFalse(sut.isEligible(for: $0)) XCTAssertFalse(sut.isEligible(for: $0))
} }
} }
@ -138,7 +141,7 @@ extension IAPManagerTests {
let sut = IAPManager(receiptReader: reader) let sut = IAPManager(receiptReader: reader)
await sut.reloadReceipt() await sut.reloadReceipt()
AppFeature.allCases.forEach { AppFeature.fullVersionFeaturesV2.forEach {
XCTAssertTrue(sut.isEligible(for: $0)) XCTAssertTrue(sut.isEligible(for: $0))
} }
XCTAssertFalse(sut.isEligible(for: .appleTV)) XCTAssertFalse(sut.isEligible(for: .appleTV))
@ -160,11 +163,11 @@ extension IAPManagerTests {
#if os(macOS) #if os(macOS)
await reader.setReceipt(withBuild: defaultBuildNumber, products: [.Full.macOS, .Features.networkSettings]) await reader.setReceipt(withBuild: defaultBuildNumber, products: [.Full.macOS, .Features.networkSettings])
await sut.reloadReceipt() await sut.reloadReceipt()
XCTAssertTrue(sut.isEligible(for: AppFeature.allCases)) XCTAssertTrue(sut.isEligible(for: AppFeature.fullVersionFeaturesV2))
#else #else
await reader.setReceipt(withBuild: defaultBuildNumber, products: [.Full.iOS, .Features.networkSettings]) await reader.setReceipt(withBuild: defaultBuildNumber, products: [.Full.iOS, .Features.networkSettings])
await sut.reloadReceipt() await sut.reloadReceipt()
XCTAssertTrue(sut.isEligible(for: AppFeature.allCases)) XCTAssertTrue(sut.isEligible(for: AppFeature.fullVersionFeaturesV2))
#endif #endif
} }
@ -175,11 +178,11 @@ extension IAPManagerTests {
#if os(macOS) #if os(macOS)
await reader.setReceipt(withBuild: defaultBuildNumber, products: [.Full.iOS, .Features.networkSettings]) await reader.setReceipt(withBuild: defaultBuildNumber, products: [.Full.iOS, .Features.networkSettings])
await sut.reloadReceipt() await sut.reloadReceipt()
XCTAssertFalse(sut.isEligible(for: AppFeature.allCases)) XCTAssertFalse(sut.isEligible(for: AppFeature.fullVersionFeaturesV2))
#else #else
await reader.setReceipt(withBuild: defaultBuildNumber, products: [.Full.macOS, .Features.networkSettings]) await reader.setReceipt(withBuild: defaultBuildNumber, products: [.Full.macOS, .Features.networkSettings])
await sut.reloadReceipt() await sut.reloadReceipt()
XCTAssertFalse(sut.isEligible(for: AppFeature.allCases)) XCTAssertFalse(sut.isEligible(for: AppFeature.fullVersionFeaturesV2))
#endif #endif
} }
@ -264,7 +267,7 @@ extension IAPManagerTests {
let sut = IAPManager(customUserLevel: .beta, receiptReader: reader) let sut = IAPManager(customUserLevel: .beta, receiptReader: reader)
await sut.reloadReceipt() await sut.reloadReceipt()
XCTAssertFalse(sut.isEligible(for: AppFeature.allCases)) XCTAssertFalse(sut.isEligible(for: AppFeature.fullVersionFeaturesV2))
} }
func test_givenBetaApp_thenIsEligibleForUnrestrictedFeature() async { func test_givenBetaApp_thenIsEligibleForUnrestrictedFeature() async {
@ -272,7 +275,7 @@ extension IAPManagerTests {
let sut = IAPManager(customUserLevel: .beta, receiptReader: reader, unrestrictedFeatures: [.onDemand]) let sut = IAPManager(customUserLevel: .beta, receiptReader: reader, unrestrictedFeatures: [.onDemand])
await sut.reloadReceipt() await sut.reloadReceipt()
AppFeature.allCases.forEach { AppFeature.fullVersionFeaturesV2.forEach {
if $0 == .onDemand { if $0 == .onDemand {
XCTAssertTrue(sut.isEligible(for: $0)) XCTAssertTrue(sut.isEligible(for: $0))
} else { } else {
@ -302,7 +305,7 @@ extension IAPManagerTests {
let sut = IAPManager(customUserLevel: .fullVersion, receiptReader: reader) let sut = IAPManager(customUserLevel: .fullVersion, receiptReader: reader)
await sut.reloadReceipt() await sut.reloadReceipt()
AppFeature.allCases.forEach { AppFeature.fullVersionFeaturesV2.forEach {
XCTAssertTrue(sut.isEligible(for: $0)) XCTAssertTrue(sut.isEligible(for: $0))
} }
XCTAssertFalse(sut.isEligible(for: .appleTV)) XCTAssertFalse(sut.isEligible(for: .appleTV))
@ -313,7 +316,7 @@ extension IAPManagerTests {
let sut = IAPManager(customUserLevel: .fullVersionPlusTV, receiptReader: reader) let sut = IAPManager(customUserLevel: .fullVersionPlusTV, receiptReader: reader)
await sut.reloadReceipt() await sut.reloadReceipt()
AppFeature.allCases.forEach { AppFeature.fullVersionFeaturesV2.forEach {
XCTAssertTrue(sut.isEligible(for: $0)) XCTAssertTrue(sut.isEligible(for: $0))
} }
XCTAssertTrue(sut.isEligible(for: .appleTV)) XCTAssertTrue(sut.isEligible(for: .appleTV))

View File

@ -0,0 +1,31 @@
{
"configurations" : [
{
"id" : "880EB747-73AE-45F8-B6D3-95D06B161AB1",
"name" : "Configuration 1",
"options" : {
}
}
],
"defaultOptions" : {
"testTimeoutsEnabled" : true
},
"testTargets" : [
{
"target" : {
"containerPath" : "container:Passepartout\/Library",
"identifier" : "AppLibraryTests",
"name" : "AppLibraryTests"
}
},
{
"target" : {
"containerPath" : "container:Passepartout\/Library",
"identifier" : "AppUITests",
"name" : "AppUITests"
}
}
],
"version" : 1
}

View File

@ -44,7 +44,7 @@ extension IAPManager {
customUserLevel: customUserLevel, customUserLevel: customUserLevel,
receiptReader: KvittoReceiptReader(), receiptReader: KvittoReceiptReader(),
// FIXME: #662, omit unrestrictedFeatures on release! // FIXME: #662, omit unrestrictedFeatures on release!
unrestrictedFeatures: [.interactiveLogin], unrestrictedFeatures: [.interactiveLogin, .sharing],
productsAtBuild: productsAtBuild productsAtBuild: productsAtBuild
) )