import { describe, expect, it } from "vitest"; import { decryptFeedToken, encryptFeedToken } from "./feedToken"; // Fixed 32-byte keys so the cipher is exercised without the FEED_TOKEN_KEY env // var (production omits the key argument and reads it from env). const key = new Uint8Array(32).fill(7); const otherKey = new Uint8Array(32).fill(9); describe("feed token", () => { // Behaviour gate: the feed must still resolve to the same refresh token it // was minted from, and the cleartext must never appear in the URL blob. it("round-trips the refresh token without leaking it", async () => { const refreshToken = "anilist-refresh-token-abc123"; const sealed = await encryptFeedToken(refreshToken, key); expect(sealed).not.toContain(refreshToken); expect(await decryptFeedToken(sealed, key)).toBe(refreshToken); }); it("produces a fresh ciphertext each time (random IV)", async () => { expect(await encryptFeedToken("same-token", key)).not.toBe( await encryptFeedToken("same-token", key), ); }); it("rejects a token sealed with a different key", async () => { const sealed = await encryptFeedToken("secret", otherKey); expect(await decryptFeedToken(sealed, key)).toBeNull(); }); it("rejects a tampered ciphertext", async () => { const sealed = await encryptFeedToken("secret", key); const tampered = `${sealed[0] === "A" ? "B" : "A"}${sealed.slice(1)}`; expect(await decryptFeedToken(tampered, key)).toBeNull(); }); it("rejects malformed input", async () => { expect(await decryptFeedToken("", key)).toBeNull(); expect(await decryptFeedToken("short", key)).toBeNull(); expect(await decryptFeedToken("not valid base64url!!", key)).toBeNull(); }); });