/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ // TODO: These tests should be made atomic in separate files import React from 'react'; import fetchMock from 'fetch-mock'; import userEvent from '@testing-library/user-event'; import { render, screen, within, cleanup, act, waitFor, } from 'spec/helpers/testing-library'; import { getExtensionsRegistry } from '@superset-ui/core'; import setupExtensions from 'src/setup/setupExtensions'; import * as hooks from 'src/views/CRUD/hooks'; import { DatabaseObject, ConfigurationMethod } from '../types'; import DatabaseModal, { dbReducer, DBReducerActionType, ActionType, } from './index'; jest.mock('@superset-ui/core', () => ({ ...jest.requireActual('@superset-ui/core'), isFeatureEnabled: () => true, })); const mockHistoryPush = jest.fn(); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useHistory: () => ({ push: mockHistoryPush, }), })); const dbProps = { show: true, database_name: 'my database', sqlalchemy_uri: 'postgres://superset:superset@something:1234/superset', onHide: () => {}, }; const DATABASE_FETCH_ENDPOINT = 'glob:*/api/v1/database/10'; const AVAILABLE_DB_ENDPOINT = 'glob:*/api/v1/database/available*'; const VALIDATE_PARAMS_ENDPOINT = 'glob:*/api/v1/database/validate_parameters*'; const DATABASE_CONNECT_ENDPOINT = 'glob:*/api/v1/database/'; fetchMock.post(DATABASE_CONNECT_ENDPOINT, { id: 10, result: { configuration_method: 'sqlalchemy_form', database_name: 'Other2', driver: 'apsw', expose_in_sqllab: true, extra: '{"allows_virtual_table_explore":true}', sqlalchemy_uri: 'gsheets://', }, json: 'foo', }); fetchMock.config.overwriteRoutes = true; fetchMock.get(DATABASE_FETCH_ENDPOINT, { result: { id: 10, database_name: 'my database', expose_in_sqllab: false, allow_ctas: false, allow_cvas: false, configuration_method: 'sqlalchemy_form', }, }); fetchMock.mock(AVAILABLE_DB_ENDPOINT, { databases: [ { available_drivers: ['psycopg2'], default_driver: 'psycopg2', engine: 'postgresql', name: 'PostgreSQL', parameters: { properties: { database: { description: 'Database name', type: 'string', }, encryption: { description: 'Use an encrypted connection to the database', type: 'boolean', }, host: { description: 'Hostname or IP address', type: 'string', }, password: { description: 'Password', nullable: true, type: 'string', }, port: { description: 'Database port', format: 'int32', maximum: 65536, minimum: 0, type: 'integer', }, query: { additionalProperties: {}, description: 'Additional parameters', type: 'object', }, ssh: { description: 'Create SSH Tunnel', type: 'boolean', }, username: { description: 'Username', nullable: true, type: 'string', }, }, required: ['database', 'host', 'port', 'username'], type: 'object', }, preferred: true, sqlalchemy_uri_placeholder: 'postgresql://user:password@host:port/dbname[?key=value&key=value...]', engine_information: { supports_file_upload: true, disable_ssh_tunneling: false, }, }, { available_drivers: ['rest'], engine: 'presto', name: 'Presto', preferred: true, engine_information: { supports_file_upload: true, disable_ssh_tunneling: false, }, }, { available_drivers: ['mysqldb'], default_driver: 'mysqldb', engine: 'mysql', name: 'MySQL', parameters: { properties: { database: { description: 'Database name', type: 'string', }, encryption: { description: 'Use an encrypted connection to the database', type: 'boolean', }, host: { description: 'Hostname or IP address', type: 'string', }, password: { description: 'Password', nullable: true, type: 'string', }, port: { description: 'Database port', format: 'int32', maximum: 65536, minimum: 0, type: 'integer', }, query: { additionalProperties: {}, description: 'Additional parameters', type: 'object', }, username: { description: 'Username', nullable: true, type: 'string', }, }, required: ['database', 'host', 'port', 'username'], type: 'object', }, preferred: true, sqlalchemy_uri_placeholder: 'mysql://user:password@host:port/dbname[?key=value&key=value...]', engine_information: { supports_file_upload: true, disable_ssh_tunneling: false, }, }, { available_drivers: ['pysqlite'], engine: 'sqlite', name: 'SQLite', preferred: true, engine_information: { supports_file_upload: true, disable_ssh_tunneling: false, }, }, { available_drivers: ['rest'], engine: 'druid', name: 'Apache Druid', preferred: false, engine_information: { supports_file_upload: true, disable_ssh_tunneling: false, }, }, { available_drivers: ['bigquery'], default_driver: 'bigquery', engine: 'bigquery', name: 'Google BigQuery', parameters: { properties: { credentials_info: { description: 'Contents of BigQuery JSON credentials.', type: 'string', 'x-encrypted-extra': true, }, query: { type: 'object', }, }, type: 'object', }, preferred: false, sqlalchemy_uri_placeholder: 'bigquery://{project_id}', engine_information: { supports_file_upload: true, disable_ssh_tunneling: true, }, }, { available_drivers: ['rest'], default_driver: 'apsw', engine: 'gsheets', name: 'Google Sheets', preferred: false, engine_information: { supports_file_upload: false, disable_ssh_tunneling: true, }, }, { available_drivers: ['connector'], default_driver: 'connector', engine: 'databricks', name: 'Databricks', parameters: { properties: { access_token: { type: 'string', }, database: { type: 'string', }, host: { type: 'string', }, http_path: { type: 'string', }, port: { format: 'int32', type: 'integer', }, }, required: ['access_token', 'database', 'host', 'http_path', 'port'], type: 'object', }, preferred: true, sqlalchemy_uri_placeholder: 'databricks+connector://token:{access_token}@{host}:{port}/{database_name}', }, ], }); fetchMock.post(VALIDATE_PARAMS_ENDPOINT, { message: 'OK', }); const databaseFixture: DatabaseObject = { id: 123, backend: 'postgres', configuration_method: ConfigurationMethod.DynamicForm, database_name: 'Postgres', name: 'PostgresDB', is_managed_externally: false, driver: 'psycopg2', }; describe('DatabaseModal', () => { const renderAndWait = async () => { const mounted = act(async () => { render(, { useRedux: true, }); }); return mounted; }; beforeEach(async () => { await renderAndWait(); }); afterEach(cleanup); describe('Visual: New database connection', () => { test('renders the initial load of Step 1 correctly', () => { // ---------- Components ---------- // - AntD header const closeButton = screen.getByLabelText('Close'); const step1Header = screen.getByRole('heading', { name: /connect a database/i, }); // - Connection header const step1Helper = screen.getByText(/step 1 of 3/i); const selectDbHeader = screen.getByRole('heading', { name: /select a database to connect/i, }); // - Preferred database buttons const preferredDbButtonPostgreSQL = screen.getByRole('button', { name: /postgresql/i, }); const preferredDbTextPostgreSQL = within( preferredDbButtonPostgreSQL, ).getByText(/postgresql/i); const preferredDbButtonPresto = screen.getByRole('button', { name: /presto/i, }); const preferredDbTextPresto = within(preferredDbButtonPresto).getByText( /presto/i, ); const preferredDbButtonMySQL = screen.getByRole('button', { name: /mysql/i, }); const preferredDbTextMySQL = within(preferredDbButtonMySQL).getByText( /mysql/i, ); const preferredDbButtonSQLite = screen.getByRole('button', { name: /sqlite/i, }); const preferredDbTextSQLite = within(preferredDbButtonSQLite).getByText( /sqlite/i, ); // renderAvailableSelector() =>