Files

568 lines
17 KiB
TypeScript

/**
* Global test setup — mock heavy external dependencies so unit tests
* never touch real databases, APIs, or file-system keys.
*/
import { mock } from "bun:test";
import crypto from "crypto";
// ---------------------------------------------------------------------------
// Generate a real RSA key pair for modules that call crypto.createPrivateKey()
// at import time (e.g. readSecureValue.ts).
// ---------------------------------------------------------------------------
const { privateKey: _testPrivateKey, publicKey: _testPublicKey } =
crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
privateKeyEncoding: { type: "pkcs8", format: "pem" },
publicKeyEncoding: { type: "spki", format: "pem" },
});
// ---------------------------------------------------------------------------
// Mock the globalEvents module — many source files import `events` from here.
// We provide both `events` and `setupEventDebugger` so that no test
// encounters "export not found" when another test's mock.module is stale.
// ---------------------------------------------------------------------------
mock.module("../src/modules/globalEvents", () => ({
events: {
emit: mock(),
on: mock(),
off: mock(),
once: mock(),
removeAllListeners: mock(),
},
setupEventDebugger: mock(),
}));
// ---------------------------------------------------------------------------
// Mock modules that are commonly mocked by test files at top-level.
// Having them in the preload ensures that even when per-test mock.module
// calls persist globally, the baseline mock is complete.
// ---------------------------------------------------------------------------
mock.module("../src/modules/fetchMicrosoftUser", () => ({
fetchMicrosoftUser: mock(() => Promise.resolve({})),
}));
// ---------------------------------------------------------------------------
// Mock workert — instantiates PgBoss at module load time which requires
// DATABASE_URL. Tests don't have a real DB, so we stub all exports.
// ---------------------------------------------------------------------------
mock.module("../src/workert", () => ({
getBoss: mock(() => ({
send: mock(() => Promise.resolve("mock-job-id")),
createQueue: mock(() => Promise.resolve()),
start: mock(() => Promise.resolve()),
on: mock(),
})),
initializeWorkerSystem: mock(() => Promise.resolve()),
ensureManagerSocketReady: mock(() => Promise.resolve({ on: mock(), emit: mock() })),
reserveWorkerId: mock(() => Promise.resolve("mock-worker-id")),
}));
mock.module("../src/managers/sessions", () => ({
sessions: {
create: mock(() =>
Promise.resolve({
accessToken: "mock-access",
refreshToken: "mock-refresh",
})
),
fetch: mock(() => Promise.resolve(null)),
},
}));
// ---------------------------------------------------------------------------
// Mock the constants module — almost every source file imports from here.
// We provide safe defaults so modules can be imported without side-effects.
// ---------------------------------------------------------------------------
mock.module("../src/constants", () => ({
prisma: createMockPrisma(),
PORT: "3333",
API_BASE_URL: "http://localhost:3333",
sessionDuration: 30 * 24 * 60 * 60_000,
accessTokenDuration: "10min",
refreshTokenDuration: "30d",
accessTokenPrivateKey: _testPrivateKey,
refreshTokenPrivateKey: _testPrivateKey,
permissionsPrivateKey: _testPrivateKey,
secureValuesPrivateKey: _testPrivateKey,
secureValuesPublicKey: _testPublicKey,
msalClient: { acquireTokenByCode: mock(() => Promise.resolve({})) },
connectWiseApi: {
get: mock(() => Promise.resolve({ data: {} })),
post: mock(() => Promise.resolve({ data: {} })),
put: mock(() => Promise.resolve({ data: {} })),
patch: mock(() => Promise.resolve({ data: {} })),
delete: mock(() => Promise.resolve({ data: {} })),
},
redis: {
get: mock(() => Promise.resolve(null)),
set: mock(() => Promise.resolve("OK")),
del: mock(() => Promise.resolve(1)),
},
unifi: createMockUnifi(),
unifiControllerBaseUrl: "https://unifi.test.local",
unifiSite: "default",
unifiUsername: "admin",
unifiPassword: "test-pass",
io: { of: mock(() => ({ on: mock() })) },
engine: {},
collectorSocket: {
connected: false,
connect: mock(),
disconnect: mock(),
on: mock(),
emit: mock(),
},
connectCollectorSocket: mock(),
}));
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
* Build a complete mock constants object for use with `mock.module()`.
*
* Pass `overrides` to replace specific exports (e.g. a custom prisma mock).
* All keys from the preload mock are included so that downstream modules
* importing named exports (secureValuesPublicKey, connectWiseApi, etc.)
* never encounter "export not found" errors.
*/
export function buildMockConstants(
overrides: Record<string, any> = {}
): Record<string, any> {
return {
prisma: createMockPrisma(),
PORT: "3333",
API_BASE_URL: "http://localhost:3333",
sessionDuration: 30 * 24 * 60 * 60_000,
accessTokenDuration: "10min",
refreshTokenDuration: "30d",
accessTokenPrivateKey: _testPrivateKey,
refreshTokenPrivateKey: _testPrivateKey,
permissionsPrivateKey: _testPrivateKey,
secureValuesPrivateKey: _testPrivateKey,
secureValuesPublicKey: _testPublicKey,
msalClient: { acquireTokenByCode: mock(() => Promise.resolve({})) },
connectWiseApi: {
get: mock(() => Promise.resolve({ data: {} })),
post: mock(() => Promise.resolve({ data: {} })),
put: mock(() => Promise.resolve({ data: {} })),
patch: mock(() => Promise.resolve({ data: {} })),
delete: mock(() => Promise.resolve({ data: {} })),
},
redis: {
get: mock(() => Promise.resolve(null)),
set: mock(() => Promise.resolve("OK")),
del: mock(() => Promise.resolve(1)),
},
unifi: createMockUnifi(),
unifiControllerBaseUrl: "https://unifi.test.local",
unifiSite: "default",
unifiUsername: "admin",
unifiPassword: "test-pass",
io: { of: mock(() => ({ on: mock() })) },
engine: {},
collectorSocket: {
connected: false,
connect: mock(),
disconnect: mock(),
on: mock(),
emit: mock(),
},
connectCollectorSocket: mock(),
...overrides,
};
}
/**
* Build a complete mock globalEvents object for use with `mock.module()`.
* Includes both `events` and `setupEventDebugger` so downstream modules
* never encounter "export not found" errors.
*/
export function buildMockGlobalEvents(
overrides: Record<string, any> = {}
): Record<string, any> {
return {
events: {
emit: mock(),
on: mock(),
off: mock(),
once: mock(),
removeAllListeners: mock(),
},
setupEventDebugger: mock(),
...overrides,
};
}
export function createMockPrisma() {
const createModelProxy = () =>
new Proxy(
{},
{
get(_target, prop) {
return mock(() => Promise.resolve(null));
},
}
);
return new Proxy(
{},
{
get(_target, prop) {
if (prop === "$connect" || prop === "$disconnect")
return mock(() => Promise.resolve());
return createModelProxy();
},
}
);
}
export function createMockUnifi() {
return {
login: mock(() => Promise.resolve()),
getAllSites: mock(() => Promise.resolve([])),
getSiteOverview: mock(() => Promise.resolve({})),
getDevices: mock(() => Promise.resolve([])),
getWlanConf: mock(() => Promise.resolve([])),
updateWlanConf: mock(() => Promise.resolve({})),
getNetworks: mock(() => Promise.resolve([])),
createSite: mock(() =>
Promise.resolve({ name: "default", description: "Default" })
),
getWlanGroups: mock(() => Promise.resolve([])),
createWlanGroup: mock(() => Promise.resolve({})),
getUserGroups: mock(() => Promise.resolve([])),
createUserGroup: mock(() => Promise.resolve({})),
getApGroups: mock(() => Promise.resolve([])),
createApGroup: mock(() => Promise.resolve({})),
updateApGroup: mock(() => Promise.resolve({})),
getAccessPoints: mock(() => Promise.resolve([])),
getWifiLimits: mock(() => Promise.resolve({})),
getPrivatePSKs: mock(() => Promise.resolve([])),
createPrivatePSK: mock(() => Promise.resolve({})),
};
}
/** Build a minimal Prisma-shaped User row for controller tests. */
export function buildMockUser(overrides: Record<string, any> = {}) {
return {
id: "user-1",
userId: "ms-uid-1",
firstName: "Test",
lastName: "User",
login: "test@example.com",
email: "test@example.com",
emailVerified: null,
image: null,
token: "ms-token",
permissions: null,
createdAt: new Date("2025-01-01"),
updatedAt: new Date("2025-01-01"),
roles: [],
...overrides,
};
}
/** Build a minimal Prisma-shaped Role row. */
export function buildMockRole(overrides: Record<string, any> = {}) {
return {
id: "role-1",
title: "Test Role",
moniker: "test-role",
permissions: "mock-permissions-token",
createdAt: new Date("2025-01-01"),
updatedAt: new Date("2025-01-01"),
users: [],
...overrides,
};
}
/** Build a minimal Prisma-shaped Company row. */
export function buildMockCompany(overrides: Record<string, any> = {}) {
return {
id: 123,
uid: "company-1",
identifier: "TestCo",
name: "Test Company",
phone: "555-1234",
website: "https://test.com",
contacts: [],
companyAddresses: [],
createdAt: new Date("2025-01-01"),
updatedAt: new Date("2025-01-01"),
...overrides,
};
}
/** Build a minimal Session row. */
export function buildMockSession(overrides: Record<string, any> = {}) {
return {
id: "session-1",
sessionKey: "sk-abc123",
userId: "user-1",
expires: new Date(Date.now() + 30 * 24 * 60 * 60_000),
refreshedAt: null,
invalidatedAt: null,
refreshTokenGenerated: false,
...overrides,
};
}
/** Build a minimal CredentialType row. */
export function buildMockCredentialType(overrides: Record<string, any> = {}) {
return {
id: "ctype-1",
name: "Login Credential",
permissionScope: "credential.login",
icon: null,
fields: [
{
id: "username",
name: "Username",
required: true,
secure: false,
valueType: "plain_text",
},
{
id: "password",
name: "Password",
required: true,
secure: true,
valueType: "password",
},
],
credentials: [],
createdAt: new Date("2025-01-01"),
updatedAt: new Date("2025-01-01"),
...overrides,
};
}
/** Build a minimal Credential row. */
export function buildMockCredential(overrides: Record<string, any> = {}) {
const ctype = buildMockCredentialType();
const company = buildMockCompany();
return {
id: "cred-1",
name: "Test Credential",
notes: null,
typeId: ctype.id,
companyId: company.uid,
subCredentialOfId: null,
fields: { username: "admin" },
type: ctype,
company,
securevalues: [
{
id: "sv-1",
name: "password",
content: "encrypted-data",
hash: "BLAKE2s$abc$salt",
credentialId: "cred-1",
createdAt: new Date("2025-01-01"),
updatedAt: new Date("2025-01-01"),
},
],
subCredentials: [],
createdAt: new Date("2025-01-01"),
updatedAt: new Date("2025-01-01"),
...overrides,
};
}
/** Build a minimal UnifiSite row. */
export function buildMockUnifiSite(overrides: Record<string, any> = {}) {
return {
id: "usite-1",
name: "Main Office",
siteId: "default",
companyId: null,
createdAt: new Date("2025-01-01"),
updatedAt: new Date("2025-01-01"),
...overrides,
};
}
/** Build a minimal Prisma-shaped Opportunity row. */
export function buildMockOpportunity(overrides: Record<string, any> = {}) {
return {
uid: "opp-1",
id: 1001,
name: "Test Opportunity",
notes: "Some notes",
typeId: 1,
type: { id: 1, name: "New Business" },
stage: { id: 2, name: "Proposal" },
stageName: "Proposal",
stageCwId: 2,
statusId: 3,
status: { id: 3, name: "Active" },
priorityName: "High",
priorityCwId: 4,
ratingName: "Hot",
ratingCwId: 5,
source: "Referral",
campaignName: null,
campaignCwId: null,
primarySalesRepName: "John",
primarySalesRepIdentifier: "jroberts",
primarySalesRepCwId: 10,
secondarySalesRepName: null,
secondarySalesRepIdentifier: null,
secondarySalesRepCwId: null,
companyCwId: 123,
companyName: "Test Company",
companyId: 123,
contactCwId: 200,
contactName: "Jane Doe",
siteCwId: 300,
siteName: "Main Office",
customerPO: "PO-12345",
totalSalesTax: 50.0,
probability: 80,
locationName: "HQ",
locationCwId: 400,
departmentName: "Sales",
departmentCwId: 500,
expectedCloseDate: new Date("2026-04-01"),
pipelineChangeDate: new Date("2026-02-15"),
dateBecameLead: new Date("2026-01-01"),
closedDate: null,
closedFlag: false,
closedByName: null,
closedByCwId: null,
cwLastUpdated: new Date("2026-02-28"),
cwDateEntered: new Date("2026-01-01"),
productSequence: [],
createdAt: new Date("2026-01-01"),
updatedAt: new Date("2026-02-28"),
company: null,
...overrides,
};
}
/** Build a minimal CW Activity object for ActivityController tests. */
export function buildMockCWActivity(overrides: Record<string, any> = {}) {
return {
id: 5001,
name: "Test Activity",
notes: "Activity notes",
type: { id: 1, name: "Call" },
status: { id: 2, name: "Open" },
company: { id: 123, identifier: "TestCo", name: "Test Company" },
contact: { id: 200, name: "Jane Doe" },
phoneNumber: "555-1234",
email: "jane@test.com",
opportunity: { id: 1001, name: "Test Opportunity" },
ticket: { id: 0, name: "" },
agreement: { id: 0, name: "" },
campaign: { id: 0, name: "" },
assignTo: { id: 10, identifier: "jroberts", name: "John Roberts" },
scheduleStatus: { id: 1, name: "Firm" },
reminder: { id: 1, name: "15 Minutes" },
where: { id: 1, name: "Office" },
dateStart: "2026-03-01T09:00:00Z",
dateEnd: "2026-03-01T10:00:00Z",
notifyFlag: false,
mobileGuid: "guid-abc123",
currency: { id: 1, name: "USD" },
customFields: [],
_info: {
lastUpdated: "2026-02-28T12:00:00Z",
updatedBy: "jroberts",
dateEntered: "2026-01-15T08:00:00Z",
enteredBy: "jroberts",
},
...overrides,
};
}
/** Build a minimal CW Forecast Item for ForecastProductController tests. */
export function buildMockCWForecastItem(overrides: Record<string, any> = {}) {
return {
id: 7001,
forecastDescription: "Network Switch",
opportunity: { id: 1001, name: "Test Opportunity" },
quantity: 5,
status: { id: 1, name: "Won" },
catalogItem: { id: 500, identifier: "USW-Pro-24" },
productDescription: "UniFi Switch Pro 24",
productClass: "Product",
forecastType: "Product",
revenue: 2500.0,
cost: 1800.0,
margin: 700.0,
percentage: 100,
includeFlag: true,
linkFlag: false,
recurringFlag: false,
taxableFlag: true,
recurringRevenue: 0,
recurringCost: 0,
cycles: 0,
sequenceNumber: 1,
subNumber: 0,
quoteWerksQuantity: 0,
_info: {
lastUpdated: "2026-02-28T12:00:00Z",
updatedBy: "jroberts",
},
...overrides,
};
}
/** Build a minimal Prisma-shaped GeneratedQuotes row. */
export function buildMockGeneratedQuote(overrides: Record<string, any> = {}) {
return {
id: "quote-1",
quoteRegenData: { theme: "default" },
quoteFile: new Uint8Array([0x25, 0x50, 0x44, 0x46]),
quoteFileName: "Quote-TestOpp.pdf",
opportunityId: "opp-1",
createdById: "user-1",
createdAt: new Date("2026-03-01"),
updatedAt: new Date("2026-03-01"),
opportunity: null,
createdBy: null,
...overrides,
};
}
/** Build a minimal Prisma-shaped CatalogItem row. */
export function buildMockCatalogItem(overrides: Record<string, any> = {}) {
return {
id: 500,
uid: "cat-1",
identifier: "USW-Pro-24",
name: "UniFi Switch Pro 24",
description: "24-port managed switch",
customerDescription: "Enterprise switch",
internalNotes: null,
subcategory: {
id: 112,
name: "Network-Switch",
category: { id: 18, name: "Technology" },
},
manufacturer: { id: 248, name: "Ubiquiti" },
partNumber: "USW-Pro-24",
vendorName: "Ubiquiti Inc",
vendorSku: "USW-Pro-24",
vendorCwId: 100,
price: 500.0,
cost: 360.0,
inactive: false,
salesTaxable: true,
onHand: 10,
cwLastUpdated: new Date("2026-02-28"),
linkedItems: [],
createdAt: new Date("2026-01-01"),
updatedAt: new Date("2026-02-28"),
...overrides,
};
}