139 lines
5.4 KiB
Swift
139 lines
5.4 KiB
Swift
//
|
|
// SQLitePreferenceStoreTests.swift
|
|
//
|
|
// Copyright 2026 Brendan Szymanski <hello@bscubed.dev>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
|
|
import Foundation
|
|
import Testing
|
|
|
|
@testable import LuminateCore
|
|
@testable import LuminateStore
|
|
|
|
@Suite struct SQLitePreferenceStoreTests {
|
|
@Test("A fresh database has schema version one and no preferences")
|
|
func freshDatabase() throws {
|
|
let (url, directory) = makeDatabaseURL()
|
|
defer { try? FileManager.default.removeItem(at: directory) }
|
|
|
|
let store = try SQLitePreferenceStore(url: url)
|
|
#expect(store.initialSnapshot.isEmpty)
|
|
let database = try SQLiteDatabase(url: url)
|
|
#expect(try database.userVersion == 1)
|
|
}
|
|
|
|
@Test("Queued values survive flush and reopen")
|
|
func persistsValue() async throws {
|
|
let (url, directory) = makeDatabaseURL()
|
|
defer { try? FileManager.default.removeItem(at: directory) }
|
|
|
|
let store = try SQLitePreferenceStore(url: url)
|
|
store.enqueue("server.url", .text("http://jellyfin.test:8096"))
|
|
try await store.flush()
|
|
|
|
let reopened = try SQLitePreferenceStore(url: url)
|
|
#expect(reopened.initialSnapshot["server.url"] == .text("http://jellyfin.test:8096"))
|
|
}
|
|
|
|
@Test("A nil queued value deletes the stored row")
|
|
func deletesValue() async throws {
|
|
let (url, directory) = makeDatabaseURL()
|
|
defer { try? FileManager.default.removeItem(at: directory) }
|
|
|
|
let store = try SQLitePreferenceStore(url: url)
|
|
store.enqueue("auth.accessToken", .text("secret"))
|
|
store.enqueue("auth.accessToken", nil)
|
|
try await store.flush()
|
|
|
|
let reopened = try SQLitePreferenceStore(url: url)
|
|
#expect(reopened.initialSnapshot["auth.accessToken"] == nil)
|
|
}
|
|
|
|
@Test("Every SQLite storage class round trips")
|
|
func storageClassesRoundTrip() async throws {
|
|
let (url, directory) = makeDatabaseURL()
|
|
defer { try? FileManager.default.removeItem(at: directory) }
|
|
|
|
let store = try SQLitePreferenceStore(url: url)
|
|
let values: [String: StoredPreference] = [
|
|
"integer": .integer(42),
|
|
"real": .real(3.5),
|
|
"text": .text("value"),
|
|
"blob": .blob(Data([1, 2, 3])),
|
|
"emptyBlob": .blob(Data()),
|
|
"nulText": .text("a\0b"),
|
|
]
|
|
for (key, value) in values {
|
|
store.enqueue(key, value)
|
|
}
|
|
try await store.flush()
|
|
|
|
let reopened = try SQLitePreferenceStore(url: url)
|
|
#expect(reopened.initialSnapshot == values)
|
|
}
|
|
|
|
@Test("Writes to one key are applied in submission order")
|
|
func preservesOrder() async throws {
|
|
let (url, directory) = makeDatabaseURL()
|
|
defer { try? FileManager.default.removeItem(at: directory) }
|
|
|
|
let store = try SQLitePreferenceStore(url: url)
|
|
store.enqueue("server.url", .text("first"))
|
|
store.enqueue("server.url", .text("second"))
|
|
try await store.flush()
|
|
|
|
let reopened = try SQLitePreferenceStore(url: url)
|
|
#expect(reopened.initialSnapshot["server.url"] == .text("second"))
|
|
}
|
|
|
|
@Test("A write failure surfaces once and does not repeat on the next flush")
|
|
func writeFailureIsRetainedThenCleared() async throws {
|
|
let (url, directory) = makeDatabaseURL()
|
|
defer { try? FileManager.default.removeItem(at: directory) }
|
|
|
|
let store = try SQLitePreferenceStore(url: url)
|
|
let saboteur = try SQLiteDatabase(url: url)
|
|
try saboteur.exec("DROP TABLE preference;")
|
|
store.enqueue("server.url", .text("http://x"))
|
|
await #expect(throws: PreferenceStoreError.self) { try await store.flush() }
|
|
try await store.flush()
|
|
}
|
|
|
|
@Test("Opening a database at a directory path fails to open")
|
|
func opensDirectoryFails() throws {
|
|
let directory = FileManager.default.temporaryDirectory
|
|
.appending(path: "luminate-store-dir-\(UUID().uuidString)", directoryHint: .isDirectory)
|
|
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
|
defer { try? FileManager.default.removeItem(at: directory) }
|
|
|
|
do {
|
|
_ = try SQLiteDatabase(url: directory)
|
|
Issue.record("Expected SQLiteDatabase(url:) to throw for a directory path")
|
|
} catch PreferenceStoreError.cannotOpen(let path, _) {
|
|
#expect(path == directory.path)
|
|
} catch {
|
|
Issue.record("Expected PreferenceStoreError.cannotOpen, got \(error)")
|
|
}
|
|
}
|
|
|
|
private func makeDatabaseURL() -> (URL, URL) {
|
|
let directory = FileManager.default.temporaryDirectory
|
|
.appending(path: "luminate-store-\(UUID().uuidString)", directoryHint: .isDirectory)
|
|
return (directory.appending(path: "preferences.sqlite"), directory)
|
|
}
|
|
}
|