mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-22 22:21:12 +00:00
* refactor: compose native SQLite bootstrap tables * refactor: share native SQLite state bootstrap * refactor(macos): move PortGuardian state to SQLite * test: align PortGuardian future-schema fixture * fix(macos): reserve PortGuardian ledger across tunnel spawn * fix(ci): align PortGuardian migration gates * fix(macos): tolerate vanished SQLite sidecars * fix(macos): revalidate PortGuardian writes * test(macos): use sqlite identity bootstrap
175 lines
6.7 KiB
Swift
175 lines
6.7 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import OpenClawNativeState
|
|
|
|
struct OpenClawNativeStateSQLiteTests {
|
|
@Test
|
|
func `version zero composes exact canonical tables`() throws {
|
|
try self.withDatabase { database in
|
|
try database.withImmediateTransaction {
|
|
try database.ensureCanonicalTable(.macosPortGuardianRecords)
|
|
try database.execute("""
|
|
INSERT INTO macos_port_guardian_records (pid, port, command, mode, timestamp)
|
|
VALUES (4242, 18789, '/usr/bin/ssh', 'remote', 42.5)
|
|
""")
|
|
try database.ensureCanonicalTable(.deviceIdentities)
|
|
}
|
|
|
|
#expect(try database.scalarInt64("PRAGMA user_version") == 0)
|
|
#expect(try database.scalarInt64(
|
|
"SELECT COUNT(*) FROM macos_port_guardian_records WHERE pid = 4242") == 1)
|
|
#expect(try database.scalarInt64(
|
|
"SELECT COUNT(*) FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%'") == 4)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `version zero rejects incomplete known sibling schema`() throws {
|
|
try self.withDatabase { database in
|
|
try database.execute("""
|
|
CREATE TABLE macos_port_guardian_records (
|
|
pid INTEGER NOT NULL PRIMARY KEY,
|
|
port INTEGER NOT NULL,
|
|
command TEXT NOT NULL,
|
|
mode TEXT NOT NULL,
|
|
timestamp REAL NOT NULL
|
|
) STRICT
|
|
""")
|
|
|
|
#expect(throws: OpenClawNativeStateError.self) {
|
|
try database.ensureCanonicalTable(.deviceIdentities)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `versioned database never synthesizes a missing canonical table`() throws {
|
|
try self.withDatabase { database in
|
|
try database.execute("""
|
|
CREATE TABLE schema_meta (
|
|
meta_key TEXT NOT NULL PRIMARY KEY,
|
|
role TEXT NOT NULL,
|
|
schema_version INTEGER NOT NULL
|
|
) STRICT;
|
|
INSERT INTO schema_meta (meta_key, role, schema_version)
|
|
VALUES ('primary', 'global', 3);
|
|
PRAGMA user_version = 3;
|
|
""")
|
|
|
|
#expect(throws: OpenClawNativeStateError.self) {
|
|
try database.ensureCanonicalTable(.deviceIdentities)
|
|
}
|
|
#expect(try database.schemaObjectExists(type: "table", name: "device_identities") == false)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `immediate transaction rolls back all writes`() throws {
|
|
try self.withDatabase { database in
|
|
try database.withImmediateTransaction {
|
|
try database.ensureCanonicalTable(.macosPortGuardianRecords)
|
|
}
|
|
|
|
#expect(throws: TestError.self) {
|
|
try database.withImmediateTransaction {
|
|
try database.execute("""
|
|
INSERT INTO macos_port_guardian_records (pid, port, command, mode, timestamp)
|
|
VALUES (4242, 18789, '/usr/bin/ssh', 'remote', 42.5)
|
|
""")
|
|
throw TestError.expected
|
|
}
|
|
}
|
|
#expect(try database.scalarInt64("SELECT COUNT(*) FROM macos_port_guardian_records") == 0)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `concurrent version zero stores compose different tables`() async throws {
|
|
let directory = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
|
defer { try? FileManager.default.removeItem(at: directory) }
|
|
let databaseURL = directory.appendingPathComponent("openclaw.sqlite", isDirectory: false)
|
|
|
|
try await withThrowingTaskGroup(of: Void.self) { group in
|
|
for table in [
|
|
OpenClawNativeStateCanonicalTable.deviceIdentities,
|
|
.macosPortGuardianRecords,
|
|
] {
|
|
group.addTask {
|
|
let database = try OpenClawNativeStateSQLite(databaseURL: databaseURL)
|
|
try database.withImmediateTransaction {
|
|
try database.ensureCanonicalTable(table)
|
|
}
|
|
}
|
|
}
|
|
try await group.waitForAll()
|
|
}
|
|
|
|
let database = try OpenClawNativeStateSQLite(databaseURL: databaseURL)
|
|
#expect(try database.scalarInt64(
|
|
"SELECT COUNT(*) FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%'") == 4)
|
|
}
|
|
|
|
@Test
|
|
func `vanished transient sidecar does not fail committed write cleanup`() throws {
|
|
let databaseURL = URL(fileURLWithPath: "/tmp/openclaw-native-state.sqlite")
|
|
var attemptedPaths: [String] = []
|
|
|
|
try OpenClawNativeStateSQLite.secureDatabaseFiles(
|
|
databaseURL,
|
|
fileExists: { _ in true },
|
|
setAttributes: { _, path in
|
|
attemptedPaths.append(path)
|
|
if path.hasSuffix("-wal") {
|
|
throw NSError(domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError)
|
|
}
|
|
})
|
|
|
|
#expect(attemptedPaths == [
|
|
databaseURL.path,
|
|
databaseURL.path + "-wal",
|
|
databaseURL.path + "-shm",
|
|
databaseURL.path + "-journal",
|
|
])
|
|
}
|
|
|
|
@Test
|
|
func `missing database or nonmissing sidecar failure remains fatal`() {
|
|
let databaseURL = URL(fileURLWithPath: "/tmp/openclaw-native-state.sqlite")
|
|
let missing = NSError(domain: NSPOSIXErrorDomain, code: Int(POSIXErrorCode.ENOENT.rawValue))
|
|
let denied = NSError(domain: NSPOSIXErrorDomain, code: Int(POSIXErrorCode.EACCES.rawValue))
|
|
|
|
#expect(throws: NSError.self) {
|
|
try OpenClawNativeStateSQLite.secureDatabaseFiles(
|
|
databaseURL,
|
|
fileExists: { _ in true },
|
|
setAttributes: { _, path in
|
|
if path == databaseURL.path { throw missing }
|
|
})
|
|
}
|
|
#expect(throws: NSError.self) {
|
|
try OpenClawNativeStateSQLite.secureDatabaseFiles(
|
|
databaseURL,
|
|
fileExists: { _ in true },
|
|
setAttributes: { _, path in
|
|
if path.hasSuffix("-wal") { throw denied }
|
|
})
|
|
}
|
|
}
|
|
|
|
private enum TestError: Error {
|
|
case expected
|
|
}
|
|
|
|
private func withDatabase(
|
|
body: (OpenClawNativeStateSQLite) throws -> Void) throws
|
|
{
|
|
let directory = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
|
defer { try? FileManager.default.removeItem(at: directory) }
|
|
let database = try OpenClawNativeStateSQLite(
|
|
databaseURL: directory.appendingPathComponent("openclaw.sqlite", isDirectory: false))
|
|
try body(database)
|
|
}
|
|
}
|