Chore: Refactor GoConvey in provisioning package (#40892)

* refactor goconvey in some provisioning subpackages

* fix goconvey in provisioning/datasources

* remove goconvey from notifiers subpackage

* finally resolve goconvey in dashboards subpackage
pull/41138/head
Serge Zaitsev 4 years ago committed by GitHub
parent b80fbe03f0
commit 7b15cd0ed2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 698
      pkg/services/provisioning/dashboards/file_reader_test.go
  2. 351
      pkg/services/provisioning/datasources/config_reader_test.go
  3. 270
      pkg/services/provisioning/notifiers/config_reader_test.go
  4. 15
      pkg/services/provisioning/utils/utils_test.go
  5. 171
      pkg/services/provisioning/values/values_test.go

@ -17,7 +17,8 @@ import (
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/require"
) )
const ( const (
@ -32,403 +33,432 @@ const (
var fakeService *fakeDashboardProvisioningService var fakeService *fakeDashboardProvisioningService
func TestCreatingNewDashboardFileReader(t *testing.T) { func TestCreatingNewDashboardFileReader(t *testing.T) {
Convey("creating new dashboard file reader", t, func() { setup := func() *config {
cfg := &config{ return &config{
Name: "Default", Name: "Default",
Type: "file", Type: "file",
OrgID: 1, OrgID: 1,
Folder: "", Folder: "",
Options: map[string]interface{}{}, Options: map[string]interface{}{},
} }
}
Convey("using path parameter", func() { t.Run("using path parameter", func(t *testing.T) {
cfg.Options["path"] = defaultDashboards cfg := setup()
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil) cfg.Options["path"] = defaultDashboards
So(err, ShouldBeNil) reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil)
So(reader.Path, ShouldNotEqual, "") require.NoError(t, err)
}) require.NotEqual(t, reader.Path, "")
})
Convey("using folder as options", func() { t.Run("using folder as options", func(t *testing.T) {
cfg.Options["folder"] = defaultDashboards cfg := setup()
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil) cfg.Options["folder"] = defaultDashboards
So(err, ShouldBeNil) reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil)
So(reader.Path, ShouldNotEqual, "") require.NoError(t, err)
}) require.NotEqual(t, reader.Path, "")
})
Convey("using foldersFromFilesStructure as options", func() { t.Run("using foldersFromFilesStructure as options", func(t *testing.T) {
cfg.Options["path"] = foldersFromFilesStructure cfg := setup()
cfg.Options["foldersFromFilesStructure"] = true cfg.Options["path"] = foldersFromFilesStructure
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil) cfg.Options["foldersFromFilesStructure"] = true
So(err, ShouldBeNil) reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil)
So(reader.Path, ShouldNotEqual, "") require.NoError(t, err)
}) require.NotEqual(t, reader.Path, "")
})
Convey("using full path", func() { t.Run("using full path", func(t *testing.T) {
fullPath := "/var/lib/grafana/dashboards" cfg := setup()
if runtime.GOOS == "windows" { fullPath := "/var/lib/grafana/dashboards"
fullPath = `c:\var\lib\grafana` if runtime.GOOS == "windows" {
} fullPath = `c:\var\lib\grafana`
}
cfg.Options["folder"] = fullPath cfg.Options["folder"] = fullPath
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil) reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil)
So(err, ShouldBeNil) require.NoError(t, err)
So(reader.Path, ShouldEqual, fullPath) require.Equal(t, reader.Path, fullPath)
So(filepath.IsAbs(reader.Path), ShouldBeTrue) require.True(t, filepath.IsAbs(reader.Path))
}) })
Convey("using relative path", func() { t.Run("using relative path", func(t *testing.T) {
cfg.Options["folder"] = defaultDashboards cfg := setup()
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil) cfg.Options["folder"] = defaultDashboards
So(err, ShouldBeNil) reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil)
require.NoError(t, err)
resolvedPath := reader.resolvedPath() resolvedPath := reader.resolvedPath()
So(filepath.IsAbs(resolvedPath), ShouldBeTrue) require.True(t, filepath.IsAbs(resolvedPath))
})
}) })
} }
func TestDashboardFileReader(t *testing.T) { func TestDashboardFileReader(t *testing.T) {
Convey("Dashboard file reader", t, func() { logger := log.New("test.logger")
cfg := &config{}
origNewDashboardProvisioningService := dashboards.NewProvisioningService
defer func() {
dashboards.NewProvisioningService = origNewDashboardProvisioningService
}()
setup := func() {
bus.ClearBusHandlers() bus.ClearBusHandlers()
origNewDashboardProvisioningService := dashboards.NewProvisioningService
Reset(func() {
dashboards.NewProvisioningService = origNewDashboardProvisioningService
})
fakeService = mockDashboardProvisioningService() fakeService = mockDashboardProvisioningService()
bus.AddHandler("test", mockGetDashboardQuery) bus.AddHandler("test", mockGetDashboardQuery)
logger := log.New("test.logger") cfg = &config{
Name: "Default",
Convey("Reading dashboards from disk", func() { Type: "file",
cfg := &config{ OrgID: 1,
Name: "Default", Folder: "",
Type: "file", Options: map[string]interface{}{},
OrgID: 1, }
Folder: "", }
Options: map[string]interface{}{},
}
Convey("Can read default dashboard", func() { t.Run("Reading dashboards from disk", func(t *testing.T) {
cfg.Options["path"] = defaultDashboards t.Run("Can read default dashboard", func(t *testing.T) {
cfg.Folder = "Team A" setup()
cfg.Options["path"] = defaultDashboards
cfg.Folder = "Team A"
reader, err := NewDashboardFileReader(cfg, logger, nil) reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) require.NoError(t, err)
err = reader.walkDisk(context.Background()) err = reader.walkDisk(context.Background())
So(err, ShouldBeNil) require.NoError(t, err)
folders := 0 folders := 0
dashboards := 0 dashboards := 0
for _, i := range fakeService.inserted { for _, i := range fakeService.inserted {
if i.Dashboard.IsFolder { if i.Dashboard.IsFolder {
folders++ folders++
} else { } else {
dashboards++ dashboards++
}
} }
}
So(folders, ShouldEqual, 1) require.Equal(t, folders, 1)
So(dashboards, ShouldEqual, 2) require.Equal(t, dashboards, 2)
}) })
Convey("Can read default dashboard and replace old version in database", func() {
cfg.Options["path"] = oneDashboard
stat, _ := os.Stat(oneDashboard + "/dashboard1.json")
fakeService.getDashboard = append(fakeService.getDashboard, &models.Dashboard{
Updated: stat.ModTime().AddDate(0, 0, -1),
Slug: "grafana",
})
reader, err := NewDashboardFileReader(cfg, logger, nil) t.Run("Can read default dashboard and replace old version in database", func(t *testing.T) {
So(err, ShouldBeNil) setup()
cfg.Options["path"] = oneDashboard
err = reader.walkDisk(context.Background()) stat, _ := os.Stat(oneDashboard + "/dashboard1.json")
So(err, ShouldBeNil)
So(len(fakeService.inserted), ShouldEqual, 1) fakeService.getDashboard = append(fakeService.getDashboard, &models.Dashboard{
Updated: stat.ModTime().AddDate(0, 0, -1),
Slug: "grafana",
}) })
Convey("Dashboard with older timestamp and the same checksum will not replace imported dashboard", func() { reader, err := NewDashboardFileReader(cfg, logger, nil)
cfg.Options["path"] = oneDashboard require.NoError(t, err)
absPath, err := filepath.Abs(oneDashboard + "/dashboard1.json")
So(err, ShouldBeNil)
stat, err := os.Stat(oneDashboard + "/dashboard1.json")
So(err, ShouldBeNil)
file, err := os.Open(filepath.Clean(absPath))
So(err, ShouldBeNil)
t.Cleanup(func() {
_ = file.Close()
})
checksum, err := util.Md5Sum(file)
So(err, ShouldBeNil)
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
"Default": {
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, +1).Unix(),
CheckSum: checksum,
},
},
}
reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil)
err = reader.walkDisk(context.Background()) err = reader.walkDisk(context.Background())
So(err, ShouldBeNil) require.NoError(t, err)
So(len(fakeService.inserted), ShouldEqual, 0)
})
Convey("Dashboard with older timestamp and different checksum will replace imported dashboard", func() { require.Equal(t, len(fakeService.inserted), 1)
cfg.Options["path"] = oneDashboard })
absPath, err := filepath.Abs(oneDashboard + "/dashboard1.json")
So(err, ShouldBeNil)
stat, err := os.Stat(oneDashboard + "/dashboard1.json")
So(err, ShouldBeNil)
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
"Default": {
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, +1).Unix(),
CheckSum: "fakechecksum",
},
},
}
reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil)
err = reader.walkDisk(context.Background()) t.Run("Dashboard with older timestamp and the same checksum will not replace imported dashboard", func(t *testing.T) {
So(err, ShouldBeNil) setup()
So(len(fakeService.inserted), ShouldEqual, 1) cfg.Options["path"] = oneDashboard
absPath, err := filepath.Abs(oneDashboard + "/dashboard1.json")
require.NoError(t, err)
stat, err := os.Stat(oneDashboard + "/dashboard1.json")
require.NoError(t, err)
file, err := os.Open(filepath.Clean(absPath))
require.NoError(t, err)
t.Cleanup(func() {
_ = file.Close()
}) })
Convey("Dashboard with newer timestamp and the same checksum will not replace imported dashboard", func() { checksum, err := util.Md5Sum(file)
cfg.Options["path"] = oneDashboard require.NoError(t, err)
absPath, err := filepath.Abs(oneDashboard + "/dashboard1.json")
So(err, ShouldBeNil) fakeService.provisioned = map[string][]*models.DashboardProvisioning{
stat, err := os.Stat(oneDashboard + "/dashboard1.json") "Default": {
So(err, ShouldBeNil) {
file, err := os.Open(filepath.Clean(absPath)) Name: "Default",
So(err, ShouldBeNil) ExternalId: absPath,
t.Cleanup(func() { Updated: stat.ModTime().AddDate(0, 0, +1).Unix(),
_ = file.Close() CheckSum: checksum,
})
checksum, err := util.Md5Sum(file)
So(err, ShouldBeNil)
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
"Default": {
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, -1).Unix(),
CheckSum: checksum,
},
}, },
} },
}
reader, err := NewDashboardFileReader(cfg, logger, nil) reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) require.NoError(t, err)
err = reader.walkDisk(context.Background()) err = reader.walkDisk(context.Background())
So(err, ShouldBeNil) require.NoError(t, err)
So(len(fakeService.inserted), ShouldEqual, 0) require.Equal(t, len(fakeService.inserted), 0)
}) })
t.Run("Dashboard with older timestamp and different checksum will replace imported dashboard", func(t *testing.T) {
setup()
cfg.Options["path"] = oneDashboard
absPath, err := filepath.Abs(oneDashboard + "/dashboard1.json")
require.NoError(t, err)
stat, err := os.Stat(oneDashboard + "/dashboard1.json")
require.NoError(t, err)
Convey("Dashboard with newer timestamp and different checksum should replace imported dashboard", func() { fakeService.provisioned = map[string][]*models.DashboardProvisioning{
cfg.Options["path"] = oneDashboard "Default": {
absPath, err := filepath.Abs(oneDashboard + "/dashboard1.json") {
So(err, ShouldBeNil) Name: "Default",
stat, err := os.Stat(oneDashboard + "/dashboard1.json") ExternalId: absPath,
So(err, ShouldBeNil) Updated: stat.ModTime().AddDate(0, 0, +1).Unix(),
CheckSum: "fakechecksum",
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
"Default": {
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, -1).Unix(),
CheckSum: "fakechecksum",
},
}, },
} },
}
reader, err := NewDashboardFileReader(cfg, logger, nil)
require.NoError(t, err)
reader, err := NewDashboardFileReader(cfg, logger, nil) err = reader.walkDisk(context.Background())
So(err, ShouldBeNil) require.NoError(t, err)
require.Equal(t, len(fakeService.inserted), 1)
})
err = reader.walkDisk(context.Background()) t.Run("Dashboard with newer timestamp and the same checksum will not replace imported dashboard", func(t *testing.T) {
So(err, ShouldBeNil) setup()
So(len(fakeService.inserted), ShouldEqual, 1) cfg.Options["path"] = oneDashboard
absPath, err := filepath.Abs(oneDashboard + "/dashboard1.json")
require.NoError(t, err)
stat, err := os.Stat(oneDashboard + "/dashboard1.json")
require.NoError(t, err)
file, err := os.Open(filepath.Clean(absPath))
require.NoError(t, err)
t.Cleanup(func() {
_ = file.Close()
}) })
Convey("Overrides id from dashboard.json files", func() { checksum, err := util.Md5Sum(file)
cfg.Options["path"] = containingID require.NoError(t, err)
reader, err := NewDashboardFileReader(cfg, logger, nil) fakeService.provisioned = map[string][]*models.DashboardProvisioning{
So(err, ShouldBeNil) "Default": {
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, -1).Unix(),
CheckSum: checksum,
},
},
}
err = reader.walkDisk(context.Background()) reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) require.NoError(t, err)
So(len(fakeService.inserted), ShouldEqual, 1) err = reader.walkDisk(context.Background())
}) require.NoError(t, err)
require.Equal(t, len(fakeService.inserted), 0)
})
Convey("Get folder from files structure", func() { t.Run("Dashboard with newer timestamp and different checksum should replace imported dashboard", func(t *testing.T) {
cfg.Options["path"] = foldersFromFilesStructure setup()
cfg.Options["foldersFromFilesStructure"] = true cfg.Options["path"] = oneDashboard
absPath, err := filepath.Abs(oneDashboard + "/dashboard1.json")
require.NoError(t, err)
stat, err := os.Stat(oneDashboard + "/dashboard1.json")
require.NoError(t, err)
reader, err := NewDashboardFileReader(cfg, logger, nil) fakeService.provisioned = map[string][]*models.DashboardProvisioning{
So(err, ShouldBeNil) "Default": {
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, -1).Unix(),
CheckSum: "fakechecksum",
},
},
}
err = reader.walkDisk(context.Background()) reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) require.NoError(t, err)
So(len(fakeService.inserted), ShouldEqual, 5) err = reader.walkDisk(context.Background())
require.NoError(t, err)
require.Equal(t, len(fakeService.inserted), 1)
})
foldersCount := 0 t.Run("Overrides id from dashboard.json files", func(t *testing.T) {
for _, d := range fakeService.inserted { setup()
if d.Dashboard.IsFolder { cfg.Options["path"] = containingID
foldersCount++
}
}
So(foldersCount, ShouldEqual, 2)
foldersAndDashboards := make(map[string]struct{}, 5)
for _, d := range fakeService.inserted {
title := d.Dashboard.Title
if _, ok := foldersAndDashboards[title]; ok {
So(fmt.Errorf("dashboard title %q already exists", title), ShouldBeNil)
}
switch title {
case "folderOne", "folderTwo":
So(d.Dashboard.IsFolder, ShouldBeTrue)
case "Grafana1", "Grafana2", "RootDashboard":
So(d.Dashboard.IsFolder, ShouldBeFalse)
default:
So(fmt.Errorf("unknown dashboard title %q", title), ShouldBeNil)
}
foldersAndDashboards[title] = struct{}{}
}
})
Convey("Invalid configuration should return error", func() { reader, err := NewDashboardFileReader(cfg, logger, nil)
cfg := &config{ require.NoError(t, err)
Name: "Default",
Type: "file",
OrgID: 1,
Folder: "",
}
_, err := NewDashboardFileReader(cfg, logger, nil) err = reader.walkDisk(context.Background())
So(err, ShouldNotBeNil) require.NoError(t, err)
})
Convey("Broken dashboards should not cause error", func() { require.Equal(t, len(fakeService.inserted), 1)
cfg.Options["path"] = brokenDashboards })
_, err := NewDashboardFileReader(cfg, logger, nil) t.Run("Get folder from files structure", func(t *testing.T) {
So(err, ShouldBeNil) setup()
}) cfg.Options["path"] = foldersFromFilesStructure
cfg.Options["foldersFromFilesStructure"] = true
Convey("Two dashboard providers should be able to provisioned the same dashboard without uid", func() { reader, err := NewDashboardFileReader(cfg, logger, nil)
cfg1 := &config{Name: "1", Type: "file", OrgID: 1, Folder: "f1", Options: map[string]interface{}{"path": containingID}} require.NoError(t, err)
cfg2 := &config{Name: "2", Type: "file", OrgID: 1, Folder: "f2", Options: map[string]interface{}{"path": containingID}}
reader1, err := NewDashboardFileReader(cfg1, logger, nil) err = reader.walkDisk(context.Background())
So(err, ShouldBeNil) require.NoError(t, err)
err = reader1.walkDisk(context.Background()) require.Equal(t, len(fakeService.inserted), 5)
So(err, ShouldBeNil)
reader2, err := NewDashboardFileReader(cfg2, logger, nil) foldersCount := 0
So(err, ShouldBeNil) for _, d := range fakeService.inserted {
if d.Dashboard.IsFolder {
foldersCount++
}
}
require.Equal(t, foldersCount, 2)
err = reader2.walkDisk(context.Background()) foldersAndDashboards := make(map[string]struct{}, 5)
So(err, ShouldBeNil) for _, d := range fakeService.inserted {
title := d.Dashboard.Title
if _, ok := foldersAndDashboards[title]; ok {
require.Nil(t, fmt.Errorf("dashboard title %q already exists", title))
}
var folderCount int switch title {
var dashCount int case "folderOne", "folderTwo":
for _, o := range fakeService.inserted { require.True(t, d.Dashboard.IsFolder)
if o.Dashboard.IsFolder { case "Grafana1", "Grafana2", "RootDashboard":
folderCount++ require.False(t, d.Dashboard.IsFolder)
} else { default:
dashCount++ require.Nil(t, fmt.Errorf("unknown dashboard title %q", title))
}
} }
So(folderCount, ShouldEqual, 2) foldersAndDashboards[title] = struct{}{}
So(dashCount, ShouldEqual, 2) }
})
}) })
Convey("Should not create new folder if folder name is missing", func() { t.Run("Invalid configuration should return error", func(t *testing.T) {
setup()
cfg := &config{ cfg := &config{
Name: "Default", Name: "Default",
Type: "file", Type: "file",
OrgID: 1, OrgID: 1,
Folder: "", Folder: "",
Options: map[string]interface{}{
"folder": defaultDashboards,
},
} }
_, err := getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder) _, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldEqual, ErrFolderNameMissing) require.NotNil(t, err)
}) })
Convey("can get or Create dashboard folder", func() { t.Run("Broken dashboards should not cause error", func(t *testing.T) {
cfg := &config{ setup()
Name: "Default", cfg.Options["path"] = brokenDashboards
Type: "file",
OrgID: 1,
Folder: "TEAM A",
Options: map[string]interface{}{
"folder": defaultDashboards,
},
}
folderID, err := getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder) _, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) require.NoError(t, err)
inserted := false })
for _, d := range fakeService.inserted {
if d.Dashboard.IsFolder && d.Dashboard.Id == folderID { t.Run("Two dashboard providers should be able to provisioned the same dashboard without uid", func(t *testing.T) {
inserted = true setup()
cfg1 := &config{Name: "1", Type: "file", OrgID: 1, Folder: "f1", Options: map[string]interface{}{"path": containingID}}
cfg2 := &config{Name: "2", Type: "file", OrgID: 1, Folder: "f2", Options: map[string]interface{}{"path": containingID}}
reader1, err := NewDashboardFileReader(cfg1, logger, nil)
require.NoError(t, err)
err = reader1.walkDisk(context.Background())
require.NoError(t, err)
reader2, err := NewDashboardFileReader(cfg2, logger, nil)
require.NoError(t, err)
err = reader2.walkDisk(context.Background())
require.NoError(t, err)
var folderCount int
var dashCount int
for _, o := range fakeService.inserted {
if o.Dashboard.IsFolder {
folderCount++
} else {
dashCount++
} }
} }
So(len(fakeService.inserted), ShouldEqual, 1)
So(inserted, ShouldBeTrue) require.Equal(t, folderCount, 2)
require.Equal(t, dashCount, 2)
}) })
})
Convey("Walking the folder with dashboards", func() { t.Run("Should not create new folder if folder name is missing", func(t *testing.T) {
noFiles := map[string]os.FileInfo{} setup()
cfg := &config{
Name: "Default",
Type: "file",
OrgID: 1,
Folder: "",
Options: map[string]interface{}{
"folder": defaultDashboards,
},
}
Convey("should skip dirs that starts with .", func() { _, err := getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder)
shouldSkip := createWalkFn(noFiles)("path", &FakeFileInfo{isDirectory: true, name: ".folder"}, nil) require.Equal(t, err, ErrFolderNameMissing)
So(shouldSkip, ShouldEqual, filepath.SkipDir) })
})
Convey("should keep walking if file is not .json", func() { t.Run("can get or Create dashboard folder", func(t *testing.T) {
shouldSkip := createWalkFn(noFiles)("path", &FakeFileInfo{isDirectory: true, name: "folder"}, nil) setup()
So(shouldSkip, ShouldBeNil) cfg := &config{
}) Name: "Default",
Type: "file",
OrgID: 1,
Folder: "TEAM A",
Options: map[string]interface{}{
"folder": defaultDashboards,
},
}
folderID, err := getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder)
require.NoError(t, err)
inserted := false
for _, d := range fakeService.inserted {
if d.Dashboard.IsFolder && d.Dashboard.Id == folderID {
inserted = true
}
}
require.Equal(t, len(fakeService.inserted), 1)
require.True(t, inserted)
})
t.Run("Walking the folder with dashboards", func(t *testing.T) {
setup()
noFiles := map[string]os.FileInfo{}
t.Run("should skip dirs that starts with .", func(t *testing.T) {
shouldSkip := createWalkFn(noFiles)("path", &FakeFileInfo{isDirectory: true, name: ".folder"}, nil)
require.Equal(t, shouldSkip, filepath.SkipDir)
}) })
Convey("Given missing dashboard file", func() { t.Run("should keep walking if file is not .json", func(t *testing.T) {
cfg := &config{ shouldSkip := createWalkFn(noFiles)("path", &FakeFileInfo{isDirectory: true, name: "folder"}, nil)
require.Nil(t, shouldSkip)
})
})
t.Run("Given missing dashboard file", func(t *testing.T) {
absPath1, err := filepath.Abs(unprovision + "/dashboard1.json")
require.NoError(t, err)
// This one does not exist on disk, simulating a deleted file
absPath2, err := filepath.Abs(unprovision + "/dashboard2.json")
require.NoError(t, err)
setupFakeService := func() {
setup()
cfg = &config{
Name: "Default", Name: "Default",
Type: "file", Type: "file",
OrgID: 1, OrgID: 1,
@ -442,44 +472,40 @@ func TestDashboardFileReader(t *testing.T) {
{Dashboard: &models.Dashboard{Id: 2}}, {Dashboard: &models.Dashboard{Id: 2}},
} }
absPath1, err := filepath.Abs(unprovision + "/dashboard1.json")
So(err, ShouldBeNil)
// This one does not exist on disk, simulating a deleted file
absPath2, err := filepath.Abs(unprovision + "/dashboard2.json")
So(err, ShouldBeNil)
fakeService.provisioned = map[string][]*models.DashboardProvisioning{ fakeService.provisioned = map[string][]*models.DashboardProvisioning{
"Default": { "Default": {
{DashboardId: 1, Name: "Default", ExternalId: absPath1}, {DashboardId: 1, Name: "Default", ExternalId: absPath1},
{DashboardId: 2, Name: "Default", ExternalId: absPath2}, {DashboardId: 2, Name: "Default", ExternalId: absPath2},
}, },
} }
}
Convey("Missing dashboard should be unprovisioned if DisableDeletion = true", func() { t.Run("Missing dashboard should be unprovisioned if DisableDeletion = true", func(t *testing.T) {
cfg.DisableDeletion = true setupFakeService()
cfg.DisableDeletion = true
reader, err := NewDashboardFileReader(cfg, logger, nil) reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) require.NoError(t, err)
err = reader.walkDisk(context.Background()) err = reader.walkDisk(context.Background())
So(err, ShouldBeNil) require.NoError(t, err)
So(len(fakeService.provisioned["Default"]), ShouldEqual, 1) require.Equal(t, len(fakeService.provisioned["Default"]), 1)
So(fakeService.provisioned["Default"][0].ExternalId, ShouldEqual, absPath1) require.Equal(t, fakeService.provisioned["Default"][0].ExternalId, absPath1)
}) })
Convey("Missing dashboard should be deleted if DisableDeletion = false", func() { t.Run("Missing dashboard should be deleted if DisableDeletion = false", func(t *testing.T) {
reader, err := NewDashboardFileReader(cfg, logger, nil) setupFakeService()
So(err, ShouldBeNil) reader, err := NewDashboardFileReader(cfg, logger, nil)
require.NoError(t, err)
err = reader.walkDisk(context.Background()) err = reader.walkDisk(context.Background())
So(err, ShouldBeNil) require.NoError(t, err)
So(len(fakeService.provisioned["Default"]), ShouldEqual, 1) require.Equal(t, len(fakeService.provisioned["Default"]), 1)
So(fakeService.provisioned["Default"][0].ExternalId, ShouldEqual, absPath1) require.Equal(t, fakeService.provisioned["Default"][0].ExternalId, absPath1)
So(len(fakeService.inserted), ShouldEqual, 1) require.Equal(t, len(fakeService.inserted), 1)
So(fakeService.inserted[0].Dashboard.Id, ShouldEqual, 1) require.Equal(t, fakeService.inserted[0].Dashboard.Id, int64(1))
})
}) })
}) })
} }

