diff --git a/Configuration/Tests/DBPUnitTests.xcconfig b/Configuration/Tests/DBPUnitTests.xcconfig new file mode 100644 index 0000000000..1852b3c454 --- /dev/null +++ b/Configuration/Tests/DBPUnitTests.xcconfig @@ -0,0 +1,25 @@ +// Copyright © 2022 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "TestsTargetsBase.xcconfig" + +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES + +FEATURE_FLAGS = FEEDBACK NETWORK_PROTECTION + +INFOPLIST_FILE = DuckDuckGoDBPTests/Info.plist +PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.macos.browser.DuckDuckGoDBPTests + +TEST_HOST=$(BUILT_PRODUCTS_DIR)/DuckDuckGoDBP.app/Contents/MacOS/DuckDuckGoDBP diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 70e5b82935..9362900700 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2878,6 +2878,7 @@ 7B1E819E27C8874900FF0E60 /* ContentOverlayPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1E819B27C8874900FF0E60 /* ContentOverlayPopover.swift */; }; 7B1E819F27C8874900FF0E60 /* ContentOverlay.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7B1E819C27C8874900FF0E60 /* ContentOverlay.storyboard */; }; 7B1E81A027C8874900FF0E60 /* ContentOverlayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1E819D27C8874900FF0E60 /* ContentOverlayViewController.swift */; }; + 7B20D5C62ADFEC6E0053C42A /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = 7B20D5C52ADFEC6E0053C42A /* PixelKitTestingUtilities */; }; 7B2DDCF82A93A8BB0039D884 /* NetworkProtectionAppEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2DDCF72A93A8BB0039D884 /* NetworkProtectionAppEvents.swift */; }; 7B2DDCFA2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; 7B2DDCFB2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; @@ -2894,6 +2895,7 @@ 7B736E6A2A4A22FC00F9922A /* enableOnDemand.app in Embed NetP Controller Apps */ = {isa = PBXBuildFile; fileRef = 7B736E5F2A4A22B700F9922A /* enableOnDemand.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7B838C382A1DD8DD00E05A13 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B2D06522A11D19B00DE1F49 /* Assets.xcassets */; }; 7B934C412A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; + 7B96D0DC2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B96D0DB2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift */; }; 7BA4727D26F01BC400EAA165 /* CoreDataTestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292C42667104B00AD2C21 /* CoreDataTestUtilities.swift */; }; 7BAF9E4B2A8A3CC9002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; 7BAF9E4C2A8A3CCA002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; @@ -2904,6 +2906,8 @@ 7BD3AF5D2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; 7BE146072A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */; }; 7BE146082A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */; }; + 7BF1A9D82AE054D300FCA683 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7BF1A9D72AE054D300FCA683 /* Info.plist */; }; + 7BF1A9DC2AE0551C00FCA683 /* DBPUnitTests.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; }; 7BF7705F2AD6C999001C9182 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7BF7705E2AD6C999001C9182 /* PixelKit */; }; 7BF770612AD6CA06001C9182 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7BF770602AD6CA06001C9182 /* PixelKit */; }; 7BF770632AD6CA0E001C9182 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7BF770622AD6CA0E001C9182 /* PixelKit */; }; @@ -3673,6 +3677,13 @@ remoteGlobalIDString = AA585D7D248FD31100E9A3E2; remoteInfo = "DuckDuckGo Privacy Browser"; }; + 7B96D0D32ADFDA7F007E02C8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = AA585D76248FD31100E9A3E2 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31929F7B2A4C4CFF0084EA89; + remoteInfo = "DuckDuckGo DBP"; + }; AA585D91248FD31400E9A3E2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = AA585D76248FD31100E9A3E2 /* Project object */; @@ -4302,6 +4313,8 @@ 7B934C3D2A866CFF00FC8F9C /* NetworkProtectionOnboardingMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionOnboardingMenu.swift; sourceTree = ""; }; 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+NetworkProtectionShared.swift"; sourceTree = ""; }; 7B9459632A4A5BAF0012535A /* NetworkProtectionEnableOnDemand.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetworkProtectionEnableOnDemand.xcconfig; sourceTree = ""; }; + 7B96D0CF2ADFDA7E007E02C8 /* DuckDuckGoDBPTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DuckDuckGoDBPTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 7B96D0DB2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionPixelTests.swift; sourceTree = ""; }; 7BB108572A43375D000AB95F /* PFMoveApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFMoveApplication.h; sourceTree = ""; }; 7BB108582A43375D000AB95F /* PFMoveApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFMoveApplication.m; sourceTree = ""; }; 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugUtilities.swift; sourceTree = ""; }; @@ -4309,6 +4322,8 @@ 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeychainType+ClientDefault.swift"; sourceTree = ""; }; 7BD8679A2A9E9E000063B9F7 /* NetworkProtectionFeatureVisibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibility.swift; sourceTree = ""; }; 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugMenu.swift; sourceTree = ""; }; + 7BF1A9D72AE054D300FCA683 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DBPUnitTests.xcconfig; sourceTree = ""; }; 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionWaitlistMenu.swift; sourceTree = ""; }; 7BFE95532A9DF2930081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+NetworkProtectionWaitlist.swift"; sourceTree = ""; }; 85012B0129133F9F003D0DCC /* NavigationBarPopovers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarPopovers.swift; sourceTree = ""; }; @@ -5064,6 +5079,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7B96D0CC2ADFDA7E007E02C8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7B20D5C62ADFEC6E0053C42A /* PixelKitTestingUtilities in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; AA585D7B248FD31100E9A3E2 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -5443,6 +5466,7 @@ children = ( 373A26962964CF0B0043FC57 /* TestsTargetsBase.xcconfig */, 378B588D295CF447002C0CC0 /* UnitTests.xcconfig */, + 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */, 378B58C8295CF9A7002C0CC0 /* IntegrationTests.xcconfig */, 37E75733296F4F0500E1C162 /* UnitTestsAppStore.xcconfig */, 37E75734296F4F0500E1C162 /* IntegrationTestsAppStore.xcconfig */, @@ -6464,6 +6488,15 @@ path = UITests; sourceTree = ""; }; + 7B96D0D02ADFDA7F007E02C8 /* DuckDuckGoDBPTests */ = { + isa = PBXGroup; + children = ( + 7BF1A9D72AE054D300FCA683 /* Info.plist */, + 7B96D0DB2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift */, + ); + path = DuckDuckGoDBPTests; + sourceTree = ""; + }; 7BB108552A43375D000AB95F /* LocalThirdParty */ = { isa = PBXGroup; children = ( @@ -6939,6 +6972,7 @@ 4B1AD89E25FC27E200261379 /* IntegrationTests */, 7B4CE8DB26F02108009134B1 /* UITests */, B6EC37E929B5DA2A001ACE79 /* tests-server */, + 7B96D0D02ADFDA7F007E02C8 /* DuckDuckGoDBPTests */, 4B5F14F72A148B230060320F /* NetworkProtectionAppExtension */, 4B25375C2A11BE7500610219 /* NetworkProtectionSystemExtension */, 4B5F14C32A145D6A0060320F /* NetworkProtectionVPNController */, @@ -6969,6 +7003,7 @@ 7B736E5F2A4A22B700F9922A /* enableOnDemand.app */, 3192A26E2A4C4CFF0084EA89 /* DuckDuckGoDBP.app */, 4B957C412AC7AE700062CA31 /* DuckDuckGo Privacy Pro.app */, + 7B96D0CF2ADFDA7E007E02C8 /* DuckDuckGoDBPTests.xctest */, ); name = Products; sourceTree = ""; @@ -8860,6 +8895,28 @@ productReference = 7B736E5F2A4A22B700F9922A /* enableOnDemand.app */; productType = "com.apple.product-type.application"; }; + 7B96D0CE2ADFDA7E007E02C8 /* DuckDuckGoDBPTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7B96D0D52ADFDA7F007E02C8 /* Build configuration list for PBXNativeTarget "DuckDuckGoDBPTests" */; + buildPhases = ( + 7B96D0CB2ADFDA7E007E02C8 /* Sources */, + 7B96D0CC2ADFDA7E007E02C8 /* Frameworks */, + 7B96D0CD2ADFDA7E007E02C8 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 7B20D5C82ADFEC730053C42A /* PBXTargetDependency */, + 7B96D0D42ADFDA7F007E02C8 /* PBXTargetDependency */, + ); + name = DuckDuckGoDBPTests; + packageProductDependencies = ( + 7B20D5C52ADFEC6E0053C42A /* PixelKitTestingUtilities */, + ); + productName = DuckDuckGoDBPTests; + productReference = 7B96D0CF2ADFDA7E007E02C8 /* DuckDuckGoDBPTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; AA585D7D248FD31100E9A3E2 /* DuckDuckGo Privacy Browser */ = { isa = PBXNativeTarget; buildConfigurationList = AA585DA4248FD31500E9A3E2 /* Build configuration list for PBXNativeTarget "DuckDuckGo Privacy Browser" */; @@ -8959,7 +9016,7 @@ AA585D76248FD31100E9A3E2 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1430; + LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1400; ORGANIZATIONNAME = DuckDuckGo; TargetAttributes = { @@ -9000,6 +9057,10 @@ CreatedOnToolsVersion = 12.5.1; TestTargetID = AA585D7D248FD31100E9A3E2; }; + 7B96D0CE2ADFDA7E007E02C8 = { + CreatedOnToolsVersion = 15.0; + TestTargetID = 31929F7B2A4C4CFF0084EA89; + }; AA585D7D248FD31100E9A3E2 = { CreatedOnToolsVersion = 11.5; }; @@ -9050,6 +9111,7 @@ 4B5F14E02A1476BC0060320F /* stopVPN */, 7B736E562A4A22B700F9922A /* enableOnDemand */, 31929F7B2A4C4CFF0084EA89 /* DuckDuckGo DBP */, + 7B96D0CE2ADFDA7E007E02C8 /* DuckDuckGoDBPTests */, 4B9579252AC7AE700062CA31 /* DuckDuckGo Privacy Pro */, ); }; @@ -9344,6 +9406,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7B96D0CD2ADFDA7E007E02C8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7BF1A9D82AE054D300FCA683 /* Info.plist in Resources */, + 7BF1A9DC2AE0551C00FCA683 /* DBPUnitTests.xcconfig in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; AA585D7C248FD31100E9A3E2 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -12315,6 +12386,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7B96D0CB2ADFDA7E007E02C8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7B96D0DC2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; AA585D7A248FD31100E9A3E2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -12501,6 +12580,7 @@ AAC5E4D025D6A709007F5990 /* Bookmark.swift in Sources */, 4BBDEE9328FC14760092FAA6 /* ConnectBitwardenViewModel.swift in Sources */, 4B5A4F4C27F3A5AA008FBD88 /* NSNotificationName+DataImport.swift in Sources */, + 7B96D0C62ADFD460007E02C8 /* DataBrokerProtectionPixelTests.swift in Sources */, B64C853826944B880048FEBE /* StoredPermission.swift in Sources */, AAE246F8270A406200BEEAEE /* FirePopoverCollectionViewHeader.swift in Sources */, AAB7320926DD0CD9002FACF9 /* FireViewController.swift in Sources */, @@ -13273,11 +13353,20 @@ target = 4B2537592A11BE7300610219 /* NetworkProtectionSystemExtension */; targetProxy = 4B9579282AC7AE700062CA31 /* PBXContainerItemProxy */; }; + 7B20D5C82ADFEC730053C42A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = 7B20D5C72ADFEC730053C42A /* PixelKitTestingUtilities */; + }; 7B4CE8E026F02108009134B1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = AA585D7D248FD31100E9A3E2 /* DuckDuckGo Privacy Browser */; targetProxy = 7B4CE8DF26F02108009134B1 /* PBXContainerItemProxy */; }; + 7B96D0D42ADFDA7F007E02C8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31929F7B2A4C4CFF0084EA89 /* DuckDuckGo DBP */; + targetProxy = 7B96D0D32ADFDA7F007E02C8 /* PBXContainerItemProxy */; + }; AA585D92248FD31400E9A3E2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = AA585D7D248FD31100E9A3E2 /* DuckDuckGo Privacy Browser */; @@ -13766,6 +13855,34 @@ }; name = Review; }; + 7B96D0D62ADFDA7F007E02C8 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 7B96D0D72ADFDA7F007E02C8 /* CI */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; + buildSettings = { + }; + name = CI; + }; + 7B96D0D82ADFDA7F007E02C8 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 7B96D0D92ADFDA7F007E02C8 /* Review */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; + buildSettings = { + }; + name = Review; + }; AA585DA2248FD31500E9A3E2 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 37717E66296B5A20002FAEDF /* Global.xcconfig */; @@ -14039,6 +14156,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 7B96D0D52ADFDA7F007E02C8 /* Build configuration list for PBXNativeTarget "DuckDuckGoDBPTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7B96D0D62ADFDA7F007E02C8 /* Debug */, + 7B96D0D72ADFDA7F007E02C8 /* CI */, + 7B96D0D82ADFDA7F007E02C8 /* Release */, + 7B96D0D92ADFDA7F007E02C8 /* Review */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; AA585D79248FD31100E9A3E2 /* Build configuration list for PBXProject "DuckDuckGo" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -14620,6 +14748,14 @@ isa = XCSwiftPackageProductDependency; productName = Purchase; }; + 7B20D5C52ADFEC6E0053C42A /* PixelKitTestingUtilities */ = { + isa = XCSwiftPackageProductDependency; + productName = PixelKitTestingUtilities; + }; + 7B20D5C72ADFEC730053C42A /* PixelKitTestingUtilities */ = { + isa = XCSwiftPackageProductDependency; + productName = PixelKitTestingUtilities; + }; 7BF7705E2AD6C999001C9182 /* PixelKit */ = { isa = XCSwiftPackageProductDependency; productName = PixelKit; diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo DBP.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo DBP.xcscheme index b869953f7d..421a9228db 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo DBP.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo DBP.xcscheme @@ -28,6 +28,19 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" shouldAutocreateTestPlan = "YES"> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index 9e8c7d23c5..cfaf36d157 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -128,40 +128,26 @@ extension DBPHomeViewController: DataBrokerProtectionInviteDialogsViewModelDeleg public class DataBrokerProtectionPixelsHandler: EventMapping { - // swiftlint:disable:next cyclomatic_complexity public init() { super.init { event, _, _, _ in switch event { case .error(let error, _): - Pixel.fire(.debug(event: .dataBrokerProtectionError, error: error), withAdditionalParameters: event.params) - case .parentChildMatches: - Pixel.fire(.parentChildMatches, withAdditionalParameters: event.params) - case .optOutStart: - Pixel.fire(.optOutStart, withAdditionalParameters: event.params) - case .optOutEmailGenerate: - Pixel.fire(.optOutEmailGenerate, withAdditionalParameters: event.params) - case .optOutCaptchaParse: - Pixel.fire(.optOutCaptchaParse, withAdditionalParameters: event.params) - case .optOutCaptchaSend: - Pixel.fire(.optOutCaptchaSend, withAdditionalParameters: event.params) - case .optOutCaptchaSolve: - Pixel.fire(.optOutCaptchaSolve, withAdditionalParameters: event.params) - case .optOutSubmit: - Pixel.fire(.optOutSubmit, withAdditionalParameters: event.params) - case .optOutEmailReceive: - Pixel.fire(.optOutEmailReceive, withAdditionalParameters: event.params) - case .optOutEmailConfirm: - Pixel.fire(.optOutEmailConfirm, withAdditionalParameters: event.params) - case .optOutValidate: - Pixel.fire(.optOutValidate, withAdditionalParameters: event.params) - case .optOutFinish: - Pixel.fire(.optOutFinish, withAdditionalParameters: event.params) - case .optOutSubmitSuccess: - Pixel.fire(.optOutSubmitSuccess, withAdditionalParameters: event.params) - case .optOutSuccess: - Pixel.fire(.optOutSuccess, withAdditionalParameters: event.params) - case .optOutFailure: - Pixel.fire(.optOutFailure, withAdditionalParameters: event.params) + Pixel.fire(.debug(event: .pixelKitEvent(event), error: error)) + case .parentChildMatches, + .optOutStart, + .optOutEmailGenerate, + .optOutCaptchaParse, + .optOutCaptchaSend, + .optOutCaptchaSolve, + .optOutSubmit, + .optOutEmailReceive, + .optOutEmailConfirm, + .optOutValidate, + .optOutFinish, + .optOutSubmitSuccess, + .optOutSuccess, + .optOutFailure: + Pixel.fire(.pixelKitEvent(event)) } } } diff --git a/DuckDuckGo/Statistics/Pixel.swift b/DuckDuckGo/Statistics/Pixel.swift index a9d16b826d..df7e606be3 100644 --- a/DuckDuckGo/Statistics/Pixel.swift +++ b/DuckDuckGo/Statistics/Pixel.swift @@ -80,7 +80,13 @@ final class Pixel { } } - init(store: @escaping @autoclosure () -> PixelDataStore, requestSender: @escaping RequestSender) { + private let appVersion: String + + init(appVersion: String = AppVersion.shared.versionNumber, + store: @escaping @autoclosure () -> PixelDataStore, + requestSender: @escaping RequestSender) { + + self.appVersion = appVersion self.store = store self.sendRequest = requestSender } @@ -124,7 +130,7 @@ final class Pixel { } if includeAppVersionParameter { - newParams[PixelKit.Parameters.appVersion] = AppVersion.shared.versionNumber + newParams[PixelKit.Parameters.appVersion] = appVersion } #if DEBUG newParams[PixelKit.Parameters.test] = PixelKit.Values.test diff --git a/DuckDuckGo/Statistics/PixelEvent.swift b/DuckDuckGo/Statistics/PixelEvent.swift index fe358736fe..378f69b640 100644 --- a/DuckDuckGo/Statistics/PixelEvent.swift +++ b/DuckDuckGo/Statistics/PixelEvent.swift @@ -20,10 +20,16 @@ import Foundation import BrowserServicesKit import Bookmarks import Configuration +import PixelKit extension Pixel { indirect enum Event { + /// This is a convenience pixel that allows us to fire `PixelKitEvents` using our + /// regular `Pixel.fire()` calls. This is a convenience intermediate step to help ensure + /// nothing breaks in the migration towards `PixelKit`. + case pixelKitEvent(_ event: PixelKitEvent) + case crash case brokenSiteReport @@ -168,29 +174,12 @@ extension Pixel { case setnewHomePage case dailyPixel(Event, isFirst: Bool) -#if DBP - case parentChildMatches - // SLO and SLI Pixels: https://app.asana.com/0/1203581873609357/1205337273100857/f - - // Stage Pixels - case optOutStart - case optOutEmailGenerate - case optOutCaptchaParse - case optOutCaptchaSend - case optOutCaptchaSolve - case optOutSubmit - case optOutEmailReceive - case optOutEmailConfirm - case optOutValidate - case optOutFinish - - // Process Pixels - case optOutSubmitSuccess - case optOutSuccess - case optOutFailure -#endif enum Debug { + /// This is a convenience pixel that allows us to fire `PixelKitEvents` using our + /// regular `Pixel.fire()` calls. This is a convenience intermediate step to help ensure + /// nothing breaks in the migration towards `PixelKit`. + case pixelKitEvent(_ event: PixelKitEvent) case assertionFailure(message: String, file: StaticString, line: UInt) @@ -335,10 +324,6 @@ extension Pixel { case networkProtectionRemoteMessageFetchingFailed case networkProtectionRemoteMessageStorageFailed - -#if DBP - case dataBrokerProtectionError -#endif } } @@ -348,6 +333,9 @@ extension Pixel.Event { var name: String { switch self { + case .pixelKitEvent(let event): + return event.name + case .crash: return "m_mac_crash" @@ -498,25 +486,6 @@ extension Pixel.Event { case .dailyPixel(let pixel, isFirst: let isFirst): return pixel.name + (isFirst ? "_d" : "_c") -#if DBP - case .parentChildMatches: return "dbp_macos_parent-child-broker-matches" - // Stage Pixels - case .optOutStart: return "dbp_macos_optout_stage_start" - case .optOutEmailGenerate: return "dbp_macos_optout_stage_email-generate" - case .optOutCaptchaParse: return "dbp_macos_optout_stage_captcha-parse" - case .optOutCaptchaSend: return "dbp_macos_optout_stage_captcha-send" - case .optOutCaptchaSolve: return "dbp_macos_optout_stage_captcha-solve" - case .optOutSubmit: return "dbp_macos_optout_stage_submit" - case .optOutEmailReceive: return "dbp_macos_optout_stage_email-receive" - case .optOutEmailConfirm: return "dbp_macos_optout_stage_email-confirm" - case .optOutValidate: return "dbp_macos_optout_stage_validate" - case .optOutFinish: return "dbp_macos_optout_stage_finish" - - // Process Pixels - case .optOutSubmitSuccess: return "dbp_macos_optout_process_submit-success" - case .optOutSuccess: return "dbp_macos_optout_process_success" - case .optOutFailure: return "dbp_macos_optout_process_failure" -#endif } } } @@ -533,6 +502,8 @@ extension Pixel.Event.Debug { var name: String { switch self { + case .pixelKitEvent(let event): + return event.name case .assertionFailure: return "assertion_failure" @@ -773,10 +744,6 @@ extension Pixel.Event.Debug { case .networkProtectionRemoteMessageFetchingFailed: return "netp_remote_message_fetching_failed" case .networkProtectionRemoteMessageStorageFailed: return "netp_remote_message_storage_failed" - -#if DBP - case .dataBrokerProtectionError: return "data_broker_error" -#endif } } } diff --git a/DuckDuckGo/Statistics/PixelParameters.swift b/DuckDuckGo/Statistics/PixelParameters.swift index 1fcb87e97e..2fab15b4f1 100644 --- a/DuckDuckGo/Statistics/PixelParameters.swift +++ b/DuckDuckGo/Statistics/PixelParameters.swift @@ -22,6 +22,9 @@ extension Pixel.Event { var parameters: [String: String]? { switch self { + case .pixelKitEvent(let event): + return event.parameters + case .debug(event: let debugEvent, error: let error): var params = error?.pixelParameters ?? [:] @@ -105,23 +108,6 @@ extension Pixel.Event { .disableHomeButton, .setnewHomePage: return nil -#if DBP - case .optOutStart, - .optOutEmailGenerate, - .optOutCaptchaParse, - .optOutCaptchaSend, - .optOutCaptchaSolve, - .optOutSubmit, - .optOutEmailReceive, - .optOutEmailConfirm, - .optOutValidate, - .optOutFinish, - .optOutSubmitSuccess, - .optOutSuccess, - .optOutFailure, - .parentChildMatches: - return nil -#endif } } diff --git a/DuckDuckGoAgent/Info-AppStore.plist b/DuckDuckGoAgent/Info-AppStore.plist index b6f094baff..1861d4c159 100644 --- a/DuckDuckGoAgent/Info-AppStore.plist +++ b/DuckDuckGoAgent/Info-AppStore.plist @@ -6,5 +6,7 @@ $(NETP_APP_GROUP) LSApplicationCategoryType public.app-category.productivity + CFBundleShortVersionString + $(MARKETING_VERSION) diff --git a/DuckDuckGoAgent/Info.plist b/DuckDuckGoAgent/Info.plist index 821d91c50f..5d64b03a02 100644 --- a/DuckDuckGoAgent/Info.plist +++ b/DuckDuckGoAgent/Info.plist @@ -8,5 +8,7 @@ $(NETP_APP_GROUP) LSApplicationCategoryType public.app-category.productivity + CFBundleShortVersionString + $(MARKETING_VERSION) diff --git a/DuckDuckGoDBPTests/DataBrokerProtectionPixelTests.swift b/DuckDuckGoDBPTests/DataBrokerProtectionPixelTests.swift new file mode 100644 index 0000000000..3450cc21a5 --- /dev/null +++ b/DuckDuckGoDBPTests/DataBrokerProtectionPixelTests.swift @@ -0,0 +1,145 @@ +// +// DataBrokerProtectionPixelTests.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import DataBrokerProtection +import Networking +import Foundation +import PixelKit +import PixelKitTestingUtilities +import XCTest +@testable import DuckDuckGo_DBP + +/// Tests to ensure that DBP pixels sent from the main app work well +/// +final class DataBrokerProtectionPixelTests: XCTestCase { + + struct PixelDataStoreMock: PixelDataStore { + func set(_ value: Int, forKey: String, completionHandler: ((Error?) -> Void)?) { + completionHandler?(nil) + } + + func set(_ value: String, forKey: String, completionHandler: ((Error?) -> Void)?) { + completionHandler?(nil) + } + + func set(_ value: Double, forKey: String, completionHandler: ((Error?) -> Void)?) { + completionHandler?(nil) + } + + func value(forKey key: String) -> Double? { + nil + } + + func value(forKey key: String) -> Int? { + nil + } + + func value(forKey key: String) -> String? { + nil + } + + func removeValue(forKey key: String, completionHandler: ((Error?) -> Void)?) { + completionHandler?(nil) + } + } + + func mapToPixelEvent(_ dbpPixel: DataBrokerProtectionPixels) -> Pixel.Event { + switch dbpPixel { + case .error(let error, _): + return .debug(event: .pixelKitEvent(dbpPixel), error: error) + default: + return .pixelKitEvent(dbpPixel) + } + } + + /// This method implements validation logic that can be used to test several events. + /// + func validatePixel(for dbpEvent: DataBrokerProtectionPixels) { + let inAppVersion = "1.0.1" + let inUserAgent = "ddg_mac/\(inAppVersion) (com.duckduckgo.macos.browser.dbp.debug; macOS Version 14.0 (Build 23A344))" + + // We want to make sure the callback is executed exactly once to validate + // all of the fire parameters + let callbackExecuted = expectation(description: "We expect the callback to be executed once") + callbackExecuted.expectedFulfillmentCount = 1 + callbackExecuted.assertForOverFulfill = true + + // This is annoyingly necessary to test the user agent right now + APIRequest.Headers.setUserAgent(inUserAgent) + + let storeMock = PixelDataStoreMock() + + let inEvent = mapToPixelEvent(dbpEvent) + + let pixel = Pixel(appVersion: inAppVersion, store: storeMock) { (event, parameters, _, headers, onComplete) in + + // Validate that the event is the one we expect + XCTAssertEqual(event, inEvent) + + // Validate that the basic params are present + let pixelRequestValidator = PixelRequestValidator() + pixelRequestValidator.validateBasicPixelParams(expectedAppVersion: inAppVersion, expectedUserAgent: inUserAgent, requestParameters: parameters, requestHeaders: headers.httpHeaders) + + // Validate that the debug params are present + if case .debug(let wrappedEvent, let error) = inEvent { + XCTAssertEqual("m_mac_debug_\(wrappedEvent.name)", inEvent.name) + + pixelRequestValidator.validateDebugPixelParams(expectedError: error, requestParameters: parameters) + } + + // Validate that the dbp-specific params are present in the fire event parameters + XCTAssertTrue( + dbpEvent.params?.allSatisfy({ key, value in + parameters[key] == value + }) ?? false) + + callbackExecuted.fulfill() + onComplete(nil) + } + + pixel.fire(inEvent, withAdditionalParameters: dbpEvent.params) + + waitForExpectations(timeout: 0.1) + } + + func testBasicPixelValidation() { + let inDataBroker = "inDataBroker" + + let eventsToTest: [DataBrokerProtectionPixels] = [ + .error(error: DataBrokerProtectionError.cancelled, dataBroker: inDataBroker), + .parentChildMatches(parent: "a", child: "b", value: 5), + .optOutStart(dataBroker: "a", attemptId: UUID()), + .optOutEmailGenerate(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutCaptchaParse(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutCaptchaSend(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutCaptchaSolve(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutSubmit(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutEmailReceive(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutEmailConfirm(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutValidate(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutFinish(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutSubmitSuccess(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutSuccess(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutFailure(dataBroker: "a", attemptId: UUID(), duration: 5) + ] + + for event in eventsToTest { + validatePixel(for: event) + } + } +} diff --git a/DuckDuckGoDBPTests/Info.plist b/DuckDuckGoDBPTests/Info.plist new file mode 100644 index 0000000000..64d65ca495 --- /dev/null +++ b/DuckDuckGoDBPTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index b8a5bb9b84..1f6534064a 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -22,7 +22,7 @@ import PackageDescription let package = Package( name: "DataBrokerProtection", - platforms: [ .macOS(.v11) ], + platforms: [ .macOS("11.4") ], products: [ .library( name: "DataBrokerProtection", @@ -30,6 +30,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "81.4.0"), + .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions") ], targets: [ @@ -37,6 +38,7 @@ let package = Package( name: "DataBrokerProtection", dependencies: [ .product(name: "BrowserServicesKit", package: "BrowserServicesKit"), + .product(name: "PixelKit", package: "PixelKit"), .product(name: "SwiftUIExtensions", package: "SwiftUIExtensions") ], resources: [.process("Resources")] diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift index e91e82aa8a..95e5a1ee9b 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift @@ -19,6 +19,7 @@ import Foundation import Common import BrowserServicesKit +import PixelKit final class DataBrokerProtectionStageDurationCalculator { @@ -128,9 +129,38 @@ public enum DataBrokerProtectionPixels: Equatable { case optOutFailure(dataBroker: String, attemptId: UUID, duration: Double) } -public extension DataBrokerProtectionPixels { +extension DataBrokerProtectionPixels: PixelKitEvent { + public var name: String { + switch self { + case .parentChildMatches: return "dbp_macos_parent-child-broker-matches" + // SLO and SLI Pixels: https://app.asana.com/0/1203581873609357/1205337273100857/f + // Stage Pixels + case .optOutStart: return "dbp_macos_optout_stage_start" + case .optOutEmailGenerate: return "dbp_macos_optout_stage_email-generate" + case .optOutCaptchaParse: return "dbp_macos_optout_stage_captcha-parse" + case .optOutCaptchaSend: return "dbp_macos_optout_stage_captcha-send" + case .optOutCaptchaSolve: return "dbp_macos_optout_stage_captcha-solve" + case .optOutSubmit: return "dbp_macos_optout_stage_submit" + case .optOutEmailReceive: return "dbp_macos_optout_stage_email-receive" + case .optOutEmailConfirm: return "dbp_macos_optout_stage_email-confirm" + case .optOutValidate: return "dbp_macos_optout_stage_validate" + case .optOutFinish: return "dbp_macos_optout_stage_finish" + + // Process Pixels + case .optOutSubmitSuccess: return "dbp_macos_optout_process_submit-success" + case .optOutSuccess: return "dbp_macos_optout_process_success" + case .optOutFailure: return "dbp_macos_optout_process_failure" + + // Debug Pixels + case .error: return "data_broker_error" + } + } + + public var params: [String: String]? { + parameters + } - var params: [String: String] { + public var parameters: [String: String]? { switch self { case .error(let error, let dataBroker): if case let .actionFailed(actionID, message) = error { diff --git a/LocalPackages/PixelKit/Package.swift b/LocalPackages/PixelKit/Package.swift index 670af9731b..a12cae821d 100644 --- a/LocalPackages/PixelKit/Package.swift +++ b/LocalPackages/PixelKit/Package.swift @@ -13,7 +13,10 @@ let package = Package( // Products define the executables and libraries a package produces, and make them visible to other packages. .library( name: "PixelKit", - targets: ["PixelKit"]) + targets: ["PixelKit"]), + .library( + name: "PixelKitTestingUtilities", + targets: ["PixelKitTestingUtilities"]) ], dependencies: [ ], @@ -23,6 +26,9 @@ let package = Package( dependencies: []), .testTarget( name: "PixelKitTests", + dependencies: ["PixelKit", "PixelKitTestingUtilities"]), + .target( + name: "PixelKitTestingUtilities", dependencies: ["PixelKit"]) ] ) diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index 44699d7312..8eeff1bec2 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -36,12 +36,12 @@ public final class PixelKit { case dailyAndContinuous } - enum Header { - static let acceptEncoding = "Accept-Encoding" - static let acceptLanguage = "Accept-Language" - static let userAgent = "User-Agent" - static let ifNoneMatch = "If-None-Match" - static let moreInfo = "X-DuckDuckGo-MoreInfo" + public enum Header { + public static let acceptEncoding = "Accept-Encoding" + public static let acceptLanguage = "Accept-Language" + public static let userAgent = "User-Agent" + public static let ifNoneMatch = "If-None-Match" + public static let moreInfo = "X-DuckDuckGo-MoreInfo" } /// A closure typealias to request sending pixels through the network. @@ -56,7 +56,7 @@ public final class PixelKit { public typealias Event = PixelKitEvent - static let duckDuckGoMorePrivacyInfo = URL(string: "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/atb/")! + public static let duckDuckGoMorePrivacyInfo = URL(string: "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/atb/")! private let defaults: UserDefaults diff --git a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/ValidatePixel.swift b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/ValidatePixel.swift new file mode 100644 index 0000000000..a56f128454 --- /dev/null +++ b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/ValidatePixel.swift @@ -0,0 +1,50 @@ +// +// ValidatePixel.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import PixelKit +import XCTest + +public final class PixelRequestValidator { + public init() {} + + public func validateBasicPixelParams( + expectedAppVersion: String, + expectedUserAgent: String, + requestParameters parameters: [String: String], + requestHeaders headers: [String: String]) { + + XCTAssertEqual(parameters[PixelKit.Parameters.test], "1") + XCTAssertEqual(parameters[PixelKit.Parameters.appVersion], expectedAppVersion) + + XCTAssertEqual(headers[PixelKit.Header.userAgent], expectedUserAgent) + XCTAssertEqual(headers[PixelKit.Header.acceptEncoding], "gzip;q=1.0, compress;q=0.5") + XCTAssertNotNil(headers[PixelKit.Header.acceptLanguage]) + XCTAssertNotNil(headers[PixelKit.Header.moreInfo], PixelKit.duckDuckGoMorePrivacyInfo.absoluteString) + } + + public func validateDebugPixelParams( + expectedError: Error?, + requestParameters parameters: [String: String]) { + + if let error = expectedError as? NSError { + XCTAssertEqual(parameters[PixelKit.Parameters.errorCode], "\(error.code)") + XCTAssertEqual(parameters[PixelKit.Parameters.errorDesc], error.domain) + } + } +}