fix: restore permissions export compatibility and add regressions
This commit is contained in:
@@ -13,6 +13,7 @@ COPY . .
|
||||
ARG PUBLIC_API_URL=https://opt-api.osdci.net
|
||||
ENV PUBLIC_API_URL=$PUBLIC_API_URL
|
||||
|
||||
RUN bun run prepare
|
||||
RUN bun run build:server
|
||||
|
||||
# Bundle the server into a single file with all dependencies
|
||||
|
||||
+5
-4
@@ -1,6 +1,7 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test('home page has expected h1', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('h1')).toBeVisible();
|
||||
test("app root renders visible content", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(page.locator("body")).toBeVisible();
|
||||
await expect(page.locator("body")).not.toHaveText(/^\s*$/);
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { defineConfig } from '@playwright/test';
|
||||
import { defineConfig } from "@playwright/test";
|
||||
|
||||
export default defineConfig({
|
||||
webServer: {
|
||||
command: 'npm run build && npm run preview',
|
||||
port: 4173
|
||||
command: "PORT=4173 ORIGIN=http://localhost:4173 node build/index.js",
|
||||
port: 4173,
|
||||
},
|
||||
testDir: 'e2e'
|
||||
testDir: "e2e",
|
||||
});
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { mockApi } = vi.hoisted(() => ({
|
||||
mockApi: {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
patch: vi.fn(),
|
||||
put: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../axios", () => ({
|
||||
default: mockApi,
|
||||
api: mockApi,
|
||||
}));
|
||||
|
||||
import { company } from "./companies";
|
||||
import { credential } from "./credentials";
|
||||
import { credentialType } from "./credentialTypes";
|
||||
import { permission } from "./permissions";
|
||||
import { procurement } from "./procurement";
|
||||
import { role } from "./roles";
|
||||
import { sales } from "./sales";
|
||||
import { unifi } from "./unifi";
|
||||
import { users } from "./users";
|
||||
|
||||
describe("optima api modules", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("company.fetchMany sends search params", async () => {
|
||||
mockApi.get.mockResolvedValueOnce({ data: { data: [] } });
|
||||
|
||||
await company.fetchMany("token", 2, "acme", 50);
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith("/v1/company/companies", {
|
||||
params: { page: 2, rpp: 50, search: "acme" },
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
});
|
||||
|
||||
it("company.count returns count", async () => {
|
||||
mockApi.get.mockResolvedValueOnce({ data: { data: { count: 17 } } });
|
||||
|
||||
const count = await company.count("token");
|
||||
|
||||
expect(count).toBe(17);
|
||||
});
|
||||
|
||||
it("credential.fetch and delete call expected routes", async () => {
|
||||
mockApi.get.mockResolvedValueOnce({ data: { data: { id: "cred-1" } } });
|
||||
mockApi.delete.mockResolvedValueOnce({ data: { ok: true } });
|
||||
|
||||
await credential.fetch("token", "cred-1");
|
||||
await credential.delete("token", "cred-1");
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith(
|
||||
"/v1/credential/credentials/cred-1",
|
||||
{
|
||||
headers: { Authorization: "Bearer token" },
|
||||
},
|
||||
);
|
||||
expect(mockApi.delete).toHaveBeenCalledWith(
|
||||
"/v1/credential/credentials/cred-1",
|
||||
{
|
||||
headers: { Authorization: "Bearer token" },
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("credential.create posts payload", async () => {
|
||||
const payload = {
|
||||
name: "VPN",
|
||||
notes: "notes",
|
||||
typeId: "type-1",
|
||||
companyId: "company-1",
|
||||
fields: [],
|
||||
};
|
||||
mockApi.post.mockResolvedValueOnce({ data: { data: payload } });
|
||||
|
||||
await credential.create("token", payload as any);
|
||||
|
||||
expect(mockApi.post).toHaveBeenCalledWith(
|
||||
"/v1/credential/credentials",
|
||||
payload,
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
});
|
||||
|
||||
it("credential.updateFields wraps fields payload", async () => {
|
||||
const fields = [{ id: "f1", name: "Username" }];
|
||||
mockApi.put.mockResolvedValueOnce({ data: { ok: true } });
|
||||
|
||||
await credential.updateFields("token", "cred-1", fields as any);
|
||||
|
||||
expect(mockApi.put).toHaveBeenCalledWith(
|
||||
"/v1/credential/credentials/cred-1/fields",
|
||||
{ fields },
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
});
|
||||
|
||||
it("credential sub-credential methods call expected endpoints", async () => {
|
||||
mockApi.get.mockResolvedValueOnce({ data: { data: [] } });
|
||||
mockApi.post.mockResolvedValueOnce({ data: { ok: true } });
|
||||
mockApi.delete.mockResolvedValueOnce({ data: { ok: true } });
|
||||
|
||||
await credential.fetchSubCredentials("token", "cred-1");
|
||||
await credential.addSubCredential("token", "cred-1", {
|
||||
fieldId: "fid",
|
||||
name: "Sub",
|
||||
fields: [{ fieldId: "f2", value: "v" }],
|
||||
});
|
||||
await credential.removeSubCredential("token", "cred-1", "sub-1");
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith(
|
||||
"/v1/credential/credentials/cred-1/sub-credentials",
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
expect(mockApi.post).toHaveBeenCalledWith(
|
||||
"/v1/credential/credentials/cred-1/sub-credentials",
|
||||
{ fieldId: "fid", name: "Sub", fields: [{ fieldId: "f2", value: "v" }] },
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
expect(mockApi.delete).toHaveBeenCalledWith(
|
||||
"/v1/credential/credentials/cred-1/sub-credentials/sub-1",
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
});
|
||||
|
||||
it("credentialType create and delete use identifier path", async () => {
|
||||
mockApi.post.mockResolvedValueOnce({ data: { ok: true } });
|
||||
mockApi.delete.mockResolvedValueOnce({ data: { ok: true } });
|
||||
|
||||
await credentialType.create("token", {
|
||||
name: "Router",
|
||||
permissionScope: "router",
|
||||
fields: [],
|
||||
} as any);
|
||||
await credentialType.delete("token", "ctype-1");
|
||||
|
||||
expect(mockApi.post).toHaveBeenCalledWith(
|
||||
"/v1/credential-type",
|
||||
{ name: "Router", permissionScope: "router", fields: [] },
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
expect(mockApi.delete).toHaveBeenCalledWith("/v1/credential-type/ctype-1", {
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
});
|
||||
|
||||
it("permission module endpoints are correct", async () => {
|
||||
mockApi.get.mockResolvedValue({ data: { data: [] } });
|
||||
|
||||
await permission.fetchCategorized("token");
|
||||
await permission.fetchFlat("token");
|
||||
await permission.fetchByCategory("token", "company");
|
||||
|
||||
expect(mockApi.get).toHaveBeenNthCalledWith(1, "/v1/permissions", {
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
expect(mockApi.get).toHaveBeenNthCalledWith(2, "/v1/permissions/nodes", {
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
expect(mockApi.get).toHaveBeenNthCalledWith(3, "/v1/permissions/company", {
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
});
|
||||
|
||||
it("procurement methods build params and count correctly", async () => {
|
||||
mockApi.get
|
||||
.mockResolvedValueOnce({ data: { data: [] } })
|
||||
.mockResolvedValueOnce({ data: { data: { count: 4 } } });
|
||||
|
||||
await procurement.fetchMany("token", 3, "switch", 10, true);
|
||||
const count = await procurement.count("token", true);
|
||||
|
||||
expect(mockApi.get).toHaveBeenNthCalledWith(1, "/v1/procurement/items", {
|
||||
params: { page: 3, rpp: 10, search: "switch", includeInactive: true },
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
expect(mockApi.get).toHaveBeenNthCalledWith(2, "/v1/procurement/count", {
|
||||
params: { activeOnly: "true" },
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
expect(count).toBe(4);
|
||||
});
|
||||
|
||||
it("procurement link and unlink post target id", async () => {
|
||||
mockApi.post.mockResolvedValue({ data: { ok: true } });
|
||||
|
||||
await procurement.linkItem("token", "item-a", "item-b");
|
||||
await procurement.unlinkItem("token", "item-a", "item-b");
|
||||
|
||||
expect(mockApi.post).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"/v1/procurement/items/item-a/link",
|
||||
{ targetId: "item-b" },
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
expect(mockApi.post).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"/v1/procurement/items/item-a/unlink",
|
||||
{ targetId: "item-b" },
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
});
|
||||
|
||||
it("role add and remove permissions include payload", async () => {
|
||||
mockApi.post.mockResolvedValueOnce({ data: { ok: true } });
|
||||
mockApi.delete.mockResolvedValueOnce({ data: { ok: true } });
|
||||
|
||||
await role.addPermissions("token", "role-1", ["a", "b"]);
|
||||
await role.removePermissions("token", "role-1", ["a"]);
|
||||
|
||||
expect(mockApi.post).toHaveBeenCalledWith(
|
||||
"/v1/role/role-1/permissions",
|
||||
{ permissions: ["a", "b"] },
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
expect(mockApi.delete).toHaveBeenCalledWith("/v1/role/role-1/permissions", {
|
||||
headers: { Authorization: "Bearer token" },
|
||||
data: { permissions: ["a"] },
|
||||
});
|
||||
});
|
||||
|
||||
it("sales.fetchMany includes includeClosed and search params", async () => {
|
||||
mockApi.get.mockResolvedValueOnce({ data: { data: [] } });
|
||||
|
||||
await sales.fetchMany("token", 1, "fiber", 25, true);
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith("/v1/sales/opportunities", {
|
||||
params: { page: 1, rpp: 25, search: "fiber", includeClosed: true },
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
});
|
||||
|
||||
it("users module uses expected endpoints", async () => {
|
||||
mockApi.get.mockResolvedValue({ data: { data: [] } });
|
||||
mockApi.post.mockResolvedValueOnce({ data: { data: { results: [] } } });
|
||||
mockApi.patch.mockResolvedValueOnce({ data: { data: {} } });
|
||||
mockApi.delete.mockResolvedValueOnce({ data: { data: {} } });
|
||||
|
||||
await users.fetchAll("token");
|
||||
await users.fetch("token", "user-1");
|
||||
await users.fetchRoles("token", "user-1");
|
||||
await users.checkPermissions("token", "user-1", ["x"]);
|
||||
await users.update("token", "user-1", { name: "New Name" });
|
||||
await users.delete("token", "user-1");
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith("/v1/user/users", {
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
expect(mockApi.get).toHaveBeenCalledWith("/v1/user/users/user-1", {
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
expect(mockApi.get).toHaveBeenCalledWith("/v1/user/users/user-1/roles", {
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
expect(mockApi.post).toHaveBeenCalledWith(
|
||||
"/v1/user/users/user-1/check-permission",
|
||||
{ permissions: ["x"] },
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
});
|
||||
|
||||
it("unifi module hits expected endpoints for site and wifi actions", async () => {
|
||||
mockApi.get.mockResolvedValue({ data: { data: [] } });
|
||||
mockApi.post.mockResolvedValue({ data: { ok: true } });
|
||||
mockApi.patch.mockResolvedValue({ data: { ok: true } });
|
||||
|
||||
await unifi.fetchSites("token");
|
||||
await unifi.syncSites("token");
|
||||
await unifi.createSite("token", "HQ");
|
||||
await unifi.linkSite("token", "site-1", "company-1");
|
||||
await unifi.unlinkSite("token", "site-1");
|
||||
await unifi.fetchSiteWifi("token", "site-1");
|
||||
await unifi.updateWifi("token", "site-1", "wlan-1", { name: "New" });
|
||||
await unifi.fetchPPSKs("token", "site-1", "wlan-1");
|
||||
await unifi.createPPSK("token", "site-1", "wlan-1", {
|
||||
key: "abc",
|
||||
name: "Staff",
|
||||
});
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith("/v1/unifi/sites", {
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
expect(mockApi.post).toHaveBeenCalledWith(
|
||||
"/v1/unifi/sites/sync",
|
||||
{},
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
expect(mockApi.patch).toHaveBeenCalledWith(
|
||||
"/v1/unifi/site/site-1/wifi/wlan-1",
|
||||
{ name: "New" },
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { mockGet, mockCreate } = vi.hoisted(() => ({
|
||||
mockGet: vi.fn(),
|
||||
mockCreate: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("axios", () => ({
|
||||
default: { create: mockCreate },
|
||||
create: mockCreate,
|
||||
}));
|
||||
|
||||
import { auth } from "./auth";
|
||||
|
||||
describe("auth module", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockCreate.mockReturnValue({ get: mockGet });
|
||||
});
|
||||
|
||||
it("fetches auth redirect uri and callback key", async () => {
|
||||
mockGet.mockResolvedValueOnce({
|
||||
data: {
|
||||
data: {
|
||||
uri: "https://auth.example.com",
|
||||
callbackKey: "cb-123",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await auth.fetchAuthRedirectUri("https://api.example.com");
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith({
|
||||
baseURL: "https://api.example.com",
|
||||
timeout: 5000,
|
||||
});
|
||||
expect(mockGet).toHaveBeenCalledWith("/v1/auth/uri");
|
||||
expect(result).toEqual({
|
||||
uri: "https://auth.example.com",
|
||||
callbackKey: "cb-123",
|
||||
});
|
||||
});
|
||||
|
||||
it("throws wrapped error when response is missing uri", async () => {
|
||||
mockGet.mockResolvedValueOnce({ data: { data: {} } });
|
||||
|
||||
await expect(
|
||||
auth.fetchAuthRedirectUri("https://api.example.com"),
|
||||
).rejects.toThrow(
|
||||
"Failed to fetch auth redirect uri: redirect uri missing from response",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws wrapped axios error message", async () => {
|
||||
mockGet.mockRejectedValueOnce(new Error("network down"));
|
||||
|
||||
await expect(
|
||||
auth.fetchAuthRedirectUri("https://api.example.com"),
|
||||
).rejects.toThrow("Failed to fetch auth redirect uri: network down");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,168 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { mockGetRequestEvent, mockRedirect, mockAxiosPost, mockIo, mockApi } =
|
||||
vi.hoisted(() => ({
|
||||
mockGetRequestEvent: vi.fn(),
|
||||
mockRedirect: vi.fn(),
|
||||
mockAxiosPost: vi.fn(),
|
||||
mockIo: vi.fn(),
|
||||
mockApi: {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("$app/server", () => ({
|
||||
getRequestEvent: mockGetRequestEvent,
|
||||
}));
|
||||
|
||||
vi.mock("$env/static/public", () => ({
|
||||
PUBLIC_API_URL: "https://api.example.com",
|
||||
}));
|
||||
|
||||
vi.mock("@sveltejs/kit", () => ({
|
||||
redirect: mockRedirect,
|
||||
}));
|
||||
|
||||
vi.mock("axios", () => ({
|
||||
default: { post: mockAxiosPost },
|
||||
post: mockAxiosPost,
|
||||
}));
|
||||
|
||||
vi.mock("../axios", () => ({
|
||||
default: mockApi,
|
||||
api: mockApi,
|
||||
}));
|
||||
|
||||
vi.mock("socket.io-client", () => ({
|
||||
io: mockIo,
|
||||
}));
|
||||
|
||||
import { user } from "./user";
|
||||
|
||||
describe("user module", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("isLoggedIn returns true when accessToken cookie exists", () => {
|
||||
mockGetRequestEvent.mockReturnValueOnce({
|
||||
cookies: {
|
||||
get: vi.fn().mockReturnValue("token"),
|
||||
},
|
||||
});
|
||||
|
||||
expect(user.isLoggedIn()).toBe(true);
|
||||
});
|
||||
|
||||
it("isLoggedIn returns false when accessToken cookie is missing", () => {
|
||||
mockGetRequestEvent.mockReturnValueOnce({
|
||||
cookies: {
|
||||
get: vi.fn().mockReturnValue(undefined),
|
||||
},
|
||||
});
|
||||
|
||||
expect(user.isLoggedIn()).toBe(false);
|
||||
});
|
||||
|
||||
it("refreshSession posts refresh header and returns token payload", async () => {
|
||||
mockAxiosPost.mockResolvedValueOnce({
|
||||
data: {
|
||||
data: {
|
||||
accessToken: "new-access",
|
||||
refreshToken: "new-refresh",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await user.refreshSession("refresh-123");
|
||||
|
||||
expect(mockAxiosPost).toHaveBeenCalledWith(
|
||||
"https://api.example.com/v1/auth/refresh",
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
"x-refresh-token": "refresh-123",
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(result).toEqual({
|
||||
accessToken: "new-access",
|
||||
refreshToken: "new-refresh",
|
||||
});
|
||||
});
|
||||
|
||||
it("fetchInfo and checkPermissions call expected endpoints", async () => {
|
||||
mockApi.get.mockResolvedValueOnce({ data: { data: { id: "me" } } });
|
||||
mockApi.post.mockResolvedValueOnce({ data: { data: { results: [] } } });
|
||||
|
||||
await user.fetchInfo("token");
|
||||
await user.checkPermissions("token", ["company.read"]);
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith("/v1/user/@me", {
|
||||
headers: { Authorization: "Bearer token" },
|
||||
});
|
||||
expect(mockApi.post).toHaveBeenCalledWith(
|
||||
"/v1/user/@me/check-permission",
|
||||
{ permissions: ["company.read"] },
|
||||
{ headers: { Authorization: "Bearer token" } },
|
||||
);
|
||||
});
|
||||
|
||||
it("logout clears auth cookies and redirects", () => {
|
||||
const deleteCookie = vi.fn();
|
||||
const fakeRedirect = { status: 303, location: "/login" };
|
||||
mockRedirect.mockReturnValueOnce(fakeRedirect);
|
||||
|
||||
const result = user.logout({
|
||||
cookies: { delete: deleteCookie },
|
||||
} as any);
|
||||
|
||||
expect(deleteCookie).toHaveBeenCalledWith("accessToken", { path: "/" });
|
||||
expect(deleteCookie).toHaveBeenCalledWith("refreshToken", { path: "/" });
|
||||
expect(mockRedirect).toHaveBeenCalledWith(303, "/login");
|
||||
expect(result).toBe(fakeRedirect);
|
||||
});
|
||||
|
||||
it("awaitAuthCallback resolves when socket event delivers tokens", async () => {
|
||||
const handlers: Record<string, (payload?: any) => void> = {};
|
||||
const disconnect = vi.fn();
|
||||
|
||||
mockIo.mockReturnValueOnce({
|
||||
on: vi.fn((event: string, callback: (payload?: any) => void) => {
|
||||
handlers[event] = callback;
|
||||
}),
|
||||
disconnect,
|
||||
});
|
||||
|
||||
const promise = user.awaitAuthCallback("cb-key");
|
||||
|
||||
handlers["auth:login:callback:cb-key"]?.({
|
||||
accessToken: "access",
|
||||
refreshToken: "refresh",
|
||||
});
|
||||
|
||||
await expect(promise).resolves.toEqual({
|
||||
accessToken: "access",
|
||||
refreshToken: "refresh",
|
||||
});
|
||||
expect(disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("awaitAuthCallback rejects on connect_error", async () => {
|
||||
const handlers: Record<string, (payload?: any) => void> = {};
|
||||
|
||||
mockIo.mockReturnValueOnce({
|
||||
on: vi.fn((event: string, callback: (payload?: any) => void) => {
|
||||
handlers[event] = callback;
|
||||
}),
|
||||
disconnect: vi.fn(),
|
||||
});
|
||||
|
||||
const promise = user.awaitAuthCallback("cb-key");
|
||||
|
||||
handlers.connect_error?.(new Error("socket failed"));
|
||||
|
||||
await expect(promise).rejects.toThrow("socket failed");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { mockCheckPermissions } = vi.hoisted(() => ({
|
||||
mockCheckPermissions: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("$lib", () => ({
|
||||
optima: {
|
||||
user: {
|
||||
checkPermissions: mockCheckPermissions,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
import {
|
||||
checkPermissions,
|
||||
hasPermission,
|
||||
resolvePermissions,
|
||||
} from "./permissions";
|
||||
|
||||
describe("permissions helpers", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns empty map when no permissions are requested", async () => {
|
||||
const result = await checkPermissions("token", []);
|
||||
|
||||
expect(result).toEqual({});
|
||||
expect(mockCheckPermissions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("maps API response into permission booleans", async () => {
|
||||
mockCheckPermissions.mockResolvedValueOnce({
|
||||
data: {
|
||||
results: [
|
||||
{ permission: "company.read", hasPermission: true },
|
||||
{ permission: "credential.create", hasPermission: false },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await checkPermissions("token", [
|
||||
"company.read",
|
||||
"credential.create",
|
||||
]);
|
||||
|
||||
expect(result).toEqual({
|
||||
"company.read": true,
|
||||
"credential.create": false,
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults requested permissions to false on API error", async () => {
|
||||
mockCheckPermissions.mockRejectedValueOnce(new Error("request failed"));
|
||||
|
||||
const result = await checkPermissions("token", ["a", "b"]);
|
||||
|
||||
expect(result).toEqual({ a: false, b: false });
|
||||
});
|
||||
|
||||
it("hasPermission returns true only for explicit true values", () => {
|
||||
expect(hasPermission({ "company.read": true }, "company.read")).toBe(true);
|
||||
expect(hasPermission({ "company.read": false }, "company.read")).toBe(
|
||||
false,
|
||||
);
|
||||
expect(hasPermission({}, "company.read")).toBe(false);
|
||||
});
|
||||
|
||||
it("exports resolvePermissions as backward-compatible alias", () => {
|
||||
expect(resolvePermissions).toBe(checkPermissions);
|
||||
});
|
||||
});
|
||||
@@ -42,6 +42,8 @@ export async function checkPermissions(
|
||||
}
|
||||
}
|
||||
|
||||
export const resolvePermissions = checkPermissions;
|
||||
|
||||
/**
|
||||
* Convenience helper — returns true when a specific permission is
|
||||
* granted inside a PermissionMap.
|
||||
|
||||
Reference in New Issue
Block a user