Files
pleno-vue/tests/unit/release-timeline.spec.js
T
Jeppe Bundgaard 3715da8fd3 Add release channel selector components, subject autocomplete, and related tests
- Implement components for `ReleaseChannelSelector`, including panel and sidebar variants.
- Add `ReleaseAssignmentSubjectAutocomplete` for user, subuser, and customer search functionality.
- Update i18n phrases for channel selector and subject assignment.
- Enhance unit and E2E tests for release channel availability, switching, and runtime configuration.
- Modify modules for channel-related state handling and runtime refresh mechanics.
2026-05-20 10:31:49 +02:00

161 lines
5.2 KiB
JavaScript

// @vitest-environment jsdom
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
__resetReleaseTimelineForTests,
__setReleaseTimelineTransportForTests,
buildReleaseTimelineContext,
configureReleaseRuntime,
flushReleaseTimelineEvents,
getRecentFrontendFailureEvents,
recordReleaseTimelineEvent,
redactReleasePayload,
releaseRuntimeState,
} from "@/services/releaseTimeline.js";
describe("release timeline runtime", () => {
beforeEach(() => {
vi.useFakeTimers();
localStorage.clear();
__resetReleaseTimelineForTests();
});
afterEach(() => {
__resetReleaseTimelineForTests();
localStorage.clear();
vi.useRealTimers();
vi.restoreAllMocks();
});
it("configures release runtime from the JSON session contract", () => {
configureReleaseRuntime({
trace_id: "trace-123",
channel: { slug: "canary", name: "Canary" },
available_channels: [{ channel: { slug: "stable", name: "Stable", default_channel: true } }],
versions: { frontend: { version_label: "abc123" }, api: null, bundle_id: 31 },
capture_policy: { enabled: true, capture_level: "full_redacted", retention_days: 7 },
});
expect(releaseRuntimeState.channel.slug).toBe("canary");
expect(releaseRuntimeState.availableChannels).toHaveLength(1);
expect(releaseRuntimeState.traceId).toBe("trace-123");
expect(releaseRuntimeState.versions.frontend.version_label).toBe("abc123");
expect(releaseRuntimeState.versions.bundle_id).toBe(31);
});
it("redacts sensitive payload keys recursively", () => {
expect(
redactReleasePayload({
Authorization: "Bearer token",
nested: {
api_key: "secret",
safe: "visible",
},
})
).toEqual({
Authorization: "[redacted]",
nested: {
api_key: "[redacted]",
safe: "visible",
},
});
});
it("always sends failure metadata and keeps non-failure events gated by capture policy", async () => {
const sentBodies = [];
__setReleaseTimelineTransportForTests(async (body) => {
sentBodies.push(body);
return { accepted: body.events.length };
});
expect(recordReleaseTimelineEvent("route_change", { to: "/superuser" })).toBe(false);
expect(
recordReleaseTimelineEvent(
"request_failed",
{
request: { headers: { Authorization: "Bearer token" } },
response: { status: 500 },
},
{ severity: "error", moduleKey: "requestqueue" }
)
).toBe(true);
await flushReleaseTimelineEvents();
expect(sentBodies).toHaveLength(1);
expect(sentBodies[0].events[0]).toMatchObject({
type: "request_failed",
severity: "error",
module_key: "requestqueue",
});
expect(sentBodies[0].events[0].payload.request.headers.Authorization).toBe("[redacted]");
});
it("keeps a local redacted frontend failure buffer for error reports", () => {
recordReleaseTimelineEvent(
"vue_component_error",
{
message: "Render failed",
token: "secret",
component: "BrokenWidget",
},
{ severity: "error", moduleKey: "frontend" }
);
recordReleaseTimelineEvent(
"request_failed",
{ message: "Handled by request queue" },
{ severity: "error", moduleKey: "requestqueue" }
);
const events = getRecentFrontendFailureEvents();
expect(events).toHaveLength(1);
expect(events[0]).toMatchObject({
type: "vue_component_error",
module_key: "frontend",
});
expect(events[0].payload.token).toBe("[redacted]");
});
it("sends non-failure timeline events when replay capture is enabled", async () => {
const sentBodies = [];
__setReleaseTimelineTransportForTests(async (body) => {
sentBodies.push(body);
return { accepted: body.events.length };
});
configureReleaseRuntime({
trace_id: "trace-enabled",
channel: { slug: "beta" },
capture_policy: { enabled: true, capture_level: "full_redacted" },
});
expect(recordReleaseTimelineEvent("route_change", { to: "/dashboard" })).toBe(true);
await flushReleaseTimelineEvents();
expect(sentBodies).toHaveLength(1);
expect(sentBodies[0].context.channel_slug).toBe("beta");
expect(sentBodies[0].context.device.type).toBe("desktop");
expect(sentBodies[0].context.frontend.version_label).toBeTruthy();
expect(sentBodies[0].events[0].type).toBe("route_change");
});
it("builds device and release context for replay sessions", () => {
configureReleaseRuntime({
trace_id: "trace-context",
channel: { slug: "canary" },
versions: {
frontend: { version_label: "frontend-canary", commit_sha: "c0ffee" },
api: { version_label: "api-canary", commit_sha: "def456" },
},
capture_policy: { enabled: true, capture_level: "full_redacted" },
});
const context = buildReleaseTimelineContext();
expect(context.trace_id).toBe("trace-context");
expect(context.channel_slug).toBe("canary");
expect(context.device.type).toBe("desktop");
expect(context.frontend.version_label).toBe("frontend-canary");
expect(context.api.version_label).toBe("api-canary");
expect(context.viewport.width).toBeGreaterThan(0);
});
});