mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
9edceef0bf
This pull request introduces support for external workspace management, allowing users to register and manage workspaces that are provisioned and managed outside of the Coder. Depends on: https://github.com/coder/terraform-provider-coder/pull/424 * GET /api/v2/init-script - Gets the agent initialization script * By default, it returns a script for Linux (amd64), but with query parameters (os and arch) you can get the init script for different platforms * GET /api/v2/workspaces/{workspace}/external-agent/{agent}/credentials - Gets credentials for an external agent **(enterprise)** * Updated queries to filter workspaces/templates by the has_external_agent field
1559 lines
58 KiB
Go
1559 lines
58 KiB
Go
package license_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/mock/gomock"
|
|
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/dbmock"
|
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
|
|
"github.com/coder/coder/v2/enterprise/coderd/license"
|
|
)
|
|
|
|
func TestEntitlements(t *testing.T) {
|
|
t.Parallel()
|
|
all := make(map[codersdk.FeatureName]bool)
|
|
for _, n := range codersdk.FeatureNames {
|
|
all[n] = true
|
|
}
|
|
|
|
empty := map[codersdk.FeatureName]bool{}
|
|
|
|
t.Run("Defaults", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
require.NoError(t, err)
|
|
require.False(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
for _, featureName := range codersdk.FeatureNames {
|
|
require.False(t, entitlements.Features[featureName].Enabled)
|
|
require.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement)
|
|
}
|
|
})
|
|
t.Run("Always return the current user count", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
require.NoError(t, err)
|
|
require.False(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
require.Equal(t, *entitlements.Features[codersdk.FeatureUserLimit].Actual, int64(0))
|
|
})
|
|
t.Run("SingleLicenseNothing", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{}),
|
|
Exp: dbtime.Now().Add(time.Hour),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, empty)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
for _, featureName := range codersdk.FeatureNames {
|
|
require.False(t, entitlements.Features[featureName].Enabled)
|
|
require.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement)
|
|
}
|
|
})
|
|
t.Run("SingleLicenseAll", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: func() license.Features {
|
|
f := make(license.Features)
|
|
for _, name := range codersdk.FeatureNames {
|
|
if name == codersdk.FeatureManagedAgentLimit {
|
|
f[codersdk.FeatureName("managed_agent_limit_soft")] = 100
|
|
f[codersdk.FeatureName("managed_agent_limit_hard")] = 200
|
|
continue
|
|
}
|
|
f[name] = 1
|
|
}
|
|
return f
|
|
}(),
|
|
}),
|
|
Exp: dbtime.Now().Add(time.Hour),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, empty)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
for _, featureName := range codersdk.FeatureNames {
|
|
require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[featureName].Entitlement, featureName)
|
|
}
|
|
})
|
|
t.Run("SingleLicenseGrace", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
codersdk.FeatureUserLimit: 100,
|
|
codersdk.FeatureAuditLog: 1,
|
|
},
|
|
|
|
NotBefore: dbtime.Now().Add(-time.Hour * 2),
|
|
GraceAt: dbtime.Now().Add(-time.Hour),
|
|
ExpiresAt: dbtime.Now().Add(time.Hour),
|
|
}),
|
|
Exp: dbtime.Now().Add(time.Hour),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
|
|
require.Equal(t, codersdk.EntitlementGracePeriod, entitlements.Features[codersdk.FeatureAuditLog].Entitlement)
|
|
require.Contains(
|
|
t, entitlements.Warnings,
|
|
fmt.Sprintf("%s is enabled but your license for this feature is expired.", codersdk.FeatureAuditLog.Humanize()),
|
|
)
|
|
})
|
|
t.Run("Expiration warning", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
codersdk.FeatureUserLimit: 100,
|
|
codersdk.FeatureAuditLog: 1,
|
|
},
|
|
|
|
GraceAt: dbtime.Now().AddDate(0, 0, 2),
|
|
ExpiresAt: dbtime.Now().AddDate(0, 0, 5),
|
|
}),
|
|
Exp: dbtime.Now().AddDate(0, 0, 5),
|
|
})
|
|
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
|
|
require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureAuditLog].Entitlement)
|
|
require.Contains(
|
|
t, entitlements.Warnings,
|
|
"Your license expires in 2 days.",
|
|
)
|
|
})
|
|
|
|
t.Run("Expiration warning for license expiring in 1 day", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
codersdk.FeatureUserLimit: 100,
|
|
codersdk.FeatureAuditLog: 1,
|
|
},
|
|
|
|
GraceAt: dbtime.Now().AddDate(0, 0, 1),
|
|
ExpiresAt: dbtime.Now().AddDate(0, 0, 5),
|
|
}),
|
|
Exp: time.Now().AddDate(0, 0, 5),
|
|
})
|
|
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
|
|
require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureAuditLog].Entitlement)
|
|
require.Contains(
|
|
t, entitlements.Warnings,
|
|
"Your license expires in 1 day.",
|
|
)
|
|
})
|
|
|
|
t.Run("Expiration warning for trials", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
codersdk.FeatureUserLimit: 100,
|
|
codersdk.FeatureAuditLog: 1,
|
|
},
|
|
|
|
Trial: true,
|
|
GraceAt: dbtime.Now().AddDate(0, 0, 8),
|
|
ExpiresAt: dbtime.Now().AddDate(0, 0, 5),
|
|
}),
|
|
Exp: dbtime.Now().AddDate(0, 0, 5),
|
|
})
|
|
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.True(t, entitlements.Trial)
|
|
|
|
require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureAuditLog].Entitlement)
|
|
require.NotContains( // it should not contain a warning since it is a trial license
|
|
t, entitlements.Warnings,
|
|
"Your license expires in 8 days.",
|
|
)
|
|
})
|
|
|
|
t.Run("Expiration warning for non trials", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
codersdk.FeatureUserLimit: 100,
|
|
codersdk.FeatureAuditLog: 1,
|
|
},
|
|
|
|
GraceAt: dbtime.Now().AddDate(0, 0, 30),
|
|
ExpiresAt: dbtime.Now().AddDate(0, 0, 5),
|
|
}),
|
|
Exp: dbtime.Now().AddDate(0, 0, 5),
|
|
})
|
|
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
|
|
require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureAuditLog].Entitlement)
|
|
require.NotContains( // it should not contain a warning since it is a trial license
|
|
t, entitlements.Warnings,
|
|
"Your license expires in 30 days.",
|
|
)
|
|
})
|
|
|
|
t.Run("SingleLicenseNotEntitled", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{}),
|
|
Exp: time.Now().Add(time.Hour),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
for _, featureName := range codersdk.FeatureNames {
|
|
if featureName == codersdk.FeatureUserLimit || featureName == codersdk.FeatureHighAvailability || featureName == codersdk.FeatureMultipleExternalAuth || featureName == codersdk.FeatureManagedAgentLimit {
|
|
// These fields don't generate warnings when not entitled unless
|
|
// a limit is breached.
|
|
continue
|
|
}
|
|
niceName := featureName.Humanize()
|
|
// Ensures features that are not entitled are properly disabled.
|
|
require.False(t, entitlements.Features[featureName].Enabled)
|
|
require.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement)
|
|
require.Contains(t, entitlements.Warnings, fmt.Sprintf("%s is enabled but your license is not entitled to this feature.", niceName))
|
|
}
|
|
})
|
|
t.Run("TooManyUsers", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
activeUser1, err := db.InsertUser(context.Background(), database.InsertUserParams{
|
|
ID: uuid.New(),
|
|
Username: "test1",
|
|
Email: "test1@coder.com",
|
|
LoginType: database.LoginTypePassword,
|
|
RBACRoles: []string{},
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.UpdateUserStatus(context.Background(), database.UpdateUserStatusParams{
|
|
ID: activeUser1.ID,
|
|
Status: database.UserStatusActive,
|
|
UpdatedAt: dbtime.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
activeUser2, err := db.InsertUser(context.Background(), database.InsertUserParams{
|
|
ID: uuid.New(),
|
|
Username: "test2",
|
|
Email: "test2@coder.com",
|
|
LoginType: database.LoginTypePassword,
|
|
RBACRoles: []string{},
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.UpdateUserStatus(context.Background(), database.UpdateUserStatusParams{
|
|
ID: activeUser2.ID,
|
|
Status: database.UserStatusActive,
|
|
UpdatedAt: dbtime.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.InsertUser(context.Background(), database.InsertUserParams{
|
|
ID: uuid.New(),
|
|
Username: "dormant-user",
|
|
Email: "dormant-user@coder.com",
|
|
LoginType: database.LoginTypePassword,
|
|
RBACRoles: []string{},
|
|
})
|
|
require.NoError(t, err)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
codersdk.FeatureUserLimit: 1,
|
|
},
|
|
}),
|
|
Exp: time.Now().Add(time.Hour),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, empty)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.Contains(t, entitlements.Warnings, "Your deployment has 2 active users but is only licensed for 1.")
|
|
})
|
|
t.Run("MaximizeUserLimit", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertUser(context.Background(), database.InsertUserParams{})
|
|
db.InsertUser(context.Background(), database.InsertUserParams{})
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
codersdk.FeatureUserLimit: 10,
|
|
},
|
|
GraceAt: time.Now().Add(59 * 24 * time.Hour),
|
|
}),
|
|
Exp: time.Now().Add(60 * 24 * time.Hour),
|
|
})
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
codersdk.FeatureUserLimit: 1,
|
|
},
|
|
GraceAt: time.Now().Add(59 * 24 * time.Hour),
|
|
}),
|
|
Exp: time.Now().Add(60 * 24 * time.Hour),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, empty)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.Empty(t, entitlements.Warnings)
|
|
})
|
|
t.Run("MultipleLicenseEnabled", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
// One trial
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
Exp: time.Now().Add(time.Hour),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Trial: true,
|
|
}),
|
|
})
|
|
// One not
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
Exp: time.Now().Add(time.Hour),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Trial: false,
|
|
}),
|
|
})
|
|
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, empty)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
})
|
|
|
|
t.Run("Enterprise", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
_, err := db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
Exp: time.Now().Add(time.Hour),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
FeatureSet: codersdk.FeatureSetEnterprise,
|
|
}),
|
|
})
|
|
require.NoError(t, err)
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
|
|
// All enterprise features should be entitled
|
|
enterpriseFeatures := codersdk.FeatureSetEnterprise.Features()
|
|
for _, featureName := range codersdk.FeatureNames {
|
|
if featureName == codersdk.FeatureUserLimit {
|
|
continue
|
|
}
|
|
if featureName == codersdk.FeatureManagedAgentLimit {
|
|
// Enterprise licenses don't get any agents by default.
|
|
continue
|
|
}
|
|
if slices.Contains(enterpriseFeatures, featureName) {
|
|
require.True(t, entitlements.Features[featureName].Enabled, featureName)
|
|
require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[featureName].Entitlement)
|
|
} else {
|
|
require.False(t, entitlements.Features[featureName].Enabled, featureName)
|
|
require.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Premium", func(t *testing.T) {
|
|
t.Parallel()
|
|
const userLimit = 1
|
|
const expectedAgentSoftLimit = 800 * userLimit
|
|
const expectedAgentHardLimit = 1000 * userLimit
|
|
|
|
db, _ := dbtestutil.NewDB(t)
|
|
licenseOptions := coderdenttest.LicenseOptions{
|
|
NotBefore: dbtime.Now().Add(-time.Hour * 2),
|
|
GraceAt: dbtime.Now().Add(time.Hour * 24),
|
|
ExpiresAt: dbtime.Now().Add(time.Hour * 24 * 2),
|
|
FeatureSet: codersdk.FeatureSetPremium,
|
|
Features: license.Features{
|
|
// Temporary: allows the default value for the
|
|
// managed_agent_limit feature to be used.
|
|
codersdk.FeatureUserLimit: 1,
|
|
},
|
|
}
|
|
_, err := db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
Exp: time.Now().Add(time.Hour),
|
|
JWT: coderdenttest.GenerateLicense(t, licenseOptions),
|
|
})
|
|
require.NoError(t, err)
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
|
|
// All premium features should be entitled
|
|
enterpriseFeatures := codersdk.FeatureSetPremium.Features()
|
|
for _, featureName := range codersdk.FeatureNames {
|
|
if featureName == codersdk.FeatureUserLimit {
|
|
continue
|
|
}
|
|
if featureName == codersdk.FeatureManagedAgentLimit {
|
|
agentEntitlement := entitlements.Features[featureName]
|
|
require.True(t, agentEntitlement.Enabled)
|
|
require.Equal(t, codersdk.EntitlementEntitled, agentEntitlement.Entitlement)
|
|
require.EqualValues(t, expectedAgentSoftLimit, *agentEntitlement.SoftLimit)
|
|
require.EqualValues(t, expectedAgentHardLimit, *agentEntitlement.Limit)
|
|
// This might be shocking, but there's a sound reason for this.
|
|
// See license.go for more details.
|
|
require.Equal(t, time.Date(2025, 7, 1, 0, 0, 0, 0, time.UTC), agentEntitlement.UsagePeriod.IssuedAt)
|
|
require.WithinDuration(t, licenseOptions.NotBefore, agentEntitlement.UsagePeriod.Start, time.Second)
|
|
require.WithinDuration(t, licenseOptions.ExpiresAt, agentEntitlement.UsagePeriod.End, time.Second)
|
|
continue
|
|
}
|
|
|
|
if slices.Contains(enterpriseFeatures, featureName) {
|
|
require.True(t, entitlements.Features[featureName].Enabled, featureName)
|
|
require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[featureName].Entitlement)
|
|
} else {
|
|
require.False(t, entitlements.Features[featureName].Enabled, featureName)
|
|
require.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("SetNone", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
_, err := db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
Exp: time.Now().Add(time.Hour),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
FeatureSet: "",
|
|
}),
|
|
})
|
|
require.NoError(t, err)
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
|
|
for _, featureName := range codersdk.FeatureNames {
|
|
require.False(t, entitlements.Features[featureName].Enabled, featureName)
|
|
require.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement)
|
|
}
|
|
})
|
|
|
|
// AllFeatures uses the deprecated 'AllFeatures' boolean.
|
|
t.Run("AllFeatures", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
Exp: time.Now().Add(time.Hour),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
AllFeatures: true,
|
|
}),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
|
|
// All enterprise features should be entitled
|
|
enterpriseFeatures := codersdk.FeatureSetEnterprise.Features()
|
|
for _, featureName := range codersdk.FeatureNames {
|
|
if featureName.UsesLimit() {
|
|
continue
|
|
}
|
|
if slices.Contains(enterpriseFeatures, featureName) {
|
|
require.True(t, entitlements.Features[featureName].Enabled, featureName)
|
|
require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[featureName].Entitlement)
|
|
} else {
|
|
require.False(t, entitlements.Features[featureName].Enabled, featureName)
|
|
require.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("AllFeaturesAlwaysEnable", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
Exp: dbtime.Now().Add(time.Hour),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
AllFeatures: true,
|
|
}),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, empty)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
// All enterprise features should be entitled
|
|
enterpriseFeatures := codersdk.FeatureSetEnterprise.Features()
|
|
for _, featureName := range codersdk.FeatureNames {
|
|
if featureName.UsesLimit() {
|
|
continue
|
|
}
|
|
|
|
feature := entitlements.Features[featureName]
|
|
if slices.Contains(enterpriseFeatures, featureName) {
|
|
require.Equal(t, featureName.AlwaysEnable(), feature.Enabled)
|
|
require.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement)
|
|
} else {
|
|
require.False(t, entitlements.Features[featureName].Enabled, featureName)
|
|
require.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("AllFeaturesGrace", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
Exp: dbtime.Now().Add(time.Hour),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
AllFeatures: true,
|
|
NotBefore: dbtime.Now().Add(-time.Hour * 2),
|
|
GraceAt: dbtime.Now().Add(-time.Hour),
|
|
ExpiresAt: dbtime.Now().Add(time.Hour),
|
|
}),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 1, coderdenttest.Keys, all)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.False(t, entitlements.Trial)
|
|
// All enterprise features should be entitled
|
|
enterpriseFeatures := codersdk.FeatureSetEnterprise.Features()
|
|
for _, featureName := range codersdk.FeatureNames {
|
|
if featureName == codersdk.FeatureUserLimit {
|
|
continue
|
|
}
|
|
if slices.Contains(enterpriseFeatures, featureName) {
|
|
require.True(t, entitlements.Features[featureName].Enabled, featureName)
|
|
require.Equal(t, codersdk.EntitlementGracePeriod, entitlements.Features[featureName].Entitlement)
|
|
} else {
|
|
require.False(t, entitlements.Features[featureName].Enabled, featureName)
|
|
require.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("MultipleReplicasNoLicense", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
entitlements, err := license.Entitlements(context.Background(), db, 2, 1, coderdenttest.Keys, all)
|
|
require.NoError(t, err)
|
|
require.False(t, entitlements.HasLicense)
|
|
require.Len(t, entitlements.Errors, 1)
|
|
require.Equal(t, "You have multiple replicas but high availability is an Enterprise feature. You will be unable to connect to workspaces.", entitlements.Errors[0])
|
|
})
|
|
|
|
t.Run("MultipleReplicasNotEntitled", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
Exp: time.Now().Add(time.Hour),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
codersdk.FeatureAuditLog: 1,
|
|
},
|
|
}),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 2, 1, coderdenttest.Keys, map[codersdk.FeatureName]bool{
|
|
codersdk.FeatureHighAvailability: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.Len(t, entitlements.Errors, 1)
|
|
require.Equal(t, "You have multiple replicas but your license is not entitled to high availability. You will be unable to connect to workspaces.", entitlements.Errors[0])
|
|
})
|
|
|
|
t.Run("MultipleReplicasGrace", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
codersdk.FeatureHighAvailability: 1,
|
|
},
|
|
NotBefore: time.Now().Add(-time.Hour * 2),
|
|
GraceAt: time.Now().Add(-time.Hour),
|
|
ExpiresAt: time.Now().Add(time.Hour),
|
|
}),
|
|
Exp: time.Now().Add(time.Hour),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 2, 1, coderdenttest.Keys, map[codersdk.FeatureName]bool{
|
|
codersdk.FeatureHighAvailability: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.Len(t, entitlements.Warnings, 1)
|
|
require.Equal(t, "You have multiple replicas but your license for high availability is expired. Reduce to one replica or workspace connections will stop working.", entitlements.Warnings[0])
|
|
})
|
|
|
|
t.Run("MultipleGitAuthNoLicense", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 2, coderdenttest.Keys, all)
|
|
require.NoError(t, err)
|
|
require.False(t, entitlements.HasLicense)
|
|
require.Len(t, entitlements.Errors, 1)
|
|
require.Equal(t, "You have multiple External Auth Providers configured but this is an Enterprise feature. Reduce to one.", entitlements.Errors[0])
|
|
})
|
|
|
|
t.Run("MultipleGitAuthNotEntitled", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
Exp: time.Now().Add(time.Hour),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
codersdk.FeatureAuditLog: 1,
|
|
},
|
|
}),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 2, coderdenttest.Keys, map[codersdk.FeatureName]bool{
|
|
codersdk.FeatureMultipleExternalAuth: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.Len(t, entitlements.Errors, 1)
|
|
require.Equal(t, "You have multiple External Auth Providers configured but your license is limited at one.", entitlements.Errors[0])
|
|
})
|
|
|
|
t.Run("MultipleGitAuthGrace", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
NotBefore: time.Now().Add(-time.Hour * 2),
|
|
GraceAt: time.Now().Add(-time.Hour),
|
|
ExpiresAt: time.Now().Add(time.Hour),
|
|
Features: license.Features{
|
|
codersdk.FeatureMultipleExternalAuth: 1,
|
|
},
|
|
}),
|
|
Exp: time.Now().Add(time.Hour),
|
|
})
|
|
entitlements, err := license.Entitlements(context.Background(), db, 1, 2, coderdenttest.Keys, map[codersdk.FeatureName]bool{
|
|
codersdk.FeatureMultipleExternalAuth: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
require.Len(t, entitlements.Warnings, 1)
|
|
require.Equal(t, "You have multiple External Auth Providers configured but your license is expired. Reduce to one.", entitlements.Warnings[0])
|
|
})
|
|
|
|
t.Run("ManagedAgentLimitHasValue", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Use a mock database for this test so I don't need to make real
|
|
// workspace builds.
|
|
ctrl := gomock.NewController(t)
|
|
mDB := dbmock.NewMockStore(ctrl)
|
|
|
|
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
|
|
}).
|
|
UserLimit(100).
|
|
ManagedAgentLimit(100, 200)
|
|
|
|
lic := database.License{
|
|
ID: 1,
|
|
JWT: coderdenttest.GenerateLicense(t, *licenseOpts),
|
|
Exp: licenseOpts.ExpiresAt,
|
|
}
|
|
|
|
mDB.EXPECT().
|
|
GetUnexpiredLicenses(gomock.Any()).
|
|
Return([]database.License{lic}, nil)
|
|
mDB.EXPECT().
|
|
GetActiveUserCount(gomock.Any(), false).
|
|
Return(int64(1), nil)
|
|
mDB.EXPECT().
|
|
GetManagedAgentCount(gomock.Any(), gomock.Cond(func(params database.GetManagedAgentCountParams) bool {
|
|
// gomock doesn't seem to compare times very nicely.
|
|
if !assert.WithinDuration(t, licenseOpts.NotBefore, params.StartTime, time.Second) {
|
|
return false
|
|
}
|
|
if !assert.WithinDuration(t, licenseOpts.ExpiresAt, params.EndTime, time.Second) {
|
|
return false
|
|
}
|
|
return true
|
|
})).
|
|
Return(int64(175), nil)
|
|
mDB.EXPECT().
|
|
GetWorkspaces(gomock.Any(), gomock.Any()).
|
|
Return([]database.GetWorkspacesRow{}, nil)
|
|
mDB.EXPECT().
|
|
GetTemplatesWithFilter(gomock.Any(), gomock.Any()).
|
|
Return([]database.Template{}, nil)
|
|
|
|
entitlements, err := license.Entitlements(context.Background(), mDB, 1, 0, coderdenttest.Keys, all)
|
|
require.NoError(t, err)
|
|
require.True(t, entitlements.HasLicense)
|
|
|
|
managedAgentLimit, ok := entitlements.Features[codersdk.FeatureManagedAgentLimit]
|
|
require.True(t, ok)
|
|
require.NotNil(t, managedAgentLimit.SoftLimit)
|
|
require.EqualValues(t, 100, *managedAgentLimit.SoftLimit)
|
|
require.NotNil(t, managedAgentLimit.Limit)
|
|
require.EqualValues(t, 200, *managedAgentLimit.Limit)
|
|
require.NotNil(t, managedAgentLimit.Actual)
|
|
require.EqualValues(t, 175, *managedAgentLimit.Actual)
|
|
|
|
// Should've also populated a warning.
|
|
require.Len(t, entitlements.Warnings, 1)
|
|
require.Equal(t, "You are approaching the managed agent limit in your license. Please refer to the Deployment Licenses page for more information.", entitlements.Warnings[0])
|
|
})
|
|
}
|
|
|
|
func TestLicenseEntitlements(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// We must use actual 'time.Now()' in tests because the jwt library does
|
|
// not accept a custom time function. The only way to change it is as a
|
|
// package global, which does not work in t.Parallel().
|
|
|
|
// This list comes from coderd.go on launch. This list is a bit arbitrary,
|
|
// maybe some should be moved to "AlwaysEnabled" instead.
|
|
defaultEnablements := map[codersdk.FeatureName]bool{
|
|
codersdk.FeatureAuditLog: true,
|
|
codersdk.FeatureConnectionLog: true,
|
|
codersdk.FeatureBrowserOnly: true,
|
|
codersdk.FeatureSCIM: true,
|
|
codersdk.FeatureMultipleExternalAuth: true,
|
|
codersdk.FeatureTemplateRBAC: true,
|
|
codersdk.FeatureExternalTokenEncryption: true,
|
|
codersdk.FeatureExternalProvisionerDaemons: true,
|
|
codersdk.FeatureAdvancedTemplateScheduling: true,
|
|
codersdk.FeatureWorkspaceProxy: true,
|
|
codersdk.FeatureUserRoleManagement: true,
|
|
codersdk.FeatureAccessControl: true,
|
|
codersdk.FeatureControlSharedPorts: true,
|
|
codersdk.FeatureWorkspaceExternalAgent: true,
|
|
}
|
|
|
|
legacyLicense := func() *coderdenttest.LicenseOptions {
|
|
return (&coderdenttest.LicenseOptions{
|
|
AccountType: "salesforce",
|
|
AccountID: "Alice",
|
|
Trial: false,
|
|
// Use the legacy boolean
|
|
AllFeatures: true,
|
|
}).Valid(time.Now())
|
|
}
|
|
|
|
enterpriseLicense := func() *coderdenttest.LicenseOptions {
|
|
return (&coderdenttest.LicenseOptions{
|
|
AccountType: "salesforce",
|
|
AccountID: "Bob",
|
|
DeploymentIDs: nil,
|
|
Trial: false,
|
|
FeatureSet: codersdk.FeatureSetEnterprise,
|
|
AllFeatures: true,
|
|
}).Valid(time.Now())
|
|
}
|
|
|
|
premiumLicense := func() *coderdenttest.LicenseOptions {
|
|
return (&coderdenttest.LicenseOptions{
|
|
AccountType: "salesforce",
|
|
AccountID: "Charlie",
|
|
DeploymentIDs: nil,
|
|
Trial: false,
|
|
FeatureSet: codersdk.FeatureSetPremium,
|
|
AllFeatures: true,
|
|
}).Valid(time.Now())
|
|
}
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
Licenses []*coderdenttest.LicenseOptions
|
|
Enablements map[codersdk.FeatureName]bool
|
|
Arguments license.FeatureArguments
|
|
|
|
ExpectedErrorContains string
|
|
AssertEntitlements func(t *testing.T, entitlements codersdk.Entitlements)
|
|
}{
|
|
{
|
|
Name: "NoLicenses",
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assertNoErrors(t, entitlements)
|
|
assertNoWarnings(t, entitlements)
|
|
assert.False(t, entitlements.HasLicense)
|
|
assert.False(t, entitlements.Trial)
|
|
},
|
|
},
|
|
{
|
|
Name: "MixedUsedCounts",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
legacyLicense().UserLimit(100),
|
|
enterpriseLicense().UserLimit(500),
|
|
},
|
|
Enablements: defaultEnablements,
|
|
Arguments: license.FeatureArguments{
|
|
ActiveUserCount: 50,
|
|
ReplicaCount: 0,
|
|
ExternalAuthCount: 0,
|
|
},
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assertEnterpriseFeatures(t, entitlements)
|
|
assertNoErrors(t, entitlements)
|
|
assertNoWarnings(t, entitlements)
|
|
userFeature := entitlements.Features[codersdk.FeatureUserLimit]
|
|
assert.Equalf(t, int64(500), *userFeature.Limit, "user limit")
|
|
assert.Equalf(t, int64(50), *userFeature.Actual, "user count")
|
|
},
|
|
},
|
|
{
|
|
Name: "MixedUsedCountsWithExpired",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
// This license is ignored
|
|
enterpriseLicense().UserLimit(500).Expired(time.Now()),
|
|
enterpriseLicense().UserLimit(100),
|
|
},
|
|
Enablements: defaultEnablements,
|
|
Arguments: license.FeatureArguments{
|
|
ActiveUserCount: 200,
|
|
ReplicaCount: 0,
|
|
ExternalAuthCount: 0,
|
|
},
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assertEnterpriseFeatures(t, entitlements)
|
|
userFeature := entitlements.Features[codersdk.FeatureUserLimit]
|
|
assert.Equalf(t, int64(100), *userFeature.Limit, "user limit")
|
|
assert.Equalf(t, int64(200), *userFeature.Actual, "user count")
|
|
|
|
require.Len(t, entitlements.Errors, 1, "invalid license error")
|
|
require.Len(t, entitlements.Warnings, 1, "user count exceeds warning")
|
|
require.Contains(t, entitlements.Errors[0], "Invalid license")
|
|
require.Contains(t, entitlements.Warnings[0], "active users but is only licensed for")
|
|
},
|
|
},
|
|
{
|
|
// The new license does not have enough seats to cover the active user count.
|
|
// The old license is in it's grace period.
|
|
Name: "MixedUsedCountsWithGrace",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
enterpriseLicense().UserLimit(500).GracePeriod(time.Now()),
|
|
enterpriseLicense().UserLimit(100),
|
|
},
|
|
Enablements: defaultEnablements,
|
|
Arguments: license.FeatureArguments{
|
|
ActiveUserCount: 200,
|
|
ReplicaCount: 0,
|
|
ExternalAuthCount: 0,
|
|
},
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
userFeature := entitlements.Features[codersdk.FeatureUserLimit]
|
|
assert.Equalf(t, int64(500), *userFeature.Limit, "user limit")
|
|
assert.Equalf(t, int64(200), *userFeature.Actual, "user count")
|
|
assert.Equal(t, userFeature.Entitlement, codersdk.EntitlementGracePeriod)
|
|
},
|
|
},
|
|
{
|
|
// Legacy license uses the "AllFeatures" boolean
|
|
Name: "LegacyLicense",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
legacyLicense().UserLimit(100),
|
|
},
|
|
Enablements: defaultEnablements,
|
|
Arguments: license.FeatureArguments{
|
|
ActiveUserCount: 50,
|
|
ReplicaCount: 0,
|
|
ExternalAuthCount: 0,
|
|
},
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assertEnterpriseFeatures(t, entitlements)
|
|
assertNoErrors(t, entitlements)
|
|
assertNoWarnings(t, entitlements)
|
|
userFeature := entitlements.Features[codersdk.FeatureUserLimit]
|
|
assert.Equalf(t, int64(100), *userFeature.Limit, "user limit")
|
|
assert.Equalf(t, int64(50), *userFeature.Actual, "user count")
|
|
},
|
|
},
|
|
{
|
|
Name: "EnterpriseDisabledMultiOrg",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
enterpriseLicense().UserLimit(100),
|
|
},
|
|
Enablements: defaultEnablements,
|
|
Arguments: license.FeatureArguments{},
|
|
ExpectedErrorContains: "",
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assert.False(t, entitlements.Features[codersdk.FeatureMultipleOrganizations].Enabled, "multi-org only enabled for premium")
|
|
assert.False(t, entitlements.Features[codersdk.FeatureCustomRoles].Enabled, "custom-roles only enabled for premium")
|
|
},
|
|
},
|
|
{
|
|
Name: "PremiumEnabledMultiOrg",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
premiumLicense().UserLimit(100),
|
|
},
|
|
Enablements: defaultEnablements,
|
|
Arguments: license.FeatureArguments{},
|
|
ExpectedErrorContains: "",
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assert.True(t, entitlements.Features[codersdk.FeatureMultipleOrganizations].Enabled, "multi-org enabled for premium")
|
|
assert.True(t, entitlements.Features[codersdk.FeatureCustomRoles].Enabled, "custom-roles enabled for premium")
|
|
},
|
|
},
|
|
{
|
|
Name: "CurrentAndFuture",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
enterpriseLicense().UserLimit(100),
|
|
premiumLicense().UserLimit(200).FutureTerm(time.Now()),
|
|
},
|
|
Enablements: defaultEnablements,
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assertEnterpriseFeatures(t, entitlements)
|
|
assertNoErrors(t, entitlements)
|
|
assertNoWarnings(t, entitlements)
|
|
userFeature := entitlements.Features[codersdk.FeatureUserLimit]
|
|
assert.Equalf(t, int64(100), *userFeature.Limit, "user limit")
|
|
assert.Equal(t, codersdk.EntitlementNotEntitled,
|
|
entitlements.Features[codersdk.FeatureMultipleOrganizations].Entitlement)
|
|
assert.Equal(t, codersdk.EntitlementNotEntitled,
|
|
entitlements.Features[codersdk.FeatureCustomRoles].Entitlement)
|
|
},
|
|
},
|
|
{
|
|
Name: "ManagedAgentLimit",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
enterpriseLicense().UserLimit(100).ManagedAgentLimit(100, 200),
|
|
},
|
|
Arguments: license.FeatureArguments{
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
// 175 will generate a warning as it's over 75% of the
|
|
// difference between the soft and hard limit.
|
|
return 174, nil
|
|
},
|
|
},
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assertNoErrors(t, entitlements)
|
|
assertNoWarnings(t, entitlements)
|
|
feature := entitlements.Features[codersdk.FeatureManagedAgentLimit]
|
|
assert.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement)
|
|
assert.True(t, feature.Enabled)
|
|
assert.Equal(t, int64(100), *feature.SoftLimit)
|
|
assert.Equal(t, int64(200), *feature.Limit)
|
|
assert.Equal(t, int64(174), *feature.Actual)
|
|
},
|
|
},
|
|
{
|
|
Name: "ManagedAgentLimitWithGrace",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
// Add another license that is not entitled to managed agents to
|
|
// suppress warnings for other features.
|
|
enterpriseLicense().
|
|
UserLimit(100).
|
|
WithIssuedAt(time.Now().Add(-time.Hour * 2)),
|
|
enterpriseLicense().
|
|
UserLimit(100).
|
|
ManagedAgentLimit(100, 100).
|
|
WithIssuedAt(time.Now().Add(-time.Hour * 1)).
|
|
GracePeriod(time.Now()),
|
|
},
|
|
Arguments: license.FeatureArguments{
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
// When the soft and hard limit are equal, the warning is
|
|
// triggered at 75% of the hard limit.
|
|
return 74, nil
|
|
},
|
|
},
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assertNoErrors(t, entitlements)
|
|
assertNoWarnings(t, entitlements)
|
|
feature := entitlements.Features[codersdk.FeatureManagedAgentLimit]
|
|
assert.Equal(t, codersdk.EntitlementGracePeriod, feature.Entitlement)
|
|
assert.True(t, feature.Enabled)
|
|
assert.Equal(t, int64(100), *feature.SoftLimit)
|
|
assert.Equal(t, int64(100), *feature.Limit)
|
|
assert.Equal(t, int64(74), *feature.Actual)
|
|
},
|
|
},
|
|
{
|
|
Name: "ManagedAgentLimitWithExpired",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
// Add another license that is not entitled to managed agents to
|
|
// suppress warnings for other features.
|
|
enterpriseLicense().
|
|
UserLimit(100).
|
|
WithIssuedAt(time.Now().Add(-time.Hour * 2)),
|
|
enterpriseLicense().
|
|
UserLimit(100).
|
|
ManagedAgentLimit(100, 200).
|
|
WithIssuedAt(time.Now().Add(-time.Hour * 1)).
|
|
Expired(time.Now()),
|
|
},
|
|
Arguments: license.FeatureArguments{
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
return 10, nil
|
|
},
|
|
},
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
feature := entitlements.Features[codersdk.FeatureManagedAgentLimit]
|
|
assert.Equal(t, codersdk.EntitlementNotEntitled, feature.Entitlement)
|
|
assert.False(t, feature.Enabled)
|
|
assert.Nil(t, feature.SoftLimit)
|
|
assert.Nil(t, feature.Limit)
|
|
assert.Nil(t, feature.Actual)
|
|
},
|
|
},
|
|
{
|
|
Name: "ManagedAgentLimitWarning/ApproachingLimit/DifferentSoftAndHardLimit",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
enterpriseLicense().
|
|
UserLimit(100).
|
|
ManagedAgentLimit(100, 200),
|
|
},
|
|
Arguments: license.FeatureArguments{
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
return 175, nil
|
|
},
|
|
},
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assert.Len(t, entitlements.Warnings, 1)
|
|
assert.Equal(t, "You are approaching the managed agent limit in your license. Please refer to the Deployment Licenses page for more information.", entitlements.Warnings[0])
|
|
assertNoErrors(t, entitlements)
|
|
|
|
feature := entitlements.Features[codersdk.FeatureManagedAgentLimit]
|
|
assert.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement)
|
|
assert.True(t, feature.Enabled)
|
|
assert.Equal(t, int64(100), *feature.SoftLimit)
|
|
assert.Equal(t, int64(200), *feature.Limit)
|
|
assert.Equal(t, int64(175), *feature.Actual)
|
|
},
|
|
},
|
|
{
|
|
Name: "ManagedAgentLimitWarning/ApproachingLimit/EqualSoftAndHardLimit",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
enterpriseLicense().
|
|
UserLimit(100).
|
|
ManagedAgentLimit(100, 100),
|
|
},
|
|
Arguments: license.FeatureArguments{
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
return 75, nil
|
|
},
|
|
},
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assert.Len(t, entitlements.Warnings, 1)
|
|
assert.Equal(t, "You are approaching the managed agent limit in your license. Please refer to the Deployment Licenses page for more information.", entitlements.Warnings[0])
|
|
assertNoErrors(t, entitlements)
|
|
|
|
feature := entitlements.Features[codersdk.FeatureManagedAgentLimit]
|
|
assert.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement)
|
|
assert.True(t, feature.Enabled)
|
|
assert.Equal(t, int64(100), *feature.SoftLimit)
|
|
assert.Equal(t, int64(100), *feature.Limit)
|
|
assert.Equal(t, int64(75), *feature.Actual)
|
|
},
|
|
},
|
|
{
|
|
Name: "ManagedAgentLimitWarning/BreachedLimit",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
enterpriseLicense().
|
|
UserLimit(100).
|
|
ManagedAgentLimit(100, 200),
|
|
},
|
|
Arguments: license.FeatureArguments{
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
return 200, nil
|
|
},
|
|
},
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assert.Len(t, entitlements.Warnings, 1)
|
|
assert.Equal(t, "You have built more workspaces with managed agents than your license allows. Further managed agent builds will be blocked.", entitlements.Warnings[0])
|
|
assertNoErrors(t, entitlements)
|
|
|
|
feature := entitlements.Features[codersdk.FeatureManagedAgentLimit]
|
|
assert.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement)
|
|
assert.True(t, feature.Enabled)
|
|
assert.Equal(t, int64(100), *feature.SoftLimit)
|
|
assert.Equal(t, int64(200), *feature.Limit)
|
|
assert.Equal(t, int64(200), *feature.Actual)
|
|
},
|
|
},
|
|
{
|
|
Name: "ExternalWorkspace",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
enterpriseLicense().UserLimit(100),
|
|
},
|
|
Arguments: license.FeatureArguments{
|
|
ExternalWorkspaceCount: 1,
|
|
},
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assert.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureWorkspaceExternalAgent].Entitlement)
|
|
assert.True(t, entitlements.Features[codersdk.FeatureWorkspaceExternalAgent].Enabled)
|
|
},
|
|
},
|
|
{
|
|
Name: "ExternalTemplate",
|
|
Licenses: []*coderdenttest.LicenseOptions{
|
|
enterpriseLicense().UserLimit(100),
|
|
},
|
|
Arguments: license.FeatureArguments{
|
|
ExternalTemplateCount: 1,
|
|
},
|
|
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
|
|
assert.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureWorkspaceExternalAgent].Entitlement)
|
|
assert.True(t, entitlements.Features[codersdk.FeatureWorkspaceExternalAgent].Enabled)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
generatedLicenses := make([]database.License, 0, len(tc.Licenses))
|
|
for i, lo := range tc.Licenses {
|
|
generatedLicenses = append(generatedLicenses, database.License{
|
|
ID: int32(i), // nolint:gosec
|
|
UploadedAt: time.Now().Add(time.Hour * -1),
|
|
JWT: lo.Generate(t),
|
|
Exp: lo.GraceAt,
|
|
UUID: uuid.New(),
|
|
})
|
|
}
|
|
|
|
// Default to 0 managed agent count.
|
|
if tc.Arguments.ManagedAgentCountFn == nil {
|
|
tc.Arguments.ManagedAgentCountFn = func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
return 0, nil
|
|
}
|
|
}
|
|
|
|
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), generatedLicenses, tc.Enablements, coderdenttest.Keys, tc.Arguments)
|
|
if tc.ExpectedErrorContains != "" {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), tc.ExpectedErrorContains)
|
|
} else {
|
|
require.NoError(t, err)
|
|
tc.AssertEntitlements(t, entitlements)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUsageLimitFeatures(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
sdkFeatureName codersdk.FeatureName
|
|
softLimitFeatureName codersdk.FeatureName
|
|
hardLimitFeatureName codersdk.FeatureName
|
|
}{
|
|
{
|
|
sdkFeatureName: codersdk.FeatureManagedAgentLimit,
|
|
softLimitFeatureName: codersdk.FeatureName("managed_agent_limit_soft"),
|
|
hardLimitFeatureName: codersdk.FeatureName("managed_agent_limit_hard"),
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(string(c.sdkFeatureName), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Test for either a missing soft or hard limit feature value.
|
|
t.Run("MissingGroupedFeature", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, feature := range []codersdk.FeatureName{
|
|
c.softLimitFeatureName,
|
|
c.hardLimitFeatureName,
|
|
} {
|
|
t.Run(string(feature), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
lic := database.License{
|
|
ID: 1,
|
|
UploadedAt: time.Now(),
|
|
Exp: time.Now().Add(time.Hour),
|
|
UUID: uuid.New(),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
feature: 100,
|
|
},
|
|
}),
|
|
}
|
|
|
|
arguments := license.FeatureArguments{
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
return 0, nil
|
|
},
|
|
}
|
|
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), []database.License{lic}, map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments)
|
|
require.NoError(t, err)
|
|
|
|
feature, ok := entitlements.Features[c.sdkFeatureName]
|
|
require.True(t, ok, "feature %s not found", c.sdkFeatureName)
|
|
require.Equal(t, codersdk.EntitlementNotEntitled, feature.Entitlement)
|
|
|
|
require.Len(t, entitlements.Errors, 1)
|
|
require.Equal(t, fmt.Sprintf("Invalid license (%v): feature %s has missing soft or hard limit values", lic.UUID, c.sdkFeatureName), entitlements.Errors[0])
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("HardBelowSoft", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
lic := database.License{
|
|
ID: 1,
|
|
UploadedAt: time.Now(),
|
|
Exp: time.Now().Add(time.Hour),
|
|
UUID: uuid.New(),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
Features: license.Features{
|
|
c.softLimitFeatureName: 100,
|
|
c.hardLimitFeatureName: 50,
|
|
},
|
|
}),
|
|
}
|
|
|
|
arguments := license.FeatureArguments{
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
return 0, nil
|
|
},
|
|
}
|
|
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), []database.License{lic}, map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments)
|
|
require.NoError(t, err)
|
|
|
|
feature, ok := entitlements.Features[c.sdkFeatureName]
|
|
require.True(t, ok, "feature %s not found", c.sdkFeatureName)
|
|
require.Equal(t, codersdk.EntitlementNotEntitled, feature.Entitlement)
|
|
|
|
require.Len(t, entitlements.Errors, 1)
|
|
require.Equal(t, fmt.Sprintf("Invalid license (%v): feature %s has a hard limit less than the soft limit", lic.UUID, c.sdkFeatureName), entitlements.Errors[0])
|
|
})
|
|
|
|
// Ensures that these features are ranked by issued at, not by
|
|
// values.
|
|
t.Run("IssuedAtRanking", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Generate 2 real licenses both with managed agent limit
|
|
// features. lic2 should trump lic1 even though it has a lower
|
|
// limit, because it was issued later.
|
|
lic1 := database.License{
|
|
ID: 1,
|
|
UploadedAt: time.Now(),
|
|
Exp: time.Now().Add(time.Hour),
|
|
UUID: uuid.New(),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
IssuedAt: time.Now().Add(-time.Minute * 2),
|
|
NotBefore: time.Now().Add(-time.Minute * 2),
|
|
ExpiresAt: time.Now().Add(time.Hour * 2),
|
|
Features: license.Features{
|
|
c.softLimitFeatureName: 100,
|
|
c.hardLimitFeatureName: 200,
|
|
},
|
|
}),
|
|
}
|
|
lic2Iat := time.Now().Add(-time.Minute * 1)
|
|
lic2Nbf := lic2Iat.Add(-time.Minute)
|
|
lic2Exp := lic2Iat.Add(time.Hour)
|
|
lic2 := database.License{
|
|
ID: 2,
|
|
UploadedAt: time.Now(),
|
|
Exp: lic2Exp,
|
|
UUID: uuid.New(),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
IssuedAt: lic2Iat,
|
|
NotBefore: lic2Nbf,
|
|
ExpiresAt: lic2Exp,
|
|
Features: license.Features{
|
|
c.softLimitFeatureName: 50,
|
|
c.hardLimitFeatureName: 100,
|
|
},
|
|
}),
|
|
}
|
|
|
|
const actualAgents = 10
|
|
arguments := license.FeatureArguments{
|
|
ActiveUserCount: 10,
|
|
ReplicaCount: 0,
|
|
ExternalAuthCount: 0,
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
return actualAgents, nil
|
|
},
|
|
}
|
|
|
|
// Load the licenses in both orders to ensure the correct
|
|
// behavior is observed no matter the order.
|
|
for _, order := range [][]database.License{
|
|
{lic1, lic2},
|
|
{lic2, lic1},
|
|
} {
|
|
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), order, map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments)
|
|
require.NoError(t, err)
|
|
|
|
feature, ok := entitlements.Features[c.sdkFeatureName]
|
|
require.True(t, ok, "feature %s not found", c.sdkFeatureName)
|
|
require.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement)
|
|
require.NotNil(t, feature.Limit)
|
|
require.EqualValues(t, 100, *feature.Limit)
|
|
require.NotNil(t, feature.SoftLimit)
|
|
require.EqualValues(t, 50, *feature.SoftLimit)
|
|
require.NotNil(t, feature.Actual)
|
|
require.EqualValues(t, actualAgents, *feature.Actual)
|
|
require.NotNil(t, feature.UsagePeriod)
|
|
require.WithinDuration(t, lic2Iat, feature.UsagePeriod.IssuedAt, 2*time.Second)
|
|
require.WithinDuration(t, lic2Nbf, feature.UsagePeriod.Start, 2*time.Second)
|
|
require.WithinDuration(t, lic2Exp, feature.UsagePeriod.End, 2*time.Second)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestManagedAgentLimitDefault(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// "Enterprise" licenses should not receive a default managed agent limit.
|
|
t.Run("Enterprise", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
lic := database.License{
|
|
ID: 1,
|
|
UploadedAt: time.Now(),
|
|
Exp: time.Now().Add(time.Hour),
|
|
UUID: uuid.New(),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
FeatureSet: codersdk.FeatureSetEnterprise,
|
|
Features: license.Features{
|
|
codersdk.FeatureUserLimit: 100,
|
|
},
|
|
}),
|
|
}
|
|
|
|
arguments := license.FeatureArguments{
|
|
ActiveUserCount: 10,
|
|
ReplicaCount: 0,
|
|
ExternalAuthCount: 0,
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
return 0, nil
|
|
},
|
|
}
|
|
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), []database.License{lic}, map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments)
|
|
require.NoError(t, err)
|
|
|
|
feature, ok := entitlements.Features[codersdk.FeatureManagedAgentLimit]
|
|
require.True(t, ok, "feature %s not found", codersdk.FeatureManagedAgentLimit)
|
|
require.Equal(t, codersdk.EntitlementNotEntitled, feature.Entitlement)
|
|
require.Nil(t, feature.Limit)
|
|
require.Nil(t, feature.SoftLimit)
|
|
require.Nil(t, feature.Actual)
|
|
require.Nil(t, feature.UsagePeriod)
|
|
})
|
|
|
|
// "Premium" licenses should receive a default managed agent limit of:
|
|
// soft = 800 * user_limit
|
|
// hard = 1000 * user_limit
|
|
t.Run("Premium", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const userLimit = 100
|
|
const softLimit = 800 * userLimit
|
|
const hardLimit = 1000 * userLimit
|
|
lic := database.License{
|
|
ID: 1,
|
|
UploadedAt: time.Now(),
|
|
Exp: time.Now().Add(time.Hour),
|
|
UUID: uuid.New(),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
FeatureSet: codersdk.FeatureSetPremium,
|
|
Features: license.Features{
|
|
codersdk.FeatureUserLimit: userLimit,
|
|
},
|
|
}),
|
|
}
|
|
|
|
const actualAgents = 10
|
|
arguments := license.FeatureArguments{
|
|
ActiveUserCount: 10,
|
|
ReplicaCount: 0,
|
|
ExternalAuthCount: 0,
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
return actualAgents, nil
|
|
},
|
|
}
|
|
|
|
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), []database.License{lic}, map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments)
|
|
require.NoError(t, err)
|
|
|
|
feature, ok := entitlements.Features[codersdk.FeatureManagedAgentLimit]
|
|
require.True(t, ok, "feature %s not found", codersdk.FeatureManagedAgentLimit)
|
|
require.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement)
|
|
require.NotNil(t, feature.Limit)
|
|
require.EqualValues(t, hardLimit, *feature.Limit)
|
|
require.NotNil(t, feature.SoftLimit)
|
|
require.EqualValues(t, softLimit, *feature.SoftLimit)
|
|
require.NotNil(t, feature.Actual)
|
|
require.EqualValues(t, actualAgents, *feature.Actual)
|
|
require.NotNil(t, feature.UsagePeriod)
|
|
require.NotZero(t, feature.UsagePeriod.IssuedAt)
|
|
require.NotZero(t, feature.UsagePeriod.Start)
|
|
require.NotZero(t, feature.UsagePeriod.End)
|
|
})
|
|
|
|
// "Premium" licenses with an explicit managed agent limit should not
|
|
// receive a default managed agent limit.
|
|
t.Run("PremiumExplicitValues", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
lic := database.License{
|
|
ID: 1,
|
|
UploadedAt: time.Now(),
|
|
Exp: time.Now().Add(time.Hour),
|
|
UUID: uuid.New(),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
FeatureSet: codersdk.FeatureSetPremium,
|
|
Features: license.Features{
|
|
codersdk.FeatureUserLimit: 100,
|
|
codersdk.FeatureName("managed_agent_limit_soft"): 100,
|
|
codersdk.FeatureName("managed_agent_limit_hard"): 200,
|
|
},
|
|
}),
|
|
}
|
|
|
|
const actualAgents = 10
|
|
arguments := license.FeatureArguments{
|
|
ActiveUserCount: 10,
|
|
ReplicaCount: 0,
|
|
ExternalAuthCount: 0,
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
return actualAgents, nil
|
|
},
|
|
}
|
|
|
|
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), []database.License{lic}, map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments)
|
|
require.NoError(t, err)
|
|
|
|
feature, ok := entitlements.Features[codersdk.FeatureManagedAgentLimit]
|
|
require.True(t, ok, "feature %s not found", codersdk.FeatureManagedAgentLimit)
|
|
require.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement)
|
|
require.NotNil(t, feature.Limit)
|
|
require.EqualValues(t, 200, *feature.Limit)
|
|
require.NotNil(t, feature.SoftLimit)
|
|
require.EqualValues(t, 100, *feature.SoftLimit)
|
|
require.NotNil(t, feature.Actual)
|
|
require.EqualValues(t, actualAgents, *feature.Actual)
|
|
require.NotNil(t, feature.UsagePeriod)
|
|
require.NotZero(t, feature.UsagePeriod.IssuedAt)
|
|
require.NotZero(t, feature.UsagePeriod.Start)
|
|
require.NotZero(t, feature.UsagePeriod.End)
|
|
})
|
|
|
|
// "Premium" licenses with an explicit 0 count should be entitled to 0
|
|
// agents and should not receive a default managed agent limit.
|
|
t.Run("PremiumExplicitZero", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
lic := database.License{
|
|
ID: 1,
|
|
UploadedAt: time.Now(),
|
|
Exp: time.Now().Add(time.Hour),
|
|
UUID: uuid.New(),
|
|
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
|
FeatureSet: codersdk.FeatureSetPremium,
|
|
Features: license.Features{
|
|
codersdk.FeatureUserLimit: 100,
|
|
codersdk.FeatureName("managed_agent_limit_soft"): 0,
|
|
codersdk.FeatureName("managed_agent_limit_hard"): 0,
|
|
},
|
|
}),
|
|
}
|
|
|
|
const actualAgents = 10
|
|
arguments := license.FeatureArguments{
|
|
ActiveUserCount: 10,
|
|
ReplicaCount: 0,
|
|
ExternalAuthCount: 0,
|
|
ManagedAgentCountFn: func(ctx context.Context, from time.Time, to time.Time) (int64, error) {
|
|
return actualAgents, nil
|
|
},
|
|
}
|
|
|
|
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), []database.License{lic}, map[codersdk.FeatureName]bool{}, coderdenttest.Keys, arguments)
|
|
require.NoError(t, err)
|
|
|
|
feature, ok := entitlements.Features[codersdk.FeatureManagedAgentLimit]
|
|
require.True(t, ok, "feature %s not found", codersdk.FeatureManagedAgentLimit)
|
|
require.Equal(t, codersdk.EntitlementEntitled, feature.Entitlement)
|
|
require.False(t, feature.Enabled)
|
|
require.NotNil(t, feature.Limit)
|
|
require.EqualValues(t, 0, *feature.Limit)
|
|
require.NotNil(t, feature.SoftLimit)
|
|
require.EqualValues(t, 0, *feature.SoftLimit)
|
|
require.NotNil(t, feature.Actual)
|
|
require.EqualValues(t, actualAgents, *feature.Actual)
|
|
require.NotNil(t, feature.UsagePeriod)
|
|
require.NotZero(t, feature.UsagePeriod.IssuedAt)
|
|
require.NotZero(t, feature.UsagePeriod.Start)
|
|
require.NotZero(t, feature.UsagePeriod.End)
|
|
})
|
|
}
|
|
|
|
func assertNoErrors(t *testing.T, entitlements codersdk.Entitlements) {
|
|
t.Helper()
|
|
assert.Empty(t, entitlements.Errors, "no errors")
|
|
}
|
|
|
|
func assertNoWarnings(t *testing.T, entitlements codersdk.Entitlements) {
|
|
t.Helper()
|
|
assert.Empty(t, entitlements.Warnings, "no warnings")
|
|
}
|
|
|
|
func assertEnterpriseFeatures(t *testing.T, entitlements codersdk.Entitlements) {
|
|
t.Helper()
|
|
for _, expected := range codersdk.FeatureSetEnterprise.Features() {
|
|
f := entitlements.Features[expected]
|
|
assert.Equalf(t, codersdk.EntitlementEntitled, f.Entitlement, "%s entitled", expected)
|
|
assert.Equalf(t, true, f.Enabled, "%s enabled", expected)
|
|
}
|
|
}
|