import type { Page } from "@playwright/test"; import { apiEnvelope } from "../data/common"; import { chatSendResponse, createChatSendResponse, createPaidImageHistoryResponse, createWeeklyLimitChatSendResponse, emptyChatHistoryResponse, emptyChatPreviewsResponse, insufficientCreditsUnlockImageResponse, insufficientCreditsUnlockVoiceResponse, paidImageChatSendResponse, paidImageUnlockHistoryResponse, paidVoiceHistoryResponse } from "../data/chat"; export interface ChatMockOptions { chatLimitTriggerAt?: number; chatSendTokenRefreshFlow: boolean; paidImageFlow: boolean; paidImageInsufficientCreditsFlow: boolean; paidVoiceInsufficientCreditsFlow: boolean; } export interface ChatMockState { hasExpiredChatSend: boolean; paidImageRequested: boolean; paidImageUnlocked: boolean; } export async function registerChatMocks(page: Page, options: ChatMockOptions, state: ChatMockState) { let sendCount = 0; await page.route("**/api/chat/opening-message", async (route) => { const body = route.request().postDataJSON() as { openingMessage?: unknown; } | undefined; await route.fulfill({ json: apiEnvelope({ messageId: "mock-opening-message", created: true, openingMessage: typeof body?.openingMessage === "string" ? body.openingMessage : "Hello", createdAt: "2026-07-23T00:00:00Z", }), }); }); await page.route("**/api/chat/history**", async (route) => { const response = options.paidVoiceInsufficientCreditsFlow ? paidVoiceHistoryResponse : options.paidImageInsufficientCreditsFlow ? createPaidImageHistoryResponse(false) : options.paidImageFlow && state.paidImageRequested ? createPaidImageHistoryResponse(state.paidImageUnlocked) : emptyChatHistoryResponse; await route.fulfill({ json: apiEnvelope(response) }); }); await page.route("**/api/chat/previews", async (route) => { await route.fulfill({ json: apiEnvelope(emptyChatPreviewsResponse) }); }); await page.route("**/api/chat/send", async (route) => { sendCount += 1; const requestBody = route.request().postDataJSON() as { message?: unknown } | undefined; const message = typeof requestBody?.message === "string" ? requestBody.message : ""; if (options.chatSendTokenRefreshFlow && !state.hasExpiredChatSend) { state.hasExpiredChatSend = true; await route.fulfill({ status: 401, json: { message: "expired" } }); return; } const response = message.includes("discount offer test") ? { ...chatSendResponse, reply: "I know you're thinking carefully about the price.", messageId: "discount-action-message", commercialAction: { actionId: "13ec8a10-58d7-4d24-b66b-8db5699a1aa8", type: "discountOffer", copy: "Want me to ask for my best private offer?", ctaLabel: "Yes, ask for me", target: "discountConsent", ruleId: "discount_after_price_objection", }, } : message.includes("private album offer test") ? { ...chatSendResponse, reply: "You always know how to make me smile.", messageId: "commercial-action-message", commercialAction: { actionId: "commercial-action-1", type: "privateAlbumOffer", copy: "There are more private photos waiting for you.", ctaLabel: "Open private zone", target: "privateZone", ruleId: "private_album_after_appearance_praise", }, } : options.paidImageFlow && message.includes("给我发图片") ? paidImageChatSendResponse : options.chatLimitTriggerAt != null && sendCount >= options.chatLimitTriggerAt ? createWeeklyLimitChatSendResponse(sendCount, options.chatLimitTriggerAt) : options.chatLimitTriggerAt != null ? createChatSendResponse(sendCount) : chatSendResponse; if (options.paidImageFlow && message.includes("给我发图片")) { state.paidImageRequested = true; state.paidImageUnlocked = false; } await route.fulfill({ json: apiEnvelope(response) }); }); await page.route("**/api/chat/unlock-history", async (route) => { if (options.paidImageFlow && state.paidImageRequested) state.paidImageUnlocked = true; const response = options.paidImageFlow && state.paidImageRequested ? paidImageUnlockHistoryResponse : { unlocked: true, reason: "no_locked_messages", totalLocked: 0, unlockedCount: 0, privateCount: 0, imageCount: 0, voiceCount: 0, requiredCredits: 0, currentCredits: 0, remainingCredits: 0, shortfallCredits: 0, costsByMessage: {}, messageIds: [] }; await route.fulfill({ json: apiEnvelope(response) }); }); await page.route("**/api/chat/unlock-private", async (route) => { const response = options.paidVoiceInsufficientCreditsFlow ? insufficientCreditsUnlockVoiceResponse : options.paidImageInsufficientCreditsFlow || options.paidImageFlow && state.paidImageRequested && !state.paidImageUnlocked ? insufficientCreditsUnlockImageResponse : { unlocked: true, content: "Unlocked message", reason: "ok", creditBalance: 100, creditsCharged: 0, requiredCredits: 0, shortfallCredits: 0, lockDetail: { locked: false, showContent: true, showUpgrade: false, reason: null, hint: null, detail: null } }; await route.fulfill({ json: apiEnvelope(response) }); }); }