diff --git a/frontend/src/components/ConnectionModal.edit-password.test.tsx b/frontend/src/components/ConnectionModal.edit-password.test.tsx index 5408548..9a74f22 100644 --- a/frontend/src/components/ConnectionModal.edit-password.test.tsx +++ b/frontend/src/components/ConnectionModal.edit-password.test.tsx @@ -5,7 +5,8 @@ const connectionModalSource = readFileSync(new URL('./ConnectionModal.tsx', impo const redisSectionsSource = readFileSync(new URL('./ConnectionModalRedisSections.tsx', import.meta.url), 'utf8'); const mongoSectionsSource = readFileSync(new URL('./ConnectionModalMongoSections.tsx', import.meta.url), 'utf8'); const connectionTypeCatalogSource = readFileSync(new URL('../utils/connectionTypeCatalog.ts', import.meta.url), 'utf8'); -const source = `${connectionModalSource}\n${redisSectionsSource}\n${mongoSectionsSource}\n${connectionTypeCatalogSource}`; +const connectionTypeCapabilitiesSource = readFileSync(new URL('../utils/connectionTypeCapabilities.ts', import.meta.url), 'utf8'); +const source = `${connectionModalSource}\n${redisSectionsSource}\n${mongoSectionsSource}\n${connectionTypeCatalogSource}\n${connectionTypeCapabilitiesSource}`; describe('ConnectionModal edit password behavior', () => { it('keeps the prefilled primary password masked by default', () => { diff --git a/frontend/src/components/ConnectionModal.tsx b/frontend/src/components/ConnectionModal.tsx index 758299e..fd52b26 100644 --- a/frontend/src/components/ConnectionModal.tsx +++ b/frontend/src/components/ConnectionModal.tsx @@ -69,6 +69,16 @@ import { getConnectionTypeDefaultPort as getDefaultPortByType, getConnectionTypeHint, } from "../utils/connectionTypeCatalog"; +import { + isFileDatabaseType, + isMySQLCompatibleType, + isPostgresCompatibleSSLType, + singleHostUriSchemesByType, + supportsConnectionParamsForType, + supportsSSLCAPathForType, + supportsSSLClientCertificateForType, + supportsSSLForType, +} from "../utils/connectionTypeCapabilities"; import { CUSTOM_CONNECTION_DRIVER_HELP } from "../utils/driverImportGuidance"; import { describeUnsupportedOceanBaseProtocol, @@ -236,144 +246,6 @@ const resolveInitialSecretFieldValue = ( } }; -const singleHostUriSchemesByType: Record = { - postgres: ["postgresql", "postgres"], - opengauss: ["opengauss", "jdbc:opengauss", "postgresql", "postgres"], - clickhouse: ["clickhouse"], - oracle: ["oracle"], - sqlserver: ["sqlserver"], - iris: ["iris", "intersystems"], - redis: ["redis"], - tdengine: ["tdengine"], - dameng: ["dameng", "dm"], - kingbase: ["kingbase"], - highgo: ["highgo"], - vastbase: ["vastbase"], - elasticsearch: ["http", "https"], -}; - -const sslSupportedTypes = new Set([ - "mysql", - "mariadb", - "oceanbase", - "doris", - "diros", - "starrocks", - "sphinx", - "dameng", - "clickhouse", - "postgres", - "sqlserver", - "oracle", - "kingbase", - "highgo", - "vastbase", - "opengauss", - "mongodb", - "redis", - "tdengine", - "elasticsearch", -]); - -const supportsSSLForType = (type: string) => - sslSupportedTypes.has( - String(type || "") - .trim() - .toLowerCase(), - ); - -const sslCAPathSupportedTypes = new Set([ - "mysql", - "mariadb", - "oceanbase", - "diros", - "starrocks", - "sphinx", - "clickhouse", - "postgres", - "sqlserver", - "kingbase", - "highgo", - "vastbase", - "opengauss", - "mongodb", - "redis", - "elasticsearch", -]); - -const sslClientCertificateSupportedTypes = new Set([ - "mysql", - "mariadb", - "oceanbase", - "diros", - "starrocks", - "sphinx", - "dameng", - "clickhouse", - "postgres", - "kingbase", - "highgo", - "vastbase", - "opengauss", - "mongodb", - "redis", -]); - -const supportsSSLCAPathForType = (type: string) => - sslCAPathSupportedTypes.has( - String(type || "") - .trim() - .toLowerCase(), - ); - -const supportsSSLClientCertificateForType = (type: string) => - sslClientCertificateSupportedTypes.has( - String(type || "") - .trim() - .toLowerCase(), - ); - -const isPostgresCompatibleSSLType = (type: string) => - [ - "postgres", - "kingbase", - "highgo", - "vastbase", - "opengauss", - ].includes( - String(type || "") - .trim() - .toLowerCase(), - ); - -const isFileDatabaseType = (type: string) => - type === "sqlite" || type === "duckdb"; - -const isMySQLCompatibleType = (type: string) => - type === "mysql" || - type === "mariadb" || - type === "oceanbase" || - type === "doris" || - type === "diros" || - type === "starrocks" || - type === "sphinx"; - -const supportsConnectionParamsForType = (type: string) => - isMySQLCompatibleType(type) || - type === "postgres" || - type === "kingbase" || - type === "highgo" || - type === "vastbase" || - type === "opengauss" || - type === "oracle" || - type === "sqlserver" || - type === "iris" || - type === "clickhouse" || - type === "mongodb" || - type === "dameng" || - type === "tdengine" || - type === "elasticsearch"; - type DriverStatusSnapshot = { type: string; name: string; diff --git a/frontend/src/utils/connectionTypeCapabilities.test.ts b/frontend/src/utils/connectionTypeCapabilities.test.ts new file mode 100644 index 0000000..bdad996 --- /dev/null +++ b/frontend/src/utils/connectionTypeCapabilities.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it } from 'vitest'; + +import { + isFileDatabaseType, + isMySQLCompatibleType, + isPostgresCompatibleSSLType, + singleHostUriSchemesByType, + supportsConnectionParamsForType, + supportsSSLCAPathForType, + supportsSSLClientCertificateForType, + supportsSSLForType, +} from './connectionTypeCapabilities'; + +describe('connectionTypeCapabilities', () => { + it('keeps single-host URI scheme aliases for URI parsing', () => { + expect(singleHostUriSchemesByType.postgres).toEqual(['postgresql', 'postgres']); + expect(singleHostUriSchemesByType.opengauss).toContain('jdbc:opengauss'); + expect(singleHostUriSchemesByType.dameng).toEqual(['dameng', 'dm']); + expect(singleHostUriSchemesByType.elasticsearch).toEqual(['http', 'https']); + expect(singleHostUriSchemesByType.redis).toEqual(['redis']); + }); + + it('detects SSL-capable connection types with case-insensitive normalization', () => { + expect(supportsSSLForType('redis')).toBe(true); + expect(supportsSSLForType('MongoDB')).toBe(true); + expect(supportsSSLForType('elasticsearch')).toBe(true); + expect(supportsSSLForType('tdengine')).toBe(true); + expect(supportsSSLForType('dameng')).toBe(true); + expect(supportsSSLForType('sqlite')).toBe(false); + }); + + it('keeps CA path and client certificate support distinct', () => { + expect(supportsSSLCAPathForType('dameng')).toBe(false); + expect(supportsSSLClientCertificateForType('dameng')).toBe(true); + expect(supportsSSLCAPathForType('sqlserver')).toBe(true); + expect(supportsSSLClientCertificateForType('sqlserver')).toBe(false); + expect(supportsSSLCAPathForType('redis')).toBe(true); + expect(supportsSSLClientCertificateForType('redis')).toBe(true); + }); + + it('detects postgres-compatible SSL parameter dialects', () => { + expect(isPostgresCompatibleSSLType('postgres')).toBe(true); + expect(isPostgresCompatibleSSLType('kingbase')).toBe(true); + expect(isPostgresCompatibleSSLType('HighGo')).toBe(true); + expect(isPostgresCompatibleSSLType('mysql')).toBe(false); + }); + + it('keeps file and MySQL-compatible database detection explicit', () => { + expect(isFileDatabaseType('sqlite')).toBe(true); + expect(isFileDatabaseType('duckdb')).toBe(true); + expect(isFileDatabaseType('DuckDB')).toBe(false); + expect(isMySQLCompatibleType('mysql')).toBe(true); + expect(isMySQLCompatibleType('oceanbase')).toBe(true); + expect(isMySQLCompatibleType('diros')).toBe(true); + expect(isMySQLCompatibleType('postgres')).toBe(false); + }); + + it('keeps advanced connection params enabled only for supported database types', () => { + expect(supportsConnectionParamsForType('mysql')).toBe(true); + expect(supportsConnectionParamsForType('postgres')).toBe(true); + expect(supportsConnectionParamsForType('oracle')).toBe(true); + expect(supportsConnectionParamsForType('mongodb')).toBe(true); + expect(supportsConnectionParamsForType('dameng')).toBe(true); + expect(supportsConnectionParamsForType('tdengine')).toBe(true); + expect(supportsConnectionParamsForType('elasticsearch')).toBe(true); + expect(supportsConnectionParamsForType('redis')).toBe(false); + expect(supportsConnectionParamsForType('sqlite')).toBe(false); + expect(supportsConnectionParamsForType('jvm')).toBe(false); + }); +}); diff --git a/frontend/src/utils/connectionTypeCapabilities.ts b/frontend/src/utils/connectionTypeCapabilities.ts new file mode 100644 index 0000000..e6dff16 --- /dev/null +++ b/frontend/src/utils/connectionTypeCapabilities.ts @@ -0,0 +1,126 @@ +export const singleHostUriSchemesByType: Record = { + postgres: ["postgresql", "postgres"], + opengauss: ["opengauss", "jdbc:opengauss", "postgresql", "postgres"], + clickhouse: ["clickhouse"], + oracle: ["oracle"], + sqlserver: ["sqlserver"], + iris: ["iris", "intersystems"], + redis: ["redis"], + tdengine: ["tdengine"], + dameng: ["dameng", "dm"], + kingbase: ["kingbase"], + highgo: ["highgo"], + vastbase: ["vastbase"], + elasticsearch: ["http", "https"], +}; + +const normalizeConnectionType = (type: string) => + String(type || "") + .trim() + .toLowerCase(); + +const sslSupportedTypes = new Set([ + "mysql", + "mariadb", + "oceanbase", + "doris", + "diros", + "starrocks", + "sphinx", + "dameng", + "clickhouse", + "postgres", + "sqlserver", + "oracle", + "kingbase", + "highgo", + "vastbase", + "opengauss", + "mongodb", + "redis", + "tdengine", + "elasticsearch", +]); + +export const supportsSSLForType = (type: string) => + sslSupportedTypes.has(normalizeConnectionType(type)); + +const sslCAPathSupportedTypes = new Set([ + "mysql", + "mariadb", + "oceanbase", + "diros", + "starrocks", + "sphinx", + "clickhouse", + "postgres", + "sqlserver", + "kingbase", + "highgo", + "vastbase", + "opengauss", + "mongodb", + "redis", + "elasticsearch", +]); + +const sslClientCertificateSupportedTypes = new Set([ + "mysql", + "mariadb", + "oceanbase", + "diros", + "starrocks", + "sphinx", + "dameng", + "clickhouse", + "postgres", + "kingbase", + "highgo", + "vastbase", + "opengauss", + "mongodb", + "redis", +]); + +export const supportsSSLCAPathForType = (type: string) => + sslCAPathSupportedTypes.has(normalizeConnectionType(type)); + +export const supportsSSLClientCertificateForType = (type: string) => + sslClientCertificateSupportedTypes.has(normalizeConnectionType(type)); + +export const isPostgresCompatibleSSLType = (type: string) => + [ + "postgres", + "kingbase", + "highgo", + "vastbase", + "opengauss", + ].includes(normalizeConnectionType(type)); + +export const isFileDatabaseType = (type: string) => + type === "sqlite" || type === "duckdb"; + +export const isMySQLCompatibleType = (type: string) => + type === "mysql" || + type === "mariadb" || + type === "oceanbase" || + type === "doris" || + type === "diros" || + type === "starrocks" || + type === "sphinx"; + +export const supportsConnectionParamsForType = (type: string) => + isMySQLCompatibleType(type) || + type === "postgres" || + type === "kingbase" || + type === "highgo" || + type === "vastbase" || + type === "opengauss" || + type === "oracle" || + type === "sqlserver" || + type === "iris" || + type === "clickhouse" || + type === "mongodb" || + type === "dameng" || + type === "tdengine" || + type === "elasticsearch";