Files

95 lines
4.9 KiB
JavaScript

/**
* Logs Patterns Multi-Stream Guard (OSS).
*
* The Patterns extraction runs on a dedicated single-stream API. This spec verifies the
* client-side guard in Index.vue's extractPatternsForCurrentQuery(): when more than one
* stream is selected in non-SQL mode it cancels the in-flight extraction, clears stale
* patterns and surfaces an error toast BEFORE any API call. The Patterns toggle button is
* enterprise-gated, so in OSS we enter patterns mode via the URL query param
* (logs_visualize_toggle=patterns). No ingestion/FTS/patterns-config setup is required —
* the guard fires on selectedStream.length alone.
*/
const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
const testLogger = require('../utils/test-logger.js');
const PageManager = require('../../pages/page-manager.js');
const { waitForStreamListed } = require('../utils/data-ingestion.js');
const { getOrgIdentifier } = require('../utils/cloud-auth.js');
// POST /api/{org}/streams/{stream}/patterns/extract — the single-stream extraction API
// the guard exists to keep multi-stream selections away from (web/src/services/patterns.ts).
const PATTERNS_EXTRACT_URL = /\/streams\/[^/]+\/patterns\/extract/;
// Unique per-test stream names so parallel tests never collide. cleanup.spec.js reclaims
// these via the /^e2e_multi_patterns/ prefix.
function makeStreamName(letter) {
return `e2e_multi_patterns_${letter}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 5)}`;
}
test.describe("Logs Patterns Multi-Stream Guard testcases", () => {
test.describe.configure({ mode: 'parallel' });
let pm;
test.beforeEach(async ({ page }, testInfo) => {
testLogger.testStart(testInfo.title, testInfo.file);
await navigateToBase(page);
pm = new PageManager(page);
testLogger.info('Test setup completed');
});
test.afterEach(async ({}, testInfo) => {
testLogger.testEnd(testInfo.title, testInfo.status);
});
test("should show an error toast and refuse patterns when multiple streams are selected @P0 @smoke", {
tag: ['@logs-patterns-multi-stream-guard', '@all', '@smoke', '@logs']
}, async ({ page }) => {
testLogger.info('Test: multi-stream patterns guard fires an error toast');
// The guard fires before any network call, so the streams only need to exist.
const streamA = makeStreamName('a');
const streamB = makeStreamName('b');
await pm.streamsPage.createStream(streamA, 'logs');
await pm.streamsPage.createStream(streamB, 'logs');
await waitForStreamListed(page, streamA, 'logs');
await waitForStreamListed(page, streamB, 'logs');
// The guard's whole point is that it returns before the request is built, so record
// every extraction call and assert none was made — a toast alone would still pass if
// the guard ran after the API had already been hit.
const extractCalls = [];
page.on('request', (req) => {
if (PATTERNS_EXTRACT_URL.test(req.url())) extractCalls.push(req.url());
});
// Enter patterns mode via URL (the toggle is enterprise-gated in OSS). restoreUrlQueryParams
// splits `stream` on ',' into [a, b], so the guard condition is true on mount and the toast fires.
const logsUrl = `${process.env.ZO_BASE_URL}/web/logs?org_identifier=${getOrgIdentifier()}`;
await pm.logsPage.startToastRecorder();
await page.goto(`${logsUrl}&stream_type=logs&stream=${streamA},${streamB}&logs_visualize_toggle=patterns`, { waitUntil: 'domcontentloaded' });
await pm.logsPage.expectMultiStreamPatternsToast();
expect(extractCalls, `patterns/extract must not be called for a multi-stream selection, got: ${extractCalls.join(', ')}`).toHaveLength(0);
testLogger.info('PASSED: multi-stream guard error toast shown, no extraction request issued');
});
test("should not show the multi-stream patterns toast for a single stream @P1", {
tag: ['@logs-patterns-multi-stream-guard', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Test: single-stream patterns skips the multi-stream guard');
const streamA = makeStreamName('a');
await pm.streamsPage.createStream(streamA, 'logs');
await waitForStreamListed(page, streamA, 'logs');
const logsUrl = `${process.env.ZO_BASE_URL}/web/logs?org_identifier=${getOrgIdentifier()}`;
await pm.logsPage.startToastRecorder();
await page.goto(`${logsUrl}&stream_type=logs&stream=${streamA}&logs_visualize_toggle=patterns`, { waitUntil: 'domcontentloaded' });
// selectedStream.length is 1, so the guard is skipped and extraction proceeds. The exact
// multi-stream error message must never appear (a generic errorExtractingPatterns toast may).
await pm.logsPage.expectNoMultiStreamPatternsToast();
testLogger.info('PASSED: no multi-stream toast for a single stream');
});
});