Files
cozsweet-frontend-nextjs/src/lib/navigation/__tests__/subscription_exit.test.ts
T

196 lines
6.1 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { buildGlobalPageUrl } from "@/router/global-route-context";
import { getCharacterRoutes, ROUTES } from "@/router/routes";
import { consumePendingChatImageReturn } from "../chat_image_return_session";
import {
clearPendingChatUnlock,
peekPendingChatUnlock,
} from "../chat_unlock_session";
import {
consumeSubscriptionExitUrl,
consumeSubscriptionExplicitExitUrl,
getSubscriptionFallbackExitUrl,
peekSubscriptionExplicitExitUrl,
resolveSubscriptionSuccessExitUrl,
} from "../subscription_exit";
vi.mock("../chat_image_return_session", () => ({
consumePendingChatImageReturn: vi.fn(),
}));
vi.mock("../chat_unlock_session", () => ({
clearPendingChatUnlock: vi.fn(),
peekPendingChatUnlock: vi.fn(),
}));
const consumePendingChatImageReturnMock = vi.mocked(
consumePendingChatImageReturn,
);
const clearPendingChatUnlockMock = vi.mocked(clearPendingChatUnlock);
const peekPendingChatUnlockMock = vi.mocked(peekPendingChatUnlock);
describe("subscription exit helpers", () => {
beforeEach(() => {
consumePendingChatImageReturnMock.mockReset();
clearPendingChatUnlockMock.mockReset();
peekPendingChatUnlockMock.mockReset();
});
it("uses explicit chat image return urls first", async () => {
consumePendingChatImageReturnMock.mockResolvedValue({
reason: "image_paywall",
characterId: "elio",
messageId: "msg_1",
returnUrl: "/chat?image=msg_1",
createdAt: 1,
});
await expect(consumeSubscriptionExplicitExitUrl()).resolves.toBe(
"/chat?image=msg_1",
);
});
it("falls back to chat when requested", async () => {
consumePendingChatImageReturnMock.mockResolvedValue(null);
peekPendingChatUnlockMock.mockResolvedValue(null);
await expect(consumeSubscriptionExitUrl("chat")).resolves.toBe(
getCharacterRoutes("elio").chat,
);
});
it("falls back to sidebar by default", async () => {
consumePendingChatImageReturnMock.mockResolvedValue(null);
peekPendingChatUnlockMock.mockResolvedValue(null);
expect(getSubscriptionFallbackExitUrl(null)).toBe(ROUTES.sidebar);
await expect(consumeSubscriptionExitUrl(null)).resolves.toBe(
ROUTES.sidebar,
);
});
it("returns to global sidebar with the source character", async () => {
const expectedUrl = buildGlobalPageUrl(
ROUTES.sidebar,
getCharacterRoutes("maya").chat,
);
expect(getSubscriptionFallbackExitUrl("sidebar", "maya")).toBe(
expectedUrl,
);
await expect(
consumeSubscriptionExitUrl("sidebar", "maya"),
).resolves.toBe(expectedUrl);
await expect(
resolveSubscriptionSuccessExitUrl("sidebar", "maya"),
).resolves.toBe(expectedUrl);
expect(consumePendingChatImageReturnMock).not.toHaveBeenCalled();
expect(peekPendingChatUnlockMock).not.toHaveBeenCalled();
});
it("returns directly to private room without consuming chat state", async () => {
consumePendingChatImageReturnMock.mockResolvedValue({
reason: "image_paywall",
characterId: "elio",
messageId: "msg_1",
returnUrl: "/chat?image=msg_1",
createdAt: 1,
});
peekPendingChatUnlockMock.mockResolvedValue({
reason: "single_message_unlock",
characterId: "elio",
displayMessageId: "msg_1",
messageId: "msg_1",
kind: "image",
returnUrl: "/chat?image=msg_1",
stage: "payment",
createdAt: 1,
});
expect(getSubscriptionFallbackExitUrl("private-room")).toBe(
getCharacterRoutes("elio").privateRoom,
);
await expect(consumeSubscriptionExitUrl("private-room")).resolves.toBe(
getCharacterRoutes("elio").privateRoom,
);
expect(consumePendingChatImageReturnMock).not.toHaveBeenCalled();
expect(peekPendingChatUnlockMock).not.toHaveBeenCalled();
expect(clearPendingChatUnlockMock).not.toHaveBeenCalled();
});
it("keeps private room ahead of stale chat state after payment", async () => {
peekPendingChatUnlockMock.mockResolvedValue({
reason: "single_message_unlock",
characterId: "elio",
displayMessageId: "msg_1",
messageId: "msg_1",
kind: "image",
returnUrl: "/chat?image=msg_1",
stage: "payment",
createdAt: 1,
});
await expect(
resolveSubscriptionSuccessExitUrl("private-room", "maya"),
).resolves.toBe(getCharacterRoutes("maya").privateRoom);
expect(peekPendingChatUnlockMock).not.toHaveBeenCalled();
expect(clearPendingChatUnlockMock).not.toHaveBeenCalled();
});
it("keeps pending chat unlocks ahead of chat fallback after payment", async () => {
peekPendingChatUnlockMock.mockResolvedValue({
reason: "single_message_unlock",
characterId: "elio",
displayMessageId: "msg_1",
messageId: "msg_1",
kind: "image",
returnUrl: "/chat?image=msg_1",
stage: "payment",
createdAt: 1,
});
await expect(resolveSubscriptionSuccessExitUrl("chat")).resolves.toBe(
"/chat?image=msg_1",
);
expect(clearPendingChatUnlockMock).not.toHaveBeenCalled();
});
it("peeks chat unlock return urls without clearing them", async () => {
peekPendingChatUnlockMock.mockResolvedValue({
reason: "single_message_unlock",
characterId: "elio",
displayMessageId: "msg_1",
messageId: "msg_1",
kind: "image",
returnUrl: "/chat?image=msg_1",
stage: "payment",
createdAt: 1,
});
await expect(peekSubscriptionExplicitExitUrl()).resolves.toBe(
"/chat?image=msg_1",
);
expect(clearPendingChatUnlockMock).not.toHaveBeenCalled();
});
it("clears chat unlock return urls when consumed", async () => {
consumePendingChatImageReturnMock.mockResolvedValue(null);
peekPendingChatUnlockMock.mockResolvedValue({
reason: "single_message_unlock",
characterId: "elio",
displayMessageId: "msg_1",
messageId: "msg_1",
kind: "image",
returnUrl: "/chat?image=msg_1",
stage: "payment",
createdAt: 1,
});
await expect(consumeSubscriptionExplicitExitUrl()).resolves.toBe(
"/chat?image=msg_1",
);
expect(clearPendingChatUnlockMock).toHaveBeenCalledTimes(1);
});
});