diff --git a/.gitignore b/.gitignore index 07ebc5f934..d460333497 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,5 @@ fastlane/test_output Configuration/ExternalDeveloper.xcconfig scripts/assets + +DuckDuckGoTests/NetworkProtectionVPNLocationViewModelTests.swift*.plist diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 37e55d037b..0b4c62ca31 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -577,7 +577,6 @@ 987130C7294AAB9F00AB05E0 /* MenuBookmarksViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987130C1294AAB9E00AB05E0 /* MenuBookmarksViewModelTests.swift */; }; 987130C8294AAB9F00AB05E0 /* BookmarksTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987130C2294AAB9E00AB05E0 /* BookmarksTestHelpers.swift */; }; 987130C9294AAB9F00AB05E0 /* BookmarkUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987130C3294AAB9E00AB05E0 /* BookmarkUtilsTests.swift */; }; - 98728E822417E3300033960E /* BrokenSiteInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98728E812417E3300033960E /* BrokenSiteInfo.swift */; }; 9872D205247DCAC100CEF398 /* TabPreviewsSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9872D204247DCAC100CEF398 /* TabPreviewsSource.swift */; }; 9874F9EE2187AFCE00CAF33D /* Themable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9874F9ED2187AFCE00CAF33D /* Themable.swift */; }; 9875E00722316B8400B1373F /* Instruments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9875E00622316B8400B1373F /* Instruments.swift */; }; @@ -879,6 +878,7 @@ F1134ED21F40EF3A00B73467 /* JsonTestDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1134ECF1F40EBE200B73467 /* JsonTestDataLoader.swift */; }; F1134ED61F40F29F00B73467 /* StatisticsUserDefaultsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1134ED41F40F15800B73467 /* StatisticsUserDefaultsTests.swift */; }; F114C55B1E66EB020018F95F /* NibLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = F114C55A1E66EB020018F95F /* NibLoading.swift */; }; + F115ED9C2B4EFC8E001A0453 /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = F115ED9B2B4EFC8E001A0453 /* TestUtils */; }; F130D73A1E5776C500C45811 /* OmniBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130D7391E5776C500C45811 /* OmniBarDelegate.swift */; }; F1386BA41E6846C40062FC3C /* TabDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1386BA31E6846C40062FC3C /* TabDelegate.swift */; }; F13B4BC01F180D8A00814661 /* TabsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13B4BBF1F180D8A00814661 /* TabsModel.swift */; }; @@ -2130,7 +2130,6 @@ 987130C1294AAB9E00AB05E0 /* MenuBookmarksViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuBookmarksViewModelTests.swift; sourceTree = ""; }; 987130C2294AAB9E00AB05E0 /* BookmarksTestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksTestHelpers.swift; sourceTree = ""; }; 987130C3294AAB9E00AB05E0 /* BookmarkUtilsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkUtilsTests.swift; sourceTree = ""; }; - 98728E812417E3300033960E /* BrokenSiteInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSiteInfo.swift; sourceTree = ""; }; 9872D204247DCAC100CEF398 /* TabPreviewsSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPreviewsSource.swift; sourceTree = ""; }; 9874F9ED2187AFCE00CAF33D /* Themable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Themable.swift; sourceTree = ""; }; 9875E00622316B8400B1373F /* Instruments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instruments.swift; sourceTree = ""; }; @@ -2711,6 +2710,7 @@ files = ( F486D3362506A037002D07D7 /* OHHTTPStubs in Frameworks */, F486D3382506A225002D07D7 /* OHHTTPStubsSwift in Frameworks */, + F115ED9C2B4EFC8E001A0453 /* TestUtils in Frameworks */, EEFAB4672A73C230008A38E4 /* NetworkProtectionTestUtils in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -5513,7 +5513,6 @@ F1DF09502B039E6E008CC908 /* PrivacyDashboard */ = { isa = PBXGroup; children = ( - 98728E812417E3300033960E /* BrokenSiteInfo.swift */, 1E87615828A1517200C7C5CE /* PrivacyDashboardViewController.swift */, 984147B924F0268D00362052 /* PrivacyDashboard.storyboard */, ); @@ -5754,6 +5753,7 @@ F486D3352506A037002D07D7 /* OHHTTPStubs */, F486D3372506A225002D07D7 /* OHHTTPStubsSwift */, EEFAB4662A73C230008A38E4 /* NetworkProtectionTestUtils */, + F115ED9B2B4EFC8E001A0453 /* TestUtils */, ); productName = DuckDuckGoTests; productReference = 84E341A61E2F7EFB00BDBA6F /* UnitTests.xctest */; @@ -6908,7 +6908,6 @@ 02EC02C429AFA33000557F1A /* AppTPBreakageFormView.swift in Sources */, F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */, BD862E092B30F63E0073E2EE /* VPNMetadataCollector.swift in Sources */, - 98728E822417E3300033960E /* BrokenSiteInfo.swift in Sources */, D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */, 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */, 1E4FAA6627D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift in Sources */, @@ -9978,7 +9977,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 102.0.2; + version = 103.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { @@ -10209,6 +10208,11 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = NetworkProtectionTestUtils; }; + F115ED9B2B4EFC8E001A0453 /* TestUtils */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = TestUtils; + }; F42D541C29DCA40B004C4FF1 /* DesignResourcesKit */ = { isa = XCSwiftPackageProductDependency; package = F42D541B29DCA40B004C4FF1 /* XCRemoteSwiftPackageReference "DesignResourcesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 946e0c3b86..1ccd2c196c 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "072df96b846e3350b23ab32a61b6c1a0983b5e85", - "version" : "102.0.2" + "revision" : "5af8fbcff0913aa543ba3eea60cc24e706a3a4e5", + "version" : "103.0.0" } }, { @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "38336a574e13090764ba09a6b877d15ee514e371", - "version" : "3.1.1" + "revision" : "c67d268bf234760f49034a0fe7a6137a1b216b05", + "version" : "3.2.0" } }, { diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 7002be5231..0d994f5699 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -124,7 +124,6 @@ extension MainViewController { os_log(#function, log: .generalLog, type: .debug) hideAllHighlightsIfNeeded() - let brokenSiteInfo = currentTab?.getCurrentWebsiteInfo() guard let currentURL = currentTab?.url, let privacyInfo = currentTab?.makePrivacyInfo(url: currentURL) else { assertionFailure("Missing fundamental data") @@ -133,11 +132,12 @@ extension MainViewController { let storyboard = UIStoryboard(name: "PrivacyDashboard", bundle: nil) let controller = storyboard.instantiateInitialViewController { coder in - PrivacyDashboardViewController(coder: coder, + PrivacyDashboardViewController(coder: coder, privacyInfo: privacyInfo, privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, contentBlockingManager: ContentBlocking.shared.contentBlockingManager, - initMode: .reportBrokenSite) + initMode: .reportBrokenSite, + breakageAdditionalInfo: self.currentTab?.makeBreakageAdditionalInfo()) } guard let controller = controller else { @@ -147,7 +147,6 @@ extension MainViewController { currentTab?.privacyDashboard = controller controller.popoverPresentationController?.delegate = controller - controller.brokenSiteInfo = brokenSiteInfo if UIDevice.current.userInterfaceIdiom == .pad { controller.modalPresentationStyle = .formSheet diff --git a/DuckDuckGo/PrivacyDashboard/BrokenSiteInfo.swift b/DuckDuckGo/PrivacyDashboard/BrokenSiteInfo.swift deleted file mode 100644 index a6395d695d..0000000000 --- a/DuckDuckGo/PrivacyDashboard/BrokenSiteInfo.swift +++ /dev/null @@ -1,130 +0,0 @@ -// -// BrokenSiteInfo.swift -// DuckDuckGo -// -// Copyright © 2020 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 Core - -public struct BrokenSiteInfo { - - static let allowedQueryReservedCharacters = CharacterSet(charactersIn: ",") - - private struct Keys { - static let url = "siteUrl" - static let category = "category" - static let reportFlow = "reportFlow" - static let description = "description" - static let upgradedHttps = "upgradedHttps" - static let tds = "tds" - static let blockedTrackers = "blockedTrackers" - static let surrogates = "surrogates" - static let atb = "atb" - static let os = "os" - static let manufacturer = "manufacturer" - static let model = "model" - static let siteType = "siteType" - static let gpc = "gpc" - static let ampUrl = "ampUrl" - static let urlParametersRemoved = "urlParametersRemoved" - static let protectionsState = "protectionsState" - } - - public enum Source: String { - case appMenu = "menu" - case dashboard - } - - private let url: URL? - private let httpsUpgrade: Bool - private let blockedTrackerDomains: [String] - private let installedSurrogates: [String] - private let isDesktop: Bool - private let tdsETag: String? - private let ampUrl: String? - private let urlParametersRemoved: Bool - private let model: String - private let manufacturer: String - private let systemVersion: String - private let gpc: Bool - private let protectionsState: Bool - - public init(url: URL?, httpsUpgrade: Bool, - blockedTrackerDomains: [String], - installedSurrogates: [String], - isDesktop: Bool, - tdsETag: String?, - ampUrl: String?, - urlParametersRemoved: Bool, - protectionsState: Bool, - model: String = UIDevice.current.model, - manufacturer: String = "Apple", - systemVersion: String = UIDevice.current.systemVersion, - gpc: Bool? = nil) { - self.url = url - self.httpsUpgrade = httpsUpgrade - self.blockedTrackerDomains = blockedTrackerDomains - self.installedSurrogates = installedSurrogates - self.isDesktop = isDesktop - self.tdsETag = tdsETag - self.ampUrl = ampUrl - self.urlParametersRemoved = urlParametersRemoved - - self.model = model - self.manufacturer = manufacturer - self.systemVersion = systemVersion - self.protectionsState = protectionsState - - if let gpcParam = gpc { - self.gpc = gpcParam - } else { - self.gpc = AppDependencyProvider.shared.appSettings.sendDoNotSell - } - } - - func send(with category: String?, description: String, source: Source) { - - let parameters: [String: String] = [ - Keys.url: normalize(url), - Keys.category: category ?? "", - Keys.description: description, - Keys.reportFlow: source.rawValue, - Keys.upgradedHttps: httpsUpgrade ? "true" : "false", - Keys.siteType: isDesktop ? "desktop" : "mobile", - Keys.tds: tdsETag?.trimmingCharacters(in: CharacterSet(charactersIn: "\"")) ?? "", - Keys.blockedTrackers: blockedTrackerDomains.joined(separator: ","), - Keys.surrogates: installedSurrogates.joined(separator: ","), - Keys.atb: StatisticsUserDefaults().atb ?? "", - Keys.os: systemVersion, - Keys.manufacturer: manufacturer, - Keys.model: model, - Keys.gpc: gpc ? "true" : "false", - Keys.ampUrl: ampUrl ?? "", - Keys.urlParametersRemoved: urlParametersRemoved ? "true" : "false", - Keys.protectionsState: protectionsState ? "true" : "false" - ] - - Pixel.fire(pixel: .brokenSiteReport, - withAdditionalParameters: parameters, - allowedQueryReservedCharacters: BrokenSiteInfo.allowedQueryReservedCharacters) - } - - private func normalize(_ url: URL?) -> String { - return url?.normalized()?.absoluteString ?? "" - } - -} diff --git a/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift b/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift index c4a2e67877..48a5b0d6e0 100644 --- a/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift +++ b/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift @@ -23,6 +23,7 @@ import Combine import Core import BrowserServicesKit import PrivacyDashboard +import Common /// View controller used for `Privacy Dasboard` or `Report broken site`, the web content is chosen at init time setting the correct `initMode` class PrivacyDashboardViewController: UIViewController { @@ -39,22 +40,31 @@ class PrivacyDashboardViewController: UIViewController { private let privacyDashboardController: PrivacyDashboardController private let privacyConfigurationManager: PrivacyConfigurationManaging private let contentBlockingManager: ContentBlockerRulesManager - public var brokenSiteInfo: BrokenSiteInfo? + public var breakageAdditionalInfo: BreakageAdditionalInfo? - var source: BrokenSiteInfo.Source { + var source: WebsiteBreakage.Source { initMode == .reportBrokenSite ? .appMenu : .dashboard } + private let websiteBreakageReporter: WebsiteBreakageReporter = { + WebsiteBreakageReporter(pixelHandler: { parameters in + Pixel.fire(pixel: .brokenSiteReport, + withAdditionalParameters: parameters, + allowedQueryReservedCharacters: WebsiteBreakage.allowedQueryReservedCharacters) + }, keyValueStoring: UserDefaults.standard) + }() + init?(coder: NSCoder, privacyInfo: PrivacyInfo?, privacyConfigurationManager: PrivacyConfigurationManaging, contentBlockingManager: ContentBlockerRulesManager, - initMode: Mode) { + initMode: Mode, + breakageAdditionalInfo: BreakageAdditionalInfo?) { self.privacyDashboardController = PrivacyDashboardController(privacyInfo: privacyInfo) self.privacyConfigurationManager = privacyConfigurationManager self.contentBlockingManager = contentBlockingManager self.initMode = initMode - + self.breakageAdditionalInfo = breakageAdditionalInfo super.init(coder: coder) self.privacyDashboardController.privacyDashboardDelegate = self @@ -126,6 +136,8 @@ extension PrivacyDashboardViewController: Themable { } } +// MARK: - PrivacyDashboardControllerDelegate + extension PrivacyDashboardViewController: PrivacyDashboardControllerDelegate { func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didChangeProtectionSwitch protectionState: ProtectionState) { @@ -159,6 +171,8 @@ extension PrivacyDashboardViewController: PrivacyDashboardControllerDelegate { } } +// MARK: - PrivacyDashboardNavigationDelegate + extension PrivacyDashboardViewController: PrivacyDashboardNavigationDelegate { func privacyDashboardController(_ privacyDashboardController: PrivacyDashboard.PrivacyDashboardController, didSetHeight height: Int) { @@ -171,23 +185,74 @@ extension PrivacyDashboardViewController: PrivacyDashboardNavigationDelegate { } } +// MARK: - PrivacyDashboardReportBrokenSiteDelegate + extension PrivacyDashboardViewController: PrivacyDashboardReportBrokenSiteDelegate { - - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, reportBrokenSiteDidChangeProtectionSwitch protectionState: ProtectionState) { + + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + reportBrokenSiteDidChangeProtectionSwitch protectionState: ProtectionState) { privacyDashboardProtectionSwitchChangeHandler(state: protectionState) } - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboard.PrivacyDashboardController, didRequestSubmitBrokenSiteReportWithCategory category: String, description: String) { - - guard let brokenSiteInfo = brokenSiteInfo else { - assertionFailure("brokenSiteInfo not initialised") - return + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboard.PrivacyDashboardController, + didRequestSubmitBrokenSiteReportWithCategory category: String, description: String) { + + do { + let breakageReport = try makeWebsiteBreakage(category: category, description: description) + try websiteBreakageReporter.report(breakage: breakageReport) + } catch { + os_log("Failed to generate or send the website breakage report: %@", type: .error, error.localizedDescription) } - brokenSiteInfo.send(with: category, description: description, source: source) ActionMessageView.present(message: UserText.feedbackSumbittedConfirmation) privacyDashboardCloseHandler() } } extension PrivacyDashboardViewController: UIPopoverPresentationControllerDelegate {} + +extension PrivacyDashboardViewController { + + struct BreakageAdditionalInfo { + let currentURL: URL + let httpsForced: Bool + let ampURLString: String + let urlParametersRemoved: Bool + let isDesktop: Bool + } + + enum WebsiteBreakageError: Error { + case failedToFetchTheCurrentWebsiteInfo + } + + private func makeWebsiteBreakage(category: String, description: String) throws -> WebsiteBreakage { + + guard let privacyInfo = privacyDashboardController.privacyInfo, + let breakageAdditionalInfo = breakageAdditionalInfo else { + throw WebsiteBreakageError.failedToFetchTheCurrentWebsiteInfo + } + + let blockedTrackerDomains = privacyInfo.trackerInfo.trackersBlocked.compactMap { $0.domain } + let configuration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig + let protectionsState = configuration.isFeature(.contentBlocking, enabledForDomain: breakageAdditionalInfo.currentURL.host) + + return WebsiteBreakage(siteUrl: breakageAdditionalInfo.currentURL, + category: category, + description: description, + osVersion: "\(ProcessInfo().operatingSystemVersion.majorVersion)", + manufacturer: "Apple", + upgradedHttps: breakageAdditionalInfo.httpsForced, + tdsETag: ContentBlocking.shared.contentBlockingManager.currentMainRules?.etag ?? "", + blockedTrackerDomains: blockedTrackerDomains, + installedSurrogates: privacyInfo.trackerInfo.installedSurrogates.map { $0 }, + isGPCEnabled: AppDependencyProvider.shared.appSettings.sendDoNotSell, + ampURL: breakageAdditionalInfo.ampURLString, + urlParametersRemoved: breakageAdditionalInfo.urlParametersRemoved, + protectionsState: protectionsState, + reportFlow: source, + siteType: breakageAdditionalInfo.isDesktop ? .desktop : .mobile, + atb: StatisticsUserDefaults().atb ?? "", + model: UIDevice.current.model) + + } +} diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index f54d55883b..f0d388a281 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -722,7 +722,6 @@ class TabViewController: UIViewController { controller.popoverPresentationController?.sourceRect = iconView.bounds } privacyDashboard = controller - privacyDashboard?.brokenSiteInfo = getCurrentWebsiteInfo() } if let controller = segue.destination as? FullscreenDaxDialogViewController { @@ -749,7 +748,8 @@ class TabViewController: UIViewController { privacyInfo: privacyInfo, privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, contentBlockingManager: ContentBlocking.shared.contentBlockingManager, - initMode: .privacyDashboard) + initMode: .privacyDashboard, + breakageAdditionalInfo: makeBreakageAdditionalInfo()) } private func addTextSizeObserver() { @@ -912,22 +912,17 @@ class TabViewController: UIViewController { webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.canGoBack)) webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.title)) } + + public func makeBreakageAdditionalInfo() -> PrivacyDashboardViewController.BreakageAdditionalInfo? { - public func getCurrentWebsiteInfo() -> BrokenSiteInfo { - let blockedTrackerDomains = privacyInfo?.trackerInfo.trackersBlocked.compactMap { $0.domain } ?? [] - - let configuration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig - let protectionsState = configuration.isFeature(.contentBlocking, enabledForDomain: url?.host) - - return BrokenSiteInfo(url: url, - httpsUpgrade: httpsForced, - blockedTrackerDomains: blockedTrackerDomains, - installedSurrogates: privacyInfo?.trackerInfo.installedSurrogates.map { $0 } ?? [], - isDesktop: tabModel.isDesktop, - tdsETag: ContentBlocking.shared.contentBlockingManager.currentMainRules?.etag ?? "", - ampUrl: linkProtection.lastAMPURLString, - urlParametersRemoved: linkProtection.urlParametersRemoved, - protectionsState: protectionsState) + guard let currentURL = url else { + return nil + } + return PrivacyDashboardViewController.BreakageAdditionalInfo(currentURL: currentURL, + httpsForced: httpsForced, + ampURLString: linkProtection.lastAMPURLString ?? "", + urlParametersRemoved: linkProtection.urlParametersRemoved, + isDesktop: tabModel.isDesktop) } public func print() { diff --git a/DuckDuckGoTests/BrokenSiteReportingTests.swift b/DuckDuckGoTests/BrokenSiteReportingTests.swift index 3959b8cbf3..40c578f113 100644 --- a/DuckDuckGoTests/BrokenSiteReportingTests.swift +++ b/DuckDuckGoTests/BrokenSiteReportingTests.swift @@ -23,8 +23,9 @@ import BrowserServicesKit import OHHTTPStubs import OHHTTPStubsSwift @testable import Core - +import PrivacyDashboard @testable import DuckDuckGo +import TestUtils final class BrokenSiteReportingTests: XCTestCase { private let data = JsonTestDataLoader() @@ -52,7 +53,6 @@ final class BrokenSiteReportingTests: XCTestCase { func testBrokenSiteReporting() throws { let testJSON = data.fromJsonFile(Resource.tests) - let testString = String(data: testJSON, encoding: .utf8) let testData = try JSONDecoder().decode(BrokenSiteReportingTestData.self, from: testJSON) referenceTests = testData.reportURL.tests.filter { @@ -61,13 +61,12 @@ final class BrokenSiteReportingTests: XCTestCase { let testsExecuted = expectation(description: "tests executed") testsExecuted.expectedFulfillmentCount = referenceTests.count - - runReferenceTests(onTestExecuted: testsExecuted) - waitForExpectations(timeout: 30, handler: nil) - + + try runReferenceTests(onTestExecuted: testsExecuted) + waitForExpectations(timeout: 10, handler: nil) } - private func runReferenceTests(onTestExecuted: XCTestExpectation) { + private func runReferenceTests(onTestExecuted: XCTestExpectation) throws { guard let test = referenceTests.popLast() else { return @@ -75,56 +74,41 @@ final class BrokenSiteReportingTests: XCTestCase { os_log("Testing [%s]", type: .info, test.name) - let brokenSiteInfo = BrokenSiteInfo(url: URL(string: test.siteURL), - httpsUpgrade: test.wasUpgraded, - blockedTrackerDomains: test.blockedTrackers, - installedSurrogates: test.surrogates, - isDesktop: true, - tdsETag: test.blocklistVersion, - ampUrl: nil, - urlParametersRemoved: false, - protectionsState: test.protectionsEnabled, - model: test.model ?? "", - manufacturer: test.manufacturer ?? "", - systemVersion: test.os ?? "", - gpc: test.gpcEnabled) + let websiteBreakage = WebsiteBreakage(siteUrl: URL(string: test.siteURL)!, + category: test.category, + description: "", + osVersion: test.os ?? "", + manufacturer: test.manufacturer ?? "", + upgradedHttps: test.wasUpgraded, + tdsETag: test.blocklistVersion, + blockedTrackerDomains: test.blockedTrackers, + installedSurrogates: test.surrogates, + isGPCEnabled: test.gpcEnabled ?? false, + ampURL: "", + urlParametersRemoved: false, + protectionsState: test.protectionsEnabled, + reportFlow: .dashboard, + siteType: .mobile, + atb: "", + model: test.model ?? "") - stub(condition: isHost(host)) { request -> HTTPStubsResponse in + let reporter = WebsiteBreakageReporter(pixelHandler: { params in - guard let requestURL = request.url else { - XCTFail("Couldn't create request URL") - return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) - } - - let absoluteURL = requestURL.absoluteString - .replacingOccurrences(of: "%20", with: " ") - - if test.expectReportURLPrefix.count > 0 { - XCTAssertTrue(requestURL.absoluteString.contains(test.expectReportURLPrefix), "Prefix [\(test.expectReportURLPrefix)] not found") - } - - for param in test.expectReportURLParams { - let pattern = "[?&]\(param.name)=\(param.value)[&$]?" - - guard let regex = try? NSRegularExpression(pattern: pattern, - options: []) else { - XCTFail("Couldn't create regex") - return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) + for expectedParam in test.expectReportURLParams { + + if let actualValue = params[expectedParam.name], + let expectedCleanValue = expectedParam.value.removingPercentEncoding { + if actualValue != expectedCleanValue { + XCTFail("Mismatching param: \(expectedParam.name) => \(expectedCleanValue) != \(actualValue)") + } + } else { + XCTFail("Missing param: \(expectedParam.name)") } - - let match = regex.matches(in: absoluteURL, range: NSRange(location: 0, length: absoluteURL.count)) - XCTAssertEqual(match.count, 1, "Param [\(param.name)] with value [\(param.value)] not found in [\(absoluteURL)]") - } - - DispatchQueue.main.async { - onTestExecuted.fulfill() - self.runReferenceTests(onTestExecuted: onTestExecuted) } - - return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) - } - - brokenSiteInfo.send(with: test.category, description: "", source: .dashboard) + onTestExecuted.fulfill() + try? self.runReferenceTests(onTestExecuted: onTestExecuted) + }, keyValueStoring: MockKeyValueStore()) + try reporter.report(breakage: websiteBreakage) } } diff --git a/Gemfile.lock b/Gemfile.lock index d857156940..41d2cd26f5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -32,7 +32,8 @@ GEM declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20231109) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) excon (0.104.0) @@ -188,6 +189,9 @@ GEM tty-spinner (0.9.3) tty-cursor (~> 0.7) uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.9.1) unicode-display_width (2.5.0) webrick (1.8.1) word_wrap (1.0.0) diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 604624a245..cf31671f45 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "102.0.2"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "103.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 727d00e286..6543b2f149 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "102.0.2"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "103.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 70220d0c31..598bd55103 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "102.0.2"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "103.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index 6b7ad1e7f1..a3acc21947 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22 +Subproject commit a3acc2194758bec0f01f57dd0c5f106de01a354e