Granularize app features (#671)
Split .networkSettings and add .sharing for #668
This commit is contained in:
parent
63b0199a39
commit
0917e47ea3
|
@ -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 */,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue