134 lines
4.0 KiB
JavaScript
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");
|
|
});
|
|
});
|