From b16b54c8fd9b6785ce48349cc11b90b50038056e Mon Sep 17 00:00:00 2001 From: Pete Smith <5278441+aataraxiaa@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:29:37 +0000 Subject: [PATCH] Freemium PIR: Manual Removal Links (#3466) Task/Issue URL: https://app.asana.com/0/72649045549333/1208619435900636/f FE PR: https://dub.duckduckgo.com/duckduckgo/static-pages/pull/1012 **Description**: Adds a manual removal link to each Freemium PIR result record view --- .../DataBrokerProtectionDataManager.swift | 4 +- .../Model/DBPUICommunicationModel.swift | 20 +-- .../Model/DataBroker.swift | 8 +- .../Storage/Mappers.swift | 3 +- .../UI/DBPUICommunicationLayer.swift | 2 +- .../DataBrokerProtection/UI/UIMapper.swift | 21 ++-- .../DBPUICommunicationModelTests.swift | 81 ++++++++++-- ...kerProfileQueryOperationManagerTests.swift | 25 ++-- .../DataBrokerProtectionUpdaterTests.swift | 6 +- .../MapperToModelTests.swift | 118 ++++++++++++++++++ .../MapperToUITests.swift | 67 ++++++++++ .../MismatchCalculatorUseCaseTests.swift | 6 +- .../DataBrokerProtectionTests/Mocks.swift | 11 +- .../OperationPreferredDateUpdaterTests.swift | 3 +- 14 files changed, 329 insertions(+), 46 deletions(-) create mode 100644 LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToModelTests.swift diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift index ffb525774a..f6ad3673cb 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift @@ -409,10 +409,10 @@ extension InMemoryDataCache: DBPUICommunicationDelegate { // 2. We map the brokers to the UI model .flatMap { dataBroker -> [DBPUIDataBroker] in var result: [DBPUIDataBroker] = [] - result.append(DBPUIDataBroker(name: dataBroker.name, url: dataBroker.url, parentURL: dataBroker.parent)) + result.append(DBPUIDataBroker(name: dataBroker.name, url: dataBroker.url, parentURL: dataBroker.parent, optOutUrl: dataBroker.optOutUrl)) for mirrorSite in dataBroker.mirrorSites { - result.append(DBPUIDataBroker(name: mirrorSite.name, url: mirrorSite.url, parentURL: dataBroker.parent)) + result.append(DBPUIDataBroker(name: mirrorSite.name, url: mirrorSite.url, parentURL: dataBroker.parent, optOutUrl: dataBroker.optOutUrl)) } return result } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUICommunicationModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUICommunicationModel.swift index 24fe6b6a1a..68e5f19d0e 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUICommunicationModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUICommunicationModel.swift @@ -127,12 +127,14 @@ struct DBPUIDataBroker: Codable, Hashable { let url: String let date: Double? let parentURL: String? + let optOutUrl: String - init(name: String, url: String, date: Double? = nil, parentURL: String?) { + init(name: String, url: String, date: Double? = nil, parentURL: String?, optOutUrl: String) { self.name = name self.url = url self.date = date self.parentURL = parentURL + self.optOutUrl = optOutUrl } func hash(into hasher: inout Hasher) { @@ -170,7 +172,8 @@ extension DBPUIDataBrokerProfileMatch { dataBrokerName: String, dataBrokerURL: String, dataBrokerParentURL: String?, - parentBrokerOptOutJobData: [OptOutJobData]?) { + parentBrokerOptOutJobData: [OptOutJobData]?, + optOutUrl: String) { let extractedProfile = optOutJobData.extractedProfile /* @@ -205,7 +208,7 @@ extension DBPUIDataBrokerProfileMatch { extractedProfile.doesMatchExtractedProfile(parentOptOut.extractedProfile) } ?? false - self.init(dataBroker: DBPUIDataBroker(name: dataBrokerName, url: dataBrokerURL, parentURL: dataBrokerParentURL), + self.init(dataBroker: DBPUIDataBroker(name: dataBrokerName, url: dataBrokerURL, parentURL: dataBrokerParentURL, optOutUrl: optOutUrl), name: extractedProfile.fullName ?? "No name", addresses: extractedProfile.addresses?.map {DBPUIUserProfileAddress(addressCityState: $0) } ?? [], alternativeNames: extractedProfile.alternativeNames ?? [String](), @@ -217,12 +220,13 @@ extension DBPUIDataBrokerProfileMatch { hasMatchingRecordOnParentBroker: hasFoundParentMatch) } - init(optOutJobData: OptOutJobData, dataBroker: DataBroker, parentBrokerOptOutJobData: [OptOutJobData]?) { + init(optOutJobData: OptOutJobData, dataBroker: DataBroker, parentBrokerOptOutJobData: [OptOutJobData]?, optOutUrl: String) { self.init(optOutJobData: optOutJobData, dataBrokerName: dataBroker.name, dataBrokerURL: dataBroker.url, dataBrokerParentURL: dataBroker.parent, - parentBrokerOptOutJobData: parentBrokerOptOutJobData) + parentBrokerOptOutJobData: parentBrokerOptOutJobData, + optOutUrl: optOutUrl) } /// Generates an array of `DBPUIDataBrokerProfileMatch` objects from the provided query data. @@ -253,7 +257,8 @@ extension DBPUIDataBrokerProfileMatch { // Create a profile match for the current data broker and append it to the list of profiles. profiles.append(DBPUIDataBrokerProfileMatch(optOutJobData: optOutJobData, dataBroker: dataBroker, - parentBrokerOptOutJobData: parentBrokerOptOutJobData)) + parentBrokerOptOutJobData: parentBrokerOptOutJobData, + optOutUrl: dataBroker.optOutUrl)) // Handle mirror sites associated with the data broker. if !dataBroker.mirrorSites.isEmpty { @@ -264,7 +269,8 @@ extension DBPUIDataBrokerProfileMatch { dataBrokerName: mirrorSite.name, dataBrokerURL: mirrorSite.url, dataBrokerParentURL: dataBroker.parent, - parentBrokerOptOutJobData: parentBrokerOptOutJobData) + parentBrokerOptOutJobData: parentBrokerOptOutJobData, + optOutUrl: dataBroker.optOutUrl) } return nil } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift index 9014bcb185..5328a699f6 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift @@ -115,6 +115,7 @@ public struct DataBroker: Codable, Sendable { let schedulingConfig: DataBrokerScheduleConfig let parent: String? let mirrorSites: [MirrorSite] + let optOutUrl: String var isFakeBroker: Bool { name.contains("fake") // A future improvement will be to add a property in the JSON file. @@ -128,6 +129,7 @@ public struct DataBroker: Codable, Sendable { case schedulingConfig case parent case mirrorSites + case optOutUrl } init(id: Int64? = nil, @@ -137,7 +139,8 @@ public struct DataBroker: Codable, Sendable { version: String, schedulingConfig: DataBrokerScheduleConfig, parent: String? = nil, - mirrorSites: [MirrorSite] = [MirrorSite]() + mirrorSites: [MirrorSite] = [MirrorSite](), + optOutUrl: String ) { self.id = id self.name = name @@ -153,6 +156,7 @@ public struct DataBroker: Codable, Sendable { self.schedulingConfig = schedulingConfig self.parent = parent self.mirrorSites = mirrorSites + self.optOutUrl = optOutUrl } public init(from decoder: Decoder) throws { @@ -179,6 +183,8 @@ public struct DataBroker: Codable, Sendable { mirrorSites = [MirrorSite]() } + optOutUrl = (try? container.decode(String.self, forKey: .optOutUrl)) ?? "" + id = nil } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/Mappers.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/Mappers.swift index fc221f3872..ab3be3f110 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/Mappers.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/Mappers.swift @@ -176,7 +176,8 @@ struct MapperToModel { version: decodedBroker.version, schedulingConfig: decodedBroker.schedulingConfig, parent: decodedBroker.parent, - mirrorSites: decodedBroker.mirrorSites + mirrorSites: decodedBroker.mirrorSites, + optOutUrl: decodedBroker.optOutUrl ) } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift index 16f78bfe45..5992962044 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift @@ -79,7 +79,7 @@ struct DBPUICommunicationLayer: Subfeature { weak var delegate: DBPUICommunicationDelegate? private enum Constants { - static let version = 7 + static let version = 8 } internal init(webURLSettings: DataBrokerProtectionWebUIURLSettingsRepresentable, diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift index 4636f3b58d..36efa17943 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift @@ -73,7 +73,8 @@ struct MapperToUI { let profileMatch = DBPUIDataBrokerProfileMatch(optOutJobData: optOutJob, dataBroker: dataBroker, - parentBrokerOptOutJobData: parentBrokerOptOutJobData) + parentBrokerOptOutJobData: parentBrokerOptOutJobData, + optOutUrl: dataBroker.optOutUrl) if extractedProfile.removedDate == nil { inProgressOptOuts.append(profileMatch) @@ -87,7 +88,8 @@ struct MapperToUI { dataBrokerName: mirrorSite.name, dataBrokerURL: mirrorSite.url, dataBrokerParentURL: dataBroker.parent, - parentBrokerOptOutJobData: parentBrokerOptOutJobData) + parentBrokerOptOutJobData: parentBrokerOptOutJobData, + optOutUrl: dataBroker.optOutUrl) if let extractedProfileRemovedDate = extractedProfile.removedDate, mirrorSite.shouldWeIncludeMirrorSite(for: extractedProfileRemovedDate) { @@ -135,13 +137,15 @@ struct MapperToUI { brokers.append(DBPUIDataBroker(name: $0.dataBroker.name, url: $0.dataBroker.url, date: $0.scanJobData.lastRunDate!.timeIntervalSince1970, - parentURL: $0.dataBroker.parent)) + parentURL: $0.dataBroker.parent, + optOutUrl: $0.dataBroker.optOutUrl)) for mirrorSite in $0.dataBroker.mirrorSites where mirrorSite.addedAt < $0.scanJobData.lastRunDate! { brokers.append(DBPUIDataBroker(name: mirrorSite.name, url: mirrorSite.url, date: $0.scanJobData.lastRunDate!.timeIntervalSince1970, - parentURL: $0.dataBroker.parent)) + parentURL: $0.dataBroker.parent, + optOutUrl: $0.dataBroker.optOutUrl)) } return brokers @@ -171,7 +175,8 @@ struct MapperToUI { brokers.append(DBPUIDataBroker(name: $0.dataBroker.name, url: $0.dataBroker.url, date: $0.scanJobData.preferredRunDate!.timeIntervalSince1970, - parentURL: $0.dataBroker.parent)) + parentURL: $0.dataBroker.parent, + optOutUrl: $0.dataBroker.optOutUrl)) for mirrorSite in $0.dataBroker.mirrorSites { if let removedDate = mirrorSite.removedAt { @@ -179,13 +184,15 @@ struct MapperToUI { brokers.append(DBPUIDataBroker(name: mirrorSite.name, url: mirrorSite.url, date: $0.scanJobData.preferredRunDate!.timeIntervalSince1970, - parentURL: $0.dataBroker.parent)) + parentURL: $0.dataBroker.parent, + optOutUrl: $0.dataBroker.optOutUrl)) } } else { brokers.append(DBPUIDataBroker(name: mirrorSite.name, url: mirrorSite.url, date: $0.scanJobData.preferredRunDate!.timeIntervalSince1970, - parentURL: $0.dataBroker.parent)) + parentURL: $0.dataBroker.parent, + optOutUrl: $0.dataBroker.optOutUrl)) } } diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DBPUICommunicationModelTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DBPUICommunicationModelTests.swift index 68b71f4fdd..aff311ce82 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DBPUICommunicationModelTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DBPUICommunicationModelTests.swift @@ -46,7 +46,8 @@ final class DBPUICommunicationModelTests: XCTestCase { dataBrokerName: "doesn't matter for the test", dataBrokerURL: "see above", dataBrokerParentURL: "whatever", - parentBrokerOptOutJobData: nil) + parentBrokerOptOutJobData: nil, + optOutUrl: "broker.com") // Then XCTAssertEqual(profileMatch.foundDate, createdDate.timeIntervalSince1970) @@ -77,7 +78,8 @@ final class DBPUICommunicationModelTests: XCTestCase { dataBrokerName: "doesn't matter for the test", dataBrokerURL: "see above", dataBrokerParentURL: "whatever", - parentBrokerOptOutJobData: nil) + parentBrokerOptOutJobData: nil, + optOutUrl: "broker.com") // Then XCTAssertEqual(profileMatch.foundDate, foundEventDate.timeIntervalSince1970) @@ -116,7 +118,8 @@ final class DBPUICommunicationModelTests: XCTestCase { dataBrokerName: "doesn't matter for the test", dataBrokerURL: "see above", dataBrokerParentURL: "whatever", - parentBrokerOptOutJobData: nil) + parentBrokerOptOutJobData: nil, + optOutUrl: "broker.com") // Then XCTAssertEqual(profileMatch.foundDate, foundEventDate2.timeIntervalSince1970) @@ -147,7 +150,8 @@ final class DBPUICommunicationModelTests: XCTestCase { dataBrokerName: "doesn't matter for the test", dataBrokerURL: "see above", dataBrokerParentURL: "whatever", - parentBrokerOptOutJobData: [parentOptOut]) + parentBrokerOptOutJobData: [parentOptOut], + optOutUrl: "broker.com") // Then XCTAssertTrue(profileMatch.hasMatchingRecordOnParentBroker) @@ -177,7 +181,8 @@ final class DBPUICommunicationModelTests: XCTestCase { dataBrokerParentURL: "whatever", parentBrokerOptOutJobData: [parentOptOutNonmatching1, parentOptOutMatching, - parentOptOutNonmatching2]) + parentOptOutNonmatching2], + optOutUrl: "broker.com") // Then XCTAssertTrue(profileMatch.hasMatchingRecordOnParentBroker) @@ -203,7 +208,8 @@ final class DBPUICommunicationModelTests: XCTestCase { dataBrokerURL: "see above", dataBrokerParentURL: "whatever", parentBrokerOptOutJobData: [parentOptOutNonmatching1, - parentOptOutNonmatching2]) + parentOptOutNonmatching2], + optOutUrl: "broker.com") // Then XCTAssertFalse(profileMatch.hasMatchingRecordOnParentBroker) @@ -225,9 +231,70 @@ final class DBPUICommunicationModelTests: XCTestCase { dataBrokerName: "doesn't matter for the test", dataBrokerURL: "see above", dataBrokerParentURL: "whatever", - parentBrokerOptOutJobData: [parentOptOut]) + parentBrokerOptOutJobData: [parentOptOut], + optOutUrl: "broker.com") // Then XCTAssertTrue(profileMatch.hasMatchingRecordOnParentBroker) } + + // MARK: - `profileMatches` Broker OptOut URL & Name tests + + func testProfileMatches_optOutUrlAndBrokerNameForChildBroker() { + // Given + let extractedProfile = ExtractedProfile(id: 1, name: "Sample Name", profileUrl: "profile.com") + + let childBroker = BrokerProfileQueryData.mock( + dataBrokerName: "ChildBroker", + url: "child.com", + parentURL: "parent.com", + optOutUrl: "child.com/optout", + extractedProfile: extractedProfile + ) + + let parentBroker = BrokerProfileQueryData.mock( + dataBrokerName: "ParentBroker", + url: "parent.com", + optOutUrl: "parent.com/optout", + extractedProfile: extractedProfile + ) + + // When + let results = DBPUIDataBrokerProfileMatch.profileMatches(from: [childBroker, parentBroker]) + + // Then + XCTAssertEqual(results.count, 2) + + let childProfile = results.first { $0.dataBroker.name == "ChildBroker" } + XCTAssertEqual(childProfile?.dataBroker.optOutUrl, "child.com/optout") + } + + func testProfileMatches_optOutUrlAndBrokerNameForParentBroker() { + // Given + let extractedProfile = ExtractedProfile(id: 1, name: "Sample Name", profileUrl: "profile.com") + + let childBroker = BrokerProfileQueryData.mock( + dataBrokerName: "ChildBroker", + url: "child.com", + parentURL: "parent.com", + optOutUrl: "parent.com/optout", + extractedProfile: extractedProfile + ) + + let parentBroker = BrokerProfileQueryData.mock( + dataBrokerName: "ParentBroker", + url: "parent.com", + optOutUrl: "parent.com/optout", + extractedProfile: extractedProfile + ) + + // When + let results = DBPUIDataBrokerProfileMatch.profileMatches(from: [childBroker, parentBroker]) + + // Then + XCTAssertEqual(results.count, 2) + + let childProfile = results.first { $0.dataBroker.name == "ChildBroker" } + XCTAssertEqual(childProfile?.dataBroker.optOutUrl, "parent.com/optout") + } } diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProfileQueryOperationManagerTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProfileQueryOperationManagerTests.swift index b41f802d47..f33b340479 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProfileQueryOperationManagerTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProfileQueryOperationManagerTests.swift @@ -44,7 +44,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { let extractedProfileId: Int64 = 1 let currentPreferredRunDate = Date() - let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", steps: [Step](), version: "1.0", schedulingConfig: config) + let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", steps: [Step](), version: "1.0", schedulingConfig: config, optOutUrl: "") let mockProfileQuery = ProfileQuery(id: profileQueryId, firstName: "a", lastName: "b", city: "c", state: "d", birthYear: 1222) let historyEvents = [HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested)] @@ -92,7 +92,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { let extractedProfileId: Int64 = 1 let currentPreferredRunDate = Date() - let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", steps: [Step](), version: "1.0", schedulingConfig: config) + let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", steps: [Step](), version: "1.0", schedulingConfig: config, optOutUrl: "") let mockProfileQuery = ProfileQuery(id: profileQueryId, firstName: "a", lastName: "b", city: "c", state: "d", birthYear: 1222) let historyEvents = [HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested)] @@ -143,7 +143,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { let extractedProfileId: Int64 = 1 let currentPreferredRunDate = Date() - let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", steps: [Step](), version: "1.0", schedulingConfig: config) + let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", steps: [Step](), version: "1.0", schedulingConfig: config, optOutUrl: "") let mockProfileQuery = ProfileQuery(id: profileQueryId, firstName: "a", lastName: "b", city: "c", state: "d", birthYear: 1222) let historyEvents = [HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested)] @@ -888,7 +888,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { let extractedProfileId: Int64 = 1 let currentPreferredRunDate = Date() - let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", steps: [Step](), version: "1.0", schedulingConfig: config) + let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", steps: [Step](), version: "1.0", schedulingConfig: config, optOutUrl: "") let mockProfileQuery = ProfileQuery(id: profileQueryId, firstName: "a", lastName: "b", city: "c", state: "d", birthYear: 1222) let historyEvents = [HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested)] @@ -913,7 +913,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { let currentPreferredRunDate = Date() let expectedPreferredRunDate = Date().addingTimeInterval(config.confirmOptOutScan.hoursToSeconds) - let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", steps: [Step](), version: "1.0", schedulingConfig: config) + let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", steps: [Step](), version: "1.0", schedulingConfig: config, optOutUrl: "") let mockProfileQuery = ProfileQuery(id: profileQueryId, firstName: "a", lastName: "b", city: "c", state: "d", birthYear: 1222) let historyEvents = [HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested)] @@ -987,7 +987,8 @@ extension DataBroker { retryError: 0, confirmOptOutScan: 0, maintenanceScan: 0 - ) + ), + optOutUrl: "" ) } @@ -1006,7 +1007,8 @@ extension DataBroker { confirmOptOutScan: 0, maintenanceScan: 0 ), - parent: "some" + parent: "some", + optOutUrl: "" ) } @@ -1020,7 +1022,8 @@ extension DataBroker { retryError: 0, confirmOptOutScan: 0, maintenanceScan: 0 - ) + ), + optOutUrl: "" ) } @@ -1033,7 +1036,8 @@ extension DataBroker { retryError: 0, confirmOptOutScan: 0, maintenanceScan: 0 - ) + ), + optOutUrl: "" ) } @@ -1052,7 +1056,8 @@ extension DataBroker { confirmOptOutScan: 0, maintenanceScan: 0 ), - mirrorSites: mirroSites + mirrorSites: mirroSites, + optOutUrl: "" ) } } diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionUpdaterTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionUpdaterTests.swift index 4f70f8934a..93145f1be4 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionUpdaterTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionUpdaterTests.swift @@ -111,7 +111,7 @@ final class DataBrokerProtectionUpdaterTests: XCTestCase { if let vault = self.vault { let sut = DefaultDataBrokerProtectionBrokerUpdater(repository: repository, resources: resources, vault: vault) repository.lastCheckedVersion = nil - resources.brokersList = [.init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.1", schedulingConfig: .mock)] + resources.brokersList = [.init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.1", schedulingConfig: .mock, optOutUrl: "")] vault.shouldReturnOldVersionBroker = true sut.checkForUpdatesInBrokerJSONFiles() @@ -129,7 +129,7 @@ final class DataBrokerProtectionUpdaterTests: XCTestCase { if let vault = self.vault { let sut = DefaultDataBrokerProtectionBrokerUpdater(repository: repository, resources: resources, vault: vault) repository.lastCheckedVersion = nil - resources.brokersList = [.init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.1", schedulingConfig: .mock)] + resources.brokersList = [.init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.1", schedulingConfig: .mock, optOutUrl: "")] vault.shouldReturnNewVersionBroker = true sut.checkForUpdatesInBrokerJSONFiles() @@ -146,7 +146,7 @@ final class DataBrokerProtectionUpdaterTests: XCTestCase { if let vault = self.vault { let sut = DefaultDataBrokerProtectionBrokerUpdater(repository: repository, resources: resources, vault: vault) repository.lastCheckedVersion = nil - resources.brokersList = [.init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.0", schedulingConfig: .mock)] + resources.brokersList = [.init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.0", schedulingConfig: .mock, optOutUrl: "")] vault.profileQueries = [.mock] sut.checkForUpdatesInBrokerJSONFiles() diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToModelTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToModelTests.swift new file mode 100644 index 0000000000..c3a70fa5de --- /dev/null +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToModelTests.swift @@ -0,0 +1,118 @@ +// +// MapperToModelTests.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 XCTest +@testable import DataBrokerProtection + +final class MapperToModelTests: XCTestCase { + + private var sut = MapperToModel(mechanism: {_ in Data()}) + private var jsonDecoder: JSONDecoder! + private var jsonEncoder: JSONEncoder! + + override func setUpWithError() throws { + jsonDecoder = JSONDecoder() + jsonEncoder = JSONEncoder() + } + + func testMapToModel_validData() throws { + // Given + let brokerData = DataBroker( + id: 1, + name: "TestBroker", + url: "https://example.com", + steps: [], + version: "1.0", + schedulingConfig: DataBrokerScheduleConfig(retryError: 1, confirmOptOutScan: 2, maintenanceScan: 3), + parent: "ParentBroker", + mirrorSites: [], + optOutUrl: "https://example.com/opt-out" + ) + let jsonData = try jsonEncoder.encode(brokerData) + let brokerDB = BrokerDB(id: 1, name: "TestBroker", json: jsonData, version: "1.0", url: "https://example.com") + + // When + let result = try sut.mapToModel(brokerDB) + + // Then + XCTAssertEqual(result.id, brokerDB.id) + XCTAssertEqual(result.name, brokerDB.name) + XCTAssertEqual(result.url, brokerData.url) + XCTAssertEqual(result.version, brokerData.version) + XCTAssertEqual(result.steps.count, brokerData.steps.count) + XCTAssertEqual(result.parent, brokerData.parent) + XCTAssertEqual(result.mirrorSites.count, brokerData.mirrorSites.count) + XCTAssertEqual(result.optOutUrl, brokerData.optOutUrl) + } + + func testMapToModel_missingOptionalFields() throws { + // Given + let brokerData = """ + { + "name": "TestBroker", + "url": "https://example.com", + "steps": [], + "version": "1.0", + "schedulingConfig": {"retryError": 1, "confirmOptOutScan": 2, "maintenanceScan": 3} + } + """.data(using: .utf8)! + let brokerDB = BrokerDB(id: 1, name: "TestBroker", json: brokerData, version: "1.0", url: "https://example.com") + + // When + let result = try sut.mapToModel(brokerDB) + + // Then + XCTAssertNil(result.parent) + XCTAssertEqual(result.mirrorSites.count, 0) + XCTAssertEqual(result.optOutUrl, "") + } + + func testMapToModel_invalidJSONStructure() throws { + // Given + let invalidJsonData = """ + { + "invalidKey": "value" + } + """.data(using: .utf8)! + let brokerDB = BrokerDB(id: 1, name: "InvalidBroker", json: invalidJsonData, version: "1.0", url: "https://example.com") + + // When & Then + XCTAssertThrowsError(try sut.mapToModel(brokerDB)) { error in + XCTAssertTrue(error is DecodingError) + } + } + + func testMapToModel_missingUrlFallbackToName() throws { + // Given + let brokerData = """ + { + "name": "TestBroker", + "steps": [], + "version": "1.0", + "schedulingConfig": {"retryError": 1, "confirmOptOutScan": 2, "maintenanceScan": 3} + } + """.data(using: .utf8)! + let brokerDB = BrokerDB(id: 1, name: "TestBroker", json: brokerData, version: "1.0", url: "") + + // When + let result = try sut.mapToModel(brokerDB) + + // Then + XCTAssertEqual(result.url, brokerDB.name) + } +} diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToUITests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToUITests.swift index a47d13d87a..edff8837e8 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToUITests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToUITests.swift @@ -394,6 +394,73 @@ final class MapperToUITests: XCTestCase { XCTAssertEqual(result.scanProgress.scannedBrokers, expected) } + // MARK: - `maintenanceScanState` Broker OptOut URL & Name tests + + func testMaintenanceScanState_childBrokerWithOwnOptOutUrl() { + // Given + let extractedProfile = ExtractedProfile(id: 2, name: "Another Sample", profileUrl: "anotherprofile.com", removedDate: nil) + + let childBroker = BrokerProfileQueryData.mock( + dataBrokerName: "ChildBrokerWithOwnOptOut", + url: "child.com", + parentURL: "parent.com", + optOutUrl: "child.com/optout", + extractedProfile: extractedProfile + ) + + let parentBroker = BrokerProfileQueryData.mock( + dataBrokerName: "ParentBroker", + url: "parent.com", + optOutUrl: "parent.com/optout", + extractedProfile: extractedProfile + ) + + // When + let state = sut.maintenanceScanState([childBroker, parentBroker]) + + // Then + XCTAssertEqual(state.inProgressOptOuts.count, 2) + XCTAssertEqual(state.completedOptOuts.count, 0) + + let childProfile = state.inProgressOptOuts.first { $0.dataBroker.name == "ChildBrokerWithOwnOptOut" } + XCTAssertEqual(childProfile?.dataBroker.optOutUrl, "child.com/optout") + + let parentProfile = state.inProgressOptOuts.first { $0.dataBroker.name == "ParentBroker" } + XCTAssertEqual(parentProfile?.dataBroker.optOutUrl, "parent.com/optout") + } + + func testMaintenanceScanState_childBrokerWithParentOptOutUrl() { + // Given + let extractedProfile = ExtractedProfile(id: 1, name: "Sample Name", profileUrl: "profile.com", removedDate: nil) + + let childBroker = BrokerProfileQueryData.mock( + dataBrokerName: "ChildBroker", + url: "child.com", + parentURL: "parent.com", + optOutUrl: "parent.com/optout", + extractedProfile: extractedProfile + ) + + let parentBroker = BrokerProfileQueryData.mock( + dataBrokerName: "ParentBroker", + url: "parent.com", + optOutUrl: "parent.com/optout", + extractedProfile: extractedProfile + ) + + // When + let state = sut.maintenanceScanState([childBroker, parentBroker]) + + // Then + XCTAssertEqual(state.inProgressOptOuts.count, 2) + XCTAssertEqual(state.completedOptOuts.count, 0) + + let childProfile = state.inProgressOptOuts.first { $0.dataBroker.name == "ChildBroker" } + XCTAssertEqual(childProfile?.dataBroker.optOutUrl, "parent.com/optout") + + let parentProfile = state.inProgressOptOuts.first { $0.dataBroker.name == "ParentBroker" } + XCTAssertEqual(childProfile?.dataBroker.optOutUrl, "parent.com/optout") + } } extension DBPUIScanProgress.ScannedBroker { diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MismatchCalculatorUseCaseTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MismatchCalculatorUseCaseTests.swift index fb2af7862b..06ef01eae4 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MismatchCalculatorUseCaseTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MismatchCalculatorUseCaseTests.swift @@ -155,7 +155,8 @@ extension BrokerProfileQueryData { url: "parent.com", steps: [Step](), version: "1.0.0", - schedulingConfig: DataBrokerScheduleConfig.mock + schedulingConfig: DataBrokerScheduleConfig.mock, + optOutUrl: "" ), profileQuery: ProfileQuery(firstName: "John", lastName: "Doe", city: "Miami", state: "FL", birthYear: 50), scanJobData: ScanJobData(brokerId: 1, profileQueryId: 1, historyEvents: historyEvents) @@ -170,7 +171,8 @@ extension BrokerProfileQueryData { steps: [Step](), version: "1.0.0", schedulingConfig: DataBrokerScheduleConfig.mock, - parent: "parent.com" + parent: "parent.com", + optOutUrl: "" ), profileQuery: ProfileQuery(firstName: "John", lastName: "Doe", city: "Miami", state: "FL", birthYear: 50), scanJobData: ScanJobData(brokerId: 2, profileQueryId: 1, historyEvents: historyEvents) diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift index 3647654770..e9c98d5773 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift @@ -33,6 +33,7 @@ extension BrokerProfileQueryData { dataBrokerName: String = "test", url: String = "test.com", parentURL: String? = nil, + optOutUrl: String? = nil, lastRunDate: Date? = nil, preferredRunDate: Date? = nil, extractedProfile: ExtractedProfile? = nil, @@ -48,7 +49,8 @@ extension BrokerProfileQueryData { version: "1.0.0", schedulingConfig: DataBrokerScheduleConfig.mock, parent: parentURL, - mirrorSites: mirrorSites + mirrorSites: mirrorSites, + optOutUrl: optOutUrl ?? "" ), profileQuery: ProfileQuery(firstName: "John", lastName: "Doe", city: "Miami", state: "FL", birthYear: 50, deprecated: deprecated), scanJobData: ScanJobData(brokerId: 1, @@ -623,9 +625,9 @@ final class DataBrokerProtectionSecureVaultMock: DataBrokerProtectionSecureVault func fetchBroker(with name: String) throws -> DataBroker? { if shouldReturnOldVersionBroker { - return .init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.0", schedulingConfig: .mock) + return .init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.0", schedulingConfig: .mock, optOutUrl: "") } else if shouldReturnNewVersionBroker { - return .init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.1", schedulingConfig: .mock) + return .init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.1", schedulingConfig: .mock, optOutUrl: "") } return nil @@ -1231,7 +1233,8 @@ extension DataBroker { retryError: 0, confirmOptOutScan: 0, maintenanceScan: 0 - ) + ), + optOutUrl: "" ) } } diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateUpdaterTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateUpdaterTests.swift index e7fdb4cb45..76d2e4bb65 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateUpdaterTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateUpdaterTests.swift @@ -42,7 +42,8 @@ final class OperationPreferredDateUpdaterTests: XCTestCase { retryError: 1, confirmOptOutScan: confirmOptOutScanHours, maintenanceScan: 1 - ) + ), + optOutUrl: "" ) databaseMock.childBrokers = [childBroker]