mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat(enterprise/coderd): add soft warning for AI Bridge GA transition (#21675)
## Summary AI Bridge is moving to General Availability in v2.30 and will require the AI Governance Add-On license in future versions. This adds a soft warning for deployments using AI Bridge via Premium/Enterprise FeatureSet without an explicit AI Bridge add-on license. Relates to: https://github.com/coder/internal/issues/1226 ## Changes - Track whether AI Bridge was explicitly granted via license Features (add-on) vs inherited from FeatureSet - Show soft warning when AI Bridge is enabled and entitled via FeatureSet but not via explicit add-on - Changed AI Bridge enablement from hardcoded `true` to check `CODER_AIBRIDGE_ENABLED` deployment config ## Behavior Change AI Bridge is now only marked as "enabled" in entitlements when `CODER_AIBRIDGE_ENABLED=true` is set in the deployment config. Previously, it was always enabled for Premium/Enterprise licenses regardless of the config setting. This change ensures that users who do not use AI Bridge will not see the soft warning about the upcoming license requirement. ## Warning Message > AI Bridge is now Generally Available in v2.30. In a future Coder version, your deployment will require the AI Governance Add-On to continue using this feature. Please reach out to your account team or sales@coder.com to learn more. ## Behavior | Condition | Warning Shown | |-----------|---------------| | AI Bridge disabled | ❌ No | | AI Bridge enabled + explicit add-on license | ❌ No | | AI Bridge enabled + Premium/Enterprise FeatureSet (no add-on) | ✅ Yes | ## Screenshots ### 1. No license <img width="1708" height="577" alt="image" src="https://github.com/user-attachments/assets/cbdbfd4d-55de-4d70-8abf-2665f458e96f" /> ### 2. No license + CODER_AIBRIDGE_ENABLED=true <img width="1716" height="513" alt="image" src="https://github.com/user-attachments/assets/344aae76-7703-485f-b568-1f13a1efa48f" /> ### 3. Premium license + CODER_AIBRIDGE_ENABLED=false <img width="1687" height="389" alt="image" src="https://github.com/user-attachments/assets/c2be12b0-1c0f-438d-a293-f9ec9fe6a736" /> ### 4. Premium license + CODER_AIBRIDGE_ENABLED=true <img width="1707" height="525" alt="image" src="https://github.com/user-attachments/assets/1a4640e1-e656-4f9b-bed0-9390cb5d6a84" /> ## Notes - TODO comments added to mark code that should be removed when AI Bridge enforcement is added - Feature continues to work - this is just a transitional warning (soft enforcement)
This commit is contained in:
@@ -27,6 +27,7 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = true
|
||||
client, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
@@ -77,6 +78,7 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = true
|
||||
client, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
@@ -162,6 +164,7 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = true
|
||||
client, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/license"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
|
||||
func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
@@ -51,6 +52,7 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
t.Run("EmptyDB", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
@@ -71,6 +73,7 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
client, db, firstUser := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
@@ -189,6 +192,7 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
client, db, firstUser := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
@@ -304,6 +308,7 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
t.Run("InflightInterceptions", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
client, db, firstUser := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
@@ -337,6 +342,7 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
t.Run("Authorized", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
adminClient, db, firstUser := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
@@ -381,6 +387,7 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
t.Run("Filter", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
client, db, firstUser := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
@@ -561,6 +568,7 @@ func TestAIBridgeListInterceptions(t *testing.T) {
|
||||
t.Run("FilterErrors", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
@@ -643,6 +651,7 @@ func TestAIBridgeRouting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
client, closer, api, _ := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
@@ -703,6 +712,7 @@ func TestAIBridgeRateLimiting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
// Set a low rate limit for testing.
|
||||
dv.AI.BridgeConfig.RateLimit = 2
|
||||
|
||||
@@ -758,6 +768,7 @@ func TestAIBridgeConcurrencyLimiting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
// Set a low concurrency limit for testing.
|
||||
dv.AI.BridgeConfig.MaxConcurrency = 1
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/license"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
|
||||
func TestAIBridgeProxyCertificateRetrieval(t *testing.T) {
|
||||
@@ -20,6 +21,7 @@ func TestAIBridgeProxyCertificateRetrieval(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
// Proxy is disabled by default, so we don't need to set it explicitly.
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
@@ -50,6 +52,7 @@ func TestAIBridgeProxyCertificateRetrieval(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
@@ -78,6 +81,7 @@ func TestAIBridgeProxyCertificateRetrieval(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.AI.BridgeConfig.Enabled = serpent.Bool(true)
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
|
||||
@@ -769,7 +769,7 @@ func (api *API) updateEntitlements(ctx context.Context) error {
|
||||
codersdk.FeatureUserRoleManagement: true,
|
||||
codersdk.FeatureAccessControl: true,
|
||||
codersdk.FeatureControlSharedPorts: true,
|
||||
codersdk.FeatureAIBridge: true,
|
||||
codersdk.FeatureAIBridge: api.DeploymentValues.AI.BridgeConfig.Enabled.Value(),
|
||||
})
|
||||
if err != nil {
|
||||
return codersdk.Entitlements{}, err
|
||||
|
||||
@@ -167,6 +167,12 @@ func LicensesEntitlements(
|
||||
keys map[string]ed25519.PublicKey,
|
||||
featureArguments FeatureArguments,
|
||||
) (codersdk.Entitlements, error) {
|
||||
// TODO: Remove this tracking once AI Bridge is enforced as an add-on license.
|
||||
// Track if AI Bridge was explicitly granted via license Features (add-on)
|
||||
// vs inherited from FeatureSet (Premium). Only explicit grants should
|
||||
// suppress the soft warning for AI Bridge GA.
|
||||
hasExplicitAIBridgeEntitlement := false
|
||||
|
||||
// Default all entitlements to be disabled.
|
||||
entitlements := codersdk.Entitlements{
|
||||
Features: map[codersdk.FeatureName]codersdk.Feature{
|
||||
@@ -335,6 +341,12 @@ func LicensesEntitlements(
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: Remove this tracking once AI Bridge is enforced as an add-on license.
|
||||
// Track explicit AI Bridge entitlement (add-on license).
|
||||
if featureName == codersdk.FeatureAIBridge && featureValue > 0 {
|
||||
hasExplicitAIBridgeEntitlement = true
|
||||
}
|
||||
|
||||
// Special handling for grouped (e.g. usage period) features.
|
||||
if grouping, ok := featureGrouping[featureName]; ok {
|
||||
ul := uncommittedUsageFeatures[grouping.sdkFeature]
|
||||
@@ -583,6 +595,17 @@ func LicensesEntitlements(
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove this soft warning block once AI Bridge is enforced as an add-on license.
|
||||
// AI Bridge soft warning: Show warning when AI Bridge is enabled and
|
||||
// entitled via Premium FeatureSet but not via explicit add-on license.
|
||||
// This is a transitional warning as AI Bridge moves to GA and will
|
||||
// require a separate add-on license in future versions.
|
||||
aiBridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
if aiBridgeFeature.Enabled && aiBridgeFeature.Entitlement.Entitled() && !hasExplicitAIBridgeEntitlement {
|
||||
entitlements.Warnings = append(entitlements.Warnings,
|
||||
"AI Bridge is now Generally Available in v2.30. In a future Coder version, your deployment will require the AI Governance Add-On to continue using this feature. Please reach out to your account team or sales@coder.com to learn more.")
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap up by disabling all features that are not entitled.
|
||||
|
||||
@@ -188,9 +188,11 @@ func TestEntitlements(t *testing.T) {
|
||||
graceDate := dbtime.Now().AddDate(0, 0, 1)
|
||||
_, err := db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
||||
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
||||
// TODO: Remove explicit FeatureAIBridge once AI Bridge is enforced as an add-on license.
|
||||
Features: license.Features{
|
||||
codersdk.FeatureUserLimit: 100,
|
||||
codersdk.FeatureAuditLog: 1,
|
||||
codersdk.FeatureAIBridge: 1, // Explicit AI Bridge to avoid soft warning
|
||||
},
|
||||
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
@@ -214,9 +216,11 @@ func TestEntitlements(t *testing.T) {
|
||||
// license expires.
|
||||
_, err = db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
||||
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
||||
// TODO: Remove explicit FeatureAIBridge once AI Bridge is enforced as an add-on license.
|
||||
Features: license.Features{
|
||||
codersdk.FeatureUserLimit: 100,
|
||||
codersdk.FeatureAuditLog: 1,
|
||||
codersdk.FeatureAIBridge: 1, // Explicit AI Bridge to avoid soft warning
|
||||
},
|
||||
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
@@ -245,9 +249,11 @@ func TestEntitlements(t *testing.T) {
|
||||
graceDate := dbtime.Now().AddDate(0, 0, 1)
|
||||
_, err := db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
||||
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
||||
// TODO: Remove explicit FeatureAIBridge once AI Bridge is enforced as an add-on license.
|
||||
Features: license.Features{
|
||||
codersdk.FeatureUserLimit: 100,
|
||||
codersdk.FeatureAuditLog: 1,
|
||||
codersdk.FeatureAIBridge: 1, // Explicit AI Bridge to avoid soft warning
|
||||
},
|
||||
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
@@ -271,9 +277,11 @@ func TestEntitlements(t *testing.T) {
|
||||
// license expires (e.g. there's a gap)
|
||||
_, err = db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
||||
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
||||
// TODO: Remove explicit FeatureAIBridge once AI Bridge is enforced as an add-on license.
|
||||
Features: license.Features{
|
||||
codersdk.FeatureUserLimit: 100,
|
||||
codersdk.FeatureAuditLog: 1,
|
||||
codersdk.FeatureAIBridge: 1, // Explicit AI Bridge to avoid soft warning
|
||||
},
|
||||
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
@@ -806,12 +814,17 @@ func TestEntitlements(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mDB := dbmock.NewMockStore(ctrl)
|
||||
|
||||
// TODO: Remove explicit FeatureAIBridge once AI Bridge is enforced as an add-on license.
|
||||
licenseOpts := (&coderdenttest.LicenseOptions{
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
IssuedAt: dbtime.Now().Add(-2 * time.Hour).Truncate(time.Second),
|
||||
NotBefore: dbtime.Now().Add(-time.Hour).Truncate(time.Second),
|
||||
GraceAt: dbtime.Now().Add(time.Hour * 24 * 60).Truncate(time.Second), // 60 days to remove warning
|
||||
ExpiresAt: dbtime.Now().Add(time.Hour * 24 * 90).Truncate(time.Second), // 90 days to remove warning
|
||||
// Explicit AI Bridge to avoid soft warning in tests
|
||||
Features: license.Features{
|
||||
codersdk.FeatureAIBridge: 1,
|
||||
},
|
||||
}).
|
||||
UserLimit(100).
|
||||
ManagedAgentLimit(100, 200)
|
||||
@@ -895,6 +908,7 @@ func TestLicenseEntitlements(t *testing.T) {
|
||||
codersdk.FeatureAIBridge: true,
|
||||
}
|
||||
|
||||
// TODO: Remove explicit FeatureAIBridge from these license helpers once AI Bridge is enforced as an add-on license.
|
||||
legacyLicense := func() *coderdenttest.LicenseOptions {
|
||||
return (&coderdenttest.LicenseOptions{
|
||||
AccountType: "salesforce",
|
||||
@@ -902,6 +916,10 @@ func TestLicenseEntitlements(t *testing.T) {
|
||||
Trial: false,
|
||||
// Use the legacy boolean
|
||||
AllFeatures: true,
|
||||
// Explicit AI Bridge to avoid soft warning in tests
|
||||
Features: license.Features{
|
||||
codersdk.FeatureAIBridge: 1,
|
||||
},
|
||||
}).Valid(time.Now())
|
||||
}
|
||||
|
||||
@@ -913,6 +931,10 @@ func TestLicenseEntitlements(t *testing.T) {
|
||||
Trial: false,
|
||||
FeatureSet: codersdk.FeatureSetEnterprise,
|
||||
AllFeatures: true,
|
||||
// Explicit AI Bridge to avoid soft warning in tests
|
||||
Features: license.Features{
|
||||
codersdk.FeatureAIBridge: 1,
|
||||
},
|
||||
}).Valid(time.Now())
|
||||
}
|
||||
|
||||
@@ -924,6 +946,10 @@ func TestLicenseEntitlements(t *testing.T) {
|
||||
Trial: false,
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
AllFeatures: true,
|
||||
// Explicit AI Bridge to avoid soft warning in tests
|
||||
Features: license.Features{
|
||||
codersdk.FeatureAIBridge: 1,
|
||||
},
|
||||
}).Valid(time.Now())
|
||||
}
|
||||
|
||||
@@ -1285,6 +1311,164 @@ func TestLicenseEntitlements(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAIBridgeSoftWarning(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aiBridgeEnabledEnablements := map[codersdk.FeatureName]bool{
|
||||
codersdk.FeatureAIBridge: true,
|
||||
}
|
||||
|
||||
aiBridgeDisabledEnablements := map[codersdk.FeatureName]bool{
|
||||
codersdk.FeatureAIBridge: false,
|
||||
}
|
||||
|
||||
aiBridgeWarningMessage := "AI Bridge is now Generally Available in v2.30. In a future Coder version, your deployment will require the AI Governance Add-On to continue using this feature. Please reach out to your account team or sales@coder.com to learn more."
|
||||
|
||||
t.Run("PremiumLicenseWithAIBridgeEnabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Premium license with AI Bridge enabled should show soft warning
|
||||
// because AI Bridge is not explicitly granted via add-on.
|
||||
lo := (&coderdenttest.LicenseOptions{
|
||||
AccountType: "salesforce",
|
||||
AccountID: "test",
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
}).Valid(time.Now())
|
||||
|
||||
generatedLicenses := []database.License{
|
||||
{
|
||||
ID: 1,
|
||||
UploadedAt: time.Now().Add(time.Hour * -1),
|
||||
JWT: lo.Generate(t),
|
||||
Exp: lo.GraceAt,
|
||||
UUID: uuid.New(),
|
||||
},
|
||||
}
|
||||
|
||||
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), generatedLicenses, aiBridgeEnabledEnablements, coderdenttest.Keys, license.FeatureArguments{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// AI Bridge should be enabled and entitled.
|
||||
aiBridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
assert.True(t, aiBridgeFeature.Enabled)
|
||||
assert.Equal(t, codersdk.EntitlementEntitled, aiBridgeFeature.Entitlement)
|
||||
|
||||
// Should have the soft warning.
|
||||
require.Contains(t, entitlements.Warnings, aiBridgeWarningMessage)
|
||||
})
|
||||
|
||||
t.Run("ExplicitAIBridgeLicense", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// License with explicit AI Bridge feature (add-on) should NOT show warning.
|
||||
lo := (&coderdenttest.LicenseOptions{
|
||||
AccountType: "salesforce",
|
||||
AccountID: "test",
|
||||
Features: license.Features{
|
||||
codersdk.FeatureAIBridge: 1,
|
||||
},
|
||||
}).Valid(time.Now())
|
||||
|
||||
generatedLicenses := []database.License{
|
||||
{
|
||||
ID: 1,
|
||||
UploadedAt: time.Now().Add(time.Hour * -1),
|
||||
JWT: lo.Generate(t),
|
||||
Exp: lo.GraceAt,
|
||||
UUID: uuid.New(),
|
||||
},
|
||||
}
|
||||
|
||||
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), generatedLicenses, aiBridgeEnabledEnablements, coderdenttest.Keys, license.FeatureArguments{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// AI Bridge should be enabled and entitled.
|
||||
aiBridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
assert.True(t, aiBridgeFeature.Enabled)
|
||||
assert.Equal(t, codersdk.EntitlementEntitled, aiBridgeFeature.Entitlement)
|
||||
|
||||
// Should NOT have the soft warning.
|
||||
require.NotContains(t, entitlements.Warnings, aiBridgeWarningMessage)
|
||||
})
|
||||
|
||||
t.Run("PremiumLicenseWithAIBridgeDisabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Premium license with AI Bridge NOT enabled should NOT show warning.
|
||||
lo := (&coderdenttest.LicenseOptions{
|
||||
AccountType: "salesforce",
|
||||
AccountID: "test",
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
}).Valid(time.Now())
|
||||
|
||||
generatedLicenses := []database.License{
|
||||
{
|
||||
ID: 1,
|
||||
UploadedAt: time.Now().Add(time.Hour * -1),
|
||||
JWT: lo.Generate(t),
|
||||
Exp: lo.GraceAt,
|
||||
UUID: uuid.New(),
|
||||
},
|
||||
}
|
||||
|
||||
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), generatedLicenses, aiBridgeDisabledEnablements, coderdenttest.Keys, license.FeatureArguments{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// AI Bridge should NOT be enabled.
|
||||
aiBridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
assert.False(t, aiBridgeFeature.Enabled)
|
||||
|
||||
// Should NOT have the soft warning.
|
||||
require.NotContains(t, entitlements.Warnings, aiBridgeWarningMessage)
|
||||
})
|
||||
|
||||
t.Run("PremiumPlusExplicitAIBridge", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Premium license PLUS explicit AI Bridge add-on should NOT show warning.
|
||||
lo := (&coderdenttest.LicenseOptions{
|
||||
AccountType: "salesforce",
|
||||
AccountID: "test",
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
Features: license.Features{
|
||||
codersdk.FeatureAIBridge: 1,
|
||||
},
|
||||
}).Valid(time.Now())
|
||||
|
||||
generatedLicenses := []database.License{
|
||||
{
|
||||
ID: 1,
|
||||
UploadedAt: time.Now().Add(time.Hour * -1),
|
||||
JWT: lo.Generate(t),
|
||||
Exp: lo.GraceAt,
|
||||
UUID: uuid.New(),
|
||||
},
|
||||
}
|
||||
|
||||
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), generatedLicenses, aiBridgeEnabledEnablements, coderdenttest.Keys, license.FeatureArguments{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// AI Bridge should be enabled and entitled.
|
||||
aiBridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
assert.True(t, aiBridgeFeature.Enabled)
|
||||
assert.Equal(t, codersdk.EntitlementEntitled, aiBridgeFeature.Entitlement)
|
||||
|
||||
// Should NOT have the soft warning.
|
||||
require.NotContains(t, entitlements.Warnings, aiBridgeWarningMessage)
|
||||
})
|
||||
|
||||
t.Run("NoLicenseWithAIBridgeEnabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// No license with AI Bridge enabled should NOT show the soft warning
|
||||
// (it will show the generic "not entitled" warning instead).
|
||||
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), []database.License{}, aiBridgeEnabledEnablements, coderdenttest.Keys, license.FeatureArguments{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// AI Bridge should NOT be entitled.
|
||||
aiBridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
assert.Equal(t, codersdk.EntitlementNotEntitled, aiBridgeFeature.Entitlement)
|
||||
|
||||
// Should NOT have the soft warning (the feature is not entitled so it won't be enabled).
|
||||
require.NotContains(t, entitlements.Warnings, aiBridgeWarningMessage)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUsageLimitFeatures(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user