feat(external-entry): define Facebook business links

This commit is contained in:
Codex
2026-07-28 14:53:58 +08:00
parent cab2ba868f
commit fcb2492118
4 changed files with 336 additions and 121 deletions
@@ -0,0 +1,201 @@
import { expect, test, type Page } from "@playwright/test";
import { mockCoreApis } from "@e2e/fixtures/api-mocks";
import { apiEnvelope } from "@e2e/fixtures/data/common";
import { clearBrowserState } from "@e2e/fixtures/test-helpers";
const testPsid = "e2e-facebook-psid-27511427698460020";
const characters = [
{
id: "elio",
slug: "elio",
displayName: "Elio Silvestri",
shortName: "Elio",
},
{
id: "maya-tan",
slug: "maya",
displayName: "Maya Tan",
shortName: "Maya",
},
{
id: "nayeli-cervantes",
slug: "nayeli",
displayName: "Nayeli Cervantes",
shortName: "Nayeli",
},
] as const;
type Character = (typeof characters)[number];
type EntryKind = "private-space" | "voice" | "image-pack" | "coffee";
interface FacebookEntryCase {
character: Character;
kind: EntryKind;
target: "chat" | "private-zone" | "tip";
expectedPath: string;
promotionType?: "voice";
}
const entryCases: FacebookEntryCase[] = characters.flatMap((character) => [
{
character,
kind: "private-space",
target: "chat",
expectedPath: `/characters/${character.slug}/chat`,
},
{
character,
kind: "voice",
target: "chat",
expectedPath: `/characters/${character.slug}/chat`,
promotionType: "voice",
},
{
character,
kind: "image-pack",
target: "private-zone",
expectedPath: `/characters/${character.slug}/private-zone`,
},
{
character,
kind: "coffee",
target: "tip",
expectedPath: `/characters/${character.slug}/tip`,
},
]);
test.beforeEach(async ({ baseURL, context, page }) => {
await clearBrowserState(context, page, baseURL);
await mockCoreApis(page);
await registerEntryPageMocks(page);
});
for (const entryCase of entryCases) {
test(`${entryCase.character.displayName} opens Facebook ${entryCase.kind} entry`, async ({
page,
}) => {
await page.goto(buildEntryUrl(entryCase));
await expect(page).toHaveURL(entryCase.expectedPath, { timeout: 20_000 });
await expect
.poll(() =>
page.evaluate(() => localStorage.getItem("cozsweet:psid")),
)
.toBe(testPsid);
if (entryCase.kind === "private-space") {
await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible();
await expect(
page.getByRole("button", { name: "Unlock voice message" }),
).toHaveCount(0);
return;
}
if (entryCase.kind === "voice") {
await expect(page.getByRole("textbox", { name: "Message" })).toBeVisible();
await expect(
page.getByRole("button", { name: "Unlock voice message" }),
).toHaveCount(1);
return;
}
if (entryCase.kind === "image-pack") {
await expect(
page.getByRole("heading", { name: "Private albums" }),
).toBeVisible();
await expect(
page.getByText(entryCase.character.displayName).last(),
).toBeVisible();
return;
}
await expect(
page.getByRole("heading", {
name: `Buy ${entryCase.character.shortName} a coffee`,
}),
).toBeVisible();
});
}
function buildEntryUrl(entryCase: FacebookEntryCase): string {
const params = new URLSearchParams({
target: entryCase.target,
character: entryCase.character.slug,
psid: testPsid,
});
if (entryCase.promotionType) {
params.set("mode", "promotion");
params.set("promotion_type", entryCase.promotionType);
}
return `/external-entry?${params.toString()}`;
}
async function registerEntryPageMocks(page: Page): Promise<void> {
await page.route("**/api/private-zone/posts**", async (route) => {
const url = new URL(route.request().url());
const characterId = url.searchParams.get("characterId") ?? "elio";
await route.fulfill({
json: apiEnvelope({
characterId,
items: [],
nextCursor: null,
hasMore: false,
creditBalance: 1000,
currency: "credits",
}),
});
});
await page.route("**/api/private-zone/albums**", async (route) => {
await route.fulfill({
json: apiEnvelope({
creditBalance: 1000,
pendingImageCount: 0,
hasIncompleteContent: false,
items: [],
}),
});
});
await page.route("**/api/payment/gift-products**", async (route) => {
const url = new URL(route.request().url());
const characterId = url.searchParams.get("characterId") ?? "elio";
const planPrefix =
characterId === "elio"
? "tip"
: `tip_${characterId.replaceAll("-", "_")}`;
await route.fulfill({
json: apiEnvelope({
characterId,
categories: [
{
category: "coffee",
name: "Coffee",
productCount: 1,
imageUrl: null,
},
],
plans: [
{
planId: `${planPrefix}_coffee_usd_4_99`,
planName: "Small Coffee",
orderType: "tip",
tipType: "coffee_small",
category: "coffee",
characterId,
description: "A character-scoped coffee gift",
imageUrl: null,
amountCents: 499,
currency: "USD",
autoRenew: false,
isFirstRechargeOffer: false,
firstRechargeDiscountPercent: 0,
promotionType: null,
},
],
}),
});
});
}