Files
pleno-vue/tests/unit/session-bootstrap.spec.js
T

186 lines
6.2 KiB
JavaScript

// @vitest-environment jsdom
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
authenticatedRequest: vi.fn(),
parseError: vi.fn(),
pingApiServer: vi.fn(),
swalFire: vi.fn(() => Promise.resolve({ isConfirmed: true })),
loginRedirect: vi.fn(),
}));
vi.mock("@/components/session/authenticatedRequest.vue", () => ({
authenticatedRequest: mocks.authenticatedRequest,
}));
vi.mock("@/components/request/HandleGlobalError.vue", () => ({
parseError: mocks.parseError,
}));
vi.mock("@/services/apiHealth.js", () => ({
pingApiServer: mocks.pingApiServer,
}));
vi.mock("sweetalert2", () => ({
default: {
fire: mocks.swalFire,
},
}));
import { getSessionData, SessionUser } from "@/components/session/token/SessionUser.vue";
import { EDGE_GATEWAY_WORKSPACE_CACHE_KEY } from "@/services/edgeGateways.js";
import {
buildPeriodCacheKey,
getCachedPeriodPage,
setCachedPeriodPage,
} from "@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/imports/InvoicingBillingPeriodImportPaging.js";
import {
getCachedXlvaskUsageAmount,
setCachedXlvaskUsageAmount,
} from "@/components/displays/department/pos/sync/xlvaskUsageAmountCache.js";
const seedStoredSession = (token = "stored-token") => {
localStorage.setItem("token", token);
SessionUser.token.value = token;
SessionUser.authenticated.value = true;
SessionUser.isSubuser.value = false;
SessionUser.initiated.value = false;
};
const invalidSessionError = () => ({
response: {
status: 401,
data: {
message: "Unauthenticated",
},
},
});
describe("session bootstrap failure handling", () => {
beforeEach(() => {
SessionUser.auth.forceClearSession();
SessionUser.functions.redirectTo.pages.login = mocks.loginRedirect;
mocks.authenticatedRequest.mockReset();
mocks.parseError.mockReset();
mocks.pingApiServer.mockReset();
mocks.swalFire.mockClear();
mocks.loginRedirect.mockReset();
window.history.replaceState({}, "", "/user");
});
it("clears privileged session caches when forcing the session closed", () => {
seedStoredSession("cached-session-token");
const periodCacheKey = buildPeriodCacheKey({
dateFrom: "2026-05-01",
dateTo: "2026-05-31",
periodView: "all",
page: 1,
limit: 100,
search: "",
flagTab: "all",
includeRequiresAction: true,
includeBooked: true,
});
setCachedPeriodPage(
periodCacheKey,
{ types: { all: [{ customer_name: "Sensitive Customer" }] } },
{ page: 1, per_page: 100, total: 1 }
);
setCachedXlvaskUsageAmount(8101, {
order_items: [{ license_plate: "PII-123" }],
potential_duplicates: [],
});
window.sessionStorage.setItem(EDGE_GATEWAY_WORKSPACE_CACHE_KEY, JSON.stringify({ details: { 1: "gateway" } }));
window.localStorage.setItem(EDGE_GATEWAY_WORKSPACE_CACHE_KEY, JSON.stringify({ details: { 1: "legacy" } }));
expect(getCachedPeriodPage(periodCacheKey)?.data?.types?.all?.[0]?.customer_name).toBe("Sensitive Customer");
expect(getCachedXlvaskUsageAmount(8101)?.order_items?.[0]?.license_plate).toBe("PII-123");
expect(window.sessionStorage.getItem(EDGE_GATEWAY_WORKSPACE_CACHE_KEY)).toContain("gateway");
expect(window.localStorage.getItem(EDGE_GATEWAY_WORKSPACE_CACHE_KEY)).toContain("legacy");
SessionUser.auth.forceClearSession();
expect(getCachedPeriodPage(periodCacheKey)).toBeNull();
expect(getCachedXlvaskUsageAmount(8101)).toBeNull();
expect(Object.keys(window.sessionStorage).filter((key) => key.startsWith("invoicing-period-page:")).length).toBe(0);
expect(Object.keys(window.sessionStorage).filter((key) => key.startsWith("xlvask-usage-amount:")).length).toBe(0);
expect(window.sessionStorage.getItem(EDGE_GATEWAY_WORKSPACE_CACHE_KEY)).toBeNull();
expect(window.localStorage.getItem(EDGE_GATEWAY_WORKSPACE_CACHE_KEY)).toBeNull();
});
it("clears an invalid stored session after ping confirms the API is reachable", async () => {
seedStoredSession("invalid-token");
mocks.authenticatedRequest.mockRejectedValueOnce(invalidSessionError());
mocks.pingApiServer.mockResolvedValueOnce({
ok: true,
status: 200,
url: "/api/ping",
error: null,
});
const result = await getSessionData();
expect(mocks.pingApiServer).toHaveBeenCalledTimes(1);
expect(localStorage.getItem("token")).toBeNull();
expect(SessionUser.authenticated.value).toBe(false);
expect(mocks.loginRedirect).toHaveBeenCalledTimes(1);
expect(result).toMatchObject({
cleared: true,
apiReachable: true,
redirectedTo: "/login",
});
});
it("preserves an invalid stored session and routes to connectivity when ping fails", async () => {
seedStoredSession("invalid-token");
mocks.authenticatedRequest.mockRejectedValueOnce(invalidSessionError());
mocks.pingApiServer.mockResolvedValueOnce({
ok: false,
status: null,
url: "/api/ping",
error: new Error("Network unavailable"),
});
const result = await getSessionData();
expect(mocks.pingApiServer).toHaveBeenCalledTimes(1);
expect(localStorage.getItem("token")).toBe("invalid-token");
expect(SessionUser.authenticated.value).toBe(true);
expect(mocks.loginRedirect).not.toHaveBeenCalled();
expect(result).toMatchObject({
cleared: false,
apiReachable: false,
redirectedTo: "/connectivity-issue",
});
});
it("does not clear the stored session for generic server bootstrap errors", async () => {
seedStoredSession("still-valid-token");
const serverError = {
response: {
status: 500,
data: {
message: "Internal server error",
},
},
};
mocks.authenticatedRequest.mockRejectedValueOnce(serverError);
const result = await getSessionData();
expect(mocks.pingApiServer).not.toHaveBeenCalled();
expect(localStorage.getItem("token")).toBe("still-valid-token");
expect(SessionUser.authenticated.value).toBe(true);
expect(mocks.loginRedirect).not.toHaveBeenCalled();
expect(mocks.parseError).toHaveBeenCalledWith(serverError, "auth");
expect(mocks.swalFire).toHaveBeenCalledTimes(1);
expect(result).toMatchObject({
cleared: false,
apiReachable: null,
redirectedTo: null,
});
});
});