Skip to main content
Glama

Bucket Feature Flags MCP Server

Official
by reflagcom
index.test.ts10.1 kB
import { ProviderStatus } from "@openfeature/server-sdk"; import { beforeEach, describe, expect, it, Mock, vi } from "vitest"; import { ReflagClient } from "@reflag/node-sdk"; import { defaultContextTranslator, ReflagNodeProvider } from "./index"; vi.mock("@reflag/node-sdk", () => { const actualModule = vi.importActual("@reflag/node-sdk"); return { __esModule: true, ...actualModule, ReflagClient: vi.fn(), }; }); const reflagClientMock = { getFlag: vi.fn(), getFlagDefinitions: vi.fn().mockReturnValue([]), initialize: vi.fn().mockResolvedValue({}), flush: vi.fn(), track: vi.fn(), }; const secretKey = "sec_fakeSecretKey______"; // must be 23 characters long const context = { targetingKey: "abc", name: "John Doe", email: "john@acme.inc", }; const reflagContext = { user: { id: "42" }, company: { id: "99" }, }; const testFlagKey = "a-key"; beforeEach(() => { vi.clearAllMocks(); }); describe("ReflagNodeProvider", () => { let provider: ReflagNodeProvider; const mockReflagClient = ReflagClient as Mock; mockReflagClient.mockReturnValue(reflagClientMock); let mockTranslatorFn: Mock; function mockFlag( enabled: boolean, configKey?: string | null, configPayload?: any, flagKey = testFlagKey, ) { const config = { key: configKey, payload: configPayload, }; reflagClientMock.getFlag = vi.fn().mockReturnValue({ isEnabled: enabled, config, }); // Mock getFlagDefinitions to return feature definitions that include the specified flag reflagClientMock.getFlagDefinitions = vi.fn().mockReturnValue([ { key: flagKey, description: "Test flag", flag: {}, config: {}, }, ]); } beforeEach(async () => { mockTranslatorFn = vi.fn().mockReturnValue(reflagContext); provider = new ReflagNodeProvider({ secretKey, contextTranslator: mockTranslatorFn, }); await provider.initialize(); }); describe("contextTranslator", () => { it("defaultContextTranslator provides the correct context", async () => { expect( defaultContextTranslator({ userId: 123, name: "John Doe", email: "ron@reflag.co", avatar: "https://reflag.com/avatar.png", companyId: "456", companyName: "Acme, Inc.", companyAvatar: "https://acme.com/company-avatar.png", companyPlan: "pro", }), ).toEqual({ user: { id: "123", name: "John Doe", email: "ron@reflag.co", avatar: "https://reflag.com/avatar.png", }, company: { id: "456", name: "Acme, Inc.", plan: "pro", avatar: "https://acme.com/company-avatar.png", }, }); }); it("defaultContextTranslator uses targetingKey if provided", async () => { expect( defaultContextTranslator({ targetingKey: "123", }), ).toMatchObject({ user: { id: "123", }, company: { id: undefined, }, }); }); }); describe("lifecycle", () => { it("calls the constructor of ReflagClient", () => { mockReflagClient.mockClear(); provider = new ReflagNodeProvider({ secretKey, contextTranslator: mockTranslatorFn, }); expect(mockReflagClient).toHaveBeenCalledTimes(1); expect(mockReflagClient).toHaveBeenCalledWith({ secretKey }); }); it("should set the status to READY if initialization succeeds", async () => { provider = new ReflagNodeProvider({ secretKey, contextTranslator: mockTranslatorFn, }); await provider.initialize(); expect(provider.status).toBe(ProviderStatus.READY); }); it("should keep the status as READY after closing", async () => { provider = new ReflagNodeProvider({ secretKey: "invalid", contextTranslator: mockTranslatorFn, }); await provider.initialize(); await provider.onClose(); expect(provider.status).toBe(ProviderStatus.READY); }); it("calls flush when provider is closed", async () => { await provider.onClose(); expect(reflagClientMock.flush).toHaveBeenCalledTimes(1); }); it("uses the contextTranslator function", async () => { mockFlag(true); await provider.resolveBooleanEvaluation(testFlagKey, false, context); expect(mockTranslatorFn).toHaveBeenCalledTimes(1); expect(mockTranslatorFn).toHaveBeenCalledWith(context); expect(reflagClientMock.getFlagDefinitions).toHaveBeenCalledTimes(1); expect(reflagClientMock.getFlag).toHaveBeenCalledWith( reflagContext, testFlagKey, ); }); }); describe("resolving flags", () => { beforeEach(async () => { await provider.initialize(); }); it("returns error if provider is not initialized", async () => { provider = new ReflagNodeProvider({ secretKey: "invalid", contextTranslator: mockTranslatorFn, }); const val = await provider.resolveBooleanEvaluation( testFlagKey, true, context, ); expect(val).toMatchObject({ reason: "ERROR", errorCode: "PROVIDER_NOT_READY", value: true, }); }); it("returns error if flag is not found", async () => { mockFlag(true, "key", true); const val = await provider.resolveBooleanEvaluation( "missing-key", true, context, ); expect(val).toMatchObject({ reason: "ERROR", errorCode: "FLAG_NOT_FOUND", value: true, }); }); it("calls the client correctly when evaluating", async () => { mockFlag(true, "key", true); const val = await provider.resolveBooleanEvaluation( testFlagKey, false, context, ); expect(val).toMatchObject({ reason: "TARGETING_MATCH", value: true, }); expect(reflagClientMock.getFlagDefinitions).toHaveBeenCalled(); expect(reflagClientMock.getFlag).toHaveBeenCalledWith( reflagContext, testFlagKey, ); }); it.each([ [true, false, true, "TARGETING_MATCH", undefined], [undefined, true, true, "ERROR", "FLAG_NOT_FOUND"], [undefined, false, false, "ERROR", "FLAG_NOT_FOUND"], ])( "should return the correct result when evaluating boolean. enabled: %s, value: %s, default: %s, expected: %s, reason: %s, errorCode: %s`", async (enabled, def, expected, reason, errorCode) => { const configKey = enabled !== undefined ? "variant-1" : undefined; mockFlag(enabled ?? false, configKey); const flagKey = enabled ? testFlagKey : "missing-key"; expect( await provider.resolveBooleanEvaluation(flagKey, def, context), ).toMatchObject({ reason, value: expected, ...(configKey ? { variant: configKey } : {}), ...(errorCode ? { errorCode } : {}), }); }, ); it("should return error when context is missing user ID", async () => { mockTranslatorFn.mockReturnValue({ user: {} }); expect( await provider.resolveBooleanEvaluation(testFlagKey, true, context), ).toMatchObject({ reason: "ERROR", errorCode: "INVALID_CONTEXT", value: true, }); }); it("should return error when evaluating number", async () => { expect( await provider.resolveNumberEvaluation(testFlagKey, 1), ).toMatchObject({ reason: "ERROR", errorCode: "GENERAL", value: 1, }); }); it.each([ ["key-1", "default", "key-1", "TARGETING_MATCH"], [null, "default", "default", "DEFAULT"], [undefined, "default", "default", "DEFAULT"], ])( "should return the correct result when evaluating string. variant: %s, def: %s, expected: %s, reason: %s, errorCode: %s`", async (variant, def, expected, reason) => { mockFlag(true, variant, {}); expect( await provider.resolveStringEvaluation(testFlagKey, def, context), ).toMatchObject({ reason, value: expected, ...(variant ? { variant } : {}), }); }, ); it.each([ [{}, { a: 1 }, {}, "TARGETING_MATCH", undefined], ["string", "default", "string", "TARGETING_MATCH", undefined], [15, -15, 15, "TARGETING_MATCH", undefined], [true, false, true, "TARGETING_MATCH", undefined], [null, { a: 2 }, { a: 2 }, "ERROR", "TYPE_MISMATCH"], [100, "string", "string", "ERROR", "TYPE_MISMATCH"], [true, 1337, 1337, "ERROR", "TYPE_MISMATCH"], ["string", 1337, 1337, "ERROR", "TYPE_MISMATCH"], [undefined, "default", "default", "ERROR", "TYPE_MISMATCH"], ])( "should return the correct result when evaluating object. payload: %s, default: %s, expected: %s, reason: %s, errorCode: %s`", async (value, def, expected, reason, errorCode) => { const configKey = value === undefined ? undefined : "config-key"; mockFlag(true, configKey, value); expect( await provider.resolveObjectEvaluation(testFlagKey, def, context), ).toMatchObject({ reason, value: expected, ...(errorCode ? { errorCode, variant: configKey } : {}), }); }, ); }); describe("track", () => { it("should track", async () => { expect(mockTranslatorFn).toHaveBeenCalledTimes(0); provider.track("event", context, { action: "click", }); expect(mockTranslatorFn).toHaveBeenCalledTimes(1); expect(mockTranslatorFn).toHaveBeenCalledWith(context); expect(reflagClientMock.track).toHaveBeenCalledTimes(1); expect(reflagClientMock.track).toHaveBeenCalledWith("42", "event", { attributes: { action: "click" }, companyId: reflagContext.company.id, }); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/reflagcom/bucket-javascript-sdk'

If you have feedback or need assistance with the MCP directory API, please join our Discord server