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..d97bd2913 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,74 @@ struct DBeaverImporterTests { "pg-1": makeConnection(name: "PG") ] try writeDataSources(makeDataSourcesJSON(connections: connections)) - // Even if credentials file exists, it should not be read 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("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