Files

100 lines
4.4 KiB
JavaScript

// Copyright 2026 OpenObserve Inc.
/** REST plumbing for trace regression specs; every helper takes an APIRequestContext (page.request or the request fixture). */
const { expect } = require('@playwright/test');
const { getAuthHeaders, getOrgIdentifier } = require('./cloud-auth.js');
const MINUTE_US = 60 * 1000 * 1000;
const HOUR_US = 60 * MINUTE_US;
const ingestionBaseUrl = () => (process.env.INGESTION_URL || process.env.ZO_BASE_URL || '').replace(/\/+$/, '');
const apiBaseUrl = () => (process.env.ZO_BASE_URL || '').replace(/\/+$/, '');
const orgId = () => getOrgIdentifier() || process.env.ORGNAME || 'default';
const stringAttr = (key, value) => ({ key, value: { stringValue: value } });
const intAttr = (key, value) => ({ key, value: { intValue: String(value) } });
/** Ingest an OTLP/JSON `{ resourceSpans }` payload into `stream` and assert it returned 200. */
async function ingestOtlpTraces(request, stream, payload, { message = 'Precondition: traces must ingest', org = orgId() } = {}) {
const response = await request.post(`${ingestionBaseUrl()}/api/${org}/v1/traces`, {
headers: { ...getAuthHeaders(), 'stream-name': stream },
data: payload,
});
expect(response.status(), message).toBe(200);
}
/** Ingest RUM rows into `_rumdata` and assert it returned 200. */
async function ingestRumData(request, rows, { message = 'Precondition: RUM rows must ingest', org = orgId() } = {}) {
const response = await request.post(`${ingestionBaseUrl()}/api/${org}/_rumdata/_json`, {
headers: getAuthHeaders(),
data: rows,
});
expect(response.status(), message).toBe(200);
}
/** Run one SQL search over [startUs, endUs] (default: the last hour, one minute ahead); `hits` is [] unless the response is OK. */
async function searchStream(request, sql, { type = 'traces', startUs, endUs, size = 100, org = orgId() } = {}) {
const windowEndUs = endUs ?? Date.now() * 1000 + MINUTE_US;
const windowStartUs = startUs ?? windowEndUs - HOUR_US;
const response = await request.post(`${ingestionBaseUrl()}/api/${org}/_search?type=${type}`, {
headers: getAuthHeaders(),
data: { query: { sql, start_time: windowStartUs, end_time: windowEndUs, from: 0, size } },
});
const text = await response.text();
return { status: response.status(), text, hits: response.ok() ? JSON.parse(text).hits || [] : [] };
}
/** Poll searchStream until `pick(result)` (default: the hit count) equals `expected`, and return the last result. */
async function waitForSearch(request, sql, { expected, message, pick = (result) => result.hits.length, timeout = 60000, ...searchOpts }) {
let last = null;
await expect.poll(async () => {
last = await searchStream(request, sql, searchOpts);
return pick(last);
}, { timeout, intervals: [2000], message }).toBe(expected);
return last;
}
/** Field names in a stream's schema, or [] when the schema cannot be read. */
async function streamFieldNames(request, stream, { type = 'traces', org = orgId() } = {}) {
const response = await request.get(`${ingestionBaseUrl()}/api/${org}/streams/${stream}/schema?type=${type}`, { headers: getAuthHeaders() });
if (!response.ok()) return [];
return ((await response.json()).schema || []).map((field) => field.name);
}
/** Best-effort stream delete for afterEach, so cleanup never masks the test's own result. */
async function deleteStream(request, stream, { type = 'traces', org = orgId() } = {}) {
await request
.delete(`${ingestionBaseUrl()}/api/${org}/streams/${stream}?type=${type}`, { headers: getAuthHeaders() })
.catch(() => {});
}
/** Create a saved view, assert it returned 200, and resolve to its view_id. */
async function createSavedView(request, view, { message = 'Precondition: saved view must be created', org = orgId() } = {}) {
const response = await request.post(`${apiBaseUrl()}/api/${org}/savedviews`, {
headers: getAuthHeaders(),
data: view,
});
expect(response.status(), message).toBe(200);
return (await response.json()).view_id;
}
/** Best-effort saved-view delete for afterEach; a missing id is a no-op. */
async function deleteSavedView(request, viewId, { org = orgId() } = {}) {
if (!viewId) return;
await request
.delete(`${apiBaseUrl()}/api/${org}/savedviews/${viewId}`, { headers: getAuthHeaders() })
.catch(() => {});
}
module.exports = {
orgId,
stringAttr,
intAttr,
ingestOtlpTraces,
ingestRumData,
searchStream,
waitForSearch,
streamFieldNames,
deleteStream,
createSavedView,
deleteSavedView,
};