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("Query Time Hash - Combined Test", { tag: '@enterprise' }, () => { test.describe.configure({ mode: 'serial' }); let pm; // Generate unique test run ID for isolation const testRunId = Date.now().toString(36); // All patterns used in this spec file - with full pattern definitions for uniqueness const patternsToTest = [ { name: `email_format_query_hash_${testRunId}`, description: 'Email address validation pattern (for query time hash tests)', pattern: '^[\\w\\.-]+@[\\w\\.-]+\\.\\w{2,}$', field: 'user_email', value: 'john.doe@example.com' }, { name: `us_phone_query_hash_${testRunId}`, description: 'US phone number (10 digits) (for query time hash tests)', pattern: '^\\d{10}$', field: 'phone', value: '5551234567' }, { name: `credit_card_query_hash_${testRunId}`, description: 'Credit card number pattern (for query time hash tests)', pattern: '^\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}$', field: 'cc_number', value: '1234 5678 9012 3456' }, { name: `ssn_query_hash_${testRunId}`, description: 'Social Security Number pattern (for query time hash tests)', pattern: '^\\d{3}-\\d{2}-\\d{4}$', field: 'ssn', value: '123-45-6789' } ]; // Use unique stream name to avoid "stream being deleted" conflicts const testStreamName = `sdr_query_hash_${testRunId}`; test.beforeEach(async ({ page }, testInfo) => { testLogger.testStart(testInfo.title, testInfo.file); await navigateToBase(page); pm = new PageManager(page); await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {}); testLogger.info('Combined query time hash test setup completed'); }); test("setup: cleanup patterns", { tag: ['@sdr', '@cleanup', '@queryHash'] }, async ({ page }) => { testLogger.info('=== SPEC-LEVEL CLEANUP: Cleaning up patterns for combined query hash test ==='); for (const pattern of patternsToTest) { testLogger.info(`Checking and cleaning up pattern: ${pattern.name}`); await pm.sdrPatternsPage.navigateToRegexPatterns(); const exists = await pm.sdrPatternsPage.checkPatternExists(pattern.name); if (exists) { testLogger.info(`Pattern ${pattern.name} exists. Deleting (and unlinking if needed).`); const deleteResult = await pm.sdrPatternsPage.deletePatternByName(pattern.name); if (!deleteResult.success && deleteResult.reason === 'in_use') { testLogger.info('Pattern in use, unlinking from streams first'); for (const association of deleteResult.associations) { await pm.streamAssociationPage.unlinkPatternFromField( association.streamName, association.fieldName, pattern.name ); } // Delete again after unlinking await pm.sdrPatternsPage.navigateToRegexPatterns(); await pm.sdrPatternsPage.deletePatternByName(pattern.name); } testLogger.info(`✓ Pattern ${pattern.name} deleted successfully`); } else { testLogger.info(`✓ Pattern ${pattern.name} does not exist, no cleanup needed`); } } testLogger.info('=== SPEC-LEVEL CLEANUP COMPLETE ==='); }); test("should hash multiple fields at query time when patterns are applied", { tag: ['@sdr', '@queryHash', '@all', '@combined'] }, async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'dependency', description: 'setup: cleanup patterns' }); testLogger.info('=== Testing combined HASH for 4 patterns at query time ==='); // STEP 1: Ingest data once (data will remain unchanged in storage) testLogger.info('STEP 1: Ingest 4 lines of data (before any patterns are applied)'); const dataToIngest = patternsToTest.map(p => ({ fieldName: p.field, fieldValue: p.value })); // Query-time SDR transforms data at search time, so STEP 1 (visible) and STEP 4 // (hashed) inspect the SAME ingested batch — reuse one marker for both. const ingestMarker = await pm.logsPage.ingestMultipleFields(testStreamName, dataToIngest); // Verify all fields are visible without hashing const fieldsBeforeHashing = patternsToTest.map(p => ({ fieldName: p.field, shouldBeHashed: false })); await pm.sdrVerificationPage.verifyMultipleFields(pm.logsPage, testStreamName, fieldsBeforeHashing, ingestMarker); testLogger.info('✓ STEP 1 PASSED: All fields visible without SDR'); // STEP 2: Create all 4 SDR patterns testLogger.info('STEP 2: Create all 4 SDR patterns'); for (const patternConfig of patternsToTest) { await pm.sdrPatternsPage.navigateToRegexPatterns(); await pm.sdrPatternsPage.createPattern(patternConfig.name, patternConfig.description, patternConfig.pattern); await pm.sdrPatternsPage.verifyPatternCreatedSuccess(); // Verify pattern was created await pm.sdrPatternsPage.navigateToRegexPatterns(); const exists = await pm.sdrPatternsPage.waitForPatternCreated(patternConfig.name); // backend-consistent create gate (API source of truth) expect(exists).toBeTruthy(); testLogger.info(`✓ Created and verified pattern: ${patternConfig.name}`); } testLogger.info('✓ STEP 2 PASSED: All 4 patterns created and verified'); // STEP 3: Link all 4 patterns to their respective fields with QUERY TIME hashing testLogger.info('STEP 3: Link all 4 patterns to their respective fields with HASH action'); for (const patternConfig of patternsToTest) { await pm.streamAssociationPage.associatePatternWithStream( testStreamName, patternConfig.name, 'hash', 'query', // QUERY TIME - key difference from ingestion time patternConfig.field ); testLogger.info(`✓ Linked pattern ${patternConfig.name} to field ${patternConfig.field} with HASH action`); } testLogger.info('✓ STEP 3 PASSED: All 4 patterns linked to fields with HASH action'); // STEP 4: Verify all fields are HASHED at query time (NO RE-INGESTION) testLogger.info('STEP 4: Verify fields are HASHED at query time (data unchanged in storage)'); const fieldsAfterHashing = patternsToTest.map(p => ({ fieldName: p.field, shouldBeHashed: true })); await pm.sdrVerificationPage.verifyMultipleFields(pm.logsPage, testStreamName, fieldsAfterHashing, ingestMarker); testLogger.info('✓ STEP 4 PASSED: All fields are HASHED at query time'); testLogger.info('=== ✓ COMBINED QUERY TIME HASH TEST COMPLETED SUCCESSFULLY ==='); }); });