Hackaton: Add more unit tests, take 2 (#101473)

* login/social/socialimpl: add assertions for usage stats, support bundle and oauthinfo methods

* accesscontrol/acimpl: add tests for GetRoleByName

* anonymous/sortopts: add tests for Sorter

* cloudmigration/gmsclient: add basic test cases for all methods

* shorturls/shorturlimpl: add more edge test cases

* tag/tagimpl: add test to cover duplicate tag kv and nil pairs

* updatechecker: add test cases for module
This commit is contained in:
Matheus Macabu
2025-03-03 13:12:13 +01:00
committed by GitHub
parent d1b20c652d
commit b110754c9a
10 changed files with 887 additions and 5 deletions
+37 -2
View File
@@ -1,6 +1,7 @@
package socialimpl
import (
"context"
"testing"
"github.com/stretchr/testify/require"
@@ -85,6 +86,8 @@ func TestSocialService_ProvideService(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
env := &testEnv{
features: featuremgmt.WithFeatures(),
}
@@ -92,13 +95,45 @@ func TestSocialService_ProvideService(t *testing.T) {
tc.setup(t, env)
}
socialService := ProvideService(cfg, env.features, &usagestats.UsageStatsMock{}, supportbundlestest.NewFakeBundleService(), remotecache.NewFakeStore(t), nil, ssoSettingsSvc)
require.Equal(t, tc.expectedSocialMapLength, len(socialService.socialMap))
usageInsights := &usagestats.UsageStatsMock{}
supportBundle := supportbundlestest.NewFakeBundleService()
socialService := ProvideService(cfg, env.features, usageInsights, supportBundle, remotecache.NewFakeStore(t), nil, ssoSettingsSvc)
require.Equal(t, tc.expectedSocialMapLength, len(socialService.GetOAuthProviders()))
genericOAuthInfo := socialService.GetOAuthInfoProvider("generic_oauth")
if genericOAuthInfo != nil {
require.Equal(t, tc.expectedGenericOAuthSkipOrgRoleSync, genericOAuthInfo.SkipOrgRoleSync)
}
for name, enabled := range socialService.GetOAuthProviders() {
client, err := socialService.GetOAuthHttpClient(name)
if !enabled {
require.Error(t, err)
require.Nil(t, client)
} else {
require.NoError(t, err)
require.NotNil(t, client)
}
}
report, err := usageInsights.GetUsageReport(ctx)
require.NoError(t, err)
require.NotNil(t, report)
require.Len(t, report.Metrics, tc.expectedSocialMapLength)
require.Len(t, supportBundle.Collectors, tc.expectedSocialMapLength)
createdBundles := make(map[string]struct{}, 0)
for _, collector := range supportBundle.Collectors {
supportItem, err := collector.Fn(ctx)
require.NoError(t, err)
require.NotNil(t, supportItem)
createdBundles[supportItem.Filename] = struct{}{}
}
require.Len(t, createdBundles, tc.expectedSocialMapLength)
})
}
}
@@ -970,3 +970,38 @@ func TestService_DeleteExternalServiceRole(t *testing.T) {
})
}
}
func TestService_GetRoleByName(t *testing.T) {
t.Parallel()
ctx := context.Background()
t.Run("when the role does not exists, it returns an error", func(t *testing.T) {
t.Parallel()
ac := setupTestEnv(t)
ac.registrations = accesscontrol.RegistrationList{}
role, err := ac.GetRoleByName(ctx, 0, "not-found-role")
require.ErrorIs(t, err, accesscontrol.ErrRoleNotFound)
require.Nil(t, role)
})
t.Run("when the role exists, it is returned", func(t *testing.T) {
t.Parallel()
roleName := "fixed:test:test"
ac := setupTestEnv(t)
ac.registrations = accesscontrol.RegistrationList{}
ac.registrations.Append(accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{Name: roleName},
Grants: []string{"Admin"},
})
role, err := ac.GetRoleByName(ctx, 0, roleName)
require.NoError(t, err)
require.NotNil(t, role)
require.Equal(t, roleName, role.Name)
})
}
@@ -0,0 +1,147 @@
package sortopts
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSorter(t *testing.T) {
tests := []struct {
name string
query string
expectedSQL []string
expectedErr bool
}{
{
name: "empty query",
query: "",
},
{
name: "single field ascending",
query: "userAgent-asc",
expectedSQL: []string{"user_agent ASC"},
},
{
name: "single field descending",
query: "userAgent-desc",
expectedSQL: []string{"user_agent DESC"},
},
{
name: "multiple fields",
query: "userAgent-asc,updatedAt-desc",
expectedSQL: []string{"user_agent ASC", "updated_at DESC"},
},
{
name: "unknown option",
query: "uzer_agent",
expectedErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sortOptions, err := ParseSortQueryParam(tt.query)
if tt.expectedErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
if len(tt.expectedSQL) > 0 {
orderBy := make([]string, 0)
for _, opt := range sortOptions {
require.Len(t, opt.Filter, 1)
orderBy = append(orderBy, opt.Filter[0].OrderBy())
}
assert.EqualValues(t, tt.expectedSQL, orderBy)
}
})
}
}
func TestSorter_OrderBy(t *testing.T) {
tests := []struct {
name string
sorter Sorter
expectedSQL string
}{
{
name: "with table name ascending",
sorter: Sorter{
Field: "user_agent",
WithTableName: true,
},
expectedSQL: "anon_device.user_agent ASC",
},
{
name: "with table name descending",
sorter: Sorter{
Field: "user_agent",
WithTableName: true,
Descending: true,
},
expectedSQL: "anon_device.user_agent DESC",
},
{
name: "without table name ascending",
sorter: Sorter{
Field: "user_agent",
},
expectedSQL: "user_agent ASC",
},
{
name: "without table name descending",
sorter: Sorter{
Field: "user_agent",
Descending: true,
},
expectedSQL: "user_agent DESC",
},
{
name: "with table name lowercase ascending",
sorter: Sorter{
Field: "user_agent",
WithTableName: true,
LowerCase: true,
},
expectedSQL: "LOWER(anon_device.user_agent) ASC",
},
{
name: "with table name lowercase descending",
sorter: Sorter{
Field: "user_agent",
WithTableName: true,
LowerCase: true,
Descending: true,
},
expectedSQL: "LOWER(anon_device.user_agent) DESC",
},
{
name: "without table name lowercase ascending",
sorter: Sorter{
Field: "user_agent",
LowerCase: true,
},
expectedSQL: "LOWER(user_agent) ASC",
},
{
name: "without table name lowercase descending",
sorter: Sorter{
Field: "user_agent",
LowerCase: true,
Descending: true,
},
expectedSQL: "LOWER(user_agent) DESC",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expectedSQL, tt.sorter.OrderBy())
})
}
}
@@ -32,7 +32,7 @@ func NewGMSClient(cfg *setting.Cfg, httpClient *http.Client) (Client, error) {
type gmsClientImpl struct {
cfg *setting.Cfg
log *log.ConcreteLogger
log log.Logger
httpClient *http.Client
getStatusMux sync.Mutex
@@ -1,13 +1,20 @@
package gmsclient
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"runtime"
"testing"
"time"
"github.com/grafana/grafana/pkg/infra/log/logtest"
"github.com/grafana/grafana/pkg/services/cloudmigration"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"
)
func Test_buildURL(t *testing.T) {
@@ -129,3 +136,474 @@ func Test_handleGMSErrors(t *testing.T) {
require.ErrorIs(t, resError, tc.expectedError)
}
}
func Test_ValidateKey(t *testing.T) {
t.Parallel()
t.Run("when the key is valid, it returns no error", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
session := cloudmigration.CloudMigrationSession{
StackID: 1234,
AuthToken: "auth-tok",
ClusterSlug: "cluster-slug",
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
auth := r.Header.Get("Authorization")
require.Equal(t, fmt.Sprintf("Bearer %d:%s", session.StackID, session.AuthToken), auth)
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)
cfg := &setting.Cfg{
CloudMigration: setting.CloudMigrationSettings{
GMSDomain: server.URL,
GMSValidateKeyTimeout: time.Hour, // arbitrary, it just can't be 0.
},
}
logger := &logtest.Fake{}
client := gmsClientImpl{cfg: cfg, log: logger, httpClient: http.DefaultClient}
err := client.ValidateKey(ctx, session)
require.NoError(t, err)
})
t.Run("when the key invalidated for any reason, it returns a token validation failure", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
session := cloudmigration.CloudMigrationSession{
StackID: 1234,
AuthToken: "auth-tok",
ClusterSlug: "cluster-slug",
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
auth := r.Header.Get("Authorization")
require.Equal(t, fmt.Sprintf("Bearer %d:%s", session.StackID, session.AuthToken), auth)
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(`{"message": "instance is unreachable"}`)) // could be any other error that is unmapped.
}))
t.Cleanup(server.Close)
cfg := &setting.Cfg{
CloudMigration: setting.CloudMigrationSettings{
GMSDomain: server.URL,
GMSValidateKeyTimeout: time.Hour, // arbitrary, it just can't be 0.
},
}
logger := &logtest.Fake{}
client := gmsClientImpl{cfg: cfg, log: logger, httpClient: http.DefaultClient}
err := client.ValidateKey(ctx, session)
require.Error(t, err)
})
}
func Test_StartSnapshot(t *testing.T) {
t.Parallel()
t.Run("when the session is valid, a snapshot result is returned", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
session := cloudmigration.CloudMigrationSession{
StackID: 1234,
AuthToken: "auth-tok",
ClusterSlug: "cluster-slug",
}
expectedSnapshot := &cloudmigration.StartSnapshotResponse{
SnapshotID: "uuid",
MaxItemsPerPartition: 1024,
Algo: "nacl",
EncryptionKey: []uint8{0x66, 0x6f, 0x6f, 0xa}, // foo
Metadata: []uint8{0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xa}, // metadata
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
auth := r.Header.Get("Authorization")
require.Equal(t, fmt.Sprintf("Bearer %d:%s", session.StackID, session.AuthToken), auth)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{
"snapshotID": "uuid",
"maxItemsPerPartition": 1024,
"algo": "nacl",
"encryptionKey": "Zm9vCg==",
"metadata": "bWV0YWRhdGEK"
}`))
}))
t.Cleanup(server.Close)
cfg := &setting.Cfg{
CloudMigration: setting.CloudMigrationSettings{
GMSDomain: server.URL,
GMSStartSnapshotTimeout: time.Hour, // arbitrary, it just can't be 0.
},
}
logger := &logtest.Fake{}
client := gmsClientImpl{cfg: cfg, log: logger, httpClient: http.DefaultClient}
resp, err := client.StartSnapshot(ctx, session)
require.NoError(t, err)
require.NotNil(t, resp)
require.EqualValues(t, expectedSnapshot, resp)
})
t.Run("when there is an error in the upstream, it logs and returns the error", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
session := cloudmigration.CloudMigrationSession{
StackID: 1234,
AuthToken: "auth-tok",
ClusterSlug: "cluster-slug",
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
auth := r.Header.Get("Authorization")
require.Equal(t, fmt.Sprintf("Bearer %d:%s", session.StackID, session.AuthToken), auth)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(`{"message": "internal server error"}`))
}))
t.Cleanup(server.Close)
cfg := &setting.Cfg{
CloudMigration: setting.CloudMigrationSettings{
GMSDomain: server.URL,
GMSStartSnapshotTimeout: time.Hour, // arbitrary, it just can't be 0.
},
}
logger := &logtest.Fake{}
client := gmsClientImpl{cfg: cfg, log: logger, httpClient: http.DefaultClient}
resp, err := client.StartSnapshot(ctx, session)
require.Error(t, err)
require.Nil(t, resp)
require.Equal(t, 1, logger.ErrorLogs.Calls)
})
}
func Test_GetSnapshotStatus(t *testing.T) {
t.Parallel()
t.Run("it queries the snapshot status and returns it", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
session := cloudmigration.CloudMigrationSession{
StackID: 1234,
AuthToken: "auth-tok",
ClusterSlug: "cluster-slug",
}
snapshot := cloudmigration.CloudMigrationSnapshot{
UID: "snapshot-uuid",
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodGet, r.Method)
auth := r.Header.Get("Authorization")
require.Equal(t, fmt.Sprintf("Bearer %d:%s", session.StackID, session.AuthToken), auth)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{
"state": "PROCESSING",
"results": []
}`))
}))
t.Cleanup(server.Close)
cfg := &setting.Cfg{
CloudMigration: setting.CloudMigrationSettings{
GMSDomain: server.URL,
GMSGetSnapshotStatusTimeout: time.Hour, // arbitrary, it just can't be 0.
},
}
logger := &logtest.Fake{}
client := gmsClientImpl{cfg: cfg, log: logger, httpClient: http.DefaultClient}
g, gctx := errgroup.WithContext(ctx)
for range runtime.NumCPU() * 2 { // run a couple of concurrent requests to check for race condition.
g.Go(func() error {
resp, err := client.GetSnapshotStatus(gctx, session, snapshot, 0)
require.NotNil(t, resp)
return err
})
}
require.NoError(t, g.Wait())
require.NotEmpty(t, client.getStatusLastQueried)
})
t.Run("when there is an error in the upstream, it logs and returns the error", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
session := cloudmigration.CloudMigrationSession{
StackID: 1234,
AuthToken: "auth-tok",
ClusterSlug: "cluster-slug",
}
snapshot := cloudmigration.CloudMigrationSnapshot{
UID: "snapshot-uuid",
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodGet, r.Method)
auth := r.Header.Get("Authorization")
require.Equal(t, fmt.Sprintf("Bearer %d:%s", session.StackID, session.AuthToken), auth)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(`{"message": "internal server error"}`))
}))
t.Cleanup(server.Close)
cfg := &setting.Cfg{
CloudMigration: setting.CloudMigrationSettings{
GMSDomain: server.URL,
GMSGetSnapshotStatusTimeout: time.Hour, // arbitrary, it just can't be 0.
},
}
logger := &logtest.Fake{}
client := gmsClientImpl{cfg: cfg, log: logger, httpClient: http.DefaultClient}
resp, err := client.GetSnapshotStatus(ctx, session, snapshot, 0)
require.Error(t, err)
require.Nil(t, resp)
require.Equal(t, 1, logger.ErrorLogs.Calls)
})
}
func Test_CreatePresignedUploadUrl(t *testing.T) {
t.Parallel()
t.Run("when the snapshot and session are valid, it returns a presigned url string", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
session := cloudmigration.CloudMigrationSession{
StackID: 1234,
AuthToken: "auth-tok",
ClusterSlug: "cluster-slug",
}
snapshot := cloudmigration.CloudMigrationSnapshot{
UID: "snapshot-uuid",
}
expectedURL := "http://example.com"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
auth := r.Header.Get("Authorization")
require.Equal(t, fmt.Sprintf("Bearer %d:%s", session.StackID, session.AuthToken), auth)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"uploadUrl": "` + expectedURL + `"}`))
}))
t.Cleanup(server.Close)
cfg := &setting.Cfg{
CloudMigration: setting.CloudMigrationSettings{
GMSDomain: server.URL,
GMSCreateUploadUrlTimeout: time.Hour, // arbitrary, it just can't be 0.
},
}
logger := &logtest.Fake{}
client := gmsClientImpl{cfg: cfg, log: logger, httpClient: http.DefaultClient}
url, err := client.CreatePresignedUploadUrl(ctx, session, snapshot)
require.NoError(t, err)
require.Equal(t, expectedURL, url)
})
t.Run("when there is an error in the upstream, it logs and returns the error", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
session := cloudmigration.CloudMigrationSession{
StackID: 1234,
AuthToken: "auth-tok",
ClusterSlug: "cluster-slug",
}
snapshot := cloudmigration.CloudMigrationSnapshot{
UID: "snapshot-uuid",
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
auth := r.Header.Get("Authorization")
require.Equal(t, fmt.Sprintf("Bearer %d:%s", session.StackID, session.AuthToken), auth)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(`{"message": "internal server error"}`))
}))
t.Cleanup(server.Close)
cfg := &setting.Cfg{
CloudMigration: setting.CloudMigrationSettings{
GMSDomain: server.URL,
GMSCreateUploadUrlTimeout: time.Hour, // arbitrary, it just can't be 0.
},
}
logger := &logtest.Fake{}
client := gmsClientImpl{cfg: cfg, log: logger, httpClient: http.DefaultClient}
url, err := client.CreatePresignedUploadUrl(ctx, session, snapshot)
require.Error(t, err)
require.Empty(t, url)
require.Equal(t, 1, logger.ErrorLogs.Calls)
})
}
func Test_ReportEvent(t *testing.T) {
t.Parallel()
t.Run("when the session data is valid, it does not log an error", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
session := cloudmigration.CloudMigrationSession{
StackID: 1234,
AuthToken: "auth-tok",
ClusterSlug: "cluster-slug",
}
event := EventRequestDTO{
LocalID: "local-id",
Event: EventDoneUploadingSnapshot,
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
auth := r.Header.Get("Authorization")
require.Equal(t, fmt.Sprintf("Bearer %d:%s", session.StackID, session.AuthToken), auth)
w.WriteHeader(http.StatusNoContent)
}))
t.Cleanup(server.Close)
cfg := &setting.Cfg{
CloudMigration: setting.CloudMigrationSettings{
GMSDomain: server.URL,
GMSReportEventTimeout: time.Hour, // arbitrary, it just can't be 0.
},
}
logger := &logtest.Fake{}
client := gmsClientImpl{cfg: cfg, log: logger, httpClient: http.DefaultClient}
client.ReportEvent(ctx, session, event)
require.Zero(t, logger.DebugLogs.Calls)
require.Zero(t, logger.WarnLogs.Calls)
require.Zero(t, logger.InfoLogs.Calls)
require.Zero(t, logger.ErrorLogs.Calls)
})
t.Run("when the session is missing required data, it returns without doing anything", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
event := EventRequestDTO{
Event: EventDoneUploadingSnapshot,
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.True(t, false) // This will never be called, but if it does, it will cause the test to fail.
}))
t.Cleanup(server.Close)
cfg := &setting.Cfg{
CloudMigration: setting.CloudMigrationSettings{
GMSDomain: server.URL,
GMSReportEventTimeout: 0, // this won't be called.
},
}
logger := &logtest.Fake{}
client := gmsClientImpl{cfg: cfg, log: logger, httpClient: http.DefaultClient}
client.ReportEvent(ctx, cloudmigration.CloudMigrationSession{}, event)
require.Zero(t, logger.DebugLogs.Calls)
require.Zero(t, logger.WarnLogs.Calls)
require.Zero(t, logger.InfoLogs.Calls)
require.Zero(t, logger.ErrorLogs.Calls)
})
t.Run("when the upstream server is down, it logs the error", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
session := cloudmigration.CloudMigrationSession{
StackID: 1234,
AuthToken: "auth-tok",
ClusterSlug: "cluster-slug",
}
event := EventRequestDTO{
LocalID: "local-id",
Event: EventDoneUploadingSnapshot,
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(`{"message": "internal server error"}`))
}))
t.Cleanup(server.Close)
cfg := &setting.Cfg{
CloudMigration: setting.CloudMigrationSettings{
GMSDomain: server.URL,
GMSReportEventTimeout: time.Hour, // arbitrary, it just can't be 0.
},
}
logger := &logtest.Fake{}
client := gmsClientImpl{cfg: cfg, log: logger, httpClient: http.DefaultClient}
client.ReportEvent(ctx, session, event)
require.Zero(t, logger.DebugLogs.Calls)
require.Zero(t, logger.WarnLogs.Calls)
require.Zero(t, logger.InfoLogs.Calls)
require.Equal(t, 2, logger.ErrorLogs.Calls)
})
}
@@ -75,6 +75,8 @@ func TestShortURLService(t *testing.T) {
t.Run("and no action when no stale short urls exist", func(t *testing.T) {
cmd := shorturls.DeleteShortUrlCommand{OlderThan: time.Unix(existingShortURL.CreatedAt, 0)}
err = service.DeleteStaleShortURLs(context.Background(), &cmd)
require.NoError(t, err)
require.Equal(t, int64(0), cmd.NumDeleted)
})
@@ -89,4 +91,46 @@ func TestShortURLService(t *testing.T) {
require.True(t, shorturls.ErrShortURLNotFound.Is(err))
require.Nil(t, shortURL)
})
t.Run("User cannot create short URLs from invalid paths", func(t *testing.T) {
service := ShortURLService{SQLStore: &sqlStore{db: store}}
ctx := context.Background()
absolutePath := "/path?test=true"
newShortURL, err := service.CreateShortURL(ctx, user, absolutePath)
require.ErrorIs(t, err, shorturls.ErrShortURLAbsolutePath)
require.Nil(t, newShortURL)
relativePath := "path/../test?test=true"
newShortURL, err = service.CreateShortURL(ctx, user, relativePath)
require.ErrorIs(t, err, shorturls.ErrShortURLInvalidPath)
require.Nil(t, newShortURL)
relativePath = "../path/test?test=true"
newShortURL, err = service.CreateShortURL(ctx, user, relativePath)
require.ErrorIs(t, err, shorturls.ErrShortURLInvalidPath)
require.Nil(t, newShortURL)
})
t.Run("The same URL will generate different entries", func(t *testing.T) {
service := ShortURLService{SQLStore: &sqlStore{db: store}}
ctx := context.Background()
const refPath = "mock/path?test=true"
newShortURL1, err := service.CreateShortURL(ctx, user, refPath)
require.NoError(t, err)
require.NotNil(t, newShortURL1)
require.NotEmpty(t, newShortURL1.Uid)
newShortURL2, err := service.CreateShortURL(ctx, user, refPath)
require.NoError(t, err)
require.NotNil(t, newShortURL2)
require.NotEmpty(t, newShortURL2.Uid)
require.NotEqual(t, newShortURL1.Uid, newShortURL2.Uid)
require.Equal(t, newShortURL1.Path, newShortURL2.Path)
})
}
@@ -3,10 +3,13 @@ package supportbundlestest
import "github.com/grafana/grafana/pkg/services/supportbundles"
type FakeBundleService struct {
Collectors []supportbundles.Collector
}
func NewFakeBundleService() *FakeBundleService {
return &FakeBundleService{}
}
func (s *FakeBundleService) RegisterSupportItemCollector(collector supportbundles.Collector) {}
func (s *FakeBundleService) RegisterSupportItemCollector(collector supportbundles.Collector) {
s.Collectors = append(s.Collectors, collector)
}
+6
View File
@@ -78,6 +78,12 @@ func TestParseTagPairs(t *testing.T) {
assert.Equal(t, "key", tags[2].Key)
assert.Equal(t, "val2", tags[2].Value)
})
t.Run("Nil tag returns an allocated but empty pair", func(t *testing.T) {
tags := ParseTagPairs(nil)
require.NotNil(t, tags)
require.Empty(t, tags)
})
}
func TestJoinTagPairs(t *testing.T) {
+3 -1
View File
@@ -26,10 +26,12 @@ func testIntegrationSavingTags(t *testing.T, fn getStore) {
{Key: "outage"},
{Key: "type", Value: "outage"},
{Key: "server", Value: "server-1"},
{Key: "server", Value: "server-1"}, // duplicates will generate a new ID.
{Key: "error"},
{Key: "error"}, // duplicates will generate a new ID.
}
tags, err := store.EnsureTagsExist(context.Background(), tagPairs)
require.Nil(t, err)
require.Equal(t, 4, len(tags))
require.Equal(t, len(tagPairs), len(tags))
}
+132
View File
@@ -0,0 +1,132 @@
package updatechecker
import (
"context"
"testing"
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/stretchr/testify/require"
)
func TestGrafanaService(t *testing.T) {
t.Parallel()
ctx := context.Background()
t.Run("when the Grafana version is stable", func(t *testing.T) {
t.Parallel()
t.Run("and the latest version is newer than the current one", func(t *testing.T) {
t.Parallel()
grafanaVersion := "99.0.0"
latestVersion := "99.0.1"
httpClient := &fakeHTTPClient{
fakeResp: `{"version": "` + latestVersion + `"}`,
}
service := &GrafanaService{
enabled: true,
grafanaVersion: grafanaVersion,
httpClient: httpClient,
log: log.NewNopLogger(),
tracer: tracing.NewNoopTracerService(),
}
err := service.checkForUpdates(ctx)
require.NoError(t, err)
require.True(t, service.UpdateAvailable())
require.Equal(t, latestVersion, service.LatestVersion())
require.False(t, service.IsDisabled())
})
t.Run("and the latest version is the same as the current one", func(t *testing.T) {
t.Parallel()
grafanaVersion := "99.0.0"
latestVersion := grafanaVersion
httpClient := &fakeHTTPClient{
fakeResp: `{"version": "` + latestVersion + `"}`,
}
service := &GrafanaService{
enabled: true,
grafanaVersion: grafanaVersion,
httpClient: httpClient,
log: log.NewNopLogger(),
tracer: tracing.NewNoopTracerService(),
}
err := service.checkForUpdates(ctx)
require.NoError(t, err)
require.False(t, service.UpdateAvailable())
require.Equal(t, grafanaVersion, service.LatestVersion())
require.False(t, service.IsDisabled())
})
})
t.Run("when the Grafana version is a development build", func(t *testing.T) {
t.Parallel()
grafanaVersion := "99.0.0-222555"
lastestVersion := "99.0.1"
httpClient := &fakeHTTPClient{
fakeResp: `{"version": "` + lastestVersion + `"}`,
}
service := &GrafanaService{
enabled: true,
grafanaVersion: grafanaVersion,
httpClient: httpClient,
log: log.NewNopLogger(),
tracer: tracing.NewNoopTracerService(),
}
err := service.checkForUpdates(ctx)
require.NoError(t, err)
require.False(t, service.UpdateAvailable())
require.Empty(t, service.LatestVersion())
require.False(t, service.IsDisabled())
})
}
func TestGrafanaService_Run(t *testing.T) {
latestVersion := "99.0.1"
service := &GrafanaService{
enabled: true,
grafanaVersion: "99.0.0",
httpClient: &fakeHTTPClient{
fakeResp: `{"version": "` + latestVersion + `"}`,
},
log: log.NewNopLogger(),
tracer: tracing.NewNoopTracerService(),
}
ctx, cancel := context.WithCancel(context.Background())
// Initially there won't be any data.
require.False(t, service.UpdateAvailable())
require.Empty(t, service.LatestVersion())
// Run in the background so we can cancel it after the first run.
errChan := make(chan error, 1)
go func() {
errChan <- service.Run(ctx)
}()
// It will run once immediately then schedule it for 24 hours later. This will be true because latest > current.
require.Eventually(t, func() bool { return service.UpdateAvailable() }, 5*time.Second, 20*time.Millisecond)
cancel()
require.ErrorIs(t, <-errChan, context.Canceled)
}