@_spi(Internal) import CliClient import ConfigurationClient import Dependencies import FileClient import Foundation import ShellClient import Testing import TestSupport @Suite("CliClientTests") struct CliClientTests: TestCase { @Test func capturingClient() async throws { let captured = CliClient.CapturingClient() let client = CliClient.capturing(captured) try await client.runCommand(quiet: false, shell: .zsh(), "foo", "bar") let quiet = await captured.quiet! #expect(quiet == false) let shell = await captured.shell #expect(shell == .zsh()) let arguments = await captured.arguments! #expect(arguments == ["foo", "bar"]) } @Test(arguments: ["encrypt", "decrypt"]) func runVault(argument: String) async throws { let captured = CliClient.CapturingClient() try await withMockConfiguration(captured, key: "runVault") { $0.fileClient.findVaultFileInCurrentDirectory = { URL(filePath: "vault.yml") } } operation: { @Dependency(\.cliClient) var cliClient let configuration = Configuration.mock try await cliClient.runVaultCommand(.init(arguments: [argument], quiet: false, shell: nil)) let shell = await captured.shell #expect(shell == .zsh(useDashC: true)) let vaultPath = URL(filePath: #file) .deletingLastPathComponent() .deletingLastPathComponent() .appending(path: "vault.yml") var encryptArgs: [String] = [] if argument == "encrypt", let id = configuration.vault.encryptId { encryptArgs = ["--encrypt-vault-id", id] } let expectedArguments = [ "ansible-vault", argument ] + configuration.vault.args! + encryptArgs + [vaultPath.cleanFilePath] let arguments = await captured.arguments #expect(arguments == expectedArguments) } } @Test(arguments: [ Configuration( args: ["--tags", "debug"], useVaultArgs: true, playbook: .init(directory: "playbook", inventory: nil), vault: .mock ) ]) func runPlaybook(configuration: Configuration) async throws { let captured = CliClient.CapturingClient() try await withMockConfiguration(captured, configuration: configuration, key: "runPlaybook") { @Dependency(\.cliClient) var cliClient try await cliClient.runPlaybookCommand(.init( arguments: [], quiet: false, shell: nil )) let expectedArguments = [ "ansible-playbook", "playbook/main.yml", "--inventory", "playbook/inventory.ini", "--tags", "debug", "--vault-id=myId@$SCRIPTS/vault-gopass-client" ] let arguments = await captured.arguments #expect(arguments == expectedArguments) } } @Test func ensuredPlaybookDirectory() throws { let configuration = Configuration.mock let playbookDir = try configuration.ensuredPlaybookDirectory("playbook") #expect(playbookDir == "playbook") do { _ = try configuration.ensuredPlaybookDirectory(nil) #expect(Bool(false)) } catch { #expect(Bool(true)) } } @Test func shellOrDefault() { var shell: String? = "/bin/bash" #expect(shell.orDefault == .custom(path: "/bin/bash", useDashC: true)) shell = nil #expect(shell.orDefault == .zsh(useDashC: true)) } @Test func testEnsuredInventoryPath() { let configuration = Configuration(playbook: .init(inventory: "inventory.ini")) let playbookDir = "playbook" let inventoryPath = "inventory.ini" var output = ensuredInventoryPath( inventoryPath, configuration: configuration, playbookDirectory: playbookDir ) #expect(output == "inventory.ini") output = ensuredInventoryPath( nil, configuration: configuration, playbookDirectory: playbookDir ) #expect(output == "inventory.ini") } @Test func vaultFilePath() async throws { var fileClient = FileClient.testValue fileClient.findVaultFileInCurrentDirectory = { URL(string: "vault.yml") } var output = try await fileClient.ensuredVaultFilePath("vault.yml") #expect(output == "vault.yml") output = try await fileClient.ensuredVaultFilePath(nil) fileClient.findVaultFileInCurrentDirectory = { nil } do { _ = try await fileClient.ensuredVaultFilePath(nil) #expect(Bool(false)) } catch { #expect(Bool(true)) } } func withMockConfiguration( _ capturing: CliClient.CapturingClient, configuration: Configuration = .mock, key: String, logLevel: Logger.Level = .trace, depednencies setupDependencies: @escaping (inout DependencyValues) -> Void = { _ in }, operation: @Sendable @escaping () async throws -> Void ) async rethrows { try await withTestLogger(key: key, logLevel: logLevel) { $0.configurationClient = .mock(configuration) $0.cliClient = .capturing(capturing) setupDependencies(&$0) } operation: { try await operation() } } } extension ConfigurationClient { static func mock(_ configuration: Configuration) -> Self { var mock = Self.testValue mock.find = { throw TestError() } mock.load = { _ in configuration } return mock } } struct TestError: Error {}