manageable-users/Sources/ManageableUsers/Utilities/MockDatabase.swift

116 lines
3.5 KiB
Swift

import SQLKit
import Logging
import NIOCore
import Vapor
#if DEBUG
public class MockDatabase: @unchecked Sendable {
public let logger: Logger = Logger (label: "MockDatabase")
public let eventLoop: any EventLoop
public var queries = [any SQLExpression]()
public var results = [[any SQLRow]]()
public init (eventLoop: any EventLoop) {
self.eventLoop = eventLoop
}
func withConnection<T> (_ body: (any SQLDatabase) async throws -> T) async rethrows -> T {
return try await body (self)
}
public let dialect: any SQLDialect = Dialect()
struct Dialect: SQLDialect {
let name = "MockDatabase"
let identifierQuote: any SQLExpression = SQLRaw(#"""#)
let supportsAutoIncrement = false
let autoIncrementClause: any SQLExpression = SQLRaw("GENERATED BY DEFAULT AS IDENTITY")
func bindPlaceholder(at position: Int) -> any SQLExpression {
SQLRaw("$\(position)")
}
func literalBoolean(_ value: Bool) -> any SQLKit.SQLExpression {
SQLRaw("\(value)")
}
}
}
enum MockDatabaseErrors: Error {
case empty
}
extension MockDatabase: SQLDatabase {
public func execute (sql query: any SQLExpression, _ onRow: @escaping @Sendable (any SQLRow) -> ()) -> EventLoopFuture<Void> {
self.queries.append (query)
guard !results.isEmpty else {
return eventLoop.makeFailedFuture (MockDatabaseErrors.empty)
}
for try row in results.removeFirst() {
onRow (row)
}
return eventLoop.makeSucceededFuture(())
}
}
extension MockDatabase {
public struct Row: SQLRow {
public let values: [String: any Sendable]
public init(values: [String : any Sendable]) {
self.values = values
}
public var allColumns: [String] { values.keys.sorted() }
public func contains (column: String) -> Bool {
values.keys.contains (column)
}
public func decodeNil (column: String) throws -> Bool { !values.keys.contains (column) }
public func decode<D> (column: String, as: D.Type) throws -> D where D : Decodable {
guard let value = values[column] as? D else {
throw DecodingError.dataCorrupted (DecodingError.Context (codingPath: [], debugDescription: "Cannot decode \(D.self) from \(String (describing: self))"))
}
return value
}
}
}
extension Application {
protocol MockDatabaseConnectionKey: StorageKey where Value == MockDatabase {}
public struct MockDatabaseKey: MockDatabaseConnectionKey {
public typealias Value = MockDatabase
}
}
/// A wrapper around the errors that can occur during a transaction.
public struct TransactionError: Error {
/// The file in which the transaction was started
public var file: String
/// The line in which the transaction was started
public var line: Int
/// The error thrown when running the `BEGIN` query
public var beginError: (any Error)?
/// The error thrown in the transaction closure
public var closureError: (any Error)?
/// The error thrown while rolling the transaction back. If the ``closureError`` is set,
/// but the ``rollbackError`` is empty, the rollback was successful. If the ``rollbackError``
/// is set, the rollback failed.
public var rollbackError: (any Error)?
/// The error thrown while commiting the transaction.
public var commitError: (any Error)?
}
#endif