Chore: Add unit test for cloudmigration package (#88868)

This commit is contained in:
lean.dev
2024-06-07 13:46:19 -03:00
committed by GitHub
parent 3a8b59f5e8
commit 330da7916d
6 changed files with 952 additions and 53 deletions
+446 -4
View File
@@ -1,12 +1,454 @@
package api
import (
"io"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/cloudmigration/cloudmigrationimpl/fake"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/web/webtest"
"github.com/stretchr/testify/require"
)
func Test_OnlyEnabledForGrafanaAdmi(t *testing.T) {
// TODO: implement
assert.True(t, true)
type TestCase struct {
desc string
requestHttpMethod string
requestUrl string
requestBody string
basicRole org.RoleType
// if the CloudMigrationService should return an error
serviceReturnError bool
expectedHttpResult int
expectedBody string
}
func TestCloudMigrationAPI_GetToken(t *testing.T) {
tests := []TestCase{
{
desc: "should return 200 if everything is ok",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/token",
basicRole: org.RoleAdmin,
expectedHttpResult: http.StatusOK,
expectedBody: `{"id":"mock_id","displayName":"mock_name","expiresAt":"","firstUsedAt":"","lastUsedAt":"","createdAt":""}`,
},
{
desc: "should return 403 if no used is not admin",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/token",
basicRole: org.RoleEditor,
expectedHttpResult: http.StatusForbidden,
expectedBody: "",
},
{
desc: "should return 500 if service returns an error",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/token",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusInternalServerError,
expectedBody: "",
},
}
for _, tt := range tests {
t.Run(tt.desc, runSimpleApiTest(tt))
}
}
func TestCloudMigrationAPI_CreateToken(t *testing.T) {
tests := []TestCase{
{
desc: "should return 200 if everything is ok",
requestHttpMethod: http.MethodPost,
requestUrl: "/api/cloudmigration/token",
basicRole: org.RoleAdmin,
expectedHttpResult: http.StatusOK,
expectedBody: `{"token":"mock_token"}`,
},
{
desc: "should return 403 if no used is not admin",
requestHttpMethod: http.MethodPost,
requestUrl: "/api/cloudmigration/token",
basicRole: org.RoleEditor,
expectedHttpResult: http.StatusForbidden,
expectedBody: "",
},
{
desc: "should return 500 if service returns an error",
requestHttpMethod: http.MethodPost,
requestUrl: "/api/cloudmigration/token",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusInternalServerError,
expectedBody: "",
},
}
for _, tt := range tests {
t.Run(tt.desc, runSimpleApiTest(tt))
}
}
func TestCloudMigrationAPI_DeleteToken(t *testing.T) {
tests := []TestCase{
{
desc: "should return 200 if everything is ok",
requestHttpMethod: http.MethodDelete,
requestUrl: "/api/cloudmigration/token/1234",
basicRole: org.RoleAdmin,
expectedHttpResult: http.StatusNoContent,
},
{
desc: "should return 403 if no used is not admin",
requestHttpMethod: http.MethodDelete,
requestUrl: "/api/cloudmigration/token/1234",
basicRole: org.RoleEditor,
expectedHttpResult: http.StatusForbidden,
},
{
desc: "should return 500 if service returns an error",
requestHttpMethod: http.MethodDelete,
requestUrl: "/api/cloudmigration/token/1234",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusInternalServerError,
},
{
desc: "should return 400 if uid is invalid",
requestHttpMethod: http.MethodDelete,
requestUrl: "/api/cloudmigration/token/***",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.desc, runSimpleApiTest(tt))
}
}
func TestCloudMigrationAPI_GetMigration(t *testing.T) {
tests := []TestCase{
{
desc: "should return 200 if everything is ok",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration/1234",
basicRole: org.RoleAdmin,
expectedHttpResult: http.StatusOK,
},
{
desc: "should return 403 if no used is not admin",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration/1234",
basicRole: org.RoleEditor,
expectedHttpResult: http.StatusForbidden,
},
{
desc: "should return 500 if service returns an error",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration/1234",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusNotFound,
},
{
desc: "should return 400 if uid is invalid",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration/****",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.desc, runSimpleApiTest(tt))
}
}
func TestCloudMigrationAPI_GetMigrationList(t *testing.T) {
tests := []TestCase{
{
desc: "should return 200 if everything is ok",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration",
basicRole: org.RoleAdmin,
expectedHttpResult: http.StatusOK,
expectedBody: `{"migrations":[{"uid":"mock_uid_1","stack":"mock_stack_1","created":"2024-06-05T17:30:40Z","updated":"2024-06-05T17:30:40Z"},{"uid":"mock_uid_2","stack":"mock_stack_2","created":"2024-06-05T17:30:40Z","updated":"2024-06-05T17:30:40Z"}]}`,
},
{
desc: "should return 403 if no used is not admin",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration",
basicRole: org.RoleEditor,
expectedHttpResult: http.StatusForbidden,
expectedBody: "",
},
{
desc: "should return 500 if service returns an error",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusInternalServerError,
expectedBody: "",
},
}
for _, tt := range tests {
t.Run(tt.desc, runSimpleApiTest(tt))
}
}
func TestCloudMigrationAPI_CreateMigration(t *testing.T) {
tests := []TestCase{
{
desc: "should return 200 if everything is ok",
requestHttpMethod: http.MethodPost,
requestUrl: "/api/cloudmigration/migration",
requestBody: `{"auth_token":"asdf"}`,
basicRole: org.RoleAdmin,
expectedHttpResult: http.StatusOK,
expectedBody: `{"uid":"fake_uid","stack":"fake_stack","created":"2024-06-05T17:30:40Z","updated":"2024-06-05T17:30:40Z"}`,
},
{
desc: "should return 403 if no used is not admin",
requestHttpMethod: http.MethodPost,
requestUrl: "/api/cloudmigration/migration",
basicRole: org.RoleEditor,
expectedHttpResult: http.StatusForbidden,
expectedBody: "",
},
{
desc: "should return 500 if service returns an error",
requestHttpMethod: http.MethodPost,
requestUrl: "/api/cloudmigration/migration",
requestBody: `{"authToken":"asdf"}`,
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusInternalServerError,
expectedBody: "",
},
{
desc: "should return 400 if body is not a valid json",
requestHttpMethod: http.MethodPost,
requestUrl: "/api/cloudmigration/migration",
requestBody: "asdf",
basicRole: org.RoleAdmin,
serviceReturnError: false,
expectedHttpResult: http.StatusBadRequest,
expectedBody: "",
},
}
for _, tt := range tests {
t.Run(tt.desc, runSimpleApiTest(tt))
}
}
func TestCloudMigrationAPI_RunMigration(t *testing.T) {
tests := []TestCase{
{
desc: "should return 200 if everything is ok",
requestHttpMethod: http.MethodPost,
requestUrl: "/api/cloudmigration/migration/1234/run",
basicRole: org.RoleAdmin,
expectedHttpResult: http.StatusOK,
expectedBody: `{"uid":"fake_uid","items":[{"type":"type","refId":"make_refid","status":"ok","error":"none"}]}`,
},
{
desc: "should return 403 if no used is not admin",
requestHttpMethod: http.MethodPost,
requestUrl: "/api/cloudmigration/migration/1234/run",
basicRole: org.RoleEditor,
expectedHttpResult: http.StatusForbidden,
expectedBody: "",
},
{
desc: "should return 500 if service returns an error",
requestHttpMethod: http.MethodPost,
requestUrl: "/api/cloudmigration/migration/1234/run",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusInternalServerError,
expectedBody: "",
},
{
desc: "should return 400 if uid is invalid",
requestHttpMethod: http.MethodPost,
requestUrl: "/api/cloudmigration/migration/***/run",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.desc, runSimpleApiTest(tt))
}
}
func TestCloudMigrationAPI_GetMigrationRun(t *testing.T) {
tests := []TestCase{
{
desc: "should return 200 if everything is ok",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration/run/1234",
basicRole: org.RoleAdmin,
expectedHttpResult: http.StatusOK,
expectedBody: `{"uid":"fake_uid","items":[{"type":"type","refId":"make_refid","status":"ok","error":"none"}]}`,
},
{
desc: "should return 403 if no used is not admin",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration/run/1234",
basicRole: org.RoleEditor,
expectedHttpResult: http.StatusForbidden,
expectedBody: "",
},
{
desc: "should return 500 if service returns an error",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration/run/1234",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusInternalServerError,
expectedBody: "",
},
{
desc: "should return 400 if uid is invalid",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration/run/****",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.desc, runSimpleApiTest(tt))
}
}
func TestCloudMigrationAPI_GetMigrationRunList(t *testing.T) {
tests := []TestCase{
{
desc: "should return 200 if everything is ok",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration/1234/run",
basicRole: org.RoleAdmin,
expectedHttpResult: http.StatusOK,
expectedBody: `{"runs":[{"uid":"fake_run_uid_1"},{"uid":"fake_run_uid_2"}]}`,
},
{
desc: "should return 403 if no used is not admin",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration/1234/run",
basicRole: org.RoleEditor,
expectedHttpResult: http.StatusForbidden,
expectedBody: "",
},
{
desc: "should return 500 if service returns an error",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration/1234/run",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusInternalServerError,
expectedBody: "",
},
{
desc: "should return 400 if uid is invalid",
requestHttpMethod: http.MethodGet,
requestUrl: "/api/cloudmigration/migration/****/run",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.desc, runSimpleApiTest(tt))
}
}
func TestCloudMigrationAPI_DeleteMigration(t *testing.T) {
tests := []TestCase{
{
desc: "should return 200 if everything is ok",
requestHttpMethod: http.MethodDelete,
requestUrl: "/api/cloudmigration/migration/1234",
basicRole: org.RoleAdmin,
expectedHttpResult: http.StatusOK,
expectedBody: "",
},
{
desc: "should return 403 if no used is not admin",
requestHttpMethod: http.MethodDelete,
requestUrl: "/api/cloudmigration/migration/1234",
basicRole: org.RoleEditor,
expectedHttpResult: http.StatusForbidden,
expectedBody: "",
},
{
desc: "should return 500 if service returns an error",
requestHttpMethod: http.MethodDelete,
requestUrl: "/api/cloudmigration/migration/1234",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusInternalServerError,
expectedBody: "",
},
{
desc: "should return 400 if uid is invalid",
requestHttpMethod: http.MethodDelete,
requestUrl: "/api/cloudmigration/migration/****",
basicRole: org.RoleAdmin,
serviceReturnError: true,
expectedHttpResult: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.desc, runSimpleApiTest(tt))
}
}
func runSimpleApiTest(tt TestCase) func(t *testing.T) {
return func(t *testing.T) {
// setup server
api := RegisterApi(routing.NewRouteRegister(), fake.FakeServiceImpl{ReturnError: tt.serviceReturnError}, tracing.InitializeTracerForTest())
server := webtest.NewServer(t, api.routeRegister)
var body io.Reader = nil
if tt.requestBody != "" {
body = strings.NewReader(tt.requestBody)
}
req := server.NewRequest(tt.requestHttpMethod, tt.requestUrl, body)
req.Header.Set("Content-Type", "application/json")
// create test request
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
OrgID: 1,
OrgRole: tt.basicRole,
})
res, err := server.Send(req)
defer func() { require.NoError(t, res.Body.Close()) }()
// validations
require.NoError(t, err)
require.Equal(t, tt.expectedHttpResult, res.StatusCode)
if tt.expectedBody != "" {
require.NotNil(t, t, res.Body)
b, err := io.ReadAll(res.Body)
require.NoError(t, err)
require.Equal(t, tt.expectedBody, string(b))
}
}
}
+12 -13
View File
@@ -7,22 +7,21 @@ import (
)
type Service interface {
// Returns the cloud migration token if it exists.
GetToken(context.Context) (gcom.TokenView, error)
// Creates a cloud migration token.
CreateToken(context.Context) (CreateAccessTokenResponse, error)
// Sends a request to CMS to test the token.
ValidateToken(context.Context, CloudMigration) error
// GetToken Returns the cloud migration token if it exists.
GetToken(ctx context.Context) (gcom.TokenView, error)
// CreateToken Creates a cloud migration token.
CreateToken(ctx context.Context) (CreateAccessTokenResponse, error)
// ValidateToken Sends a request to CMS to test the token.
ValidateToken(ctx context.Context, mig CloudMigration) error
DeleteToken(ctx context.Context, uid string) error
CreateMigration(context.Context, CloudMigrationRequest) (*CloudMigrationResponse, error)
GetMigration(ctx context.Context, uid string) (*CloudMigration, error)
DeleteMigration(ctx context.Context, uid string) (*CloudMigration, error)
UpdateMigration(ctx context.Context, uid string, request CloudMigrationRequest) (*CloudMigrationResponse, error)
CreateMigration(ctx context.Context, req CloudMigrationRequest) (*CloudMigrationResponse, error)
GetMigration(ctx context.Context, migUID string) (*CloudMigration, error)
DeleteMigration(ctx context.Context, migUID string) (*CloudMigration, error)
UpdateMigration(ctx context.Context, migUID string, request CloudMigrationRequest) (*CloudMigrationResponse, error)
GetMigrationList(context.Context) (*CloudMigrationListResponse, error)
RunMigration(ctx context.Context, uid string) (*MigrateDataResponseDTO, error)
CreateMigrationRun(context.Context, CloudMigrationRun) (string, error)
RunMigration(ctx context.Context, migUID string) (*MigrateDataResponseDTO, error)
GetMigrationStatus(ctx context.Context, runUID string) (*CloudMigrationRun, error)
GetMigrationRunList(context.Context, string) (*CloudMigrationRunList, error)
GetMigrationRunList(ctx context.Context, migUID string) (*CloudMigrationRunList, error)
}
@@ -411,7 +411,7 @@ func (s *Service) RunMigration(ctx context.Context, uid string) (*cloudmigration
}
// save the result of the migration
runUID, err := s.CreateMigrationRun(ctx, cloudmigration.CloudMigrationRun{
runUID, err := s.createMigrationRun(ctx, cloudmigration.CloudMigrationRun{
CloudMigrationUID: migration.UID,
Result: respData,
})
@@ -547,7 +547,7 @@ func (s *Service) getDashboards(ctx context.Context) ([]dashboards.Dashboard, er
return result, nil
}
func (s *Service) CreateMigrationRun(ctx context.Context, cmr cloudmigration.CloudMigrationRun) (string, error) {
func (s *Service) createMigrationRun(ctx context.Context, cmr cloudmigration.CloudMigrationRun) (string, error) {
uid, err := s.store.CreateMigrationRun(ctx, cmr)
if err != nil {
s.log.Error("Failed to save migration run", "err", err)
@@ -4,8 +4,27 @@ import (
"context"
"testing"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/cloudmigration"
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
datafakes "github.com/grafana/grafana/pkg/services/datasources/fakes"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
secretsfakes "github.com/grafana/grafana/pkg/services/secrets/fakes"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
)
func Test_NoopServiceDoesNothing(t *testing.T) {
@@ -13,3 +32,143 @@ func Test_NoopServiceDoesNothing(t *testing.T) {
_, e := s.CreateToken(context.Background())
assert.ErrorIs(t, e, cloudmigration.ErrFeatureDisabledError)
}
func Test_CreateGetAndDeleteToken(t *testing.T) {
s := setUpServiceTest(t, false)
createResp, err := s.CreateToken(context.Background())
assert.NoError(t, err)
assert.NotEmpty(t, createResp.Token)
token, err := s.GetToken(context.Background())
assert.NoError(t, err)
assert.NotEmpty(t, token.Name)
err = s.DeleteToken(context.Background(), token.ID)
assert.NoError(t, err)
_, err = s.GetToken(context.Background())
assert.ErrorIs(t, cloudmigration.ErrTokenNotFound, err)
cm := cloudmigration.CloudMigration{}
err = s.ValidateToken(context.Background(), cm)
assert.NoError(t, err)
}
func Test_CreateGetRunMigrationsAndRuns(t *testing.T) {
s := setUpServiceTest(t, true)
createTokenResp, err := s.CreateToken(context.Background())
assert.NoError(t, err)
assert.NotEmpty(t, createTokenResp.Token)
cmd := cloudmigration.CloudMigrationRequest{
AuthToken: createTokenResp.Token,
}
createResp, err := s.CreateMigration(context.Background(), cmd)
require.NoError(t, err)
require.NotEmpty(t, createResp.UID)
require.NotEmpty(t, createResp.Stack)
getMigResp, err := s.GetMigration(context.Background(), createResp.UID)
require.NoError(t, err)
require.NotNil(t, getMigResp)
require.Equal(t, createResp.UID, getMigResp.UID)
require.Equal(t, createResp.Stack, getMigResp.Stack)
listResp, err := s.GetMigrationList(context.Background())
require.NoError(t, err)
require.NotNil(t, listResp)
require.Equal(t, 1, len(listResp.Migrations))
require.Equal(t, createResp.UID, listResp.Migrations[0].UID)
require.Equal(t, createResp.Stack, listResp.Migrations[0].Stack)
runResp, err := s.RunMigration(ctxWithSignedInUser(), createResp.UID)
require.NoError(t, err)
require.NotNil(t, runResp)
resultItemsByType := make(map[string]int)
for _, item := range runResp.Items {
resultItemsByType[string(item.Type)] = resultItemsByType[string(item.Type)] + 1
}
require.Equal(t, 1, resultItemsByType["DASHBOARD"])
require.Equal(t, 2, resultItemsByType["DATASOURCE"])
require.Equal(t, 2, len(resultItemsByType))
runStatusResp, err := s.GetMigrationStatus(context.Background(), runResp.RunUID)
require.NoError(t, err)
require.Equal(t, runResp.RunUID, runStatusResp.UID)
listRunResp, err := s.GetMigrationRunList(context.Background(), createResp.UID)
require.NoError(t, err)
require.Equal(t, 1, len(listRunResp.Runs))
require.Equal(t, runResp.RunUID, listRunResp.Runs[0].RunUID)
delMigResp, err := s.DeleteMigration(context.Background(), createResp.UID)
require.NoError(t, err)
require.NotNil(t, createResp.UID, delMigResp.UID)
}
func ctxWithSignedInUser() context.Context {
c := &contextmodel.ReqContext{
SignedInUser: &user.SignedInUser{OrgID: 1},
}
k := ctxkey.Key{}
ctx := context.WithValue(context.Background(), k, c)
return ctx
}
func setUpServiceTest(t *testing.T, withDashboardMock bool) cloudmigration.Service {
sqlStore := db.InitTestDB(t)
secretsService := secretsfakes.NewFakeSecretsService()
rr := routing.NewRouteRegister()
spanRecorder := tracetest.NewSpanRecorder()
tracer := tracing.InitializeTracerForTest(tracing.WithSpanProcessor(spanRecorder))
mockFolder := &foldertest.FakeService{
ExpectedFolder: &folder.Folder{UID: "folderUID", Title: "Folder"},
}
cfg := setting.NewCfg()
section, err := cfg.Raw.NewSection("cloud_migration")
require.NoError(t, err)
_, err = section.NewKey("domain", "localhost:1234")
require.NoError(t, err)
// dont know if this is the best, but dont want to refactor at the moment
cfg.CloudMigration.IsDeveloperMode = true
dashboardService := dashboards.NewFakeDashboardService(t)
if withDashboardMock {
dashboardService.On("GetAllDashboards", mock.Anything).Return(
[]*dashboards.Dashboard{
{
UID: "1",
Data: simplejson.New(),
},
},
nil,
)
}
dsService := &datafakes.FakeDataSourceService{
DataSources: []*datasources.DataSource{
{Name: "mmm", Type: "mysql"},
{Name: "ZZZ", Type: "infinity"},
},
}
s, err := ProvideService(
cfg,
featuremgmt.WithFeatures(featuremgmt.FlagOnPremToCloudMigrations),
sqlStore,
dsService,
secretsService,
rr,
prometheus.DefaultRegisterer,
tracer,
dashboardService,
mockFolder,
)
require.NoError(t, err)
return s
}
@@ -0,0 +1,139 @@
package fake
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/grafana/grafana/pkg/services/cloudmigration"
"github.com/grafana/grafana/pkg/services/gcom"
)
var fixedDate = time.Date(2024, 6, 5, 17, 30, 40, 0, time.UTC)
// FakeServiceImpl fake implementation of cloudmigration.Service for testing purposes
type FakeServiceImpl struct {
ReturnError bool
}
var _ cloudmigration.Service = (*FakeServiceImpl)(nil)
func (m FakeServiceImpl) GetToken(_ context.Context) (gcom.TokenView, error) {
if m.ReturnError {
return gcom.TokenView{}, fmt.Errorf("mock error")
}
return gcom.TokenView{ID: "mock_id", DisplayName: "mock_name"}, nil
}
func (m FakeServiceImpl) CreateToken(_ context.Context) (cloudmigration.CreateAccessTokenResponse, error) {
if m.ReturnError {
return cloudmigration.CreateAccessTokenResponse{}, fmt.Errorf("mock error")
}
return cloudmigration.CreateAccessTokenResponse{Token: "mock_token"}, nil
}
func (m FakeServiceImpl) ValidateToken(ctx context.Context, migration cloudmigration.CloudMigration) error {
panic("implement me")
}
func (m FakeServiceImpl) DeleteToken(_ context.Context, _ string) error {
if m.ReturnError {
return fmt.Errorf("mock error")
}
return nil
}
func (m FakeServiceImpl) CreateMigration(_ context.Context, _ cloudmigration.CloudMigrationRequest) (*cloudmigration.CloudMigrationResponse, error) {
if m.ReturnError {
return nil, fmt.Errorf("mock error")
}
return &cloudmigration.CloudMigrationResponse{
UID: "fake_uid",
Stack: "fake_stack",
Created: fixedDate,
Updated: fixedDate,
}, nil
}
func (m FakeServiceImpl) GetMigration(_ context.Context, _ string) (*cloudmigration.CloudMigration, error) {
if m.ReturnError {
return nil, fmt.Errorf("mock error")
}
return &cloudmigration.CloudMigration{UID: "fake"}, nil
}
func (m FakeServiceImpl) DeleteMigration(_ context.Context, _ string) (*cloudmigration.CloudMigration, error) {
if m.ReturnError {
return nil, fmt.Errorf("mock error")
}
return &cloudmigration.CloudMigration{UID: "fake"}, nil
}
func (m FakeServiceImpl) UpdateMigration(ctx context.Context, uid string, request cloudmigration.CloudMigrationRequest) (*cloudmigration.CloudMigrationResponse, error) {
panic("implement me")
}
func (m FakeServiceImpl) GetMigrationList(_ context.Context) (*cloudmigration.CloudMigrationListResponse, error) {
if m.ReturnError {
return nil, fmt.Errorf("mock error")
}
return &cloudmigration.CloudMigrationListResponse{
Migrations: []cloudmigration.CloudMigrationResponse{
{UID: "mock_uid_1", Stack: "mock_stack_1", Created: fixedDate, Updated: fixedDate},
{UID: "mock_uid_2", Stack: "mock_stack_2", Created: fixedDate, Updated: fixedDate},
},
}, nil
}
func (m FakeServiceImpl) RunMigration(_ context.Context, _ string) (*cloudmigration.MigrateDataResponseDTO, error) {
if m.ReturnError {
return nil, fmt.Errorf("mock error")
}
r := fakeMigrateDataResponseDTO()
return &r, nil
}
func fakeMigrateDataResponseDTO() cloudmigration.MigrateDataResponseDTO {
return cloudmigration.MigrateDataResponseDTO{
RunUID: "fake_uid",
Items: []cloudmigration.MigrateDataResponseItemDTO{
{Type: "type", RefID: "make_refid", Status: "ok", Error: "none"},
},
}
}
func (m FakeServiceImpl) CreateMigrationRun(ctx context.Context, run cloudmigration.CloudMigrationRun) (string, error) {
panic("implement me")
}
func (m FakeServiceImpl) GetMigrationStatus(_ context.Context, _ string) (*cloudmigration.CloudMigrationRun, error) {
if m.ReturnError {
return nil, fmt.Errorf("mock error")
}
result, err := json.Marshal(fakeMigrateDataResponseDTO())
if err != nil {
return nil, err
}
return &cloudmigration.CloudMigrationRun{
ID: 0,
UID: "fake_uid",
CloudMigrationUID: "fake_mig_uid",
Result: result,
Created: fixedDate,
Updated: fixedDate,
Finished: fixedDate,
}, nil
}
func (m FakeServiceImpl) GetMigrationRunList(_ context.Context, _ string) (*cloudmigration.CloudMigrationRunList, error) {
if m.ReturnError {
return nil, fmt.Errorf("mock error")
}
return &cloudmigration.CloudMigrationRunList{
Runs: []cloudmigration.MigrateDataResponseListDTO{
{RunUID: "fake_run_uid_1"},
{RunUID: "fake_run_uid_2"},
},
}, nil
}
@@ -1,48 +1,208 @@
package cloudmigrationimpl
import (
"context"
"encoding/base64"
"strconv"
"testing"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/cloudmigration"
fakeSecrets "github.com/grafana/grafana/pkg/services/secrets/fakes"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/tests/testsuite"
"github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
testsuite.Run(m)
}
// TODO rewrite this to include encoding and decryption
// func TestGetAllCloudMigrations(t *testing.T) {
// testDB := db.InitTestDB(t)
// s := &sqlStore{db: testDB}
// ctx := context.Background()
func Test_GetAllCloudMigrations(t *testing.T) {
_, s := setUpTest(t)
ctx := context.Background()
// t.Run("get all cloud_migrations", func(t *testing.T) {
// // replace this with proper method when created
// _, err := testDB.GetSqlxSession().Exec(ctx, `
// INSERT INTO cloud_migration (id, auth_token, stack, stack_id, region_slug, cluster_slug, created, updated)
// VALUES (1, '12345', '11111', 11111, 'test', 'test', '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000'),
// (2, '6789', '22222', 22222, 'test', 'test', '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000'),
// (3, '777', '33333', 33333, 'test', 'test', '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000');
// `)
// require.NoError(t, err)
t.Run("get all cloud_migrations", func(t *testing.T) {
value, err := s.GetAllCloudMigrations(ctx)
require.NoError(t, err)
require.Equal(t, 3, len(value))
for _, m := range value {
switch m.ID {
case 1:
require.Equal(t, "11111", m.Stack)
require.Equal(t, "12345", m.AuthToken)
case 2:
require.Equal(t, "22222", m.Stack)
require.Equal(t, "6789", m.AuthToken)
case 3:
require.Equal(t, "33333", m.Stack)
require.Equal(t, "777", m.AuthToken)
default:
require.Fail(t, "ID value not expected: "+strconv.FormatInt(m.ID, 10))
}
}
})
}
// value, err := s.GetAllCloudMigrations(ctx)
// require.NoError(t, err)
// require.Equal(t, 3, len(value))
// for _, m := range value {
// switch m.ID {
// case 1:
// require.Equal(t, "11111", m.Stack)
// require.Equal(t, "12345", m.AuthToken)
// case 2:
// require.Equal(t, "22222", m.Stack)
// require.Equal(t, "6789", m.AuthToken)
// case 3:
// require.Equal(t, "33333", m.Stack)
// require.Equal(t, "777", m.AuthToken)
// default:
// require.Fail(t, "ID value not expected: "+strconv.FormatInt(m.ID, 10))
// }
// }
// })
// }
func Test_CreateMigration(t *testing.T) {
_, s := setUpTest(t)
ctx := context.Background()
t.Run("creates migrations and reads it from the db", func(t *testing.T) {
cm := cloudmigration.CloudMigration{
AuthToken: encodeToken("token"),
Stack: "fake_stack",
StackID: 1234,
RegionSlug: "fake_slug",
ClusterSlug: "fake_cluster_slug",
}
mig, err := s.CreateMigration(ctx, cm)
require.NoError(t, err)
require.NotEmpty(t, mig.ID)
require.NotEmpty(t, mig.UID)
getRes, err := s.GetMigrationByUID(ctx, mig.UID)
require.NoError(t, err)
require.Equal(t, mig.ID, getRes.ID)
require.Equal(t, mig.UID, getRes.UID)
require.Equal(t, cm.AuthToken, getRes.AuthToken)
require.Equal(t, cm.Stack, getRes.Stack)
require.Equal(t, cm.StackID, getRes.StackID)
require.Equal(t, cm.RegionSlug, getRes.RegionSlug)
require.Equal(t, cm.ClusterSlug, getRes.ClusterSlug)
})
}
func Test_GetMigrationByUID(t *testing.T) {
_, s := setUpTest(t)
ctx := context.Background()
t.Run("find migration by uid", func(t *testing.T) {
uid := "qwerty"
mig, err := s.GetMigrationByUID(ctx, uid)
require.NoError(t, err)
require.Equal(t, uid, mig.UID)
})
t.Run("returns error if migration is not found by uid", func(t *testing.T) {
_, err := s.GetMigrationByUID(ctx, "fake_uid_1234")
require.ErrorIs(t, cloudmigration.ErrMigrationNotFound, err)
})
}
func Test_DeleteMigration(t *testing.T) {
_, s := setUpTest(t)
ctx := context.Background()
t.Run("deletes a migration from the db", func(t *testing.T) {
uid := "qwerty"
delResp, err := s.DeleteMigration(ctx, uid)
require.NoError(t, err)
require.Equal(t, uid, delResp.UID)
// now we try to find it, should return an error
_, err = s.GetMigrationByUID(ctx, uid)
require.ErrorIs(t, cloudmigration.ErrMigrationNotFound, err)
})
}
func Test_CreateMigrationRun(t *testing.T) {
_, s := setUpTest(t)
ctx := context.Background()
t.Run("creates a migration run and retrieves it from db", func(t *testing.T) {
result := []byte("OK")
cmr := cloudmigration.CloudMigrationRun{
CloudMigrationUID: "asdfg",
Result: result,
}
createResp, err := s.CreateMigrationRun(ctx, cmr)
require.NoError(t, err)
require.NotEmpty(t, createResp)
getMRResp, err := s.GetMigrationStatus(ctx, createResp)
require.NoError(t, err)
require.Equal(t, result, getMRResp.Result)
})
}
func Test_GetMigrationStatus(t *testing.T) {
_, s := setUpTest(t)
ctx := context.Background()
t.Run("gets a migration status by uid", func(t *testing.T) {
getMRResp, err := s.GetMigrationStatus(ctx, "poiuy")
require.NoError(t, err)
require.Equal(t, "poiuy", getMRResp.UID)
})
t.Run("returns error if migration run was not found", func(t *testing.T) {
getMRResp, err := s.GetMigrationStatus(ctx, "fake_uid")
require.ErrorIs(t, cloudmigration.ErrMigrationRunNotFound, err)
require.Equal(t, int64(0), getMRResp.ID)
require.Equal(t, "", getMRResp.UID)
})
}
func Test_GetMigrationStatusList(t *testing.T) {
_, s := setUpTest(t)
ctx := context.Background()
t.Run("gets migration status list from db", func(t *testing.T) {
list, err := s.GetMigrationStatusList(ctx, "qwerty")
require.NoError(t, err)
require.Equal(t, 2, len(list))
// TODO validate that this is ok
})
t.Run("returns no error if migration was not found, just empty list", func(t *testing.T) {
list, err := s.GetMigrationStatusList(ctx, "fake_migration")
require.NoError(t, err)
require.Equal(t, 0, len(list))
})
}
func setUpTest(t *testing.T) (*sqlstore.SQLStore, *sqlStore) {
testDB := db.InitTestDB(t)
s := &sqlStore{
db: testDB,
secretsService: fakeSecrets.FakeSecretsService{},
}
ctx := context.Background()
// insert cloud migration test data
_, err := testDB.GetSqlxSession().Exec(ctx, `
INSERT INTO
cloud_migration (id, uid, auth_token, stack, stack_id, region_slug, cluster_slug, created, updated)
VALUES
(1,'qwerty', ?, '11111', 11111, 'test', 'test', '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000'),
(2,'asdfgh', ?, '22222', 22222, 'test', 'test', '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000'),
(3,'zxcvbn', ?, '33333', 33333, 'test', 'test', '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000');
`,
encodeToken("12345"),
encodeToken("6789"),
encodeToken("777"),
)
require.NoError(t, err)
// insert cloud migration run test data
_, err = testDB.GetSqlxSession().Exec(ctx, `
INSERT INTO
cloud_migration_run (cloud_migration_uid, uid, result, created, updated, finished)
VALUES
('qwerty', 'poiuy', ?, '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000'),
('qwerty', 'lkjhg', ?, '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000'),
('zxcvbn', 'mnbvvc', ?, '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000');
`,
[]byte("ERROR"),
[]byte("OK"),
[]byte("OK"),
)
require.NoError(t, err)
return testDB, s
}
func encodeToken(t string) string {
return base64.StdEncoding.EncodeToString([]byte(t))
}