210 lines
6.6 KiB
TypeScript

/*
* Copyright 2024 Collate.
* Licensed 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 { APIResponse, expect, Page } from '@playwright/test';
import { BIG_ENTITY_DELETE_TIMEOUT } from '../constant/delete';
import { GlobalSettingOptions } from '../constant/settings';
import { EntityTypeEndpoint } from '../support/entity/Entity.interface';
import { getApiContext, toastNotification } from './common';
import { escapeESReservedCharacters, getEncodedFqn } from './entity';
export enum Services {
Database = GlobalSettingOptions.DATABASES,
Messaging = GlobalSettingOptions.MESSAGING,
Dashboard = GlobalSettingOptions.DASHBOARDS,
Pipeline = GlobalSettingOptions.PIPELINES,
MLModels = GlobalSettingOptions.MLMODELS,
Storage = GlobalSettingOptions.STORAGES,
Search = GlobalSettingOptions.SEARCH,
API = GlobalSettingOptions.APIS,
}
export const getEntityTypeFromService = (service: Services) => {
switch (service) {
case Services.Dashboard:
return EntityTypeEndpoint.DashboardService;
case Services.Database:
return EntityTypeEndpoint.DatabaseService;
case Services.Storage:
return EntityTypeEndpoint.StorageService;
case Services.Messaging:
return EntityTypeEndpoint.MessagingService;
case Services.Search:
return EntityTypeEndpoint.SearchService;
case Services.MLModels:
return EntityTypeEndpoint.MlModelService;
case Services.Pipeline:
return EntityTypeEndpoint.PipelineService;
case Services.API:
return EntityTypeEndpoint.ApiService;
default:
return EntityTypeEndpoint.DatabaseService;
}
};
export const getServiceCategoryFromService = (service: Services) => {
switch (service) {
case Services.Dashboard:
return 'dashboardService';
case Services.Database:
return 'databaseService';
case Services.Storage:
return 'storageService';
case Services.Messaging:
return 'messagingService';
case Services.Search:
return 'searchService';
case Services.MLModels:
return 'mlmodelService';
case Services.Pipeline:
return 'pipelineService';
case Services.API:
return 'apiService';
default:
return 'databaseService';
}
};
export const deleteService = async (
typeOfService: Services,
serviceName: string,
page: Page
) => {
await page.goto(
`/service/${getServiceCategoryFromService(typeOfService)}s/${getEncodedFqn(
serviceName
)}?currentPage=1`
);
await page.waitForLoadState('networkidle');
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
await expect(page.getByTestId('entity-header-name')).toHaveText(serviceName);
// Clicking on permanent delete radio button and checking the service name
await page.click('[data-testid="manage-button"]');
await page.waitForSelector('[data-menu-id*="delete-button"]');
await page.click('[data-testid="delete-button-title"]');
// Clicking on permanent delete radio button and checking the service name
await page.click('[data-testid="hard-delete-option"]');
await page.click(`[data-testid="hard-delete-option"] >> text=${serviceName}`);
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
const deleteResponse = page.waitForResponse((response) =>
response
.url()
.includes(
`/api/v1/services/${getServiceCategoryFromService(
typeOfService
)}s/async`
)
);
await page.click('[data-testid="confirm-button"]');
await deleteResponse;
// Closing the toast notification
await toastNotification(
page,
/deleted successfully!/,
BIG_ENTITY_DELETE_TIMEOUT
); // Wait for up to 5 minutes for the toast notification to appear
await page.reload();
await page.waitForLoadState('networkidle');
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
const serviceSearchResponse = page.waitForResponse(
`/api/v1/search/query?q=*${encodeURIComponent(
escapeESReservedCharacters(serviceName)
)}*`
);
await page.fill('[data-testid="searchbar"]', serviceName);
await serviceSearchResponse;
await page.waitForSelector(`[data-testid="service-name-${serviceName}"]`, {
state: 'detached',
});
};
export const testConnection = async (page: Page) => {
// Test the connection
await page.waitForSelector('[data-testid="test-connection-btn"]');
await page.click('[data-testid="test-connection-btn"]');
const modalTitle = page.locator(
'[data-testid="test-connection-modal"] .ant-modal-title'
);
await expect(modalTitle).toBeVisible();
await page.getByRole('button', { name: 'OK' }).click();
// Wait for the success badge or the warning badge to appear
const successBadge = page.locator('[data-testid="success-badge"]');
const warningBadge = page.locator('[data-testid="warning-badge"]');
const failBadge = page.locator('[data-testid="fail-badge"]');
await expect(successBadge.or(warningBadge).or(failBadge)).toBeVisible({
timeout: 2.5 * 60 * 1000,
});
await expect(page.getByTestId('messag-text')).toContainText(
new RegExp(
'Connection test was successful.|' +
'Test connection partially successful: Some steps had failures, we will only ingest partial metadata. Click here to view details.|' +
'Test connection failed, please validate your connection and permissions for the failed steps.',
'g'
)
);
};
export const checkServiceFieldSectionHighlighting = async (
page: Page,
field: string
) => {
await page.waitForSelector(`[data-id="${field}"][data-highlighted="true"]`);
};
type RetryRequestData = {
page: Page;
retries?: number;
} & (
| { url: string; fn?: never }
| { fn: () => Promise<APIResponse>; url?: never }
);
export const makeRetryRequest = async (data: RetryRequestData) => {
const { url, page, retries = 3, fn } = data;
const { apiContext } = await getApiContext(page);
for (let i = 0; i < retries; i++) {
try {
const response = await (fn ? fn() : apiContext.get(url));
return response.json();
} catch (error) {
if (i === retries - 1) {
throw error;
}
await page.waitForTimeout(1000 * (i + 1)); // Exponential backoff
}
}
};