Phase 4 seam: in-memory DatabaseService + DB injection for ContextSelectionService

DatabaseService gains a testable init(dbQueue:) plus a makeInMemory()
convenience and schema-introspection helpers (tableExists/columnNames),
so migration tests don't need the test target to import GRDB directly.
ContextSelectionService now takes an injected DatabaseService (defaults
to .shared) and its DB-coupled methods (smartSelection,
getSummariesForExcludedRange, isMessageStarred) are bumped to internal.
No logic changes to existing call sites.
This commit is contained in:
2026-07-24 09:16:42 +02:00
parent 02bf73fec6
commit c6a7439648
2 changed files with 36 additions and 11 deletions
+25 -5
View File
@@ -154,7 +154,14 @@ final class DatabaseService: Sendable {
(try? isoStyle.parse(string)) ?? (try? Date(string, strategy: .iso8601))
}
nonisolated private init() {
/// Test seam: build a DatabaseService around a caller-provided queue (e.g. an in-memory `DatabaseQueue()`).
/// Each test should create its own fresh instance never mutate `.shared` from a test.
nonisolated init(dbQueue: DatabaseQueue) {
self.dbQueue = dbQueue
try! migrator.migrate(dbQueue)
}
nonisolated private convenience init() {
let fileManager = FileManager.default
let appSupport = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let dbDirectory = appSupport.appendingPathComponent("oAI", isDirectory: true)
@@ -163,12 +170,25 @@ final class DatabaseService: Sendable {
let dbPath = dbDirectory.appendingPathComponent("oai_conversations.db").path
Log.db.info("Opening database at \(dbPath)")
dbQueue = try! DatabaseQueue(path: dbPath)
try! migrator.migrate(dbQueue)
self.init(dbQueue: try! DatabaseQueue(path: dbPath))
}
private nonisolated var migrator: DatabaseMigrator {
/// Test seam: a throwaway in-memory instance with all migrations applied. Avoids requiring
/// `import GRDB` from the test target just to construct a `DatabaseQueue()`.
nonisolated static func makeInMemory() -> DatabaseService {
DatabaseService(dbQueue: try! DatabaseQueue())
}
/// Test seam: schema introspection, so migration tests don't need `import GRDB` either.
nonisolated func tableExists(_ name: String) -> Bool {
(try? dbQueue.read { db in try db.tableExists(name) }) ?? false
}
nonisolated func columnNames(in table: String) -> [String] {
(try? dbQueue.read { db in try db.columns(in: table).map(\.name) }) ?? []
}
nonisolated var migrator: DatabaseMigrator {
var migrator = DatabaseMigrator()
migrator.registerMigration("v1") { db in