Unified Storage: Testing For Fix Create, Update and Delete wrt Resource Versions (#88568)
Add testing harness
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
const DefaultContextTimeout = time.Second
|
||||
|
||||
// TestContext is a context.Context that can be canceled with or without a
|
||||
// cause. This is only relevant for testing purposes.
|
||||
type TestContext interface {
|
||||
context.Context
|
||||
|
||||
// Cancel cancels the context. The `Err` method and the `context.Cause`
|
||||
// function will return context.Canceled.
|
||||
Cancel()
|
||||
|
||||
// CancelCause cancels the current context with the given cause. The `Err`
|
||||
// method will return context.Canceled and the `context.Cause` function will
|
||||
// return the given error.
|
||||
CancelCause(err error)
|
||||
|
||||
// WithUser returns a derived user with the given user associated. To derive
|
||||
// a context without an associated user, pass a nil value.
|
||||
WithUser(*user.SignedInUser) TestContext
|
||||
}
|
||||
|
||||
// NewDefaultTestContext calls NewTestContext with the provided `t` and a
|
||||
// timeout of DefaultContextTimeout. This should work fine for most unit tests.
|
||||
func NewDefaultTestContext(t T) TestContext {
|
||||
return NewTestContext(t, time.Now().Add(DefaultContextTimeout))
|
||||
}
|
||||
|
||||
// NewTestCtx returns a new TestContext with the following features:
|
||||
// 1. Provides a `deadline` argument which is especially useful in integration
|
||||
// tests.
|
||||
// 2. It honours the `-timeout` flag of `go test` if it is given, so it will
|
||||
// actually timeout at the earliest deadline (either the one resulting from
|
||||
// the command line or the one resulting from the `deadline` argument).
|
||||
// 3. By default it has an empty user (i.e. the user at the UI login), so most
|
||||
// of the code paths to be tested will not need any special setup, unless
|
||||
// you need to test permissions. In that case, you can use any of the test
|
||||
// users from the SignedInUser struct (all of them come from real payloads).
|
||||
func NewTestContext(t T, deadline time.Time) TestContext {
|
||||
t.Helper()
|
||||
|
||||
// if the test has a deadline and it happens before our previously
|
||||
// calculated deadline, then use it instead
|
||||
if td, ok := t.Deadline(); ok && td.Before(deadline) {
|
||||
deadline = td
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
ctx, cancelCause := context.WithCancelCause(ctx)
|
||||
|
||||
tctx := testContextFunc(func() (context.Context, context.CancelFunc, context.CancelCauseFunc) {
|
||||
return ctx, cancel, cancelCause
|
||||
})
|
||||
|
||||
user, err := SignedInUser{}.NewEmpty()
|
||||
require.NoError(t, err)
|
||||
|
||||
// TODO: improve by adding a better anonymous user struct
|
||||
return tctx.WithUser(user)
|
||||
}
|
||||
|
||||
type testContextFunc func() (context.Context, context.CancelFunc, context.CancelCauseFunc)
|
||||
|
||||
func (f testContextFunc) Deadline() (deadline time.Time, ok bool) {
|
||||
ctx, _, _ := f()
|
||||
return ctx.Deadline()
|
||||
}
|
||||
|
||||
func (f testContextFunc) Done() <-chan struct{} {
|
||||
ctx, _, _ := f()
|
||||
return ctx.Done()
|
||||
}
|
||||
|
||||
func (f testContextFunc) Err() error {
|
||||
ctx, _, _ := f()
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
func (f testContextFunc) Value(key any) any {
|
||||
ctx, _, _ := f()
|
||||
return ctx.Value(key)
|
||||
}
|
||||
|
||||
func (f testContextFunc) Cancel() {
|
||||
_, c, _ := f()
|
||||
c()
|
||||
}
|
||||
|
||||
func (f testContextFunc) CancelCause(err error) {
|
||||
_, _, cc := f()
|
||||
cc(err)
|
||||
}
|
||||
|
||||
func (f testContextFunc) WithUser(usr *user.SignedInUser) TestContext {
|
||||
ctx := appcontext.WithUser(f, usr)
|
||||
|
||||
return testContextFunc(func() (context.Context, context.CancelFunc, context.CancelCauseFunc) {
|
||||
return ctx, f.Cancel, f.CancelCause
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util/testutil/mocks"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// make sure we don't leak goroutines after tests in this package have
|
||||
// finished, which means we haven't leaked contexts either
|
||||
goleak.VerifyTestMain(m)
|
||||
}
|
||||
|
||||
func TestTestContextFunc(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const tolerance = 100 * time.Millisecond
|
||||
|
||||
t.Run("no explicit deadline - no test deadline", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := mocks.NewT(t)
|
||||
tt.EXPECT().Helper()
|
||||
tt.EXPECT().Deadline().Return(time.Time{}, false).Once()
|
||||
tt.EXPECT().Cleanup(mock.Anything).Once()
|
||||
|
||||
ctx := NewDefaultTestContext(tt)
|
||||
d, ok := ctx.Deadline()
|
||||
require.True(t, ok)
|
||||
require.False(t, d.IsZero())
|
||||
diff := time.Now().Add(DefaultContextTimeout).Sub(d)
|
||||
require.GreaterOrEqual(t, diff, time.Duration(0))
|
||||
require.Less(t, diff, tolerance)
|
||||
|
||||
ctx.Cancel()
|
||||
require.ErrorIs(t, ctx.Err(), context.Canceled)
|
||||
|
||||
// already canceled, we shouldn't be able to set a cause now
|
||||
ctx.CancelCause(context.DeadlineExceeded)
|
||||
require.ErrorIs(t, context.Cause(ctx), context.Canceled)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
t.Fatalf("done channel not closed")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("explicit deadline - earlier test deadline", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// make sure the context will be deadlined already at creation
|
||||
now := time.Now().Add(-time.Second)
|
||||
|
||||
tt := mocks.NewT(t)
|
||||
tt.EXPECT().Helper()
|
||||
tt.EXPECT().Deadline().Return(now, true).Once()
|
||||
tt.EXPECT().Cleanup(mock.Anything).Once()
|
||||
|
||||
ctx := NewTestContext(tt, now.Add(time.Second))
|
||||
d, ok := ctx.Deadline()
|
||||
require.True(t, ok)
|
||||
require.Equal(t, now, d)
|
||||
require.ErrorIs(t, ctx.Err(), context.DeadlineExceeded)
|
||||
})
|
||||
|
||||
t.Run("explicit deadline - later test deadline", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now().Add(-time.Second)
|
||||
|
||||
tt := mocks.NewT(t)
|
||||
tt.EXPECT().Helper()
|
||||
tt.EXPECT().Deadline().Return(now.Add(time.Hour), true).Once()
|
||||
tt.EXPECT().Cleanup(mock.Anything).Once()
|
||||
|
||||
ctx := NewTestContext(tt, now)
|
||||
d, ok := ctx.Deadline()
|
||||
require.True(t, ok)
|
||||
require.Equal(t, now, d)
|
||||
require.ErrorIs(t, ctx.Err(), context.DeadlineExceeded)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"UserID": 0,
|
||||
"UserUID": "",
|
||||
"OrgID": 1,
|
||||
"OrgName": "Main Org.",
|
||||
"OrgRole": "Viewer",
|
||||
"Login": "",
|
||||
"Name": "",
|
||||
"Email": "",
|
||||
"EmailVerified": false,
|
||||
"AuthID": "",
|
||||
"AuthenticatedBy": "",
|
||||
"ApiKeyID": 0,
|
||||
"IsServiceAccount": false,
|
||||
"IsGrafanaAdmin": false,
|
||||
"IsAnonymous": true,
|
||||
"IsDisabled": false,
|
||||
"HelpFlags1": 0,
|
||||
"LastSeenAt": "0001-01-01T00:00:00Z",
|
||||
"Teams": null,
|
||||
"NamespacedID": {}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"UserID": 2,
|
||||
"UserUID": "ednity0wr3d34d",
|
||||
"OrgID": 1,
|
||||
"OrgName": "Main Org.",
|
||||
"OrgRole": "Editor",
|
||||
"Login": "editor",
|
||||
"Name": "editor",
|
||||
"Email": "editor",
|
||||
"EmailVerified": false,
|
||||
"AuthID": "",
|
||||
"AuthenticatedBy": "",
|
||||
"ApiKeyID": 0,
|
||||
"IsServiceAccount": false,
|
||||
"IsGrafanaAdmin": false,
|
||||
"IsAnonymous": false,
|
||||
"IsDisabled": false,
|
||||
"HelpFlags1": 0,
|
||||
"LastSeenAt": "2024-06-01T23:03:26-03:00",
|
||||
"Teams": [],
|
||||
"NamespacedID": {}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"UserID": 0,
|
||||
"UserUID": "",
|
||||
"OrgID": 0,
|
||||
"OrgName": "",
|
||||
"OrgRole": "",
|
||||
"Login": "",
|
||||
"Name": "",
|
||||
"Email": "",
|
||||
"EmailVerified": false,
|
||||
"AuthID": "",
|
||||
"AuthenticatedBy": "",
|
||||
"ApiKeyID": 0,
|
||||
"IsServiceAccount": false,
|
||||
"IsGrafanaAdmin": false,
|
||||
"IsAnonymous": false,
|
||||
"IsDisabled": false,
|
||||
"HelpFlags1": 0,
|
||||
"LastSeenAt": "0001-01-01T00:00:00Z",
|
||||
"Teams": null,
|
||||
"NamespacedID": {}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"UserID": 1,
|
||||
"UserUID": "",
|
||||
"OrgID": 1,
|
||||
"OrgName": "Main Org.",
|
||||
"OrgRole": "Admin",
|
||||
"Login": "admin",
|
||||
"Name": "",
|
||||
"Email": "admin@localhost",
|
||||
"EmailVerified": false,
|
||||
"AuthID": "",
|
||||
"AuthenticatedBy": "",
|
||||
"ApiKeyID": 0,
|
||||
"IsServiceAccount": false,
|
||||
"IsGrafanaAdmin": true,
|
||||
"IsAnonymous": false,
|
||||
"IsDisabled": false,
|
||||
"HelpFlags1": 0,
|
||||
"LastSeenAt": "2024-06-01T23:01:11-03:00",
|
||||
"Teams": [],
|
||||
"NamespacedID": {}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"UserID": 4,
|
||||
"UserUID": "fdnivb2e7c4cga",
|
||||
"OrgID": 1,
|
||||
"OrgName": "Main Org.",
|
||||
"OrgRole": "Viewer",
|
||||
"Login": "sa-1-something",
|
||||
"Name": "something",
|
||||
"Email": "sa-1-something",
|
||||
"EmailVerified": false,
|
||||
"AuthID": "",
|
||||
"AuthenticatedBy": "apikey",
|
||||
"ApiKeyID": 0,
|
||||
"IsServiceAccount": true,
|
||||
"IsGrafanaAdmin": false,
|
||||
"IsAnonymous": false,
|
||||
"IsDisabled": false,
|
||||
"HelpFlags1": 0,
|
||||
"LastSeenAt": "2024-06-01T23:20:42-03:00",
|
||||
"Teams": [],
|
||||
"NamespacedID": {}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"UserID": 3,
|
||||
"UserUID": "fdniuj44r0zr4d",
|
||||
"OrgID": 1,
|
||||
"OrgName": "Main Org.",
|
||||
"OrgRole": "Viewer",
|
||||
"Login": "viewer",
|
||||
"Name": "viewer",
|
||||
"Email": "viewer",
|
||||
"EmailVerified": false,
|
||||
"AuthID": "",
|
||||
"AuthenticatedBy": "",
|
||||
"ApiKeyID": 0,
|
||||
"IsServiceAccount": false,
|
||||
"IsGrafanaAdmin": false,
|
||||
"IsAnonymous": false,
|
||||
"IsDisabled": false,
|
||||
"HelpFlags1": 0,
|
||||
"LastSeenAt": "2024-06-01T23:09:25-03:00",
|
||||
"Teams": [],
|
||||
"NamespacedID": {}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// Code generated by mockery v2.43.1. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
time "time"
|
||||
)
|
||||
|
||||
// T is an autogenerated mock type for the T type
|
||||
type T struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type T_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *T) EXPECT() *T_Expecter {
|
||||
return &T_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Cleanup provides a mock function with given fields: _a0
|
||||
func (_m *T) Cleanup(_a0 func()) {
|
||||
_m.Called(_a0)
|
||||
}
|
||||
|
||||
// T_Cleanup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Cleanup'
|
||||
type T_Cleanup_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Cleanup is a helper method to define mock.On call
|
||||
// - _a0 func()
|
||||
func (_e *T_Expecter) Cleanup(_a0 interface{}) *T_Cleanup_Call {
|
||||
return &T_Cleanup_Call{Call: _e.mock.On("Cleanup", _a0)}
|
||||
}
|
||||
|
||||
func (_c *T_Cleanup_Call) Run(run func(_a0 func())) *T_Cleanup_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(func()))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *T_Cleanup_Call) Return() *T_Cleanup_Call {
|
||||
_c.Call.Return()
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *T_Cleanup_Call) RunAndReturn(run func(func())) *T_Cleanup_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Deadline provides a mock function with given fields:
|
||||
func (_m *T) Deadline() (time.Time, bool) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Deadline")
|
||||
}
|
||||
|
||||
var r0 time.Time
|
||||
var r1 bool
|
||||
if rf, ok := ret.Get(0).(func() (time.Time, bool)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() time.Time); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(time.Time)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() bool); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Get(1).(bool)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// T_Deadline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Deadline'
|
||||
type T_Deadline_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Deadline is a helper method to define mock.On call
|
||||
func (_e *T_Expecter) Deadline() *T_Deadline_Call {
|
||||
return &T_Deadline_Call{Call: _e.mock.On("Deadline")}
|
||||
}
|
||||
|
||||
func (_c *T_Deadline_Call) Run(run func()) *T_Deadline_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *T_Deadline_Call) Return(_a0 time.Time, _a1 bool) *T_Deadline_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *T_Deadline_Call) RunAndReturn(run func() (time.Time, bool)) *T_Deadline_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Errorf provides a mock function with given fields: format, args
|
||||
func (_m *T) Errorf(format string, args ...interface{}) {
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, format)
|
||||
_ca = append(_ca, args...)
|
||||
_m.Called(_ca...)
|
||||
}
|
||||
|
||||
// T_Errorf_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Errorf'
|
||||
type T_Errorf_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Errorf is a helper method to define mock.On call
|
||||
// - format string
|
||||
// - args ...interface{}
|
||||
func (_e *T_Expecter) Errorf(format interface{}, args ...interface{}) *T_Errorf_Call {
|
||||
return &T_Errorf_Call{Call: _e.mock.On("Errorf",
|
||||
append([]interface{}{format}, args...)...)}
|
||||
}
|
||||
|
||||
func (_c *T_Errorf_Call) Run(run func(format string, args ...interface{})) *T_Errorf_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := make([]interface{}, len(args)-1)
|
||||
for i, a := range args[1:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(interface{})
|
||||
}
|
||||
}
|
||||
run(args[0].(string), variadicArgs...)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *T_Errorf_Call) Return() *T_Errorf_Call {
|
||||
_c.Call.Return()
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *T_Errorf_Call) RunAndReturn(run func(string, ...interface{})) *T_Errorf_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// FailNow provides a mock function with given fields:
|
||||
func (_m *T) FailNow() {
|
||||
_m.Called()
|
||||
}
|
||||
|
||||
// T_FailNow_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FailNow'
|
||||
type T_FailNow_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// FailNow is a helper method to define mock.On call
|
||||
func (_e *T_Expecter) FailNow() *T_FailNow_Call {
|
||||
return &T_FailNow_Call{Call: _e.mock.On("FailNow")}
|
||||
}
|
||||
|
||||
func (_c *T_FailNow_Call) Run(run func()) *T_FailNow_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *T_FailNow_Call) Return() *T_FailNow_Call {
|
||||
_c.Call.Return()
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *T_FailNow_Call) RunAndReturn(run func()) *T_FailNow_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Helper provides a mock function with given fields:
|
||||
func (_m *T) Helper() {
|
||||
_m.Called()
|
||||
}
|
||||
|
||||
// T_Helper_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Helper'
|
||||
type T_Helper_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Helper is a helper method to define mock.On call
|
||||
func (_e *T_Expecter) Helper() *T_Helper_Call {
|
||||
return &T_Helper_Call{Call: _e.mock.On("Helper")}
|
||||
}
|
||||
|
||||
func (_c *T_Helper_Call) Run(run func()) *T_Helper_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *T_Helper_Call) Return() *T_Helper_Call {
|
||||
_c.Call.Return()
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *T_Helper_Call) RunAndReturn(run func()) *T_Helper_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewT creates a new instance of T. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewT(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *T {
|
||||
mock := &T{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed data/*
|
||||
var dataFS embed.FS
|
||||
|
||||
//go:generate mockery --with-expecter --name T
|
||||
|
||||
// T provides a clean way to test the utilities of this package.
|
||||
type T interface {
|
||||
Helper()
|
||||
Cleanup(func())
|
||||
Deadline() (time.Time, bool)
|
||||
Errorf(format string, args ...any)
|
||||
FailNow()
|
||||
}
|
||||
|
||||
func init() {
|
||||
// At the moment of this writing, there is already testing code imported in
|
||||
// server runtime code. Please, consider refactoring your code to keep
|
||||
// runtime dependencies clean.
|
||||
if !testing.Testing() {
|
||||
panic("importing testing libraries in runtime code is not allowed")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
type SignedInUser struct{}
|
||||
|
||||
func (SignedInUser) NewAnonymous() (*user.SignedInUser, error) {
|
||||
return readUser(`user-anonymous.json`)
|
||||
}
|
||||
|
||||
func (SignedInUser) NewEditor() (*user.SignedInUser, error) {
|
||||
return readUser(`user-editor.json`)
|
||||
}
|
||||
|
||||
func (SignedInUser) NewGrafanaAdmin() (*user.SignedInUser, error) {
|
||||
return readUser(`user-grafana-admin.json`)
|
||||
}
|
||||
|
||||
func (SignedInUser) NewEmpty() (*user.SignedInUser, error) {
|
||||
return readUser(`user-empty.json`)
|
||||
}
|
||||
|
||||
func (SignedInUser) NewServiceAccount() (*user.SignedInUser, error) {
|
||||
return readUser(`user-service-account-viewer.json`)
|
||||
}
|
||||
|
||||
func (SignedInUser) NewViewer() (*user.SignedInUser, error) {
|
||||
return readUser(`user-viewer.json`)
|
||||
}
|
||||
|
||||
func readUser(filename string) (*user.SignedInUser, error) {
|
||||
file, err := dataFS.Open(`data/` + filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := new(user.SignedInUser)
|
||||
|
||||
return ret, json.NewDecoder(file).Decode(ret)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
func TestSignedInUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := func(user *user.SignedInUser, err error) {
|
||||
require.NotNil(t, user)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
r(SignedInUser{}.NewAnonymous())
|
||||
r(SignedInUser{}.NewEditor())
|
||||
r(SignedInUser{}.NewGrafanaAdmin())
|
||||
r(SignedInUser{}.NewEmpty())
|
||||
r(SignedInUser{}.NewServiceAccount())
|
||||
r(SignedInUser{}.NewViewer())
|
||||
|
||||
user, err := readUser(`non existent!!!`)
|
||||
require.Nil(t, user)
|
||||
require.Error(t, err)
|
||||
}
|
||||
Reference in New Issue
Block a user