From 759c5ff465e355498ea48f3d2db9c9b8678933ee Mon Sep 17 00:00:00 2001 From: Decheng Date: Thu, 11 Jun 2020 17:35:28 +1000 Subject: [PATCH 01/29] Point SDK source back to AvdLee Master --- Package.resolved | 4 ++-- Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.resolved b/Package.resolved index d0206c97..3475e08d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -3,10 +3,10 @@ "pins": [ { "package": "AppStoreConnect-Swift-SDK", - "repositoryURL": "https://github.com/ittybittyapps/appstoreconnect-swift-sdk.git", + "repositoryURL": "https://github.com/AvdLee/appstoreconnect-swift-sdk.git", "state": { "branch": "master", - "revision": "e3a5e2b820f88b8e4236257fcae03c890b6362eb", + "revision": "1d1887cd6b5f6312e4ba999e888619fe1845d0a9", "version": null } }, diff --git a/Package.swift b/Package.swift index f89119a3..18e8e694 100644 --- a/Package.swift +++ b/Package.swift @@ -24,7 +24,7 @@ let package = Package( from: "0.0.2" ), .package( - url: "https://github.com/ittybittyapps/appstoreconnect-swift-sdk.git", + url: "https://github.com/AvdLee/appstoreconnect-swift-sdk.git", .branch("master") ), .package( From 7dc4c567c6d9fd5229c8418ae0aa9b6b45a82b5e Mon Sep 17 00:00:00 2001 From: Decheng Date: Thu, 11 Jun 2020 17:37:46 +1000 Subject: [PATCH 02/29] update fileContent() -> Data to return FileContent --- .../Certificates/CertificateProcessor.swift | 4 ++-- Sources/FileSystem/FileProvider.swift | 18 ++++++++++++++++++ .../FileSystem/Profile/ProfileProcessor.swift | 4 ++-- 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 Sources/FileSystem/FileProvider.swift diff --git a/Sources/FileSystem/Certificates/CertificateProcessor.swift b/Sources/FileSystem/Certificates/CertificateProcessor.swift index 89d9e92f..e52d7b64 100644 --- a/Sources/FileSystem/Certificates/CertificateProcessor.swift +++ b/Sources/FileSystem/Certificates/CertificateProcessor.swift @@ -36,7 +36,7 @@ extension Certificate: FileProvider { } } - func fileContent() throws -> Data { + func fileContent() throws -> FileContent { guard let content = content, let data = Data(base64Encoded: content) @@ -44,7 +44,7 @@ extension Certificate: FileProvider { throw Error.noContent } - return data + return .data(data) } var fileName: String { diff --git a/Sources/FileSystem/FileProvider.swift b/Sources/FileSystem/FileProvider.swift new file mode 100644 index 00000000..a2b085f2 --- /dev/null +++ b/Sources/FileSystem/FileProvider.swift @@ -0,0 +1,18 @@ +// Copyright 2020 Itty Bitty Apps Pty Ltd + +import Foundation + +protocol FileProvider: FileNameProvider, FileContentProvider { } + +protocol FileNameProvider { + var fileName: String { get } +} + +enum FileContent { + case data(Data) + case string(String) +} + +protocol FileContentProvider { + func fileContent() throws -> FileContent +} diff --git a/Sources/FileSystem/Profile/ProfileProcessor.swift b/Sources/FileSystem/Profile/ProfileProcessor.swift index 310dcc8c..eb34912c 100644 --- a/Sources/FileSystem/Profile/ProfileProcessor.swift +++ b/Sources/FileSystem/Profile/ProfileProcessor.swift @@ -38,14 +38,14 @@ extension Profile: FileProvider { } } - func fileContent() throws -> Data { + func fileContent() throws -> FileContent { guard let content = profileContent, let data = Data(base64Encoded: content) else { throw Error.noContent } - return data + return .data(data) } var fileName: String { From de1c76cefad75a99ec23699fea58f9a4ff75fa1f Mon Sep 17 00:00:00 2001 From: Decheng Date: Thu, 11 Jun 2020 17:40:53 +1000 Subject: [PATCH 03/29] Add id to beta group, make group name non-nil --- .../AppStoreConnectCLI/Model/BetaGroup.swift | 22 ++++++++++++++++--- Sources/Model/BetaGroup.swift | 12 +++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Sources/AppStoreConnectCLI/Model/BetaGroup.swift b/Sources/AppStoreConnectCLI/Model/BetaGroup.swift index 979f1982..935b23ba 100755 --- a/Sources/AppStoreConnectCLI/Model/BetaGroup.swift +++ b/Sources/AppStoreConnectCLI/Model/BetaGroup.swift @@ -29,7 +29,7 @@ extension BetaGroup: TableInfoProvider, ResultRenderable { app.id, app.bundleId ?? "", app.name ?? "", - groupName ?? "", + groupName, isInternal ?? "", publicLink ?? "", publicLinkEnabled ?? "", @@ -41,13 +41,29 @@ extension BetaGroup: TableInfoProvider, ResultRenderable { } extension BetaGroup { + enum Error: LocalizedError { + case invalidName + + var errorDescription: String? { + switch self { + case .invalidName: + return "Beta group doesn't have a valid group name." + } + } + } + init( _ apiApp: AppStoreConnect_Swift_SDK.App, _ apiBetaGroup: AppStoreConnect_Swift_SDK.BetaGroup - ) { + ) throws { + guard let groupName = apiBetaGroup.attributes?.name else { + throw Error.invalidName + } + self.init( app: App(apiApp), - groupName: apiBetaGroup.attributes?.name, + id: apiBetaGroup.id, + groupName: groupName, isInternal: apiBetaGroup.attributes?.isInternalGroup, publicLink: apiBetaGroup.attributes?.publicLink, publicLinkEnabled: apiBetaGroup.attributes?.publicLinkEnabled, diff --git a/Sources/Model/BetaGroup.swift b/Sources/Model/BetaGroup.swift index 13cfc074..d92f55d8 100644 --- a/Sources/Model/BetaGroup.swift +++ b/Sources/Model/BetaGroup.swift @@ -4,25 +4,30 @@ import Foundation public struct BetaGroup: Codable, Equatable { public let app: App - public let groupName: String? + public let id: String? + public let groupName: String public let isInternal: Bool? public let publicLink: String? public let publicLinkEnabled: Bool? public let publicLinkLimit: Int? public let publicLinkLimitEnabled: Bool? public let creationDate: String? + public var testers: String? // tester csv file path public init( app: App, - groupName: String?, + id: String?, + groupName: String, isInternal: Bool?, publicLink: String?, publicLinkEnabled: Bool?, publicLinkLimit: Int?, publicLinkLimitEnabled: Bool?, - creationDate: String? + creationDate: String?, + testers: String? = nil ) { self.app = app + self.id = id self.groupName = groupName self.isInternal = isInternal self.publicLink = publicLink @@ -30,5 +35,6 @@ public struct BetaGroup: Codable, Equatable { self.publicLinkLimit = publicLinkLimit self.publicLinkLimitEnabled = publicLinkLimitEnabled self.creationDate = creationDate + self.testers = testers } } From 3b33c4145901afba1ab3a610f38e6d679d3746c8 Mon Sep 17 00:00:00 2001 From: Decheng Date: Thu, 11 Jun 2020 17:42:04 +1000 Subject: [PATCH 04/29] Add delete to processor with some tweaks, create betagroup processor --- .../Beta Group/BetaGroupProcessor.swift | 44 ++++++++++++++++ Sources/FileSystem/ResourceProcessor.swift | 52 ++++++++++++------- 2 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 Sources/FileSystem/Beta Group/BetaGroupProcessor.swift diff --git a/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift b/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift new file mode 100644 index 00000000..391cf4ae --- /dev/null +++ b/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift @@ -0,0 +1,44 @@ +// Copyright 2020 Itty Bitty Apps Pty Ltd + +import Files +import Foundation +import Model +import Yams + +public struct BetaGroupProcessor: ResourceProcessor { + + public init(path: ResourcePath) { + self.path = path + } + + func write(_: [BetaGroup]) throws -> [File] { + fatalError() + } + + var path: ResourcePath + + func write(_ betaGroup: BetaGroup) throws -> File { + try writeFile(betaGroup) + } + + func read() throws -> [BetaGroup] { + fatalError() + } + + public func write(groupsWithTesters: [(betaGroup: BetaGroup, testers: [BetaTester])]) throws { + deleteFile() + + try groupsWithTesters.map { try write($0.betaGroup) } + } + +} + +extension BetaGroup: FileProvider { + var fileName: String { + "\(app.id)_\(groupName).yml" + } + + func fileContent() throws -> FileContent { + .string(try YAMLEncoder().encode(self)) + } +} diff --git a/Sources/FileSystem/ResourceProcessor.swift b/Sources/FileSystem/ResourceProcessor.swift index db675b8f..c42c1d04 100644 --- a/Sources/FileSystem/ResourceProcessor.swift +++ b/Sources/FileSystem/ResourceProcessor.swift @@ -37,33 +37,47 @@ extension ResourceWriter { func writeFile(_ resource: FileProvider) throws -> File { var file: File + var folder: Folder + var fileName: String switch path { case .file(let path): let standardizedPath = path as NSString - file = try Folder(path: standardizedPath.deletingLastPathComponent) - .createFile( - named: standardizedPath.lastPathComponent, - contents: resource.fileContent() - ) + folder = try Folder(path: "").createSubfolderIfNeeded(at: standardizedPath.deletingLastPathComponent) + fileName = standardizedPath.lastPathComponent case .folder(let folderPath): - file = try Folder(path: folderPath) - .createFile( - named: resource.fileName, - contents: resource.fileContent() - ) + fileName = resource.fileName + folder = try Folder(path: "").createSubfolderIfNeeded(at: folderPath) + } + + switch try resource.fileContent() { + case .data(let data): + file = try folder.createFileIfNeeded(withName: fileName, contents: data) + case .string(let string): + file = try folder.createFileIfNeeded(at: fileName) + try file.write(string) } return file } -} - -protocol FileProvider: FileNameProvider, FileContentProvider { } -protocol FileNameProvider { - var fileName: String { get } -} - -protocol FileContentProvider { - func fileContent() throws -> Data + func deleteFile() { + do { + switch path { + case .file(let filePath): + let standardizedPath = filePath as NSString + try Folder(path: standardizedPath.deletingLastPathComponent) + .files.forEach { + if $0.name == standardizedPath.lastPathComponent { + try $0.delete() + } + } + case .folder(let folderPath): + try Folder(path: folderPath) + .files.forEach { try $0.delete() } + } + } catch { + print("\(error)") + } + } } From 4b9ad36c2477ce61030eae719703e64b2ee05d28 Mon Sep 17 00:00:00 2001 From: Decheng Date: Thu, 11 Jun 2020 17:42:44 +1000 Subject: [PATCH 05/29] Remove not found error in ListBetaTesterOperation. make options optional --- .../Operations/ListBetaTestersOperation.swift | 39 ++++++------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/Sources/AppStoreConnectCLI/Services/Operations/ListBetaTestersOperation.swift b/Sources/AppStoreConnectCLI/Services/Operations/ListBetaTestersOperation.swift index 1f246525..7943901f 100644 --- a/Sources/AppStoreConnectCLI/Services/Operations/ListBetaTestersOperation.swift +++ b/Sources/AppStoreConnectCLI/Services/Operations/ListBetaTestersOperation.swift @@ -7,26 +7,15 @@ import Foundation struct ListBetaTestersOperation: APIOperation { struct Options { - let email: String? - let firstName: String? - let lastName: String? - let inviteType: BetaInviteType? - let appIds: [String]? - let groupIds: [String]? - let sort: ListBetaTesters.Sort? - let limit: Int? - let relatedResourcesLimit: Int? - } - - enum Error: LocalizedError { - case notFound - - var errorDescription: String? { - switch self { - case .notFound: - return "Beta testers with provided filters not found." - } - } + var email: String? + var firstName: String? + var lastName: String? + var inviteType: BetaInviteType? + var appIds: [String]? + var groupIds: [String]? + var sort: ListBetaTesters.Sort? + var limit: Int? + var relatedResourcesLimit: Int? } private let options: Options @@ -107,13 +96,9 @@ struct ListBetaTestersOperation: APIOperation { next: $0 ) } - .tryMap { (responses: [BetaTestersResponse]) throws -> Output in - try responses.flatMap { (response: BetaTestersResponse) -> Output in - guard !response.data.isEmpty else { - throw Error.notFound - } - - return response.data.map { + .map { + $0.flatMap { (response: BetaTestersResponse) -> Output in + response.data.map { .init(betaTester: $0, includes: response.included) } } From aefdb400f2787232e70c0a0ee98a3b10ee79db45 Mon Sep 17 00:00:00 2001 From: Decheng Date: Thu, 11 Jun 2020 17:43:03 +1000 Subject: [PATCH 06/29] Create service function pullBetaGroups --- .../Services/AppStoreConnectService.swift | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift b/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift index cc3085f9..f4d6a00c 100644 --- a/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift +++ b/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift @@ -410,7 +410,7 @@ class AppStoreConnectService { .execute(with: requestor) .await() - return Model.BetaGroup(app, betaGroup) + return try Model.BetaGroup(app, betaGroup) } func createBetaGroup( @@ -432,7 +432,7 @@ class AppStoreConnectService { ) let betaGroupResponse = createBetaGroupOperation.execute(with: requestor) - return try betaGroupResponse.map(Model.BetaGroup.init).await() + return try betaGroupResponse.tryMap(Model.BetaGroup.init).await() } func deleteBetaGroup(appBundleId: String, betaGroupName: String) throws { @@ -507,7 +507,29 @@ class AppStoreConnectService { let modifyBetaGroupOperation = ModifyBetaGroupOperation(options: modifyBetaGroupOptions) let modifiedBetaGroup = try modifyBetaGroupOperation.execute(with: requestor).await() - return Model.BetaGroup(app, modifiedBetaGroup) + return try Model.BetaGroup(app, modifiedBetaGroup) + } + + func pullBetaGroups() throws -> [(betaGroup: Model.BetaGroup, testers: [Model.BetaTester])] { + let groupOutputs = try ListBetaGroupsOperation(options: .init(appIds: [], names: [], sort: nil)).execute(with: requestor).await() + + return try groupOutputs.map { + let testers = try ListBetaTestersOperation( + options: .init(groupIds: [$0.betaGroup.id]) + ) + .execute(with: requestor) + .await() + .map(BetaTester.init) + + return (try BetaGroup($0.app, $0.betaGroup), testers) + } + } + + func updateBetaGroup(betaGroup: Model.BetaGroup) throws { + // TODO +// _ = try UpdateBetaGroupOperation(options: .init(betaGroup: betaGroup)) +// .execute(with: requestor) +// .await() } func readBuild(bundleId: String, buildNumber: String, preReleaseVersion: String) throws -> Model.Build { From ca6d9f58a8c0b8e5c11bd33e734fe575bb66a10b Mon Sep 17 00:00:00 2001 From: Decheng Date: Thu, 11 Jun 2020 17:43:32 +1000 Subject: [PATCH 07/29] Implement basic pull beta groups command --- .../BetaGroups/BetaGroupCommand.swift | 1 + .../Sync/PullBetaGroupsCommand.swift | 30 +++++++++++++++++++ .../Sync/PushBetaGroupsCommand.swift | 28 +++++++++++++++++ .../Sync/SyncBetaGroupsCommand.swift | 17 +++++++++++ 4 files changed, 76 insertions(+) create mode 100644 Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PullBetaGroupsCommand.swift create mode 100644 Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift create mode 100644 Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/SyncBetaGroupsCommand.swift diff --git a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/BetaGroupCommand.swift b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/BetaGroupCommand.swift index f677c506..405af792 100644 --- a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/BetaGroupCommand.swift +++ b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/BetaGroupCommand.swift @@ -18,6 +18,7 @@ struct TestFlightBetaGroupCommand: ParsableCommand { ReadBetaGroupCommand.self, RemoveTestersFromGroupCommand.self, AddTestersToGroupCommand.self, + SyncBetaGroupsCommand.self, ], defaultSubcommand: ListBetaGroupsCommand.self ) diff --git a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PullBetaGroupsCommand.swift b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PullBetaGroupsCommand.swift new file mode 100644 index 00000000..4a0849d4 --- /dev/null +++ b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PullBetaGroupsCommand.swift @@ -0,0 +1,30 @@ +// Copyright 2020 Itty Bitty Apps Pty Ltd + +import ArgumentParser +import FileSystem + +struct PullBetaGroupsCommand: CommonParsableCommand { + + static var configuration = CommandConfiguration( + commandName: "pull", + abstract: "Pull down server beta groups, refresh local beta group config files" + ) + + @OptionGroup() + var common: CommonOptions + + @Option( + default: "./config/betagroups", + help: "Path to the Folder containing the information about beta groups. (default: './config/betagroups')" + ) var outputPath: String + + func run() throws { + let service = try makeService() + + let betaGroupWithTesters = try service.pullBetaGroups() + + try BetaGroupProcessor(path: .folder(path: outputPath)) + .write(groupsWithTesters: betaGroupWithTesters) + } + +} diff --git a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift new file mode 100644 index 00000000..ce4e03a8 --- /dev/null +++ b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift @@ -0,0 +1,28 @@ +// Copyright 2020 Itty Bitty Apps Pty Ltd + +import ArgumentParser +import struct Model.BetaGroup + +struct PushBetaGroupsCommand: CommonParsableCommand { + + static var configuration = CommandConfiguration( + commandName: "push", + abstract: "Push local beta group config files to server, update server beta groups" + ) + + @OptionGroup() + var common: CommonOptions + + @Option( + default: "./config/betagroups", + help: "Path to the Folder containing the information about beta groups. (default: './config/betagroups')" + ) var inputPath: String + + @Flag(help: "Perform a dry run.") + var dryRun: Bool + + func run() throws { + + } + +} diff --git a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/SyncBetaGroupsCommand.swift b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/SyncBetaGroupsCommand.swift new file mode 100644 index 00000000..8c4166b2 --- /dev/null +++ b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/SyncBetaGroupsCommand.swift @@ -0,0 +1,17 @@ +// Copyright 2020 Itty Bitty Apps Pty Ltd + +import ArgumentParser + +struct SyncBetaGroupsCommand: ParsableCommand { + static var configuration = CommandConfiguration( + commandName: "sync", + abstract: """ + Sync information about beta groups with provided configuration file. + """, + subcommands: [ + PullBetaGroupsCommand.self, + PushBetaGroupsCommand.self, + ], + defaultSubcommand: PullBetaGroupsCommand.self + ) +} From ba2d7ff7dfce702ebbdb00749af98dfd7491efe2 Mon Sep 17 00:00:00 2001 From: Decheng Date: Fri, 12 Jun 2020 11:42:03 +1000 Subject: [PATCH 08/29] Create BetaTesterProcessor for rendering CSV --- Package.resolved | 4 +- Package.swift | 2 +- .../Beta Tester/BetaTesterProcessor.swift | 45 +++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 Sources/FileSystem/Beta Tester/BetaTesterProcessor.swift diff --git a/Package.resolved b/Package.resolved index 3475e08d..1eea750a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -3,10 +3,10 @@ "pins": [ { "package": "AppStoreConnect-Swift-SDK", - "repositoryURL": "https://github.com/AvdLee/appstoreconnect-swift-sdk.git", + "repositoryURL": "https://github.com/ittybittyapps/appstoreconnect-swift-sdk.git", "state": { "branch": "master", - "revision": "1d1887cd6b5f6312e4ba999e888619fe1845d0a9", + "revision": "65d95d0979734597e7fb7d2d30028c659594ac53", "version": null } }, diff --git a/Package.swift b/Package.swift index 18e8e694..f89119a3 100644 --- a/Package.swift +++ b/Package.swift @@ -24,7 +24,7 @@ let package = Package( from: "0.0.2" ), .package( - url: "https://github.com/AvdLee/appstoreconnect-swift-sdk.git", + url: "https://github.com/ittybittyapps/appstoreconnect-swift-sdk.git", .branch("master") ), .package( diff --git a/Sources/FileSystem/Beta Tester/BetaTesterProcessor.swift b/Sources/FileSystem/Beta Tester/BetaTesterProcessor.swift new file mode 100644 index 00000000..f547d404 --- /dev/null +++ b/Sources/FileSystem/Beta Tester/BetaTesterProcessor.swift @@ -0,0 +1,45 @@ +// Copyright 2020 Itty Bitty Apps Pty Ltd + +import CodableCSV +import Files +import Foundation +import Model + +struct BetaTesterProcessor { + + let folder: Folder + + typealias FilePath = String + + func write(group: BetaGroup, testers: [BetaTester]) throws -> FilePath { + let file = try folder.createFile(named: "\(group.app.bundleId ?? "")_\(group.groupName)_beta-testers.csv") + + try file.write(testers.renderAsCSV()) + + return file.name + } +} + +// TODO: merge this with ResultRenderable in main module +protocol CSVRenderable: Codable { + var headers: [String] { get } + var rows: [[String]] { get } +} + +extension CSVRenderable { + func renderAsCSV() -> String { + let wholeTable = [headers] + rows + + return try! CSVWriter.encode(rows: wholeTable, into: String.self) // swiftlint:disable:this force_try + } +} + +extension Array: CSVRenderable where Element == BetaTester { + var headers: [String] { + ["Email", "First Name", "Last Name", "Invite Type"] + } + + var rows: [[String]] { + self.map { [$0.email, $0.firstName, $0.lastName, $0.inviteType].compactMap { $0 } } + } +} From 58be82636f26af6ff0e55387be455a736b96fba2 Mon Sep 17 00:00:00 2001 From: Decheng Date: Fri, 12 Jun 2020 11:42:46 +1000 Subject: [PATCH 09/29] Wire BetaGroupProcessor up with TesterProcessor --- .../Beta Group/BetaGroupProcessor.swift | 17 +++++++++++-- Sources/FileSystem/ResourceProcessor.swift | 24 +++++++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift b/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift index 391cf4ae..32eebc2c 100644 --- a/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift +++ b/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift @@ -17,6 +17,7 @@ public struct BetaGroupProcessor: ResourceProcessor { var path: ResourcePath + @discardableResult func write(_ betaGroup: BetaGroup) throws -> File { try writeFile(betaGroup) } @@ -28,14 +29,26 @@ public struct BetaGroupProcessor: ResourceProcessor { public func write(groupsWithTesters: [(betaGroup: BetaGroup, testers: [BetaTester])]) throws { deleteFile() - try groupsWithTesters.map { try write($0.betaGroup) } + let betagroups = try groupsWithTesters + .map { try write(betaTesters: $0.testers, into: $0.betaGroup) } + + try betagroups.forEach { try write($0) } + } + + private func write(betaTesters: [BetaTester], into betaGroup: BetaGroup) throws -> BetaGroup { + let testerProcessor = BetaTesterProcessor(folder: try getFolder()) + + var group = betaGroup + group.testers = try testerProcessor.write(group: group, testers: betaTesters) + + return group } } extension BetaGroup: FileProvider { var fileName: String { - "\(app.id)_\(groupName).yml" + "\(app.bundleId ?? "")_\(groupName).yml" } func fileContent() throws -> FileContent { diff --git a/Sources/FileSystem/ResourceProcessor.swift b/Sources/FileSystem/ResourceProcessor.swift index c42c1d04..07f9a17c 100644 --- a/Sources/FileSystem/ResourceProcessor.swift +++ b/Sources/FileSystem/ResourceProcessor.swift @@ -33,23 +33,37 @@ protocol PathProvider { var path: ResourcePath { get } } +extension PathProvider { + func getFolder() throws -> Folder { + var folder: Folder + switch path { + case .file(let path): + let standardizedPath = path as NSString + folder = try Folder(path: "").createSubfolderIfNeeded(at: standardizedPath.deletingLastPathComponent) + case .folder(let folderPath): + folder = try Folder(path: "").createSubfolderIfNeeded(at: folderPath) + } + + return folder + } +} + extension ResourceWriter { func writeFile(_ resource: FileProvider) throws -> File { var file: File - var folder: Folder var fileName: String switch path { case .file(let path): let standardizedPath = path as NSString - folder = try Folder(path: "").createSubfolderIfNeeded(at: standardizedPath.deletingLastPathComponent) fileName = standardizedPath.lastPathComponent - case .folder(let folderPath): + case .folder(_): fileName = resource.fileName - folder = try Folder(path: "").createSubfolderIfNeeded(at: folderPath) } + let folder: Folder = try getFolder() + switch try resource.fileContent() { case .data(let data): file = try folder.createFileIfNeeded(withName: fileName, contents: data) @@ -77,7 +91,7 @@ extension ResourceWriter { .files.forEach { try $0.delete() } } } catch { - print("\(error)") + // Skip delete failed error, if folder is missing. } } } From 18f8fe5fe25fcd7c18941b88f47c724a2436895e Mon Sep 17 00:00:00 2001 From: Decheng Date: Fri, 12 Jun 2020 14:17:21 +1000 Subject: [PATCH 10/29] Model.BetaGroup + Hashable, Equatable --- Sources/Model/BetaGroup.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Sources/Model/BetaGroup.swift b/Sources/Model/BetaGroup.swift index d92f55d8..c360094c 100644 --- a/Sources/Model/BetaGroup.swift +++ b/Sources/Model/BetaGroup.swift @@ -38,3 +38,21 @@ public struct BetaGroup: Codable, Equatable { self.testers = testers } } + +extension BetaGroup: Hashable { + public static func == (lhs: BetaGroup, rhs: BetaGroup) -> Bool { + return lhs.id == rhs.id && + lhs.groupName == rhs.groupName && + lhs.publicLinkEnabled == rhs.publicLinkEnabled && + lhs.publicLinkLimit == rhs.publicLinkLimit && + lhs.publicLinkLimitEnabled == rhs.publicLinkLimitEnabled + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(groupName) + hasher.combine(publicLinkEnabled) + hasher.combine(publicLinkLimit) + hasher.combine(publicLinkLimitEnabled) + } +} From d7b714817edd743e641ca53ff5484a70e93408e8 Mon Sep 17 00:00:00 2001 From: Decheng Date: Fri, 12 Jun 2020 14:17:52 +1000 Subject: [PATCH 11/29] Add read() to BetaGroupProcessor --- .../Beta Group/BetaGroupProcessor.swift | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift b/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift index 32eebc2c..cb989ae5 100644 --- a/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift +++ b/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift @@ -7,23 +7,22 @@ import Yams public struct BetaGroupProcessor: ResourceProcessor { + var path: ResourcePath + public init(path: ResourcePath) { self.path = path } - func write(_: [BetaGroup]) throws -> [File] { - fatalError() - } - - var path: ResourcePath - - @discardableResult - func write(_ betaGroup: BetaGroup) throws -> File { - try writeFile(betaGroup) - } + public func read() throws -> [BetaGroup] { + try getFolder().files.compactMap { (file: File) -> BetaGroup? in + if file.extension == "yml" { + return Readers + .FileReader(format: .yaml) + .read(filePath: file.path) + } - func read() throws -> [BetaGroup] { - fatalError() + return nil + } } public func write(groupsWithTesters: [(betaGroup: BetaGroup, testers: [BetaTester])]) throws { @@ -32,14 +31,24 @@ public struct BetaGroupProcessor: ResourceProcessor { let betagroups = try groupsWithTesters .map { try write(betaTesters: $0.testers, into: $0.betaGroup) } - try betagroups.forEach { try write($0) } + try write(betagroups) + } + + @discardableResult + func write(_ betaGroups: [BetaGroup]) throws -> [File] { + try betaGroups.map { try write($0) } + } + + @discardableResult + func write(_ betaGroup: BetaGroup) throws -> File { + try writeFile(betaGroup) } private func write(betaTesters: [BetaTester], into betaGroup: BetaGroup) throws -> BetaGroup { - let testerProcessor = BetaTesterProcessor(folder: try getFolder()) var group = betaGroup - group.testers = try testerProcessor.write(group: group, testers: betaTesters) + group.testers = try BetaTesterProcessor(folder: try getFolder()) + .write(group: group, testers: betaTesters) return group } From f235a5315f3befae6fad5d9525bbd9b214c748db Mon Sep 17 00:00:00 2001 From: Decheng Date: Fri, 12 Jun 2020 14:18:56 +1000 Subject: [PATCH 12/29] Implement UpdateBetaGroupOperation, add update and delete service func --- .../Services/AppStoreConnectService.swift | 13 ++++--- .../Operations/UpdateBetaGroupOperation.swift | 34 +++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 Sources/AppStoreConnectCLI/Services/Operations/UpdateBetaGroupOperation.swift diff --git a/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift b/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift index f4d6a00c..6d1c4496 100644 --- a/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift +++ b/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift @@ -526,10 +526,15 @@ class AppStoreConnectService { } func updateBetaGroup(betaGroup: Model.BetaGroup) throws { - // TODO -// _ = try UpdateBetaGroupOperation(options: .init(betaGroup: betaGroup)) -// .execute(with: requestor) -// .await() + _ = try UpdateBetaGroupOperation(options: .init(betaGroup: betaGroup)) + .execute(with: requestor) + .await() + } + + func deleteBetaGroup(with id: String) throws { + try DeleteBetaGroupOperation(options: .init(betaGroupId: id)) + .execute(with: requestor) + .await() } func readBuild(bundleId: String, buildNumber: String, preReleaseVersion: String) throws -> Model.Build { diff --git a/Sources/AppStoreConnectCLI/Services/Operations/UpdateBetaGroupOperation.swift b/Sources/AppStoreConnectCLI/Services/Operations/UpdateBetaGroupOperation.swift new file mode 100644 index 00000000..abe72722 --- /dev/null +++ b/Sources/AppStoreConnectCLI/Services/Operations/UpdateBetaGroupOperation.swift @@ -0,0 +1,34 @@ +// Copyright 2020 Itty Bitty Apps Pty Ltd + +import AppStoreConnect_Swift_SDK +import Combine +import Foundation +import struct Model.BetaGroup + +struct UpdateBetaGroupOperation: APIOperation { + + struct Options { + let betaGroup: BetaGroup + } + + private let options: Options + + init(options: Options) { + self.options = options + } + + func execute(with requestor: EndpointRequestor) throws -> AnyPublisher { + let betaGroup = options.betaGroup + + let endpoint = APIEndpoint.modify( + betaGroupWithId: betaGroup.id!, + name: betaGroup.groupName, + publicLinkEnabled: betaGroup.publicLinkEnabled, + publicLinkLimit: betaGroup.publicLinkLimit, + publicLinkLimitEnabled: betaGroup.publicLinkLimitEnabled + ) + + return requestor.request(endpoint).eraseToAnyPublisher() + } + +} From 2082ac60eeb3531e36556e3811f2899579159757 Mon Sep 17 00:00:00 2001 From: Decheng Date: Fri, 12 Jun 2020 14:20:40 +1000 Subject: [PATCH 13/29] Create SyncStrategy, SyncResultRenderable, SyncResultRenderer --- .../Readers and Renderers/Renderers.swift | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Sources/AppStoreConnectCLI/Readers and Renderers/Renderers.swift b/Sources/AppStoreConnectCLI/Readers and Renderers/Renderers.swift index bf37fcdf..0a09dc8c 100644 --- a/Sources/AppStoreConnectCLI/Readers and Renderers/Renderers.swift +++ b/Sources/AppStoreConnectCLI/Readers and Renderers/Renderers.swift @@ -113,3 +113,42 @@ extension ResultRenderable where Self: TableInfoProvider { return table.render() } } + +protocol SyncResultRenderable { + var syncResultText: String { get } +} + +enum SyncStrategy { + case delete(T) + case create(T) + case update(T) +} + +extension Renderers { + + struct SyncResultRenderer { + + func render(_ strategy: [SyncStrategy], isDryRun: Bool) { + strategy.forEach { renderResultText($0, isDryRun) } + } + + func render(_ strategy: SyncStrategy, isDryRun: Bool) { + renderResultText(strategy, isDryRun) + } + + private func renderResultText(_ strategy: SyncStrategy, _ isDryRun: Bool) { + let resultText: String + switch strategy { + case .create(let input): + resultText = "➕ \(input.syncResultText)" + case .delete(let input): + resultText = "➖ \(input.syncResultText)" + case .update(let input): + resultText = "⬆️ \(input.syncResultText)" + } + + print("\(isDryRun ? "" : "✅") \(resultText)") + } + } + +} From fe92a6c05226dacc9c2b8a755fc646070015a126 Mon Sep 17 00:00:00 2001 From: Decheng Date: Fri, 12 Jun 2020 14:23:26 +1000 Subject: [PATCH 14/29] Wire up PushBetaGroupCommand with sync funcs and strategies --- .../Sync/PushBetaGroupsCommand.swift | 61 +++++++++++++++++++ .../AppStoreConnectCLI/Model/BetaGroup.swift | 6 ++ 2 files changed, 67 insertions(+) diff --git a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift index ce4e03a8..10a1e37f 100644 --- a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift +++ b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift @@ -1,6 +1,8 @@ // Copyright 2020 Itty Bitty Apps Pty Ltd import ArgumentParser +import FileSystem +import Foundation import struct Model.BetaGroup struct PushBetaGroupsCommand: CommonParsableCommand { @@ -22,7 +24,66 @@ struct PushBetaGroupsCommand: CommonParsableCommand { var dryRun: Bool func run() throws { + let service = try makeService() + let resourceProcessor = BetaGroupProcessor(path: .folder(path: inputPath)) + + let serverGroups = Set(try service.pullBetaGroups().map{ $0.betaGroup }) + let localGroups = Set(try resourceProcessor.read()) + + let strategies = compareGroups( + localGroups: localGroups, + serverGroups: serverGroups + ) + + let renderer = Renderers.SyncResultRenderer() + + if dryRun { + renderer.render(strategies, isDryRun: true) + } else { + try strategies.forEach { (strategy: SyncStrategy) in + switch strategy { + case .create(let group): + _ = try service.createBetaGroup( + appBundleId: group.app.bundleId!, + groupName: group.groupName, + publicLinkEnabled: group.publicLinkEnabled ?? false, + publicLinkLimit: group.publicLinkLimit + ) + case .delete(let group): + try service.deleteBetaGroup(with: group.id!) + case .update(let group): + try service.updateBetaGroup(betaGroup: group) + } + + renderer.render(strategy, isDryRun: false) + } + + let betaGroupWithTesters = try service.pullBetaGroups() + + try resourceProcessor.write(groupsWithTesters: betaGroupWithTesters) + } + } + + func compareGroups(localGroups: Set, serverGroups: Set) -> [SyncStrategy] { + var strategies: [SyncStrategy] = [] + + let groupToCreate = localGroups.subtracting(serverGroups) + let groupToDelete = serverGroups.subtracting(localGroups) + + groupToDelete.forEach { group in + if !localGroups.contains(where: { group.id == $0.id }) { + strategies.append(.delete(group)) + } + } + + groupToCreate.forEach { group in + serverGroups.contains(where: { group.id == $0.id }) + ? strategies.append(.update(group)) + : strategies.append(.create(group)) + } + + return strategies } } diff --git a/Sources/AppStoreConnectCLI/Model/BetaGroup.swift b/Sources/AppStoreConnectCLI/Model/BetaGroup.swift index 935b23ba..e29e452a 100755 --- a/Sources/AppStoreConnectCLI/Model/BetaGroup.swift +++ b/Sources/AppStoreConnectCLI/Model/BetaGroup.swift @@ -73,3 +73,9 @@ extension BetaGroup { ) } } + +extension BetaGroup: SyncResultRenderable { + var syncResultText: String { + "\(app.bundleId ?? "" )_\(groupName)" + } +} From 508b7c797ffb8883fbfc85095dc61017178b5bd8 Mon Sep 17 00:00:00 2001 From: Decheng Date: Fri, 12 Jun 2020 14:34:54 +1000 Subject: [PATCH 15/29] Reformat files for linting get passed --- .../TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift | 2 +- .../AppStoreConnectCLI/Readers and Renderers/Renderers.swift | 2 +- Sources/FileSystem/ResourceProcessor.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift index 10a1e37f..8b6b561d 100644 --- a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift +++ b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift @@ -28,7 +28,7 @@ struct PushBetaGroupsCommand: CommonParsableCommand { let resourceProcessor = BetaGroupProcessor(path: .folder(path: inputPath)) - let serverGroups = Set(try service.pullBetaGroups().map{ $0.betaGroup }) + let serverGroups = Set(try service.pullBetaGroups().map { $0.betaGroup }) let localGroups = Set(try resourceProcessor.read()) let strategies = compareGroups( diff --git a/Sources/AppStoreConnectCLI/Readers and Renderers/Renderers.swift b/Sources/AppStoreConnectCLI/Readers and Renderers/Renderers.swift index 0a09dc8c..eb4857f7 100644 --- a/Sources/AppStoreConnectCLI/Readers and Renderers/Renderers.swift +++ b/Sources/AppStoreConnectCLI/Readers and Renderers/Renderers.swift @@ -150,5 +150,5 @@ extension Renderers { print("\(isDryRun ? "" : "✅") \(resultText)") } } - + } diff --git a/Sources/FileSystem/ResourceProcessor.swift b/Sources/FileSystem/ResourceProcessor.swift index 07f9a17c..418e29ef 100644 --- a/Sources/FileSystem/ResourceProcessor.swift +++ b/Sources/FileSystem/ResourceProcessor.swift @@ -58,7 +58,7 @@ extension ResourceWriter { case .file(let path): let standardizedPath = path as NSString fileName = standardizedPath.lastPathComponent - case .folder(_): + case .folder: fileName = resource.fileName } From 9ef4ece7469ecb29892d10a16c40ac4840031288 Mon Sep 17 00:00:00 2001 From: Decheng Date: Mon, 15 Jun 2020 09:36:57 +1000 Subject: [PATCH 16/29] Introduce SyncResourceComparator, --- .../Services/SyncService.swift | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Sources/AppStoreConnectCLI/Services/SyncService.swift diff --git a/Sources/AppStoreConnectCLI/Services/SyncService.swift b/Sources/AppStoreConnectCLI/Services/SyncService.swift new file mode 100644 index 00000000..ad1067d6 --- /dev/null +++ b/Sources/AppStoreConnectCLI/Services/SyncService.swift @@ -0,0 +1,42 @@ +// Copyright 2020 Itty Bitty Apps Pty Ltd + +import Foundation + +protocol SyncResourceProcessable: SyncResourceComparable, SyncResultRenderable { } + +protocol SyncResourceComparable: Hashable { + associatedtype T: Comparable + + var compareIdentity: T? { get } +} + +struct SyncResourceComparator { + + let localResources: [T] + let serverResources: [T] + + private var localResourcesSet: Set { Set(localResources) } + private var serverResourcesSet: Set { Set(serverResources) } + + func compare() -> [SyncStrategy] { + serverResourcesSet + .subtracting(localResourcesSet) + .compactMap { resource -> SyncStrategy? in + localResources + .contains(where: { resource.compareIdentity == $0.compareIdentity }) + ? nil + : .delete(resource) + } + + + localResourcesSet + .subtracting(serverResourcesSet) + .compactMap { resource -> SyncStrategy? in + serverResourcesSet + .contains( + where: { resource.compareIdentity == $0.compareIdentity } + ) + ? .update(resource) + : .create(resource) + } + } +} From 2c2b02fc8bf9a869dba407071dc3c93232593179 Mon Sep 17 00:00:00 2001 From: Decheng Date: Mon, 15 Jun 2020 09:37:35 +1000 Subject: [PATCH 17/29] Apply new SyncResourceComparator to sync betagroup command --- .../Sync/PushBetaGroupsCommand.swift | 34 ++++--------------- Sources/AppStoreConnectCLI/Model/App.swift | 2 +- .../AppStoreConnectCLI/Model/BetaGroup.swift | 8 ++++- .../Readers and Renderers/Renderers.swift | 2 +- Sources/Model/App.swift | 4 +-- 5 files changed, 18 insertions(+), 32 deletions(-) diff --git a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift index 8b6b561d..25f1bf45 100644 --- a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift +++ b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift @@ -28,13 +28,14 @@ struct PushBetaGroupsCommand: CommonParsableCommand { let resourceProcessor = BetaGroupProcessor(path: .folder(path: inputPath)) - let serverGroups = Set(try service.pullBetaGroups().map { $0.betaGroup }) - let localGroups = Set(try resourceProcessor.read()) + let serverGroups = try service.pullBetaGroups().map { $0.betaGroup } + let localGroups = try resourceProcessor.read() - let strategies = compareGroups( - localGroups: localGroups, - serverGroups: serverGroups - ) + let strategies = SyncResourceComparator( + localResources: localGroups, + serverResources: serverGroups + ) + .compare() let renderer = Renderers.SyncResultRenderer() @@ -65,25 +66,4 @@ struct PushBetaGroupsCommand: CommonParsableCommand { } } - func compareGroups(localGroups: Set, serverGroups: Set) -> [SyncStrategy] { - var strategies: [SyncStrategy] = [] - - let groupToCreate = localGroups.subtracting(serverGroups) - let groupToDelete = serverGroups.subtracting(localGroups) - - groupToDelete.forEach { group in - if !localGroups.contains(where: { group.id == $0.id }) { - strategies.append(.delete(group)) - } - } - - groupToCreate.forEach { group in - serverGroups.contains(where: { group.id == $0.id }) - ? strategies.append(.update(group)) - : strategies.append(.create(group)) - } - - return strategies - } - } diff --git a/Sources/AppStoreConnectCLI/Model/App.swift b/Sources/AppStoreConnectCLI/Model/App.swift index 9b12fab9..946fc576 100644 --- a/Sources/AppStoreConnectCLI/Model/App.swift +++ b/Sources/AppStoreConnectCLI/Model/App.swift @@ -38,7 +38,7 @@ extension App: TableInfoProvider { var tableRow: [CustomStringConvertible] { return [ - id, + id ?? "", bundleId ?? "", name ?? "", primaryLocale ?? "", diff --git a/Sources/AppStoreConnectCLI/Model/BetaGroup.swift b/Sources/AppStoreConnectCLI/Model/BetaGroup.swift index e29e452a..54d91949 100755 --- a/Sources/AppStoreConnectCLI/Model/BetaGroup.swift +++ b/Sources/AppStoreConnectCLI/Model/BetaGroup.swift @@ -26,7 +26,7 @@ extension BetaGroup: TableInfoProvider, ResultRenderable { var tableRow: [CustomStringConvertible] { [ - app.id, + app.id ?? "", app.bundleId ?? "", app.name ?? "", groupName, @@ -79,3 +79,9 @@ extension BetaGroup: SyncResultRenderable { "\(app.bundleId ?? "" )_\(groupName)" } } + +extension BetaGroup: SyncResourceProcessable { + var compareIdentity: String? { + id + } +} diff --git a/Sources/AppStoreConnectCLI/Readers and Renderers/Renderers.swift b/Sources/AppStoreConnectCLI/Readers and Renderers/Renderers.swift index eb4857f7..2dafbecf 100644 --- a/Sources/AppStoreConnectCLI/Readers and Renderers/Renderers.swift +++ b/Sources/AppStoreConnectCLI/Readers and Renderers/Renderers.swift @@ -114,7 +114,7 @@ extension ResultRenderable where Self: TableInfoProvider { } } -protocol SyncResultRenderable { +protocol SyncResultRenderable: Equatable { var syncResultText: String { get } } diff --git a/Sources/Model/App.swift b/Sources/Model/App.swift index b68cd27e..474425e8 100644 --- a/Sources/Model/App.swift +++ b/Sources/Model/App.swift @@ -3,14 +3,14 @@ import Foundation public struct App: Codable, Equatable { - public let id: String + public let id: String? public var bundleId: String? public var name: String? public var primaryLocale: String? public var sku: String? public init( - id: String, + id: String?, bundleId: String?, name: String?, primaryLocale: String?, From 8f2a4aeff1680ff879dfe475bae9a7bd43242afa Mon Sep 17 00:00:00 2001 From: Decheng Date: Mon, 15 Jun 2020 09:37:57 +1000 Subject: [PATCH 18/29] Add tests to SyncResourceComparator --- .../SyncResourceComparatorTests.swift | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 Tests/appstoreconnect-cliTests/Serivces/SyncResourceComparatorTests.swift diff --git a/Tests/appstoreconnect-cliTests/Serivces/SyncResourceComparatorTests.swift b/Tests/appstoreconnect-cliTests/Serivces/SyncResourceComparatorTests.swift new file mode 100644 index 00000000..df57727c --- /dev/null +++ b/Tests/appstoreconnect-cliTests/Serivces/SyncResourceComparatorTests.swift @@ -0,0 +1,107 @@ +// Copyright 2020 Itty Bitty Apps Pty Ltd + +@testable import AppStoreConnectCLI +import Model +import Foundation +import XCTest + +final class SyncResourceComparatorTests: XCTestCase { + func testCompare_returnCreateStrategies() throws { + let localGroups = [generateGroup(id: "123")] + + let strategies = SyncResourceComparator( + localResources: localGroups, + serverResources: [] + ) + .compare() + + XCTAssertEqual(strategies.count, 1) + + XCTAssertEqual(strategies.first!, .create(localGroups.first!)) + } + + func testCompare_returnUpdateStrategies() throws { + let localGroups = [generateGroup(id: "123", name: "foo")] + let serverGroups = [generateGroup(id: "123", name: "bar")] + + let strategies = SyncResourceComparator( + localResources: localGroups, + serverResources: serverGroups + ) + .compare() + + XCTAssertEqual(strategies.count, 1) + XCTAssertEqual(strategies.first!, .update(localGroups.first!)) + } + + func testCompare_returnDelete() { + let serverGroups = [generateGroup(id: "123"), generateGroup(id: "456")] + + let strategies = SyncResourceComparator( + localResources: [], + serverResources: serverGroups + ) + .compare() + + XCTAssertEqual(strategies.count, 2) + XCTAssertEqual(strategies.contains(.delete(serverGroups.first!)), true) + XCTAssertEqual(strategies.contains(.delete(serverGroups[1])), true) + XCTAssertNotEqual(strategies.contains(.update(generateGroup(id: "1234"))), true) + } + + func testCompare_returnDeleteAndUpdateAndCreate() { + let localGroups = [generateGroup(id: "1", publicLinkEnabled: true), generateGroup(name: "hi")] + let serverGroups = [generateGroup(id: "1", publicLinkEnabled: false), generateGroup(id: "3", name: "there")] + + let strategies = SyncResourceComparator( + localResources: localGroups, + serverResources: serverGroups + ) + .compare() + + XCTAssertEqual(strategies.count, 3) + + XCTAssertEqual(strategies.contains(.delete(serverGroups[1])), true) + XCTAssertEqual(strategies.contains(.create(localGroups[1])), true) + XCTAssertEqual(strategies.contains(.update(localGroups[0])), true) + } +} + +private extension SyncResourceComparatorTests { + func generateGroup( + id: String? = nil, + name: String = "foo", + isInternal: Bool = false, + publicLinkEnabled: Bool = false, + publicLinkLimit: Int = 10, + publicLinkLimitEnabled: Bool = false + ) -> BetaGroup { + BetaGroup( + app: App(id: "", bundleId: "com.example.foo", name: "foo", primaryLocale: "", sku: ""), + id: id, + groupName: name, + isInternal: isInternal, + publicLink: "", + publicLinkEnabled: publicLinkEnabled, + publicLinkLimit: publicLinkLimit, + publicLinkLimitEnabled: publicLinkLimitEnabled, + creationDate: "", + testers: "" + ) + } +} + +extension SyncStrategy: Equatable { + public static func == (lhs: SyncStrategy, rhs: SyncStrategy) -> Bool { + switch (lhs, rhs) { + case (let .create(lhsItem), let .create(rhsItem)): + return lhsItem == rhsItem + case (let .update(lhsItem), let .update(rhsItem)): + return lhsItem == rhsItem + case (let .delete(lhsItem), let .delete(rhsItem)): + return lhsItem == rhsItem + default: + return false + } + } +} From 7616eae637c9dc62613c63da585ec8370011b5a4 Mon Sep 17 00:00:00 2001 From: Decheng Date: Mon, 15 Jun 2020 10:43:14 +1000 Subject: [PATCH 19/29] PushBetaGroupsCommand syncBetaGroup function code separation --- .../Sync/PushBetaGroupsCommand.swift | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift index 25f1bf45..ef321b36 100644 --- a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift +++ b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift @@ -43,20 +43,7 @@ struct PushBetaGroupsCommand: CommonParsableCommand { renderer.render(strategies, isDryRun: true) } else { try strategies.forEach { (strategy: SyncStrategy) in - switch strategy { - case .create(let group): - _ = try service.createBetaGroup( - appBundleId: group.app.bundleId!, - groupName: group.groupName, - publicLinkEnabled: group.publicLinkEnabled ?? false, - publicLinkLimit: group.publicLinkLimit - ) - case .delete(let group): - try service.deleteBetaGroup(with: group.id!) - case .update(let group): - try service.updateBetaGroup(betaGroup: group) - } - + try syncBetaGroup(strategy: strategy, with: service) renderer.render(strategy, isDryRun: false) } @@ -66,4 +53,23 @@ struct PushBetaGroupsCommand: CommonParsableCommand { } } + func syncBetaGroup( + strategy: SyncStrategy, + with service: AppStoreConnectService + ) throws { + switch strategy { + case .create(let group): + _ = try service.createBetaGroup( + appBundleId: group.app.bundleId!, + groupName: group.groupName, + publicLinkEnabled: group.publicLinkEnabled ?? false, + publicLinkLimit: group.publicLinkLimit + ) + case .delete(let group): + try service.deleteBetaGroup(with: group.id!) + case .update(let group): + try service.updateBetaGroup(betaGroup: group) + } + } + } From 962ea210dd0c9c05893645c312e907dec66ea82c Mon Sep 17 00:00:00 2001 From: Decheng Date: Mon, 15 Jun 2020 12:01:38 +1000 Subject: [PATCH 20/29] mark compareIdentity non-optional in ResourceComparable, tweak sync test --- Sources/AppStoreConnectCLI/Model/BetaGroup.swift | 4 ++-- Sources/AppStoreConnectCLI/Services/SyncService.swift | 2 +- .../Serivces/SyncResourceComparatorTests.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/AppStoreConnectCLI/Model/BetaGroup.swift b/Sources/AppStoreConnectCLI/Model/BetaGroup.swift index 54d91949..b3785ce5 100755 --- a/Sources/AppStoreConnectCLI/Model/BetaGroup.swift +++ b/Sources/AppStoreConnectCLI/Model/BetaGroup.swift @@ -81,7 +81,7 @@ extension BetaGroup: SyncResultRenderable { } extension BetaGroup: SyncResourceProcessable { - var compareIdentity: String? { - id + var compareIdentity: String { + id ?? "" } } diff --git a/Sources/AppStoreConnectCLI/Services/SyncService.swift b/Sources/AppStoreConnectCLI/Services/SyncService.swift index ad1067d6..c4ccdc9e 100644 --- a/Sources/AppStoreConnectCLI/Services/SyncService.swift +++ b/Sources/AppStoreConnectCLI/Services/SyncService.swift @@ -7,7 +7,7 @@ protocol SyncResourceProcessable: SyncResourceComparable, SyncResultRenderable { protocol SyncResourceComparable: Hashable { associatedtype T: Comparable - var compareIdentity: T? { get } + var compareIdentity: T { get } } struct SyncResourceComparator { diff --git a/Tests/appstoreconnect-cliTests/Serivces/SyncResourceComparatorTests.swift b/Tests/appstoreconnect-cliTests/Serivces/SyncResourceComparatorTests.swift index df57727c..3c7fdc93 100644 --- a/Tests/appstoreconnect-cliTests/Serivces/SyncResourceComparatorTests.swift +++ b/Tests/appstoreconnect-cliTests/Serivces/SyncResourceComparatorTests.swift @@ -7,7 +7,7 @@ import XCTest final class SyncResourceComparatorTests: XCTestCase { func testCompare_returnCreateStrategies() throws { - let localGroups = [generateGroup(id: "123")] + let localGroups = [generateGroup(name: "a new group")] let strategies = SyncResourceComparator( localResources: localGroups, From 6798d52dfcafa163cebb17e64ecac935821fb728 Mon Sep 17 00:00:00 2001 From: Decheng Date: Mon, 15 Jun 2020 10:33:54 +1000 Subject: [PATCH 21/29] Model.BetaTester + Hashable, Equatable, SyncResourceProcessable --- .../AppStoreConnectCLI/Model/BetaTester.swift | 10 ++++++++++ Sources/Model/BetaTester.swift | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Sources/AppStoreConnectCLI/Model/BetaTester.swift b/Sources/AppStoreConnectCLI/Model/BetaTester.swift index 18b2930b..1c9191c8 100644 --- a/Sources/AppStoreConnectCLI/Model/BetaTester.swift +++ b/Sources/AppStoreConnectCLI/Model/BetaTester.swift @@ -58,3 +58,13 @@ extension BetaTester: ResultRenderable, TableInfoProvider { ] } } + +extension BetaTester: SyncResourceProcessable { + var syncResultText: String { + email! + } + + var compareIdentity: String? { + email + } +} diff --git a/Sources/Model/BetaTester.swift b/Sources/Model/BetaTester.swift index 9f3e65bb..9be17ecd 100644 --- a/Sources/Model/BetaTester.swift +++ b/Sources/Model/BetaTester.swift @@ -26,3 +26,19 @@ public struct BetaTester: Codable, Equatable { self.apps = apps } } + +extension BetaTester: Hashable { + + public static func == (lhs: BetaTester, rhs: BetaTester) -> Bool { + return lhs.email == rhs.email && + lhs.firstName == rhs.firstName && + lhs.lastName == rhs.lastName + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(email) + hasher.combine(firstName) + hasher.combine(lastName) + } + +} From cdd0238b0a162f105c99ce216679adf8ad7f2666 Mon Sep 17 00:00:00 2001 From: Decheng Date: Mon, 15 Jun 2020 10:34:57 +1000 Subject: [PATCH 22/29] Add func readGroupAndTesters to BetaGroupProcessor --- .../FileSystem/Beta Group/BetaGroupProcessor.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift b/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift index cb989ae5..6616d725 100644 --- a/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift +++ b/Sources/FileSystem/Beta Group/BetaGroupProcessor.swift @@ -25,6 +25,19 @@ public struct BetaGroupProcessor: ResourceProcessor { } } + public func readGroupAndTesters() throws -> [(betaGroup: BetaGroup, testers: [BetaTester])] { + try read().map { group -> (BetaGroup, [BetaTester]) in + guard let testercsv = group.testers else { + return (group, []) + } + + let testers = Readers.FileReader<[BetaTester]>(format: .csv) + .read(filePath: testercsv) + + return (group, testers) + } + } + public func write(groupsWithTesters: [(betaGroup: BetaGroup, testers: [BetaTester])]) throws { deleteFile() From b394db23bbdde511c24748d65ffb3e07e878c723 Mon Sep 17 00:00:00 2001 From: Decheng Date: Mon, 15 Jun 2020 10:35:20 +1000 Subject: [PATCH 23/29] Add service function inviteBetaTesterToGroups && removeTesterFromGroups --- .../Services/AppStoreConnectService.swift | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift b/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift index 6d1c4496..c32214d3 100644 --- a/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift +++ b/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift @@ -157,6 +157,22 @@ class AppStoreConnectService { return Model.BetaTester(output) } + func inviteBetaTesterToGroups( + firstName: String?, + lastName: String?, + email: String, + groupIds: [String] + ) throws { + _ = try InviteTesterOperation(options: .init( + firstName: firstName, + lastName: lastName, + email: email, + identifers: .resourceId(groupIds)) + ) + .execute(with: requestor) + .await() + } + func addTestersToGroup( bundleId: String, groupName: String, @@ -400,6 +416,30 @@ class AppStoreConnectService { try operation.execute(with: requestor).await() } + func removeTesterFromGroups( + email: String, + groupIds: [String] + ) throws { + let testerId = try GetBetaTesterOperation( + options: .init(identifier: .email(email)) + ) + .execute(with: requestor) + .await() + .betaTester + .id + + try RemoveTesterOperation( + options: .init( + removeStrategy: .removeTesterFromGroups( + testerId: testerId, + groupIds: groupIds + ) + ) + ) + .execute(with: requestor) + .await() + } + func readBetaGroup(bundleId: String, groupName: String) throws -> Model.BetaGroup { let app = try ReadAppOperation(options: .init(identifier: .bundleId(bundleId))) .execute(with: requestor) From 532459e0ce09ba469bdf90bc3c10a9725f680bb9 Mon Sep 17 00:00:00 2001 From: Decheng Date: Mon, 15 Jun 2020 10:51:21 +1000 Subject: [PATCH 24/29] Add sync beta testers function to PushBetaGroupsCommand --- .../Sync/PushBetaGroupsCommand.swift | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift index ef321b36..00045755 100644 --- a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift +++ b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift @@ -4,6 +4,7 @@ import ArgumentParser import FileSystem import Foundation import struct Model.BetaGroup +import struct Model.BetaTester struct PushBetaGroupsCommand: CommonParsableCommand { @@ -28,12 +29,45 @@ struct PushBetaGroupsCommand: CommonParsableCommand { let resourceProcessor = BetaGroupProcessor(path: .folder(path: inputPath)) - let serverGroups = try service.pullBetaGroups().map { $0.betaGroup } + let serverGroupsWithTesters = try service.pullBetaGroups() let localGroups = try resourceProcessor.read() + // Sync Beta Testers + let localGroupWithTesters = try resourceProcessor.readGroupAndTesters() + + try localGroupWithTesters.forEach { + let localGroupId = $0.betaGroup.id + let localTesters = $0.testers + + let serverTesters = serverGroupsWithTesters.first { + $0.betaGroup.id == localGroupId + }?.testers ?? [] + + let testerStrategies = SyncResourceComparator( + localResources: localTesters, + serverResources: serverTesters + ) + .compare() + + let renderer = Renderers.SyncResultRenderer() + + if dryRun { + renderer.render(testerStrategies, isDryRun: true) + } else { + let renderer = Renderers.SyncResultRenderer() + + try testerStrategies.forEach { + try syncTester(with: service, groupId: localGroupId!, strategies: $0) + + renderer.render($0, isDryRun: false) + } + } + } + + // Sync Beta Groups let strategies = SyncResourceComparator( localResources: localGroups, - serverResources: serverGroups + serverResources: serverGroupsWithTesters.map { $0.betaGroup } ) .compare() @@ -72,4 +106,24 @@ struct PushBetaGroupsCommand: CommonParsableCommand { } } + func syncTester( + with service: AppStoreConnectService, + groupId: String, + strategies: SyncStrategy + ) throws { + switch strategies { + case .create(let tester): + try service.inviteBetaTesterToGroups( + firstName: tester.firstName, + lastName: tester.lastName, + email: tester.email!, + groupIds: [groupId] + ) + case .update: + print("Update single beta tester is not supported.") + case .delete(let tester): + try service.removeTesterFromGroups(email: tester.email!, groupIds: [groupId]) + } + } + } From 68de2cee38d2bdd96eddb06d81492af7425cdfa8 Mon Sep 17 00:00:00 2001 From: Decheng Date: Mon, 15 Jun 2020 15:35:20 +1000 Subject: [PATCH 25/29] Add coding key to Beta Tester for reading csv --- Sources/AppStoreConnectCLI/Model/BetaTester.swift | 4 ++-- Sources/Model/BetaTester.swift | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Sources/AppStoreConnectCLI/Model/BetaTester.swift b/Sources/AppStoreConnectCLI/Model/BetaTester.swift index 1c9191c8..535af14f 100644 --- a/Sources/AppStoreConnectCLI/Model/BetaTester.swift +++ b/Sources/AppStoreConnectCLI/Model/BetaTester.swift @@ -64,7 +64,7 @@ extension BetaTester: SyncResourceProcessable { email! } - var compareIdentity: String? { - email + var compareIdentity: String { + email! } } diff --git a/Sources/Model/BetaTester.swift b/Sources/Model/BetaTester.swift index 9be17ecd..5ec68b78 100644 --- a/Sources/Model/BetaTester.swift +++ b/Sources/Model/BetaTester.swift @@ -42,3 +42,16 @@ extension BetaTester: Hashable { } } + +extension BetaTester { + + private enum CodingKeys: String, CodingKey { + case email = "Email" + case firstName = "First Name" + case lastName = "Last Name" + case inviteType = "Invite Type" + case betaGroups + case apps + } + +} From 86c9459271bf0e01443319e9ed7350a91713f201 Mon Sep 17 00:00:00 2001 From: Decheng Date: Mon, 15 Jun 2020 15:35:41 +1000 Subject: [PATCH 26/29] Use relative path when storing beta tester file path --- Sources/FileSystem/Beta Tester/BetaTesterProcessor.swift | 2 +- Sources/FileSystem/Readers.swift | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/FileSystem/Beta Tester/BetaTesterProcessor.swift b/Sources/FileSystem/Beta Tester/BetaTesterProcessor.swift index f547d404..0b0a5ed6 100644 --- a/Sources/FileSystem/Beta Tester/BetaTesterProcessor.swift +++ b/Sources/FileSystem/Beta Tester/BetaTesterProcessor.swift @@ -16,7 +16,7 @@ struct BetaTesterProcessor { try file.write(testers.renderAsCSV()) - return file.name + return file.path(relativeTo: try Folder(path: FileManager.default.currentDirectoryPath)) } } diff --git a/Sources/FileSystem/Readers.swift b/Sources/FileSystem/Readers.swift index c792bccc..c10eb647 100644 --- a/Sources/FileSystem/Readers.swift +++ b/Sources/FileSystem/Readers.swift @@ -60,8 +60,7 @@ public enum Readers { } guard - let url = URL(string: "file://\(filePath)"), - let result = try? decoder.decode(T.self, from: url) else { + let result = try? decoder.decode(T.self, from: URL(fileURLWithPath: filePath)) else { fatalError("Could not read CSV file: \(filePath)") } From 265f63979a428c0de8f7f8bae029c685b81b41f2 Mon Sep 17 00:00:00 2001 From: Decheng Date: Tue, 16 Jun 2020 10:12:54 +1000 Subject: [PATCH 27/29] Print group name before testers when syncing beta testers --- .../BetaGroups/Sync/PushBetaGroupsCommand.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift index 00045755..cae202cb 100644 --- a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift +++ b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift @@ -36,11 +36,11 @@ struct PushBetaGroupsCommand: CommonParsableCommand { let localGroupWithTesters = try resourceProcessor.readGroupAndTesters() try localGroupWithTesters.forEach { - let localGroupId = $0.betaGroup.id + let localGroup = $0.betaGroup let localTesters = $0.testers let serverTesters = serverGroupsWithTesters.first { - $0.betaGroup.id == localGroupId + $0.betaGroup.id == localGroup.id }?.testers ?? [] let testerStrategies = SyncResourceComparator( @@ -51,13 +51,17 @@ struct PushBetaGroupsCommand: CommonParsableCommand { let renderer = Renderers.SyncResultRenderer() + if testerStrategies.count > 0 { + print("\(localGroup.groupName): ") + } + if dryRun { renderer.render(testerStrategies, isDryRun: true) } else { let renderer = Renderers.SyncResultRenderer() try testerStrategies.forEach { - try syncTester(with: service, groupId: localGroupId!, strategies: $0) + try syncTester(with: service, groupId: localGroup.id!, strategies: $0) renderer.render($0, isDryRun: false) } From 7b05cae5ebfaabc3c9acd65e7977018c6ade1406 Mon Sep 17 00:00:00 2001 From: Decheng Date: Tue, 16 Jun 2020 10:49:56 +1000 Subject: [PATCH 28/29] Tweak sync betagroup logic, support creating group and testers together --- .../Sync/PushBetaGroupsCommand.swift | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift index cae202cb..5ace9557 100644 --- a/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift +++ b/Sources/AppStoreConnectCLI/Commands/TestFlight/BetaGroups/Sync/PushBetaGroupsCommand.swift @@ -32,6 +32,24 @@ struct PushBetaGroupsCommand: CommonParsableCommand { let serverGroupsWithTesters = try service.pullBetaGroups() let localGroups = try resourceProcessor.read() + // Sync Beta Groups + let strategies = SyncResourceComparator( + localResources: localGroups, + serverResources: serverGroupsWithTesters.map { $0.betaGroup } + ) + .compare() + + let renderer = Renderers.SyncResultRenderer() + + if dryRun { + renderer.render(strategies, isDryRun: true) + } else { + try strategies.forEach { (strategy: SyncStrategy) in + try syncBetaGroup(strategy: strategy, with: service) + renderer.render(strategy, isDryRun: false) + } + } + // Sync Beta Testers let localGroupWithTesters = try resourceProcessor.readGroupAndTesters() @@ -58,36 +76,20 @@ struct PushBetaGroupsCommand: CommonParsableCommand { if dryRun { renderer.render(testerStrategies, isDryRun: true) } else { - let renderer = Renderers.SyncResultRenderer() - try testerStrategies.forEach { - try syncTester(with: service, groupId: localGroup.id!, strategies: $0) + try syncTester(with: service, + bundleId: localGroup.app.bundleId!, + groupName: localGroup.groupName, + strategies: $0) renderer.render($0, isDryRun: false) } } } - // Sync Beta Groups - let strategies = SyncResourceComparator( - localResources: localGroups, - serverResources: serverGroupsWithTesters.map { $0.betaGroup } - ) - .compare() - - let renderer = Renderers.SyncResultRenderer() - - if dryRun { - renderer.render(strategies, isDryRun: true) - } else { - try strategies.forEach { (strategy: SyncStrategy) in - try syncBetaGroup(strategy: strategy, with: service) - renderer.render(strategy, isDryRun: false) - } - - let betaGroupWithTesters = try service.pullBetaGroups() - - try resourceProcessor.write(groupsWithTesters: betaGroupWithTesters) + // After all operations, sync group and testers + if !dryRun { + try resourceProcessor.write(groupsWithTesters: try service.pullBetaGroups()) } } @@ -112,21 +114,23 @@ struct PushBetaGroupsCommand: CommonParsableCommand { func syncTester( with service: AppStoreConnectService, - groupId: String, + bundleId: String, + groupName: String, strategies: SyncStrategy ) throws { switch strategies { case .create(let tester): - try service.inviteBetaTesterToGroups( + _ = try service.inviteBetaTesterToGroups( firstName: tester.firstName, lastName: tester.lastName, email: tester.email!, - groupIds: [groupId] + bundleId: bundleId, + groupNames: [groupName] ) case .update: print("Update single beta tester is not supported.") case .delete(let tester): - try service.removeTesterFromGroups(email: tester.email!, groupIds: [groupId]) + try service.removeTesterFromGroups(email: tester.email!, groupNames: [groupName]) } } From 4be72517e13febd234a154921a0a2116094b6038 Mon Sep 17 00:00:00 2001 From: Decheng Date: Tue, 16 Jun 2020 11:31:52 +1000 Subject: [PATCH 29/29] Remove unused beta tester service function --- .../Services/AppStoreConnectService.swift | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift b/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift index c32214d3..6d1c4496 100644 --- a/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift +++ b/Sources/AppStoreConnectCLI/Services/AppStoreConnectService.swift @@ -157,22 +157,6 @@ class AppStoreConnectService { return Model.BetaTester(output) } - func inviteBetaTesterToGroups( - firstName: String?, - lastName: String?, - email: String, - groupIds: [String] - ) throws { - _ = try InviteTesterOperation(options: .init( - firstName: firstName, - lastName: lastName, - email: email, - identifers: .resourceId(groupIds)) - ) - .execute(with: requestor) - .await() - } - func addTestersToGroup( bundleId: String, groupName: String, @@ -416,30 +400,6 @@ class AppStoreConnectService { try operation.execute(with: requestor).await() } - func removeTesterFromGroups( - email: String, - groupIds: [String] - ) throws { - let testerId = try GetBetaTesterOperation( - options: .init(identifier: .email(email)) - ) - .execute(with: requestor) - .await() - .betaTester - .id - - try RemoveTesterOperation( - options: .init( - removeStrategy: .removeTesterFromGroups( - testerId: testerId, - groupIds: groupIds - ) - ) - ) - .execute(with: requestor) - .await() - } - func readBetaGroup(bundleId: String, groupName: String) throws -> Model.BetaGroup { let app = try ReadAppOperation(options: .init(identifier: .bundleId(bundleId))) .execute(with: requestor)