From 2b14de1db82f999a44652353295ee8c1203be9a1 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Mon, 28 Oct 2024 18:44:56 +0000 Subject: [PATCH] PIR broker configs: Add sub actions to ExpectationAction (#3452) Task/Issue URL: https://app.asana.com/0/1199230911884351/1208594469701836/f Tech Design URL: CC: **Description**: Add add sub actions to ExpectationAction **Steps to test this PR**: 1. Test PIR still works and check using the debug DB browser that brokers are still read correctly **Definition of Done**: * [ ] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? --- ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --------- Co-authored-by: Brian Hall --- .../Model/Actions/Expectaction.swift | 41 ++++++++++++++++- .../DataBrokerProtection/Model/Step.swift | 44 +++++++++++-------- .../Utils/CodableExtension.swift | 10 +++++ .../DataBrokerOperationActionTests.swift | 4 +- 4 files changed, 77 insertions(+), 22 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Actions/Expectaction.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Actions/Expectaction.swift index d3669a3290..13b03761de 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Actions/Expectaction.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Actions/Expectaction.swift @@ -29,11 +29,50 @@ struct Item: Codable, Sendable { let expect: String? let selector: String? let parent: String? + let failSilently: Bool? } -internal struct ExpectationAction: Action { +internal final class ExpectationAction: Action { let id: String let actionType: ActionType let expectations: [Item] let dataSource: DataSource? + let actions: [Action]? + + enum CodingKeys: String, CodingKey { + case id, actionType, expectations, dataSource, actions + } + + init(id: String, actionType: ActionType, expectations: [Item], dataSource: DataSource?, actions: [Action]?) { + self.id = id + self.actionType = actionType + self.expectations = expectations + self.dataSource = dataSource + self.actions = actions + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.actionType = try container.decode(ActionType.self, forKey: .actionType) + self.expectations = try container.decode([Item].self, forKey: .expectations) + self.dataSource = try container.decodeIfPresent(DataSource.self, forKey: .dataSource) + let actionsList = try container.decodeIfPresent([[String: Any]].self, forKey: .actions) + if let actionsList = actionsList { + self.actions = try Step.parse(actionsList) + } else { + self.actions = nil + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(actionType, forKey: .actionType) + try container.encode(expectations, forKey: .expectations) + try container.encode(dataSource, forKey: .dataSource) + + var actionsContainer = container.nestedUnkeyedContainer(forKey: .actions) + try actions?.encode(to: &actionsContainer) + } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Step.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Step.swift index d1ffc96754..97b13dd9a9 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Step.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Step.swift @@ -58,25 +58,7 @@ struct Step: Codable, Sendable { try container.encode(optOutType, forKey: .optOutType) var actionsContainer = container.nestedUnkeyedContainer(forKey: .actions) - for action in actions { - if let navigateAction = action as? NavigateAction { - try actionsContainer.encode(navigateAction) - } else if let extractAction = action as? ExtractAction { - try actionsContainer.encode(extractAction) - } else if let fillFormAction = action as? FillFormAction { - try actionsContainer.encode(fillFormAction) - } else if let getCaptchaInfoAction = action as? GetCaptchaInfoAction { - try actionsContainer.encode(getCaptchaInfoAction) - } else if let solveCaptchaInfoAction = action as? SolveCaptchaAction { - try actionsContainer.encode(solveCaptchaInfoAction) - } else if let emailConfirmationAction = action as? EmailConfirmationAction { - try actionsContainer.encode(emailConfirmationAction) - } else if let clickAction = action as? ClickAction { - try actionsContainer.encode(clickAction) - } else if let expectactionAction = action as? ExpectationAction { - try actionsContainer.encode(expectactionAction) - } - } + try actions.encode(to: &actionsContainer) } static func parse(_ actions: [[String: Any]]) throws -> [Action] { @@ -120,3 +102,27 @@ struct Step: Codable, Sendable { return actionList } } + +extension Array where Element == Action { + func encode(to container: inout any UnkeyedEncodingContainer) throws { + for action in self { + if let navigateAction = action as? NavigateAction { + try container.encode(navigateAction) + } else if let extractAction = action as? ExtractAction { + try container.encode(extractAction) + } else if let fillFormAction = action as? FillFormAction { + try container.encode(fillFormAction) + } else if let getCaptchaInfoAction = action as? GetCaptchaInfoAction { + try container.encode(getCaptchaInfoAction) + } else if let solveCaptchaInfoAction = action as? SolveCaptchaAction { + try container.encode(solveCaptchaInfoAction) + } else if let emailConfirmationAction = action as? EmailConfirmationAction { + try container.encode(emailConfirmationAction) + } else if let clickAction = action as? ClickAction { + try container.encode(clickAction) + } else if let expectactionAction = action as? ExpectationAction { + try container.encode(expectactionAction) + } + } + } +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/CodableExtension.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/CodableExtension.swift index 46cc1e4a56..4106124961 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/CodableExtension.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/CodableExtension.swift @@ -59,6 +59,16 @@ extension KeyedDecodingContainer { return try decode(type, forKey: key) } + func decodeIfPresent(_ type: [[String: Any]].Type, forKey key: K) throws -> [[String: Any]]? { + guard contains(key) else { + return nil + } + guard try decodeNil(forKey: key) == false else { + return nil + } + return try decode(type, forKey: key) + } + func decode(_ type: [Any].Type, forKey key: K) throws -> [Any] { var container = try self.nestedUnkeyedContainer(forKey: key) return try container.decode(type) diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerOperationActionTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerOperationActionTests.swift index 330619c12d..cc14f56b56 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerOperationActionTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerOperationActionTests.swift @@ -331,7 +331,7 @@ final class DataBrokerOperationActionTests: XCTestCase { } func testWhenRunningActionWithoutExtractedProfile_thenExecuteIsCalledWithProfileData() async { - let expectationAction = ExpectationAction(id: "1", actionType: .expectation, expectations: [Item](), dataSource: nil) + let expectationAction = ExpectationAction(id: "1", actionType: .expectation, expectations: [Item](), dataSource: nil, actions: nil) let sut = OptOutJob( privacyConfig: PrivacyConfigurationManagingMock(), prefs: ContentScopeProperties.mock, @@ -415,7 +415,7 @@ final class DataBrokerOperationActionTests: XCTestCase { func testWhenExpectationActionRuns_thenStageIsSetToSubmit() async { let mockStageCalculator = MockStageDurationCalculator() - let expectationAction = ExpectationAction(id: "1", actionType: .expectation, expectations: [Item](), dataSource: nil) + let expectationAction = ExpectationAction(id: "1", actionType: .expectation, expectations: [Item](), dataSource: nil, actions: nil) let sut = OptOutJob( privacyConfig: PrivacyConfigurationManagingMock(), prefs: ContentScopeProperties.mock,