airbnb/caravel

View on GitHub
superset-frontend/src/hooks/apiResources/tables.test.ts

Summary

Maintainability
C
1 day
Test Coverage
/**
 * 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 rison from 'rison';
import fetchMock from 'fetch-mock';
import { act, renderHook } from '@testing-library/react-hooks';
import {
  createWrapper,
  defaultStore as store,
} from 'spec/helpers/testing-library';
import { api } from 'src/hooks/apiResources/queryApi';
import { useTables } from './tables';

const fakeApiResult = {
  count: 2,
  result: [
    {
      id: 1,
      name: 'fake api result1',
      label: 'fake api label1',
    },
    {
      id: 2,
      name: 'fake api result2',
      label: 'fake api label2',
    },
  ],
};

const fakeHasMoreApiResult = {
  count: 4,
  result: [
    {
      id: 1,
      name: 'fake api result1',
      label: 'fake api label1',
    },
    {
      id: 2,
      name: 'fake api result2',
      label: 'fake api label2',
    },
  ],
};

const fakeSchemaApiResult = ['schema1', 'schema2'];

const expectedData = {
  options: fakeApiResult.result,
  hasMore: false,
};

const expectedHasMoreData = {
  options: fakeHasMoreApiResult.result,
  hasMore: true,
};

describe('useTables hook', () => {
  afterEach(() => {
    fetchMock.reset();
    act(() => {
      store.dispatch(api.util.resetApiState());
    });
  });

  test('returns api response mapping json options', async () => {
    const expectDbId = 'db1';
    const expectedSchema = 'schema1';
    const catalogApiRoute = `glob:*/api/v1/database/${expectDbId}/catalogs/*`;
    const schemaApiRoute = `glob:*/api/v1/database/${expectDbId}/schemas/*`;
    const tableApiRoute = `glob:*/api/v1/database/${expectDbId}/tables/?q=*`;
    fetchMock.get(tableApiRoute, fakeApiResult);
    fetchMock.get(catalogApiRoute, { count: 0, result: [] });
    fetchMock.get(schemaApiRoute, {
      result: fakeSchemaApiResult,
    });
    const { result, waitFor } = renderHook(
      () =>
        useTables({
          dbId: expectDbId,
          schema: expectedSchema,
        }),
      {
        wrapper: createWrapper({
          useRedux: true,
          store,
        }),
      },
    );
    await waitFor(() => expect(result.current.data).toEqual(expectedData));
    expect(fetchMock.calls(schemaApiRoute).length).toBe(1);
    expect(
      fetchMock.calls(
        `end:api/v1/database/${expectDbId}/tables/?q=${rison.encode({
          force: false,
          schema_name: expectedSchema,
        })}`,
      ).length,
    ).toBe(1);
    act(() => {
      result.current.refetch();
    });
    await waitFor(() =>
      expect(
        fetchMock.calls(
          `end:api/v1/database/${expectDbId}/tables/?q=${rison.encode({
            force: true,
            schema_name: expectedSchema,
          })}`,
        ).length,
      ).toBe(1),
    );
    expect(fetchMock.calls(schemaApiRoute).length).toBe(1);
    expect(result.current.data).toEqual(expectedData);
  });

  test('skips the deprecated schema option', async () => {
    const expectDbId = 'db1';
    const unexpectedSchema = 'invalid schema';
    const catalogApiRoute = `glob:*/api/v1/database/${expectDbId}/catalogs/*`;
    const schemaApiRoute = `glob:*/api/v1/database/${expectDbId}/schemas/*`;
    const tableApiRoute = `glob:*/api/v1/database/${expectDbId}/tables/?q=*`;
    fetchMock.get(tableApiRoute, fakeApiResult);
    fetchMock.get(catalogApiRoute, { count: 0, result: [] });
    fetchMock.get(schemaApiRoute, {
      result: fakeSchemaApiResult,
    });
    const { result, waitFor } = renderHook(
      () =>
        useTables({
          dbId: expectDbId,
          schema: unexpectedSchema,
        }),
      {
        wrapper: createWrapper({
          useRedux: true,
          store,
        }),
      },
    );
    await waitFor(() => expect(fetchMock.calls(schemaApiRoute).length).toBe(1));
    expect(result.current.data).toEqual(undefined);
    expect(
      fetchMock.calls(
        `end:api/v1/database/${expectDbId}/tables/?q=${rison.encode({
          force: false,
          schema_name: unexpectedSchema,
        })}`,
      ).length,
    ).toBe(0);
  });

  test('returns hasMore when total is larger than result size', async () => {
    const expectDbId = 'db1';
    const expectedSchema = 'schema2';
    const tableApiRoute = `glob:*/api/v1/database/${expectDbId}/tables/?q=*`;
    fetchMock.get(tableApiRoute, fakeHasMoreApiResult);
    fetchMock.get(`glob:*/api/v1/database/${expectDbId}/catalogs/*`, {
      count: 0,
      result: [],
    });
    fetchMock.get(`glob:*/api/v1/database/${expectDbId}/schemas/*`, {
      result: fakeSchemaApiResult,
    });
    const { result, waitFor } = renderHook(
      () =>
        useTables({
          dbId: expectDbId,
          schema: expectedSchema,
        }),
      {
        wrapper: createWrapper({
          useRedux: true,
          store,
        }),
      },
    );
    await waitFor(() => expect(fetchMock.calls(tableApiRoute).length).toBe(1));
    expect(result.current.data).toEqual(expectedHasMoreData);
  });

  test('returns cached data without api request', async () => {
    const expectDbId = 'db1';
    const expectedSchema = 'schema1';
    const tableApiRoute = `glob:*/api/v1/database/${expectDbId}/tables/?q=*`;
    fetchMock.get(tableApiRoute, fakeApiResult);
    fetchMock.get(`glob:*/api/v1/database/${expectDbId}/catalogs/*`, {
      count: 0,
      result: [],
    });
    fetchMock.get(`glob:*/api/v1/database/${expectDbId}/schemas/*`, {
      result: fakeSchemaApiResult,
    });
    const { result, rerender, waitFor } = renderHook(
      () =>
        useTables({
          dbId: expectDbId,
          schema: expectedSchema,
        }),
      {
        wrapper: createWrapper({
          useRedux: true,
          store,
        }),
      },
    );
    await waitFor(() => expect(fetchMock.calls(tableApiRoute).length).toBe(1));
    rerender();
    await waitFor(() => expect(result.current.data).toEqual(expectedData));
    expect(fetchMock.calls(tableApiRoute).length).toBe(1);
  });

  test('returns refreshed data after expires', async () => {
    const expectDbId = 'db1';
    const expectedSchema = 'schema1';
    const tableApiRoute = `glob:*/api/v1/database/${expectDbId}/tables/?q=*`;
    fetchMock.get(tableApiRoute, url =>
      url.includes(expectedSchema) ? fakeApiResult : fakeHasMoreApiResult,
    );
    fetchMock.get(`glob:*/api/v1/database/${expectDbId}/catalogs/*`, {
      count: 0,
      result: [],
    });
    fetchMock.get(`glob:*/api/v1/database/${expectDbId}/schemas/*`, {
      result: fakeSchemaApiResult,
    });
    const { result, rerender, waitFor } = renderHook(
      ({ schema }) =>
        useTables({
          dbId: expectDbId,
          schema,
        }),
      {
        initialProps: { schema: expectedSchema },
        wrapper: createWrapper({
          useRedux: true,
          store,
        }),
      },
    );

    await waitFor(() => expect(result.current.data).toEqual(expectedData));
    expect(fetchMock.calls(tableApiRoute).length).toBe(1);

    rerender({ schema: 'schema2' });
    await waitFor(() =>
      expect(result.current.data).toEqual(expectedHasMoreData),
    );
    expect(fetchMock.calls(tableApiRoute).length).toBe(2);

    rerender({ schema: expectedSchema });
    await waitFor(() => expect(result.current.data).toEqual(expectedData));
    expect(fetchMock.calls(tableApiRoute).length).toBe(2);

    // clean up cache
    act(() => {
      store.dispatch(api.util.invalidateTags(['Tables']));
    });

    await waitFor(() => expect(fetchMock.calls(tableApiRoute).length).toBe(3));
    await waitFor(() => expect(result.current.data).toEqual(expectedData));

    rerender({ schema: 'schema2' });
    await waitFor(() =>
      expect(result.current.data).toEqual(expectedHasMoreData),
    );
    expect(fetchMock.calls(tableApiRoute).length).toBe(4);

    rerender({ schema: expectedSchema });
    await waitFor(() => expect(result.current.data).toEqual(expectedData));
    expect(fetchMock.calls(tableApiRoute).length).toBe(4);
  });
});