fix: add validation for test case name, added restriction for :: " > character (#23391)

* fix: add validation for test case name, added restriction for `:: " >` character

* added playwright test for the changes

* fx failing test
This commit is contained in:
Shailesh Parmar 2025-09-15 17:50:38 +05:30
parent 46fb77fd8e
commit eac640ed27
23 changed files with 168 additions and 12 deletions

View File

@ -57,6 +57,23 @@ test.describe('Add TestCase New Flow', () => {
paramsValue,
expectSchedulerCard = true,
} = data;
await page.getByTestId('test-case-name').click();
// test case name restriction for `:: " >` character
const invalidTestCaseNames = ['test::case', 'test"case', 'test>case'];
for (const name of invalidTestCaseNames) {
await page.getByTestId('test-case-name').fill(name);
await page.waitForSelector(`#testCaseFormV1_testName_help`, {
state: 'visible',
});
await expect(page.locator('#testCaseFormV1_testName_help')).toHaveText(
'Name cannot contain double colons (::), quotes ("), or greater-than symbols (>).'
);
await page.getByTestId('test-case-name').clear();
}
await page.getByTestId('test-case-name').fill(`${testTypeId}_test_case`);
await page.click('#testCaseFormV1_testTypeId');
await page.fill('#testCaseFormV1_testTypeId', testType);

View File

@ -10,14 +10,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
act,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import { forwardRef } from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { act, forwardRef } from 'react';
import { TEST_CASE_NAME_REGEX } from '../../../../constants/regex.constants';
import { MOCK_TABLE } from '../../../../mocks/TableData.mock';
import { MOCK_TEST_CASE } from '../../../../mocks/TestSuite.mock';
import { getIngestionPipelines } from '../../../../rest/ingestionPipelineAPI';
@ -164,6 +159,7 @@ jest.mock('../../../../rest/testAPI', () => ({
getListTestDefinitions: jest.fn().mockResolvedValue(mockTestDefinitions),
getListTestCase: jest.fn().mockResolvedValue({ data: [] }),
createTestCase: jest.fn().mockResolvedValue(MOCK_TEST_CASE[0]),
getListTestCaseBySearch: jest.fn().mockResolvedValue({ data: [] }),
getTestCaseByFqn: jest.fn().mockResolvedValue(MOCK_TEST_CASE[0]),
TestCaseType: {
all: 'all',
@ -305,6 +301,12 @@ jest.mock('../../../../utils/ToastUtils', () => ({
showSuccessToast: jest.fn(),
}));
// Mock formUtils to prevent scroll issues in tests
jest.mock('../../../../utils/formUtils', () => ({
...jest.requireActual('../../../../utils/formUtils'),
createScrollToErrorHandler: jest.fn(() => jest.fn()),
}));
describe('TestCaseFormV1 Component', () => {
beforeEach(() => {
jest.clearAllMocks();
@ -983,6 +985,63 @@ describe('TestCaseFormV1 Component', () => {
});
});
describe('Test Case Name Validation', () => {
it('should render test case name field with validation rules', async () => {
render(<TestCaseFormV1 {...mockProps} />);
await waitFor(() => {
expect(screen.getByTestId('test-case-form-v1')).toBeInTheDocument();
});
const testNameField = screen.getByTestId('test-case-name');
expect(testNameField).toBeInTheDocument();
// Test that the field accepts input
await act(async () => {
fireEvent.change(testNameField, {
target: { value: 'valid_test_name' },
});
});
expect(testNameField).toHaveValue('valid_test_name');
});
it('should accept valid test case names without validation errors', async () => {
render(<TestCaseFormV1 {...mockProps} />);
await waitFor(() => {
expect(screen.getByTestId('test-case-form-v1')).toBeInTheDocument();
});
const testNameField = screen.getByTestId('test-case-name');
// Test valid name format
const validName = 'table_column_count_equals';
await act(async () => {
fireEvent.change(testNameField, { target: { value: validName } });
fireEvent.blur(testNameField);
});
// Field should accept the valid input
expect(testNameField).toHaveValue(validName);
});
it('should have TEST_CASE_NAME_REGEX validation configured', () => {
// Test that TEST_CASE_NAME_REGEX pattern is correctly configured
// Test forbidden characters
expect(TEST_CASE_NAME_REGEX.test('test::case')).toBe(false);
expect(TEST_CASE_NAME_REGEX.test('test"case')).toBe(false);
expect(TEST_CASE_NAME_REGEX.test('test>case')).toBe(false);
// Test allowed characters
expect(TEST_CASE_NAME_REGEX.test('table_column_count_equals')).toBe(true);
expect(TEST_CASE_NAME_REGEX.test('valid.test.name')).toBe(true);
expect(TEST_CASE_NAME_REGEX.test('test case with spaces')).toBe(true);
});
});
describe('Test Cases Selection Logic', () => {
it('should set testCases to undefined when selectAllTestCases is not false', () => {
const testValues = {

View File

@ -42,7 +42,7 @@ import {
PAGE_SIZE_LARGE,
PAGE_SIZE_MEDIUM,
} from '../../../../constants/constants';
import { ENTITY_NAME_REGEX } from '../../../../constants/regex.constants';
import { TEST_CASE_NAME_REGEX } from '../../../../constants/regex.constants';
import { DEFAULT_SCHEDULE_CRON_DAILY } from '../../../../constants/Schedular.constants';
import { useAirflowStatus } from '../../../../context/AirflowStatusProvider/AirflowStatusProvider';
import { useLimitStore } from '../../../../context/LimitsProvider/useLimitsStore';
@ -344,8 +344,8 @@ const TestCaseFormV1: FC<TestCaseFormV1Props> = ({
placeholder: t('message.enter-test-case-name'),
rules: [
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
pattern: TEST_CASE_NAME_REGEX,
message: t('message.test-case-name-validation'),
},
{
max: MAX_NAME_LENGTH,

View File

@ -10,7 +10,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ENTITY_NAME_REGEX, TAG_NAME_REGEX } from './regex.constants';
import {
ENTITY_NAME_REGEX,
TAG_NAME_REGEX,
TEST_CASE_NAME_REGEX,
} from './regex.constants';
describe('Test Regex', () => {
it('EntityName regex should pass for the valid entity name', () => {
@ -209,4 +213,29 @@ describe('Test Regex', () => {
expect(TAG_NAME_REGEX.test('')).toEqual(false);
});
});
describe('TEST_CASE_NAME_REGEX', () => {
it('should reject names with forbidden characters', () => {
expect(TEST_CASE_NAME_REGEX.test('test::case')).toEqual(false);
expect(TEST_CASE_NAME_REGEX.test('test"case')).toEqual(false);
expect(TEST_CASE_NAME_REGEX.test('test>case')).toEqual(false);
expect(TEST_CASE_NAME_REGEX.test('test::case"name>invalid')).toEqual(
false
);
});
it('should accept names with allowed characters', () => {
expect(TEST_CASE_NAME_REGEX.test('table_column_count_equals')).toEqual(
true
);
expect(
TEST_CASE_NAME_REGEX.test('shop_id.column_value_max_to_be_between')
).toEqual(true);
expect(TEST_CASE_NAME_REGEX.test('test case with spaces')).toEqual(true);
expect(TEST_CASE_NAME_REGEX.test('test_case_123')).toEqual(true);
expect(TEST_CASE_NAME_REGEX.test('TestCase-WithHyphens')).toEqual(true);
expect(TEST_CASE_NAME_REGEX.test('test.case.with.dots')).toEqual(true);
expect(TEST_CASE_NAME_REGEX.test('test_case_!@#$%^&*()')).toEqual(true);
});
});
});

View File

@ -20,6 +20,15 @@ export const EMAIL_REG_EX = /^\S+@\S+\.\S+$/;
*/
export const ENTITY_NAME_REGEX = /^((?!::).)*$/;
/**
* Matches any string that does NOT contain the following:
* - Double colon (::)
* - Double quote (")
* - Greater-than symbol (>)
* Useful for restricting names from including these forbidden characters.
*/
export const TEST_CASE_NAME_REGEX = /^((?!::)(?!")(?!>).)*$/;
export const TAG_NAME_REGEX = /^[\p{L}\p{M}\w\- .&()]+$/u;
export const passwordRegex =

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "Team erfolgreich verschoben!",
"team-no-asset": "Ihr Team hat keine Vermögenswerte.",
"test-case-insight-description": "Zugriff auf eine zentrale Ansicht der Gesundheit Ihres Datensatzes basierend auf konfigurierten Testvalidierungen.",
"test-case-name-validation": "Der Name darf keine doppelten Doppelpunkte (::), Anführungszeichen (\") oder Größer-als-Symbole (>) enthalten.",
"test-case-schedule-description": "Die Datenqualitätstests können mit der gewünschten Häufigkeit geplant werden. Die Zeitzone ist UTC.",
"test-connection-cannot-be-triggered": "Die Testverbindung kann nicht ausgelöst werden.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "Team moved successfully!",
"team-no-asset": "Your team does not have any assets.",
"test-case-insight-description": "Access a centralized view of your dataset's health based on configured test validations.",
"test-case-name-validation": "Name cannot contain double colons (::), quotes (\"), or greater-than symbols (>).",
"test-case-schedule-description": "The data quality tests can be scheduled to run at the desired frequency. The timezone is in UTC.",
"test-connection-cannot-be-triggered": "Test connection cannot be triggered.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "¡Equipo movido con éxito!",
"team-no-asset": "Tu equipo no tiene ningún activo.",
"test-case-insight-description": "Accede a una vista centralizada de la salud de tu dataset basada en las validaciones de prueba configuradas.",
"test-case-name-validation": "El nombre no puede contener dos puntos dobles (::), comillas (\") o símbolos de mayor que (>).",
"test-case-schedule-description": "The data quality tests can be scheduled to run at the desired frequency. The timezone is in UTC.",
"test-connection-cannot-be-triggered": "No se puede iniciar la prueba de conexión.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "L'équipe a été déplacée avec succès !",
"team-no-asset": "Votre équipe n'a pas d'actifs de données",
"test-case-insight-description": "Accédez à une vue centralisée de la santé de votre dataset basée sur les validations de test configurées.",
"test-case-name-validation": "Le nom ne peut pas contenir de double deux-points (::), de guillemets (\") ou de symboles supérieur à (>).",
"test-case-schedule-description": "Les tests de quelité de données peuvent être planifiés pour tourner à la fréquence désirée. La timezone est en UTC.",
"test-connection-cannot-be-triggered": "Le test de connexion ne peut pas être déclenché.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "O equipo moveuse correctamente!",
"team-no-asset": "O teu equipo non ten ningún activo.",
"test-case-insight-description": "Accede a unha vista centralizada da saúde do teu conxunto de datos baseada en validacións de proba configuradas.",
"test-case-name-validation": "O nome non pode conter dous puntos duplos (::), comiñas (\") ou símbolos de maior que (>).",
"test-case-schedule-description": "As probas de calidade de datos poden programarse para executarse coa frecuencia desexada. A zona horaria está en UTC.",
"test-connection-cannot-be-triggered": "Non se pode iniciar a conexión de proba.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "הקבוצה הועברה בהצלחה!",
"team-no-asset": "לקבוצתך אין נכסים.",
"test-case-insight-description": "גישה לתצוגה מרכזית של בריאות מערך הנתונים שלך על בסיס אימותי בדיקה מוגדרים.",
"test-case-name-validation": "השם לא יכול להכיל נקודתיים כפולות (::), מרכאות (\") או סמלי גדול מ- (>).",
"test-case-schedule-description": "The data quality tests can be scheduled to run at the desired frequency. The timezone is in UTC.",
"test-connection-cannot-be-triggered": "אין אפשרות להפעיל בדיקת חיבור.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "チームの移動に成功しました!",
"team-no-asset": "あなたのチームにはアセットがありません。",
"test-case-insight-description": "設定されたテストバリデーションに基づいて、データセットの健全性を集中して確認できます。",
"test-case-name-validation": "名前にはダブルコロン(::)、引用符(\")、またはより大きい記号(>)を含めることはできません。",
"test-case-schedule-description": "データ品質テストは希望の頻度でスケジュール実行できます。タイムゾーンはUTCです。",
"test-connection-cannot-be-triggered": "接続テストを開始できません。",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "팀이 성공적으로 이동되었습니다!",
"team-no-asset": "귀하의 팀은 자산이 없습니다.",
"test-case-insight-description": "구성된 테스트 검증을 기반으로 데이터셋 상태의 중앙 집중식 보기에 액세스합니다.",
"test-case-name-validation": "이름에는 이중 콜론(::), 따옴표(\"), 또는 보다 큰 기호(>)를 포함할 수 없습니다.",
"test-case-schedule-description": "데이터 품질 테스트는 원하는 빈도로 실행되도록 예약할 수 있습니다. 시간대는 UTC입니다.",
"test-connection-cannot-be-triggered": "연결 테스트를 트리거할 수 없습니다.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "टीम यशस्वीरित्या हलवली!",
"team-no-asset": "तुमच्या टीमकडे कोणतीही ॲसेट नाहीत.",
"test-case-insight-description": "कॉन्फिगर केलेल्या चाचणी प्रमाणीकरणांवर आधारित आपल्या डेटासेटच्या आरोग्याचे केंद्रीकृत दृश्य प्रवेश करा.",
"test-case-name-validation": "नावामध्ये डबल कॉलन (::), अवतरण (\") किंवा मोठे चिन्ह (>) असू शकत नाही.",
"test-case-schedule-description": "डेटा गुणवत्ता चाचण्या इच्छित वारंवारतेवर चालवण्यासाठी वेळापत्रक करता येतात. टाइमझोन UTC मध्ये आहे.",
"test-connection-cannot-be-triggered": "कनेक्शन चाचणी ट्रिगर केली जाऊ शकत नाही.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "Team succesvol verplaatst!",
"team-no-asset": "Je team heeft geen assets.",
"test-case-insight-description": "Toegang tot een gecentraliseerde weergave van de gezondheid van uw dataset op basis van geconfigureerde testvalidaties.",
"test-case-name-validation": "Naam mag geen dubbele dubbelpunten (::), aanhalingstekens (\") of groter-dan symbolen (>) bevatten.",
"test-case-schedule-description": "The data quality tests can be scheduled to run at the desired frequency. The timezone is in UTC.",
"test-connection-cannot-be-triggered": "Testconnectie kan niet worden geactiveerd.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "تیم با موفقیت منتقل شد!",
"team-no-asset": "تیم شما هیچ دارایی ندارد.",
"test-case-insight-description": "دسترسی به نمای متمرکز سلامت مجموعه داده‌های شما بر اساس اعتبارسنجی‌های آزمایش پیکربندی شده.",
"test-case-name-validation": "نام نمی‌تواند شامل دو نقطه دوگانه (::)، نقل قول (\") یا نمادهای بزرگتر از (>) باشد.",
"test-case-schedule-description": "آزمون‌های کیفیت داده می‌توانند به صورت دوره‌ای برنامه‌ریزی شوند. منطقه زمانی به صورت UTC است.",
"test-connection-cannot-be-triggered": "اتصال تست نمی‌تواند اجرا شود.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "Equipe movida com sucesso!",
"team-no-asset": "Sua equipe não possui ativos.",
"test-case-insight-description": "Acesse uma visão centralizada da saúde do seu dataset baseada em validações de teste configuradas.",
"test-case-name-validation": "O nome não pode conter dois pontos duplos (::), aspas (\") ou símbolos de maior que (>).",
"test-case-schedule-description": "Os testes de qualidade de dados podem ser programados para serem executados na frequência desejada. O fuso horário está em UTC.",
"test-connection-cannot-be-triggered": "Não é possível acionar o teste de conexão.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "Equipa movida com sucesso!",
"team-no-asset": "Sua equipa não possui ativos.",
"test-case-insight-description": "Aceda a uma vista centralizada da saúde do seu conjunto de dados baseada em validações de teste configuradas.",
"test-case-name-validation": "O nome não pode conter dois pontos duplos (::), aspas (\") ou símbolos de maior que (>).",
"test-case-schedule-description": "The data quality tests can be scheduled to run at the desired frequency. The timezone is in UTC.",
"test-connection-cannot-be-triggered": "Não é possível acionar o teste de conexão.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "Команда успешно переехала!",
"team-no-asset": "У вашей команды нет объектов.",
"test-case-insight-description": "Доступ к централизованному представлению состояния вашего набора данных на основе настроенных проверок тестов.",
"test-case-name-validation": "Имя не может содержать двойные двоеточия (::), кавычки (\") или символы больше (>).",
"test-case-schedule-description": "Тесты качества данных можно запланировать на желаемую частоту. Часовой пояс — UTC.",
"test-connection-cannot-be-triggered": "Тестовое соединение не может быть запущено.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "ย้ายทีมสำเร็จ!",
"team-no-asset": "ทีมของคุณไม่มีสินทรัพย์ใด ๆ",
"test-case-insight-description": "เข้าถึงมุมมองรวมศูนย์ของสุขภาพชุดข้อมูลของคุณตามการตรวจสอบทดสอบที่กำหนดค่าไว้",
"test-case-name-validation": "ชื่อไม่สามารถมีเครื่องหมายโคลอนคู่ (::) เครื่องหมายคำพูด (\") หรือเครื่องหมายมากกว่า (>) ได้",
"test-case-schedule-description": "การทดสอบคุณภาพข้อมูลสามารถกำหนดเวลาให้ดำเนินการในความถี่ที่ต้องการ เขตเวลาคือ UTC",
"test-connection-cannot-be-triggered": "ไม่สามารถกระตุ้นการทดสอบการเชื่อมต่อได้",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "Takım başarıyla taşındı!",
"team-no-asset": "Takımınızın herhangi bir varlığı yok.",
"test-case-insight-description": "Yapılandırılmış test doğrulamalarına dayalı olarak veri setinizin sağlığının merkezi görünümüne erişin.",
"test-case-name-validation": "Ad çift iki nokta (::), tırnak işareti (\") veya büyüktür simgesi (>) içeremez.",
"test-case-schedule-description": "Veri kalitesi testleri istenen sıklıkta çalışacak şekilde zamanlanabilir. Saat dilimi UTC'dir.",
"test-connection-cannot-be-triggered": "Bağlantı testi tetiklenemez.",
"test-connection-taking-too-long": {

View File

@ -2424,6 +2424,7 @@
"team-moved-success": "团队移动成功",
"team-no-asset": "您的团队没有任何资产",
"test-case-insight-description": "基于配置的测试验证,访问数据集健康状况的集中视图。",
"test-case-name-validation": "名称不能包含双冒号(::)、引号(\")或大于符号(>)。",
"test-case-schedule-description": "数据质控测试可按所需频率安排运行, 时区为 UTC",
"test-connection-cannot-be-triggered": "连接测试无法被触发",
"test-connection-taking-too-long": {

View File

@ -693,7 +693,14 @@
"frequently-joined-column-plural": "經常連接的欄位",
"frequently-joined-table-plural": "經常連接的資料表",
"friday": "星期五",
"from-child-entity-fqn": "From Child Entity FQN",
"from-domain": "From Domain",
"from-entity": "From Entity",
"from-entity-fqn": "From Entity FQN",
"from-lowercase": "從",
"from-owner-plural": "From Owners",
"from-service-name": "From Service Name",
"from-service-type": "From Service Type",
"full-name": "全名",
"full-screen": "全螢幕",
"full-size": "完整大小",
@ -755,6 +762,7 @@
"hide": "隱藏",
"hide-all": "全部隱藏",
"hide-deleted-entity": "隱藏已刪除的 {{entity}}",
"hide-property-plural": "Hide Properties",
"hierarchy": "階層",
"hierarchy-depth": "階層深度",
"hierarchy-label": "階層:",
@ -795,6 +803,7 @@
"ingestion-pipeline": "擷取管線",
"ingestion-pipeline-name": "擷取管線名稱",
"ingestion-plural": "擷取",
"ingestion-runner": "Ingestion Runner",
"ingestion-workflow-lowercase": "擷取工作流程",
"inherited": "已繼承",
"inherited-entity": "已繼承的 {{entity}}",
@ -1154,14 +1163,21 @@
"pii-uppercase": "PII",
"pipe-symbol": "|",
"pipeline": "管線",
"pipeline-description": "Pipeline Description",
"pipeline-detail-plural": "管線詳情",
"pipeline-detail-plural-lowercase": "管線詳情",
"pipeline-display-name": "Pipeline Display Name",
"pipeline-domain": "Pipeline Domain",
"pipeline-lowercase": "管線",
"pipeline-lowercase-plural": "管線",
"pipeline-name": "管線名稱",
"pipeline-owner-plural": "Pipeline Owners",
"pipeline-plural": "管線",
"pipeline-service": "管線服務",
"pipeline-service-name": "Pipeline Service Name",
"pipeline-service-type": "Pipeline Service Type",
"pipeline-state": "管線狀態",
"pipeline-type": "Pipeline Type",
"platform": "平台",
"platform-type-lineage": "{{platformType}} 血緣",
"play": "播放",
@ -1621,9 +1637,16 @@
"timeout": "逾時",
"timezone": "時區",
"title": "標題",
"to-child-entity-fqn": "To Child Entity FQN",
"to-domain": "To Domain",
"to-entity": "To Entity",
"to-entity-fqn": "To Entity FQN",
"to-learn-more-please-check-out": "要了解更多,請查看",
"to-lowercase": "至",
"to-owner-plural": "To Owners",
"to-perform": "執行",
"to-service-name": "To Service Name",
"to-service-type": "To Service Type",
"token-end-point": "權杖端點",
"token-expiration": "權杖到期",
"token-expired": "權杖已到期",
@ -1733,6 +1756,7 @@
"view-new-count": "檢視 {{count}} 個新的",
"view-parsing-timeout-limit": "檢視定義剖析逾時限制",
"view-plural": "檢視",
"view-property-plural": "View Properties",
"visit-developer-website": "造訪開發者網站",
"volume-change": "量變更",
"wants-to-access-your-account": "想要存取您的 {{username}} 帳戶",
@ -2400,6 +2424,7 @@
"team-moved-success": "團隊移動成功!",
"team-no-asset": "您的團隊沒有任何資產。",
"test-case-insight-description": "根據設定的測試驗證,存取您資料集的健康狀況集中檢視。",
"test-case-name-validation": "名稱不能包含雙冒號(::)、引號(\")或大於符號(>)。",
"test-case-schedule-description": "可以排程資料品質測試以所需的頻率執行。時區為 UTC。",
"test-connection-cannot-be-triggered": "無法觸發測試連線。",
"test-connection-taking-too-long": {