UNPKG

better-auth-feature-flags

Version:

Ship features safely with feature flags, A/B testing, and progressive rollouts - Better Auth plugin for modern release management

356 lines (285 loc) 9.78 kB
// SPDX-FileCopyrightText: 2025-present Kriasoft // SPDX-License-Identifier: MIT import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import { SecureOverrideManager } from "./override-manager"; describe("SecureOverrideManager", () => { let manager: SecureOverrideManager; afterEach(() => { manager?.dispose(); }); describe("Environment Detection", () => { it("should block overrides in production by default", () => { manager = new SecureOverrideManager({ environment: "production", }); const success = manager.set("testFlag", true); expect(success).toBe(false); expect(manager.get("testFlag")).toBeUndefined(); }); it("should allow overrides in development", () => { manager = new SecureOverrideManager({ environment: "development", }); const success = manager.set("testFlag", true); expect(success).toBe(true); expect(manager.get("testFlag")).toBe(true); }); it("should allow production overrides when explicitly enabled", () => { manager = new SecureOverrideManager({ environment: "production", allowInProduction: true, }); const success = manager.set("testFlag", true); expect(success).toBe(true); expect(manager.get("testFlag")).toBe(true); }); }); describe("Expiration", () => { it("should expire overrides after TTL", async () => { manager = new SecureOverrideManager({ environment: "development", ttl: 100, // 100ms for testing }); manager.set("testFlag", true); expect(manager.get("testFlag")).toBe(true); // Wait for expiration await new Promise((resolve) => setTimeout(resolve, 150)); expect(manager.get("testFlag")).toBeUndefined(); }); it("should not return expired overrides", () => { manager = new SecureOverrideManager({ environment: "development", ttl: -1, // Already expired }); manager.set("testFlag", true); expect(manager.get("testFlag")).toBeUndefined(); }); it("should clean up expired overrides periodically", async () => { manager = new SecureOverrideManager({ environment: "development", ttl: 50, }); manager.set("flag1", true); manager.set("flag2", false); expect(manager.getAll()).toEqual({ flag1: true, flag2: false, }); // Wait for expiration and cleanup await new Promise((resolve) => setTimeout(resolve, 100)); // Force manual cleanup to test expired removal manager["cleanupExpired"](); expect(manager.getAll()).toEqual({}); }); }); describe("Override Operations", () => { beforeEach(() => { manager = new SecureOverrideManager({ environment: "development", }); }); it("should set and get overrides", () => { manager.set("flag1", true); manager.set("flag2", "value"); manager.set("flag3", { nested: "object" }); expect(manager.get("flag1")).toBe(true); expect(manager.get("flag2")).toBe("value"); expect(manager.get("flag3")).toEqual({ nested: "object" }); }); it("should check if override exists", () => { manager.set("existingFlag", true); expect(manager.has("existingFlag")).toBe(true); expect(manager.has("nonExistentFlag")).toBe(false); }); it("should delete specific overrides", () => { manager.set("flag1", true); manager.set("flag2", false); manager.delete("flag1"); expect(manager.get("flag1")).toBeUndefined(); expect(manager.get("flag2")).toBe(false); }); it("should clear all overrides", () => { manager.set("flag1", true); manager.set("flag2", false); manager.set("flag3", "value"); manager.clear(); expect(manager.get("flag1")).toBeUndefined(); expect(manager.get("flag2")).toBeUndefined(); expect(manager.get("flag3")).toBeUndefined(); }); it("should get all active overrides", () => { manager.set("flag1", true); manager.set("flag2", "value"); manager.set("flag3", 42); const all = manager.getAll(); expect(all).toEqual({ flag1: true, flag2: "value", flag3: 42, }); }); }); describe("Storage Persistence", () => { beforeEach(() => { // Mock localStorage with in-memory storage const storage: Record<string, string> = {}; global.localStorage = { getItem: (key: string) => storage[key] || null, setItem: (key: string, value: string) => { storage[key] = value; }, removeItem: (key: string) => { delete storage[key]; }, clear: () => { Object.keys(storage).forEach((key) => delete storage[key]); }, length: 0, key: (index: number) => null, }; }); afterEach(() => { // @ts-ignore - Cleanup localStorage mock delete global.localStorage; }); it("should persist overrides to localStorage", () => { manager = new SecureOverrideManager({ environment: "development", persist: true, keyPrefix: "test", }); manager.set("flag1", true); manager.set("flag2", "value"); const stored = localStorage.getItem("test-overrides"); expect(stored).toBeTruthy(); const data = JSON.parse(stored!); expect(data.flag1.value).toBe(true); expect(data.flag2.value).toBe("value"); }); it("should load persisted overrides on init", () => { // Set up initial overrides const initialManager = new SecureOverrideManager({ environment: "development", persist: true, keyPrefix: "test", ttl: 3600000, // 1 hour }); initialManager.set("persistedFlag", "persisted"); initialManager.dispose(); // Create new manager instance manager = new SecureOverrideManager({ environment: "development", persist: true, keyPrefix: "test", }); expect(manager.get("persistedFlag")).toBe("persisted"); }); it("should not load expired overrides from storage", () => { const expiredData = { expiredFlag: { value: "old", expires: Date.now() - 1000, // Expired environment: "development", }, validFlag: { value: "valid", expires: Date.now() + 1000000, // Still valid environment: "development", }, }; localStorage.setItem("test-overrides", JSON.stringify(expiredData)); manager = new SecureOverrideManager({ environment: "development", persist: true, keyPrefix: "test", }); expect(manager.get("expiredFlag")).toBeUndefined(); expect(manager.get("validFlag")).toBe("valid"); }); it("should clear storage when clearing overrides", () => { manager = new SecureOverrideManager({ environment: "development", persist: true, keyPrefix: "test", }); manager.set("flag1", true); expect(localStorage.getItem("test-overrides")).toBeTruthy(); manager.clear(); expect(localStorage.getItem("test-overrides")).toBeNull(); }); }); describe("Security Warnings", () => { it("should warn about environment mismatches", () => { const originalWarn = console.warn; let warnMessage = ""; console.warn = (msg: string) => { warnMessage = msg; }; // Test cross-environment override detection manager = new SecureOverrideManager({ environment: "production", allowInProduction: true, }); // Simulate override set in different environment manager["overrides"].set("testFlag", { value: true, expires: Date.now() + 10000, environment: "development", }); manager.get("testFlag"); expect(warnMessage).toContain( "was set in development but current is production", ); console.warn = originalWarn; }); it("should warn when trying to set overrides in production", () => { const originalWarn = console.warn; let warnMessage = ""; console.warn = (msg: string) => { warnMessage = msg; }; manager = new SecureOverrideManager({ environment: "production", }); manager.set("testFlag", true); expect(warnMessage).toContain("Overrides are disabled in production"); console.warn = originalWarn; }); }); describe("Edge Cases", () => { it("should handle undefined and null values", () => { manager = new SecureOverrideManager({ environment: "development", }); manager.set("undefinedFlag", undefined); manager.set("nullFlag", null); expect(manager.get("undefinedFlag")).toBeUndefined(); expect(manager.get("nullFlag")).toBeNull(); }); it("should handle complex objects", () => { manager = new SecureOverrideManager({ environment: "development", }); const complexValue = { nested: { deeply: { value: [1, 2, 3], flag: true, }, }, }; manager.set("complexFlag", complexValue); expect(manager.get("complexFlag")).toEqual(complexValue); }); it("should return empty object in production without allowInProduction", () => { manager = new SecureOverrideManager({ environment: "production", }); // Attempts to set overrides should fail in production manager.set("flag1", true); manager.set("flag2", false); const all = manager.getAll(); expect(all).toEqual({}); }); }); });