feat: Adds createProject and createJson tests for playbook-client.
This commit is contained in:
@@ -109,6 +109,7 @@ let package = Package(
|
|||||||
.target(
|
.target(
|
||||||
name: "PlaybookClient",
|
name: "PlaybookClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
"CodersClient",
|
||||||
"CommandClient",
|
"CommandClient",
|
||||||
"ConfigurationClient",
|
"ConfigurationClient",
|
||||||
"FileClient",
|
"FileClient",
|
||||||
|
|||||||
@@ -28,9 +28,16 @@ extension PlaybookClient.RunPlaybook.BuildOptions {
|
|||||||
|
|
||||||
extension PlaybookClient.RunPlaybook.CreateOptions {
|
extension PlaybookClient.RunPlaybook.CreateOptions {
|
||||||
|
|
||||||
func run() async throws {
|
func run(encoder jsonEncoder: JSONEncoder?) async throws {
|
||||||
try await shared.run { arguments, configuration in
|
try await shared.run { arguments, configuration in
|
||||||
let json = try createJSONData(configuration: configuration)
|
let jsonData = try createJSONData(
|
||||||
|
configuration: configuration,
|
||||||
|
encoder: jsonEncoder
|
||||||
|
)
|
||||||
|
|
||||||
|
guard let json = String(data: jsonData, encoding: .utf8) else {
|
||||||
|
throw PlaybookClientError.encodingError
|
||||||
|
}
|
||||||
|
|
||||||
arguments.append(contentsOf: [
|
arguments.append(contentsOf: [
|
||||||
"--tags", "setup-project",
|
"--tags", "setup-project",
|
||||||
@@ -130,17 +137,20 @@ public extension PlaybookClient.RunPlaybook {
|
|||||||
// want the output to be `prettyPrinted` or anything, unless we're running
|
// want the output to be `prettyPrinted` or anything, unless we're running
|
||||||
// tests, so we use a supplied json encoder.
|
// tests, so we use a supplied json encoder.
|
||||||
//
|
//
|
||||||
extension PlaybookClient.RunPlaybook.CreateOptions {
|
@_spi(Internal)
|
||||||
|
public extension PlaybookClient.RunPlaybook.CreateOptions {
|
||||||
|
|
||||||
func createJSONData(
|
func createJSONData(
|
||||||
configuration: Configuration,
|
configuration: Configuration,
|
||||||
encoder: JSONEncoder = .init()
|
encoder: JSONEncoder?
|
||||||
) throws -> Data {
|
) throws -> Data {
|
||||||
@Dependency(\.logger) var logger
|
@Dependency(\.logger) var logger
|
||||||
|
|
||||||
let templateDir = template.directory ?? configuration.template.directory
|
let encoder = encoder ?? jsonEncoder
|
||||||
let templateRepo = template.url ?? configuration.template.url
|
|
||||||
let version = template.version ?? configuration.template.version
|
let templateDir = template?.directory ?? configuration.template.directory
|
||||||
|
let templateRepo = template?.url ?? configuration.template.url
|
||||||
|
let version = template?.version ?? configuration.template.version
|
||||||
|
|
||||||
logger.debug("""
|
logger.debug("""
|
||||||
(\(useLocalTemplateDirectory), \(String(describing: templateDir)), \(String(describing: templateRepo)))
|
(\(useLocalTemplateDirectory), \(String(describing: templateDir)), \(String(describing: templateRepo)))
|
||||||
@@ -203,3 +213,9 @@ private struct TemplateRepo: Encodable {
|
|||||||
let version: String
|
let version: String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let jsonEncoder: JSONEncoder = {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
encoder.outputFormatting = [.withoutEscapingSlashes, .sortedKeys]
|
||||||
|
return encoder
|
||||||
|
}()
|
||||||
|
|||||||
@@ -43,7 +43,11 @@ public extension PlaybookClient {
|
|||||||
@DependencyClient
|
@DependencyClient
|
||||||
struct RunPlaybook: Sendable {
|
struct RunPlaybook: Sendable {
|
||||||
public var buildProject: @Sendable (BuildOptions) async throws -> Void
|
public var buildProject: @Sendable (BuildOptions) async throws -> Void
|
||||||
public var createProject: @Sendable (CreateOptions) async throws -> Void
|
public var createProject: @Sendable (CreateOptions, JSONEncoder?) async throws -> Void
|
||||||
|
|
||||||
|
public func createProject(_ options: CreateOptions) async throws {
|
||||||
|
try await createProject(options, nil)
|
||||||
|
}
|
||||||
|
|
||||||
public struct SharedRunOptions: Equatable, Sendable {
|
public struct SharedRunOptions: Equatable, Sendable {
|
||||||
public let extraOptions: [String]?
|
public let extraOptions: [String]?
|
||||||
@@ -53,11 +57,11 @@ public extension PlaybookClient {
|
|||||||
public let shell: String?
|
public let shell: String?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
extraOptions: [String]?,
|
extraOptions: [String]? = nil,
|
||||||
inventoryFilePath: String?,
|
inventoryFilePath: String? = nil,
|
||||||
loggingOptions: LoggingOptions,
|
loggingOptions: LoggingOptions,
|
||||||
quiet: Bool,
|
quiet: Bool = false,
|
||||||
shell: String?
|
shell: String? = nil
|
||||||
) {
|
) {
|
||||||
self.extraOptions = extraOptions
|
self.extraOptions = extraOptions
|
||||||
self.inventoryFilePath = inventoryFilePath
|
self.inventoryFilePath = inventoryFilePath
|
||||||
@@ -73,12 +77,20 @@ public extension PlaybookClient {
|
|||||||
public let shared: SharedRunOptions
|
public let shared: SharedRunOptions
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
extraOptions: [String]?,
|
projectDirectory: String? = nil,
|
||||||
inventoryFilePath: String?,
|
shared: SharedRunOptions
|
||||||
|
) {
|
||||||
|
self.projectDirectory = projectDirectory
|
||||||
|
self.shared = shared
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
extraOptions: [String]? = nil,
|
||||||
|
inventoryFilePath: String? = nil,
|
||||||
loggingOptions: LoggingOptions,
|
loggingOptions: LoggingOptions,
|
||||||
quiet: Bool,
|
quiet: Bool = false,
|
||||||
shell: String?,
|
shell: String? = nil,
|
||||||
projectDirectory: String
|
projectDirectory: String? = nil
|
||||||
) {
|
) {
|
||||||
self.projectDirectory = projectDirectory
|
self.projectDirectory = projectDirectory
|
||||||
self.shared = .init(
|
self.shared = .init(
|
||||||
@@ -99,18 +111,30 @@ public extension PlaybookClient {
|
|||||||
public struct CreateOptions: Equatable, Sendable {
|
public struct CreateOptions: Equatable, Sendable {
|
||||||
public let projectDirectory: String
|
public let projectDirectory: String
|
||||||
public let shared: SharedRunOptions
|
public let shared: SharedRunOptions
|
||||||
public let template: Configuration.Template
|
public let template: Configuration.Template?
|
||||||
public let useLocalTemplateDirectory: Bool
|
public let useLocalTemplateDirectory: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
extraOptions: [String]?,
|
projectDirectory: String,
|
||||||
inventoryFilePath: String?,
|
shared: SharedRunOptions,
|
||||||
|
template: Configuration.Template? = nil,
|
||||||
|
useLocalTemplateDirectory: Bool
|
||||||
|
) {
|
||||||
|
self.projectDirectory = projectDirectory
|
||||||
|
self.shared = shared
|
||||||
|
self.template = template
|
||||||
|
self.useLocalTemplateDirectory = useLocalTemplateDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
extraOptions: [String]? = nil,
|
||||||
|
inventoryFilePath: String? = nil,
|
||||||
loggingOptions: LoggingOptions,
|
loggingOptions: LoggingOptions,
|
||||||
projectDirectory: String,
|
projectDirectory: String,
|
||||||
quiet: Bool,
|
quiet: Bool = false,
|
||||||
shell: String?,
|
shell: String? = nil,
|
||||||
template: Configuration.Template,
|
template: Configuration.Template? = nil,
|
||||||
useLocalTemplateDirectory: Bool
|
useLocalTemplateDirectory: Bool = false
|
||||||
) {
|
) {
|
||||||
self.projectDirectory = projectDirectory
|
self.projectDirectory = projectDirectory
|
||||||
self.template = template
|
self.template = template
|
||||||
@@ -145,7 +169,7 @@ extension PlaybookClient.RunPlaybook: DependencyKey {
|
|||||||
public static var liveValue: PlaybookClient.RunPlaybook {
|
public static var liveValue: PlaybookClient.RunPlaybook {
|
||||||
.init(
|
.init(
|
||||||
buildProject: { try await $0.run() },
|
buildProject: { try await $0.run() },
|
||||||
createProject: { try await $0.run() }
|
createProject: { try await $0.run(encoder: $1) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum PlaybookClientError: Error {
|
enum PlaybookClientError: Error {
|
||||||
|
case encodingError
|
||||||
case projectDirectoryNotFound
|
case projectDirectoryNotFound
|
||||||
case templateDirectoryNotFound
|
case templateDirectoryNotFound
|
||||||
case templateDirectoryOrRepoNotSpecified
|
case templateDirectoryOrRepoNotSpecified
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import CodersClient
|
||||||
|
@_spi(Internal) import CommandClient
|
||||||
import ConfigurationClient
|
import ConfigurationClient
|
||||||
import Dependencies
|
import Dependencies
|
||||||
import FileClient
|
import FileClient
|
||||||
@@ -8,13 +10,24 @@ import Testing
|
|||||||
import TestSupport
|
import TestSupport
|
||||||
|
|
||||||
@Suite("PlaybookClientTests")
|
@Suite("PlaybookClientTests")
|
||||||
struct PlaybookClientTests {
|
struct PlaybookClientTests: TestCase {
|
||||||
|
|
||||||
@Test
|
static let loggingOptions: LoggingOptions = {
|
||||||
func installation() async throws {
|
let levelString = ProcessInfo.processInfo.environment["LOG_LEVEL"] ?? "debug"
|
||||||
|
let logLevel = Logger.Level(rawValue: levelString) ?? .debug
|
||||||
|
return .init(commandName: "PlaybookClientTests", logLevel: logLevel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
static var sharedRunOptions: PlaybookClient.RunPlaybook.SharedRunOptions {
|
||||||
|
.init(loggingOptions: loggingOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(.tags(.repository))
|
||||||
|
func repositoryInstallation() async throws {
|
||||||
try await withDependencies {
|
try await withDependencies {
|
||||||
$0.fileClient = .liveValue
|
$0.fileClient = .liveValue
|
||||||
$0.asyncShellClient = .liveValue
|
$0.asyncShellClient = .liveValue
|
||||||
|
$0.commandClient = .liveValue
|
||||||
} operation: {
|
} operation: {
|
||||||
try await withTemporaryDirectory { tempDirectory in
|
try await withTemporaryDirectory { tempDirectory in
|
||||||
let pathUrl = tempDirectory.appending(path: "playbook")
|
let pathUrl = tempDirectory.appending(path: "playbook")
|
||||||
@@ -23,20 +36,256 @@ struct PlaybookClientTests {
|
|||||||
let configuration = Configuration(playbook: .init(directory: pathUrl.cleanFilePath))
|
let configuration = Configuration(playbook: .init(directory: pathUrl.cleanFilePath))
|
||||||
|
|
||||||
try? FileManager.default.removeItem(at: pathUrl)
|
try? FileManager.default.removeItem(at: pathUrl)
|
||||||
try await playbookClient.installPlaybook(configuration)
|
try await playbookClient.repository.install(configuration)
|
||||||
let exists = FileManager.default.fileExists(atPath: pathUrl.cleanFilePath)
|
let exists = FileManager.default.fileExists(atPath: pathUrl.cleanFilePath)
|
||||||
#expect(exists)
|
#expect(exists)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(arguments: [
|
@Test(
|
||||||
|
.tags(.repository),
|
||||||
|
arguments: [
|
||||||
(Configuration(), PlaybookClient.Constants.defaultInstallationPath),
|
(Configuration(), PlaybookClient.Constants.defaultInstallationPath),
|
||||||
(Configuration(playbook: .init(directory: "playbook")), "playbook")
|
(Configuration(playbook: .init(directory: "playbook")), "playbook")
|
||||||
])
|
]
|
||||||
func playbookDirectory(configuration: Configuration, expected: String) async throws {
|
)
|
||||||
|
func repositoryDirectory(configuration: Configuration, expected: String) async throws {
|
||||||
let client = PlaybookClient.liveValue
|
let client = PlaybookClient.liveValue
|
||||||
let result = try await client.playbookDirectory(configuration)
|
let result = try await client.repository.directory(configuration)
|
||||||
#expect(result == expected)
|
#expect(result == expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(.tags(.run))
|
||||||
|
func runBuildProject() async throws {
|
||||||
|
let captured = CommandClient.CapturingClient()
|
||||||
|
|
||||||
|
try await withMockConfiguration(captured, key: "runBuildProject") {
|
||||||
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
|
|
||||||
|
let configuration = Configuration.mock
|
||||||
|
|
||||||
|
try await playbookClient.run.buildProject(.init(projectDirectory: "/foo", shared: Self.sharedRunOptions))
|
||||||
|
|
||||||
|
let arguments = await captured.options!.arguments
|
||||||
|
print(arguments)
|
||||||
|
|
||||||
|
#expect(arguments == [
|
||||||
|
"ansible-playbook", "~/.local/share/hpa/playbook/main.yml",
|
||||||
|
"--inventory", "~/.local/share/hpa/playbook/inventory.ini",
|
||||||
|
configuration.vault.args!.first!,
|
||||||
|
"--tags", "build-project",
|
||||||
|
"--extra-vars", "project_dir=/foo"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(
|
||||||
|
.tags(.run),
|
||||||
|
arguments: [
|
||||||
|
(true, "\'{\"template\":{\"path\":\"\(Configuration.mock.template.directory!)\"}}\'"),
|
||||||
|
(false, "\'{\"template\":{\"repo\":{\"url\":\"\(Configuration.mock.template.url!)\",\"version\":\"\(Configuration.mock.template.version!)\"}}}\'")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
func runCreateProject(useLocalTemplateDirectory: Bool, json: String) async throws {
|
||||||
|
let captured = CommandClient.CapturingClient()
|
||||||
|
|
||||||
|
try await withMockConfiguration(captured, key: "runBuildProject") {
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
|
|
||||||
|
let configuration = Configuration.mock
|
||||||
|
|
||||||
|
try await playbookClient.run.createProject(
|
||||||
|
.init(
|
||||||
|
projectDirectory: "/project",
|
||||||
|
shared: Self.sharedRunOptions,
|
||||||
|
useLocalTemplateDirectory: useLocalTemplateDirectory
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
let arguments = await captured.options!.arguments
|
||||||
|
logger.debug("\(arguments)")
|
||||||
|
|
||||||
|
#expect(arguments == [
|
||||||
|
"ansible-playbook", "~/.local/share/hpa/playbook/main.yml",
|
||||||
|
"--inventory", "~/.local/share/hpa/playbook/inventory.ini",
|
||||||
|
configuration.vault.args!.first!,
|
||||||
|
"--tags", "setup-project",
|
||||||
|
"--extra-vars", "project_dir=/project",
|
||||||
|
"--extra-vars", json
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(arguments: CreateJsonTestOption.testCases)
|
||||||
|
func createJson(input: CreateJsonTestOption) {
|
||||||
|
withTestLogger(key: "generateJson") {
|
||||||
|
$0.coders.jsonEncoder = { jsonEncoder }
|
||||||
|
$0.configurationClient = .mock(input.configuration)
|
||||||
|
} operation: {
|
||||||
|
@Dependency(\.coders) var coders
|
||||||
|
|
||||||
|
let jsonData = try? input.options.createJSONData(
|
||||||
|
configuration: input.configuration,
|
||||||
|
encoder: coders.jsonEncoder()
|
||||||
|
)
|
||||||
|
|
||||||
|
switch input.expectation {
|
||||||
|
case let .success(expected):
|
||||||
|
let json = String(data: jsonData!, encoding: .utf8)!
|
||||||
|
if json != expected {
|
||||||
|
print("json:", json)
|
||||||
|
print("expected:", expected)
|
||||||
|
}
|
||||||
|
#expect(json == expected)
|
||||||
|
case .failure:
|
||||||
|
#expect(jsonData == nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withMockConfiguration(
|
||||||
|
_ capturing: CommandClient.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 withDependencies {
|
||||||
|
$0.configurationClient = .mock(configuration)
|
||||||
|
$0.commandClient = .capturing(capturing)
|
||||||
|
$0.playbookClient = .liveValue
|
||||||
|
setupDependencies(&$0)
|
||||||
|
} operation: {
|
||||||
|
try await operation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CreateJsonTestOption: Sendable {
|
||||||
|
let options: PlaybookClient.RunPlaybook.CreateOptions
|
||||||
|
let configuration: Configuration
|
||||||
|
let expectation: Result<String, TestError>
|
||||||
|
|
||||||
|
static let testCases: [Self] = [
|
||||||
|
CreateJsonTestOption(
|
||||||
|
options: .init(
|
||||||
|
projectDirectory: "/project",
|
||||||
|
shared: PlaybookClientTests.sharedRunOptions,
|
||||||
|
template: .init(url: nil, version: nil, directory: nil),
|
||||||
|
useLocalTemplateDirectory: true
|
||||||
|
),
|
||||||
|
configuration: .init(),
|
||||||
|
expectation: .failing
|
||||||
|
),
|
||||||
|
CreateJsonTestOption(
|
||||||
|
options: .init(
|
||||||
|
projectDirectory: "/project",
|
||||||
|
shared: PlaybookClientTests.sharedRunOptions,
|
||||||
|
template: .init(url: nil, version: nil, directory: nil),
|
||||||
|
useLocalTemplateDirectory: false
|
||||||
|
),
|
||||||
|
configuration: .init(),
|
||||||
|
expectation: .failing
|
||||||
|
),
|
||||||
|
CreateJsonTestOption(
|
||||||
|
options: .init(
|
||||||
|
projectDirectory: "/project",
|
||||||
|
shared: PlaybookClientTests.sharedRunOptions,
|
||||||
|
template: .init(url: nil, version: nil, directory: "/template"),
|
||||||
|
useLocalTemplateDirectory: true
|
||||||
|
),
|
||||||
|
configuration: .init(template: .init(directory: "/template")),
|
||||||
|
expectation: .success("""
|
||||||
|
{
|
||||||
|
"template" : {
|
||||||
|
"path" : "/template"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
),
|
||||||
|
CreateJsonTestOption(
|
||||||
|
options: .init(
|
||||||
|
projectDirectory: "/project",
|
||||||
|
shared: PlaybookClientTests.sharedRunOptions,
|
||||||
|
template: .init(url: nil, version: nil, directory: "/template"),
|
||||||
|
useLocalTemplateDirectory: true
|
||||||
|
),
|
||||||
|
configuration: .init(template: .init(directory: "/template")),
|
||||||
|
expectation: .success("""
|
||||||
|
{
|
||||||
|
"template" : {
|
||||||
|
"path" : "/template"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
),
|
||||||
|
CreateJsonTestOption(
|
||||||
|
options: .init(
|
||||||
|
projectDirectory: "/project",
|
||||||
|
shared: PlaybookClientTests.sharedRunOptions,
|
||||||
|
template: .init(url: "https://git.example.com/template.git", version: "main", directory: nil),
|
||||||
|
useLocalTemplateDirectory: false
|
||||||
|
),
|
||||||
|
configuration: .init(),
|
||||||
|
expectation: .success("""
|
||||||
|
{
|
||||||
|
"template" : {
|
||||||
|
"repo" : {
|
||||||
|
"url" : "https://git.example.com/template.git",
|
||||||
|
"version" : "main"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
),
|
||||||
|
CreateJsonTestOption(
|
||||||
|
options: .init(
|
||||||
|
projectDirectory: "/project",
|
||||||
|
shared: PlaybookClientTests.sharedRunOptions,
|
||||||
|
template: .init(url: "https://git.example.com/template.git", version: "v0.1.0", directory: nil),
|
||||||
|
useLocalTemplateDirectory: false
|
||||||
|
),
|
||||||
|
configuration: .init(template: .init(url: "https://git.example.com/template.git", version: "v0.1.0")),
|
||||||
|
expectation: .success("""
|
||||||
|
{
|
||||||
|
"template" : {
|
||||||
|
"repo" : {
|
||||||
|
"url" : "https://git.example.com/template.git",
|
||||||
|
"version" : "v0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Result where Failure == TestError {
|
||||||
|
static var failing: Self { .failure(TestError()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {}
|
||||||
|
|
||||||
|
extension Tag {
|
||||||
|
@Tag static var repository: Self
|
||||||
|
@Tag static var run: Self
|
||||||
|
}
|
||||||
|
|
||||||
|
let jsonEncoder: JSONEncoder = {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes, .sortedKeys]
|
||||||
|
return encoder
|
||||||
|
}()
|
||||||
|
|||||||
Reference in New Issue
Block a user