121 lines
5.4 KiB
TypeScript
121 lines
5.4 KiB
TypeScript
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) });
|
|
});
|
|
}
|