vitess.io/vitess@v0.16.2/web/vtadmin/src/components/routes/tablet/Advanced.test.tsx (about)

     1  /**
     2   * Copyright 2022 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  
    17  import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
    18  import { createMemoryHistory } from 'history';
    19  import { merge } from 'lodash-es';
    20  import React from 'react';
    21  import { QueryClient, QueryClientProvider } from 'react-query';
    22  import { Router } from 'react-router';
    23  
    24  import { topodata, vtadmin } from '../../../proto/vtadmin';
    25  import { formatAlias } from '../../../util/tablets';
    26  import Advanced from './Advanced';
    27  
    28  const makeTablet = (overrides: Partial<vtadmin.ITablet> = {}): vtadmin.Tablet => {
    29      const defaults: vtadmin.ITablet = {
    30          cluster: { id: 'some-cluster-id', name: 'some-cluster-name' },
    31          state: vtadmin.Tablet.ServingState.SERVING,
    32          tablet: {
    33              alias: {
    34                  cell: 'zone1',
    35                  uid: 101,
    36              },
    37              type: topodata.TabletType.REPLICA,
    38          },
    39      };
    40      return vtadmin.Tablet.create(merge(defaults, overrides));
    41  };
    42  
    43  const makePrimaryTablet = (overrides: Partial<vtadmin.ITablet> = {}): vtadmin.Tablet => {
    44      return makeTablet({
    45          ...overrides,
    46          tablet: {
    47              type: topodata.TabletType.PRIMARY,
    48          },
    49      });
    50  };
    51  
    52  const renderHelper = (children: React.ReactNode) => {
    53      const history = createMemoryHistory();
    54  
    55      const queryClient = new QueryClient({
    56          defaultOptions: { queries: { retry: false } },
    57      });
    58  
    59      render(
    60          <Router history={history}>
    61              <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    62          </Router>
    63      );
    64  };
    65  
    66  const ORIGINAL_PROCESS_ENV = process.env;
    67  const TEST_PROCESS_ENV = {
    68      ...process.env,
    69      REACT_APP_VTADMIN_API_ADDRESS: '',
    70  };
    71  
    72  describe('Advanced', () => {
    73      beforeAll(() => {
    74          process.env = { ...TEST_PROCESS_ENV } as NodeJS.ProcessEnv;
    75          jest.spyOn(global, 'fetch');
    76      });
    77  
    78      beforeEach(() => {
    79          process.env = { ...TEST_PROCESS_ENV } as NodeJS.ProcessEnv;
    80          jest.clearAllMocks();
    81      });
    82  
    83      afterAll(() => {
    84          process.env = { ...ORIGINAL_PROCESS_ENV };
    85      });
    86  
    87      describe('Advanced tablet actions', () => {
    88          describe('Start Replication', () => {
    89              it('starts replication', async () => {
    90                  const tablet = makeTablet();
    91                  const alias = formatAlias(tablet.tablet?.alias) as string;
    92                  renderHelper(<Advanced alias={alias} clusterID={tablet.cluster?.id as string} tablet={tablet} />);
    93  
    94                  const container = screen.getByTitle('Start Replication');
    95                  const button = within(container).getByRole('button');
    96  
    97                  // This action does not require confirmation
    98                  const input = within(container).queryByRole('textbox');
    99                  expect(input).toBeNull();
   100  
   101                  expect(button).not.toHaveAttribute('disabled');
   102  
   103                  fireEvent.click(button);
   104  
   105                  await waitFor(() => {
   106                      expect(global.fetch).toHaveBeenCalledTimes(1);
   107                  });
   108  
   109                  expect(global.fetch).toHaveBeenCalledWith(
   110                      `/api/tablet/${alias}/start_replication?cluster=some-cluster-id`,
   111                      {
   112                          credentials: undefined,
   113                          method: 'put',
   114                      }
   115                  );
   116              });
   117  
   118              it('prevents starting replication if primary', () => {
   119                  const tablet = makePrimaryTablet();
   120                  renderHelper(
   121                      <Advanced
   122                          alias={formatAlias(tablet.tablet?.alias) as string}
   123                          clusterID={tablet.cluster?.id as string}
   124                          tablet={tablet}
   125                      />
   126                  );
   127  
   128                  const container = screen.getByTitle('Start Replication');
   129                  const button = within(container).getByRole('button');
   130                  expect(button).toHaveAttribute('disabled');
   131              });
   132          });
   133  
   134          describe('Stop Replication', () => {
   135              it('stops replication', async () => {
   136                  const tablet = makeTablet();
   137                  const alias = formatAlias(tablet.tablet?.alias) as string;
   138                  renderHelper(<Advanced alias={alias} clusterID={tablet.cluster?.id as string} tablet={tablet} />);
   139  
   140                  const container = screen.getByTitle('Stop Replication');
   141                  const button = within(container).getByRole('button');
   142  
   143                  // This action does not require confirmation
   144                  const input = within(container).queryByRole('textbox');
   145                  expect(input).toBeNull();
   146  
   147                  expect(button).not.toHaveAttribute('disabled');
   148  
   149                  fireEvent.click(button);
   150  
   151                  await waitFor(() => {
   152                      expect(global.fetch).toHaveBeenCalledTimes(1);
   153                  });
   154  
   155                  expect(global.fetch).toHaveBeenCalledWith(
   156                      `/api/tablet/${alias}/stop_replication?cluster=some-cluster-id`,
   157                      {
   158                          credentials: undefined,
   159                          method: 'put',
   160                      }
   161                  );
   162              });
   163  
   164              it('prevents stopping replication if primary', () => {
   165                  const tablet = makePrimaryTablet();
   166                  renderHelper(
   167                      <Advanced
   168                          alias={formatAlias(tablet.tablet?.alias) as string}
   169                          clusterID={tablet.cluster?.id as string}
   170                          tablet={tablet}
   171                      />
   172                  );
   173  
   174                  const container = screen.getByTitle('Stop Replication');
   175                  const button = within(container).getByRole('button');
   176                  expect(button).toHaveAttribute('disabled');
   177              });
   178          });
   179  
   180          describe('Refresh Replication Source', () => {
   181              it('refreshes replication source', async () => {
   182                  const tablet = makeTablet();
   183                  const alias = formatAlias(tablet.tablet?.alias) as string;
   184                  renderHelper(<Advanced alias={alias} clusterID={tablet.cluster?.id as string} tablet={tablet} />);
   185  
   186                  const container = screen.getByTitle('Refresh Replication Source');
   187                  const button = within(container).getByRole('button');
   188  
   189                  // This action does not require confirmation
   190                  const input = within(container).queryByRole('textbox');
   191                  expect(input).toBeNull();
   192  
   193                  expect(button).not.toHaveAttribute('disabled');
   194  
   195                  fireEvent.click(button);
   196  
   197                  await waitFor(() => {
   198                      expect(global.fetch).toHaveBeenCalledTimes(1);
   199                  });
   200  
   201                  expect(global.fetch).toHaveBeenCalledWith(`/api/tablet/${alias}/refresh_replication_source`, {
   202                      credentials: undefined,
   203                      method: 'put',
   204                  });
   205              });
   206  
   207              it('prevents refreshing if primary', () => {
   208                  const tablet = makePrimaryTablet();
   209                  renderHelper(
   210                      <Advanced
   211                          alias={formatAlias(tablet.tablet?.alias) as string}
   212                          clusterID={tablet.cluster?.id as string}
   213                          tablet={tablet}
   214                      />
   215                  );
   216  
   217                  const container = screen.getByTitle('Refresh Replication Source');
   218                  const button = within(container).getByRole('button');
   219                  expect(button).toHaveAttribute('disabled');
   220              });
   221          });
   222  
   223          describe('Delete', () => {
   224              it('deletes the tablet', async () => {
   225                  const tablet = makeTablet();
   226                  renderHelper(
   227                      <Advanced
   228                          alias={formatAlias(tablet.tablet?.alias) as string}
   229                          clusterID={tablet.cluster?.id as string}
   230                          tablet={tablet}
   231                      />
   232                  );
   233  
   234                  const container = screen.getByTitle('Delete Tablet');
   235                  const button = within(container).getByRole('button');
   236                  const input = within(container).getByRole('textbox');
   237  
   238                  expect(button).toHaveAttribute('disabled');
   239  
   240                  fireEvent.change(input, { target: { value: 'zone1-101' } });
   241                  expect(button).not.toHaveAttribute('disabled');
   242  
   243                  fireEvent.click(button);
   244  
   245                  await waitFor(() => {
   246                      expect(global.fetch).toHaveBeenCalledTimes(1);
   247                  });
   248  
   249                  expect(global.fetch).toHaveBeenCalledWith('/api/tablet/zone1-101?cluster=some-cluster-id', {
   250                      credentials: undefined,
   251                      method: 'delete',
   252                  });
   253              });
   254  
   255              it('deletes the tablet with allow_primary=true if primary', async () => {
   256                  const tablet = makePrimaryTablet();
   257                  renderHelper(
   258                      <Advanced
   259                          alias={formatAlias(tablet.tablet?.alias) as string}
   260                          clusterID={tablet.cluster?.id as string}
   261                          tablet={tablet}
   262                      />
   263                  );
   264  
   265                  const container = screen.getByTitle('Delete Tablet');
   266                  const button = within(container).getByRole('button');
   267                  const input = within(container).getByRole('textbox');
   268  
   269                  expect(button).toHaveAttribute('disabled');
   270  
   271                  fireEvent.change(input, { target: { value: 'zone1-101' } });
   272                  expect(button).not.toHaveAttribute('disabled');
   273  
   274                  fireEvent.click(button);
   275  
   276                  await waitFor(() => {
   277                      expect(global.fetch).toHaveBeenCalledTimes(1);
   278                  });
   279  
   280                  expect(global.fetch).toHaveBeenCalledWith(
   281                      '/api/tablet/zone1-101?cluster=some-cluster-id&allow_primary=true',
   282                      {
   283                          credentials: undefined,
   284                          method: 'delete',
   285                      }
   286                  );
   287              });
   288          });
   289      });
   290  });