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 });