@ -9,7 +9,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/require"
) )
var ( var (
@ -29,7 +29,7 @@ var (
) )
func TestDatasourceAsConfig(t *testing.T) { func TestDatasourceAsConfig(t *testing.T) {
Convey("Testing datasource as configuration", t, func() { setup := func() {
fakeRepo = &fakeRepository{} fakeRepo = &fakeRepository{}
bus.ClearBusHandlers() bus.ClearBusHandlers()
bus.AddHandler("test", mockDelete) bus.AddHandler("test", mockDelete)
@ -37,226 +37,229 @@ func TestDatasourceAsConfig(t *testing.T) {
bus.AddHandler("test", mockUpdate) bus.AddHandler("test", mockUpdate)
bus.AddHandler("test", mockGet) bus.AddHandler("test", mockGet)
bus.AddHandler("test", mockGetOrg) bus.AddHandler("test", mockGetOrg)
}
t.Run("apply default values when missing", func(t *testing.T) {
setup()
dc := newDatasourceProvisioner(logger)
err := dc.applyChanges(context.Background(), withoutDefaults)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
require.Equal(t, len(fakeRepo.inserted), 1)
require.Equal(t, fakeRepo.inserted[0].OrgId, int64(1))
require.Equal(t, fakeRepo.inserted[0].Access, models.DsAccess("proxy"))
})
t.Run("no datasource in database", func(t *testing.T) {
setup()
dc := newDatasourceProvisioner(logger)
err := dc.applyChanges(context.Background(), twoDatasourcesConfig)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
require.Equal(t, len(fakeRepo.deleted), 0)
require.Equal(t, len(fakeRepo.inserted), 2)
require.Equal(t, len(fakeRepo.updated), 0)
})
t.Run("One datasource in database with same name", func(t *testing.T) {
setup()
fakeRepo.loadAll = []*models.DataSource{
{Name: "Graphite", OrgId: 1, Id: 1},
}
Convey("apply default values when missing", func() { t.Run("should update one datasource", func(t *testing.T) {
dc := newDatasourceProvisioner(logger) dc := newDatasourceProvisioner(logger)
err := dc.applyChanges(context.Background(), withoutDefaults) err := dc.applyChanges(context.Background(), twoDatasourcesConfig)
if err != nil { if err != nil {
t.Fatalf("applyChanges return an error %v", err) t.Fatalf("applyChanges return an error %v", err)
} }
So(len(fakeRepo.inserted), ShouldEqual, 1) require.Equal(t, len(fakeRepo.deleted), 0)
So(fakeRepo.inserted[0].OrgId, ShouldEqual, 1) require.Equal(t, len(fakeRepo.inserted), 1)
So(fakeRepo.inserted[0].Access, ShouldEqual, "proxy") require.Equal(t, len(fakeRepo.updated), 1)
}) })
})
Convey("One configured datasource", func() { t.Run("Two datasources with is_default", func(t *testing.T) {
Convey("no datasource in database", func() { setup()
dc := newDatasourceProvisioner(logger) dc := newDatasourceProvisioner(logger)
err := dc.applyChanges(context.Background(), twoDatasourcesConfig) err := dc.applyChanges(context.Background(), doubleDatasourcesConfig)
if err != nil { t.Run("should raise error", func(t *testing.T) { require.Equal(t, err, ErrInvalidConfigToManyDefault) })
t.Fatalf("applyChanges return an error %v", err) })
}
So(len(fakeRepo.deleted), ShouldEqual, 0)
So(len(fakeRepo.inserted), ShouldEqual, 2)
So(len(fakeRepo.updated), ShouldEqual, 0)
})
Convey("One datasource in database with same name", func() {
fakeRepo.loadAll = []*models.DataSource{
{Name: "Graphite", OrgId: 1, Id: 1},
}
Convey("should update one datasource", func() {
dc := newDatasourceProvisioner(logger)
err := dc.applyChanges(context.Background(), twoDatasourcesConfig)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
So(len(fakeRepo.deleted), ShouldEqual, 0)
So(len(fakeRepo.inserted), ShouldEqual, 1)
So(len(fakeRepo.updated), ShouldEqual, 1)
})
})
Convey("Two datasources with is_default", func() { t.Run("Multiple datasources in different organizations with isDefault in each organization", func(t *testing.T) {
dc := newDatasourceProvisioner(logger) setup()
err := dc.applyChanges(context.Background(), doubleDatasourcesConfig) dc := newDatasourceProvisioner(logger)
Convey("should raise error", func() { err := dc.applyChanges(context.Background(), multipleOrgsWithDefault)
So(err, ShouldEqual, ErrInvalidConfigToManyDefault) t.Run("should not raise error", func(t *testing.T) {
}) require.NoError(t, err)
}) require.Equal(t, len(fakeRepo.inserted), 4)
require.True(t, fakeRepo.inserted[0].IsDefault)
require.Equal(t, fakeRepo.inserted[0].OrgId, int64(1))
require.True(t, fakeRepo.inserted[2].IsDefault)
require.Equal(t, fakeRepo.inserted[2].OrgId, int64(2))
}) })
})
Convey("Multiple datasources in different organizations with isDefault in each organization", func() { t.Run("Two configured datasource and purge others ", func(t *testing.T) {
dc := newDatasourceProvisioner(logger) setup()
err := dc.applyChanges(context.Background(), multipleOrgsWithDefault) t.Run("two other datasources in database", func(t *testing.T) {
Convey("should not raise error", func() { fakeRepo.loadAll = []*models.DataSource{
So(err, ShouldBeNil) {Name: "old-graphite", OrgId: 1, Id: 1},
So(len(fakeRepo.inserted), ShouldEqual, 4) {Name: "old-graphite2", OrgId: 1, Id: 2},
So(fakeRepo.inserted[0].IsDefault, ShouldBeTrue) }
So(fakeRepo.inserted[0].OrgId, ShouldEqual, 1)
So(fakeRepo.inserted[2].IsDefault, ShouldBeTrue)
So(fakeRepo.inserted[2].OrgId, ShouldEqual, 2)
})
})
Convey("Two configured datasource and purge others ", func() { t.Run("should have two new datasources", func(t *testing.T) {
Convey("two other datasources in database", func() { dc := newDatasourceProvisioner(logger)
fakeRepo.loadAll = []*models.DataSource{ err := dc.applyChanges(context.Background(), twoDatasourcesConfigPurgeOthers)
{Name: "old-graphite", OrgId: 1, Id: 1}, if err != nil {
{Name: "old-graphite2", OrgId: 1, Id: 2}, t.Fatalf("applyChanges return an error %v", err)
} }
Convey("should have two new datasources", func() { require.Equal(t, len(fakeRepo.deleted), 2)
dc := newDatasourceProvisioner(logger) require.Equal(t, len(fakeRepo.inserted), 2)
err := dc.applyChanges(context.Background(), twoDatasourcesConfigPurgeOthers) require.Equal(t, len(fakeRepo.updated), 0)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
So(len(fakeRepo.deleted), ShouldEqual, 2)
So(len(fakeRepo.inserted), ShouldEqual, 2)
So(len(fakeRepo.updated), ShouldEqual, 0)
})
}) })
}) })
})
Convey("Two configured datasource and purge others = false", func() { t.Run("Two configured datasource and purge others = false", func(t *testing.T) {
Convey("two other datasources in database", func() { setup()
fakeRepo.loadAll = []*models.DataSource{ t.Run("two other datasources in database", func(t *testing.T) {
{Name: "Graphite", OrgId: 1, Id: 1}, fakeRepo.loadAll = []*models.DataSource{
{Name: "old-graphite2", OrgId: 1, Id: 2}, {Name: "Graphite", OrgId: 1, Id: 1},
{Name: "old-graphite2", OrgId: 1, Id: 2},
}
t.Run("should have two new datasources", func(t *testing.T) {
dc := newDatasourceProvisioner(logger)
err := dc.applyChanges(context.Background(), twoDatasourcesConfig)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
} }
Convey("should have two new datasources", func() { require.Equal(t, len(fakeRepo.deleted), 0)
dc := newDatasourceProvisioner(logger) require.Equal(t, len(fakeRepo.inserted), 1)
err := dc.applyChanges(context.Background(), twoDatasourcesConfig) require.Equal(t, len(fakeRepo.updated), 1)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
So(len(fakeRepo.deleted), ShouldEqual, 0)
So(len(fakeRepo.inserted), ShouldEqual, 1)
So(len(fakeRepo.updated), ShouldEqual, 1)
})
}) })
}) })
})
Convey("broken yaml should return error", func() { t.Run("broken yaml should return error", func(t *testing.T) {
reader := &configReader{} reader := &configReader{}
_, err := reader.readConfig(brokenYaml) _, err := reader.readConfig(brokenYaml)
So(err, ShouldNotBeNil) require.NotNil(t, err)
}) })
Convey("invalid access should warn about invalid value and return 'proxy'", func() { t.Run("invalid access should warn about invalid value and return 'proxy'", func(t *testing.T) {
reader := &configReader{log: logger} reader := &configReader{log: logger}
configs, err := reader.readConfig(invalidAccess) configs, err := reader.readConfig(invalidAccess)
So(err, ShouldBeNil) require.NoError(t, err)
So(configs[0].Datasources[0].Access, ShouldEqual, models.DS_ACCESS_PROXY) require.Equal(t, configs[0].Datasources[0].Access, models.DS_ACCESS_PROXY)
}) })
Convey("skip invalid directory", func() { t.Run("skip invalid directory", func(t *testing.T) {
cfgProvider := &configReader{log: log.New("test logger")} cfgProvider := &configReader{log: log.New("test logger")}
cfg, err := cfgProvider.readConfig("./invalid-directory") cfg, err := cfgProvider.readConfig("./invalid-directory")
if err != nil { if err != nil {
t.Fatalf("readConfig return an error %v", err) t.Fatalf("readConfig return an error %v", err)
} }
So(len(cfg), ShouldEqual, 0) require.Equal(t, len(cfg), 0)
}) })
Convey("can read all properties from version 1", func() { t.Run("can read all properties from version 1", func(t *testing.T) {
_ = os.Setenv("TEST_VAR", "name") _ = os.Setenv("TEST_VAR", "name")
cfgProvider := &configReader{log: log.New("test logger")} cfgProvider := &configReader{log: log.New("test logger")}
cfg, err := cfgProvider.readConfig(allProperties) cfg, err := cfgProvider.readConfig(allProperties)
_ = os.Unsetenv("TEST_VAR") _ = os.Unsetenv("TEST_VAR")
if err != nil { if err != nil {
t.Fatalf("readConfig return an error %v", err) t.Fatalf("readConfig return an error %v", err)
} }
So(len(cfg), ShouldEqual, 3) require.Equal(t, len(cfg), 3)
dsCfg := cfg[0] dsCfg := cfg[0]
So(dsCfg.APIVersion, ShouldEqual, 1) require.Equal(t, dsCfg.APIVersion, int64(1))
validateDatasourceV1(dsCfg) validateDatasourceV1(t, dsCfg)
validateDeleteDatasources(dsCfg) validateDeleteDatasources(t, dsCfg)
dsCount := 0 dsCount := 0
delDsCount := 0 delDsCount := 0
for _, c := range cfg { for _, c := range cfg {
dsCount += len(c.Datasources) dsCount += len(c.Datasources)
delDsCount += len(c.DeleteDatasources) delDsCount += len(c.DeleteDatasources)
} }
So(dsCount, ShouldEqual, 2) require.Equal(t, dsCount, 2)
So(delDsCount, ShouldEqual, 1) require.Equal(t, delDsCount, 1)
}) })
Convey("can read all properties from version 0", func() { t.Run("can read all properties from version 0", func(t *testing.T) {
cfgProvider := &configReader{log: log.New("test logger")} cfgProvider := &configReader{log: log.New("test logger")}
cfg, err := cfgProvider.readConfig(versionZero) cfg, err := cfgProvider.readConfig(versionZero)
if err != nil { if err != nil {
t.Fatalf("readConfig return an error %v", err) t.Fatalf("readConfig return an error %v", err)
} }
So(len(cfg), ShouldEqual, 1) require.Equal(t, len(cfg), 1)
dsCfg := cfg[0] dsCfg := cfg[0]
So(dsCfg.APIVersion, ShouldEqual, 0) require.Equal(t, dsCfg.APIVersion, int64(0))
validateDatasource(dsCfg) validateDatasource(t, dsCfg)
validateDeleteDatasources(dsCfg) validateDeleteDatasources(t, dsCfg)
})
}) })
} }
func validateDeleteDatasources(dsCfg *configs) { func validateDeleteDatasources(t *testing.T, dsCfg *configs) {
So(len(dsCfg.DeleteDatasources), ShouldEqual, 1) require.Equal(t, len(dsCfg.DeleteDatasources), 1)
deleteDs := dsCfg.DeleteDatasources[0] deleteDs := dsCfg.DeleteDatasources[0]
So(deleteDs.Name, ShouldEqual, "old-graphite3") require.Equal(t, deleteDs.Name, "old-graphite3")
So(deleteDs.OrgID, ShouldEqual, 2) require.Equal(t, deleteDs.OrgID, int64(2))
} }
func validateDatasource(dsCfg *configs) { func validateDatasource(t *testing.T, dsCfg *configs) {
ds := dsCfg.Datasources[0] ds := dsCfg.Datasources[0]
So(ds.Name, ShouldEqual, "name") require.Equal(t, ds.Name, "name")
So(ds.Type, ShouldEqual, "type") require.Equal(t, ds.Type, "type")
So(ds.Access, ShouldEqual, models.DS_ACCESS_PROXY) require.Equal(t, ds.Access, models.DS_ACCESS_PROXY)
So(ds.OrgID, ShouldEqual, 2) require.Equal(t, ds.OrgID, int64(2))
So(ds.URL, ShouldEqual, "url") require.Equal(t, ds.URL, "url")
So(ds.User, ShouldEqual, "user") require.Equal(t, ds.User, "user")
So(ds.Password, ShouldEqual, "password") require.Equal(t, ds.Password, "password")
So(ds.Database, ShouldEqual, "database") require.Equal(t, ds.Database, "database")
So(ds.BasicAuth, ShouldBeTrue) require.True(t, ds.BasicAuth)
So(ds.BasicAuthUser, ShouldEqual, "basic_auth_user") require.Equal(t, ds.BasicAuthUser, "basic_auth_user")
So(ds.BasicAuthPassword, ShouldEqual, "basic_auth_password") require.Equal(t, ds.BasicAuthPassword, "basic_auth_password")
So(ds.WithCredentials, ShouldBeTrue) require.True(t, ds.WithCredentials)
So(ds.IsDefault, ShouldBeTrue) require.True(t, ds.IsDefault)
So(ds.Editable, ShouldBeTrue) require.True(t, ds.Editable)
So(ds.Version, ShouldEqual, 10) require.Equal(t, ds.Version, 10)
So(len(ds.JSONData), ShouldBeGreaterThan, 2) require.Greater(t, len(ds.JSONData), 2)
So(ds.JSONData["graphiteVersion"], ShouldEqual, "1.1") require.Equal(t, ds.JSONData["graphiteVersion"], "1.1")
So(ds.JSONData["tlsAuth"], ShouldEqual, true) require.Equal(t, ds.JSONData["tlsAuth"], true)
So(ds.JSONData["tlsAuthWithCACert"], ShouldEqual, true) require.Equal(t, ds.JSONData["tlsAuthWithCACert"], true)
So(len(ds.SecureJSONData), ShouldBeGreaterThan, 2) require.Greater(t, len(ds.SecureJSONData), 2)
So(ds.SecureJSONData["tlsCACert"], ShouldEqual, "MjNOcW9RdkbUDHZmpco2HCYzVq9dE+i6Yi+gmUJotq5CDA==") require.Equal(t, ds.SecureJSONData["tlsCACert"], "MjNOcW9RdkbUDHZmpco2HCYzVq9dE+i6Yi+gmUJotq5CDA==")
So(ds.SecureJSONData["tlsClientCert"], ShouldEqual, "ckN0dGlyMXN503YNfjTcf9CV+GGQneN+xmAclQ==") require.Equal(t, ds.SecureJSONData["tlsClientCert"], "ckN0dGlyMXN503YNfjTcf9CV+GGQneN+xmAclQ==")
So(ds.SecureJSONData["tlsClientKey"], ShouldEqual, "ZkN4aG1aNkja/gKAB1wlnKFIsy2SRDq4slrM0A==") require.Equal(t, ds.SecureJSONData["tlsClientKey"], "ZkN4aG1aNkja/gKAB1wlnKFIsy2SRDq4slrM0A==")
} }
func validateDatasourceV1(dsCfg *configs) { func validateDatasourceV1(t *testing.T, dsCfg *configs) {
validateDatasource(dsCfg) validateDatasource(t, dsCfg)
ds := dsCfg.Datasources[0] ds := dsCfg.Datasources[0]
So(ds.UID, ShouldEqual, "test_uid") require.Equal(t, ds.UID, "test_uid")
} }
type fakeRepository struct { type fakeRepository struct {

@ -12,7 +12,8 @@ import (
"github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/require"
) )
var ( var (
@ -29,31 +30,35 @@ var (
) )
func TestNotificationAsConfig(t *testing.T) { func TestNotificationAsConfig(t *testing.T) {
var sqlStore *sqlstore.SQLStore
logger := log.New("fake.log") logger := log.New("fake.log")
Convey("Testing notification as configuration", t, func() { t.Run("Testing notification as configuration", func(t *testing.T) {
sqlStore := sqlstore.InitTestDB(t) setup := func() {
setupBusHandlers(sqlStore) sqlStore = sqlstore.InitTestDB(t)
setupBusHandlers(sqlStore)
for i := 1; i < 5; i++ { for i := 1; i < 5; i++ {
orgCommand := models.CreateOrgCommand{Name: fmt.Sprintf("Main Org. %v", i)} orgCommand := models.CreateOrgCommand{Name: fmt.Sprintf("Main Org. %v", i)}
err := sqlstore.CreateOrg(&orgCommand) err := sqlstore.CreateOrg(&orgCommand)
So(err, ShouldBeNil) require.NoError(t, err)
} }
alerting.RegisterNotifier(&alerting.NotifierPlugin{ alerting.RegisterNotifier(&alerting.NotifierPlugin{
Type: "slack", Type: "slack",
Name: "slack", Name: "slack",
Factory: notifiers.NewSlackNotifier, Factory: notifiers.NewSlackNotifier,
}) })
alerting.RegisterNotifier(&alerting.NotifierPlugin{ alerting.RegisterNotifier(&alerting.NotifierPlugin{
Type: "email", Type: "email",
Name: "email", Name: "email",
Factory: notifiers.NewEmailNotifier, Factory: notifiers.NewEmailNotifier,
}) })
}
Convey("Can read correct properties", func() { t.Run("Can read correct properties", func(t *testing.T) {
setup()
_ = os.Setenv("TEST_VAR", "default") _ = os.Setenv("TEST_VAR", "default")
cfgProvider := &configReader{ cfgProvider := &configReader{
encryptionService: ossencryption.ProvideService(), encryptionService: ossencryption.ProvideService(),
@ -65,73 +70,74 @@ func TestNotificationAsConfig(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("readConfig return an error %v", err) t.Fatalf("readConfig return an error %v", err)
} }
So(len(cfg), ShouldEqual, 1) require.Equal(t, len(cfg), 1)
ntCfg := cfg[0] ntCfg := cfg[0]
nts := ntCfg.Notifications nts := ntCfg.Notifications
So(len(nts), ShouldEqual, 4) require.Equal(t, len(nts), 4)
nt := nts[0] nt := nts[0]
So(nt.Name, ShouldEqual, "default-slack-notification") require.Equal(t, nt.Name, "default-slack-notification")
So(nt.Type, ShouldEqual, "slack") require.Equal(t, nt.Type, "slack")
So(nt.OrgID, ShouldEqual, 2) require.Equal(t, nt.OrgID, int64(2))
So(nt.UID, ShouldEqual, "notifier1") require.Equal(t, nt.UID, "notifier1")
So(nt.IsDefault, ShouldBeTrue) require.True(t, nt.IsDefault)
So(nt.Settings, ShouldResemble, map[string]interface{}{ require.Equal(t, nt.Settings, map[string]interface{}{
"recipient": "XXX", "token": "xoxb", "uploadImage": true, "url": "https://slack.com", "recipient": "XXX", "token": "xoxb", "uploadImage": true, "url": "https://slack.com",
}) })
So(nt.SecureSettings, ShouldResemble, map[string]string{ require.Equal(t, nt.SecureSettings, map[string]string{
"token": "xoxbsecure", "url": "https://slack.com/secure", "token": "xoxbsecure", "url": "https://slack.com/secure",
}) })
So(nt.SendReminder, ShouldBeTrue) require.True(t, nt.SendReminder)
So(nt.Frequency, ShouldEqual, "1h") require.Equal(t, nt.Frequency, "1h")
nt = nts[1] nt = nts[1]
So(nt.Name, ShouldEqual, "another-not-default-notification") require.Equal(t, nt.Name, "another-not-default-notification")
So(nt.Type, ShouldEqual, "email") require.Equal(t, nt.Type, "email")
So(nt.OrgID, ShouldEqual, 3) require.Equal(t, nt.OrgID, int64(3))
So(nt.UID, ShouldEqual, "notifier2") require.Equal(t, nt.UID, "notifier2")
So(nt.IsDefault, ShouldBeFalse) require.False(t, nt.IsDefault)
nt = nts[2] nt = nts[2]
So(nt.Name, ShouldEqual, "check-unset-is_default-is-false") require.Equal(t, nt.Name, "check-unset-is_default-is-false")
So(nt.Type, ShouldEqual, "slack") require.Equal(t, nt.Type, "slack")
So(nt.OrgID, ShouldEqual, 3) require.Equal(t, nt.OrgID, int64(3))
So(nt.UID, ShouldEqual, "notifier3") require.Equal(t, nt.UID, "notifier3")
So(nt.IsDefault, ShouldBeFalse) require.False(t, nt.IsDefault)
nt = nts[3] nt = nts[3]
So(nt.Name, ShouldEqual, "Added notification with whitespaces in name") require.Equal(t, nt.Name, "Added notification with whitespaces in name")
So(nt.Type, ShouldEqual, "email") require.Equal(t, nt.Type, "email")
So(nt.UID, ShouldEqual, "notifier4") require.Equal(t, nt.UID, "notifier4")
So(nt.OrgID, ShouldEqual, 3) require.Equal(t, nt.OrgID, int64(3))
deleteNts := ntCfg.DeleteNotifications deleteNts := ntCfg.DeleteNotifications
So(len(deleteNts), ShouldEqual, 4) require.Equal(t, len(deleteNts), 4)
deleteNt := deleteNts[0] deleteNt := deleteNts[0]
So(deleteNt.Name, ShouldEqual, "default-slack-notification") require.Equal(t, deleteNt.Name, "default-slack-notification")
So(deleteNt.UID, ShouldEqual, "notifier1") require.Equal(t, deleteNt.UID, "notifier1")
So(deleteNt.OrgID, ShouldEqual, 2) require.Equal(t, deleteNt.OrgID, int64(2))
deleteNt = deleteNts[1] deleteNt = deleteNts[1]
So(deleteNt.Name, ShouldEqual, "deleted-notification-without-orgId") require.Equal(t, deleteNt.Name, "deleted-notification-without-orgId")
So(deleteNt.OrgID, ShouldEqual, 1) require.Equal(t, deleteNt.OrgID, int64(1))
So(deleteNt.UID, ShouldEqual, "notifier2") require.Equal(t, deleteNt.UID, "notifier2")
deleteNt = deleteNts[2] deleteNt = deleteNts[2]
So(deleteNt.Name, ShouldEqual, "deleted-notification-with-0-orgId") require.Equal(t, deleteNt.Name, "deleted-notification-with-0-orgId")
So(deleteNt.OrgID, ShouldEqual, 1) require.Equal(t, deleteNt.OrgID, int64(1))
So(deleteNt.UID, ShouldEqual, "notifier3") require.Equal(t, deleteNt.UID, "notifier3")
deleteNt = deleteNts[3] deleteNt = deleteNts[3]
So(deleteNt.Name, ShouldEqual, "Deleted notification with whitespaces in name") require.Equal(t, deleteNt.Name, "Deleted notification with whitespaces in name")
So(deleteNt.OrgID, ShouldEqual, 1) require.Equal(t, deleteNt.OrgID, int64(1))
So(deleteNt.UID, ShouldEqual, "notifier4") require.Equal(t, deleteNt.UID, "notifier4")
}) })
Convey("One configured notification", func() { t.Run("One configured notification", func(t *testing.T) {
Convey("no notification in database", func() { t.Run("no notification in database", func(t *testing.T) {
setup()
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger) dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(twoNotificationsConfig) err := dc.applyChanges(twoNotificationsConfig)
@ -140,12 +146,13 @@ func TestNotificationAsConfig(t *testing.T) {
} }
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1} notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlStore.GetAllAlertNotifications(&notificationsQuery) err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil) require.NoError(t, err)
So(notificationsQuery.Result, ShouldNotBeNil) require.NotNil(t, notificationsQuery.Result)
So(len(notificationsQuery.Result), ShouldEqual, 2) require.Equal(t, len(notificationsQuery.Result), 2)
}) })
Convey("One notification in database with same name and uid", func() { t.Run("One notification in database with same name and uid", func(t *testing.T) {
setup()
existingNotificationCmd := models.CreateAlertNotificationCommand{ existingNotificationCmd := models.CreateAlertNotificationCommand{
Name: "channel1", Name: "channel1",
OrgId: 1, OrgId: 1,
@ -153,56 +160,58 @@ func TestNotificationAsConfig(t *testing.T) {
Type: "slack", Type: "slack",
} }
err := sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd) err := sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd)
So(err, ShouldBeNil) require.NoError(t, err)
So(existingNotificationCmd.Result, ShouldNotBeNil) require.NotNil(t, existingNotificationCmd.Result)
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1} notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlStore.GetAllAlertNotifications(&notificationsQuery) err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil) require.NoError(t, err)
So(notificationsQuery.Result, ShouldNotBeNil) require.NotNil(t, notificationsQuery.Result)
So(len(notificationsQuery.Result), ShouldEqual, 1) require.Equal(t, len(notificationsQuery.Result), 1)
Convey("should update one notification", func() { t.Run("should update one notification", func(t *testing.T) {
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger) dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err = dc.applyChanges(twoNotificationsConfig) err = dc.applyChanges(twoNotificationsConfig)
if err != nil { if err != nil {
t.Fatalf("applyChanges return an error %v", err) t.Fatalf("applyChanges return an error %v", err)
} }
err = sqlStore.GetAllAlertNotifications(&notificationsQuery) err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil) require.NoError(t, err)
So(notificationsQuery.Result, ShouldNotBeNil) require.NotNil(t, notificationsQuery.Result)
So(len(notificationsQuery.Result), ShouldEqual, 2) require.Equal(t, len(notificationsQuery.Result), 2)
nts := notificationsQuery.Result nts := notificationsQuery.Result
nt1 := nts[0] nt1 := nts[0]
So(nt1.Type, ShouldEqual, "email") require.Equal(t, nt1.Type, "email")
So(nt1.Name, ShouldEqual, "channel1") require.Equal(t, nt1.Name, "channel1")
So(nt1.Uid, ShouldEqual, "notifier1") require.Equal(t, nt1.Uid, "notifier1")
nt2 := nts[1] nt2 := nts[1]
So(nt2.Type, ShouldEqual, "slack") require.Equal(t, nt2.Type, "slack")
So(nt2.Name, ShouldEqual, "channel2") require.Equal(t, nt2.Name, "channel2")
So(nt2.Uid, ShouldEqual, "notifier2") require.Equal(t, nt2.Uid, "notifier2")
}) })
}) })
Convey("Two notifications with is_default", func() { t.Run("Two notifications with is_default", func(t *testing.T) {
setup()
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger) dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(doubleNotificationsConfig) err := dc.applyChanges(doubleNotificationsConfig)
Convey("should both be inserted", func() { t.Run("should both be inserted", func(t *testing.T) {
So(err, ShouldBeNil) require.NoError(t, err)
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1} notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlStore.GetAllAlertNotifications(&notificationsQuery) err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil) require.NoError(t, err)
So(notificationsQuery.Result, ShouldNotBeNil) require.NotNil(t, notificationsQuery.Result)
So(len(notificationsQuery.Result), ShouldEqual, 2) require.Equal(t, len(notificationsQuery.Result), 2)
So(notificationsQuery.Result[0].IsDefault, ShouldBeTrue) require.True(t, notificationsQuery.Result[0].IsDefault)
So(notificationsQuery.Result[1].IsDefault, ShouldBeTrue) require.True(t, notificationsQuery.Result[1].IsDefault)
}) })
}) })
}) })
Convey("Two configured notification", func() { t.Run("Two configured notification", func(t *testing.T) {
Convey("two other notifications in database", func() { t.Run("two other notifications in database", func(t *testing.T) {
setup()
existingNotificationCmd := models.CreateAlertNotificationCommand{ existingNotificationCmd := models.CreateAlertNotificationCommand{
Name: "channel0", Name: "channel0",
OrgId: 1, OrgId: 1,
@ -210,7 +219,7 @@ func TestNotificationAsConfig(t *testing.T) {
Type: "slack", Type: "slack",
} }
err := sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd) err := sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd)
So(err, ShouldBeNil) require.NoError(t, err)
existingNotificationCmd = models.CreateAlertNotificationCommand{ existingNotificationCmd = models.CreateAlertNotificationCommand{
Name: "channel3", Name: "channel3",
OrgId: 1, OrgId: 1,
@ -218,15 +227,15 @@ func TestNotificationAsConfig(t *testing.T) {
Type: "slack", Type: "slack",
} }
err = sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd) err = sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd)
So(err, ShouldBeNil) require.NoError(t, err)
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1} notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlStore.GetAllAlertNotifications(&notificationsQuery) err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil) require.NoError(t, err)
So(notificationsQuery.Result, ShouldNotBeNil) require.NotNil(t, notificationsQuery.Result)
So(len(notificationsQuery.Result), ShouldEqual, 2) require.Equal(t, len(notificationsQuery.Result), 2)
Convey("should have two new notifications", func() { t.Run("should have two new notifications", func(t *testing.T) {
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger) dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(twoNotificationsConfig) err := dc.applyChanges(twoNotificationsConfig)
if err != nil { if err != nil {
@ -234,22 +243,23 @@ func TestNotificationAsConfig(t *testing.T) {
} }
notificationsQuery = models.GetAllAlertNotificationsQuery{OrgId: 1} notificationsQuery = models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlStore.GetAllAlertNotifications(&notificationsQuery) err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil) require.NoError(t, err)
So(notificationsQuery.Result, ShouldNotBeNil) require.NotNil(t, notificationsQuery.Result)
So(len(notificationsQuery.Result), ShouldEqual, 4) require.Equal(t, len(notificationsQuery.Result), 4)
}) })
}) })
}) })
Convey("Can read correct properties with orgName instead of orgId", func() { t.Run("Can read correct properties with orgName instead of orgId", func(t *testing.T) {
setup()
existingOrg1 := models.GetOrgByNameQuery{Name: "Main Org. 1"} existingOrg1 := models.GetOrgByNameQuery{Name: "Main Org. 1"}
err := sqlstore.GetOrgByName(&existingOrg1) err := sqlstore.GetOrgByName(&existingOrg1)
So(err, ShouldBeNil) require.NoError(t, err)
So(existingOrg1.Result, ShouldNotBeNil) require.NotNil(t, existingOrg1.Result)
existingOrg2 := models.GetOrgByNameQuery{Name: "Main Org. 2"} existingOrg2 := models.GetOrgByNameQuery{Name: "Main Org. 2"}
err = sqlstore.GetOrgByName(&existingOrg2) err = sqlstore.GetOrgByName(&existingOrg2)
So(err, ShouldBeNil) require.NoError(t, err)
So(existingOrg2.Result, ShouldNotBeNil) require.NotNil(t, existingOrg2.Result)
existingNotificationCmd := models.CreateAlertNotificationCommand{ existingNotificationCmd := models.CreateAlertNotificationCommand{
Name: "default-notification-delete", Name: "default-notification-delete",
@ -258,7 +268,7 @@ func TestNotificationAsConfig(t *testing.T) {
Type: "slack", Type: "slack",
} }
err = sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd) err = sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd)
So(err, ShouldBeNil) require.NoError(t, err)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger) dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err = dc.applyChanges(correctPropertiesWithOrgName) err = dc.applyChanges(correctPropertiesWithOrgName)
@ -268,29 +278,31 @@ func TestNotificationAsConfig(t *testing.T) {
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: existingOrg2.Result.Id} notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: existingOrg2.Result.Id}
err = sqlStore.GetAllAlertNotifications(&notificationsQuery) err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil) require.NoError(t, err)
So(notificationsQuery.Result, ShouldNotBeNil) require.NotNil(t, notificationsQuery.Result)
So(len(notificationsQuery.Result), ShouldEqual, 1) require.Equal(t, len(notificationsQuery.Result), 1)
nt := notificationsQuery.Result[0] nt := notificationsQuery.Result[0]
So(nt.Name, ShouldEqual, "default-notification-create") require.Equal(t, nt.Name, "default-notification-create")
So(nt.OrgId, ShouldEqual, existingOrg2.Result.Id) require.Equal(t, nt.OrgId, existingOrg2.Result.Id)
}) })
Convey("Config doesn't contain required field", func() { t.Run("Config doesn't contain required field", func(t *testing.T) {
setup()
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger) dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(noRequiredFields) err := dc.applyChanges(noRequiredFields)
So(err, ShouldNotBeNil) require.NotNil(t, err)
errString := err.Error() errString := err.Error()
So(errString, ShouldContainSubstring, "Deleted alert notification item 1 in configuration doesn't contain required field uid") require.Contains(t, errString, "Deleted alert notification item 1 in configuration doesn't contain required field uid")
So(errString, ShouldContainSubstring, "Deleted alert notification item 2 in configuration doesn't contain required field name") require.Contains(t, errString, "Deleted alert notification item 2 in configuration doesn't contain required field name")
So(errString, ShouldContainSubstring, "Added alert notification item 1 in configuration doesn't contain required field name") require.Contains(t, errString, "Added alert notification item 1 in configuration doesn't contain required field name")
So(errString, ShouldContainSubstring, "Added alert notification item 2 in configuration doesn't contain required field uid") require.Contains(t, errString, "Added alert notification item 2 in configuration doesn't contain required field uid")
}) })
Convey("Empty yaml file", func() { t.Run("Empty yaml file", func(t *testing.T) {
Convey("should have not changed repo", func() { t.Run("should have not changed repo", func(t *testing.T) {
setup()
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger) dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(emptyFile) err := dc.applyChanges(emptyFile)
if err != nil { if err != nil {
@ -298,22 +310,22 @@ func TestNotificationAsConfig(t *testing.T) {
} }
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1} notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlStore.GetAllAlertNotifications(&notificationsQuery) err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil) require.NoError(t, err)
So(notificationsQuery.Result, ShouldBeEmpty) require.Empty(t, notificationsQuery.Result)
}) })
}) })
Convey("Broken yaml should return error", func() { t.Run("Broken yaml should return error", func(t *testing.T) {
reader := &configReader{ reader := &configReader{
encryptionService: ossencryption.ProvideService(), encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"), log: log.New("test logger"),
} }
_, err := reader.readConfig(brokenYaml) _, err := reader.readConfig(brokenYaml)
So(err, ShouldNotBeNil) require.NotNil(t, err)
}) })
Convey("Skip invalid directory", func() { t.Run("Skip invalid directory", func(t *testing.T) {
cfgProvider := &configReader{ cfgProvider := &configReader{
encryptionService: ossencryption.ProvideService(), encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"), log: log.New("test logger"),
@ -323,27 +335,27 @@ func TestNotificationAsConfig(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("readConfig return an error %v", err) t.Fatalf("readConfig return an error %v", err)
} }
So(len(cfg), ShouldEqual, 0) require.Equal(t, len(cfg), 0)
}) })
Convey("Unknown notifier should return error", func() { t.Run("Unknown notifier should return error", func(t *testing.T) {
cfgProvider := &configReader{ cfgProvider := &configReader{
encryptionService: ossencryption.ProvideService(), encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"), log: log.New("test logger"),
} }
_, err := cfgProvider.readConfig(unknownNotifier) _, err := cfgProvider.readConfig(unknownNotifier)
So(err, ShouldNotBeNil) require.NotNil(t, err)
So(err.Error(), ShouldEqual, `unsupported notification type "nonexisting"`) require.Equal(t, err.Error(), `unsupported notification type "nonexisting"`)
}) })
Convey("Read incorrect properties", func() { t.Run("Read incorrect properties", func(t *testing.T) {
cfgProvider := &configReader{ cfgProvider := &configReader{
encryptionService: ossencryption.ProvideService(), encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"), log: log.New("test logger"),
} }
_, err := cfgProvider.readConfig(incorrectSettings) _, err := cfgProvider.readConfig(incorrectSettings)
So(err, ShouldNotBeNil) require.NotNil(t, err)
So(err.Error(), ShouldEqual, "alert validation error: token must be specified when using the Slack chat API") require.Equal(t, err.Error(), "alert validation error: token must be specified when using the Slack chat API")
}) })
}) })
} }

