From ba231332507f153d20000d322bceed74736433ef Mon Sep 17 00:00:00 2001 From: Jesse Yang Date: Wed, 11 Nov 2020 11:29:14 -0800 Subject: [PATCH] Add cypress tests --- .../cypress/integration/database/helper.ts | 19 +++++ .../integration/database/modal.test.ts | 73 +++++++++++++++++++ .../src/common/components/Modal/Modal.tsx | 4 +- .../src/utils/getClientErrorObject.ts | 2 +- .../views/CRUD/data/database/DatabaseList.tsx | 2 + .../CRUD/data/database/DatabaseModal.tsx | 3 +- superset/databases/schemas.py | 2 +- superset/models/core.py | 5 +- 8 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 superset-frontend/cypress-base/cypress/integration/database/helper.ts create mode 100644 superset-frontend/cypress-base/cypress/integration/database/modal.test.ts diff --git a/superset-frontend/cypress-base/cypress/integration/database/helper.ts b/superset-frontend/cypress-base/cypress/integration/database/helper.ts new file mode 100644 index 0000000000000..a339865e1fe6c --- /dev/null +++ b/superset-frontend/cypress-base/cypress/integration/database/helper.ts @@ -0,0 +1,19 @@ +/** + * 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. + */ +export const DATABASE_LIST = '/databaseview/list'; diff --git a/superset-frontend/cypress-base/cypress/integration/database/modal.test.ts b/superset-frontend/cypress-base/cypress/integration/database/modal.test.ts new file mode 100644 index 0000000000000..0e999793bcaae --- /dev/null +++ b/superset-frontend/cypress-base/cypress/integration/database/modal.test.ts @@ -0,0 +1,73 @@ +/** + * 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. + */ +import { DATABASE_LIST } from './helper'; + +describe('Add database', () => { + beforeEach(() => { + cy.server(); + cy.login(); + }); + + it('should keep create modal open when error', () => { + cy.visit(DATABASE_LIST); + + // open modal + cy.get('[data-test="btn-create-database"]').click(); + + // type values + cy.get('[data-test="database-modal"] input[name="database_name"]') + .focus() + .type('cypress'); + cy.get('[data-test="database-modal"] input[name="sqlalchemy_uri"]') + .focus() + .type('bad_db_uri'); + + // click save + cy.get('[data-test="modal-confirm-button"]:not(:disabled)').click(); + + // should show error alerts and keep modal open + cy.get('.toast').contains('error'); + cy.wait(1000); // wait for potential (incorrect) closing annimation + cy.get('[data-test="database-modal"]').should('be.visible'); + + // should be able to close modal + cy.get('[data-test="modal-cancel-button"]').click(); + cy.get('[data-test="database-modal"]').should('not.be.visible'); + }); + + it('should keep update modal open when error', () => { + // open modal + cy.get('[data-test="database-edit"]:last').click(); + + cy.get('[data-test="database-modal"]:last input[name="sqlalchemy_uri"]') + .focus() + .dblclick() + .type('{selectall}{backspace}bad_uri'); + + // click save + cy.get('[data-test="modal-confirm-button"]:not(:disabled)').click(); + + // should show error alerts + cy.get('.toast').contains('error').should('be.visible'); + + // modal should still be open + cy.wait(1000); // wait for potential (incorrect) closing annimation + cy.get('[data-test="database-modal"]').should('be.visible'); + }); +}); diff --git a/superset-frontend/src/common/components/Modal/Modal.tsx b/superset-frontend/src/common/components/Modal/Modal.tsx index 0f997e353a932..4431fd32c85d2 100644 --- a/superset-frontend/src/common/components/Modal/Modal.tsx +++ b/superset-frontend/src/common/components/Modal/Modal.tsx @@ -32,6 +32,7 @@ interface ModalProps { primaryButtonName?: string; primaryButtonType?: 'primary' | 'danger'; show: boolean; + name?: string; title: React.ReactNode; width?: string; maxWidth?: string; @@ -114,6 +115,7 @@ const CustomModal = ({ primaryButtonName = t('OK'), primaryButtonType = 'primary', show, + name, title, width, maxWidth, @@ -159,7 +161,7 @@ const CustomModal = ({ } footer={!hideFooter ? modalFooter : null} - wrapProps={{ 'data-test': `${title}-modal`, ...wrapProps }} + wrapProps={{ 'data-test': `${name || title}-modal`, ...wrapProps }} {...rest} > {children} diff --git a/superset-frontend/src/utils/getClientErrorObject.ts b/superset-frontend/src/utils/getClientErrorObject.ts index 6d892fdc64f44..274b8b4d93cfd 100644 --- a/superset-frontend/src/utils/getClientErrorObject.ts +++ b/superset-frontend/src/utils/getClientErrorObject.ts @@ -31,7 +31,7 @@ export type ClientErrorObject = { link?: string; // marshmallow field validation returns the error mssage in the format // of { field: [msg1, msg2] } - message?: string | Record; + message?: string; severity?: string; stacktrace?: string; } & Partial; diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx index 80407899ce20f..db9252c68ca84 100644 --- a/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx +++ b/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx @@ -132,6 +132,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) { if (canCreate) { menuData.buttons = [ { + 'data-test': 'btn-create-database', name: ( <> {t('Database')}{' '} @@ -295,6 +296,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) { > = ({ ? `${t('ERROR: ')}${ typeof error.message === 'string' ? error.message - : error.message.sqlalchemy_uri + : (error.message as Record).sqlalchemy_uri }` : t('ERROR: Connection failed. '), ); @@ -315,6 +315,7 @@ const DatabaseModal: FunctionComponent = ({ return ( str: """ try: make_url(value.strip()) - except (ArgumentError, AttributeError): + except (ArgumentError, AttributeError, ValueError): raise ValidationError( [ _( diff --git a/superset/models/core.py b/superset/models/core.py index c9e87b056f428..382239209f9e0 100755 --- a/superset/models/core.py +++ b/superset/models/core.py @@ -649,9 +649,10 @@ def get_schema_access_for_csv_upload( # pylint: disable=invalid-name def sqlalchemy_uri_decrypted(self) -> str: try: conn = sqla.engine.url.make_url(self.sqlalchemy_uri) - except ArgumentError: + except (ArgumentError, ValueError): # if the URI is invalid, ignore and return a placeholder url - return "dialect://host" + # (so users see 500 less often) + return "dialect://invalid_uri" if custom_password_store: conn.password = custom_password_store(conn) else: