feat: restructure sales, add PDF quote generation and WebSocket support
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
import { describe, test, expect, mock, beforeEach } from "bun:test";
|
||||
import { GeneratedQuoteController } from "../../src/controllers/GeneratedQuoteController";
|
||||
import {
|
||||
buildMockGeneratedQuote,
|
||||
buildMockOpportunity,
|
||||
buildMockUser,
|
||||
} from "../setup";
|
||||
|
||||
/**
|
||||
* The Proxy-based prisma mock in setup.ts creates a fresh mock() for every
|
||||
* property access, so we cannot use `(prisma.x.findFirst as any).mockReturnValueOnce()`
|
||||
* because the manager's import access gets a different mock.
|
||||
*
|
||||
* Instead, we mock the entire constants module per-test with stable mock functions.
|
||||
*/
|
||||
|
||||
function createStablePrismaMock(
|
||||
overrides: Record<string, Record<string, any>> = {},
|
||||
) {
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_target, model: string) {
|
||||
if (model === "$connect" || model === "$disconnect")
|
||||
return mock(() => Promise.resolve());
|
||||
if (overrides[model]) return overrides[model];
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get() {
|
||||
return mock(() => Promise.resolve(null));
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
describe("generatedQuotes manager", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// fetch
|
||||
// -------------------------------------------------------------------
|
||||
describe("fetch()", () => {
|
||||
test("returns a GeneratedQuoteController when found", async () => {
|
||||
const mockData = buildMockGeneratedQuote();
|
||||
const findFirst = mock(() => Promise.resolve(mockData));
|
||||
mock.module("../../src/constants", () => ({
|
||||
prisma: createStablePrismaMock({
|
||||
generatedQuotes: { findFirst },
|
||||
}),
|
||||
}));
|
||||
|
||||
// Re-import to pick up the fresh mock
|
||||
const { generatedQuotes } =
|
||||
await import("../../src/managers/generatedQuotes");
|
||||
const result = await generatedQuotes.fetch("quote-1");
|
||||
expect(result).toBeInstanceOf(GeneratedQuoteController);
|
||||
expect(result.id).toBe("quote-1");
|
||||
});
|
||||
|
||||
test("throws GenericError with 404 when not found", async () => {
|
||||
const findFirst = mock(() => Promise.resolve(null));
|
||||
mock.module("../../src/constants", () => ({
|
||||
prisma: createStablePrismaMock({
|
||||
generatedQuotes: { findFirst },
|
||||
}),
|
||||
}));
|
||||
|
||||
const { generatedQuotes } =
|
||||
await import("../../src/managers/generatedQuotes");
|
||||
try {
|
||||
await generatedQuotes.fetch("nonexistent");
|
||||
expect(true).toBe(false);
|
||||
} catch (e: any) {
|
||||
expect(e.name).toBe("GeneratedQuoteNotFound");
|
||||
expect(e.status).toBe(404);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// fetchByOpportunity
|
||||
// -------------------------------------------------------------------
|
||||
describe("fetchByOpportunity()", () => {
|
||||
test("returns array of GeneratedQuoteController", async () => {
|
||||
const rows = [
|
||||
buildMockGeneratedQuote({ id: "q-1" }),
|
||||
buildMockGeneratedQuote({ id: "q-2" }),
|
||||
];
|
||||
const findMany = mock(() => Promise.resolve(rows));
|
||||
mock.module("../../src/constants", () => ({
|
||||
prisma: createStablePrismaMock({
|
||||
generatedQuotes: { findMany },
|
||||
}),
|
||||
}));
|
||||
|
||||
const { generatedQuotes } =
|
||||
await import("../../src/managers/generatedQuotes");
|
||||
const result = await generatedQuotes.fetchByOpportunity("opp-1");
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toBeInstanceOf(GeneratedQuoteController);
|
||||
expect(result[0].id).toBe("q-1");
|
||||
});
|
||||
|
||||
test("returns empty array when none found", async () => {
|
||||
const findMany = mock(() => Promise.resolve([]));
|
||||
mock.module("../../src/constants", () => ({
|
||||
prisma: createStablePrismaMock({
|
||||
generatedQuotes: { findMany },
|
||||
}),
|
||||
}));
|
||||
|
||||
const { generatedQuotes } =
|
||||
await import("../../src/managers/generatedQuotes");
|
||||
const result = await generatedQuotes.fetchByOpportunity("opp-999");
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// fetchByCreator
|
||||
// -------------------------------------------------------------------
|
||||
describe("fetchByCreator()", () => {
|
||||
test("returns array of GeneratedQuoteController", async () => {
|
||||
const rows = [buildMockGeneratedQuote({ id: "q-1" })];
|
||||
const findMany = mock(() => Promise.resolve(rows));
|
||||
mock.module("../../src/constants", () => ({
|
||||
prisma: createStablePrismaMock({
|
||||
generatedQuotes: { findMany },
|
||||
}),
|
||||
}));
|
||||
|
||||
const { generatedQuotes } =
|
||||
await import("../../src/managers/generatedQuotes");
|
||||
const result = await generatedQuotes.fetchByCreator("user-1");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toBeInstanceOf(GeneratedQuoteController);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// create
|
||||
// -------------------------------------------------------------------
|
||||
describe("create()", () => {
|
||||
test("throws 404 when opportunity not found", async () => {
|
||||
mock.module("../../src/constants", () => ({
|
||||
prisma: createStablePrismaMock({
|
||||
opportunity: { findFirst: mock(() => Promise.resolve(null)) },
|
||||
}),
|
||||
}));
|
||||
|
||||
const { generatedQuotes } =
|
||||
await import("../../src/managers/generatedQuotes");
|
||||
try {
|
||||
await generatedQuotes.create({
|
||||
quoteRegenData: {},
|
||||
quoteFile: Buffer.from("pdf"),
|
||||
quoteFileName: "test.pdf",
|
||||
opportunityId: "nonexistent-opp",
|
||||
createdById: "user-1",
|
||||
});
|
||||
expect(true).toBe(false);
|
||||
} catch (e: any) {
|
||||
expect(e.name).toBe("OpportunityNotFound");
|
||||
expect(e.status).toBe(404);
|
||||
}
|
||||
});
|
||||
|
||||
test("throws 404 when user not found", async () => {
|
||||
mock.module("../../src/constants", () => ({
|
||||
prisma: createStablePrismaMock({
|
||||
opportunity: {
|
||||
findFirst: mock(() => Promise.resolve({ id: "opp-1" })),
|
||||
},
|
||||
user: { findFirst: mock(() => Promise.resolve(null)) },
|
||||
}),
|
||||
}));
|
||||
|
||||
const { generatedQuotes } =
|
||||
await import("../../src/managers/generatedQuotes");
|
||||
try {
|
||||
await generatedQuotes.create({
|
||||
quoteRegenData: {},
|
||||
quoteFile: Buffer.from("pdf"),
|
||||
quoteFileName: "test.pdf",
|
||||
opportunityId: "opp-1",
|
||||
createdById: "nonexistent-user",
|
||||
});
|
||||
expect(true).toBe(false);
|
||||
} catch (e: any) {
|
||||
expect(e.name).toBe("UserNotFound");
|
||||
expect(e.status).toBe(404);
|
||||
}
|
||||
});
|
||||
|
||||
test("creates and returns GeneratedQuoteController", async () => {
|
||||
const mockQuote = buildMockGeneratedQuote();
|
||||
mock.module("../../src/constants", () => ({
|
||||
prisma: createStablePrismaMock({
|
||||
opportunity: {
|
||||
findFirst: mock(() => Promise.resolve({ id: "opp-1" })),
|
||||
},
|
||||
user: { findFirst: mock(() => Promise.resolve({ id: "user-1" })) },
|
||||
generatedQuotes: { create: mock(() => Promise.resolve(mockQuote)) },
|
||||
}),
|
||||
}));
|
||||
|
||||
const { generatedQuotes } =
|
||||
await import("../../src/managers/generatedQuotes");
|
||||
const result = await generatedQuotes.create({
|
||||
quoteRegenData: { theme: "default" },
|
||||
quoteFile: Buffer.from("pdf-content"),
|
||||
quoteFileName: "Quote.pdf",
|
||||
opportunityId: "opp-1",
|
||||
createdById: "user-1",
|
||||
});
|
||||
|
||||
expect(result).toBeInstanceOf(GeneratedQuoteController);
|
||||
expect(result.id).toBe("quote-1");
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// delete
|
||||
// -------------------------------------------------------------------
|
||||
describe("delete()", () => {
|
||||
test("throws 404 when quote not found", async () => {
|
||||
mock.module("../../src/constants", () => ({
|
||||
prisma: createStablePrismaMock({
|
||||
generatedQuotes: { findFirst: mock(() => Promise.resolve(null)) },
|
||||
}),
|
||||
}));
|
||||
|
||||
const { generatedQuotes } =
|
||||
await import("../../src/managers/generatedQuotes");
|
||||
try {
|
||||
await generatedQuotes.delete("nonexistent");
|
||||
expect(true).toBe(false);
|
||||
} catch (e: any) {
|
||||
expect(e.name).toBe("GeneratedQuoteNotFound");
|
||||
expect(e.status).toBe(404);
|
||||
}
|
||||
});
|
||||
|
||||
test("deletes successfully when quote exists", async () => {
|
||||
mock.module("../../src/constants", () => ({
|
||||
prisma: createStablePrismaMock({
|
||||
generatedQuotes: {
|
||||
findFirst: mock(() => Promise.resolve({ id: "quote-1" })),
|
||||
delete: mock(() => Promise.resolve(undefined)),
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const { generatedQuotes } =
|
||||
await import("../../src/managers/generatedQuotes");
|
||||
await expect(generatedQuotes.delete("quote-1")).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user