diff --git a/apps/web/modules/ee/license-check/lib/utils.test.ts b/apps/web/modules/ee/license-check/lib/utils.test.ts index fb20583a14..f31bbe47de 100644 --- a/apps/web/modules/ee/license-check/lib/utils.test.ts +++ b/apps/web/modules/ee/license-check/lib/utils.test.ts @@ -9,6 +9,8 @@ import { getEnterpriseLicense, getLicenseFeatures } from "./license"; import { getAccessControlPermission, getBiggerUploadFileSizePermission, + getIsAIDataAnalysisEnabled, + getIsAISmartToolsEnabled, getIsAuditLogsEnabled, getIsContactsEnabled, getIsMultiOrgEnabled, @@ -195,6 +197,72 @@ describe("License Utils", () => { expect(access).toBe(true); expect(quotas).toBe(true); }); + + test("uses cloud AI smart tools entitlement", async () => { + vi.mocked(constants).IS_FORMBRICKS_CLOUD = true; + vi.mocked(hasOrganizationEntitlementWithLicenseGuard).mockResolvedValueOnce(true); + + const result = await getIsAISmartToolsEnabled("org_1"); + + expect(result).toBe(true); + expect(hasOrganizationEntitlementWithLicenseGuard).toHaveBeenCalledWith( + "org_1", + CLOUD_STRIPE_FEATURE_LOOKUP_KEYS.AI_SMART_TOOLS + ); + }); + + test("uses cloud AI data analysis entitlement", async () => { + vi.mocked(constants).IS_FORMBRICKS_CLOUD = true; + vi.mocked(hasOrganizationEntitlementWithLicenseGuard).mockResolvedValueOnce(true); + + const result = await getIsAIDataAnalysisEnabled("org_1"); + + expect(result).toBe(true); + expect(hasOrganizationEntitlementWithLicenseGuard).toHaveBeenCalledWith( + "org_1", + CLOUD_STRIPE_FEATURE_LOOKUP_KEYS.AI_DATA_ANALYSIS + ); + }); + + test("returns self-hosted AI features from license", async () => { + vi.mocked(constants).IS_FORMBRICKS_CLOUD = false; + vi.mocked(getEnterpriseLicense).mockResolvedValue({ + ...defaultLicense, + features: { + ...defaultFeatures, + aiSmartTools: true, + aiDataAnalysis: true, + }, + }); + + const [smartTools, dataAnalysis] = await Promise.all([ + getIsAISmartToolsEnabled("org_1"), + getIsAIDataAnalysisEnabled("org_1"), + ]); + + expect(smartTools).toBe(true); + expect(dataAnalysis).toBe(true); + }); + + test("returns false for self-hosted AI features when not enabled", async () => { + vi.mocked(constants).IS_FORMBRICKS_CLOUD = false; + vi.mocked(getEnterpriseLicense).mockResolvedValue({ + ...defaultLicense, + features: { + ...defaultFeatures, + aiSmartTools: false, + aiDataAnalysis: false, + }, + }); + + const [smartTools, dataAnalysis] = await Promise.all([ + getIsAISmartToolsEnabled("org_1"), + getIsAIDataAnalysisEnabled("org_1"), + ]); + + expect(smartTools).toBe(false); + expect(dataAnalysis).toBe(false); + }); }); describe("getBiggerUploadFileSizePermission", () => { diff --git a/apps/web/modules/entitlements/lib/checks.test.ts b/apps/web/modules/entitlements/lib/checks.test.ts index 442ed8fef6..a726e5aaa3 100644 --- a/apps/web/modules/entitlements/lib/checks.test.ts +++ b/apps/web/modules/entitlements/lib/checks.test.ts @@ -125,6 +125,46 @@ describe("hasOrganizationEntitlementWithLicenseGuard", () => { expect(await hasOrganizationEntitlementWithLicenseGuard("org1", "rbac")).toBe(false); }); + test("returns true when license active and ai-smart-tools mapped feature enabled", async () => { + mockGetContext.mockResolvedValue({ + ...baseContext, + features: ["ai-smart-tools"], + licenseStatus: "active", + licenseFeatures: { aiSmartTools: true } as TOrganizationEntitlementsContext["licenseFeatures"], + }); + expect(await hasOrganizationEntitlementWithLicenseGuard("org1", "ai-smart-tools")).toBe(true); + }); + + test("returns false when license active but ai-smart-tools mapped feature disabled", async () => { + mockGetContext.mockResolvedValue({ + ...baseContext, + features: ["ai-smart-tools"], + licenseStatus: "active", + licenseFeatures: { aiSmartTools: false } as TOrganizationEntitlementsContext["licenseFeatures"], + }); + expect(await hasOrganizationEntitlementWithLicenseGuard("org1", "ai-smart-tools")).toBe(false); + }); + + test("returns true when license active and ai-data-analysis mapped feature enabled", async () => { + mockGetContext.mockResolvedValue({ + ...baseContext, + features: ["ai-data-analysis"], + licenseStatus: "active", + licenseFeatures: { aiDataAnalysis: true } as TOrganizationEntitlementsContext["licenseFeatures"], + }); + expect(await hasOrganizationEntitlementWithLicenseGuard("org1", "ai-data-analysis")).toBe(true); + }); + + test("returns false when license active but ai-data-analysis mapped feature disabled", async () => { + mockGetContext.mockResolvedValue({ + ...baseContext, + features: ["ai-data-analysis"], + licenseStatus: "active", + licenseFeatures: { aiDataAnalysis: false } as TOrganizationEntitlementsContext["licenseFeatures"], + }); + expect(await hasOrganizationEntitlementWithLicenseGuard("org1", "ai-data-analysis")).toBe(false); + }); + test("returns true when license active and feature has no license mapping", async () => { mockGetContext.mockResolvedValue({ ...baseContext, diff --git a/apps/web/modules/entitlements/lib/self-hosted-provider.test.ts b/apps/web/modules/entitlements/lib/self-hosted-provider.test.ts index f7a38ab7e4..9e05c09143 100644 --- a/apps/web/modules/entitlements/lib/self-hosted-provider.test.ts +++ b/apps/web/modules/entitlements/lib/self-hosted-provider.test.ts @@ -99,4 +99,46 @@ describe("getSelfHostedOrganizationEntitlementsContext", () => { expect(result.features).toContain("hide-branding"); }); + + test("maps aiSmartTools feature to ai-smart-tools entitlement", async () => { + mockGetOrg.mockResolvedValue({ id: "org1" } as any); + mockGetLicense.mockResolvedValue({ + status: "active", + active: true, + features: { aiSmartTools: true }, + } as any); + + const result = await getSelfHostedOrganizationEntitlementsContext("org1"); + + expect(result.features).toContain("ai-smart-tools"); + expect(result.features).not.toContain("ai-data-analysis"); + }); + + test("maps aiDataAnalysis feature to ai-data-analysis entitlement", async () => { + mockGetOrg.mockResolvedValue({ id: "org1" } as any); + mockGetLicense.mockResolvedValue({ + status: "active", + active: true, + features: { aiDataAnalysis: true }, + } as any); + + const result = await getSelfHostedOrganizationEntitlementsContext("org1"); + + expect(result.features).toContain("ai-data-analysis"); + expect(result.features).not.toContain("ai-smart-tools"); + }); + + test("maps both AI features when both are enabled", async () => { + mockGetOrg.mockResolvedValue({ id: "org1" } as any); + mockGetLicense.mockResolvedValue({ + status: "active", + active: true, + features: { aiSmartTools: true, aiDataAnalysis: true }, + } as any); + + const result = await getSelfHostedOrganizationEntitlementsContext("org1"); + + expect(result.features).toContain("ai-smart-tools"); + expect(result.features).toContain("ai-data-analysis"); + }); });