Files
optima/src/lib/optima-api/modules/api-modules.spec.ts
T
HoloPanio 4bec198db6 feat: sales opportunity detail, procurement filters, permission resilience
- Add sales opportunity detail page with tabs (overview, notes, contacts, products, forecasts, activity)
- Add sales note CRUD endpoints (create, update, delete) with server routes
- Add opportunity types, contacts, product sequencing, and refresh API methods
- Add AddProductModal component for catalog browsing
- Update procurement.fetchMany to accept CatalogItemFilters object
- Add procurement.fetchCategories and procurement.fetchFilters endpoints
- Add resilient permission check (no-token returns all-true with __checkFailed)
- Parallelize company detail data fetches for performance
- Remove stale console.log statements across modules
- Add comprehensive unit tests for all new API methods and permission edge cases
2026-03-01 13:08:58 -06:00

545 lines
17 KiB
TypeScript

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,
{ search: "switch", includeInactive: true },
10,
);
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("procurement.fetchMany passes all CatalogItemFilters as params", async () => {
mockApi.get.mockResolvedValueOnce({ data: { data: [] } });
await procurement.fetchMany("token", 1, {
search: "cable",
category: "Networking",
subcategory: "Ethernet",
group: "Cat6",
manufacturer: "Ubiquiti",
ecosystem: "unifi",
inStock: true,
minPrice: 10,
maxPrice: 500,
includeInactive: true,
});
expect(mockApi.get).toHaveBeenCalledWith("/v1/procurement/items", {
params: {
page: 1,
rpp: 30,
search: "cable",
category: "Networking",
subcategory: "Ethernet",
group: "Cat6",
manufacturer: "Ubiquiti",
ecosystem: "unifi",
inStock: true,
minPrice: 10,
maxPrice: 500,
includeInactive: true,
},
headers: { Authorization: "Bearer token" },
});
});
it("procurement.fetchMany omits filters that are not set", async () => {
mockApi.get.mockResolvedValueOnce({ data: { data: [] } });
await procurement.fetchMany("token", 2, { category: "Audio" }, 15);
expect(mockApi.get).toHaveBeenCalledWith("/v1/procurement/items", {
params: { page: 2, rpp: 15, category: "Audio" },
headers: { Authorization: "Bearer token" },
});
});
it("procurement.fetchCategories returns category tree", async () => {
const tree = { categories: [{ name: "Net" }], ecosystems: [] };
mockApi.get.mockResolvedValueOnce({ data: { data: tree } });
const result = await procurement.fetchCategories("token");
expect(mockApi.get).toHaveBeenCalledWith("/v1/procurement/categories", {
headers: { Authorization: "Bearer token" },
});
expect(result).toEqual(tree);
});
it("procurement.fetchFilters passes optional params", async () => {
const filterValues = {
categories: ["A"],
subcategories: ["B"],
manufacturers: ["C"],
};
mockApi.get.mockResolvedValueOnce({ data: { data: filterValues } });
const result = await procurement.fetchFilters("token", {
category: "Networking",
subcategory: "Switches",
includeInactive: true,
});
expect(mockApi.get).toHaveBeenCalledWith("/v1/procurement/filters", {
params: {
category: "Networking",
subcategory: "Switches",
includeInactive: "true",
},
headers: { Authorization: "Bearer token" },
});
expect(result).toEqual(filterValues);
});
it("procurement.fetchFilters works with no options", async () => {
const filterValues = {
categories: [],
subcategories: [],
manufacturers: [],
};
mockApi.get.mockResolvedValueOnce({ data: { data: filterValues } });
const result = await procurement.fetchFilters("token");
expect(mockApi.get).toHaveBeenCalledWith("/v1/procurement/filters", {
params: {},
headers: { Authorization: "Bearer token" },
});
expect(result).toEqual(filterValues);
});
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("sales.fetchOpportunityTypes calls expected endpoint", async () => {
mockApi.get.mockResolvedValueOnce({ data: { data: [] } });
await sales.fetchOpportunityTypes("token");
expect(mockApi.get).toHaveBeenCalledWith("/v1/sales/opportunity-types", {
headers: { Authorization: "Bearer token" },
});
});
it("sales.fetchOne encodes identifier in URL", async () => {
mockApi.get.mockResolvedValueOnce({ data: { data: { id: "opp-1" } } });
await sales.fetchOne("token", "opp-1");
expect(mockApi.get).toHaveBeenCalledWith(
"/v1/sales/opportunities/opp-1",
{ headers: { Authorization: "Bearer token" } },
);
});
it("sales.fetchForecasts calls forecasts endpoint", async () => {
mockApi.get.mockResolvedValueOnce({ data: { data: [] } });
await sales.fetchForecasts("token", "opp-1");
expect(mockApi.get).toHaveBeenCalledWith(
"/v1/sales/opportunities/opp-1/forecasts",
{ headers: { Authorization: "Bearer token" } },
);
});
it("sales.fetchProducts calls products endpoint", async () => {
mockApi.get.mockResolvedValueOnce({ data: { data: [] } });
await sales.fetchProducts("token", "opp-1");
expect(mockApi.get).toHaveBeenCalledWith(
"/v1/sales/opportunities/opp-1/products",
{ headers: { Authorization: "Bearer token" } },
);
});
it("sales.fetchNotes calls notes endpoint", async () => {
mockApi.get.mockResolvedValueOnce({ data: { data: [] } });
await sales.fetchNotes("token", "opp-1");
expect(mockApi.get).toHaveBeenCalledWith(
"/v1/sales/opportunities/opp-1/notes",
{ headers: { Authorization: "Bearer token" } },
);
});
it("sales.createNote posts note payload", async () => {
mockApi.post.mockResolvedValueOnce({ data: { id: 1, text: "Hello" } });
await sales.createNote("token", "opp-1", {
text: "Hello",
flagged: true,
});
expect(mockApi.post).toHaveBeenCalledWith(
"/v1/sales/opportunities/opp-1/notes",
{ text: "Hello", flagged: true },
{ headers: { Authorization: "Bearer token" } },
);
});
it("sales.updateNote patches with noteId in URL", async () => {
mockApi.patch.mockResolvedValueOnce({ data: { ok: true } });
await sales.updateNote("token", "opp-1", 42, { text: "Updated" });
expect(mockApi.patch).toHaveBeenCalledWith(
"/v1/sales/opportunities/opp-1/notes/42",
{ text: "Updated" },
{ headers: { Authorization: "Bearer token" } },
);
});
it("sales.deleteNote calls delete with noteId in URL", async () => {
mockApi.delete.mockResolvedValueOnce({ data: { ok: true } });
await sales.deleteNote("token", "opp-1", 42);
expect(mockApi.delete).toHaveBeenCalledWith(
"/v1/sales/opportunities/opp-1/notes/42",
{ headers: { Authorization: "Bearer token" } },
);
});
it("sales.fetchContacts calls contacts endpoint", async () => {
mockApi.get.mockResolvedValueOnce({ data: { data: [] } });
await sales.fetchContacts("token", "opp-1");
expect(mockApi.get).toHaveBeenCalledWith(
"/v1/sales/opportunities/opp-1/contacts",
{ headers: { Authorization: "Bearer token" } },
);
});
it("sales.sequenceProducts patches with ordered IDs", async () => {
mockApi.patch.mockResolvedValueOnce({ data: { ok: true } });
await sales.sequenceProducts("token", "opp-1", [3, 1, 2]);
expect(mockApi.patch).toHaveBeenCalledWith(
"/v1/sales/opportunities/opp-1/products/sequence",
{ orderedIds: [3, 1, 2] },
{ headers: { Authorization: "Bearer token" } },
);
});
it("sales.refreshOpportunity posts to refresh endpoint", async () => {
mockApi.post.mockResolvedValueOnce({ data: { ok: true } });
await sales.refreshOpportunity("token", "opp-1");
expect(mockApi.post).toHaveBeenCalledWith(
"/v1/sales/opportunities/opp-1/refresh",
{},
{ headers: { Authorization: "Bearer token" } },
);
});
it("sales.fetchOne encodes special characters in identifier", async () => {
mockApi.get.mockResolvedValueOnce({ data: { data: {} } });
await sales.fetchOne("token", "opp/special#1");
expect(mockApi.get).toHaveBeenCalledWith(
`/v1/sales/opportunities/${encodeURIComponent("opp/special#1")}`,
{ 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" } },
);
});
});