Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 23 additions & 27 deletions Sources/nnex/Publish/Controllers/GithubReleaseController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,28 @@ struct GithubReleaseController {
extension GithubReleaseController {
func uploadRelease(version: String, assets: [ArchivedBinary], notes: String?, notesFilePath: String?, projectFolder: any Directory) throws -> [String] {
let noteSource = try selectReleaseNoteSource(notes: notes, notesFilePath: notesFilePath, projectName: projectFolder.name)
let assetURLs = try gitHandler.createNewRelease(version: version, archivedBinaries: assets, releaseNoteInfo: noteSource.gitShellInfo, path: projectFolder.path)

return try gitHandler.createNewRelease(version: version, archivedBinaries: assets, releaseNoteInfo: noteSource.gitShellInfo, path: projectFolder.path)
moveNotesToTrashIfNecessary(noteSource: noteSource)

return assetURLs
}
}


// MARK: - Private Methods
private extension GithubReleaseController {
func moveNotesToTrashIfNecessary(noteSource: ReleaseNoteSource) {
switch noteSource {
case .filePath(let filePath):
if picker.getPermission(prompt: "Release notes uploaded. Would you like to move them to the trash?") {
try? fileSystem.moveToTrash(at: filePath)
}
default:
break
}
}

func selectReleaseNoteSource(notes: String?, notesFilePath: String?, projectName: String) throws -> ReleaseNoteSource {
let noteSource: ReleaseNoteSource

Expand All @@ -49,8 +63,6 @@ private extension GithubReleaseController {
noteSource = try selectReleaseNoteSourceInteractively(projectName: projectName)
}

try confirmReleaseNoteSource(noteSource)

return noteSource
}

Expand All @@ -62,6 +74,14 @@ private extension GithubReleaseController {
return .exact(notes)
case .selectFile:
let filePath = try folderBrowser.browseForFile(prompt: "Select the file containing your release notes.")
let confirmationPrompt = """

Release notes file path: \(filePath)

Proceed with this file?
"""

try picker.requiredPermission(prompt: confirmationPrompt)

return .filePath(filePath)
case .fromPath:
Expand Down Expand Up @@ -90,30 +110,6 @@ private extension GithubReleaseController {
return .filePath(fileName)
}
}

func confirmReleaseNoteSource(_ source: ReleaseNoteSource) throws {
let confirmationPrompt: String

switch source {
case .exact(let notes):
confirmationPrompt = """

Release Notes:
\(notes)

Proceed with these release notes?
"""
case .filePath(let filePath):
confirmationPrompt = """

Release notes file path: \(filePath)

Proceed with this file?
"""
}

try picker.requiredPermission(prompt: confirmationPrompt)
}
}


Expand Down
145 changes: 95 additions & 50 deletions Tests/nnexTests/UnitTests/GithubReleaseControllerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ extension GithubReleaseControllerTests {
#expect(noteInfo.content == expectedFilePath)
#expect(noteInfo.isFromFile == true)
}

@Test("Cancels release when file path from browser is not confirmed")
func cancelsReleaseWhenSelectedFileIsNotConfirmed() throws {
let expectedFilePath = "/selected/notes.md"
let assets = makeAssets()
let (sut, gitHandler) = makeSUT(selectionIndex: 1, permissionResults: [false], filePathToReturn: expectedFilePath)
let folder = MockDirectory(path: "/project/app")

#expect(throws: (any Error).self) {
_ = try sut.uploadRelease(version: "1.0.0", assets: assets, notes: nil, notesFilePath: nil, projectFolder: folder)
}

#expect(gitHandler.releaseNoteInfo == nil)
}

