mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 16:10:43 +00:00
Automated coverage for **email alert destinations and distribution lists**, and unblocks the email tests that have been skipped since they were written. **32 cases** — 27 active, 5 skipped regressions tracking open defects (openobserve/o2-enterprise#2471). **The unblocking.** The two skipped tests said *"the test environment does not have email infrastructure set up"*. That over-stated it: creating, editing, validating and deleting an email destination needs SMTP switched **on**, and never reads a mailbox. Only delivery assertions do. The suite is tiered on that: | Tier | Needs | Cases | |---|---|---| | A | SMTP enabled | 24 | | B | a readable sink | 2 | | C | an alias that fans out | 1 | **Tests** - `Alerts/dl-email-destinations.spec.js` (32) — the DL half (alias accepted, addressed, fanned out) and the recipient-list half, where our code actually runs: splitting/trimming/lowercasing, the org-membership gate, edit round-trips, add/remove/delete, both form paradigms (prebuilt free-text vs the custom path's org-user picker), template auto-attach, duplicate names, cancel, type-switching, keyboard, dark mode at 420px. - `utils/mail-sink.js` — pluggable sink: Mailpit, a Mailinator inbox, or a clean skip with a stated reason. - `utils/o2-api.js` — storage is asserted from the API, never the form field: an input can echo what you typed while the stored value differs. - `scripts/dl-relay.js` — SMTP relay that expands one alias into per-member deliveries, so fan-out runs with no external provider. Same pattern as the in-test HTTP receiver in `alerts-content-templates.spec.js`. - `alertDestinationsPage.js` — +17 methods; the spec carries zero raw locators. **Replaces, without losing coverage.** The two skipped tests in `alerts-destinations-prebuilt.spec.js` are removed for a pointer mapping their intent to its new home: create (`D-01`), edit-add (`D-07`), edit-remove (`ML-08`), delete (`UI-12`), format validation (`UI-02`). Three of those did not exist before. That file now has no skips. **CI.** `ZO_SMTP_ENABLED: true` was already set in `playwright.yml` but had no host or port, so it fell back to `localhost:25` where nothing listens — destinations could be **saved** while every **send** failed silently. The workflow now starts Mailpit and a DL relay as static binaries before the app (no Docker-in-Docker on these runners) and points `ZO_SMTP_*` at them. All three tiers run with no secrets and no egress. Verified in the CI topology: **27 passed, 5 skipped**, `D-02` confirmed reaching all three member mailboxes. **Behaviour.** Self-cleaning via the `auto_dest_dl_*` prefix `cleanup.spec.js` already sweeps, plus an API `afterAll`. `mode: 'parallel'` — each test owns its fixtures. **Findings** are filed in openobserve/o2-enterprise#2471, not fixed here. Two P1s: the *"Show matching rows"* toggle is ignored so log rows are always embedded, and a user removed from the org keeps receiving alerts while the destination can no longer be saved. The regression tests for each are in this PR, skipped until the fix lands. **Paired ENT PR: openobserve/o2-enterprise#2472.** ENT's workflow sets `ZO_SMTP_ENABLED: false`, so without it the suite skips all 27 cases there. The shard matrix needs no ENT change — ENT builds it from this repo's `ci_matrix.json` plus its overlay, so the new spec is picked up automatically. Merge the two together: this one alone leaves enterprise with no email coverage.
67 lines
3.2 KiB
JavaScript
67 lines
3.2 KiB
JavaScript
/**
|
|
* Thin authenticated REST client for the destination/template/user endpoints.
|
|
* Used by the DL suite to verify what the UI actually persisted — the form can
|
|
* echo your input while the stored value differs (trimming, lowercasing,
|
|
* empty entries), so every storage assertion reads the API, not the field.
|
|
*/
|
|
const BASE = process.env.ZO_BASE_URL || 'http://localhost:5080';
|
|
const ORG = process.env.ORGNAME || 'default';
|
|
const USER = process.env.ZO_ROOT_USER_EMAIL || 'root@example.com';
|
|
// The repo's documented default for local runs — the same value playwright.yml
|
|
// commits publicly. Not a secret; CI and any real environment override it.
|
|
const PASS = process.env.ZO_ROOT_USER_PASSWORD || 'Complexpass#123';
|
|
const AUTH = 'Basic ' + Buffer.from(`${USER}:${PASS}`).toString('base64');
|
|
|
|
async function req(method, path, body) {
|
|
const res = await fetch(`${BASE}/api/${ORG}${path}`, {
|
|
method,
|
|
headers: { Authorization: AUTH, 'Content-Type': 'application/json' },
|
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
});
|
|
const text = await res.text();
|
|
let json = null;
|
|
try { json = JSON.parse(text); } catch (_) { /* non-JSON body */ }
|
|
return { status: res.status, body: json, text };
|
|
}
|
|
|
|
const getDestination = (name) => req('GET', `/alerts/destinations/${name}`);
|
|
const listDestinations = () => req('GET', '/alerts/destinations');
|
|
const createDestination = (payload) => req('POST', '/alerts/destinations', payload);
|
|
const deleteDestination = (name) => req('DELETE', `/alerts/destinations/${name}`);
|
|
const testDestination = (payload) => req('POST', '/alerts/destinations/test', payload);
|
|
const listTemplates = () => req('GET', '/alerts/templates');
|
|
|
|
/**
|
|
* A destination recipient must be a user of the org — see the membership gate in
|
|
* alerts/destinations.rs. A distribution-list alias is not a person, so the DL
|
|
* fan-out case has to enrol the alias before it can be addressed at all.
|
|
*
|
|
* role MUST be 'admin': UserRoleRequest::from() (common/src/meta/user.rs) matches
|
|
* the requested role string against get_roles() and, on no match, treats it as a
|
|
* CUSTOM role — which core/src/users.rs::post_user rejects outright with 400 when
|
|
* OpenFGA/RBAC is off (the OSS CI topology this suite actually runs in restricts
|
|
* the standard-role set to Admin only). A lower role 400s the create outright,
|
|
* which is worse for the "stray privileged account" concern this once tried to
|
|
* avoid — cleanup being flag-gated (see dlUserCreated below) is the real mitigation.
|
|
*/
|
|
const createOrgUser = (email) => req('POST', '/users', {
|
|
email, first_name: 'dl', last_name: 'alias',
|
|
password: PASS,
|
|
role: 'admin',
|
|
});
|
|
const deleteOrgUser = (email) => req('DELETE', `/users/${encodeURIComponent(email)}`);
|
|
const getTemplate = (name) => req('GET', `/alerts/templates/${name}`);
|
|
|
|
/** Stored recipients for a destination, or null when it does not exist. */
|
|
async function storedRecipients(name) {
|
|
const r = await getDestination(name);
|
|
if (r.status !== 200 || !r.body) return null;
|
|
return r.body.emails;
|
|
}
|
|
|
|
module.exports = {
|
|
BASE, ORG, req, getDestination, listDestinations, createDestination,
|
|
deleteDestination, testDestination, listTemplates, getTemplate, storedRecipients,
|
|
createOrgUser, deleteOrgUser,
|
|
};
|