coder/enterprise/coderd/appearance_test.go

246 lines
7.7 KiB
Go

package coderd_test
import (
"context"
"errors"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/coderd/appearance"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbfake"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
"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 TestCustomLogoAndCompanyName(t *testing.T) {
t.Parallel()
// Prepare enterprise deployment
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
adminClient, adminUser := coderdenttest.New(t, &coderdenttest.Options{DontAddLicense: true})
coderdenttest.AddLicense(t, adminClient, coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureAppearance: 1,
},
})
anotherClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID)
// Update logo and application name
uac := codersdk.UpdateAppearanceConfig{
ApplicationName: "ACME Ltd",
LogoURL: "http://logo-url/file.png",
}
err := adminClient.UpdateAppearance(ctx, uac)
require.NoError(t, err)
// Verify update
got, err := anotherClient.Appearance(ctx)
require.NoError(t, err)
require.Equal(t, uac.ApplicationName, got.ApplicationName)
require.Equal(t, uac.LogoURL, got.LogoURL)
}
func TestNotificationBanners(t *testing.T) {
t.Parallel()
t.Run("User", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
adminClient, adminUser := coderdenttest.New(t, &coderdenttest.Options{DontAddLicense: true})
basicUserClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID)
// Without a license, there should be no banners.
sb, err := basicUserClient.Appearance(ctx)
require.NoError(t, err)
require.Empty(t, sb.NotificationBanners)
coderdenttest.AddLicense(t, adminClient, coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureAppearance: 1,
},
})
// Default state
sb, err = basicUserClient.Appearance(ctx)
require.NoError(t, err)
require.Empty(t, sb.NotificationBanners)
// Regular user should be unable to set the banner
uac := codersdk.UpdateAppearanceConfig{
NotificationBanners: []codersdk.BannerConfig{{Enabled: true}},
}
err = basicUserClient.UpdateAppearance(ctx, uac)
require.Error(t, err)
var sdkError *codersdk.Error
require.True(t, errors.As(err, &sdkError))
require.ErrorAs(t, err, &sdkError)
require.Equal(t, http.StatusForbidden, sdkError.StatusCode())
// But an admin can
wantBanner := codersdk.UpdateAppearanceConfig{
NotificationBanners: []codersdk.BannerConfig{{
Enabled: true,
Message: "The beep-bop will be boop-beeped on Saturday at 12AM PST.",
BackgroundColor: "#00FF00",
}},
}
err = adminClient.UpdateAppearance(ctx, wantBanner)
require.NoError(t, err)
gotBanner, err := adminClient.Appearance(ctx) //nolint:gocritic // we should assert at least once that the owner can get the banner
require.NoError(t, err)
require.Equal(t, wantBanner.NotificationBanners, gotBanner.NotificationBanners)
// But even an admin can't give a bad color
wantBanner.NotificationBanners[0].BackgroundColor = "#bad color"
err = adminClient.UpdateAppearance(ctx, wantBanner)
require.Error(t, err)
var sdkErr *codersdk.Error
require.ErrorAs(t, err, &sdkErr)
require.Equal(t, http.StatusBadRequest, sdkErr.StatusCode())
require.Contains(t, sdkErr.Message, "Invalid color format")
require.Contains(t, sdkErr.Detail, "expected # prefix and 6 characters")
})
t.Run("Agent", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
store, ps := dbtestutil.NewDB(t)
client, user := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: store,
Pubsub: ps,
},
DontAddLicense: true,
})
lic := coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureAppearance: 1,
},
})
cfg := codersdk.UpdateAppearanceConfig{
NotificationBanners: []codersdk.BannerConfig{{
Enabled: true,
Message: "The beep-bop will be boop-beeped on Saturday at 12AM PST.",
BackgroundColor: "#00FF00",
}},
}
err := client.UpdateAppearance(ctx, cfg)
require.NoError(t, err)
r := dbfake.WorkspaceBuild(t, store, database.Workspace{
OrganizationID: user.OrganizationID,
OwnerID: user.UserID,
}).WithAgent().Do()
agentClient := agentsdk.New(client.URL)
agentClient.SetSessionToken(r.AgentToken)
banners := requireGetNotificationBanners(ctx, t, agentClient)
require.Equal(t, cfg.NotificationBanners, banners)
// Create an AGPL Coderd against the same database
agplClient := coderdtest.New(t, &coderdtest.Options{Database: store, Pubsub: ps})
agplAgentClient := agentsdk.New(agplClient.URL)
agplAgentClient.SetSessionToken(r.AgentToken)
banners = requireGetNotificationBanners(ctx, t, agplAgentClient)
require.Equal(t, []codersdk.BannerConfig{}, banners)
// No license means no banner.
err = client.DeleteLicense(ctx, lic.ID)
require.NoError(t, err)
banners = requireGetNotificationBanners(ctx, t, agentClient)
require.Equal(t, []codersdk.BannerConfig{}, banners)
})
}
func requireGetNotificationBanners(ctx context.Context, t *testing.T, client *agentsdk.Client) []codersdk.BannerConfig {
cc, err := client.ConnectRPC(ctx)
require.NoError(t, err)
defer func() {
_ = cc.Close()
}()
aAPI := proto.NewDRPCAgentClient(cc)
bannersProto, err := aAPI.GetNotificationBanners(ctx, &proto.GetNotificationBannersRequest{})
require.NoError(t, err)
banners := make([]codersdk.BannerConfig, 0, len(bannersProto.NotificationBanners))
for _, bannerProto := range bannersProto.NotificationBanners {
banners = append(banners, agentsdk.BannerConfigFromProto(bannerProto))
}
return banners
}
func TestCustomSupportLinks(t *testing.T) {
t.Parallel()
supportLinks := []codersdk.LinkConfig{
{
Name: "First link",
Target: "http://first-link-1",
Icon: "chat",
},
{
Name: "Second link",
Target: "http://second-link-2",
Icon: "bug",
},
}
cfg := coderdtest.DeploymentValues(t)
cfg.Support.Links = serpent.Struct[[]codersdk.LinkConfig]{
Value: supportLinks,
}
adminClient, adminUser := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: cfg,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureAppearance: 1,
},
},
})
anotherClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancel()
appr, err := anotherClient.Appearance(ctx)
require.NoError(t, err)
require.Equal(t, supportLinks, appr.SupportLinks)
}
func TestDefaultSupportLinks(t *testing.T) {
t.Parallel()
// Don't need to set the license, as default links are passed without it.
adminClient, adminUser := coderdenttest.New(t, &coderdenttest.Options{DontAddLicense: true})
anotherClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancel()
appr, err := anotherClient.Appearance(ctx)
require.NoError(t, err)
require.Equal(t, appearance.DefaultSupportLinks, appr.SupportLinks)
}