From 1b9c612752f8f38640a1d814c1f9518a61cc59c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Qu=E1=BB=91c=20=C4=90=E1=BA=A1t?= Date: Thu, 21 May 2026 12:32:55 +0700 Subject: [PATCH 1/2] fix(connections): import DBeaver username from credentials-config.json (#1355) --- CHANGELOG.md | 4 ++ .../Export/ForeignApp/DBeaverImporter.swift | 21 ++++--- .../ForeignApp/DBeaverImporterTests.swift | 58 ++++++++++++++++++- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 930b10f0c..a70bf7529 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Importing connections from DBeaver now brings over the username (#1355) + ## [0.43.1] - 2026-05-20 ### Added diff --git a/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift b/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift index e98b1dc98..94da91b1f 100644 --- a/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift +++ b/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift @@ -64,12 +64,9 @@ struct DBeaverImporter: ForeignAppImporter { let foldersDict = json["folders"] as? [String: [String: Any]] ?? [:] - var credentialsMap: [String: [String: Any]] = [:] - if includePasswords { - let credentialsURL = dataSourcesURL.deletingLastPathComponent() - .appendingPathComponent("credentials-config.json") - credentialsMap = loadCredentials(from: credentialsURL) - } + let credentialsURL = dataSourcesURL.deletingLastPathComponent() + .appendingPathComponent("credentials-config.json") + let credentialsMap = loadCredentials(from: credentialsURL) var exportableConnections: [ExportableConnection] = [] var groupNames: Set = [] @@ -77,7 +74,10 @@ struct DBeaverImporter: ForeignAppImporter { for (connId, connDict) in connectionsDict { do { - let conn = try parseConnection(connId, dict: connDict, folders: foldersDict) + let credentialUsername = (credentialsMap[connId]?["#connection"] as? [String: Any])?["user"] as? String + let conn = try parseConnection( + connId, dict: connDict, folders: foldersDict, credentialUsername: credentialUsername + ) let index = exportableConnections.count exportableConnections.append(conn) @@ -159,7 +159,8 @@ struct DBeaverImporter: ForeignAppImporter { private func parseConnection( _ connId: String, dict: [String: Any], - folders: [String: [String: Any]] + folders: [String: [String: Any]], + credentialUsername: String? ) throws -> ExportableConnection { let name = dict["name"] as? String ?? connId let provider = dict["provider"] as? String ?? "" @@ -176,7 +177,9 @@ struct DBeaverImporter: ForeignAppImporter { port = defaultPort(for: dbType) } let database = config["database"] as? String ?? config["url"] as? String ?? "" - let username = config["user"] as? String ?? "" + let username = [credentialUsername, config["user"] as? String] + .compactMap { $0 } + .first { !$0.isEmpty } ?? "" let folderPath = dict["folder"] as? String let groupName: String? diff --git a/TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift b/TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift index 44d4ddd34..cb56eed2e 100644 --- a/TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift +++ b/TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift @@ -77,7 +77,7 @@ struct DBeaverImporterTests { provider: String = "postgresql", host: String = "db.example.com", port: Any? = 5432, - user: String = "admin", + user: String? = "admin", database: String = "mydb", folder: String? = nil, sshEnabled: Bool = false, @@ -95,9 +95,11 @@ struct DBeaverImporterTests { ) -> [String: Any] { var config: [String: Any] = [ "host": host, - "user": user, "database": database ] + if let user = user { + config["user"] = user + } if let port = port { config["port"] = port } @@ -442,13 +444,63 @@ struct DBeaverImporterTests { "pg-1": makeConnection(name: "PG") ] try writeDataSources(makeDataSourcesJSON(connections: connections)) - // Even if credentials file exists, it should not be read + // Passwords must not be exposed without includePasswords (the username still imports). try writeCredentials(["pg-1": ["#connection": ["password": "secret"]]]) let result = try importer.importConnections(includePasswords: false) #expect(result.envelope.credentials == nil) } + // MARK: - Username (credentials-config.json) + + @Test("Username imports from credentials-config.json") + func testImportConnections_usernameFromCredentials() throws { + let connections: [String: [String: Any]] = [ + "pg-1": makeConnection(name: "PG", user: nil) + ] + try writeDataSources(makeDataSourcesJSON(connections: connections)) + try writeCredentials(["pg-1": ["#connection": ["user": "sameer", "password": "p"]]]) + + let result = try importer.importConnections(includePasswords: true) + #expect(result.envelope.connections[0].username == "sameer") + } + + @Test("Username imports even when passwords are excluded") + func testImportConnections_usernameImportsWithoutPasswords() throws { + let connections: [String: [String: Any]] = [ + "pg-1": makeConnection(name: "PG", user: nil) + ] + try writeDataSources(makeDataSourcesJSON(connections: connections)) + try writeCredentials(["pg-1": ["#connection": ["user": "sameer", "password": "p"]]]) + + let result = try importer.importConnections(includePasswords: false) + #expect(result.envelope.connections[0].username == "sameer") + #expect(result.envelope.credentials == nil) + } + + @Test("Username falls back to data-sources configuration.user") + func testImportConnections_usernameFallsBackToConfig() throws { + let connections: [String: [String: Any]] = [ + "pg-1": makeConnection(name: "PG", user: "configuser") + ] + try writeDataSources(makeDataSourcesJSON(connections: connections)) + + let result = try importer.importConnections(includePasswords: true) + #expect(result.envelope.connections[0].username == "configuser") + } + + @Test("Credentials username takes precedence over configuration.user") + func testImportConnections_credentialsUsernameWins() throws { + let connections: [String: [String: Any]] = [ + "pg-1": makeConnection(name: "PG", user: "configuser") + ] + try writeDataSources(makeDataSourcesJSON(connections: connections)) + try writeCredentials(["pg-1": ["#connection": ["user": "creduser"]]]) + + let result = try importer.importConnections(includePasswords: true) + #expect(result.envelope.connections[0].username == "creduser") + } + @Test("importConnections invalid JSON throws parse error") func testImportConnections_invalidJSON_throwsParseError() throws { // Write invalid data to data-sources.json From d8759c139948e1e4419590ff608f55d1ee6a4a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Qu=E1=BB=91c=20=C4=90=E1=BA=A1t?= Date: Thu, 21 May 2026 12:51:17 +0700 Subject: [PATCH 2/2] test(connections): cover empty-credentials username fallback in DBeaver import --- .../Services/ForeignApp/DBeaverImporterTests.swift | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift b/TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift index cb56eed2e..d97bd2913 100644 --- a/TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift +++ b/TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift @@ -444,7 +444,6 @@ struct DBeaverImporterTests { "pg-1": makeConnection(name: "PG") ] try writeDataSources(makeDataSourcesJSON(connections: connections)) - // Passwords must not be exposed without includePasswords (the username still imports). try writeCredentials(["pg-1": ["#connection": ["password": "secret"]]]) let result = try importer.importConnections(includePasswords: false) @@ -501,6 +500,18 @@ struct DBeaverImporterTests { #expect(result.envelope.connections[0].username == "creduser") } + @Test("Empty credentials username falls back to configuration.user") + func testImportConnections_emptyCredentialsUsernameFallsBack() throws { + let connections: [String: [String: Any]] = [ + "pg-1": makeConnection(name: "PG", user: "configuser") + ] + try writeDataSources(makeDataSourcesJSON(connections: connections)) + try writeCredentials(["pg-1": ["#connection": ["user": ""]]]) + + let result = try importer.importConnections(includePasswords: true) + #expect(result.envelope.connections[0].username == "configuser") + } + @Test("importConnections invalid JSON throws parse error") func testImportConnections_invalidJSON_throwsParseError() throws { // Write invalid data to data-sources.json