Files
pleno-vue/tests/unit/release-timeline.spec.js
T

134 lines
4.0 KiB
JavaScript

// @vitest-environment jsdom
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
__resetReleaseTimelineForTests,
__setReleaseTimelineTransportForTests,
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" },
versions: { frontend: { version_label: "abc123" }, api: null },
capture_policy: { enabled: true, capture_level: "full_redacted", retention_days: 7 },
});
expect(releaseRuntimeState.channel.slug).toBe("canary");
expect(releaseRuntimeState.traceId).toBe("trace-123");
expect(releaseRuntimeState.versions.frontend.version_label).toBe("abc123");
});
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].events[0].type).toBe("route_change");
});
});