@Test("Uploads release with path from input")
func uploadsReleaseWithPathFromInput() throws {
Expand Down Expand Up @@ -124,91 +138,122 @@ extension GithubReleaseControllerTests {
}


// MARK: - Release Notes Confirmation
// MARK: - Move to Trash
extension GithubReleaseControllerTests {
@Test("Prompts confirmation for exact notes")
func promptsConfirmationForExactNotes() throws {
let expectedNotes = "Release notes content"
@Test("Moves release notes file to trash when confirmed - file path provided")
func movesFileToTrashWhenConfirmedWithFilePath() throws {
let expectedFilePath = "/path/to/notes.md"
let assets = makeAssets()
let (sut, gitHandler, picker) = makeSUTWithPicker(inputResults: [])
let folder = MockDirectory(path: "/project/myapp")

_ = try sut.uploadRelease(version: "1.0.0", assets: assets, notes: expectedNotes, notesFilePath: nil, projectFolder: folder)
let fileSystem = MockFileSystem()
let (sut, _) = makeSUT(permissionResults: [true], fileSystem: fileSystem)
let folder = MockDirectory(path: "/project/app")

let permissionPrompts = picker.capturedPermissionPrompts
let confirmationPrompt = try #require(permissionPrompts.first)
_ = try sut.uploadRelease(version: "1.0.0", assets: assets, notes: nil, notesFilePath: expectedFilePath, projectFolder: folder)

#expect(confirmationPrompt.contains("Release Notes:"))
#expect(confirmationPrompt.contains(expectedNotes))
#expect(confirmationPrompt.contains("Proceed with these release notes?"))
#expect(gitHandler.releaseNoteInfo != nil)
#expect(fileSystem.pathToMoveToTrash == expectedFilePath)
}

@Test("Prompts confirmation for file path")
func promptsConfirmationForFilePath() throws {
@Test("Does not move file to trash when user declines - file path provided")
func doesNotMoveFileToTrashWhenDeclinedWithFilePath() throws {
let expectedFilePath = "/path/to/notes.md"
let assets = makeAssets()
let (sut, gitHandler, picker) = makeSUTWithPicker(inputResults: [])
let fileSystem = MockFileSystem()
let (sut, _) = makeSUT(permissionResults: [false], fileSystem: fileSystem)
let folder = MockDirectory(path: "/project/app")

_ = try sut.uploadRelease(version: "1.0.0", assets: assets, notes: nil, notesFilePath: expectedFilePath, projectFolder: folder)

let permissionPrompts = picker.capturedPermissionPrompts
let confirmationPrompt = try #require(permissionPrompts.first)
#expect(fileSystem.pathToMoveToTrash == nil)
}

@Test("Moves release notes file to trash when confirmed - file from browser")
func movesFileToTrashWhenConfirmedWithSelectedFile() throws {
let expectedFilePath = "/selected/notes.md"
let assets = makeAssets()
let fileSystem = MockFileSystem()
let (sut, _) = makeSUT(selectionIndex: 1, permissionResults: [true, true], filePathToReturn: expectedFilePath, fileSystem: fileSystem)
let folder = MockDirectory(path: "/project/app")

_ = try sut.uploadRelease(version: "1.0.0", assets: assets, notes: nil, notesFilePath: nil, projectFolder: folder)

#expect(confirmationPrompt.contains("Release notes file path:"))
#expect(confirmationPrompt.contains(expectedFilePath))
#expect(confirmationPrompt.contains("Proceed with this file?"))
#expect(gitHandler.releaseNoteInfo != nil)
#expect(fileSystem.pathToMoveToTrash == expectedFilePath)
}

@Test("Prompts confirmation for interactively entered notes")
func promptsConfirmationForInteractiveNotes() throws {
let expectedNotes = "Interactive notes"
@Test("Does not move file to trash when user declines - file from browser")
func doesNotMoveFileToTrashWhenDeclinedWithSelectedFile() throws {
let expectedFilePath = "/selected/notes.md"
let assets = makeAssets()
let (sut, gitHandler, picker) = makeSUTWithPicker(inputResults: [expectedNotes], selectionIndex: 0)
let fileSystem = MockFileSystem()
let (sut, _) = makeSUT(selectionIndex: 1, permissionResults: [true, false], filePathToReturn: expectedFilePath, fileSystem: fileSystem)
let folder = MockDirectory(path: "/project/app")

_ = try sut.uploadRelease(version: "1.0.0", assets: assets, notes: nil, notesFilePath: nil, projectFolder: folder)

let permissionPrompts = picker.capturedPermissionPrompts
let confirmationPrompt = try #require(permissionPrompts.last)
#expect(fileSystem.pathToMoveToTrash == nil)
}

#expect(confirmationPrompt.contains("Release Notes:"))
#expect(confirmationPrompt.contains(expectedNotes))
#expect(confirmationPrompt.contains("Proceed with these release notes?"))
#expect(gitHandler.releaseNoteInfo != nil)
@Test("Moves release notes file to trash when confirmed - path from input")
func movesFileToTrashWhenConfirmedWithPathFromInput() throws {
let expectedPath = "/entered/path/notes.md"
let assets = makeAssets()
let fileSystem = MockFileSystem()
let (sut, _) = makeSUT(inputResults: [expectedPath], selectionIndex: 2, permissionResults: [true], fileSystem: fileSystem)
let folder = MockDirectory(path: "/project/app")

_ = try sut.uploadRelease(version: "1.0.0", assets: assets, notes: nil, notesFilePath: nil, projectFolder: folder)

#expect(fileSystem.pathToMoveToTrash == expectedPath)
}

@Test("Throws error when user denies confirmation")
func throwsErrorWhenUserDeniesConfirmation() throws {
let expectedNotes = "Release notes"
@Test("Does not move file to trash when user declines - path from input")
func doesNotMoveFileToTrashWhenDeclinedWithPathFromInput() throws {
let expectedPath = "/entered/path/notes.md"
let assets = makeAssets()
let (sut, gitHandler) = makeSUT(grantPermission: false)
let fileSystem = MockFileSystem()
let (sut, _) = makeSUT(inputResults: [expectedPath], selectionIndex: 2, permissionResults: [false], fileSystem: fileSystem)
let folder = MockDirectory(path: "/project/app")

#expect(throws: Error.self) {
try sut.uploadRelease(version: "1.0.0", assets: assets, notes: expectedNotes, notesFilePath: nil, projectFolder: folder)
}
_ = try sut.uploadRelease(version: "1.0.0", assets: assets, notes: nil, notesFilePath: nil, projectFolder: folder)

#expect(gitHandler.releaseNoteInfo == nil)
#expect(fileSystem.pathToMoveToTrash == nil)
}

@Test("Does not attempt to move to trash when using exact notes")
func doesNotMoveToTrashWhenUsingExactNotes() throws {
let expectedNotes = "Release notes content"
let assets = makeAssets()
let fileSystem = MockFileSystem()
let (sut, _) = makeSUT(fileSystem: fileSystem)
let folder = MockDirectory(path: "/project/app")

_ = try sut.uploadRelease(version: "1.0.0", assets: assets, notes: expectedNotes, notesFilePath: nil, projectFolder: folder)

#expect(fileSystem.pathToMoveToTrash == nil)
}

@Test("Does not attempt to move to trash when using direct input notes")
func doesNotMoveToTrashWhenUsingDirectInputNotes() throws {
let expectedNotes = "Interactive release notes"
let assets = makeAssets()
let fileSystem = MockFileSystem()
let (sut, _) = makeSUT(inputResults: [expectedNotes], selectionIndex: 0, fileSystem: fileSystem)
let folder = MockDirectory(path: "/project/app")

_ = try sut.uploadRelease(version: "1.0.0", assets: assets, notes: nil, notesFilePath: nil, projectFolder: folder)

#expect(fileSystem.pathToMoveToTrash == nil)
}
}


// MARK: - SUT
private extension GithubReleaseControllerTests {
func makeSUT(date: Date = Date(), inputResults: [String] = [], selectionIndex: Int = 0, grantPermission: Bool = true, desktop: (any Directory)? = nil, filePathToReturn: String? = nil) -> (sut: GithubReleaseController, gitHandler: MockGitHandler) {
let (sut, gitHandler, _) = makeSUTWithPicker(date: date, inputResults: inputResults, selectionIndex: selectionIndex, grantPermission: grantPermission, desktop: desktop, filePathToReturn: filePathToReturn)
return (sut, gitHandler)
}

func makeSUTWithPicker(date: Date = Date(), inputResults: [String] = [], selectionIndex: Int = 0, grantPermission: Bool = true, desktop: (any Directory)? = nil, filePathToReturn: String? = nil) -> (sut: GithubReleaseController, gitHandler: MockGitHandler, picker: MockSwiftPicker) {
func makeSUT(date: Date = Date(), inputResults: [String] = [], selectionIndex: Int = 0, permissionResults: [Bool] = [true], desktop: (any Directory)? = nil, filePathToReturn: String? = nil, fileSystem: MockFileSystem? = nil) -> (sut: GithubReleaseController, gitHandler: MockGitHandler) {
let gitHandler = MockGitHandler()
let fileSystem = MockFileSystem(desktop: desktop)
let fileSystem = fileSystem ?? MockFileSystem(desktop: desktop)
let picker = MockSwiftPicker(
inputResult: .init(type: .ordered(inputResults)),
permissionResult: .init(defaultValue: grantPermission),
permissionResult: .init(type: .ordered(permissionResults)),
selectionResult: .init(defaultSingle: .index(selectionIndex))
)
let folderBrowser = MockDirectoryBrowser(filePathToReturn: filePathToReturn, directoryToReturn: nil)
Expand All @@ -221,7 +266,7 @@ private extension GithubReleaseControllerTests {
folderBrowser: folderBrowser
)

return (sut, gitHandler, picker)
return (sut, gitHandler)
}

func makeAssets() -> [ArchivedBinary] {
Expand Down