UNPKG

@frak-labs/core-sdk

Version:

Core SDK of the Frak wallet, low level library to interact directly with the frak ecosystem.

406 lines (325 loc) 13.5 kB
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { sdkConfigStore } from "./sdkConfigStore"; vi.mock("./backendUrl", () => ({ getBackendUrl: vi.fn((walletUrl?: string) => { if (walletUrl?.includes("localhost")) { return "http://localhost:3030"; } return "https://backend.frak.id"; }), })); describe("sdkConfigStore", () => { beforeEach(() => { sdkConfigStore.clearCache(); window.sessionStorage.clear(); window.localStorage.clear(); window.__frakSdkConfig = undefined; vi.clearAllMocks(); vi.spyOn(console, "warn").mockImplementation(() => {}); }); afterEach(() => { vi.restoreAllMocks(); }); describe("sdkConfigStore.resolve", () => { it("should fetch from backend when not cached", async () => { const mockResponse = { merchantId: "merchant-123", name: "Test", domain: "shop.example.com", allowedDomains: [], }; global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); const result = await sdkConfigStore.resolve("shop.example.com"); expect(result).toEqual(mockResponse); expect(global.fetch).toHaveBeenCalledWith( "https://backend.frak.id/user/merchant/resolve?domain=shop.example.com" ); }); it("should return cached response on subsequent calls", async () => { const mockResponse = { merchantId: "merchant-456", name: "Test", domain: "shop.example.com", allowedDomains: [], }; global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); const result1 = await sdkConfigStore.resolve("shop.example.com"); expect(result1).toEqual(mockResponse); const result2 = await sdkConfigStore.resolve("shop.example.com"); expect(result2).toEqual(mockResponse); expect(global.fetch).toHaveBeenCalledTimes(1); }); it("should deduplicate concurrent requests", async () => { const mockResponse = { merchantId: "merchant-789", name: "Test", domain: "shop.example.com", allowedDomains: [], }; global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); const [result1, result2, result3] = await Promise.all([ sdkConfigStore.resolve("shop.example.com"), sdkConfigStore.resolve("shop.example.com"), sdkConfigStore.resolve("shop.example.com"), ]); expect(result1).toEqual(mockResponse); expect(result2).toEqual(mockResponse); expect(result3).toEqual(mockResponse); expect(global.fetch).toHaveBeenCalledTimes(1); }); it("should use window.location.hostname when domain not provided", async () => { const mockResponse = { merchantId: "merchant-default", name: "Test", domain: "example.com", allowedDomains: [], }; global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); Object.defineProperty(window, "location", { value: { hostname: "example.com" }, writable: true, }); const result = await sdkConfigStore.resolve(); expect(result).toEqual(mockResponse); expect(global.fetch).toHaveBeenCalledWith( "https://backend.frak.id/user/merchant/resolve?domain=example.com" ); }); it("should return undefined when domain is empty", async () => { global.fetch = vi.fn(); const result = await sdkConfigStore.resolve(""); expect(result).toBeUndefined(); expect(global.fetch).not.toHaveBeenCalled(); }); it("should handle fetch errors gracefully", async () => { global.fetch = vi .fn() .mockRejectedValueOnce(new Error("Network error")); const result = await sdkConfigStore.resolve("shop.example.com"); expect(result).toBeUndefined(); expect(console.warn).toHaveBeenCalledWith( "[Frak SDK] Failed to fetch merchant config:", expect.any(Error) ); }); it("should handle non-ok response (404, 500)", async () => { global.fetch = vi.fn().mockResolvedValueOnce({ ok: false, status: 404, }); const result = await sdkConfigStore.resolve("nonexistent.com"); expect(result).toBeUndefined(); expect(console.warn).toHaveBeenCalledWith( "[Frak SDK] Merchant lookup failed for domain nonexistent.com: 404" ); }); it("should use custom walletUrl to derive backend URL", async () => { const mockResponse = { merchantId: "merchant-local", name: "Test", domain: "shop.example.com", allowedDomains: [], }; global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); const result = await sdkConfigStore.resolve( "shop.example.com", "http://localhost:3000" ); expect(result).toEqual(mockResponse); expect(global.fetch).toHaveBeenCalledWith( "http://localhost:3030/user/merchant/resolve?domain=shop.example.com" ); }); it("should encode domain in URL query parameter", async () => { const mockResponse = { merchantId: "merchant-encoded", name: "Test", domain: "shop.example.com", allowedDomains: [], }; global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); const domainWithSpecialChars = "shop.example.com?test=1&foo=bar"; await sdkConfigStore.resolve(domainWithSpecialChars); expect(global.fetch).toHaveBeenCalledWith( "https://backend.frak.id/user/merchant/resolve?domain=shop.example.com%3Ftest%3D1%26foo%3Dbar" ); }); it("should write merchantId to sessionStorage as 'frak-merchant-id'", async () => { const mockResponse = { merchantId: "merchant-persisted", name: "Test", domain: "shop.example.com", allowedDomains: [], }; global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); await sdkConfigStore.resolve("shop.example.com"); expect(window.sessionStorage.getItem("frak-merchant-id")).toBe( "merchant-persisted" ); }); it("should pass lang parameter to backend when provided", async () => { const mockResponse = { merchantId: "merchant-lang", name: "Test", domain: "shop.example.com", allowedDomains: [], }; global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); await sdkConfigStore.resolve("shop.example.com", undefined, "fr"); expect(global.fetch).toHaveBeenCalledWith( "https://backend.frak.id/user/merchant/resolve?domain=shop.example.com&lang=fr" ); }); }); describe("sdkConfigStore.getMerchantId", () => { it("should return merchantId from resolved config", () => { window.__frakSdkConfig = { isResolved: true, merchantId: "config-merchant-123", }; const result = sdkConfigStore.getMerchantId(); expect(result).toBe("config-merchant-123"); }); it("should fall back to sessionStorage", () => { window.__frakSdkConfig = { isResolved: false, merchantId: "", }; window.sessionStorage.setItem( "frak-merchant-id", "session-merchant-456" ); const result = sdkConfigStore.getMerchantId(); expect(result).toBe("session-merchant-456"); }); it("should return undefined when nothing cached", () => { window.__frakSdkConfig = { isResolved: false, merchantId: "", }; const result = sdkConfigStore.getMerchantId(); expect(result).toBeUndefined(); }); }); describe("sdkConfigStore.resolveMerchantId", () => { it("should return merchantId from store without fetch", async () => { window.__frakSdkConfig = { isResolved: true, merchantId: "store-merchant-123", }; global.fetch = vi.fn(); const result = await sdkConfigStore.resolveMerchantId(); expect(result).toBe("store-merchant-123"); expect(global.fetch).not.toHaveBeenCalled(); }); it("should return merchantId from sessionStorage without fetch", async () => { window.__frakSdkConfig = { isResolved: false, merchantId: "", }; window.sessionStorage.setItem( "frak-merchant-id", "session-merchant-789" ); global.fetch = vi.fn(); const result = await sdkConfigStore.resolveMerchantId(); expect(result).toBe("session-merchant-789"); expect(global.fetch).not.toHaveBeenCalled(); }); it("should fetch from backend as last resort", async () => { const mockResponse = { merchantId: "fetched-merchant-456", name: "Test", domain: "shop.example.com", allowedDomains: [], }; global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); Object.defineProperty(window, "location", { value: { hostname: "shop.example.com" }, writable: true, }); const result = await sdkConfigStore.resolveMerchantId(); expect(result).toBe("fetched-merchant-456"); expect(global.fetch).toHaveBeenCalled(); }); it("should return undefined when fetch fails", async () => { global.fetch = vi .fn() .mockRejectedValueOnce(new Error("Network error")); Object.defineProperty(window, "location", { value: { hostname: "shop.example.com" }, writable: true, }); const result = await sdkConfigStore.resolveMerchantId(); expect(result).toBeUndefined(); }); }); describe("sdkConfigStore.clearCache", () => { it("should clear all caches and allow re-fetching", async () => { const mockResponse = { merchantId: "merchant-clear-test", name: "Test", domain: "shop.example.com", allowedDomains: [], }; global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockResponse, }); const result1 = await sdkConfigStore.resolve("shop.example.com"); expect(result1).toEqual(mockResponse); expect(global.fetch).toHaveBeenCalledTimes(1); sdkConfigStore.clearCache(); const result2 = await sdkConfigStore.resolve("shop.example.com"); expect(result2).toEqual(mockResponse); expect(global.fetch).toHaveBeenCalledTimes(2); }); it("should clear sessionStorage frak-merchant-id", async () => { const mockResponse = { merchantId: "merchant-session-clear", name: "Test", domain: "shop.example.com", allowedDomains: [], }; global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); await sdkConfigStore.resolve("shop.example.com"); expect(window.sessionStorage.getItem("frak-merchant-id")).toBe( "merchant-session-clear" ); sdkConfigStore.clearCache(); expect( window.sessionStorage.getItem("frak-merchant-id") ).toBeNull(); }); }); });