From 2aaaa7b8a7666fee27a7ae4a44ca47064d20400e Mon Sep 17 00:00:00 2001 From: Kamil Berdychowski Date: Mon, 29 Aug 2022 14:07:18 +0200 Subject: [PATCH 1/2] feat: Added support for Modifiable Retention Policies --- Sources/Modules/RetentionPolicyModule.swift | 10 ++++-- Sources/Responses/RetentionPolicy.swift | 31 +++++++++++++++++++ Sources/Responses/RetentionPolicyEntry.swift | 3 ++ .../Modules/RetentionPolicyModuleSpecs.swift | 15 ++++++--- .../RetentionPolicy/GetRetentionPolicies.json | 3 +- .../RetentionPolicy/GetRetentionPolicy.json | 3 +- 6 files changed, 57 insertions(+), 8 deletions(-) diff --git a/Sources/Modules/RetentionPolicyModule.swift b/Sources/Modules/RetentionPolicyModule.swift index 05df43083..2b4056297 100644 --- a/Sources/Modules/RetentionPolicyModule.swift +++ b/Sources/Modules/RetentionPolicyModule.swift @@ -49,6 +49,7 @@ public class RetentionPoliciesModule { /// - areOwnersNotified: The Owner or Co-owner will get notified when a file is nearing expiration. /// - customNotificationRecipients: Notified users. /// - completion: Returns either standard RetentionPolicy object or an error. + /// - retentionType: Specifies the retention type which can be `modifiable` or `non-modifiable` public func create( name: String, type: RetentionPolicyType, @@ -57,12 +58,14 @@ public class RetentionPoliciesModule { canOwnerExtendRetention: Bool? = nil, areOwnersNotified: Bool? = nil, customNotificationRecipients: [User]? = nil, - completion: @escaping Callback + completion: @escaping Callback, + retentionType: RetentionType? = nil ) { var body: [String: Any] = [:] body["policy_name"] = name body["policy_type"] = type.description + body["retention_type"] = retentionType?.description body["retention_length"] = length body["disposition_action"] = dispositionAction.description body["can_owner_extend_retention"] = canOwnerExtendRetention @@ -92,18 +95,21 @@ public class RetentionPoliciesModule { /// For indefinite policies, disposition action must be remove_retention. /// - status: Used to `retire` a retention policy if status is set to `retired`. If not retiring a policy, do not include or set to null. /// - completion: Returns either updated retention policy object or an error. + /// - retentionType: Specifies the retention type which can be `modifiable` or `non-modifiable` public func update( policyId id: String, name: String? = nil, dispositionAction: DispositionAction? = nil, status: RetentionPolicyStatus? = nil, - completion: @escaping Callback + completion: @escaping Callback, + retentionType: RetentionType? = nil ) { var body: [String: Any] = [:] body["policy_name"] = name body["disposition_action"] = dispositionAction?.description body["status"] = status?.description + body["retention_type"] = retentionType?.description boxClient.put( url: URL.boxAPIEndpoint("/2.0/retention_policies/\(id)", configuration: boxClient.configuration), diff --git a/Sources/Responses/RetentionPolicy.swift b/Sources/Responses/RetentionPolicy.swift index a8d82cb63..fb48f1b30 100644 --- a/Sources/Responses/RetentionPolicy.swift +++ b/Sources/Responses/RetentionPolicy.swift @@ -105,6 +105,34 @@ public enum RetentionPolicyStatus: BoxEnum { } } +public enum RetentionType: BoxEnum { + case modifiable + case nonModifiable + case customValue(String) + + public init(_ value: String) { + switch value { + case "modifiable": + self = .modifiable + case "non_modifiable": + self = .nonModifiable + default: + self = .customValue(value) + } + } + + public var description: String { + switch self { + case .modifiable: + return "modifiable" + case .nonModifiable: + return "non_modifiable" + case let .customValue(value): + return value + } + } +} + /// A retention policy blocks permanent deletion of content for a specified amount of time. public class RetentionPolicy: BoxModel { @@ -139,6 +167,8 @@ public class RetentionPolicy: BoxModel { public let areOwnersNotified: Bool? /// Other users notified about retention policy changes. public let customNotificationRecipients: [User]? + /// Specifies the retention type which can be `modifiable` or `non-modifiable` + public let retentionType: RetentionType? public required init(json: [String: Any]) throws { guard let itemType = json["type"] as? String else { @@ -164,5 +194,6 @@ public class RetentionPolicy: BoxModel { canOwnerExtendRetention = try BoxJSONDecoder.optionalDecode(json: json, forKey: "can_owner_extend_retention") areOwnersNotified = try BoxJSONDecoder.optionalDecode(json: json, forKey: "are_owners_notified") customNotificationRecipients = try BoxJSONDecoder.optionalDecodeCollection(json: json, forKey: "custom_notification_recipients") + retentionType = try BoxJSONDecoder.optionalDecodeEnum(json: json, forKey: "retention_type") } } diff --git a/Sources/Responses/RetentionPolicyEntry.swift b/Sources/Responses/RetentionPolicyEntry.swift index f6787fc0c..a70392c5a 100644 --- a/Sources/Responses/RetentionPolicyEntry.swift +++ b/Sources/Responses/RetentionPolicyEntry.swift @@ -19,6 +19,8 @@ public class RetentionPolicyEntry: BoxModel { public let id: String /// The name of the retention policy. public let name: String? + /// Specifies the retention type which can be `modifiable` or `non-modifiable` + public let retentionType: RetentionType? public required init(json: [String: Any]) throws { guard let itemType = json["type"] as? String else { @@ -34,5 +36,6 @@ public class RetentionPolicyEntry: BoxModel { id = try BoxJSONDecoder.decode(json: json, forKey: "id") name = try BoxJSONDecoder.optionalDecode(json: json, forKey: "name") + retentionType = try BoxJSONDecoder.optionalDecodeEnum(json: json, forKey: "retention_type") } } diff --git a/Tests/Modules/RetentionPolicyModuleSpecs.swift b/Tests/Modules/RetentionPolicyModuleSpecs.swift index f9d5bec92..6ab27fc87 100644 --- a/Tests/Modules/RetentionPolicyModuleSpecs.swift +++ b/Tests/Modules/RetentionPolicyModuleSpecs.swift @@ -53,6 +53,7 @@ class RetentionPolicyModuleSpecs: QuickSpec { expect(retentionPolicy.customNotificationRecipients?.count).to(equal(2)) expect(retentionPolicy.customNotificationRecipients?.first?.id).to(equal("960")) expect(retentionPolicy.createdBy?.id).to(equal("958")) + expect(retentionPolicy.retentionType).to(equal(.nonModifiable)) case let .failure(error): fail("Expected call to getRetentionPolicy to succeed, but instead got \(error)") } @@ -82,7 +83,8 @@ class RetentionPolicyModuleSpecs: QuickSpec { "disposition_action": "remove_retention", "can_owner_extend_retention": false, "are_owners_notified": true, - "custom_notification_recipients": [user.rawData] + "custom_notification_recipients": [user.rawData], + "retention_type": "modifiable" ]) ) { _ in OHHTTPStubsResponse( @@ -98,7 +100,8 @@ class RetentionPolicyModuleSpecs: QuickSpec { dispositionAction: .removeRetention, canOwnerExtendRetention: false, areOwnersNotified: true, - customNotificationRecipients: [user] + customNotificationRecipients: [user], + retentionType: .modifiable ) { result in switch result { case let .success(retentionPolicy): @@ -120,6 +123,7 @@ class RetentionPolicyModuleSpecs: QuickSpec { } } } + // TODO: czy musze testować że opcjonalne parametry się są wysyłane? } describe("update()") { @@ -132,7 +136,8 @@ class RetentionPolicyModuleSpecs: QuickSpec { hasJsonBody([ "policy_name": "Tax Documents", "disposition_action": "remove_retention", - "status": "active" + "status": "active", + "retention_type": "non_modifiable" ]) ) { _ in OHHTTPStubsResponse( @@ -145,7 +150,8 @@ class RetentionPolicyModuleSpecs: QuickSpec { policyId: id, name: "Tax Documents", dispositionAction: .removeRetention, - status: .active + status: .active, + retentionType: .nonModifiable ) { result in switch result { case let .success(retentionPolicy): @@ -189,6 +195,7 @@ class RetentionPolicyModuleSpecs: QuickSpec { let retentionPolicy = page.entries[0] expect(retentionPolicy.name).to(equal("Tax Documents")) expect(retentionPolicy.id).to(equal("123456789")) + expect(retentionPolicy.retentionType).to(equal(.nonModifiable)) case let .failure(error): fail("Expected call to list to succeed, but instead got \(error)") } diff --git a/Tests/Stubs/Resources/RetentionPolicy/GetRetentionPolicies.json b/Tests/Stubs/Resources/RetentionPolicy/GetRetentionPolicies.json index 7124b2ad2..f07ed1b76 100644 --- a/Tests/Stubs/Resources/RetentionPolicy/GetRetentionPolicies.json +++ b/Tests/Stubs/Resources/RetentionPolicy/GetRetentionPolicies.json @@ -3,7 +3,8 @@ { "type": "retention_policy", "id": "123456789", - "name": "Tax Documents" + "name": "Tax Documents", + "retention_type": "non_modifiable" } ], "limit": 100, diff --git a/Tests/Stubs/Resources/RetentionPolicy/GetRetentionPolicy.json b/Tests/Stubs/Resources/RetentionPolicy/GetRetentionPolicy.json index 047ac2d2a..9038a77b0 100644 --- a/Tests/Stubs/Resources/RetentionPolicy/GetRetentionPolicy.json +++ b/Tests/Stubs/Resources/RetentionPolicy/GetRetentionPolicy.json @@ -29,5 +29,6 @@ "id": "958", "name": "Unit Test User File Owner 1", "login": "boxautomatedqa+fileowner1@gmail.com" - } + }, + "retention_type": "non_modifiable" } From 625492bc22486497cdfc8070012e3941c07cfe43 Mon Sep 17 00:00:00 2001 From: Kamil Berdychowski Date: Tue, 30 Aug 2022 13:26:36 +0200 Subject: [PATCH 2/2] chore: added more tests --- Sources/Modules/RetentionPolicyModule.swift | 16 +++---- Sources/Responses/RetentionPolicy.swift | 15 +++++++ Sources/Responses/RetentionPolicyEntry.swift | 5 +-- .../Modules/RetentionPolicyModuleSpecs.swift | 42 ++++++++++++++++--- Tests/Responses/RetentionPolicySpecs.swift | 12 ++++++ .../CreateRetentionPolicy.json | 3 +- .../RetentionPolicy/GetRetentionPolicies.json | 5 ++- .../UpdateRetentionPolicy.json | 3 +- 8 files changed, 80 insertions(+), 21 deletions(-) diff --git a/Sources/Modules/RetentionPolicyModule.swift b/Sources/Modules/RetentionPolicyModule.swift index 2b4056297..2abccf4ab 100644 --- a/Sources/Modules/RetentionPolicyModule.swift +++ b/Sources/Modules/RetentionPolicyModule.swift @@ -48,8 +48,8 @@ public class RetentionPoliciesModule { /// - canOwnerExtendRetention: The Owner of a file will be allowed to extend the retention. /// - areOwnersNotified: The Owner or Co-owner will get notified when a file is nearing expiration. /// - customNotificationRecipients: Notified users. + /// - retentionType: Specifies the retention type which can be `modifiable` or `non-modifiable`. /// - completion: Returns either standard RetentionPolicy object or an error. - /// - retentionType: Specifies the retention type which can be `modifiable` or `non-modifiable` public func create( name: String, type: RetentionPolicyType, @@ -58,8 +58,8 @@ public class RetentionPoliciesModule { canOwnerExtendRetention: Bool? = nil, areOwnersNotified: Bool? = nil, customNotificationRecipients: [User]? = nil, - completion: @escaping Callback, - retentionType: RetentionType? = nil + retentionType: RetentionType? = nil, + completion: @escaping Callback ) { var body: [String: Any] = [:] @@ -94,22 +94,24 @@ public class RetentionPoliciesModule { /// - dispositionAction: If updating a `finite` policy, the disposition action can be `permanently_delete` or `remove_retention`. /// For indefinite policies, disposition action must be remove_retention. /// - status: Used to `retire` a retention policy if status is set to `retired`. If not retiring a policy, do not include or set to null. + /// - setRetentionTypeToNonModifiable: If value is `false` retention type is not changed. If value is true retention type is changed to `non_modifiable`. /// - completion: Returns either updated retention policy object or an error. - /// - retentionType: Specifies the retention type which can be `modifiable` or `non-modifiable` public func update( policyId id: String, name: String? = nil, dispositionAction: DispositionAction? = nil, status: RetentionPolicyStatus? = nil, - completion: @escaping Callback, - retentionType: RetentionType? = nil + setRetentionTypeToNonModifiable: Bool = false, + completion: @escaping Callback ) { var body: [String: Any] = [:] body["policy_name"] = name body["disposition_action"] = dispositionAction?.description body["status"] = status?.description - body["retention_type"] = retentionType?.description + if setRetentionTypeToNonModifiable { + body["retention_type"] = RetentionType.nonModifiable.description + } boxClient.put( url: URL.boxAPIEndpoint("/2.0/retention_policies/\(id)", configuration: boxClient.configuration), diff --git a/Sources/Responses/RetentionPolicy.swift b/Sources/Responses/RetentionPolicy.swift index fb48f1b30..27bb73dbb 100644 --- a/Sources/Responses/RetentionPolicy.swift +++ b/Sources/Responses/RetentionPolicy.swift @@ -17,6 +17,9 @@ public enum RetentionPolicyType: BoxEnum { /// Custom value not yet implemented in this SDK version. case customValue(String) + /// Creates a new value + /// + /// - Parameter value: String representation of a RetentionPolicyType rawValue public init(_ value: String) { switch value { case "finite": @@ -28,6 +31,7 @@ public enum RetentionPolicyType: BoxEnum { } } + /// Returns string representation of RetentionPolicyType public var description: String { switch self { case .finite: @@ -105,11 +109,21 @@ public enum RetentionPolicyStatus: BoxEnum { } } +/// Specifies the retention type public enum RetentionType: BoxEnum { + /// You can modify the retention policy. For example, you can add or remove folders, shorten or lengthen the policy duration, or delete the assignment. + /// Use this type if your retention policy is not related to any regulatory purposes. case modifiable + /// You can modify the retention policy only in a limited way: add a folder, lengthen the duration, retire the policy, change the disposition action or notification settings. + /// You cannot perform other actions, such as deleting the assignment or shortening the policy duration. + /// Use this type to ensure compliance with regulatory retention policies. case nonModifiable + /// Custom value that was not yet implemented in current SDK version. case customValue(String) + /// Creates a new value + /// + /// - Parameter value: String representation of a RetentionType rawValue public init(_ value: String) { switch value { case "modifiable": @@ -121,6 +135,7 @@ public enum RetentionType: BoxEnum { } } + /// Returns string representation of RetentionType public var description: String { switch self { case .modifiable: diff --git a/Sources/Responses/RetentionPolicyEntry.swift b/Sources/Responses/RetentionPolicyEntry.swift index a70392c5a..3edc08661 100644 --- a/Sources/Responses/RetentionPolicyEntry.swift +++ b/Sources/Responses/RetentionPolicyEntry.swift @@ -19,8 +19,6 @@ public class RetentionPolicyEntry: BoxModel { public let id: String /// The name of the retention policy. public let name: String? - /// Specifies the retention type which can be `modifiable` or `non-modifiable` - public let retentionType: RetentionType? public required init(json: [String: Any]) throws { guard let itemType = json["type"] as? String else { @@ -35,7 +33,6 @@ public class RetentionPolicyEntry: BoxModel { type = itemType id = try BoxJSONDecoder.decode(json: json, forKey: "id") - name = try BoxJSONDecoder.optionalDecode(json: json, forKey: "name") - retentionType = try BoxJSONDecoder.optionalDecodeEnum(json: json, forKey: "retention_type") + name = try BoxJSONDecoder.optionalDecode(json: json, forKey: "policy_name") } } diff --git a/Tests/Modules/RetentionPolicyModuleSpecs.swift b/Tests/Modules/RetentionPolicyModuleSpecs.swift index 6ab27fc87..e0e8019e0 100644 --- a/Tests/Modules/RetentionPolicyModuleSpecs.swift +++ b/Tests/Modules/RetentionPolicyModuleSpecs.swift @@ -116,6 +116,7 @@ class RetentionPolicyModuleSpecs: QuickSpec { expect(retentionPolicy.customNotificationRecipients?.count).to(equal(1)) expect(retentionPolicy.customNotificationRecipients?.first?.id).to(equal("22222")) expect(retentionPolicy.createdBy?.id).to(equal("33333")) + expect(retentionPolicy.retentionType).to(equal(.modifiable)) case let .failure(error): fail("Expected call to create to succeed, but instead got \(error)") } @@ -123,7 +124,6 @@ class RetentionPolicyModuleSpecs: QuickSpec { } } } - // TODO: czy musze testować że opcjonalne parametry się są wysyłane? } describe("update()") { @@ -136,8 +136,7 @@ class RetentionPolicyModuleSpecs: QuickSpec { hasJsonBody([ "policy_name": "Tax Documents", "disposition_action": "remove_retention", - "status": "active", - "retention_type": "non_modifiable" + "status": "active" ]) ) { _ in OHHTTPStubsResponse( @@ -150,8 +149,7 @@ class RetentionPolicyModuleSpecs: QuickSpec { policyId: id, name: "Tax Documents", dispositionAction: .removeRetention, - status: .active, - retentionType: .nonModifiable + status: .active ) { result in switch result { case let .success(retentionPolicy): @@ -162,6 +160,38 @@ class RetentionPolicyModuleSpecs: QuickSpec { expect(retentionPolicy.dispositionAction).to(equal(.removeRetention)) expect(retentionPolicy.status).to(equal(.active)) expect(retentionPolicy.createdBy?.id).to(equal("22222")) + expect(retentionPolicy.retentionType).to(equal(.modifiable)) + case let .failure(error): + fail("Expected call to updateto succeed, but instead got \(error)") + } + done() + } + } + } + + it("should update retention type to non_modifiable") { + let id = "123456789" + stub( + condition: isHost("api.box.com") && + isPath("/2.0/retention_policies/\(id)") && + isMethodPUT() && + hasJsonBody([ + "retention_type": "non_modifiable" + ]) + ) { _ in + OHHTTPStubsResponse( + fileAtPath: OHPathForFile("UpdateRetentionPolicy.json", type(of: self))!, + statusCode: 200, headers: ["Content-Type": "application/json"] + ) + } + waitUntil(timeout: .seconds(10)) { done in + self.sut.retentionPolicy.update( + policyId: id, + setRetentionTypeToNonModifiable: true + ) { result in + switch result { + case let .success(retentionPolicy): + expect(retentionPolicy.id).to(equal(id)) case let .failure(error): fail("Expected call to updateto succeed, but instead got \(error)") } @@ -176,6 +206,7 @@ class RetentionPolicyModuleSpecs: QuickSpec { stub( condition: isHost("api.box.com") && isPath("/2.0/retention_policies") && + containsQueryParams(["policy_name": "name", "policy_type": "finite", "created_by_user_id": "1234"]) && isMethodGET() ) { _ in OHHTTPStubsResponse( @@ -195,7 +226,6 @@ class RetentionPolicyModuleSpecs: QuickSpec { let retentionPolicy = page.entries[0] expect(retentionPolicy.name).to(equal("Tax Documents")) expect(retentionPolicy.id).to(equal("123456789")) - expect(retentionPolicy.retentionType).to(equal(.nonModifiable)) case let .failure(error): fail("Expected call to list to succeed, but instead got \(error)") } diff --git a/Tests/Responses/RetentionPolicySpecs.swift b/Tests/Responses/RetentionPolicySpecs.swift index 3ba91aded..28b3dcbcc 100644 --- a/Tests/Responses/RetentionPolicySpecs.swift +++ b/Tests/Responses/RetentionPolicySpecs.swift @@ -54,5 +54,17 @@ class RetentionPolicySpecs: QuickSpec { } } } + + describe("RetentionType") { + + describe("init()") { + + it("should correctly create an enum value from it's string representation") { + expect(RetentionType.modifiable).to(equal(RetentionType(RetentionType.modifiable.description))) + expect(RetentionType.nonModifiable).to(equal(RetentionType(RetentionType.nonModifiable.description))) + expect(RetentionType.customValue("custom value")).to(equal(RetentionType("custom value"))) + } + } + } } } diff --git a/Tests/Stubs/Resources/RetentionPolicy/CreateRetentionPolicy.json b/Tests/Stubs/Resources/RetentionPolicy/CreateRetentionPolicy.json index cbe14bdf8..d3a571b8b 100644 --- a/Tests/Stubs/Resources/RetentionPolicy/CreateRetentionPolicy.json +++ b/Tests/Stubs/Resources/RetentionPolicy/CreateRetentionPolicy.json @@ -23,5 +23,6 @@ "login": "creator@example.com" }, "created_at": "2015-05-01T11:12:54-07:00", - "modified_at": "2015-06-08T11:11:50-07:00" + "modified_at": "2015-06-08T11:11:50-07:00", + "retention_type": "modifiable" } diff --git a/Tests/Stubs/Resources/RetentionPolicy/GetRetentionPolicies.json b/Tests/Stubs/Resources/RetentionPolicy/GetRetentionPolicies.json index f07ed1b76..1b510843c 100644 --- a/Tests/Stubs/Resources/RetentionPolicy/GetRetentionPolicies.json +++ b/Tests/Stubs/Resources/RetentionPolicy/GetRetentionPolicies.json @@ -3,8 +3,9 @@ { "type": "retention_policy", "id": "123456789", - "name": "Tax Documents", - "retention_type": "non_modifiable" + "policy_name": "Tax Documents", + "disposition_action": "permanently_delete", + "retention_length": "365" } ], "limit": 100, diff --git a/Tests/Stubs/Resources/RetentionPolicy/UpdateRetentionPolicy.json b/Tests/Stubs/Resources/RetentionPolicy/UpdateRetentionPolicy.json index c794b48fa..bb0f0284e 100644 --- a/Tests/Stubs/Resources/RetentionPolicy/UpdateRetentionPolicy.json +++ b/Tests/Stubs/Resources/RetentionPolicy/UpdateRetentionPolicy.json @@ -13,5 +13,6 @@ "login": "user@example.com" }, "created_at": "2015-05-01T11:12:54-07:00", - "modified_at": "2015-06-08T11:11:50-07:00" + "modified_at": "2015-06-08T11:11:50-07:00", + "retention_type": "modifiable" }