import { describe, expect, it } from "vitest"; import { PrivateAlbumUnlockResponseSchema, PrivateAlbumsResponseSchema, UnlockPrivateAlbumRequestSchema, } from "@/data/schemas/private-zone"; import { ApiPath } from "@/data/services/api"; const ALBUM_ID = "a1b2c3d4-0000-0000-0000-000000000000"; const COVER_URL = "https://dbapi.banlv-ai.com/storage/v1/object/public/elio-schedules/01.jpg"; const lockedAlbum = { albumId: ALBUM_ID, momentId: `album:${ALBUM_ID}`, characterId: "elio", collectionKey: "manila_202607", title: "Private Manila set", content: null, previewText: "Only for you.", imageCount: 8, mediaCount: 8, images: [{ url: COVER_URL, type: "image", locked: true, index: 0 }], locked: true, unlocked: false, unlockCost: 320, requiredCredits: 320, creditCostPerImage: 40, currency: "credits", canUnlockWithCredits: false, publishedAt: "2026-07-13T00:00:00+00:00", lockDetail: { locked: true, showContent: false, showUpgrade: true, reason: "private_album", }, }; describe("Private Album schema models", () => { it("keeps shared payment guidance on an insufficient-credit response", () => { const response = PrivateAlbumUnlockResponseSchema.parse({ albumId: ALBUM_ID, reason: "insufficient_credits", paymentGuidance: { guidanceId: "019c8f8d-17d3-7a42-b1cc-b6927b1927d5", characterId: "elio", characterName: "Elio Silvestri", scene: "privateZoneLocked", mode: "guide", title: "Keep talking with Elio", copy: "Baby, this album needs credits.", ctaLabel: "View VIP & credit options", purchaseOptions: ["vip", "topup"], target: "subscription", ruleId: "payment_guidance_privateZoneLocked", }, }); expect(response.paymentGuidance?.characterId).toBe("elio"); }); it("keeps a locked album cover URL while preserving the lock state", () => { const response = PrivateAlbumsResponseSchema.parse({ items: [lockedAlbum], creditBalance: 100, nextCursor: null, hasMore: false, }); expect(response.items[0]?.locked).toBe(true); expect(response.items[0]?.unlocked).toBe(false); expect(response.items[0]?.images[0]?.url).toBe(COVER_URL); expect(response.items[0]?.lockDetail).toEqual({ locked: true }); }); it("strips response fields that are not used by the frontend", () => { const response = PrivateAlbumsResponseSchema.parse({ items: [lockedAlbum], creditBalance: 100, packageOptions: [{ imageCount: 8, creditCost: 320 }], }); const albumJson = response.items[0]; expect(albumJson).not.toHaveProperty("momentId"); expect(albumJson).not.toHaveProperty("characterId"); expect(albumJson).not.toHaveProperty("collectionKey"); expect(response).not.toHaveProperty("packageOptions"); }); it("keeps incomplete-content metadata without exposing pending images", () => { const response = PrivateAlbumsResponseSchema.parse({ items: [], creditBalance: 100, pendingImageCount: 7, hasIncompleteContent: true, pendingImages: [{ url: COVER_URL }], }); expect(response.pendingImageCount).toBe(7); expect(response.hasIncompleteContent).toBe(true); expect(response).not.toHaveProperty("pendingImages"); }); it("parses an unlock response with all album images", () => { const response = PrivateAlbumUnlockResponseSchema.parse({ albumId: ALBUM_ID, locked: false, unlocked: true, reason: "ok", unlockCost: 320, creditBalance: 180, images: Array.from({ length: 8 }, (_, index) => ({ url: `${COVER_URL}?image=${index}`, locked: false, index, })), creditsCharged: 320, }); expect(response.unlocked).toBe(true); expect(response.images).toHaveLength(8); expect(response.creditBalance).toBe(180); expect(response).not.toHaveProperty("creditsCharged"); }); it("serializes the expected unlock cost", () => { expect( UnlockPrivateAlbumRequestSchema.parse({ expectedCost: 320 }), ).toEqual({ expectedCost: 320 }); }); it("encodes the album id in the unlock path", () => { expect(ApiPath.privateZoneAlbumUnlock("album:91")).toBe( "/api/private-zone/albums/album%3A91/unlock", ); }); });