Files
ktx-mihirandAbhay Padamani e3927ef5d3 fix: fuctions module form migration with zod (#12970)
- form migration with zod schema for functions module

---------

Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
2026-07-14 12:57:37 +00:00

256 lines
12 KiB
JavaScript

// Copyright 2026 OpenObserve Inc.
const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
const testLogger = require('../utils/test-logger.js');
const PageManager = require('../../pages/page-manager.js');
test.describe('Functions Form Validation', { tag: ['@functions-form-validation', '@P0', '@smoke'] }, () => {
test.describe.configure({ mode: 'serial' });
let pm;
test.beforeEach(async ({ page }, testInfo) => {
testLogger.testStart(testInfo.title, testInfo.file);
await navigateToBase(page);
pm = new PageManager(page);
});
// ─────────────────────────────────────────────────────────────────────────────
// AddFunction validation
// ─────────────────────────────────────────────────────────────────────────────
test.describe('AddFunction form validation', () => {
test.describe.configure({ mode: 'serial' });
test.beforeAll(async ({ request }) => {
// Delete the test function if it exists from a previous run so the happy-path test can create it fresh.
const org = process.env.ORGNAME || 'default';
const base = (process.env.INGESTION_URL || process.env.ZO_BASE_URL || 'http://localhost:5080').replace(/\/+$/, '');
const creds = Buffer.from(`${process.env.ZO_ROOT_USER_EMAIL}:${process.env.ZO_ROOT_USER_PASSWORD}`).toString('base64');
await request.delete(
`${base}/api/${org}/functions/test_fv_vrl_001`,
{ headers: { Authorization: `Basic ${creds}` } }
).catch(() => {}); // ignore 404 if function doesn't exist
});
test('should show error when function name is empty on save', async ({ page }) => {
testLogger.info('Test: empty function name → error on save');
const fv = pm.functionsFormValidation;
await fv.openAddFunctionForm(process.env.ORGNAME || 'default');
// Do not fill name — click Save immediately
await fv.clickSave();
// Expect name error message
await fv.expectFunctionNameErrorVisible();
});
test('should show error when function name is invalid on save', async ({ page }) => {
testLogger.info('Test: invalid function name → error on save');
const fv = pm.functionsFormValidation;
await fv.openAddFunctionForm(process.env.ORGNAME || 'default');
// Fill a name that starts with a digit — invalid per the regex
await fv.fillFunctionName('1invalid_name');
await fv.clickSave();
await fv.expectFunctionNameError('Invalid');
});
test('should show name error when name is cleared after revalidation starts', async ({ page }) => {
testLogger.info('Test: after first submit, clearing the name live-shows the error');
const fv = pm.functionsFormValidation;
await fv.openAddFunctionForm(process.env.ORGNAME || 'default');
// The form uses submit-then-change validation (revalidateLogic): errors stay
// hidden while typing/on blur until the first submit. Submit with an empty
// name to reveal the error and switch the field into live change-revalidation.
await fv.clickSave();
await fv.expectFunctionNameErrorVisible();
// Now revalidating on change: a valid name clears the error…
await fv.fillFunctionName('test_fv_fn_001');
await fv.functionNameError.waitFor({ state: 'hidden', timeout: 5000 });
// …and clearing it live-re-shows the required error (no submit needed now).
await fv.functionNameField.fill('');
await fv.expectFunctionNameErrorVisible();
});
test('should create function successfully with valid VRL code', async ({ page }) => {
testLogger.info('Test: valid VRL function → created successfully');
const fv = pm.functionsFormValidation;
const functionName = 'test_fv_vrl_001';
await fv.openAddFunctionForm(process.env.ORGNAME || 'default');
await fv.fillFunctionName(functionName);
// Type minimal valid VRL code into the editor
await fv.typeInVrlEditor('. = .');
await fv.clickSave();
// Expect success toast
await fv.expectSuccessToast();
});
});
// ─────────────────────────────────────────────────────────────────────────────
// FunctionsToolbar — transform type radio
// ─────────────────────────────────────────────────────────────────────────────
test.describe('FunctionsToolbar transform type selection', () => {
test.describe.configure({ mode: 'serial' });
test('should show VRL radio selected by default when Add Function form opens', async ({ page }) => {
testLogger.info('Test: VRL radio is selected by default');
const fv = pm.functionsFormValidation;
await fv.openAddFunctionForm(process.env.ORGNAME || 'default');
await fv.assertVrlRadioSelected();
});
test('should enable Save button when valid name is entered', async ({ page }) => {
testLogger.info('Test: Save button enabled after valid name entry');
const fv = pm.functionsFormValidation;
await fv.openAddFunctionForm(process.env.ORGNAME || 'default');
await fv.fillFunctionName('test_fv_tb_001');
const saveBtn = fv.saveButton;
const saveVisible = await saveBtn.isVisible().catch(() => false);
expect(saveVisible).toBe(true);
});
test('should show error when function name has invalid format', async ({ page }) => {
testLogger.info('Test: FunctionsToolbar — invalid name format shows error');
const fv = pm.functionsFormValidation;
await fv.openAddFunctionForm(process.env.ORGNAME || 'default');
await fv.fillFunctionName('invalid-name-with-dashes');
await fv.clickSave();
await fv.expectFunctionNameError('Invalid');
});
test('should navigate back when Cancel is clicked', async ({ page }) => {
testLogger.info('Test: FunctionsToolbar — Cancel navigates back');
const fv = pm.functionsFormValidation;
await fv.openAddFunctionForm(process.env.ORGNAME || 'default');
await fv.cancelButton.waitFor({ state: 'visible' });
await fv.cancelButton.click();
// After cancel, the add function form should not be visible
const nameFieldVisible = await fv.functionNameField.isVisible().catch(() => false);
expect(nameFieldVisible).toBe(false);
});
});
// ─────────────────────────────────────────────────────────────────────────────
// StreamRouting — form validation
// ─────────────────────────────────────────────────────────────────────────────
// StreamRouting.vue is currently not mounted in any navigation path — it was
// refactored into the pipeline NodeForm and is no longer reachable as a
// standalone page or panel. These tests are skipped until navigation is restored.
test.describe.skip('StreamRouting form validation', () => {
test.describe.configure({ mode: 'serial' });
test('should show name error when Save is clicked with empty destination name', async ({ page }) => {
testLogger.info('Test: StreamRouting — empty destination name error on save');
const fv = pm.functionsFormValidation;
await fv.openStreamRoutingForm(process.env.ORGNAME || 'default');
await fv.clickStreamRoutingSave();
await fv.assertStreamRoutingNameErrorVisible();
});
test('should close routing panel when cancel icon is clicked', async ({ page }) => {
testLogger.info('Test: StreamRouting — cancel icon closes the panel');
const fv = pm.functionsFormValidation;
await fv.openStreamRoutingForm(process.env.ORGNAME || 'default');
await fv.clickStreamRoutingClose();
const section = fv.streamRoutingSection;
const sectionVisible = await section.isVisible().catch(() => false);
expect(sectionVisible).toBe(false);
});
test('should show stream type error when Save is clicked without selecting stream type', async ({ page }) => {
testLogger.info('Test: StreamRouting — stream type required error on save');
const fv = pm.functionsFormValidation;
await fv.openStreamRoutingForm(process.env.ORGNAME || 'default');
await fv.fillStreamRoutingDestName('test_fv_sr_001');
await fv.clickStreamRoutingSave();
await fv.assertStreamRoutingStreamTypeErrorVisible();
});
});
// ─────────────────────────────────────────────────────────────────────────────
// AddEnrichmentTable validation
// ─────────────────────────────────────────────────────────────────────────────
test.describe('AddEnrichmentTable form validation', () => {
test.describe.configure({ mode: 'serial' });
test('should show error when enrichment table name is empty on save', async ({ page }) => {
testLogger.info('Test: empty enrichment table name → error on save');
const fv = pm.functionsFormValidation;
await fv.openAddEnrichmentTableForm(process.env.ORGNAME || 'default');
// Submit without filling name
await fv.clickEnrichmentSave();
await fv.expectEnrichmentNameError('required');
});
test('should show error when no file is selected on save', async ({ page }) => {
testLogger.info('Test: enrichment table — name filled but no file → error on save');
const fv = pm.functionsFormValidation;
await fv.openAddEnrichmentTableForm(process.env.ORGNAME || 'default');
// Fill name but leave file empty
await fv.fillEnrichmentName('test_fv_et_001');
await fv.clickEnrichmentSave();
await fv.expectEnrichmentFileError('required');
});
test('should show format error when wrong file type is provided via URL source', async ({ page }) => {
testLogger.info('Test: enrichment table URL source — invalid URL format → error visible');
const fv = pm.functionsFormValidation;
await fv.openAddEnrichmentTableForm(process.env.ORGNAME || 'default');
// Fill name
await fv.fillEnrichmentName('test_fv_et_002');
// Switch to URL source
await fv.selectUrlSource();
// Type an invalid URL (no http:// prefix) to trigger format validation
await fv.enrichmentUrlField.waitFor({ state: 'visible', timeout: 10000 });
await fv.enrichmentUrlField.fill('example.com/data.csv');
await fv.clickEnrichmentSave();
// Expect URL format error
await fv.enrichmentUrlError.waitFor({ state: 'visible', timeout: 5000 });
await expect(fv.enrichmentUrlError).toBeVisible();
await expect(fv.enrichmentUrlError).toContainText('http');
});
});
});