import Foundation import Logging import SQLKit import Testing @testable import Persistence @Suite( "Service method", .tags(.method) ) struct ServiceTests { // MARK: Methods tests @Test func `registers an SQLite database as the default for the in-memory driver`() async throws { let service = Service( driver: .inMemory, logger: Logger(label: "test") ) let fluent = service() let database = fluent.db() as? any SQLDatabase try await fluent.shutdown() let dialect = try #require(database?.dialect) #expect(dialect.name == "sqlite") } @Test func `registers a MySQL database as the default for the mysql driver`() async throws { // Resolving the default database opens no connection — pooling is lazy — so no server // needs to be listening on the configured host and port. let service = Service( driver: .mysql( .init( host: "127.0.0.1", port: 3306, name: "loud", username: "loud", password: "loud", tls: .off, maxConnectionsPerEventLoop: 1 ) ), logger: Logger(label: "test") ) let fluent = service() let database = fluent.db() as? any SQLDatabase try await fluent.shutdown() let dialect = try #require(database?.dialect) #expect(dialect.name == "mysql") } @Test func `builds a usable in-memory database`() async throws { let service = Service( driver: .inMemory, logger: Logger(label: "test") ) let fluent = service() do { let database = try #require(fluent.db() as? any SQLDatabase) try await database.raw("SELECT 1").run() } catch { try? await fluent.shutdown() throw error } try await fluent.shutdown() } @Test("in-memory: migrate, insert, read back") func inMemoryRoundTrip() async throws { try await roundTrip(driver: .inMemory) } @Test( "mysql: migrate, insert, read back", .enabled(if: mysqlDriver != nil) ) func mysqlRoundTrip() async throws { try await roundTrip( driver: mysqlDriver!, revertAfter: true ) } } // MARK: - Helpers private extension ServiceTests { /// Migrates, inserts, and reads back a record against the given driver, shutting the pool down after. /// /// The `Fluent` service normally owns pool shutdown via its `run()` in the service group; outside that, /// the test must shut it down explicitly — even on failure — or the pool asserts on `deinit`. /// - Parameters: /// - driver: the persistence backend to exercise. /// - revertAfter: whether to revert the migrations afterwards; set for a shared database (the /// in-memory database is discarded on shutdown, so it needs no revert). func roundTrip( driver: Persistence.Driver, revertAfter: Bool = false ) async throws { let prepareDB = PrepareDB() let service = Service( driver: driver, logger: Logger(label: "test") ) let fluent = service() do { await prepareDB(for: fluent) try await fluent.migrate() let repository = ExampleRepository(fluent: fluent) let created = try await repository.create(name: "loud") #expect(try await repository.all().contains(created)) if revertAfter { try await fluent.revert() } } catch { try? await fluent.shutdown() throw error } try await fluent.shutdown() } } /// The MySQL/MariaDB driver built from the `MYSQL_TEST_*` environment variables, or `nil` when the gate /// variable `MYSQL_TEST_HOST` is unset — in which case the MySQL integration test is skipped, so the suite /// stays runnable with no database available. private let mysqlDriver: Persistence.Driver? = { let environment = ProcessInfo.processInfo.environment guard let host = environment["MYSQL_TEST_HOST"] else { return nil } return .mysql( .init( host: host, port: environment["MYSQL_TEST_PORT"].flatMap(Int.init) ?? 3306, name: environment["MYSQL_TEST_NAME"] ?? "loud", username: environment["MYSQL_TEST_USERNAME"] ?? "loud", password: environment["MYSQL_TEST_PASSWORD"] ?? "loud", tls: .off, maxConnectionsPerEventLoop: 2 ) ) }()