feat(external-entry): define Facebook business links
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
# 外部入口接入说明
|
||||
|
||||
外部应用通过 `/external-entry` 进入本应用。
|
||||
Facebook 等外部应用统一通过 `/external-entry` 进入本应用。入口先保存外部身份和短期业务意图,再跳转到对应角色页面并清理地址栏中的入口查询参数。
|
||||
|
||||
| 环境 | APP HOST |
|
||||
| --- | --- |
|
||||
| 测试环境 | `frontend-test.banlv-ai.com` |
|
||||
| 正式环境 | `cozsweet.com` |
|
||||
| 预发环境 `pre` | `frontend-test.banlv-ai.com` |
|
||||
| 正式环境 `production` | `cozsweet.com` |
|
||||
|
||||
入口格式:
|
||||
|
||||
@@ -13,85 +13,78 @@
|
||||
https://<APP_HOST>/external-entry?<参数>
|
||||
```
|
||||
|
||||
## 可选参数
|
||||
## 当前 Facebook 业务入口
|
||||
|
||||
所有参数均为可选参数。
|
||||
当前正式投放固定为 4 类入口乘以 3 个角色,共 12 条链接。Facebook 业务名称与应用页面的对应关系如下:
|
||||
|
||||
| Facebook 入口 | 参数组合 | 落地行为 |
|
||||
| --- | --- | --- |
|
||||
| 私密空间 | `target=chat` | 进入普通聊天页,不插入任何锁定内容。 |
|
||||
| 语音 | `target=chat&mode=promotion&promotion_type=voice` | 进入聊天页,并插入一条待解锁语音。 |
|
||||
| 图片包 | `target=private-zone` | 进入角色的 `private-zone` 图片空间。 |
|
||||
| 帮忙买咖啡 | `target=tip` | 进入角色的咖啡打赏页。 |
|
||||
|
||||
每条正式链接还必须显式传入:
|
||||
|
||||
- `character=elio`、`character=maya` 或 `character=nayeli`;
|
||||
- `psid=<PSID>`,其中 `<PSID>` 必须由 Facebook 发送端替换为当前用户经过 URL 编码的 Page-scoped User ID。
|
||||
|
||||
完整的 12 条正式发送模板见 [Facebook 12 条外部入口链接清单](./links.md)。
|
||||
|
||||
## 参数说明
|
||||
|
||||
| 参数 | 可选值或格式 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| `target` | `chat`、`tip`、`private-zone` | 指定进入的页面;不传或值无效时进入聊天页。历史值 `private-room` 兼容进入 `private-zone`,新链接不得继续使用。 |
|
||||
| `character` | `elio`、`maya`、`nayeli` | 指定角色;不传或值无效时使用 Elio。 |
|
||||
| `psid` | string | 传入 Facebook Page-scoped User ID。 |
|
||||
| `target` | `chat`、`tip`、`private-zone` | 指定进入的页面;不传或值无效时进入聊天页。 |
|
||||
| `character` | `elio`、`maya`、`nayeli` | 指定角色;不传或值无效时使用 Elio。正式投放链接必须显式传入。 |
|
||||
| `psid` | string | Facebook Page-scoped User ID。Facebook 正式投放链接必须动态传入。 |
|
||||
| `mode` | `promotion` | 开启聊天促销模式。 |
|
||||
| `promotion_type` | `voice`、`image`、`private` | 指定促销消息类型,仅与 `mode=promotion` 一起使用。 |
|
||||
|
||||
`mode=promotion` 只有在 `target=chat` 且同时提供有效的 `promotion_type` 时生效。
|
||||
`mode=promotion` 只有在 `target=chat` 且同时提供有效的 `promotion_type` 时生效。当前 12 条正式投放链接只使用 `promotion_type=voice`;`image` 和 `private` 保留为兼容能力,不属于本次业务清单。
|
||||
|
||||
标准私密空间参数是 `target=private-zone`。前端仅为已经发布的旧链接保留 `target=private-room` 兼容;`private_zone` 等其他拼写仍按无效目标处理并回退聊天页。
|
||||
## 四类发送模板
|
||||
|
||||
发送前必须把 `<CHARACTER>` 替换为角色值,把 `<PSID>` 替换为当前 Facebook 用户的真实 PSID;不得把占位符原样发送给用户。
|
||||
|
||||
私密空间(普通聊天):
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=chat&character=<CHARACTER>&psid=<PSID>
|
||||
```
|
||||
|
||||
语音:
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=chat&character=<CHARACTER>&mode=promotion&promotion_type=voice&psid=<PSID>
|
||||
```
|
||||
|
||||
图片包:
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=private-zone&character=<CHARACTER>&psid=<PSID>
|
||||
```
|
||||
|
||||
帮忙买咖啡:
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=tip&character=<CHARACTER>&psid=<PSID>
|
||||
```
|
||||
|
||||
## PSID 与登录状态流程
|
||||
|
||||
PSID 保存、登录状态判断和 Facebook Identity 绑定逻辑见 [PSID 与登录状态流程图](./psid-login-flow.md)。
|
||||
`psid` 可以和任意有效入口参数组合使用。应用会先保存 PSID,再执行登录状态判断和 Facebook Identity 绑定。详细流程见 [PSID 与登录状态流程图](./psid-login-flow.md)。
|
||||
|
||||
## 使用示例
|
||||
所有参数值都必须进行 URL 编码。入口中禁止传递登录 Token、Page Access Token、App Secret 或其他秘密。
|
||||
|
||||
普通聊天:
|
||||
## 兼容说明
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=chat
|
||||
```
|
||||
- 历史参数 `target=private-room` 继续兼容进入 `private-zone`,但不得用于新投放链接。
|
||||
- `private_zone` 等其他拼写仍按无效目标处理并回退聊天页。
|
||||
- `promotion_type=image` 和 `promotion_type=private` 继续由现有代码支持,但不计入当前 12 条 Facebook 业务链接。
|
||||
- 无效或缺失的 `character` 继续回退 Elio;正式投放模板始终显式传入角色,不能依赖回退。
|
||||
|
||||
进入 Maya 聊天:
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=chat&character=maya
|
||||
```
|
||||
|
||||
携带 PSID 进入聊天,示例 PSID 为 `27511427698460020`:
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=chat&psid=27511427698460020
|
||||
```
|
||||
|
||||
语音促销聊天:
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=chat&mode=promotion&promotion_type=voice
|
||||
```
|
||||
|
||||
图片促销聊天:
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=chat&mode=promotion&promotion_type=image
|
||||
```
|
||||
|
||||
私密文本促销聊天:
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=chat&mode=promotion&promotion_type=private
|
||||
```
|
||||
|
||||
咖啡打赏:
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=tip
|
||||
```
|
||||
|
||||
私密空间:
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=private-zone
|
||||
```
|
||||
|
||||
进入 Nayeli 私密空间:
|
||||
|
||||
```text
|
||||
https://<APP_HOST>/external-entry?target=private-zone&character=nayeli
|
||||
```
|
||||
|
||||
`psid` 可以与任意 `target` 或聊天促销参数组合使用。参数值需要进行 URL 编码,不要在入口中传递登录 Token、Page Access Token 或 App Secret。
|
||||
|
||||
完整可点击示例:
|
||||
## 调试与正式清单
|
||||
|
||||
- [本地调试链接清单](./debug-links.md)
|
||||
- [测试环境和正式环境链接清单](./links.md)
|
||||
- [Facebook 12 条外部入口链接清单](./links.md)
|
||||
|
||||
@@ -1,66 +1,52 @@
|
||||
# 外部入口链接清单
|
||||
# Facebook 12 条外部入口链接清单
|
||||
|
||||
这份清单可以独立发送和直接点击。所有新链接统一使用标准参数 `target=private-zone`;历史参数 `target=private-room` 仅用于兼容已经发出的旧链接。
|
||||
本清单是 Facebook 正式投放的唯一业务链接清单,共 4 类入口乘以 3 个角色,合计 12 条模板。
|
||||
|
||||
## 使用规则
|
||||
## 发送规则
|
||||
|
||||
- `target=chat`:进入聊天页。
|
||||
- `target=tip`:进入对应角色的打赏页。
|
||||
- `target=private-zone`:进入对应角色的私密空间。
|
||||
- `character` 可使用 `elio`、`maya`、`nayeli`;不传或值无效时使用 Elio。
|
||||
- `mode=promotion` 只在 `target=chat` 且 `promotion_type` 为 `voice`、`image` 或 `private` 时生效。
|
||||
- PSID 示例统一使用 `27511427698460020`;不要在入口中放登录 Token、Page Access Token 或 App Secret。
|
||||
- 发送前必须把每条模板中的 `<PSID>` 替换为当前用户经过 URL 编码的 Facebook Page-scoped User ID;不得把占位符原样发送。
|
||||
- 每条链接都显式传入 `character`,不得依赖缺省角色回退。
|
||||
- Facebook 业务名称“私密空间”进入普通聊天页;业务名称“图片包”才进入应用的 `private-zone`。
|
||||
- 不要在入口中传递登录 Token、Page Access Token、App Secret 或其他秘密。
|
||||
- 以下模板包含占位符,替换真实 PSID 后才能直接发送或点击。
|
||||
|
||||
## 测试环境
|
||||
## 正式投放模板
|
||||
|
||||
| 入口 | 可点击链接 | 预期落地页面 |
|
||||
| --- | --- | --- |
|
||||
| 普通聊天(Elio) | [打开普通聊天](https://frontend-test.banlv-ai.com/external-entry?target=chat) | `/characters/elio/chat` |
|
||||
| Maya 聊天 | [打开 Maya 聊天](https://frontend-test.banlv-ai.com/external-entry?target=chat&character=maya) | `/characters/maya/chat` |
|
||||
| Nayeli 聊天 | [打开 Nayeli 聊天](https://frontend-test.banlv-ai.com/external-entry?target=chat&character=nayeli) | `/characters/nayeli/chat` |
|
||||
| 携带 PSID 的 Elio 聊天 | [打开 PSID 聊天示例](https://frontend-test.banlv-ai.com/external-entry?target=chat&psid=27511427698460020) | `/characters/elio/chat` |
|
||||
| 语音促销 | [打开语音促销](https://frontend-test.banlv-ai.com/external-entry?target=chat&mode=promotion&promotion_type=voice) | `/characters/elio/chat`,保存语音促销意图 |
|
||||
| 图片促销 | [打开图片促销](https://frontend-test.banlv-ai.com/external-entry?target=chat&mode=promotion&promotion_type=image) | `/characters/elio/chat`,保存图片促销意图 |
|
||||
| 私密文本促销 | [打开私密文本促销](https://frontend-test.banlv-ai.com/external-entry?target=chat&mode=promotion&promotion_type=private) | `/characters/elio/chat`,保存私密文本促销意图 |
|
||||
| Elio 咖啡打赏 | [打开 Elio 咖啡打赏](https://frontend-test.banlv-ai.com/external-entry?target=tip&character=elio) | `/characters/elio/tip` |
|
||||
| Maya 咖啡打赏 | [打开 Maya 咖啡打赏](https://frontend-test.banlv-ai.com/external-entry?target=tip&character=maya) | `/characters/maya/tip` |
|
||||
| Nayeli 咖啡打赏 | [打开 Nayeli 咖啡打赏](https://frontend-test.banlv-ai.com/external-entry?target=tip&character=nayeli) | `/characters/nayeli/tip` |
|
||||
| Elio 私密空间 | [打开 Elio 私密空间](https://frontend-test.banlv-ai.com/external-entry?target=private-zone&character=elio) | `/characters/elio/private-zone` |
|
||||
| Maya 私密空间 | [打开 Maya 私密空间](https://frontend-test.banlv-ai.com/external-entry?target=private-zone&character=maya) | `/characters/maya/private-zone` |
|
||||
| Nayeli 私密空间 | [打开 Nayeli 私密空间](https://frontend-test.banlv-ai.com/external-entry?target=private-zone&character=nayeli) | `/characters/nayeli/private-zone` |
|
||||
| 编号 | Facebook 入口 | 角色 | 发送模板 | 预期落地与页面状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | 私密空间 | Elio | `https://cozsweet.com/external-entry?target=chat&character=elio&psid=<PSID>` | `/characters/elio/chat`,不显示促销锁卡 |
|
||||
| 2 | 私密空间 | Maya | `https://cozsweet.com/external-entry?target=chat&character=maya&psid=<PSID>` | `/characters/maya/chat`,不显示促销锁卡 |
|
||||
| 3 | 私密空间 | Nayeli | `https://cozsweet.com/external-entry?target=chat&character=nayeli&psid=<PSID>` | `/characters/nayeli/chat`,不显示促销锁卡 |
|
||||
| 4 | 语音 | Elio | `https://cozsweet.com/external-entry?target=chat&character=elio&mode=promotion&promotion_type=voice&psid=<PSID>` | `/characters/elio/chat`,显示一条锁定语音 |
|
||||
| 5 | 语音 | Maya | `https://cozsweet.com/external-entry?target=chat&character=maya&mode=promotion&promotion_type=voice&psid=<PSID>` | `/characters/maya/chat`,显示一条锁定语音 |
|
||||
| 6 | 语音 | Nayeli | `https://cozsweet.com/external-entry?target=chat&character=nayeli&mode=promotion&promotion_type=voice&psid=<PSID>` | `/characters/nayeli/chat`,显示一条锁定语音 |
|
||||
| 7 | 图片包 | Elio | `https://cozsweet.com/external-entry?target=private-zone&character=elio&psid=<PSID>` | `/characters/elio/private-zone` |
|
||||
| 8 | 图片包 | Maya | `https://cozsweet.com/external-entry?target=private-zone&character=maya&psid=<PSID>` | `/characters/maya/private-zone` |
|
||||
| 9 | 图片包 | Nayeli | `https://cozsweet.com/external-entry?target=private-zone&character=nayeli&psid=<PSID>` | `/characters/nayeli/private-zone` |
|
||||
| 10 | 帮忙买咖啡 | Elio | `https://cozsweet.com/external-entry?target=tip&character=elio&psid=<PSID>` | `/characters/elio/tip` |
|
||||
| 11 | 帮忙买咖啡 | Maya | `https://cozsweet.com/external-entry?target=tip&character=maya&psid=<PSID>` | `/characters/maya/tip` |
|
||||
| 12 | 帮忙买咖啡 | Nayeli | `https://cozsweet.com/external-entry?target=tip&character=nayeli&psid=<PSID>` | `/characters/nayeli/tip` |
|
||||
|
||||
## 旧链接兼容验证
|
||||
## 预发验证
|
||||
|
||||
以下链接仅用于验证已经发出的 `private-room` 旧链接仍能正确进入私密空间。新投放链接不要再使用这些地址。
|
||||
预发验证不维护第二套业务清单。将上述 12 条模板的主机统一替换为 `frontend-test.banlv-ai.com`,其余路径和参数保持不变:
|
||||
|
||||
| 旧入口 | 可点击链接 | 预期落地页面 |
|
||||
| --- | --- | --- |
|
||||
| Elio 旧私密空间 | [验证 Elio 旧链接](https://frontend-test.banlv-ai.com/external-entry?target=private-room&character=elio) | `/characters/elio/private-zone` |
|
||||
| Maya 旧私密空间 | [验证 Maya 旧链接](https://frontend-test.banlv-ai.com/external-entry?target=private-room&character=maya) | `/characters/maya/private-zone` |
|
||||
| Nayeli 旧私密空间 | [验证 Nayeli 旧链接](https://frontend-test.banlv-ai.com/external-entry?target=private-room&character=nayeli) | `/characters/nayeli/private-zone` |
|
||||
```text
|
||||
https://frontend-test.banlv-ai.com/external-entry?<与正式模板相同的查询参数>
|
||||
```
|
||||
|
||||
## 正式环境
|
||||
|
||||
正式环境仅列标准链接,不再传播 `private-room` 旧参数。
|
||||
|
||||
| 入口 | 可点击链接 | 预期落地页面 |
|
||||
| --- | --- | --- |
|
||||
| 普通聊天(Elio) | [打开普通聊天](https://cozsweet.com/external-entry?target=chat) | `/characters/elio/chat` |
|
||||
| Maya 聊天 | [打开 Maya 聊天](https://cozsweet.com/external-entry?target=chat&character=maya) | `/characters/maya/chat` |
|
||||
| Nayeli 聊天 | [打开 Nayeli 聊天](https://cozsweet.com/external-entry?target=chat&character=nayeli) | `/characters/nayeli/chat` |
|
||||
| 携带 PSID 的 Elio 聊天 | [打开 PSID 聊天示例](https://cozsweet.com/external-entry?target=chat&psid=27511427698460020) | `/characters/elio/chat` |
|
||||
| 语音促销 | [打开语音促销](https://cozsweet.com/external-entry?target=chat&mode=promotion&promotion_type=voice) | `/characters/elio/chat`,保存语音促销意图 |
|
||||
| 图片促销 | [打开图片促销](https://cozsweet.com/external-entry?target=chat&mode=promotion&promotion_type=image) | `/characters/elio/chat`,保存图片促销意图 |
|
||||
| 私密文本促销 | [打开私密文本促销](https://cozsweet.com/external-entry?target=chat&mode=promotion&promotion_type=private) | `/characters/elio/chat`,保存私密文本促销意图 |
|
||||
| Elio 咖啡打赏 | [打开 Elio 咖啡打赏](https://cozsweet.com/external-entry?target=tip&character=elio) | `/characters/elio/tip` |
|
||||
| Maya 咖啡打赏 | [打开 Maya 咖啡打赏](https://cozsweet.com/external-entry?target=tip&character=maya) | `/characters/maya/tip` |
|
||||
| Nayeli 咖啡打赏 | [打开 Nayeli 咖啡打赏](https://cozsweet.com/external-entry?target=tip&character=nayeli) | `/characters/nayeli/tip` |
|
||||
| Elio 私密空间 | [打开 Elio 私密空间](https://cozsweet.com/external-entry?target=private-zone&character=elio) | `/characters/elio/private-zone` |
|
||||
| Maya 私密空间 | [打开 Maya 私密空间](https://cozsweet.com/external-entry?target=private-zone&character=maya) | `/characters/maya/private-zone` |
|
||||
| Nayeli 私密空间 | [打开 Nayeli 私密空间](https://cozsweet.com/external-entry?target=private-zone&character=nayeli) | `/characters/nayeli/private-zone` |
|
||||
预发环境是 `pre`,不是正式环境。预发验证通过不代表已经更新 `cozsweet.com` 或获得生产发布批准。
|
||||
|
||||
## 验收判断
|
||||
|
||||
打开入口后,浏览器地址最终应与“预期落地页面”一致。普通聊天和私密空间必须分别落在 `/chat` 与 `/private-zone`,不能再进入同一个页面。
|
||||
- 最终浏览器地址必须是表格中的预期页面,且不再保留 `/external-entry` 的查询参数。
|
||||
- `<PSID>` 替换后的真实值必须由应用保存。
|
||||
- “私密空间”只进入普通聊天,不得出现锁定语音、锁定图片或私密文本促销卡。
|
||||
- “语音”进入聊天后必须只注入一条锁定语音。
|
||||
- “图片包”和“帮忙买咖啡”必须分别进入对应角色的 `private-zone` 与 `tip`,不能串到其他角色。
|
||||
|
||||
如需在其他入口携带 PSID,在已有查询参数的链接末尾追加 `&psid=27511427698460020`。
|
||||
## 不属于当前 12 条清单的兼容能力
|
||||
|
||||
- 历史 `target=private-room` 继续兼容,但不得创建新的投放链接。
|
||||
- `promotion_type=image`、`promotion_type=private` 和独立 PSID 示例不属于当前正式业务清单。
|
||||
- 兼容能力的保留不改变本页“只有 12 条正式投放模板”的口径。
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -9,6 +9,21 @@ import {
|
||||
shouldBindExternalFacebookIdentity,
|
||||
} from "../external_entry";
|
||||
|
||||
const facebookBusinessEntryCases = [
|
||||
["private-space", "elio", "chat", null, null, getCharacterRoutes("elio").chat, null],
|
||||
["private-space", "maya", "chat", null, null, getCharacterRoutes("maya").chat, null],
|
||||
["private-space", "nayeli", "chat", null, null, getCharacterRoutes("nayeli").chat, null],
|
||||
["voice", "elio", "chat", "promotion", "voice", getCharacterRoutes("elio").chat, "voice"],
|
||||
["voice", "maya", "chat", "promotion", "voice", getCharacterRoutes("maya").chat, "voice"],
|
||||
["voice", "nayeli", "chat", "promotion", "voice", getCharacterRoutes("nayeli").chat, "voice"],
|
||||
["image-pack", "elio", "private-zone", null, null, getCharacterRoutes("elio").privateZone, null],
|
||||
["image-pack", "maya", "private-zone", null, null, getCharacterRoutes("maya").privateZone, null],
|
||||
["image-pack", "nayeli", "private-zone", null, null, getCharacterRoutes("nayeli").privateZone, null],
|
||||
["coffee", "elio", "tip", null, null, getCharacterRoutes("elio").tip, null],
|
||||
["coffee", "maya", "tip", null, null, getCharacterRoutes("maya").tip, null],
|
||||
["coffee", "nayeli", "tip", null, null, getCharacterRoutes("nayeli").tip, null],
|
||||
] as const;
|
||||
|
||||
describe("external entry navigation", () => {
|
||||
it("defaults to chat", () => {
|
||||
expect(resolveExternalEntryTarget({})).toBe(ROUTES.chat);
|
||||
@@ -62,6 +77,26 @@ describe("external entry navigation", () => {
|
||||
).toBe(getCharacterRoutes("elio").privateZone);
|
||||
});
|
||||
|
||||
it.each(facebookBusinessEntryCases)(
|
||||
"routes Facebook business entry %s for %s",
|
||||
(
|
||||
_entry,
|
||||
character,
|
||||
target,
|
||||
mode,
|
||||
promotionType,
|
||||
expectedDestination,
|
||||
expectedPromotionType,
|
||||
) => {
|
||||
expect(resolveExternalEntryDestination({ target, character })).toBe(
|
||||
expectedDestination,
|
||||
);
|
||||
expect(
|
||||
resolveExternalEntryPromotionType({ mode, promotionType }),
|
||||
).toBe(expectedPromotionType);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["chat", "elio", getCharacterRoutes("elio").chat],
|
||||
["chat", "maya", getCharacterRoutes("maya").chat],
|
||||
|
||||
Reference in New Issue
Block a user