/** * 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 { act, render, screen, waitFor } from 'spec/helpers/testing-library'; import { ErrorTypeEnum, SupersetClient } from '@superset-ui/core'; import type { SupersetClientResponse } from '@superset-ui/core'; import { DatabaseErrorMessage, getErrorMessageComponentRegistry, OAuth2RedirectMessage, } from 'src/components/ErrorMessage'; import DatasetPanelWrapper from 'src/features/datasets/AddDataset/DatasetPanel'; jest.mock( '@superset-ui/core/components/Icons/AsyncIcon', () => ({ fileName }: { fileName: string }) => ( // eslint-disable-next-line jsx-a11y/prefer-tag-over-role -- mirrors AsyncIcon's real span+role="img" shape ), ); const errorMessageRegistry = getErrorMessageComponentRegistry(); afterEach(() => { errorMessageRegistry.remove(ErrorTypeEnum.GENERIC_BACKEND_ERROR); errorMessageRegistry.remove(ErrorTypeEnum.OAUTH2_REDIRECT); jest.restoreAllMocks(); }); const tableMetadataResponse = ( name: string, columnName: string, ): SupersetClientResponse => ({ response: new Response(), json: { name, columns: [{ name: columnName, type: 'INTEGER', longType: 'INTEGER' }], }, }); test('fetches table metadata for schema-less database without schema', async () => { const getSpy = jest .spyOn(SupersetClient, 'get') .mockResolvedValue(tableMetadataResponse('my_table', 'id')); render( , { useRouter: true }, ); await waitFor(() => { expect(getSpy).toHaveBeenCalledWith( expect.objectContaining({ endpoint: expect.stringContaining('/api/v1/database/1/table_metadata/'), }), ); }); }); test('renders a fallback message for an unstructured metadata error', async () => { jest.spyOn(SupersetClient, 'get').mockRejectedValue({ response: new Response('{}', { status: 500, headers: { 'Content-Type': 'application/json' }, }), }); errorMessageRegistry.registerValue( ErrorTypeEnum.GENERIC_BACKEND_ERROR, DatabaseErrorMessage, ); render( , { useRouter: true }, ); expect( await screen.findByText('Unable to load columns for the selected table.'), ).toBeVisible(); }); test('retries only table metadata after matching OAuth completion', async () => { const oauthError = { error_type: ErrorTypeEnum.OAUTH2_REDIRECT, message: 'OAuth authorization is required.', extra: { url: 'https://example.com/authorize', tab_id: 'dataset-oauth-tab', }, level: 'warning', }; const getSpy = jest .spyOn(SupersetClient, 'get') .mockRejectedValueOnce({ response: new Response(JSON.stringify({ errors: [oauthError] }), { status: 403, headers: { 'Content-Type': 'application/json' }, }), }) .mockResolvedValueOnce(tableMetadataResponse('oauth_table', 'oauth_id')); errorMessageRegistry.registerValue( ErrorTypeEnum.OAUTH2_REDIRECT, OAuth2RedirectMessage, ); render( , { initialState: { charts: {}, dashboardInfo: {}, explore: {}, sqlLab: { queries: {}, queryEditors: [], tabHistory: [], }, }, useRedux: true, useRouter: true, }, ); const authorizationLink = await screen.findByRole('link', { name: /provide authorization/i, }); expect(authorizationLink).toHaveAttribute( 'href', 'https://example.com/authorize', ); expect(getSpy).toHaveBeenCalledTimes(1); act(() => { window.dispatchEvent( new StorageEvent('storage', { key: 'oauth2_auth_complete', newValue: JSON.stringify({ tabId: 'dataset-oauth-tab' }), }), ); }); expect(await screen.findByText('oauth_id')).toBeVisible(); expect(getSpy).toHaveBeenCalledTimes(2); expect(getSpy.mock.calls[1]).toEqual(getSpy.mock.calls[0]); });