@ -5,25 +5,26 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/require"
) )
func TestCheckOrgExists(t *testing.T) { func TestCheckOrgExists(t *testing.T) {
Convey("with default org in database", t, func() { t.Run("with default org in database", func(t *testing.T) {
sqlstore.InitTestDB(t) sqlstore.InitTestDB(t)
defaultOrg := models.CreateOrgCommand{Name: "Main Org."} defaultOrg := models.CreateOrgCommand{Name: "Main Org."}
err := sqlstore.CreateOrg(&defaultOrg) err := sqlstore.CreateOrg(&defaultOrg)
So(err, ShouldBeNil) require.NoError(t, err)
Convey("default org exists", func() { t.Run("default org exists", func(t *testing.T) {
err := CheckOrgExists(defaultOrg.Result.Id) err := CheckOrgExists(defaultOrg.Result.Id)
So(err, ShouldBeNil) require.NoError(t, err)
}) })
Convey("other org doesn't exist", func() { t.Run("other org doesn't exist", func(t *testing.T) {
err := CheckOrgExists(defaultOrg.Result.Id + 1) err := CheckOrgExists(defaultOrg.Result.Id + 1)
So(err, ShouldEqual, models.ErrOrgNotFound) require.Equal(t, err, models.ErrOrgNotFound)
}) })
}) })
} }

