/** * 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.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: {}, })); // --------------------------------------------------------------------------- // 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 = {}, ): Record { 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: {}, ...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 = {}, ): Record { 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 = {}) { return { id: "user-1", userId: "ms-uid-1", name: "Test 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 = {}) { 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 = {}) { return { id: "company-1", name: "Test Company", cw_Identifier: "TestCo", cw_CompanyId: 123, createdAt: new Date("2025-01-01"), updatedAt: new Date("2025-01-01"), ...overrides, }; } /** Build a minimal Session row. */ export function buildMockSession(overrides: Record = {}) { 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 = {}) { 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 = {}) { const ctype = buildMockCredentialType(); const company = buildMockCompany(); return { id: "cred-1", name: "Test Credential", notes: null, typeId: ctype.id, companyId: company.id, 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 = {}) { 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 = {}) { return { id: "opp-1", cwOpportunityId: 1001, name: "Test Opportunity", notes: "Some notes", typeName: "New Business", typeCwId: 1, stageName: "Proposal", stageCwId: 2, statusName: "Active", statusCwId: 3, 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", contactCwId: 200, contactName: "Jane Doe", siteCwId: 300, siteName: "Main Office", customerPO: "PO-12345", totalSalesTax: 50.0, 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, companyId: "company-1", cwLastUpdated: new Date("2026-02-28"), 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 = {}) { 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 = {}) { 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 = {}) { 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 = {}) { return { id: "cat-1", cwCatalogId: 500, identifier: "USW-Pro-24", name: "UniFi Switch Pro 24", description: "24-port managed switch", customerDescription: "Enterprise switch", internalNotes: null, category: "Technology", categoryCwId: 18, subcategory: "Network-Switch", subcategoryCwId: 112, manufacturer: "Ubiquiti", manufactureCwId: 248, 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, }; }