diff --git a/CHANGELOG.md b/CHANGELOG.md index 8de81a340d..68f4504087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changes to a theme file made outside TablePro not shown until Settings > Appearance was opened. - Theme files with a JSON syntax error reported without the line and column. - Two themes with the same name impossible to tell apart in the theme pickers. +- No warning on a connection in the Welcome window whose driver plugin failed to load or is out of date. ## [0.81.0] - 2026-10-16 diff --git a/TablePro/ViewModels/WelcomeViewModel+Rows.swift b/TablePro/ViewModels/WelcomeViewModel+Rows.swift index 0b7a628804..ffc62518aa 100644 --- a/TablePro/ViewModels/WelcomeViewModel+Rows.swift +++ b/TablePro/ViewModels/WelcomeViewModel+Rows.swift @@ -1,8 +1,3 @@ -// -// WelcomeViewModel+Rows.swift -// TablePro -// - import AppKit import TableProConnectionLibrary @@ -38,8 +33,8 @@ extension WelcomeViewModel { let group = connection.groupId.flatMap { groupsById[$0] } let groupPath = group.map { groupGraph.pathNames(to: $0.id) } ?? [] let typeId = connection.type.pluginTypeId - let isDriverRejected = services.pluginManager.rejectedPlugins.contains { rejected in - rejected.bundleId == typeId || rejected.registryId == typeId + let isDriverRejected = services.pluginManager.rejectedPlugins.contains { + $0.providedDatabaseTypeIds.contains(typeId) } return WelcomeRowPresentation.savedConnection( connection, diff --git a/TableProTests/ViewModels/WelcomeViewModelTests.swift b/TableProTests/ViewModels/WelcomeViewModelTests.swift index 369a4f1470..70bd0cecb9 100644 --- a/TableProTests/ViewModels/WelcomeViewModelTests.swift +++ b/TableProTests/ViewModels/WelcomeViewModelTests.swift @@ -56,10 +56,11 @@ final class WelcomeViewModelTests { private func makeViewModel( importableAppDetector: @escaping @MainActor () -> Bool = { false }, - isUITestSandbox: Bool = false + isUITestSandbox: Bool = false, + pluginManager: PluginManager = AppServices.live.pluginManager ) -> WelcomeViewModel { WelcomeViewModel( - services: makeServices(), + services: makeServices(pluginManager: pluginManager), importableAppDetector: importableAppDetector, groupExpansionStore: WelcomeGroupExpansionStore(defaults: defaults), recentConnections: recents, @@ -72,7 +73,7 @@ final class WelcomeViewModelTests { try? FileManager.default.removeItem(at: connectionFileURL.deletingLastPathComponent()) } - private func makeServices() -> AppServices { + private func makeServices(pluginManager: PluginManager) -> AppServices { let live = AppServices.live return AppServices( appEvents: live.appEvents, @@ -80,7 +81,7 @@ final class WelcomeViewModelTests { appSettingsStorage: AppSettingsStorage(userDefaults: defaults), connectionStorage: connectionStorage, databaseManager: live.databaseManager, - pluginManager: live.pluginManager, + pluginManager: pluginManager, schemaService: live.schemaService, schemaRefreshService: live.schemaRefreshService, schemaProviderRegistry: live.schemaProviderRegistry, @@ -776,6 +777,42 @@ final class WelcomeViewModelTests { #expect(viewModel.libraryErrorMessage != nil, "A refused delete must say so") } + // MARK: - Driver Plugins + + @Test func testARejectedPluginFlagsEveryConnectionOfTheTypesItProvides() { + let pluginManager = PluginManager( + userDefaults: defaults, + builtInPluginsURL: nil, + userPluginsDir: connectionFileURL.deletingLastPathComponent().appendingPathComponent("Plugins") + ) + pluginManager.rejectedPlugins = [RejectedPlugin( + url: URL(fileURLWithPath: "/tmp/CassandraDriver.tableplugin"), + bundleId: "com.TablePro.CassandraDriver", + registryId: "com.TablePro.CassandraDriver", + name: "Cassandra Driver", + reason: "Built for an older PluginKit", + isOutdated: true, + providedDatabaseTypeIds: ["Cassandra", "ScyllaDB"] + )] + let welcome = makeViewModel(pluginManager: pluginManager) + let cassandra = DatabaseConnection(name: "Ring", type: .cassandra, sortOrder: 0) + let scylla = DatabaseConnection(name: "Scylla", type: .scylladb, sortOrder: 1) + let mysql = DatabaseConnection(name: "Shop", type: .mysql, sortOrder: 2) + save([cassandra, scylla, mysql]) + + welcome.loadConnections() + + func isDriverRejected(_ connection: DatabaseConnection) -> Bool? { + guard case .connection(let row) = welcome.rowModel(for: .connection(connection.id, section: .connections)) else { + return nil + } + return row.isDriverRejected + } + #expect(isDriverRejected(cassandra) == true) + #expect(isDriverRejected(scylla) == true) + #expect(isDriverRejected(mysql) == false) + } + // MARK: - Shared Connections @Test func testSharedRowIdsAreDerivedFromThePayloadAndAreStable() {