/**
* 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() =>