diff --git a/Package.resolved b/Package.resolved index a6a07c2..27b7723 100644 --- a/Package.resolved +++ b/Package.resolved @@ -12,11 +12,11 @@ }, { "package": "OctoKit", - "repositoryURL": "https://github.com/nerdishbynature/octokit.swift", + "repositoryURL": "https://github.com/AvdLee/octokit.swift", "state": { - "branch": null, - "revision": "9521cdff919053868ab13cd08a228f7bc1bde2a9", - "version": "0.11.0" + "branch": "master", + "revision": "c1d49d82fa34d8022b0cdc95ca94113a54a4ec81", + "version": null } }, { @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/nerdishbynature/RequestKit.git", "state": { "branch": null, - "revision": "fd5e9e99aada7432170366c9e95967011ce13bad", - "version": "2.4.0" + "revision": "e266fd8ac7d71caf4422ffc56ad98ff368329fde", + "version": "3.1.0" } }, { diff --git a/Package.swift b/Package.swift index 18cc30d..8833c0e 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,9 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/WeTransfer/Mocker.git", .upToNextMajor(from: "2.1.0")), - .package(url: "https://github.com/nerdishbynature/octokit.swift", .upToNextMajor(from: "0.10.1")), +// .package(path: "../../Forks/octokit.swift"), +// .package(url: "https://github.com/nerdishbynature/octokit.swift", .upToNextMajor(from: "0.10.1")), + .package(url: "https://github.com/AvdLee/octokit.swift", .branch("master")), .package(url: "https://github.com/apple/swift-argument-parser", .upToNextMajor(from: "1.0.0")) ], targets: [ diff --git a/Sources/GitBuddyCore/Commands/GitBuddy.swift b/Sources/GitBuddyCore/Commands/GitBuddy.swift index fb7093d..5d57480 100644 --- a/Sources/GitBuddyCore/Commands/GitBuddy.swift +++ b/Sources/GitBuddyCore/Commands/GitBuddy.swift @@ -17,7 +17,7 @@ public struct GitBuddy: ParsableCommand { commandName: "gitbuddy", abstract: "Manage your GitHub repositories with ease", version: Self.version, - subcommands: [ChangelogCommand.self, ReleaseCommand.self] + subcommands: [ChangelogCommand.self, ReleaseCommand.self, TagDeletionsCommand.self] ) public init() { } diff --git a/Sources/GitBuddyCore/Commands/TagDeletionsCommand.swift b/Sources/GitBuddyCore/Commands/TagDeletionsCommand.swift new file mode 100644 index 0000000..40659cc --- /dev/null +++ b/Sources/GitBuddyCore/Commands/TagDeletionsCommand.swift @@ -0,0 +1,35 @@ +import Foundation +import ArgumentParser + +struct TagDeletionsCommand: ParsableCommand { + + public static let configuration = CommandConfiguration(commandName: "tagDeletion", abstract: "Delete a batch of tags based on given predicates.") + + @Option(name: .shortAndLong, help: "The date of this tag will be used as a limit. Defaults to the latest tag.") + private var upUntilTag: String? + + @Option(name: .shortAndLong, help: "The limit of tags to delete in this batch. Defaults to 50") + private var limit: Int = 50 + + @Flag(name: .long, help: "Delete pre releases only") + private var prereleaseOnly: Bool = false + + @Flag(name: .long, help: "Does not actually delete but just logs which tags would be deleted") + private var dryRun: Bool = false + + @Flag(name: .long, help: "Show extra logging for debugging purposes") + var verbose: Bool = false + + func run() throws { + Log.isVerbose = verbose + + let tagsDeleter = try TagsDeleter(upUntilTagName: upUntilTag, limit: limit, prereleaseOnly: prereleaseOnly, dryRun: dryRun) + let deletedTags = try tagsDeleter.run() + + guard !deletedTags.isEmpty else { + Log.message("There were no tags found to be deleted.") + return + } + Log.message("Deleted tags: \(deletedTags)") + } +} diff --git a/Sources/GitBuddyCore/Helpers/DateFormatters.swift b/Sources/GitBuddyCore/Helpers/DateFormatters.swift new file mode 100644 index 0000000..c33fcfa --- /dev/null +++ b/Sources/GitBuddyCore/Helpers/DateFormatters.swift @@ -0,0 +1,19 @@ +// +// DateFormatters.swift +// +// +// Created by Antoine van der Lee on 25/01/2022. +// Copyright © 2020 WeTransfer. All rights reserved. +// + +import Foundation + +enum Formatter { + static let gitDateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z" + dateFormatter.locale = Locale(identifier: "en_US_POSIX") + dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) + return dateFormatter + }() +} diff --git a/Sources/GitBuddyCore/Helpers/Shell.swift b/Sources/GitBuddyCore/Helpers/Shell.swift index 975f035..249e586 100644 --- a/Sources/GitBuddyCore/Helpers/Shell.swift +++ b/Sources/GitBuddyCore/Helpers/Shell.swift @@ -14,6 +14,7 @@ enum ShellCommand { case previousTag case repositoryName case tagCreationDate(tag: String) + case commitDate(commitish: String) var rawValue: String { switch self { @@ -27,6 +28,8 @@ enum ShellCommand { return "git remote show origin -n | ruby -ne 'puts /^\\s*Fetch.*(:|\\/){1}([^\\/]+\\/[^\\/]+).git/.match($_)[2] rescue nil'" case .tagCreationDate(let tag): return "git log -1 --format=%ai \(tag)" + case .commitDate(let commitish): + return "git show -s --format=%ai \(commitish)" } } } @@ -43,9 +46,11 @@ extension Process { let data = outputPipe.fileHandleForReading.readDataToEndOfFile() guard let outputData = String(data: data, encoding: String.Encoding.utf8) else { return "" } - return outputData.reduce("") { (result, value) in - return result + String(value) - }.trimmingCharacters(in: .whitespacesAndNewlines) + return outputData + .reduce("") { (result, value) in + return result + String(value) + } + .trimmingCharacters(in: .whitespacesAndNewlines) } } diff --git a/Sources/GitBuddyCore/Models/Tag.swift b/Sources/GitBuddyCore/Models/Tag.swift index 1c19d8b..6230dda 100644 --- a/Sources/GitBuddyCore/Models/Tag.swift +++ b/Sources/GitBuddyCore/Models/Tag.swift @@ -40,18 +40,13 @@ struct Tag: ShellInjectable, Encodable { } else { let tagCreationDate = Self.shell.execute(.tagCreationDate(tag: name)) if tagCreationDate.isEmpty { - Log.debug("Tag creation date could not be found") + Log.debug("Tag creation date could not be found for \(name)") throw Error.missingTagCreationDate } Log.debug("Tag \(name) is created at \(tagCreationDate)") - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z" - dateFormatter.locale = Locale(identifier: "en_US_POSIX") - dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) - - guard let date = dateFormatter.date(from: tagCreationDate) else { + guard let date = Formatter.gitDateFormatter.date(from: tagCreationDate) else { throw Error.missingTagCreationDate } self.created = date diff --git a/Sources/GitBuddyCore/Release/ReleaseProducer.swift b/Sources/GitBuddyCore/Release/ReleaseProducer.swift index 99d9e5b..e524d6d 100644 --- a/Sources/GitBuddyCore/Release/ReleaseProducer.swift +++ b/Sources/GitBuddyCore/Release/ReleaseProducer.swift @@ -53,22 +53,17 @@ final class ReleaseProducer: URLSessionInjectable, ShellInjectable { } @discardableResult public func run(isSectioned: Bool) throws -> Release { - /// If a tagname exists, it means we're creating a new tag. - /// In this case, we need another way to fetch the from date for the changelog. - if tagName != nil, changelogToTag == nil { - throw Error.changelogTargetDateMissing - } - - let changelogToTag = try changelogToTag.map { try Tag(name: $0) } ?? Tag.latest() - let changelogSinceTag = lastReleaseTag ?? Self.shell.execute(.previousTag) + let changelogToDate = try fetchChangelogToDate() /// We're adding 60 seconds to make sure the tag commit itself is included in the changelog as well. - let toDate = changelogToTag.created.addingTimeInterval(60) - let changelogProducer = try ChangelogProducer(since: .tag(tag: changelogSinceTag), to: toDate, baseBranch: baseBranch) + let adjustedChangelogToDate = changelogToDate.addingTimeInterval(60) + + let changelogSinceTag = lastReleaseTag ?? Self.shell.execute(.previousTag) + let changelogProducer = try ChangelogProducer(since: .tag(tag: changelogSinceTag), to: adjustedChangelogToDate, baseBranch: baseBranch) let changelog = try changelogProducer.run(isSectioned: isSectioned) Log.debug("\(changelog)\n") - let tagName = tagName ?? changelogToTag.name + let tagName = try tagName ?? Tag.latest().name try updateChangelogFile(adding: changelog.description, for: tagName) let repositoryName = Self.shell.execute(.repositoryName) @@ -81,6 +76,36 @@ final class ReleaseProducer: URLSessionInjectable, ShellInjectable { return release } + private func fetchChangelogToDate() throws -> Date { + if tagName != nil { + /// If a tagname exists, it means we're creating a new tag. + /// In this case, we need another way to fetch the `to` date for the changelog. + /// + /// One option is using the `changelogToTag`: + if let changelogToTag = changelogToTag { + return try Tag(name: changelogToTag).created + } else if let targetCommitishDate = targetCommitishDate() { + /// We fallback to the target commit date, covering cases in which we create a release + /// from a certain branch + return targetCommitishDate + } else { + /// Since we were unable to fetch the date + throw Error.changelogTargetDateMissing + } + } else { + /// This is the scenario of creating a release for an already created tag. + return try Tag.latest().created + } + } + + private func targetCommitishDate() -> Date? { + guard let targetCommitish = targetCommitish else { + return nil + } + let commitishDate = Self.shell.execute(.commitDate(commitish: targetCommitish)) + return Formatter.gitDateFormatter.date(from: commitishDate) + } + private func postComments(for changelog: Changelog, project: GITProject, release: Release) { guard !skipComments else { Log.debug("Skipping comments") diff --git a/Sources/GitBuddyCore/Tag Deletions/TagsDeleter.swift b/Sources/GitBuddyCore/Tag Deletions/TagsDeleter.swift new file mode 100644 index 0000000..b8bad80 --- /dev/null +++ b/Sources/GitBuddyCore/Tag Deletions/TagsDeleter.swift @@ -0,0 +1,121 @@ +import Foundation +import OctoKit + +final class TagsDeleter: URLSessionInjectable, ShellInjectable { + + private lazy var octoKit: Octokit = Octokit() + let upUntilTagName: String? + let limit: Int + let prereleaseOnly: Bool + let dryRun: Bool + + init(upUntilTagName: String? = nil, limit: Int, prereleaseOnly: Bool, dryRun: Bool) throws { + try Octokit.authenticate() + + self.upUntilTagName = upUntilTagName + self.limit = limit + self.prereleaseOnly = prereleaseOnly + self.dryRun = dryRun + } + + @discardableResult public func run() throws -> [String] { + let upUntilTag = try upUntilTagName.map { try Tag(name: $0) } ?? Tag.latest() + Log.debug("Deleting up to \(limit) tags before \(upUntilTag.name) (Dry run: \(dryRun.description))") + + let currentProject = GITProject.current() + let releases = try fetchReleases(project: currentProject, upUntil: upUntilTag.created) + + guard !releases.isEmpty else { + return [] + } + try deleteReleases(releases, project: currentProject) + try deleteTags(releases, project: currentProject) + + return releases.map { $0.tagName } + } + + private func fetchReleases(project: GITProject, upUntil: Date) throws -> [OctoKit.Release] { + let group = DispatchGroup() + group.enter() + + var result: Result<[OctoKit.Release], Swift.Error>! + octoKit.listReleases(urlSession, owner: project.organisation, repository: project.repository, perPage: 100) { response in + result = response + group.leave() + } + group.wait() + + let releases = try result.get() + Log.debug("Fetched releases: \(releases.map { $0.tagName }.joined(separator: ", "))") + + return releases + .filter({ release in + guard !prereleaseOnly || release.prerelease else { + return false + } + return release.createdAt < upUntil + }) + .suffix(limit) + } + + private func deleteReleases(_ releases: [OctoKit.Release], project: GITProject) throws { + let releasesToDelete = releases.map { $0.tagName } + Log.debug("Deleting tags: \(releasesToDelete.joined(separator: ", "))") + + let group = DispatchGroup() + var lastError: Error? + for release in releases { + group.enter() + Log.debug("Deleting release \(release.tagName) with id \(release.id) url: \(release.htmlURL)") + guard !dryRun else { + group.leave() + return + } + + octoKit.deleteRelease(urlSession, owner: project.organisation, repository: project.repository, releaseId: release.id) { error in + defer { group.leave() } + guard let error = error else { + Log.debug("Successfully deleted release \(release.tagName)") + return + } + Log.debug("Deletion of release \(release.tagName) failed: \(error)") + lastError = error + } + } + group.wait() + + if let lastError = lastError { + throw lastError + } + } + + private func deleteTags(_ releases: [OctoKit.Release], project: GITProject) throws { + let tagsToDelete = releases.map { $0.tagName } + Log.debug("Deleting tags: \(tagsToDelete.joined(separator: ", "))") + + let group = DispatchGroup() + var lastError: Error? + for release in releases { + group.enter() + Log.debug("Deleting tag \(release.tagName) with id \(release.id) url: \(release.htmlURL)") + guard !dryRun else { + group.leave() + return + } + + octoKit.deleteReference(urlSession, owner: project.organisation, repository: project.repository, ref: "tags/\(release.tagName)") { error in + defer { group.leave() } + guard let error = error else { + Log.debug("Successfully deleted tag \(release.tagName)") + return + } + Log.debug("Deletion of tag \(release.tagName) failed: \(error)") + lastError = error + } + } + group.wait() + if let lastError = lastError { + throw lastError + } + } +} diff --git a/Submodules/WeTransfer-iOS-CI b/Submodules/WeTransfer-iOS-CI index 9f61063..ea38f28 160000 --- a/Submodules/WeTransfer-iOS-CI +++ b/Submodules/WeTransfer-iOS-CI @@ -1 +1 @@ -Subproject commit 9f610636865411fc0355f6650adc7dd7bb489a57 +Subproject commit ea38f283c557acbbe8a370bf19398d7a717f3e00 diff --git a/Tests/GitBuddyTests/Release/ReleaseProducerTests.swift b/Tests/GitBuddyTests/Release/ReleaseProducerTests.swift index 280e25c..1f9dc9f 100644 --- a/Tests/GitBuddyTests/Release/ReleaseProducerTests.swift +++ b/Tests/GitBuddyTests/Release/ReleaseProducerTests.swift @@ -213,6 +213,40 @@ final class ReleaseProducerTests: XCTestCase { wait(for: [mockExpectation], timeout: 0.3) } + func testUsingTargetCommitishDate() throws { + let existingChangelog = """ + ### 1.0.0 + - Initial release + """ + let tagName = "2.0.0b1233" + let changelogToTag = "2.0.0b1232" + let commitish = "1e88145e2101dc7203af3095d6e" + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + let date = dateFormatter.date(from: "2020-01-05")! + MockedShell.mockCommitish(commitish, date: date) + MockedShell.mockRelease(tag: changelogToTag, date: date) + + let tempFileURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("Changelog.md") + XCTAssertTrue(FileManager.default.createFile(atPath: tempFileURL.path, contents: Data(existingChangelog.utf8), attributes: nil)) + + try executeCommand("gitbuddy release --target-commitish \(commitish) -s -n \(tagName) -c \(tempFileURL.path)") + + let updatedChangelogContents = try String(contentsOfFile: tempFileURL.path) + + // Merged at: 2020-01-06 - Add charset utf-8 to html head + // Closed at: 2020-01-03 - Get warning for file + // Setting the tag date to 2020-01-05 should remove the Add charset + + XCTAssertEqual(updatedChangelogContents, """ + ### 2.0.0b1233 + - Get warning for file \'style.css\' after building \ + ([#39](https://github.com/WeTransfer/Diagnostics/issues/39)) via [@AvdLee](https://github.com/AvdLee) + + \(existingChangelog) + """) + } + func testThrowsMissingTargetDateError() { XCTAssertThrowsError(try executeCommand("gitbuddy release -s -n 3.0.0"), "Missing target date error should be thrown") { error in XCTAssertEqual(error as? ReleaseProducer.Error, .changelogTargetDateMissing) diff --git a/Tests/GitBuddyTests/Tag Deletions/TagsDeleterTests.swift b/Tests/GitBuddyTests/Tag Deletions/TagsDeleterTests.swift new file mode 100644 index 0000000..564f534 --- /dev/null +++ b/Tests/GitBuddyTests/Tag Deletions/TagsDeleterTests.swift @@ -0,0 +1,79 @@ +// +// TagsDeleterTests.swift +// +// +// Created by Antoine van der Lee on 25/01/2022. +// + +import XCTest +@testable import GitBuddyCore +import Mocker +import OctoKit + +final class TagsDeleterTests: XCTestCase { + + override func setUp() { + super.setUp() + Octokit.protocolClasses = [MockingURLProtocol.self] + mockGITAuthentication() + ShellInjector.shell = MockedShell.self + Mocker.mockListReleases() + MockedShell.mockGITProject(organisation: "WeTransfer", repository: "Diagnostics") + } + + override func tearDown() { + super.tearDown() + MockedShell.commandMocks.removeAll() + Mocker.removeAll() + } + + func testDeletingUpUntilTagName() throws { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd-MM-yyyy" + let dateMatchingTagInMockedJSON = dateFormatter.date(from: "26-06-2013")! + MockedShell.mockRelease(tag: "2.0.0", date: dateMatchingTagInMockedJSON) + + let expectedTags = ["v1.0.0", "v1.0.1", "v1.0.2", "v1.0.3"] + for (index, tagName) in expectedTags.enumerated() { + Mocker.mockDeletingRelease(id: index) + Mocker.mockDeletingReference(tagName: tagName) + } + + let output = try executeCommand("gitbuddy tagDeletion -u 2.0.0 -l 100") + XCTAssertEqual(output, "Deleted tags: [\"\(expectedTags.joined(separator: "\", \""))\"]") + } + + func testDeletingLimit() throws { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd-MM-yyyy" + let dateMatchingTagInMockedJSON = dateFormatter.date(from: "26-06-2013")! + MockedShell.mockRelease(tag: "2.0.0", date: dateMatchingTagInMockedJSON) + + Mocker.mockDeletingRelease(id: 1) + Mocker.mockDeletingReference(tagName: "v1.0.3") + + let output = try executeCommand("gitbuddy tagDeletion -u 2.0.0 -l 1") + XCTAssertEqual(output, "Deleted tags: [\"v1.0.3\"]") + } + + func testDeletingPrereleaseOnly() throws { + MockedShell.mockRelease(tag: "2.0.0", date: Date()) + + Mocker.mockDeletingRelease(id: 1) + Mocker.mockDeletingReference(tagName: "v1.0.3") + + let output = try executeCommand("gitbuddy tagDeletion -u 2.0.0 -l 1 --prerelease-only") + XCTAssertEqual(output, "Deleted tags: [\"v1.0.3\"]") + } + + func testDeletingUpUntilLastTag() throws { + MockedShell.mockRelease(tag: "2.0.0", date: Date()) + + MockedShell.mock(.previousTag, value: "2.0.0") + Mocker.mockDeletingRelease(id: 1) + Mocker.mockDeletingReference(tagName: "v1.0.3") + + let output = try executeCommand("gitbuddy tagDeletion -l 1 --prerelease-only") + XCTAssertEqual(output, "Deleted tags: [\"v1.0.3\"]") + } +} diff --git a/Tests/GitBuddyTests/TestHelpers/ListReleasesJSON.swift b/Tests/GitBuddyTests/TestHelpers/ListReleasesJSON.swift new file mode 100644 index 0000000..282efb9 --- /dev/null +++ b/Tests/GitBuddyTests/TestHelpers/ListReleasesJSON.swift @@ -0,0 +1,208 @@ +// +// ListReleasesJSON.swift +// +// +// Created by Antoine van der Lee on 25/01/2022. +// + +import Foundation + +let ListReleasesJSON = """ + +[{ + "url": "https://api.github.com/repos/octocat/Hello-World/releases/1", + "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.0", + "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/1/assets", + "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}", + "tarball_url": null, + "zipball_url": null, + "id": 1, + "node_id": "MDc6UmVsZWFzZTE=", + "tag_name": "v1.0.0", + "target_commitish": "master", + "name": "v1.0.0 Release", + "body": "The changelog of this release", + "draft": true, + "prerelease": false, + "created_at": "2013-02-27T19:35:32Z", + "published_at": null, + "author": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "assets": [] + }, + { + "url": "https://api.github.com/repos/octocat/Hello-World/releases/2", + "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.1", + "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/2/assets", + "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/2/assets{?name,label}", + "tarball_url": "https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.1", + "zipball_url": "https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.1", + "id": 1, + "node_id": "MDc6UmVsZWFzZTE=", + "tag_name": "v1.0.1", + "target_commitish": "master", + "name": "v1.0.1 Release", + "body": "The changelog of this release", + "draft": false, + "prerelease": false, + "created_at": "2013-03-27T19:35:32Z", + "published_at": "2013-03-27T19:35:32Z", + "author": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "assets": [] + }, + { + "url": "https://api.github.com/repos/octocat/Hello-World/releases/3", + "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.2", + "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/3/assets", + "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/3/assets{?name,label}", + "tarball_url": "https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.2", + "zipball_url": "https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.2", + "id": 1, + "node_id": "MDc6UmVsZWFzZTE=", + "tag_name": "v1.0.2", + "target_commitish": "master", + "name": "v1.0.2 Release", + "body": "The changelog of this release", + "draft": false, + "prerelease": true, + "created_at": "2013-04-27T19:35:32Z", + "published_at": "2013-04-27T19:35:32Z", + "author": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "assets": [] + }, + { + "url": "https://api.github.com/repos/octocat/Hello-World/releases/4", + "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.3", + "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/4/assets", + "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/4/assets{?name,label}", + "tarball_url": "https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.3", + "zipball_url": "https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.3", + "id": 1, + "node_id": "MDc6UmVsZWFzZTE=", + "tag_name": "v1.0.3", + "target_commitish": "master", + "name": "v1.0.3 Release", + "body": "The changelog of this release", + "draft": false, + "prerelease": true, + "created_at": "2013-05-27T19:35:32Z", + "published_at": "2013-05-27T19:35:32Z", + "author": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "assets": [] + }, + { + "url": "https://api.github.com/repos/octocat/Hello-World/releases/5", + "html_url": "https://github.com/octocat/Hello-World/releases/v2.0.0", + "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/5/assets", + "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/5/assets{?name,label}", + "tarball_url": "https://api.github.com/repos/octocat/Hello-World/tarball/v2.0.0", + "zipball_url": "https://api.github.com/repos/octocat/Hello-World/zipball/v2.0.0", + "id": 1, + "node_id": "MDc6UmVsZWFzZTE=", + "tag_name": "v2.0.0", + "target_commitish": "master", + "name": "v2.0.0 Release", + "body": "The changelog of this release", + "draft": false, + "prerelease": false, + "created_at": "2013-06-27T19:35:32Z", + "published_at": "2013-06-27T19:35:32Z", + "author": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "assets": [] + } +] +""" diff --git a/Tests/GitBuddyTests/TestHelpers/Mocks.swift b/Tests/GitBuddyTests/TestHelpers/Mocks.swift index e4b9c5d..d5221cc 100644 --- a/Tests/GitBuddyTests/TestHelpers/Mocks.swift +++ b/Tests/GitBuddyTests/TestHelpers/Mocks.swift @@ -5,7 +5,7 @@ // Created by Antoine van der Lee on 10/01/2020. // Copyright © 2020 WeTransfer. All rights reserved. // -// danger:disable final_class +// swiftlint:disable final_class import Foundation import Mocker @@ -25,6 +25,12 @@ struct MockedShell: ShellExecuting { commandMocks[command.rawValue] = value } + static func mockCommitish(_ commitish: String, date: Date = Date()) { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z" + commandMocks[ShellCommand.commitDate(commitish: commitish).rawValue] = dateFormatter.string(from: date) + } + static func mockRelease(tag: String, date: Date = Date()) { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z" @@ -107,6 +113,25 @@ extension Mocker { return mock } + @discardableResult static func mockListReleases() -> Mock { + let releaseJSONData = ListReleasesJSON.data(using: .utf8)! + let mock = Mock(url: URL(string: "https://api.github.com/repos/WeTransfer/Diagnostics/releases?per_page=100")!, dataType: .json, statusCode: 200, data: [.get: releaseJSONData]) + mock.register() + return mock + } + + @discardableResult static func mockDeletingRelease(id: Int) -> Mock { + let mock = Mock(url: URL(string: "https://api.github.com/repos/WeTransfer/Diagnostics/releases/\(id)")!, dataType: .json, statusCode: 204, data: [.delete: Data()]) + mock.register() + return mock + } + + @discardableResult static func mockDeletingReference(tagName: String) -> Mock { + let mock = Mock(url: URL(string: "https://api.github.com/repos/WeTransfer/Diagnostics/git/refs/tags/\(tagName)")!, dataType: .json, statusCode: 204, data: [.delete: Data()]) + mock.register() + return mock + } + static func mockForCommentingOn(issueNumber: Int) -> Mock { let urlComponents = URLComponents(string: "https://api.github.com/repos/WeTransfer/Diagnostics/issues/\(issueNumber)/comments")! let commentJSONData = CommentJSON.data(using: .utf8)!