console (tests): add unit and interaction tests for ConnectPostgresWidget

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9478
GitOrigin-RevId: 1fbfca3839b488fc46d774d8883cc74e44d22abe
This commit is contained in:
Vijay Prasanna 2023-06-12 13:05:07 +05:30 committed by hasura-bot
parent d5467d8d3a
commit 31c6699013
4 changed files with 700 additions and 106 deletions

View File

@ -1,24 +1,16 @@
import { InputField, useConsoleForm } from '../../../../new-components/Form';
import { Button } from '../../../../new-components/Button';
import { GraphQLCustomization } from '../GraphQLCustomization/GraphQLCustomization';
import { useConsoleForm } from '../../../../new-components/Form';
import { getDefaultValues, PostgresConnectionSchema, schema } from './schema';
import { ReadReplicas } from './parts/ReadReplicas';
import { useManageDatabaseConnection } from '../../hooks/useManageDatabaseConnection';
import { hasuraToast } from '../../../../new-components/Toasts';
import { useMetadata } from '../../../hasura-metadata-api';
import { generatePostgresRequestPayload } from './utils/generateRequests';
import { DatabaseUrl } from './parts/DatabaseUrl';
import { PoolSettings } from './parts/PoolSettings';
import { IsolationLevel } from './parts/IsolationLevel';
import { UsePreparedStatements } from './parts/UsePreparedStatements';
import { SslSettings } from './parts/SslSettings';
import { Collapsible } from '../../../../new-components/Collapsible';
import { ExtensionSchema } from './parts/ExtensionSchema';
import { useEffect, useState } from 'react';
import { LimitedFeatureWrapper } from '../LimitedFeatureWrapper/LimitedFeatureWrapper';
import { DynamicDBRouting } from './parts/DynamicDBRouting';
import { Tabs } from '../../../../new-components/Tabs';
import { DisplayToastErrorMessage } from '../Common/DisplayToastErrorMessage';
import { ConnectPostgresForm } from './parts/ConnectPostgresForm';
import { Button } from '../../../../new-components/Button';
interface ConnectPostgresWidgetProps {
dataSourceName?: string;
@ -130,101 +122,7 @@ export const ConnectPostgresWidget = (props: ConnectPostgresWidgetProps) => {
content: (
<div className="mt-sm">
<Form onSubmit={handleSubmit}>
<InputField
name="name"
label="Database name"
placeholder="Database name"
/>
<div className="bg-white border border-hasGray-300 rounded-md shadow-sm overflow-hidden p-4">
<DatabaseUrl
name="configuration.connectionInfo.databaseUrl"
hideOptions={hiddenOptions}
/>
</div>
<div className="mt-sm">
<Collapsible
triggerChildren={
<div className="font-semibold text-muted">
Advanced Settings
</div>
}
>
<PoolSettings
name={`configuration.connectionInfo.poolSettings`}
/>
<IsolationLevel
name={`configuration.connectionInfo.isolationLevel`}
/>
<UsePreparedStatements
name={`configuration.connectionInfo.usePreparedStatements`}
/>
<ExtensionSchema name="configuration.extensionSchema" />
<LimitedFeatureWrapper
title="Looking to add SSL Settings?"
id="db-ssl-settings"
description="Get production-ready today with a 30-day free trial of Hasura EE, no credit card required."
>
<div className="mt-sm">
<Collapsible
triggerChildren={
<div className="font-semibold text-muted">
SSL Certificates Settings
<span className="px-1.5 italic font-light">
(Certificates will be loaded from{' '}
<a href="https://hasura.io/docs/latest/graphql/cloud/projects/create.html#existing-database">
environment variables
</a>
)
</span>
</div>
}
>
<SslSettings
name={`configuration.connectionInfo.sslSettings`}
/>
</Collapsible>
</div>
</LimitedFeatureWrapper>
</Collapsible>
</div>
<div className="mt-sm">
<Collapsible
triggerChildren={
<div className="font-semibold text-muted">
GraphQL Customization
</div>
}
>
<GraphQLCustomization name="customization" />
</Collapsible>
</div>
<div className="mt-sm">
<LimitedFeatureWrapper
id="read-replicas"
title="Improve performance and handle increased traffic with read replicas"
description="Scale your database by offloading read queries to
read-only replicas, allowing for better performance
and availability for users."
>
<Collapsible
triggerChildren={
<div className="font-semibold text-muted">
Read Replicas
</div>
}
>
<ReadReplicas
name="configuration.readReplicas"
hideOptions={hiddenOptions}
/>
</Collapsible>
</LimitedFeatureWrapper>
</div>
<ConnectPostgresForm hiddenOptions={hiddenOptions} />
<div className="flex justify-end mt-sm">
<Button
type="submit"

View File

@ -0,0 +1,278 @@
import { SimpleForm } from '../../../../../new-components/Form';
import { Button } from '../../../../../new-components/Button';
import { Meta, StoryObj } from '@storybook/react';
import { ConnectPostgresForm } from './ConnectPostgresForm';
import { PostgresConnectionSchema, getDefaultValues, schema } from '../schema';
import { ReactQueryDecorator } from '../../../../../storybook/decorators/react-query';
import { useState } from 'react';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
export default {
component: ConnectPostgresForm,
decorators: [ReactQueryDecorator()],
} as Meta<typeof ConnectPostgresForm>;
export const TestPostgresForm: StoryObj<typeof ConnectPostgresForm> = {
render: () => {
const [formValues, setFormValues] = useState<PostgresConnectionSchema>();
return (
<SimpleForm
onSubmit={data => setFormValues(data)}
schema={schema}
options={{
defaultValues: getDefaultValues(),
}}
>
<ConnectPostgresForm hiddenOptions={[]} />
<Button type="submit" className="my-2">
Submit
</Button>
<div data-testid="result">{JSON.stringify(formValues)}</div>
</SimpleForm>
);
},
name: '🧪 Add database with various configurations',
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// verify if all the fields are present (in oss mode)
await userEvent.type(
await canvas.findByLabelText('Database name'),
'chinook'
);
await userEvent.type(
await canvas.findByPlaceholderText(
'postgresql://username:password@hostname:port/postgres'
),
'postgresql://myusername:password123@localhost:5432/chinook'
);
await userEvent.click(await canvas.findByText('Submit'));
await waitFor(
async () => {
await expect(await canvas.findByTestId('result')).toHaveTextContent(
JSON.stringify({
name: 'chinook',
configuration: {
connectionInfo: {
databaseUrl: {
connectionType: 'databaseUrl',
url: 'postgresql://myusername:password123@localhost:5432/chinook',
},
},
},
})
);
},
{
timeout: 5000,
}
);
await userEvent.click(
await canvas.findByTestId(
'configuration.connectionInfo.databaseUrl.connectionType-envVar'
)
);
await userEvent.type(
await canvas.findByPlaceholderText('HASURA_GRAPHQL_DB_URL_FROM_ENV'),
'MY_SECRET_ENV_VAR'
);
await userEvent.click(await canvas.findByText('Submit'));
await waitFor(
async () => {
await expect(await canvas.findByTestId('result')).toHaveTextContent(
JSON.stringify({
name: 'chinook',
configuration: {
connectionInfo: {
databaseUrl: {
connectionType: 'envVar',
envVar: 'MY_SECRET_ENV_VAR',
},
},
},
})
);
},
{
timeout: 5000,
}
);
await userEvent.click(
await canvas.findByTestId(
'configuration.connectionInfo.databaseUrl.connectionType-connectionParams'
)
);
await userEvent.type(
await canvas.findByPlaceholderText('postgres_user'),
'myusername'
);
await userEvent.type(
await canvas.findByPlaceholderText('password'),
'password123'
);
await userEvent.type(
await canvas.findByPlaceholderText('postgres'),
'chinook'
);
await userEvent.type(
await canvas.findByPlaceholderText('localhost'),
'localhost'
);
await userEvent.type(await canvas.findByPlaceholderText('5432'), '5432');
await userEvent.click(await canvas.findByText('Submit'));
await waitFor(
async () => {
await expect(await canvas.findByTestId('result')).toHaveTextContent(
JSON.stringify({
name: 'chinook',
configuration: {
connectionInfo: {
databaseUrl: {
connectionType: 'connectionParams',
username: 'myusername',
password: 'password123',
database: 'chinook',
host: 'localhost',
port: 5432,
},
},
},
})
);
},
{
timeout: 5000,
}
);
await userEvent.click(await canvas.findByText('Advanced Settings'));
await userEvent.type(await canvas.findByPlaceholderText('1000'), '100');
await userEvent.type(await canvas.findByPlaceholderText('180'), '100');
await userEvent.type(await canvas.findByPlaceholderText('1'), '100');
await userEvent.type(await canvas.findByPlaceholderText('360'), '100');
await userEvent.type(await canvas.findByPlaceholderText('600'), '100100');
await userEvent.type(
await canvas.findByPlaceholderText('public'),
'public_schema'
);
await userEvent.click(await canvas.findByText('Submit'));
await waitFor(
async () => {
await expect(await canvas.findByTestId('result')).toHaveTextContent(
JSON.stringify({
name: 'chinook',
configuration: {
connectionInfo: {
databaseUrl: {
connectionType: 'connectionParams',
username: 'myusername',
password: 'password123',
database: 'chinook',
host: 'localhost',
port: 5432,
},
poolSettings: {
totalMaxConnections: 100,
idleTimeout: 100,
retries: 100,
poolTimeout: 100,
connectionLifetime: 100100,
},
isolationLevel: 'read-committed',
},
extensionSchema: 'public_schema',
},
})
);
},
{
timeout: 5000,
}
);
await userEvent.click(await canvas.findByText('GraphQL Customization'));
await userEvent.type(
await canvas.findByTestId('customization.rootFields.namespace'),
'root_field_namespace'
);
await userEvent.type(
await canvas.findByTestId('customization.rootFields.prefix'),
'root_field_prefix'
);
await userEvent.type(
await canvas.findByTestId('customization.rootFields.suffix'),
'root_field_suffix'
);
await userEvent.type(
await canvas.findByTestId('customization.typeNames.prefix'),
'type_names_prefix'
);
await userEvent.type(
await canvas.findByTestId('customization.typeNames.suffix'),
'type_names_suffix'
);
await userEvent.click(await canvas.findByText('Submit'));
await waitFor(
async () => {
await expect(await canvas.findByTestId('result')).toHaveTextContent(
JSON.stringify({
name: 'chinook',
configuration: {
connectionInfo: {
databaseUrl: {
connectionType: 'connectionParams',
username: 'myusername',
password: 'password123',
database: 'chinook',
host: 'localhost',
port: 5432,
},
poolSettings: {
totalMaxConnections: 100,
idleTimeout: 100,
retries: 100,
poolTimeout: 100,
connectionLifetime: 100100,
},
isolationLevel: 'read-committed',
},
extensionSchema: 'public_schema',
},
customization: {
rootFields: {
namespace: 'root_field_namespace',
prefix: 'root_field_prefix',
suffix: 'root_field_suffix',
},
typeNames: {
prefix: 'type_names_prefix',
suffix: 'type_names_suffix',
},
},
})
);
},
{
timeout: 5000,
}
);
},
};

View File

@ -0,0 +1,110 @@
import { Collapsible } from '../../../../../new-components/Collapsible';
import { InputField } from '../../../../../new-components/Form';
import { GraphQLCustomization } from '../../GraphQLCustomization';
import { LimitedFeatureWrapper } from '../../LimitedFeatureWrapper/LimitedFeatureWrapper';
import { DatabaseUrl } from './DatabaseUrl';
import { ExtensionSchema } from './ExtensionSchema';
import { IsolationLevel } from './IsolationLevel';
import { PoolSettings } from './PoolSettings';
import { ReadReplicas } from './ReadReplicas';
import { SslSettings } from './SslSettings';
import { UsePreparedStatements } from './UsePreparedStatements';
export const ConnectPostgresForm = ({
hiddenOptions,
}: {
hiddenOptions: string[];
}) => {
return (
<>
<InputField
name="name"
label="Database name"
placeholder="Database name"
/>
<div className="bg-white border border-hasGray-300 rounded-md shadow-sm overflow-hidden p-4">
<DatabaseUrl
name="configuration.connectionInfo.databaseUrl"
hideOptions={hiddenOptions}
/>
</div>
<div className="mt-sm">
<Collapsible
triggerChildren={
<div className="font-semibold text-muted">Advanced Settings</div>
}
>
<PoolSettings name={`configuration.connectionInfo.poolSettings`} />
<IsolationLevel
name={`configuration.connectionInfo.isolationLevel`}
/>
<UsePreparedStatements
name={`configuration.connectionInfo.usePreparedStatements`}
/>
<ExtensionSchema name="configuration.extensionSchema" />
<LimitedFeatureWrapper
title="Looking to add SSL Settings?"
id="db-ssl-settings"
description="Get production-ready today with a 30-day free trial of Hasura EE, no credit card required."
>
<div className="mt-sm">
<Collapsible
triggerChildren={
<div className="font-semibold text-muted">
SSL Certificates Settings
<span className="px-1.5 italic font-light">
(Certificates will be loaded from{' '}
<a href="https://hasura.io/docs/latest/graphql/cloud/projects/create.html#existing-database">
environment variables
</a>
)
</span>
</div>
}
>
<SslSettings
name={`configuration.connectionInfo.sslSettings`}
/>
</Collapsible>
</div>
</LimitedFeatureWrapper>
</Collapsible>
</div>
<div className="mt-sm">
<Collapsible
triggerChildren={
<div className="font-semibold text-muted">
GraphQL Customization
</div>
}
>
<GraphQLCustomization name="customization" />
</Collapsible>
</div>
<div className="mt-sm">
<LimitedFeatureWrapper
id="read-replicas"
title="Improve performance and handle increased traffic with read replicas"
description="Scale your database by offloading read queries to
read-only replicas, allowing for better performance
and availability for users."
>
<Collapsible
triggerChildren={
<div className="font-semibold text-muted">Read Replicas</div>
}
>
<ReadReplicas
name="configuration.readReplicas"
hideOptions={hiddenOptions}
/>
</Collapsible>
</LimitedFeatureWrapper>
</div>
</>
);
};

View File

@ -0,0 +1,308 @@
import { generatePostgresRequestPayload } from './generateRequests';
describe('[generatePostgresRequestPayload] generates the correct payload when', () => {
it('only the connection info is filled in (basic case - connection params)', () => {
const result = generatePostgresRequestPayload({
driver: 'postgres',
values: {
name: 'chinook',
configuration: {
connectionInfo: {
databaseUrl: {
connectionType: 'connectionParams',
username: 'myusername',
password: 'password123',
database: 'chinook',
host: 'localhost',
port: 5432,
},
},
},
},
});
expect(result).toMatchInlineSnapshot(`
{
"details": {
"configuration": {
"connection_info": {
"database_url": {
"connection_parameters": {
"database": "chinook",
"host": "localhost",
"password": "password123",
"port": 5432,
"username": "myusername",
},
},
},
},
"name": "chinook",
},
"driver": "postgres",
}
`);
});
it('only the connection info is filled in (basic case - database url)', () => {
const result = generatePostgresRequestPayload({
driver: 'postgres',
values: {
name: 'chinook',
configuration: {
connectionInfo: {
databaseUrl: {
connectionType: 'databaseUrl',
url: 'postgresql://myusername:password123@localhost:5432/chinook',
},
},
},
},
});
expect(result).toMatchInlineSnapshot(`
{
"details": {
"configuration": {
"connection_info": {
"database_url": "postgresql://myusername:password123@localhost:5432/chinook",
},
},
"name": "chinook",
},
"driver": "postgres",
}
`);
});
it('only the connection info is filled in (basic case - env var)', () => {
const result = generatePostgresRequestPayload({
driver: 'postgres',
values: {
name: 'chinook',
configuration: {
connectionInfo: {
databaseUrl: {
connectionType: 'envVar',
envVar: 'MY_SECRET_ENV_VAR',
},
},
},
},
});
expect(result).toMatchInlineSnapshot(`
{
"details": {
"configuration": {
"connection_info": {
"database_url": {
"from_env": "MY_SECRET_ENV_VAR",
},
},
},
"name": "chinook",
},
"driver": "postgres",
}
`);
});
it('connection info and pool settings are filled in', () => {
const result = generatePostgresRequestPayload({
driver: 'postgres',
values: {
name: 'chinook',
configuration: {
connectionInfo: {
databaseUrl: {
connectionType: 'connectionParams',
username: 'myusername',
password: 'password123',
database: 'chinook',
host: 'localhost',
port: 5432,
},
poolSettings: {
totalMaxConnections: 100,
idleTimeout: 100,
retries: 100,
poolTimeout: 100,
connectionLifetime: 100100,
},
isolationLevel: 'read-committed',
},
extensionSchema: 'public_schema',
},
},
});
expect(result).toMatchInlineSnapshot(`
{
"details": {
"configuration": {
"connection_info": {
"database_url": {
"connection_parameters": {
"database": "chinook",
"host": "localhost",
"password": "password123",
"port": 5432,
"username": "myusername",
},
},
"isolation_level": "read-committed",
"pool_settings": {
"connection_lifetime": 100100,
"idle_timeout": 100,
"pool_timeout": 100,
"retries": 100,
"total_max_connections": 100,
},
},
"extensions_schema": "public_schema",
},
"name": "chinook",
},
"driver": "postgres",
}
`);
});
it('connection info and GQL customization settings are filled in', () => {
const result = generatePostgresRequestPayload({
driver: 'postgres',
values: {
name: 'chinook',
configuration: {
connectionInfo: {
databaseUrl: {
connectionType: 'connectionParams',
username: 'myusername',
password: 'password123',
database: 'chinook',
host: 'localhost',
port: 5432,
},
},
},
customization: {
rootFields: {
namespace: 'root_field_namespace',
prefix: 'root_field_prefix',
suffix: 'root_field_suffix',
},
typeNames: {
prefix: 'type_names_prefix',
suffix: 'type_names_suffix',
},
},
},
});
expect(result).toMatchInlineSnapshot(`
{
"details": {
"configuration": {
"connection_info": {
"database_url": {
"connection_parameters": {
"database": "chinook",
"host": "localhost",
"password": "password123",
"port": 5432,
"username": "myusername",
},
},
},
},
"customization": {
"root_fields": {
"namespace": "root_field_namespace",
"prefix": "root_field_prefix",
"suffix": "root_field_suffix",
},
"type_names": {
"prefix": "type_names_prefix",
"suffix": "type_names_suffix",
},
},
"name": "chinook",
},
"driver": "postgres",
}
`);
});
it('all the input fields are filled in', () => {
const result = generatePostgresRequestPayload({
driver: 'postgres',
values: {
name: 'chinook',
configuration: {
connectionInfo: {
databaseUrl: {
connectionType: 'connectionParams',
username: 'myusername',
password: 'password123',
database: 'chinook',
host: 'localhost',
port: 5432,
},
poolSettings: {
totalMaxConnections: 100,
idleTimeout: 100,
retries: 100,
poolTimeout: 100,
connectionLifetime: 100100,
},
isolationLevel: 'read-committed',
},
extensionSchema: 'public_schema',
},
customization: {
rootFields: {
namespace: 'root_field_namespace',
prefix: 'root_field_prefix',
suffix: 'root_field_suffix',
},
typeNames: {
prefix: 'type_names_prefix',
suffix: 'type_names_suffix',
},
},
},
});
expect(result).toMatchInlineSnapshot(`
{
"details": {
"configuration": {
"connection_info": {
"database_url": {
"connection_parameters": {
"database": "chinook",
"host": "localhost",
"password": "password123",
"port": 5432,
"username": "myusername",
},
},
"isolation_level": "read-committed",
"pool_settings": {
"connection_lifetime": 100100,
"idle_timeout": 100,
"pool_timeout": 100,
"retries": 100,
"total_max_connections": 100,
},
},
"extensions_schema": "public_schema",
},
"customization": {
"root_fields": {
"namespace": "root_field_namespace",
"prefix": "root_field_prefix",
"suffix": "root_field_suffix",
},
"type_names": {
"prefix": "type_names_prefix",
"suffix": "type_names_suffix",
},
},
"name": "chinook",
},
"driver": "postgres",
}
`);
});
});