vitess.io/vitess@v0.16.2/web/vtadmin/src/hooks/api.test.tsx (about)

     1  /**
     2   * Copyright 2021 The Vitess Authors.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  import React from 'react';
    17  import { QueryClient, QueryClientProvider } from 'react-query';
    18  import { renderHook } from '@testing-library/react-hooks';
    19  
    20  import * as api from './api';
    21  import * as httpAPI from '../api/http';
    22  import { vtadmin as pb } from '../proto/vtadmin';
    23  
    24  jest.mock('../api/http');
    25  
    26  describe('useWorkflows', () => {
    27      const tests: {
    28          name: string;
    29          response: pb.GetWorkflowsResponse | undefined;
    30          expected: pb.Workflow[] | undefined;
    31      }[] = [
    32          {
    33              name: 'returns a flat list of workflows',
    34              response: pb.GetWorkflowsResponse.create({
    35                  workflows_by_cluster: {
    36                      east: {
    37                          workflows: [
    38                              {
    39                                  workflow: { name: 'one-goes-east' },
    40                              },
    41                          ],
    42                      },
    43                      west: {
    44                          workflows: [
    45                              {
    46                                  workflow: { name: 'one-goes-west' },
    47                              },
    48                          ],
    49                      },
    50                  },
    51              }),
    52              expected: [
    53                  pb.Workflow.create({ workflow: { name: 'one-goes-east' } }),
    54                  pb.Workflow.create({ workflow: { name: 'one-goes-west' } }),
    55              ],
    56          },
    57      ];
    58  
    59      const queryClient = new QueryClient();
    60      const wrapper: React.FunctionComponent = ({ children }) => (
    61          <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    62      );
    63  
    64      test.each(tests.map(Object.values))(
    65          '%s',
    66          async (name: string, response: pb.GetWorkflowsResponse | undefined, expected: pb.Workflow[] | undefined) => {
    67              (httpAPI.fetchWorkflows as any).mockResolvedValueOnce(response);
    68  
    69              const { result, waitFor } = renderHook(() => api.useWorkflows(), { wrapper });
    70  
    71              // Check that our query helper handles when the query is still in flight
    72              expect(result.current.data).toBeUndefined();
    73  
    74              // "Wait" for the underlying fetch request to resolve (scare-quotes because,
    75              // in practice, we're not "waiting" for anything since the response is mocked.)
    76              await waitFor(() => result.current.isSuccess);
    77  
    78              expect(result.current.data).toEqual(expected);
    79          }
    80      );
    81  });
    82  
    83  describe('useWorkflow', () => {
    84      it('fetches data if no cached data exists', async () => {
    85          const queryClient = new QueryClient();
    86          const wrapper: React.FunctionComponent = ({ children }) => (
    87              <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    88          );
    89  
    90          const fetchWorkflowResponse = pb.Workflow.create({
    91              cluster: { name: 'cluster1', id: 'cluster1' },
    92              keyspace: 'dogs',
    93              workflow: {
    94                  name: 'one-goes-west',
    95                  max_v_replication_lag: 0,
    96              },
    97          });
    98  
    99          (httpAPI.fetchWorkflow as any).mockResolvedValueOnce(fetchWorkflowResponse);
   100  
   101          const { result, waitFor } = renderHook(
   102              () =>
   103                  api.useWorkflow({
   104                      clusterID: 'cluster1',
   105                      keyspace: 'dogs',
   106                      name: 'one-goes-west',
   107                  }),
   108              { wrapper }
   109          );
   110  
   111          expect(result.current.data).toBeUndefined();
   112  
   113          await waitFor(() => result.current.isSuccess);
   114          expect(result.current.data).toEqual(fetchWorkflowResponse);
   115      });
   116  
   117      // This test corresponds to a common UI flow from a component that fetches all the workflows
   118      // to a component that fetches a single workflow.
   119      it('uses cached data as initialData', async () => {
   120          const queryClient = new QueryClient();
   121          const wrapper: React.FunctionComponent = ({ children }) => (
   122              <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
   123          );
   124  
   125          (httpAPI.fetchWorkflows as any).mockResolvedValueOnce(
   126              pb.GetWorkflowsResponse.create({
   127                  workflows_by_cluster: {
   128                      cluster1: {
   129                          workflows: [
   130                              {
   131                                  cluster: { name: 'cluster1', id: 'cluster1' },
   132                                  keyspace: 'dogs',
   133                                  workflow: {
   134                                      name: 'one-goes-east',
   135                                      max_v_replication_lag: 0,
   136                                  },
   137                              },
   138                              {
   139                                  cluster: { name: 'cluster1', id: 'cluster1' },
   140                                  keyspace: 'dogs',
   141                                  workflow: {
   142                                      name: 'one-goes-west',
   143                                      max_v_replication_lag: 0,
   144                                  },
   145                              },
   146                          ],
   147                      },
   148                      cluster2: {
   149                          workflows: [
   150                              {
   151                                  cluster: { name: 'cluster2', id: 'cluster2' },
   152                                  keyspace: 'dogs',
   153                                  workflow: {
   154                                      name: 'one-goes-west',
   155                                      max_v_replication_lag: 0,
   156                                  },
   157                              },
   158                          ],
   159                      },
   160                  },
   161              })
   162          );
   163  
   164          (httpAPI.fetchWorkflow as any).mockResolvedValueOnce(
   165              pb.Workflow.create({
   166                  cluster: { name: 'cluster1', id: 'cluster1' },
   167                  keyspace: 'dogs',
   168                  workflow: {
   169                      name: 'one-goes-west',
   170                      max_v_replication_lag: 420,
   171                  },
   172              })
   173          );
   174  
   175          // Execute a useWorkflows query to populate the query cache.
   176          // eslint-disable-next-line testing-library/render-result-naming-convention
   177          const useWorkflowsCall = renderHook(() => api.useWorkflows(), { wrapper });
   178          await useWorkflowsCall.waitFor(() => useWorkflowsCall.result.current.isSuccess);
   179  
   180          // Next, execute the useWorkflow query we *actually* want to inspect.
   181          const { result, waitFor } = renderHook(
   182              () =>
   183                  api.useWorkflow(
   184                      {
   185                          clusterID: 'cluster1',
   186                          keyspace: 'dogs',
   187                          name: 'one-goes-west',
   188                      },
   189                      // Force the query to refetch
   190                      { staleTime: 0 }
   191                  ),
   192              { wrapper }
   193          );
   194  
   195          // We expect the result to be successful, even though we've yet to resolve the /workflow API call,
   196          // since the workflow we want exists in the cache.
   197          expect(result.current.isSuccess).toBe(true);
   198  
   199          expect(result.current.data).toEqual(
   200              pb.Workflow.create({
   201                  cluster: { name: 'cluster1', id: 'cluster1' },
   202                  keyspace: 'dogs',
   203                  workflow: {
   204                      name: 'one-goes-west',
   205                      max_v_replication_lag: 0,
   206                  },
   207              })
   208          );
   209  
   210          // We _also_ check that a fetch request is in-flight to fetch updated data.
   211          expect(result.current.isFetching).toBe(true);
   212          expect(httpAPI.fetchWorkflow).toHaveBeenCalledTimes(1);
   213  
   214          // Then, we resolve the API call, with updated data (in this case max_v_replication_lag)
   215          // so we can check that the cache is updated.
   216          await waitFor(() => !result.current.isFetching && result.current.isSuccess);
   217          expect(result.current.data).toEqual(
   218              pb.Workflow.create({
   219                  cluster: { name: 'cluster1', id: 'cluster1' },
   220                  keyspace: 'dogs',
   221                  workflow: {
   222                      name: 'one-goes-west',
   223                      max_v_replication_lag: 420,
   224                  },
   225              })
   226          );
   227      });
   228  });