JUNOHUB-test/src/app/api/dashboard/ai-analytics/route.test.ts
2026-05-12 00:30:41 +05:00

187 lines
7.3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest"
import { GET } from "./route"
vi.mock("@/auth", () => ({
auth: vi.fn(),
}))
vi.mock("@/lib/dashboard/vendorActiveStore", () => ({
getVendorActiveStore: vi.fn(),
}))
vi.mock("@/lib/prisma", () => ({
prisma: {
storefrontChatMessage: { findMany: vi.fn() },
aiAnalyticsEvent: { findMany: vi.fn() },
storefrontChatConversation: { findMany: vi.fn() },
cachedOrder: { findMany: vi.fn() },
chatMessage: { findMany: vi.fn() },
emailLog: { findMany: vi.fn() },
},
}))
beforeEach(() => {
vi.clearAllMocks()
})
describe("GET /api/dashboard/ai-analytics", () => {
it("returns business outcome analytics instead of technical model KPIs", async () => {
const { auth } = await import("@/auth")
const { getVendorActiveStore } = await import("@/lib/dashboard/vendorActiveStore")
const { prisma } = await import("@/lib/prisma")
vi.mocked(auth).mockResolvedValue({ user: { id: "user-1", role: "VENDOR" } } as never)
vi.mocked(getVendorActiveStore).mockResolvedValue({ id: "store-1", userId: "user-1" } as never)
vi.mocked(prisma.storefrontChatMessage.findMany).mockResolvedValue([
{
id: "m1",
senderType: "CUSTOMER",
createdAt: new Date("2026-05-01T10:00:00Z"),
content: "Do you have school shoes?",
conversationId: "conv-1",
actions: null,
},
{
id: "m2",
senderType: "AI",
createdAt: new Date("2026-05-01T10:00:03Z"),
content: "Yes, we have school shoes.",
conversationId: "conv-1",
actions: null,
},
] as never)
vi.mocked(prisma.aiAnalyticsEvent.findMany).mockResolvedValue([
{
id: "event-1",
conversationId: "conv-1",
messageId: "m1",
eventType: "customer_question",
source: "storefront_chat",
productId: null,
variantId: null,
quantity: null,
amount: null,
currency: null,
sentiment: "neutral",
resolutionStatus: null,
orderId: null,
attributionType: null,
metadata: null,
createdAt: new Date("2026-05-01T10:00:00Z"),
},
{
id: "event-2",
conversationId: "conv-1",
messageId: "m2",
eventType: "resolved_by_ai",
source: "storefront_chat",
productId: null,
variantId: null,
quantity: null,
amount: null,
currency: null,
sentiment: null,
resolutionStatus: "resolved_by_ai",
orderId: null,
attributionType: null,
metadata: null,
createdAt: new Date("2026-05-01T10:00:03Z"),
},
] as never)
vi.mocked(prisma.storefrontChatConversation.findMany).mockResolvedValue([
{
id: "conv-1",
visitorId: "visitor-1",
customerEmail: null,
boundShopifyOrderId: null,
createdAt: new Date("2026-05-01T09:59:00Z"),
updatedAt: new Date("2026-05-01T10:00:03Z"),
},
] as never)
vi.mocked(prisma.cachedOrder.findMany).mockResolvedValue([] as never)
vi.mocked(prisma.chatMessage.findMany).mockResolvedValue([] as never)
vi.mocked(prisma.emailLog.findMany).mockResolvedValue([] as never)
const res = await GET(new Request("http://localhost/api/dashboard/ai-analytics?range=30d"))
const json = await res.json()
expect(res.status).toBe(200)
expect(json.modelEvaluation).toBeUndefined()
expect(json.revenueImpact).toMatchObject({
directUpsellRevenue: 0,
aiAssistedRevenue: 0,
})
expect(json.customerExperience.sentimentMix.totals.neutral).toBe(1)
expect(json.automationOutcomes.questionsAnswered).toBe(1)
expect(Array.isArray(json.timeline)).toBe(true)
})
it("derives real metrics from existing messages when persisted analytics events are not backfilled yet", async () => {
const { auth } = await import("@/auth")
const { getVendorActiveStore } = await import("@/lib/dashboard/vendorActiveStore")
const { prisma } = await import("@/lib/prisma")
vi.mocked(auth).mockResolvedValue({ user: { id: "user-1", role: "VENDOR" } } as never)
vi.mocked(getVendorActiveStore).mockResolvedValue({ id: "store-1", userId: "user-1" } as never)
vi.mocked(prisma.storefrontChatMessage.findMany).mockResolvedValue([
{
id: "m1",
senderType: "CUSTOMER",
createdAt: new Date("2026-05-01T10:00:00Z"),
content: "Thank you, do you have sandals?",
conversationId: "conv-1",
actions: null,
},
{
id: "m2",
senderType: "AI",
createdAt: new Date("2026-05-01T10:00:03Z"),
content: "Yes, we have sandals available.",
conversationId: "conv-1",
actions: [
{
type: "upsell_suggestion",
label: "Add suggested item",
message: "Would you like socks too?",
product: { productId: "p2", title: "Socks", variantId: "v2", price: "5.00" },
},
],
},
] as never)
vi.mocked(prisma.aiAnalyticsEvent.findMany).mockResolvedValue([] as never)
vi.mocked(prisma.storefrontChatConversation.findMany).mockResolvedValue([
{
id: "conv-1",
visitorId: "visitor-1",
customerEmail: "buyer@example.com",
boundShopifyOrderId: "order-1",
createdAt: new Date("2026-05-01T09:59:00Z"),
updatedAt: new Date("2026-05-01T10:00:03Z"),
},
] as never)
vi.mocked(prisma.cachedOrder.findMany).mockResolvedValue([
{
shopifyOrderId: "order-1",
orderNumber: "#1001",
email: "buyer@example.com",
totalPrice: "30.00",
currency: "GBP",
lineItems: [],
shopifyCreatedAt: new Date("2026-05-01T10:10:00Z"),
createdAt: new Date("2026-05-01T10:10:00Z"),
},
] as never)
vi.mocked(prisma.chatMessage.findMany).mockResolvedValue([] as never)
vi.mocked(prisma.emailLog.findMany).mockResolvedValue([] as never)
const res = await GET(new Request("http://localhost/api/dashboard/ai-analytics?range=30d"))
const json = await res.json()
expect(res.status).toBe(200)
expect(json.customerExperience.sentimentMix.totals.positive).toBe(1)
expect(json.automationOutcomes.resolvedByAi).toBe(1)
expect(json.revenueImpact.aiAssistedRevenue).toBe(30)
expect(json.technicalDiagnostics.derivedAnalyticsEvents).toBeGreaterThan(0)
})
})