@ -8,132 +8,140 @@ import (
"testing" "testing"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"gopkg.in/ini.v1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/ini.v1"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
func TestValues(t *testing.T) { func TestValues(t *testing.T) {
Convey("Values", t, func() { t.Run("Values", func(t *testing.T) {
err := os.Setenv("INT", "1") err := os.Setenv("INT", "1")
So(err, ShouldBeNil) require.NoError(t, err)
err = os.Setenv("STRING", "test") err = os.Setenv("STRING", "test")
So(err, ShouldBeNil) require.NoError(t, err)
err = os.Setenv("EMPTYSTRING", "") err = os.Setenv("EMPTYSTRING", "")
So(err, ShouldBeNil) require.NoError(t, err)
err = os.Setenv("BOOL", "true") err = os.Setenv("BOOL", "true")
So(err, ShouldBeNil) require.NoError(t, err)
defer func() {
err := os.Unsetenv("INT")
require.NoError(t, err)
err = os.Unsetenv("STRING")
require.NoError(t, err)
err = os.Unsetenv("EMPTYSTRING")
require.NoError(t, err)
err = os.Unsetenv("BOOL")
require.NoError(t, err)
}()
Convey("IntValue", func() { t.Run("IntValue", func(t *testing.T) {
type Data struct { type Data struct {
Val IntValue `yaml:"val"` Val IntValue `yaml:"val"`
} }
d := &Data{} d := &Data{}
Convey("Should unmarshal simple number", func() { t.Run("Should unmarshal simple number", func(t *testing.T) {
unmarshalingTest(`val: 1`, d) unmarshalingTest(t, `val: 1`, d)
So(d.Val.Value(), ShouldEqual, 1) require.Equal(t, d.Val.Value(), 1)
So(d.Val.Raw, ShouldEqual, "1") require.Equal(t, d.Val.Raw, "1")
}) })
Convey("Should unmarshal env var", func() { t.Run("Should unmarshal env var", func(t *testing.T) {
unmarshalingTest(`val: $INT`, d) unmarshalingTest(t, `val: $INT`, d)
So(d.Val.Value(), ShouldEqual, 1) require.Equal(t, d.Val.Value(), 1)
So(d.Val.Raw, ShouldEqual, "$INT") require.Equal(t, d.Val.Raw, "$INT")
}) })
Convey("Should ignore empty value", func() { t.Run("Should ignore empty value", func(t *testing.T) {
unmarshalingTest(`val: `, d) unmarshalingTest(t, `val: `, d)
So(d.Val.Value(), ShouldEqual, 0) require.Equal(t, d.Val.Value(), 0)
So(d.Val.Raw, ShouldEqual, "") require.Equal(t, d.Val.Raw, "")
}) })
}) })
Convey("StringValue", func() { t.Run("StringValue", func(t *testing.T) {
type Data struct { type Data struct {
Val StringValue `yaml:"val"` Val StringValue `yaml:"val"`
} }
d := &Data{} d := &Data{}
Convey("Should unmarshal simple string", func() { t.Run("Should unmarshal simple string", func(t *testing.T) {
unmarshalingTest(`val: test`, d) unmarshalingTest(t, `val: test`, d)
So(d.Val.Value(), ShouldEqual, "test") require.Equal(t, d.Val.Value(), "test")
So(d.Val.Raw, ShouldEqual, "test") require.Equal(t, d.Val.Raw, "test")
}) })
Convey("Should unmarshal env var", func() { t.Run("Should unmarshal env var", func(t *testing.T) {
unmarshalingTest(`val: $STRING`, d) unmarshalingTest(t, `val: $STRING`, d)
So(d.Val.Value(), ShouldEqual, "test") require.Equal(t, d.Val.Value(), "test")
So(d.Val.Raw, ShouldEqual, "$STRING") require.Equal(t, d.Val.Raw, "$STRING")
}) })
Convey("Should ignore empty value", func() { t.Run("Should ignore empty value", func(t *testing.T) {
unmarshalingTest(`val: `, d) unmarshalingTest(t, `val: `, d)
So(d.Val.Value(), ShouldEqual, "") require.Equal(t, d.Val.Value(), "")
So(d.Val.Raw, ShouldEqual, "") require.Equal(t, d.Val.Raw, "")
}) })
Convey("empty var should have empty value", func() { t.Run("empty var should have empty value", func(t *testing.T) {
unmarshalingTest(`val: $EMPTYSTRING`, d) unmarshalingTest(t, `val: $EMPTYSTRING`, d)
So(d.Val.Value(), ShouldEqual, "") require.Equal(t, d.Val.Value(), "")
So(d.Val.Raw, ShouldEqual, "$EMPTYSTRING") require.Equal(t, d.Val.Raw, "$EMPTYSTRING")
}) })
Convey("$$ should be a literal $", func() { t.Run("$$ should be a literal $", func(t *testing.T) {
unmarshalingTest(`val: $$`, d) unmarshalingTest(t, `val: $$`, d)
So(d.Val.Value(), ShouldEqual, "$") require.Equal(t, d.Val.Value(), "$")
So(d.Val.Raw, ShouldEqual, "$$") require.Equal(t, d.Val.Raw, "$$")
}) })
Convey("$$ should be a literal $ and not expanded within a string", func() { t.Run("$$ should be a literal $ and not expanded within a string", func(t *testing.T) {
unmarshalingTest(`val: mY,Passwo$$rd`, d) unmarshalingTest(t, `val: mY,Passwo$$rd`, d)
So(d.Val.Value(), ShouldEqual, "mY,Passwo$rd") require.Equal(t, d.Val.Value(), "mY,Passwo$rd")
So(d.Val.Raw, ShouldEqual, "mY,Passwo$$rd") require.Equal(t, d.Val.Raw, "mY,Passwo$$rd")
}) })
}) })
Convey("BoolValue", func() { t.Run("BoolValue", func(t *testing.T) {
type Data struct { type Data struct {
Val BoolValue `yaml:"val"` Val BoolValue `yaml:"val"`
} }
d := &Data{} d := &Data{}
Convey("Should unmarshal bool value", func() { t.Run("Should unmarshal bool value", func(t *testing.T) {
unmarshalingTest(`val: true`, d) unmarshalingTest(t, `val: true`, d)
So(d.Val.Value(), ShouldBeTrue) require.True(t, d.Val.Value())
So(d.Val.Raw, ShouldEqual, "true") require.Equal(t, d.Val.Raw, "true")
}) })
Convey("Should unmarshal explicit string", func() { t.Run("Should unmarshal explicit string", func(t *testing.T) {
unmarshalingTest(`val: "true"`, d) unmarshalingTest(t, `val: "true"`, d)
So(d.Val.Value(), ShouldBeTrue) require.True(t, d.Val.Value())
So(d.Val.Raw, ShouldEqual, "true") require.Equal(t, d.Val.Raw, "true")
}) })
Convey("Should unmarshal env var", func() { t.Run("Should unmarshal env var", func(t *testing.T) {
unmarshalingTest(`val: $BOOL`, d) unmarshalingTest(t, `val: $BOOL`, d)
So(d.Val.Value(), ShouldBeTrue) require.True(t, d.Val.Value())
So(d.Val.Raw, ShouldEqual, "$BOOL") require.Equal(t, d.Val.Raw, "$BOOL")
}) })
Convey("Should ignore empty value", func() { t.Run("Should ignore empty value", func(t *testing.T) {
unmarshalingTest(`val: `, d) unmarshalingTest(t, `val: `, d)
So(d.Val.Value(), ShouldBeFalse) require.False(t, d.Val.Value())
So(d.Val.Raw, ShouldEqual, "") require.Equal(t, d.Val.Raw, "")
}) })
}) })
Convey("JSONValue", func() { t.Run("JSONValue", func(t *testing.T) {
type Data struct { type Data struct {
Val JSONValue `yaml:"val"` Val JSONValue `yaml:"val"`
} }
d := &Data{} d := &Data{}
Convey("Should unmarshal variable nesting", func() { t.Run("Should unmarshal variable nesting", func(t *testing.T) {
doc := ` doc := `
val: val:
one: 1 one: 1
@ -153,10 +161,10 @@ func TestValues(t *testing.T) {
anchor: &label $INT anchor: &label $INT
anchored: *label anchored: *label
` `
unmarshalingTest(doc, d) unmarshalingTest(t, doc, d)
type stringMap = map[string]interface{} type stringMap = map[string]interface{}
So(d.Val.Value(), ShouldResemble, stringMap{ require.Equal(t, d.Val.Value(), stringMap{
"one": 1, "one": 1,
"two": "test", "two": "test",
"three": []interface{}{ "three": []interface{}{
@ -183,7 +191,7 @@ func TestValues(t *testing.T) {
"anchored": "1", "anchored": "1",
}) })
So(d.Val.Raw, ShouldResemble, stringMap{ require.Equal(t, d.Val.Raw, stringMap{
"one": 1, "one": 1,
"two": "$STRING", "two": "$STRING",
"three": []interface{}{ "three": []interface{}{
@ -212,13 +220,13 @@ func TestValues(t *testing.T) {
}) })
}) })
Convey("StringMapValue", func() { t.Run("StringMapValue", func(t *testing.T) {
type Data struct { type Data struct {
Val StringMapValue `yaml:"val"` Val StringMapValue `yaml:"val"`
} }
d := &Data{} d := &Data{}
Convey("Should unmarshal mapping", func() { t.Run("Should unmarshal mapping", func(t *testing.T) {
doc := ` doc := `
val: val:
one: 1 one: 1
@ -226,15 +234,15 @@ func TestValues(t *testing.T) {
three: $STRING three: $STRING
four: true four: true
` `
unmarshalingTest(doc, d) unmarshalingTest(t, doc, d)
So(d.Val.Value(), ShouldResemble, map[string]string{ require.Equal(t, d.Val.Value(), map[string]string{
"one": "1", "one": "1",
"two": "test string", "two": "test string",
"three": "test", "three": "test",
"four": "true", "four": "true",
}) })
So(d.Val.Raw, ShouldResemble, map[string]string{ require.Equal(t, d.Val.Raw, map[string]string{
"one": "1", "one": "1",
"two": "test string", "two": "test string",
"three": "$STRING", "three": "$STRING",
@ -242,23 +250,12 @@ func TestValues(t *testing.T) {
}) })
}) })
}) })
Reset(func() {
err := os.Unsetenv("INT")
So(err, ShouldBeNil)
err = os.Unsetenv("STRING")
So(err, ShouldBeNil)
err = os.Unsetenv("EMPTYSTRING")
So(err, ShouldBeNil)
err = os.Unsetenv("BOOL")
So(err, ShouldBeNil)
})
}) })
} }
func unmarshalingTest(document string, out interface{}) { func unmarshalingTest(t *testing.T, document string, out interface{}) {
err := yaml.Unmarshal([]byte(document), out) err := yaml.Unmarshal([]byte(document), out)
So(err, ShouldBeNil) require.NoError(t, err)
} }
func TestValues_readFile(t *testing.T) { func TestValues_readFile(t *testing.T) {

Loading…
Cancel
Save