mirror of https://github.com/grafana/grafana
Unified Storage: Testing For Fix Create, Update and Delete wrt Resource Versions (#88568)
Add testing harnesspull/88809/head
parent
6fcd7d9e03
commit
5fc580b401
@ -1,49 +1,92 @@ |
|||||||
package dbimpl |
package dbimpl |
||||||
|
|
||||||
import ( |
import ( |
||||||
"strings" |
|
||||||
"testing" |
"testing" |
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/setting" |
|
||||||
"github.com/stretchr/testify/assert" |
"github.com/stretchr/testify/assert" |
||||||
"github.com/stretchr/testify/require" |
|
||||||
) |
) |
||||||
|
|
||||||
func TestGetEnginePostgresFromConfig(t *testing.T) { |
func TestGetEngineMySQLFromConfig(t *testing.T) { |
||||||
cfg := setting.NewCfg() |
t.Parallel() |
||||||
s, err := cfg.Raw.NewSection("entity_api") |
|
||||||
require.NoError(t, err) |
t.Run("happy path", func(t *testing.T) { |
||||||
s.Key("db_type").SetValue("mysql") |
t.Parallel() |
||||||
s.Key("db_host").SetValue("localhost") |
|
||||||
s.Key("db_name").SetValue("grafana") |
getter := newTestSectionGetter(map[string]string{ |
||||||
s.Key("db_user").SetValue("user") |
"db_type": "mysql", |
||||||
s.Key("db_password").SetValue("password") |
"db_host": "/var/run/mysql.socket", |
||||||
|
"db_name": "grafana", |
||||||
getter := §ionGetter{ |
"db_user": "user", |
||||||
DynamicSection: cfg.SectionWithEnvOverrides("entity_api"), |
"db_password": "password", |
||||||
} |
}) |
||||||
engine, err := getEnginePostgres(getter, nil) |
engine, err := getEngineMySQL(getter, nil) |
||||||
|
assert.NotNil(t, engine) |
||||||
assert.NotNil(t, engine) |
assert.NoError(t, err) |
||||||
assert.NoError(t, err) |
}) |
||||||
assert.True(t, strings.Contains(engine.DataSourceName(), "dbname=grafana")) |
|
||||||
|
t.Run("invalid string", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
getter := newTestSectionGetter(map[string]string{ |
||||||
|
"db_type": "mysql", |
||||||
|
"db_host": "/var/run/mysql.socket", |
||||||
|
"db_name": string(invalidUTF8ByteSequence), |
||||||
|
"db_user": "user", |
||||||
|
"db_password": "password", |
||||||
|
}) |
||||||
|
engine, err := getEngineMySQL(getter, nil) |
||||||
|
assert.Nil(t, engine) |
||||||
|
assert.Error(t, err) |
||||||
|
assert.ErrorIs(t, err, ErrInvalidUTF8Sequence) |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
func TestGetEngineMySQLFromConfig(t *testing.T) { |
func TestGetEnginePostgresFromConfig(t *testing.T) { |
||||||
cfg := setting.NewCfg() |
t.Parallel() |
||||||
s, err := cfg.Raw.NewSection("entity_api") |
|
||||||
require.NoError(t, err) |
t.Run("happy path", func(t *testing.T) { |
||||||
s.Key("db_type").SetValue("mysql") |
t.Parallel() |
||||||
s.Key("db_host").SetValue("localhost") |
getter := newTestSectionGetter(map[string]string{ |
||||||
s.Key("db_name").SetValue("grafana") |
"db_type": "mysql", |
||||||
s.Key("db_user").SetValue("user") |
"db_host": "localhost", |
||||||
s.Key("db_password").SetValue("password") |
"db_name": "grafana", |
||||||
|
"db_user": "user", |
||||||
getter := §ionGetter{ |
"db_password": "password", |
||||||
DynamicSection: cfg.SectionWithEnvOverrides("entity_api"), |
}) |
||||||
} |
engine, err := getEnginePostgres(getter, nil) |
||||||
engine, err := getEngineMySQL(getter, nil) |
|
||||||
|
assert.NotNil(t, engine) |
||||||
assert.NotNil(t, engine) |
assert.NoError(t, err) |
||||||
assert.NoError(t, err) |
}) |
||||||
|
|
||||||
|
t.Run("invalid string", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
getter := newTestSectionGetter(map[string]string{ |
||||||
|
"db_type": "mysql", |
||||||
|
"db_host": string(invalidUTF8ByteSequence), |
||||||
|
"db_name": "grafana", |
||||||
|
"db_user": "user", |
||||||
|
"db_password": "password", |
||||||
|
}) |
||||||
|
engine, err := getEnginePostgres(getter, nil) |
||||||
|
|
||||||
|
assert.Nil(t, engine) |
||||||
|
assert.Error(t, err) |
||||||
|
assert.ErrorIs(t, err, ErrInvalidUTF8Sequence) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("invalid hostport", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
getter := newTestSectionGetter(map[string]string{ |
||||||
|
"db_type": "mysql", |
||||||
|
"db_host": "1:1:1", |
||||||
|
"db_name": "grafana", |
||||||
|
"db_user": "user", |
||||||
|
"db_password": "password", |
||||||
|
}) |
||||||
|
engine, err := getEnginePostgres(getter, nil) |
||||||
|
|
||||||
|
assert.Nil(t, engine) |
||||||
|
assert.Error(t, err) |
||||||
|
}) |
||||||
} |
} |
||||||
|
@ -0,0 +1,108 @@ |
|||||||
|
package dbimpl |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/setting" |
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
) |
||||||
|
|
||||||
|
var invalidUTF8ByteSequence = []byte{0xff, 0xfe, 0xfd} |
||||||
|
|
||||||
|
func setSectionKeyValues(section *setting.DynamicSection, m map[string]string) { |
||||||
|
for k, v := range m { |
||||||
|
section.Key(k).SetValue(v) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func newTestSectionGetter(m map[string]string) *sectionGetter { |
||||||
|
section := setting.NewCfg().SectionWithEnvOverrides("entity_api") |
||||||
|
setSectionKeyValues(section, m) |
||||||
|
|
||||||
|
return §ionGetter{ |
||||||
|
DynamicSection: section, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestSectionGetter(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
var ( |
||||||
|
key = "the key" |
||||||
|
val = string(invalidUTF8ByteSequence) |
||||||
|
) |
||||||
|
|
||||||
|
g := newTestSectionGetter(map[string]string{ |
||||||
|
key: val, |
||||||
|
}) |
||||||
|
|
||||||
|
v := g.String("whatever") |
||||||
|
require.Empty(t, v) |
||||||
|
require.NoError(t, g.Err()) |
||||||
|
|
||||||
|
v = g.String(key) |
||||||
|
require.Empty(t, v) |
||||||
|
require.Error(t, g.Err()) |
||||||
|
require.ErrorIs(t, g.Err(), ErrInvalidUTF8Sequence) |
||||||
|
} |
||||||
|
|
||||||
|
func TestMakeDSN(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
s, err := MakeDSN(map[string]string{ |
||||||
|
"db_name": string(invalidUTF8ByteSequence), |
||||||
|
}) |
||||||
|
require.Empty(t, s) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorIs(t, err, ErrInvalidUTF8Sequence) |
||||||
|
|
||||||
|
s, err = MakeDSN(map[string]string{ |
||||||
|
"skip": "", |
||||||
|
"user": `shou'ld esc\ape`, |
||||||
|
"pass": "noescape", |
||||||
|
}) |
||||||
|
require.NoError(t, err) |
||||||
|
require.Equal(t, `pass=noescape user='shou\'ld esc\\ape'`, s) |
||||||
|
} |
||||||
|
|
||||||
|
func TestSplitHostPort(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
testCases := []struct { |
||||||
|
hostport string |
||||||
|
defaultHost string |
||||||
|
defaultPort string |
||||||
|
fails bool |
||||||
|
|
||||||
|
host string |
||||||
|
port string |
||||||
|
}{ |
||||||
|
{hostport: "192.168.0.140:456", defaultHost: "", defaultPort: "", host: "192.168.0.140", port: "456"}, |
||||||
|
{hostport: "192.168.0.140", defaultHost: "", defaultPort: "123", host: "192.168.0.140", port: "123"}, |
||||||
|
{hostport: "[::1]:456", defaultHost: "", defaultPort: "", host: "::1", port: "456"}, |
||||||
|
{hostport: "[::1]", defaultHost: "", defaultPort: "123", host: "::1", port: "123"}, |
||||||
|
{hostport: ":456", defaultHost: "1.2.3.4", defaultPort: "", host: "1.2.3.4", port: "456"}, |
||||||
|
{hostport: "xyz.rds.amazonaws.com", defaultHost: "", defaultPort: "123", host: "xyz.rds.amazonaws.com", port: "123"}, |
||||||
|
{hostport: "xyz.rds.amazonaws.com:123", defaultHost: "", defaultPort: "", host: "xyz.rds.amazonaws.com", port: "123"}, |
||||||
|
{hostport: "", defaultHost: "localhost", defaultPort: "1433", host: "localhost", port: "1433"}, |
||||||
|
{hostport: "1:1:1", fails: true}, |
||||||
|
} |
||||||
|
|
||||||
|
for i, tc := range testCases { |
||||||
|
t.Run(fmt.Sprintf("test index #%d", i), func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
host, port, err := splitHostPortDefault(tc.hostport, tc.defaultHost, tc.defaultPort) |
||||||
|
if tc.fails { |
||||||
|
require.Error(t, err) |
||||||
|
require.Empty(t, host) |
||||||
|
require.Empty(t, port) |
||||||
|
} else { |
||||||
|
require.NoError(t, err) |
||||||
|
require.Equal(t, tc.host, host) |
||||||
|
require.Equal(t, tc.port, port) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -1,30 +0,0 @@ |
|||||||
SELECT {{ template "common_entity_select_into" . }} |
|
||||||
|
|
||||||
FROM {{ .Ident "entity_history" }} AS e |
|
||||||
|
|
||||||
WHERE 1 = 1 |
|
||||||
|
|
||||||
{{ if gt .Before 0 }} |
|
||||||
AND {{ .Ident "resource_version" }} < {{ .Arg .Before }} |
|
||||||
{{ end }} |
|
||||||
|
|
||||||
{{/* There are two mutually exclusive search modes: by GUID and by Key */}} |
|
||||||
|
|
||||||
{{ if ne .Query.GUID "" }} |
|
||||||
AND {{ .Ident "guid" }} = {{ .Arg .Query.GUID }} |
|
||||||
|
|
||||||
{{ else }} |
|
||||||
AND {{ .Ident "group" }} = {{ .Arg .Query.Key.Group }} |
|
||||||
AND {{ .Ident "resource" }} = {{ .Arg .Query.Key.Resource }} |
|
||||||
AND {{ .Ident "name" }} = {{ .Arg .Query.Key.Name }} |
|
||||||
|
|
||||||
{{ if ne .Query.Key.Namespace "" }} |
|
||||||
AND {{ .Ident "namespace" }} = {{ .Arg .Query.Key.Namespace }} |
|
||||||
{{ end }} |
|
||||||
|
|
||||||
{{ end }} |
|
||||||
|
|
||||||
ORDER BY {{ template "common_order_by" . }} |
|
||||||
LIMIT {{ .Limit }} |
|
||||||
OFFSET {{ .Offset }} |
|
||||||
; |
|
@ -1,14 +0,0 @@ |
|||||||
SELECT {{ template "common_entity_select_into" . }} |
|
||||||
|
|
||||||
FROM |
|
||||||
{{ .Ident "entity_ref" }} AS r |
|
||||||
INNER JOIN |
|
||||||
{{ .Ident "entity" }} AS e |
|
||||||
ON r.{{ .Ident "guid" }} = e.{{ .Ident "guid" }} |
|
||||||
|
|
||||||
WHERE 1 = 1 |
|
||||||
AND r.{{ .Ident "namespace" }} = {{ .Arg .Request.Namespace }} |
|
||||||
AND r.{{ .Ident "group" }} = {{ .Arg .Request.Group }} |
|
||||||
AND r.{{ .Ident "resource" }} = {{ .Arg .Request.Resource }} |
|
||||||
AND r.{{ .Ident "resolved_to" }} = {{ .Arg .Request.Name }} |
|
||||||
; |
|
@ -0,0 +1,821 @@ |
|||||||
|
package sqlstash |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"embed" |
||||||
|
"encoding/json" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
"text/template" |
||||||
|
|
||||||
|
sqlmock "github.com/DATA-DOG/go-sqlmock" |
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/store/entity" |
||||||
|
"github.com/grafana/grafana/pkg/services/store/entity/db" |
||||||
|
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate" |
||||||
|
"github.com/grafana/grafana/pkg/util/testutil" |
||||||
|
) |
||||||
|
|
||||||
|
// debug is meant to provide greater debugging detail about certain errors. The
|
||||||
|
// returned error will either provide more detailed information or be the same
|
||||||
|
// original error, suitable only for local debugging. The details provided are
|
||||||
|
// not meant to be logged, since they could include PII or otherwise
|
||||||
|
// sensitive/confidential information. These information should only be used for
|
||||||
|
// local debugging with fake or otherwise non-regulated information.
|
||||||
|
func debug(err error) error { |
||||||
|
var d interface{ Debug() string } |
||||||
|
if errors.As(err, &d) { |
||||||
|
return errors.New(d.Debug()) |
||||||
|
} |
||||||
|
|
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
var _ = debug // silence the `unused` linter
|
||||||
|
|
||||||
|
//go:embed testdata/*
|
||||||
|
var testdataFS embed.FS |
||||||
|
|
||||||
|
func testdata(t *testing.T, filename string) []byte { |
||||||
|
t.Helper() |
||||||
|
b, err := testdataFS.ReadFile(`testdata/` + filename) |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
return b |
||||||
|
} |
||||||
|
|
||||||
|
func testdataJSON(t *testing.T, filename string, dest any) { |
||||||
|
t.Helper() |
||||||
|
b := testdata(t, filename) |
||||||
|
err := json.Unmarshal(b, dest) |
||||||
|
require.NoError(t, err) |
||||||
|
} |
||||||
|
|
||||||
|
func TestQueries(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// Each template has one or more test cases, each identified with a
|
||||||
|
// descriptive name (e.g. "happy path", "error twiddling the frobb"). Each
|
||||||
|
// of them will test that for the same input data they must produce a result
|
||||||
|
// that will depend on the Dialect. Expected queries should be defined in
|
||||||
|
// separate files in the testdata directory. This improves the testing
|
||||||
|
// experience by separating test data from test code, since mixing both
|
||||||
|
// tends to make it more difficult to reason about what is being done,
|
||||||
|
// especially as we want testing code to scale and make it easy to add
|
||||||
|
// tests.
|
||||||
|
type ( |
||||||
|
// type aliases to make code more semantic and self-documenting
|
||||||
|
resultSQLFilename = string |
||||||
|
dialects = []sqltemplate.Dialect |
||||||
|
expected map[resultSQLFilename]dialects |
||||||
|
|
||||||
|
testCase = struct { |
||||||
|
Name string |
||||||
|
|
||||||
|
// Data should be the struct passed to the template.
|
||||||
|
Data sqltemplate.SQLTemplateIface |
||||||
|
|
||||||
|
// Expected maps the filename containing the expected result query
|
||||||
|
// to the list of dialects that would produce it. For simple
|
||||||
|
// queries, it is possible that more than one dialect produce the
|
||||||
|
// same output. The filename is expected to be in the `testdata`
|
||||||
|
// directory.
|
||||||
|
Expected expected |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
// Define tests cases. Most templates are trivial and testing that they
|
||||||
|
// generate correct code for a single Dialect is fine, since the one thing
|
||||||
|
// that always changes is how SQL placeholder arguments are passed (most
|
||||||
|
// Dialects use `?` while PostgreSQL uses `$1`, `$2`, etc.), and that is
|
||||||
|
// something that should be tested in the Dialect implementation instead of
|
||||||
|
// here. We will ask to have at least one test per SQL template, and we will
|
||||||
|
// lean to test MySQL. Templates containing branching (conditionals, loops,
|
||||||
|
// etc.) should be exercised at least once in each of their branches.
|
||||||
|
//
|
||||||
|
// NOTE: in the Data field, make sure to have pointers populated to simulate
|
||||||
|
// data is set as it would be in a real request. The data being correctly
|
||||||
|
// populated in each case should be tested in integration tests, where the
|
||||||
|
// data will actually flow to and from a real database. In this tests we
|
||||||
|
// only care about producing the correct SQL.
|
||||||
|
testCases := map[*template.Template][]*testCase{ |
||||||
|
sqlEntityDelete: { |
||||||
|
{ |
||||||
|
Name: "single path", |
||||||
|
Data: &sqlEntityDeleteRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
Key: new(entity.Key), |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_delete_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
"entity_delete_postgres.sql": dialects{ |
||||||
|
sqltemplate.PostgreSQL, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
sqlEntityInsert: { |
||||||
|
{ |
||||||
|
Name: "insert into entity", |
||||||
|
Data: &sqlEntityInsertRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
Entity: newReturnsEntity(), |
||||||
|
TableEntity: true, |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_insert_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
Name: "insert into entity_history", |
||||||
|
Data: &sqlEntityInsertRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
Entity: newReturnsEntity(), |
||||||
|
TableEntity: false, |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_history_insert_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
sqlEntityListFolderElements: { |
||||||
|
{ |
||||||
|
Name: "single path", |
||||||
|
Data: &sqlEntityListFolderElementsRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
FolderInfo: new(folderInfo), |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_list_folder_elements_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
sqlEntityRead: { |
||||||
|
{ |
||||||
|
Name: "with resource version and select for update", |
||||||
|
Data: &sqlEntityReadRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
Key: new(entity.Key), |
||||||
|
ResourceVersion: 1, |
||||||
|
SelectForUpdate: true, |
||||||
|
returnsEntitySet: returnsEntitySet{ |
||||||
|
Entity: newReturnsEntity(), |
||||||
|
}, |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_history_read_full_mysql.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
Name: "without resource version and select for update", |
||||||
|
Data: &sqlEntityReadRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
Key: new(entity.Key), |
||||||
|
returnsEntitySet: returnsEntitySet{ |
||||||
|
Entity: newReturnsEntity(), |
||||||
|
}, |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_read_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
sqlEntityUpdate: { |
||||||
|
{ |
||||||
|
Name: "single path", |
||||||
|
Data: &sqlEntityUpdateRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
Entity: newReturnsEntity(), |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_update_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
sqlEntityFolderInsert: { |
||||||
|
{ |
||||||
|
Name: "one item", |
||||||
|
Data: &sqlEntityFolderInsertRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
Items: []*sqlEntityFolderInsertRequestItem{{}}, |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_folder_insert_1_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
Name: "two items", |
||||||
|
Data: &sqlEntityFolderInsertRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
Items: []*sqlEntityFolderInsertRequestItem{{}, {}}, |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_folder_insert_2_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
sqlEntityLabelsDelete: { |
||||||
|
{ |
||||||
|
Name: "one element", |
||||||
|
Data: &sqlEntityLabelsDeleteRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
KeepLabels: []string{"one"}, |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_labels_delete_1_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
Name: "two elements", |
||||||
|
Data: &sqlEntityLabelsDeleteRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
KeepLabels: []string{"one", "two"}, |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_labels_delete_2_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
sqlEntityLabelsInsert: { |
||||||
|
{ |
||||||
|
Name: "one element", |
||||||
|
Data: &sqlEntityLabelsInsertRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
Labels: map[string]string{"lbl1": "val1"}, |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_labels_insert_1_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
Name: "two elements", |
||||||
|
Data: &sqlEntityLabelsInsertRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
Labels: map[string]string{"lbl1": "val1", "lbl2": "val2"}, |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"entity_labels_insert_2_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
sqlKindVersionGet: { |
||||||
|
{ |
||||||
|
Name: "single path", |
||||||
|
Data: &sqlKindVersionGetRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
returnsKindVersion: new(returnsKindVersion), |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"kind_version_get_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
sqlKindVersionInc: { |
||||||
|
{ |
||||||
|
Name: "single path", |
||||||
|
Data: &sqlKindVersionIncRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"kind_version_inc_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
sqlKindVersionInsert: { |
||||||
|
{ |
||||||
|
Name: "single path", |
||||||
|
Data: &sqlKindVersionInsertRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"kind_version_insert_mysql_sqlite.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
sqlKindVersionLock: { |
||||||
|
{ |
||||||
|
Name: "single path", |
||||||
|
Data: &sqlKindVersionLockRequest{ |
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate), |
||||||
|
returnsKindVersion: new(returnsKindVersion), |
||||||
|
}, |
||||||
|
Expected: expected{ |
||||||
|
"kind_version_lock_mysql.sql": dialects{ |
||||||
|
sqltemplate.MySQL, |
||||||
|
}, |
||||||
|
"kind_version_lock_postgres.sql": dialects{ |
||||||
|
sqltemplate.PostgreSQL, |
||||||
|
}, |
||||||
|
"kind_version_lock_sqlite.sql": dialects{ |
||||||
|
sqltemplate.SQLite, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
// Execute test cases
|
||||||
|
for tmpl, tcs := range testCases { |
||||||
|
t.Run(tmpl.Name(), func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
for _, tc := range tcs { |
||||||
|
t.Run(tc.Name, func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
for filename, ds := range tc.Expected { |
||||||
|
t.Run(filename, func(t *testing.T) { |
||||||
|
// not parallel because we're sharing tc.Data, not
|
||||||
|
// worth it deep cloning
|
||||||
|
|
||||||
|
rawQuery := string(testdata(t, filename)) |
||||||
|
expectedQuery := sqltemplate.FormatSQL(rawQuery) |
||||||
|
|
||||||
|
for _, d := range ds { |
||||||
|
t.Run(d.Name(), func(t *testing.T) { |
||||||
|
// not parallel for the same reason
|
||||||
|
|
||||||
|
tc.Data.SetDialect(d) |
||||||
|
err := tc.Data.Validate() |
||||||
|
require.NoError(t, err) |
||||||
|
got, err := sqltemplate.Execute(tmpl, tc.Data) |
||||||
|
require.NoError(t, err) |
||||||
|
got = sqltemplate.FormatSQL(got) |
||||||
|
require.Equal(t, expectedQuery, got) |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestReturnsEntity_marshal(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test data for maps
|
||||||
|
someMap := map[string]string{ |
||||||
|
"alpha": "aleph", |
||||||
|
"beta": "beth", |
||||||
|
} |
||||||
|
someMapJSONb, err := json.Marshal(someMap) |
||||||
|
require.NoError(t, err) |
||||||
|
someMapJSON := string(someMapJSONb) |
||||||
|
|
||||||
|
// test data for errors
|
||||||
|
someErrors := []*entity.EntityErrorInfo{ |
||||||
|
{ |
||||||
|
Code: 1, |
||||||
|
Message: "not cool", |
||||||
|
DetailsJson: []byte(`"nothing to add"`), |
||||||
|
}, |
||||||
|
} |
||||||
|
someErrorsJSONb, err := json.Marshal(someErrors) |
||||||
|
require.NoError(t, err) |
||||||
|
someErrorsJSON := string(someErrorsJSONb) |
||||||
|
|
||||||
|
t.Run("happy path - nothing to marshal", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
d := &returnsEntity{ |
||||||
|
Entity: &entity.Entity{ |
||||||
|
Labels: map[string]string{}, |
||||||
|
Fields: map[string]string{}, |
||||||
|
Errors: []*entity.EntityErrorInfo{}, |
||||||
|
}, |
||||||
|
} |
||||||
|
err := d.marshal() |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
require.JSONEq(t, `{}`, string(d.Labels)) |
||||||
|
require.JSONEq(t, `{}`, string(d.Fields)) |
||||||
|
require.JSONEq(t, `[]`, string(d.Errors)) |
||||||
|
|
||||||
|
// nil Go Object/Slice map to empty JSON Object/Array for consistency
|
||||||
|
|
||||||
|
d.Entity = new(entity.Entity) |
||||||
|
err = d.marshal() |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
require.JSONEq(t, `{}`, string(d.Labels)) |
||||||
|
require.JSONEq(t, `{}`, string(d.Fields)) |
||||||
|
require.JSONEq(t, `[]`, string(d.Errors)) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("happy path - everything to marshal", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
d := &returnsEntity{ |
||||||
|
Entity: &entity.Entity{ |
||||||
|
Labels: someMap, |
||||||
|
Fields: someMap, |
||||||
|
Errors: someErrors, |
||||||
|
}, |
||||||
|
} |
||||||
|
err := d.marshal() |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
require.JSONEq(t, someMapJSON, string(d.Labels)) |
||||||
|
require.JSONEq(t, someMapJSON, string(d.Fields)) |
||||||
|
require.JSONEq(t, someErrorsJSON, string(d.Errors)) |
||||||
|
}) |
||||||
|
|
||||||
|
// NOTE: the error path for serialization is apparently unreachable. If you
|
||||||
|
// find a way to simulate a serialization error, consider raising awareness
|
||||||
|
// of such case(s) and add the corresponding tests here
|
||||||
|
} |
||||||
|
|
||||||
|
func TestReturnsEntity_unmarshal(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
t.Run("happy path - nothing to unmarshal", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
e := newReturnsEntity() |
||||||
|
err := e.unmarshal() |
||||||
|
require.NoError(t, err) |
||||||
|
require.NotNil(t, e.Entity.Labels) |
||||||
|
require.NotNil(t, e.Entity.Fields) |
||||||
|
require.NotNil(t, e.Entity.Errors) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("happy path - everything to unmarshal", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
e := newReturnsEntity() |
||||||
|
e.Labels = []byte(`{}`) |
||||||
|
e.Fields = []byte(`{}`) |
||||||
|
e.Errors = []byte(`[]`) |
||||||
|
err := e.unmarshal() |
||||||
|
require.NoError(t, err) |
||||||
|
require.NotNil(t, e.Entity.Labels) |
||||||
|
require.NotNil(t, e.Entity.Fields) |
||||||
|
require.NotNil(t, e.Entity.Errors) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("fail to unmarshal", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
var jsonInvalid = []byte(`.`) |
||||||
|
|
||||||
|
e := newReturnsEntity() |
||||||
|
e.Labels = jsonInvalid |
||||||
|
err := e.unmarshal() |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorContains(t, err, "labels") |
||||||
|
|
||||||
|
e = newReturnsEntity() |
||||||
|
e.Labels = nil |
||||||
|
e.Fields = jsonInvalid |
||||||
|
err = e.unmarshal() |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorContains(t, err, "fields") |
||||||
|
|
||||||
|
e = newReturnsEntity() |
||||||
|
e.Fields = nil |
||||||
|
e.Errors = jsonInvalid |
||||||
|
err = e.unmarshal() |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorContains(t, err, "errors") |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestReadEntity(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// readonly, shared data for all subtests
|
||||||
|
expectedEntity := newEmptyEntity() |
||||||
|
testdataJSON(t, `grpc-res-entity.json`, expectedEntity) |
||||||
|
key, err := entity.ParseKey(expectedEntity.Key) |
||||||
|
require.NoErrorf(t, err, "provided key: %#v", expectedEntity) |
||||||
|
|
||||||
|
t.Run("happy path - entity table, optimistic locking", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
db, mock := newMockDBMatchWords(t) |
||||||
|
x := expectReadEntity(t, mock, cloneEntity(expectedEntity)) |
||||||
|
x(ctx, db) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("happy path - entity table, no optimistic locking", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
db, mock := newMockDBMatchWords(t) |
||||||
|
readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||||
|
SQLTemplate: sqltemplate.New(sqltemplate.MySQL), |
||||||
|
Key: new(entity.Key), |
||||||
|
returnsEntitySet: newReturnsEntitySet(), |
||||||
|
} |
||||||
|
readReq.Entity.Entity = cloneEntity(expectedEntity) |
||||||
|
results := newMockResults(t, mock, sqlEntityRead, readReq) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
results.AddCurrentData() |
||||||
|
mock.ExpectQuery(`select from entity where !resource_version update`). |
||||||
|
WillReturnRows(results.Rows()) |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
e, err := readEntity(ctx, db, sqltemplate.MySQL, key, 0, false, true) |
||||||
|
require.NoError(t, err) |
||||||
|
require.Equal(t, expectedEntity, e.Entity) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("happy path - entity_history table", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
db, mock := newMockDBMatchWords(t) |
||||||
|
readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||||
|
SQLTemplate: sqltemplate.New(sqltemplate.MySQL), |
||||||
|
Key: new(entity.Key), |
||||||
|
returnsEntitySet: newReturnsEntitySet(), |
||||||
|
} |
||||||
|
readReq.Entity.Entity = cloneEntity(expectedEntity) |
||||||
|
results := newMockResults(t, mock, sqlEntityRead, readReq) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
results.AddCurrentData() |
||||||
|
mock.ExpectQuery(`select from entity_history where resource_version !update`). |
||||||
|
WillReturnRows(results.Rows()) |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
e, err := readEntity(ctx, db, sqltemplate.MySQL, key, |
||||||
|
expectedEntity.ResourceVersion, false, false) |
||||||
|
require.NoError(t, err) |
||||||
|
require.Equal(t, expectedEntity, e.Entity) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("entity table, optimistic locking failed", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
db, mock := newMockDBMatchWords(t) |
||||||
|
x := expectReadEntity(t, mock, nil) |
||||||
|
x(ctx, db) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("entity_history table, entity not found", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
db, mock := newMockDBMatchWords(t) |
||||||
|
readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||||
|
SQLTemplate: sqltemplate.New(sqltemplate.MySQL), |
||||||
|
Key: new(entity.Key), |
||||||
|
returnsEntitySet: newReturnsEntitySet(), |
||||||
|
} |
||||||
|
results := newMockResults(t, mock, sqlEntityRead, readReq) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
mock.ExpectQuery(`select from entity_history where resource_version !update`). |
||||||
|
WillReturnRows(results.Rows()) |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
e, err := readEntity(ctx, db, sqltemplate.MySQL, key, |
||||||
|
expectedEntity.ResourceVersion, false, false) |
||||||
|
require.Nil(t, e) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorIs(t, err, ErrNotFound) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("entity_history table, entity was deleted = not found", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
db, mock := newMockDBMatchWords(t) |
||||||
|
readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||||
|
SQLTemplate: sqltemplate.New(sqltemplate.MySQL), |
||||||
|
Key: new(entity.Key), |
||||||
|
returnsEntitySet: newReturnsEntitySet(), |
||||||
|
} |
||||||
|
readReq.Entity.Entity = cloneEntity(expectedEntity) |
||||||
|
readReq.Entity.Entity.Action = entity.Entity_DELETED |
||||||
|
results := newMockResults(t, mock, sqlEntityRead, readReq) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
results.AddCurrentData() |
||||||
|
mock.ExpectQuery(`select from entity_history where resource_version !update`). |
||||||
|
WillReturnRows(results.Rows()) |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
e, err := readEntity(ctx, db, sqltemplate.MySQL, key, |
||||||
|
expectedEntity.ResourceVersion, false, false) |
||||||
|
require.Nil(t, e) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorIs(t, err, ErrNotFound) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// expectReadEntity arranges test expectations so that it's easier to reuse
|
||||||
|
// across tests that need to call `readEntity`. If you provide a non-nil
|
||||||
|
// *entity.Entity, that will be returned by `readEntity`. If it's nil, then
|
||||||
|
// `readEntity` will return ErrOptimisticLockingFailed. It returns the function
|
||||||
|
// to execute the actual test and assert the expectations that were set.
|
||||||
|
func expectReadEntity(t *testing.T, mock sqlmock.Sqlmock, e *entity.Entity) func(ctx context.Context, db db.DB) { |
||||||
|
t.Helper() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
readReq := sqlEntityReadRequest{ // used to generate mock results
|
||||||
|
SQLTemplate: sqltemplate.New(sqltemplate.MySQL), |
||||||
|
Key: new(entity.Key), |
||||||
|
returnsEntitySet: newReturnsEntitySet(), |
||||||
|
} |
||||||
|
results := newMockResults(t, mock, sqlEntityRead, readReq) |
||||||
|
if e != nil { |
||||||
|
readReq.Entity.Entity = cloneEntity(e) |
||||||
|
} |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
results.AddCurrentData() |
||||||
|
mock.ExpectQuery(`select from entity where !resource_version update`). |
||||||
|
WillReturnRows(results.Rows()) |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
if e != nil { |
||||||
|
return func(ctx context.Context, db db.DB) { |
||||||
|
ent, err := readEntity(ctx, db, sqltemplate.MySQL, readReq.Key, |
||||||
|
e.ResourceVersion, true, true) |
||||||
|
require.NoError(t, err) |
||||||
|
require.Equal(t, e, ent.Entity) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return func(ctx context.Context, db db.DB) { |
||||||
|
ent, err := readEntity(ctx, db, sqltemplate.MySQL, readReq.Key, 1, true, |
||||||
|
true) |
||||||
|
require.Nil(t, ent) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorIs(t, err, ErrOptimisticLockingFailed) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestKindVersionAtomicInc(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
t.Run("happy path - row locked", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
const curVersion int64 = 1 |
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
db, mock := newMockDBMatchWords(t) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
mock.ExpectQuery(`select resource_version from kind_version where group resource update`). |
||||||
|
WillReturnRows(mock.NewRows([]string{"resource_version"}).AddRow(curVersion)) |
||||||
|
mock.ExpectExec("update kind_version set resource_version updated_at where group resource"). |
||||||
|
WillReturnResult(sqlmock.NewResult(0, 1)) |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
gotVersion, err := kindVersionAtomicInc(ctx, db, sqltemplate.MySQL, "groupname", "resname") |
||||||
|
require.NoError(t, err) |
||||||
|
require.Equal(t, curVersion+1, gotVersion) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("happy path - row created", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
db, mock := newMockDBMatchWords(t) |
||||||
|
x := expectKindVersionAtomicInc(t, mock, false) |
||||||
|
x(ctx, db) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("fail to create row", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
db, mock := newMockDBMatchWords(t) |
||||||
|
x := expectKindVersionAtomicInc(t, mock, true) |
||||||
|
x(ctx, db) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// expectKindVersionAtomicInc arranges test expectations so that it's easier to
|
||||||
|
// reuse across tests that need to call `kindVersionAtomicInc`. If you the test
|
||||||
|
// shuld fail, it will do so with `errTest`, and it will return resource version
|
||||||
|
// 1 otherwise. It returns the function to execute the actual test and assert
|
||||||
|
// the expectations that were set.
|
||||||
|
func expectKindVersionAtomicInc(t *testing.T, mock sqlmock.Sqlmock, shouldFail bool) func(ctx context.Context, db db.DB) { |
||||||
|
t.Helper() |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
mock.ExpectQuery(`select resource_version from kind_version where group resource update`). |
||||||
|
WillReturnRows(mock.NewRows([]string{"resource_version"})) |
||||||
|
call := mock.ExpectExec("insert kind_version resource_version") |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
if shouldFail { |
||||||
|
call.WillReturnError(errTest) |
||||||
|
|
||||||
|
return func(ctx context.Context, db db.DB) { |
||||||
|
gotVersion, err := kindVersionAtomicInc(ctx, db, sqltemplate.MySQL, "groupname", "resname") |
||||||
|
require.Zero(t, gotVersion) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorIs(t, err, errTest) |
||||||
|
} |
||||||
|
} |
||||||
|
call.WillReturnResult(sqlmock.NewResult(0, 1)) |
||||||
|
|
||||||
|
return func(ctx context.Context, db db.DB) { |
||||||
|
gotVersion, err := kindVersionAtomicInc(ctx, db, sqltemplate.MySQL, "groupname", "resname") |
||||||
|
require.NoError(t, err) |
||||||
|
require.Equal(t, int64(1), gotVersion) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestMustTemplate(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
require.Panics(t, func() { |
||||||
|
mustTemplate("non existent file") |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// Debug provides greater detail about the SQL error. It is defined on the same
|
||||||
|
// struct but on a test file so that the intention that its results should not
|
||||||
|
// be used in runtime code is very clear. The results could include PII or
|
||||||
|
// otherwise regulated information, hence this method is only available in
|
||||||
|
// tests, so that it can be used in local debugging only. Note that the error
|
||||||
|
// information may still be available through other means, like using the
|
||||||
|
// "reflect" package, so care must be taken not to ever expose these information
|
||||||
|
// in production.
|
||||||
|
func (e SQLError) Debug() string { |
||||||
|
scanDestStr := "(none)" |
||||||
|
if len(e.ScanDest) > 0 { |
||||||
|
format := "[%T" + strings.Repeat(", %T", len(e.ScanDest)-1) + "]" |
||||||
|
scanDestStr = fmt.Sprintf(format, e.ScanDest...) |
||||||
|
} |
||||||
|
|
||||||
|
return fmt.Sprintf("%s: %s: %v\n\tArguments (%d): %#v\n\tReturn Value "+ |
||||||
|
"Types (%d): %s\n\tExecuted Query: %s\n\tRaw SQL Template Output: %s", |
||||||
|
e.TemplateName, e.CallType, e.Err, len(e.arguments), e.arguments, |
||||||
|
len(e.ScanDest), scanDestStr, e.Query, e.RawQuery) |
||||||
|
} |
@ -1,19 +1,21 @@ |
|||||||
package sqltemplate |
package sqltemplate |
||||||
|
|
||||||
// MySQL is an implementation of Dialect for the MySQL DMBS. It relies on having
|
// MySQL is the default implementation of Dialect for the MySQL DMBS, currently
|
||||||
// ANSI_QUOTES SQL Mode enabled. For more information about ANSI_QUOTES and SQL
|
// supporting MySQL-8.x. It relies on having ANSI_QUOTES SQL Mode enabled. For
|
||||||
// Modes see:
|
// more information about ANSI_QUOTES and SQL Modes see:
|
||||||
//
|
//
|
||||||
// https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_ansi_quotes
|
// https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_ansi_quotes
|
||||||
var MySQL = mysql{ |
var MySQL = mysql{ |
||||||
rowLockingClauseAll: true, |
rowLockingClauseMap: rowLockingClauseAll, |
||||||
argPlaceholderFunc: argFmtSQL92, |
argPlaceholderFunc: argFmtSQL92, |
||||||
|
name: "mysql", |
||||||
} |
} |
||||||
|
|
||||||
var _ Dialect = MySQL |
var _ Dialect = MySQL |
||||||
|
|
||||||
type mysql struct { |
type mysql struct { |
||||||
standardIdent |
standardIdent |
||||||
rowLockingClauseAll |
rowLockingClauseMap |
||||||
argPlaceholderFunc |
argPlaceholderFunc |
||||||
|
name |
||||||
} |
} |
||||||
|
@ -0,0 +1,664 @@ |
|||||||
|
// Code generated by mockery v2.43.1. DO NOT EDIT.
|
||||||
|
|
||||||
|
package mocks |
||||||
|
|
||||||
|
import ( |
||||||
|
reflect "reflect" |
||||||
|
|
||||||
|
mock "github.com/stretchr/testify/mock" |
||||||
|
|
||||||
|
sqltemplate "github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate" |
||||||
|
) |
||||||
|
|
||||||
|
// SQLTemplateIface is an autogenerated mock type for the SQLTemplateIface type
|
||||||
|
type SQLTemplateIface struct { |
||||||
|
mock.Mock |
||||||
|
} |
||||||
|
|
||||||
|
type SQLTemplateIface_Expecter struct { |
||||||
|
mock *mock.Mock |
||||||
|
} |
||||||
|
|
||||||
|
func (_m *SQLTemplateIface) EXPECT() *SQLTemplateIface_Expecter { |
||||||
|
return &SQLTemplateIface_Expecter{mock: &_m.Mock} |
||||||
|
} |
||||||
|
|
||||||
|
// Arg provides a mock function with given fields: x
|
||||||
|
func (_m *SQLTemplateIface) Arg(x interface{}) string { |
||||||
|
ret := _m.Called(x) |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for Arg") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
if rf, ok := ret.Get(0).(func(interface{}) string); ok { |
||||||
|
r0 = rf(x) |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_Arg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Arg'
|
||||||
|
type SQLTemplateIface_Arg_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Arg is a helper method to define mock.On call
|
||||||
|
// - x interface{}
|
||||||
|
func (_e *SQLTemplateIface_Expecter) Arg(x interface{}) *SQLTemplateIface_Arg_Call { |
||||||
|
return &SQLTemplateIface_Arg_Call{Call: _e.mock.On("Arg", x)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Arg_Call) Run(run func(x interface{})) *SQLTemplateIface_Arg_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run(args[0].(interface{})) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Arg_Call) Return(_a0 string) *SQLTemplateIface_Arg_Call { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Arg_Call) RunAndReturn(run func(interface{}) string) *SQLTemplateIface_Arg_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// ArgList provides a mock function with given fields: slice
|
||||||
|
func (_m *SQLTemplateIface) ArgList(slice reflect.Value) (string, error) { |
||||||
|
ret := _m.Called(slice) |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for ArgList") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
var r1 error |
||||||
|
if rf, ok := ret.Get(0).(func(reflect.Value) (string, error)); ok { |
||||||
|
return rf(slice) |
||||||
|
} |
||||||
|
if rf, ok := ret.Get(0).(func(reflect.Value) string); ok { |
||||||
|
r0 = rf(slice) |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(reflect.Value) error); ok { |
||||||
|
r1 = rf(slice) |
||||||
|
} else { |
||||||
|
r1 = ret.Error(1) |
||||||
|
} |
||||||
|
|
||||||
|
return r0, r1 |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_ArgList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ArgList'
|
||||||
|
type SQLTemplateIface_ArgList_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// ArgList is a helper method to define mock.On call
|
||||||
|
// - slice reflect.Value
|
||||||
|
func (_e *SQLTemplateIface_Expecter) ArgList(slice interface{}) *SQLTemplateIface_ArgList_Call { |
||||||
|
return &SQLTemplateIface_ArgList_Call{Call: _e.mock.On("ArgList", slice)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_ArgList_Call) Run(run func(slice reflect.Value)) *SQLTemplateIface_ArgList_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run(args[0].(reflect.Value)) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_ArgList_Call) Return(_a0 string, _a1 error) *SQLTemplateIface_ArgList_Call { |
||||||
|
_c.Call.Return(_a0, _a1) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_ArgList_Call) RunAndReturn(run func(reflect.Value) (string, error)) *SQLTemplateIface_ArgList_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// ArgPlaceholder provides a mock function with given fields: argNum
|
||||||
|
func (_m *SQLTemplateIface) ArgPlaceholder(argNum int) string { |
||||||
|
ret := _m.Called(argNum) |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for ArgPlaceholder") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
if rf, ok := ret.Get(0).(func(int) string); ok { |
||||||
|
r0 = rf(argNum) |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_ArgPlaceholder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ArgPlaceholder'
|
||||||
|
type SQLTemplateIface_ArgPlaceholder_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// ArgPlaceholder is a helper method to define mock.On call
|
||||||
|
// - argNum int
|
||||||
|
func (_e *SQLTemplateIface_Expecter) ArgPlaceholder(argNum interface{}) *SQLTemplateIface_ArgPlaceholder_Call { |
||||||
|
return &SQLTemplateIface_ArgPlaceholder_Call{Call: _e.mock.On("ArgPlaceholder", argNum)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_ArgPlaceholder_Call) Run(run func(argNum int)) *SQLTemplateIface_ArgPlaceholder_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run(args[0].(int)) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_ArgPlaceholder_Call) Return(_a0 string) *SQLTemplateIface_ArgPlaceholder_Call { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_ArgPlaceholder_Call) RunAndReturn(run func(int) string) *SQLTemplateIface_ArgPlaceholder_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// GetArgs provides a mock function with given fields:
|
||||||
|
func (_m *SQLTemplateIface) GetArgs() []interface{} { |
||||||
|
ret := _m.Called() |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for GetArgs") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 []interface{} |
||||||
|
if rf, ok := ret.Get(0).(func() []interface{}); ok { |
||||||
|
r0 = rf() |
||||||
|
} else { |
||||||
|
if ret.Get(0) != nil { |
||||||
|
r0 = ret.Get(0).([]interface{}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_GetArgs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetArgs'
|
||||||
|
type SQLTemplateIface_GetArgs_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// GetArgs is a helper method to define mock.On call
|
||||||
|
func (_e *SQLTemplateIface_Expecter) GetArgs() *SQLTemplateIface_GetArgs_Call { |
||||||
|
return &SQLTemplateIface_GetArgs_Call{Call: _e.mock.On("GetArgs")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_GetArgs_Call) Run(run func()) *SQLTemplateIface_GetArgs_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_GetArgs_Call) Return(_a0 []interface{}) *SQLTemplateIface_GetArgs_Call { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_GetArgs_Call) RunAndReturn(run func() []interface{}) *SQLTemplateIface_GetArgs_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// GetColNames provides a mock function with given fields:
|
||||||
|
func (_m *SQLTemplateIface) GetColNames() []string { |
||||||
|
ret := _m.Called() |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for GetColNames") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 []string |
||||||
|
if rf, ok := ret.Get(0).(func() []string); ok { |
||||||
|
r0 = rf() |
||||||
|
} else { |
||||||
|
if ret.Get(0) != nil { |
||||||
|
r0 = ret.Get(0).([]string) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_GetColNames_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetColNames'
|
||||||
|
type SQLTemplateIface_GetColNames_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// GetColNames is a helper method to define mock.On call
|
||||||
|
func (_e *SQLTemplateIface_Expecter) GetColNames() *SQLTemplateIface_GetColNames_Call { |
||||||
|
return &SQLTemplateIface_GetColNames_Call{Call: _e.mock.On("GetColNames")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_GetColNames_Call) Run(run func()) *SQLTemplateIface_GetColNames_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_GetColNames_Call) Return(_a0 []string) *SQLTemplateIface_GetColNames_Call { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_GetColNames_Call) RunAndReturn(run func() []string) *SQLTemplateIface_GetColNames_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// GetScanDest provides a mock function with given fields:
|
||||||
|
func (_m *SQLTemplateIface) GetScanDest() []interface{} { |
||||||
|
ret := _m.Called() |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for GetScanDest") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 []interface{} |
||||||
|
if rf, ok := ret.Get(0).(func() []interface{}); ok { |
||||||
|
r0 = rf() |
||||||
|
} else { |
||||||
|
if ret.Get(0) != nil { |
||||||
|
r0 = ret.Get(0).([]interface{}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_GetScanDest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetScanDest'
|
||||||
|
type SQLTemplateIface_GetScanDest_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// GetScanDest is a helper method to define mock.On call
|
||||||
|
func (_e *SQLTemplateIface_Expecter) GetScanDest() *SQLTemplateIface_GetScanDest_Call { |
||||||
|
return &SQLTemplateIface_GetScanDest_Call{Call: _e.mock.On("GetScanDest")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_GetScanDest_Call) Run(run func()) *SQLTemplateIface_GetScanDest_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_GetScanDest_Call) Return(_a0 []interface{}) *SQLTemplateIface_GetScanDest_Call { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_GetScanDest_Call) RunAndReturn(run func() []interface{}) *SQLTemplateIface_GetScanDest_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// Ident provides a mock function with given fields: _a0
|
||||||
|
func (_m *SQLTemplateIface) Ident(_a0 string) (string, error) { |
||||||
|
ret := _m.Called(_a0) |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for Ident") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
var r1 error |
||||||
|
if rf, ok := ret.Get(0).(func(string) (string, error)); ok { |
||||||
|
return rf(_a0) |
||||||
|
} |
||||||
|
if rf, ok := ret.Get(0).(func(string) string); ok { |
||||||
|
r0 = rf(_a0) |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(string) error); ok { |
||||||
|
r1 = rf(_a0) |
||||||
|
} else { |
||||||
|
r1 = ret.Error(1) |
||||||
|
} |
||||||
|
|
||||||
|
return r0, r1 |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_Ident_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ident'
|
||||||
|
type SQLTemplateIface_Ident_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Ident is a helper method to define mock.On call
|
||||||
|
// - _a0 string
|
||||||
|
func (_e *SQLTemplateIface_Expecter) Ident(_a0 interface{}) *SQLTemplateIface_Ident_Call { |
||||||
|
return &SQLTemplateIface_Ident_Call{Call: _e.mock.On("Ident", _a0)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Ident_Call) Run(run func(_a0 string)) *SQLTemplateIface_Ident_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run(args[0].(string)) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Ident_Call) Return(_a0 string, _a1 error) *SQLTemplateIface_Ident_Call { |
||||||
|
_c.Call.Return(_a0, _a1) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Ident_Call) RunAndReturn(run func(string) (string, error)) *SQLTemplateIface_Ident_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// Into provides a mock function with given fields: v, colName
|
||||||
|
func (_m *SQLTemplateIface) Into(v reflect.Value, colName string) (string, error) { |
||||||
|
ret := _m.Called(v, colName) |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for Into") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
var r1 error |
||||||
|
if rf, ok := ret.Get(0).(func(reflect.Value, string) (string, error)); ok { |
||||||
|
return rf(v, colName) |
||||||
|
} |
||||||
|
if rf, ok := ret.Get(0).(func(reflect.Value, string) string); ok { |
||||||
|
r0 = rf(v, colName) |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(reflect.Value, string) error); ok { |
||||||
|
r1 = rf(v, colName) |
||||||
|
} else { |
||||||
|
r1 = ret.Error(1) |
||||||
|
} |
||||||
|
|
||||||
|
return r0, r1 |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_Into_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Into'
|
||||||
|
type SQLTemplateIface_Into_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Into is a helper method to define mock.On call
|
||||||
|
// - v reflect.Value
|
||||||
|
// - colName string
|
||||||
|
func (_e *SQLTemplateIface_Expecter) Into(v interface{}, colName interface{}) *SQLTemplateIface_Into_Call { |
||||||
|
return &SQLTemplateIface_Into_Call{Call: _e.mock.On("Into", v, colName)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Into_Call) Run(run func(v reflect.Value, colName string)) *SQLTemplateIface_Into_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run(args[0].(reflect.Value), args[1].(string)) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Into_Call) Return(_a0 string, _a1 error) *SQLTemplateIface_Into_Call { |
||||||
|
_c.Call.Return(_a0, _a1) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Into_Call) RunAndReturn(run func(reflect.Value, string) (string, error)) *SQLTemplateIface_Into_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// Name provides a mock function with given fields:
|
||||||
|
func (_m *SQLTemplateIface) Name() string { |
||||||
|
ret := _m.Called() |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for Name") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
if rf, ok := ret.Get(0).(func() string); ok { |
||||||
|
r0 = rf() |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name'
|
||||||
|
type SQLTemplateIface_Name_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Name is a helper method to define mock.On call
|
||||||
|
func (_e *SQLTemplateIface_Expecter) Name() *SQLTemplateIface_Name_Call { |
||||||
|
return &SQLTemplateIface_Name_Call{Call: _e.mock.On("Name")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Name_Call) Run(run func()) *SQLTemplateIface_Name_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Name_Call) Return(_a0 string) *SQLTemplateIface_Name_Call { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Name_Call) RunAndReturn(run func() string) *SQLTemplateIface_Name_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// Reset provides a mock function with given fields:
|
||||||
|
func (_m *SQLTemplateIface) Reset() { |
||||||
|
_m.Called() |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_Reset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reset'
|
||||||
|
type SQLTemplateIface_Reset_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Reset is a helper method to define mock.On call
|
||||||
|
func (_e *SQLTemplateIface_Expecter) Reset() *SQLTemplateIface_Reset_Call { |
||||||
|
return &SQLTemplateIface_Reset_Call{Call: _e.mock.On("Reset")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Reset_Call) Run(run func()) *SQLTemplateIface_Reset_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Reset_Call) Return() *SQLTemplateIface_Reset_Call { |
||||||
|
_c.Call.Return() |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Reset_Call) RunAndReturn(run func()) *SQLTemplateIface_Reset_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// SelectFor provides a mock function with given fields: _a0
|
||||||
|
func (_m *SQLTemplateIface) SelectFor(_a0 ...string) (string, error) { |
||||||
|
_va := make([]interface{}, len(_a0)) |
||||||
|
for _i := range _a0 { |
||||||
|
_va[_i] = _a0[_i] |
||||||
|
} |
||||||
|
var _ca []interface{} |
||||||
|
_ca = append(_ca, _va...) |
||||||
|
ret := _m.Called(_ca...) |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for SelectFor") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
var r1 error |
||||||
|
if rf, ok := ret.Get(0).(func(...string) (string, error)); ok { |
||||||
|
return rf(_a0...) |
||||||
|
} |
||||||
|
if rf, ok := ret.Get(0).(func(...string) string); ok { |
||||||
|
r0 = rf(_a0...) |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(...string) error); ok { |
||||||
|
r1 = rf(_a0...) |
||||||
|
} else { |
||||||
|
r1 = ret.Error(1) |
||||||
|
} |
||||||
|
|
||||||
|
return r0, r1 |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_SelectFor_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SelectFor'
|
||||||
|
type SQLTemplateIface_SelectFor_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// SelectFor is a helper method to define mock.On call
|
||||||
|
// - _a0 ...string
|
||||||
|
func (_e *SQLTemplateIface_Expecter) SelectFor(_a0 ...interface{}) *SQLTemplateIface_SelectFor_Call { |
||||||
|
return &SQLTemplateIface_SelectFor_Call{Call: _e.mock.On("SelectFor", |
||||||
|
append([]interface{}{}, _a0...)...)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_SelectFor_Call) Run(run func(_a0 ...string)) *SQLTemplateIface_SelectFor_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
variadicArgs := make([]string, len(args)-0) |
||||||
|
for i, a := range args[0:] { |
||||||
|
if a != nil { |
||||||
|
variadicArgs[i] = a.(string) |
||||||
|
} |
||||||
|
} |
||||||
|
run(variadicArgs...) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_SelectFor_Call) Return(_a0 string, _a1 error) *SQLTemplateIface_SelectFor_Call { |
||||||
|
_c.Call.Return(_a0, _a1) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_SelectFor_Call) RunAndReturn(run func(...string) (string, error)) *SQLTemplateIface_SelectFor_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// SetDialect provides a mock function with given fields: _a0
|
||||||
|
func (_m *SQLTemplateIface) SetDialect(_a0 sqltemplate.Dialect) { |
||||||
|
_m.Called(_a0) |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_SetDialect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetDialect'
|
||||||
|
type SQLTemplateIface_SetDialect_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// SetDialect is a helper method to define mock.On call
|
||||||
|
// - _a0 sqltemplate.Dialect
|
||||||
|
func (_e *SQLTemplateIface_Expecter) SetDialect(_a0 interface{}) *SQLTemplateIface_SetDialect_Call { |
||||||
|
return &SQLTemplateIface_SetDialect_Call{Call: _e.mock.On("SetDialect", _a0)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_SetDialect_Call) Run(run func(_a0 sqltemplate.Dialect)) *SQLTemplateIface_SetDialect_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run(args[0].(sqltemplate.Dialect)) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_SetDialect_Call) Return() *SQLTemplateIface_SetDialect_Call { |
||||||
|
_c.Call.Return() |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_SetDialect_Call) RunAndReturn(run func(sqltemplate.Dialect)) *SQLTemplateIface_SetDialect_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// Validate provides a mock function with given fields:
|
||||||
|
func (_m *SQLTemplateIface) Validate() error { |
||||||
|
ret := _m.Called() |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for Validate") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 error |
||||||
|
if rf, ok := ret.Get(0).(func() error); ok { |
||||||
|
r0 = rf() |
||||||
|
} else { |
||||||
|
r0 = ret.Error(0) |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// SQLTemplateIface_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
|
||||||
|
type SQLTemplateIface_Validate_Call struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Validate is a helper method to define mock.On call
|
||||||
|
func (_e *SQLTemplateIface_Expecter) Validate() *SQLTemplateIface_Validate_Call { |
||||||
|
return &SQLTemplateIface_Validate_Call{Call: _e.mock.On("Validate")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Validate_Call) Run(run func()) *SQLTemplateIface_Validate_Call { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Validate_Call) Return(_a0 error) *SQLTemplateIface_Validate_Call { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *SQLTemplateIface_Validate_Call) RunAndReturn(run func() error) *SQLTemplateIface_Validate_Call { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// NewSQLTemplateIface creates a new instance of SQLTemplateIface. 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 NewSQLTemplateIface(t interface { |
||||||
|
mock.TestingT |
||||||
|
Cleanup(func()) |
||||||
|
}) *SQLTemplateIface { |
||||||
|
mock := &SQLTemplateIface{} |
||||||
|
mock.Mock.Test(t) |
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) }) |
||||||
|
|
||||||
|
return mock |
||||||
|
} |
@ -0,0 +1,719 @@ |
|||||||
|
// Code generated by mockery v2.43.1. DO NOT EDIT.
|
||||||
|
|
||||||
|
package mocks |
||||||
|
|
||||||
|
import ( |
||||||
|
reflect "reflect" |
||||||
|
|
||||||
|
mock "github.com/stretchr/testify/mock" |
||||||
|
|
||||||
|
sqltemplate "github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate" |
||||||
|
) |
||||||
|
|
||||||
|
// WithResults is an autogenerated mock type for the WithResults type
|
||||||
|
type WithResults[T interface{}] struct { |
||||||
|
mock.Mock |
||||||
|
} |
||||||
|
|
||||||
|
type WithResults_Expecter[T interface{}] struct { |
||||||
|
mock *mock.Mock |
||||||
|
} |
||||||
|
|
||||||
|
func (_m *WithResults[T]) EXPECT() *WithResults_Expecter[T] { |
||||||
|
return &WithResults_Expecter[T]{mock: &_m.Mock} |
||||||
|
} |
||||||
|
|
||||||
|
// Arg provides a mock function with given fields: x
|
||||||
|
func (_m *WithResults[T]) Arg(x interface{}) string { |
||||||
|
ret := _m.Called(x) |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for Arg") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
if rf, ok := ret.Get(0).(func(interface{}) string); ok { |
||||||
|
r0 = rf(x) |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_Arg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Arg'
|
||||||
|
type WithResults_Arg_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Arg is a helper method to define mock.On call
|
||||||
|
// - x interface{}
|
||||||
|
func (_e *WithResults_Expecter[T]) Arg(x interface{}) *WithResults_Arg_Call[T] { |
||||||
|
return &WithResults_Arg_Call[T]{Call: _e.mock.On("Arg", x)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Arg_Call[T]) Run(run func(x interface{})) *WithResults_Arg_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run(args[0].(interface{})) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Arg_Call[T]) Return(_a0 string) *WithResults_Arg_Call[T] { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Arg_Call[T]) RunAndReturn(run func(interface{}) string) *WithResults_Arg_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// ArgList provides a mock function with given fields: slice
|
||||||
|
func (_m *WithResults[T]) ArgList(slice reflect.Value) (string, error) { |
||||||
|
ret := _m.Called(slice) |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for ArgList") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
var r1 error |
||||||
|
if rf, ok := ret.Get(0).(func(reflect.Value) (string, error)); ok { |
||||||
|
return rf(slice) |
||||||
|
} |
||||||
|
if rf, ok := ret.Get(0).(func(reflect.Value) string); ok { |
||||||
|
r0 = rf(slice) |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(reflect.Value) error); ok { |
||||||
|
r1 = rf(slice) |
||||||
|
} else { |
||||||
|
r1 = ret.Error(1) |
||||||
|
} |
||||||
|
|
||||||
|
return r0, r1 |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_ArgList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ArgList'
|
||||||
|
type WithResults_ArgList_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// ArgList is a helper method to define mock.On call
|
||||||
|
// - slice reflect.Value
|
||||||
|
func (_e *WithResults_Expecter[T]) ArgList(slice interface{}) *WithResults_ArgList_Call[T] { |
||||||
|
return &WithResults_ArgList_Call[T]{Call: _e.mock.On("ArgList", slice)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_ArgList_Call[T]) Run(run func(slice reflect.Value)) *WithResults_ArgList_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run(args[0].(reflect.Value)) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_ArgList_Call[T]) Return(_a0 string, _a1 error) *WithResults_ArgList_Call[T] { |
||||||
|
_c.Call.Return(_a0, _a1) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_ArgList_Call[T]) RunAndReturn(run func(reflect.Value) (string, error)) *WithResults_ArgList_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// ArgPlaceholder provides a mock function with given fields: argNum
|
||||||
|
func (_m *WithResults[T]) ArgPlaceholder(argNum int) string { |
||||||
|
ret := _m.Called(argNum) |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for ArgPlaceholder") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
if rf, ok := ret.Get(0).(func(int) string); ok { |
||||||
|
r0 = rf(argNum) |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_ArgPlaceholder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ArgPlaceholder'
|
||||||
|
type WithResults_ArgPlaceholder_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// ArgPlaceholder is a helper method to define mock.On call
|
||||||
|
// - argNum int
|
||||||
|
func (_e *WithResults_Expecter[T]) ArgPlaceholder(argNum interface{}) *WithResults_ArgPlaceholder_Call[T] { |
||||||
|
return &WithResults_ArgPlaceholder_Call[T]{Call: _e.mock.On("ArgPlaceholder", argNum)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_ArgPlaceholder_Call[T]) Run(run func(argNum int)) *WithResults_ArgPlaceholder_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run(args[0].(int)) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_ArgPlaceholder_Call[T]) Return(_a0 string) *WithResults_ArgPlaceholder_Call[T] { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_ArgPlaceholder_Call[T]) RunAndReturn(run func(int) string) *WithResults_ArgPlaceholder_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// GetArgs provides a mock function with given fields:
|
||||||
|
func (_m *WithResults[T]) GetArgs() []interface{} { |
||||||
|
ret := _m.Called() |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for GetArgs") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 []interface{} |
||||||
|
if rf, ok := ret.Get(0).(func() []interface{}); ok { |
||||||
|
r0 = rf() |
||||||
|
} else { |
||||||
|
if ret.Get(0) != nil { |
||||||
|
r0 = ret.Get(0).([]interface{}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_GetArgs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetArgs'
|
||||||
|
type WithResults_GetArgs_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// GetArgs is a helper method to define mock.On call
|
||||||
|
func (_e *WithResults_Expecter[T]) GetArgs() *WithResults_GetArgs_Call[T] { |
||||||
|
return &WithResults_GetArgs_Call[T]{Call: _e.mock.On("GetArgs")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_GetArgs_Call[T]) Run(run func()) *WithResults_GetArgs_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_GetArgs_Call[T]) Return(_a0 []interface{}) *WithResults_GetArgs_Call[T] { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_GetArgs_Call[T]) RunAndReturn(run func() []interface{}) *WithResults_GetArgs_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// GetColNames provides a mock function with given fields:
|
||||||
|
func (_m *WithResults[T]) GetColNames() []string { |
||||||
|
ret := _m.Called() |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for GetColNames") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 []string |
||||||
|
if rf, ok := ret.Get(0).(func() []string); ok { |
||||||
|
r0 = rf() |
||||||
|
} else { |
||||||
|
if ret.Get(0) != nil { |
||||||
|
r0 = ret.Get(0).([]string) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_GetColNames_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetColNames'
|
||||||
|
type WithResults_GetColNames_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// GetColNames is a helper method to define mock.On call
|
||||||
|
func (_e *WithResults_Expecter[T]) GetColNames() *WithResults_GetColNames_Call[T] { |
||||||
|
return &WithResults_GetColNames_Call[T]{Call: _e.mock.On("GetColNames")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_GetColNames_Call[T]) Run(run func()) *WithResults_GetColNames_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_GetColNames_Call[T]) Return(_a0 []string) *WithResults_GetColNames_Call[T] { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_GetColNames_Call[T]) RunAndReturn(run func() []string) *WithResults_GetColNames_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// GetScanDest provides a mock function with given fields:
|
||||||
|
func (_m *WithResults[T]) GetScanDest() []interface{} { |
||||||
|
ret := _m.Called() |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for GetScanDest") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 []interface{} |
||||||
|
if rf, ok := ret.Get(0).(func() []interface{}); ok { |
||||||
|
r0 = rf() |
||||||
|
} else { |
||||||
|
if ret.Get(0) != nil { |
||||||
|
r0 = ret.Get(0).([]interface{}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_GetScanDest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetScanDest'
|
||||||
|
type WithResults_GetScanDest_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// GetScanDest is a helper method to define mock.On call
|
||||||
|
func (_e *WithResults_Expecter[T]) GetScanDest() *WithResults_GetScanDest_Call[T] { |
||||||
|
return &WithResults_GetScanDest_Call[T]{Call: _e.mock.On("GetScanDest")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_GetScanDest_Call[T]) Run(run func()) *WithResults_GetScanDest_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_GetScanDest_Call[T]) Return(_a0 []interface{}) *WithResults_GetScanDest_Call[T] { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_GetScanDest_Call[T]) RunAndReturn(run func() []interface{}) *WithResults_GetScanDest_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// Ident provides a mock function with given fields: _a0
|
||||||
|
func (_m *WithResults[T]) Ident(_a0 string) (string, error) { |
||||||
|
ret := _m.Called(_a0) |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for Ident") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
var r1 error |
||||||
|
if rf, ok := ret.Get(0).(func(string) (string, error)); ok { |
||||||
|
return rf(_a0) |
||||||
|
} |
||||||
|
if rf, ok := ret.Get(0).(func(string) string); ok { |
||||||
|
r0 = rf(_a0) |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(string) error); ok { |
||||||
|
r1 = rf(_a0) |
||||||
|
} else { |
||||||
|
r1 = ret.Error(1) |
||||||
|
} |
||||||
|
|
||||||
|
return r0, r1 |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_Ident_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ident'
|
||||||
|
type WithResults_Ident_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Ident is a helper method to define mock.On call
|
||||||
|
// - _a0 string
|
||||||
|
func (_e *WithResults_Expecter[T]) Ident(_a0 interface{}) *WithResults_Ident_Call[T] { |
||||||
|
return &WithResults_Ident_Call[T]{Call: _e.mock.On("Ident", _a0)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Ident_Call[T]) Run(run func(_a0 string)) *WithResults_Ident_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run(args[0].(string)) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Ident_Call[T]) Return(_a0 string, _a1 error) *WithResults_Ident_Call[T] { |
||||||
|
_c.Call.Return(_a0, _a1) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Ident_Call[T]) RunAndReturn(run func(string) (string, error)) *WithResults_Ident_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// Into provides a mock function with given fields: v, colName
|
||||||
|
func (_m *WithResults[T]) Into(v reflect.Value, colName string) (string, error) { |
||||||
|
ret := _m.Called(v, colName) |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for Into") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
var r1 error |
||||||
|
if rf, ok := ret.Get(0).(func(reflect.Value, string) (string, error)); ok { |
||||||
|
return rf(v, colName) |
||||||
|
} |
||||||
|
if rf, ok := ret.Get(0).(func(reflect.Value, string) string); ok { |
||||||
|
r0 = rf(v, colName) |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(reflect.Value, string) error); ok { |
||||||
|
r1 = rf(v, colName) |
||||||
|
} else { |
||||||
|
r1 = ret.Error(1) |
||||||
|
} |
||||||
|
|
||||||
|
return r0, r1 |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_Into_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Into'
|
||||||
|
type WithResults_Into_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Into is a helper method to define mock.On call
|
||||||
|
// - v reflect.Value
|
||||||
|
// - colName string
|
||||||
|
func (_e *WithResults_Expecter[T]) Into(v interface{}, colName interface{}) *WithResults_Into_Call[T] { |
||||||
|
return &WithResults_Into_Call[T]{Call: _e.mock.On("Into", v, colName)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Into_Call[T]) Run(run func(v reflect.Value, colName string)) *WithResults_Into_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run(args[0].(reflect.Value), args[1].(string)) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Into_Call[T]) Return(_a0 string, _a1 error) *WithResults_Into_Call[T] { |
||||||
|
_c.Call.Return(_a0, _a1) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Into_Call[T]) RunAndReturn(run func(reflect.Value, string) (string, error)) *WithResults_Into_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// Name provides a mock function with given fields:
|
||||||
|
func (_m *WithResults[T]) Name() string { |
||||||
|
ret := _m.Called() |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for Name") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
if rf, ok := ret.Get(0).(func() string); ok { |
||||||
|
r0 = rf() |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name'
|
||||||
|
type WithResults_Name_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Name is a helper method to define mock.On call
|
||||||
|
func (_e *WithResults_Expecter[T]) Name() *WithResults_Name_Call[T] { |
||||||
|
return &WithResults_Name_Call[T]{Call: _e.mock.On("Name")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Name_Call[T]) Run(run func()) *WithResults_Name_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Name_Call[T]) Return(_a0 string) *WithResults_Name_Call[T] { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Name_Call[T]) RunAndReturn(run func() string) *WithResults_Name_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// Reset provides a mock function with given fields:
|
||||||
|
func (_m *WithResults[T]) Reset() { |
||||||
|
_m.Called() |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_Reset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reset'
|
||||||
|
type WithResults_Reset_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Reset is a helper method to define mock.On call
|
||||||
|
func (_e *WithResults_Expecter[T]) Reset() *WithResults_Reset_Call[T] { |
||||||
|
return &WithResults_Reset_Call[T]{Call: _e.mock.On("Reset")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Reset_Call[T]) Run(run func()) *WithResults_Reset_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Reset_Call[T]) Return() *WithResults_Reset_Call[T] { |
||||||
|
_c.Call.Return() |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Reset_Call[T]) RunAndReturn(run func()) *WithResults_Reset_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// Results provides a mock function with given fields:
|
||||||
|
func (_m *WithResults[T]) Results() (T, error) { |
||||||
|
ret := _m.Called() |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for Results") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 T |
||||||
|
var r1 error |
||||||
|
if rf, ok := ret.Get(0).(func() (T, error)); ok { |
||||||
|
return rf() |
||||||
|
} |
||||||
|
if rf, ok := ret.Get(0).(func() T); ok { |
||||||
|
r0 = rf() |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(T) |
||||||
|
} |
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func() error); ok { |
||||||
|
r1 = rf() |
||||||
|
} else { |
||||||
|
r1 = ret.Error(1) |
||||||
|
} |
||||||
|
|
||||||
|
return r0, r1 |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_Results_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Results'
|
||||||
|
type WithResults_Results_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Results is a helper method to define mock.On call
|
||||||
|
func (_e *WithResults_Expecter[T]) Results() *WithResults_Results_Call[T] { |
||||||
|
return &WithResults_Results_Call[T]{Call: _e.mock.On("Results")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Results_Call[T]) Run(run func()) *WithResults_Results_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Results_Call[T]) Return(_a0 T, _a1 error) *WithResults_Results_Call[T] { |
||||||
|
_c.Call.Return(_a0, _a1) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Results_Call[T]) RunAndReturn(run func() (T, error)) *WithResults_Results_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// SelectFor provides a mock function with given fields: _a0
|
||||||
|
func (_m *WithResults[T]) SelectFor(_a0 ...string) (string, error) { |
||||||
|
_va := make([]interface{}, len(_a0)) |
||||||
|
for _i := range _a0 { |
||||||
|
_va[_i] = _a0[_i] |
||||||
|
} |
||||||
|
var _ca []interface{} |
||||||
|
_ca = append(_ca, _va...) |
||||||
|
ret := _m.Called(_ca...) |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for SelectFor") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 string |
||||||
|
var r1 error |
||||||
|
if rf, ok := ret.Get(0).(func(...string) (string, error)); ok { |
||||||
|
return rf(_a0...) |
||||||
|
} |
||||||
|
if rf, ok := ret.Get(0).(func(...string) string); ok { |
||||||
|
r0 = rf(_a0...) |
||||||
|
} else { |
||||||
|
r0 = ret.Get(0).(string) |
||||||
|
} |
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(...string) error); ok { |
||||||
|
r1 = rf(_a0...) |
||||||
|
} else { |
||||||
|
r1 = ret.Error(1) |
||||||
|
} |
||||||
|
|
||||||
|
return r0, r1 |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_SelectFor_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SelectFor'
|
||||||
|
type WithResults_SelectFor_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// SelectFor is a helper method to define mock.On call
|
||||||
|
// - _a0 ...string
|
||||||
|
func (_e *WithResults_Expecter[T]) SelectFor(_a0 ...interface{}) *WithResults_SelectFor_Call[T] { |
||||||
|
return &WithResults_SelectFor_Call[T]{Call: _e.mock.On("SelectFor", |
||||||
|
append([]interface{}{}, _a0...)...)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_SelectFor_Call[T]) Run(run func(_a0 ...string)) *WithResults_SelectFor_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
variadicArgs := make([]string, len(args)-0) |
||||||
|
for i, a := range args[0:] { |
||||||
|
if a != nil { |
||||||
|
variadicArgs[i] = a.(string) |
||||||
|
} |
||||||
|
} |
||||||
|
run(variadicArgs...) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_SelectFor_Call[T]) Return(_a0 string, _a1 error) *WithResults_SelectFor_Call[T] { |
||||||
|
_c.Call.Return(_a0, _a1) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_SelectFor_Call[T]) RunAndReturn(run func(...string) (string, error)) *WithResults_SelectFor_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// SetDialect provides a mock function with given fields: _a0
|
||||||
|
func (_m *WithResults[T]) SetDialect(_a0 sqltemplate.Dialect) { |
||||||
|
_m.Called(_a0) |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_SetDialect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetDialect'
|
||||||
|
type WithResults_SetDialect_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// SetDialect is a helper method to define mock.On call
|
||||||
|
// - _a0 sqltemplate.Dialect
|
||||||
|
func (_e *WithResults_Expecter[T]) SetDialect(_a0 interface{}) *WithResults_SetDialect_Call[T] { |
||||||
|
return &WithResults_SetDialect_Call[T]{Call: _e.mock.On("SetDialect", _a0)} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_SetDialect_Call[T]) Run(run func(_a0 sqltemplate.Dialect)) *WithResults_SetDialect_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run(args[0].(sqltemplate.Dialect)) |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_SetDialect_Call[T]) Return() *WithResults_SetDialect_Call[T] { |
||||||
|
_c.Call.Return() |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_SetDialect_Call[T]) RunAndReturn(run func(sqltemplate.Dialect)) *WithResults_SetDialect_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// Validate provides a mock function with given fields:
|
||||||
|
func (_m *WithResults[T]) Validate() error { |
||||||
|
ret := _m.Called() |
||||||
|
|
||||||
|
if len(ret) == 0 { |
||||||
|
panic("no return value specified for Validate") |
||||||
|
} |
||||||
|
|
||||||
|
var r0 error |
||||||
|
if rf, ok := ret.Get(0).(func() error); ok { |
||||||
|
r0 = rf() |
||||||
|
} else { |
||||||
|
r0 = ret.Error(0) |
||||||
|
} |
||||||
|
|
||||||
|
return r0 |
||||||
|
} |
||||||
|
|
||||||
|
// WithResults_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
|
||||||
|
type WithResults_Validate_Call[T interface{}] struct { |
||||||
|
*mock.Call |
||||||
|
} |
||||||
|
|
||||||
|
// Validate is a helper method to define mock.On call
|
||||||
|
func (_e *WithResults_Expecter[T]) Validate() *WithResults_Validate_Call[T] { |
||||||
|
return &WithResults_Validate_Call[T]{Call: _e.mock.On("Validate")} |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Validate_Call[T]) Run(run func()) *WithResults_Validate_Call[T] { |
||||||
|
_c.Call.Run(func(args mock.Arguments) { |
||||||
|
run() |
||||||
|
}) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Validate_Call[T]) Return(_a0 error) *WithResults_Validate_Call[T] { |
||||||
|
_c.Call.Return(_a0) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
func (_c *WithResults_Validate_Call[T]) RunAndReturn(run func() error) *WithResults_Validate_Call[T] { |
||||||
|
_c.Call.Return(run) |
||||||
|
return _c |
||||||
|
} |
||||||
|
|
||||||
|
// NewWithResults creates a new instance of WithResults. 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 NewWithResults[T interface{}](t interface { |
||||||
|
mock.TestingT |
||||||
|
Cleanup(func()) |
||||||
|
}) *WithResults[T] { |
||||||
|
mock := &WithResults[T]{} |
||||||
|
mock.Mock.Test(t) |
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) }) |
||||||
|
|
||||||
|
return mock |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
DELETE FROM "entity" WHERE 1 = 1 AND "namespace" = ? AND "group" = ? AND "resource" = ? AND "name" = ?; |
@ -0,0 +1 @@ |
|||||||
|
DELETE FROM "entity" WHERE 1 = 1 AND "namespace" = $1 AND "group" = $2 AND "resource" = $3 AND "name" = $4; |
@ -0,0 +1,3 @@ |
|||||||
|
INSERT INTO "entity_folder" |
||||||
|
("guid", "namespace", "name", "slug_path", "tree", "depth", "lft", "rgt", "detached") |
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); |
@ -0,0 +1,5 @@ |
|||||||
|
INSERT INTO "entity_folder" |
||||||
|
("guid", "namespace", "name", "slug_path", "tree", "depth", "lft", "rgt", "detached") |
||||||
|
VALUES |
||||||
|
(?, ?, ?, ?, ?, ?, ?, ?, ?), |
||||||
|
(?, ?, ?, ?, ?, ?, ?, ?, ?); |
@ -0,0 +1,3 @@ |
|||||||
|
INSERT INTO "entity_history" |
||||||
|
("guid", "resource_version", "key", "group", "group_version", "resource", "namespace", "name", "folder", "meta", "body", "status", "size", "etag", "created_at", "created_by", "updated_at", "updated_by", "origin", "origin_key", "origin_ts", "title", "slug", "description", "message", "labels", "fields", "errors", "action") |
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); |
@ -0,0 +1,5 @@ |
|||||||
|
SELECT e."guid", e."resource_version", e."key", e."group", e."group_version", e."resource", e."namespace", e."name", e."folder", e."meta", e."body", e."status", e."size", e."etag", e."created_at", e."created_by", e."updated_at", e."updated_by", e."origin", e."origin_key", e."origin_ts", e."title", e."slug", e."description", e."message", e."labels", e."fields", e."errors", e."action" |
||||||
|
FROM "entity_history" AS e |
||||||
|
WHERE 1 = 1 AND "namespace" = ? AND "group" = ? AND "resource" = ? AND "name" = ? AND "resource_version" <= ? |
||||||
|
ORDER BY "resource_version" DESC |
||||||
|
LIMIT 1 FOR UPDATE NOWAIT; |
@ -0,0 +1,3 @@ |
|||||||
|
INSERT INTO "entity" |
||||||
|
("guid", "resource_version", "key", "group", "group_version", "resource", "namespace", "name", "folder", "meta", "body", "status", "size", "etag", "created_at", "created_by", "updated_at", "updated_by", "origin", "origin_key", "origin_ts", "title", "slug", "description", "message", "labels", "fields", "errors", "action") |
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); |
@ -0,0 +1 @@ |
|||||||
|
DELETE FROM "entity_labels" WHERE 1 = 1 AND "guid" = ? AND "label" NOT IN (?); |
@ -0,0 +1 @@ |
|||||||
|
DELETE FROM "entity_labels" WHERE 1 = 1 AND "guid" = ? AND "label" NOT IN (?, ?); |
@ -0,0 +1,2 @@ |
|||||||
|
INSERT INTO "entity_labels" ("guid", "label", "value") |
||||||
|
VALUES (?, ?, ?); |
@ -0,0 +1,3 @@ |
|||||||
|
INSERT INTO "entity_labels" ("guid", "label", "value") VALUES |
||||||
|
(?, ?, ?), |
||||||
|
(?, ?, ?); |
@ -0,0 +1,3 @@ |
|||||||
|
SELECT "guid", "name", "folder", "name", "slug" |
||||||
|
FROM "entity" |
||||||
|
WHERE 1 = 1 AND "group" = ? AND "resource" = ? AND "namespace" = ?; |
@ -0,0 +1,3 @@ |
|||||||
|
SELECT e."guid", e."resource_version", e."key", e."group", e."group_version", e."resource", e."namespace", e."name", e."folder", e."meta", e."body", e."status", e."size", e."etag", e."created_at", e."created_by", e."updated_at", e."updated_by", e."origin", e."origin_key", e."origin_ts", e."title", e."slug", e."description", e."message", e."labels", e."fields", e."errors", e."action" |
||||||
|
FROM "entity" AS e |
||||||
|
WHERE 1 = 1 AND "namespace" = ? AND "group" = ? AND "resource" = ? AND "name" = ?; |
@ -0,0 +1,2 @@ |
|||||||
|
UPDATE "entity" SET "resource_version" = ?, "group_version" = ?, "folder" = ?, "meta" = ?, "body" = ?, "status" = ?, "size" = ?, "etag" = ?, "updated_at" = ?, "updated_by" = ?, "origin" = ?, "origin_key" = ?, "origin_ts" = ?, "title" = ?, "slug" = ?, "description" = ?, "message" = ?, "labels" = ?, "fields" = ?, "errors" = ?, "action" = ? |
||||||
|
WHERE "guid" = ?; |
@ -0,0 +1,21 @@ |
|||||||
|
{ |
||||||
|
"entity": { |
||||||
|
"guid": "b0199c60-5f3a-41be-9ba6-7a52f1de83ff", |
||||||
|
"group": "playlist.grafana.app", |
||||||
|
"resource": "playlists", |
||||||
|
"namespace": "default", |
||||||
|
"name": "adnj1llchbbi8a", |
||||||
|
"group_version": "v0alpha1", |
||||||
|
"key": "/playlist.grafana.app/playlists/namespaces/default/adnj1llchbbi8a", |
||||||
|
"meta": "eyJtZXRhZGF0YSI6eyJuYW1lIjoiYWRuajFsbGNoYmJpOGEiLCJuYW1lc3BhY2UiOiJkZWZhdWx0IiwidWlkIjoiYjAxOTljNjAtNWYzYS00MWJlLTliYTYtN2E1MmYxZGU4M2ZmIiwiY3JlYXRpb25UaW1lc3RhbXAiOiIyMDI0LTA2LTAyVDAzOjI4OjE3WiIsImFubm90YXRpb25zIjp7ImdyYWZhbmEuYXBwL29yaWdpbktleSI6IjIiLCJncmFmYW5hLmFwcC9vcmlnaW5OYW1lIjoiU1FMIiwiZ3JhZmFuYS5hcHAvb3JpZ2luVGltZXN0YW1wIjoiMjAyNC0wNi0wMlQwMzoyODoxN1oiLCJncmFmYW5hLmFwcC91cGRhdGVkVGltZXN0YW1wIjoiMjAyNC0wNi0wMlQwMzoyODoxN1oifX19", |
||||||
|
"body": "eyJraW5kIjoiUGxheWxpc3QiLCJhcGlWZXJzaW9uIjoicGxheWxpc3QuZ3JhZmFuYS5hcHAvdjBhbHBoYTEiLCJtZXRhZGF0YSI6eyJuYW1lIjoiYWRuajFsbGNoYmJpOGEiLCJuYW1lc3BhY2UiOiJkZWZhdWx0IiwidWlkIjoiYjAxOTljNjAtNWYzYS00MWJlLTliYTYtN2E1MmYxZGU4M2ZmIiwiY3JlYXRpb25UaW1lc3RhbXAiOiIyMDI0LTA2LTAyVDAzOjI4OjE3WiIsImFubm90YXRpb25zIjp7ImdyYWZhbmEuYXBwL29yaWdpbktleSI6IjIiLCJncmFmYW5hLmFwcC9vcmlnaW5OYW1lIjoiU1FMIiwiZ3JhZmFuYS5hcHAvb3JpZ2luVGltZXN0YW1wIjoiMjAyNC0wNi0wMlQwMzoyODoxN1oiLCJncmFmYW5hLmFwcC91cGRhdGVkVGltZXN0YW1wIjoiMjAyNC0wNi0wMlQwMzoyODoxN1oifX0sInNwZWMiOnsidGl0bGUiOiJ0ZXN0IHBsYXlsaXN0IiwiaW50ZXJ2YWwiOiI1bSIsIml0ZW1zIjpbeyJ0eXBlIjoiZGFzaGJvYXJkX2J5X3VpZCIsInZhbHVlIjoiY2RuaXY1M2dtZDR3MGUifV19fQo=", |
||||||
|
"title": "test playlist", |
||||||
|
"created_at": 1717298897750, |
||||||
|
"updated_at": 1717298897000, |
||||||
|
"origin": { |
||||||
|
"source": "SQL", |
||||||
|
"key": "2", |
||||||
|
"time": 1717298897000 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
{ |
||||||
|
"key": "/playlist.grafana.app/playlists/namespaces/default/sdfsdfsdf" |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
{ |
||||||
|
"entity": { |
||||||
|
"guid": "3c769b2e-aaa7-46f6-ab83-e038050a6a75", |
||||||
|
"resource_version": 1, |
||||||
|
"group": "playlist.grafana.app", |
||||||
|
"resource": "playlists", |
||||||
|
"namespace": "default", |
||||||
|
"name": "sdfsdfsdf", |
||||||
|
"group_version": "v0alpha1", |
||||||
|
"key": "/playlist.grafana.app/playlists/namespaces/default/sdfsdfsdf", |
||||||
|
"meta": "eyJtZXRhZGF0YSI6eyJuYW1lIjoic2Rmc2Rmc2RmIiwibmFtZXNwYWNlIjoiZGVmYXVsdCIsInVpZCI6IjNjNzY5YjJlLWFhYTctNDZmNi1hYjgzLWUwMzgwNTBhNmE3NSIsInJlc291cmNlVmVyc2lvbiI6IjEiLCJjcmVhdGlvblRpbWVzdGFtcCI6IjIwMjQtMDYtMDJUMDM6NDk6MjlaIiwibWFuYWdlZEZpZWxkcyI6W3sibWFuYWdlciI6Ik1vemlsbGEiLCJvcGVyYXRpb24iOiJVcGRhdGUiLCJhcGlWZXJzaW9uIjoicGxheWxpc3QuZ3JhZmFuYS5hcHAvdjBhbHBoYTEiLCJ0aW1lIjoiMjAyNC0wNi0wMlQwMzo1Mzo1NVoiLCJmaWVsZHNUeXBlIjoiRmllbGRzVjEiLCJmaWVsZHNWMSI6eyJmOnNwZWMiOnsiZjppbnRlcnZhbCI6e30sImY6aXRlbXMiOnt9LCJmOnRpdGxlIjp7fX19fV19fQ==", |
||||||
|
"body": "eyJraW5kIjoiUGxheWxpc3QiLCJhcGlWZXJzaW9uIjoicGxheWxpc3QuZ3JhZmFuYS5hcHAvdjBhbHBoYTEiLCJtZXRhZGF0YSI6eyJuYW1lIjoic2Rmc2Rmc2RmIiwibmFtZXNwYWNlIjoiZGVmYXVsdCIsInVpZCI6IjNjNzY5YjJlLWFhYTctNDZmNi1hYjgzLWUwMzgwNTBhNmE3NSIsInJlc291cmNlVmVyc2lvbiI6IjEiLCJjcmVhdGlvblRpbWVzdGFtcCI6IjIwMjQtMDYtMDJUMDM6NDk6MjlaIiwibWFuYWdlZEZpZWxkcyI6W3sibWFuYWdlciI6Ik1vemlsbGEiLCJvcGVyYXRpb24iOiJVcGRhdGUiLCJhcGlWZXJzaW9uIjoicGxheWxpc3QuZ3JhZmFuYS5hcHAvdjBhbHBoYTEiLCJ0aW1lIjoiMjAyNC0wNi0wMlQwMzo1Mzo1NVoiLCJmaWVsZHNUeXBlIjoiRmllbGRzVjEiLCJmaWVsZHNWMSI6eyJmOnNwZWMiOnsiZjppbnRlcnZhbCI6e30sImY6aXRlbXMiOnt9LCJmOnRpdGxlIjp7fX19fV19LCJzcGVjIjp7InRpdGxlIjoieHpjdnp4Y3Zxd2Vxd2UiLCJpbnRlcnZhbCI6IjVtIiwiaXRlbXMiOlt7InR5cGUiOiJkYXNoYm9hcmRfYnlfdWlkIiwidmFsdWUiOiJjZG5pdjUzZ21kNHcwZSJ9XX19Cg==", |
||||||
|
"title": "xzcvzxcvqweqwe", |
||||||
|
"created_at": 1717300169240, |
||||||
|
"origin": {} |
||||||
|
}, |
||||||
|
"previous_version": 1 |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
{ |
||||||
|
"guid": "5842f146-07b9-405b-af76-b4c4b2612518", |
||||||
|
"resource_version": 6, |
||||||
|
"group": "playlist.grafana.app", |
||||||
|
"resource": "playlists", |
||||||
|
"namespace": "default", |
||||||
|
"name": "sdfsdfsdf", |
||||||
|
"group_version": "v0alpha1", |
||||||
|
"key": "/playlist.grafana.app/playlists/namespaces/default/sdfsdfsdf", |
||||||
|
"meta": "eyJtZXRhZGF0YSI6eyJuYW1lIjoic2Rmc2Rmc2RmIiwibmFtZXNwYWNlIjoiZGVmYXVsdCIsInVpZCI6IjAyZmVhOGVlLTk2ZDYtNGIzMy04ZGI5LTU5MmI0NzU4NTM4NSIsImNyZWF0aW9uVGltZXN0YW1wIjoiMjAyNC0wNi0wNFQxNToxODozNFoiLCJtYW5hZ2VkRmllbGRzIjpbeyJtYW5hZ2VyIjoiTW96aWxsYSIsIm9wZXJhdGlvbiI6IlVwZGF0ZSIsImFwaVZlcnNpb24iOiJwbGF5bGlzdC5ncmFmYW5hLmFwcC92MGFscGhhMSIsInRpbWUiOiIyMDI0LTA2LTA0VDE1OjE4OjM0WiIsImZpZWxkc1R5cGUiOiJGaWVsZHNWMSIsImZpZWxkc1YxIjp7ImY6c3BlYyI6eyJmOmludGVydmFsIjp7fSwiZjppdGVtcyI6e30sImY6dGl0bGUiOnt9fX19XX19", |
||||||
|
"body": "eyJraW5kIjoiUGxheWxpc3QiLCJhcGlWZXJzaW9uIjoicGxheWxpc3QuZ3JhZmFuYS5hcHAvdjBhbHBoYTEiLCJtZXRhZGF0YSI6eyJuYW1lIjoic2Rmc2Rmc2RmIiwibmFtZXNwYWNlIjoiZGVmYXVsdCIsInVpZCI6IjAyZmVhOGVlLTk2ZDYtNGIzMy04ZGI5LTU5MmI0NzU4NTM4NSIsImNyZWF0aW9uVGltZXN0YW1wIjoiMjAyNC0wNi0wNFQxNToxODozNFoiLCJtYW5hZ2VkRmllbGRzIjpbeyJtYW5hZ2VyIjoiTW96aWxsYSIsIm9wZXJhdGlvbiI6IlVwZGF0ZSIsImFwaVZlcnNpb24iOiJwbGF5bGlzdC5ncmFmYW5hLmFwcC92MGFscGhhMSIsInRpbWUiOiIyMDI0LTA2LTA0VDE1OjE4OjM0WiIsImZpZWxkc1R5cGUiOiJGaWVsZHNWMSIsImZpZWxkc1YxIjp7ImY6c3BlYyI6eyJmOmludGVydmFsIjp7fSwiZjppdGVtcyI6e30sImY6dGl0bGUiOnt9fX19XX0sInNwZWMiOnsidGl0bGUiOiJ4emN2enhjdiIsImludGVydmFsIjoiNW0iLCJpdGVtcyI6W3sidHlwZSI6ImRhc2hib2FyZF9ieV91aWQiLCJ2YWx1ZSI6ImNkbml2NTNnbWQ0dzBlIn1dfX0K", |
||||||
|
"title": "xzcvzxcv", |
||||||
|
"size": 540, |
||||||
|
"ETag": "3225612903101e3b94f458cfc79baf89", |
||||||
|
"created_at": 1717514314565, |
||||||
|
"created_by": "user:1:admin", |
||||||
|
"updated_at": 1717514314565, |
||||||
|
"updated_by": "user:1:admin", |
||||||
|
"origin": {}, |
||||||
|
"action": 1 |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
SELECT "resource_version", "created_at", "updated_at" |
||||||
|
FROM "kind_version" |
||||||
|
WHERE 1 = 1 AND "group" = ? AND "resource" = ?; |
@ -0,0 +1,4 @@ |
|||||||
|
UPDATE "kind_version" |
||||||
|
SET "resource_version" = ? + 1, |
||||||
|
"updated_at" = ? |
||||||
|
WHERE 1 = 1 AND "group" = ? AND "resource" = ? AND "resource_version" = ?; |
@ -0,0 +1,3 @@ |
|||||||
|
INSERT INTO "kind_version" |
||||||
|
("group", "resource", "resource_version", "created_at", "updated_at") |
||||||
|
VALUES (?, ?, 1, ?, ?); |
@ -0,0 +1,3 @@ |
|||||||
|
SELECT "resource_version" |
||||||
|
FROM "kind_version" |
||||||
|
WHERE 1 = 1 AND "group" = ? AND "resource" = ? FOR UPDATE; |
@ -0,0 +1,3 @@ |
|||||||
|
SELECT "resource_version" |
||||||
|
FROM "kind_version" |
||||||
|
WHERE 1 = 1 AND "group" = $1 AND "resource" = $2 FOR UPDATE; |
@ -0,0 +1,3 @@ |
|||||||
|
SELECT "resource_version" |
||||||
|
FROM "kind_version" |
||||||
|
WHERE 1 = 1 AND "group" = ? AND "resource" = ?; |
@ -0,0 +1,525 @@ |
|||||||
|
package sqlstash |
||||||
|
|
||||||
|
import ( |
||||||
|
"database/sql" |
||||||
|
"database/sql/driver" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"regexp" |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
"text/template" |
||||||
|
|
||||||
|
sqlmock "github.com/DATA-DOG/go-sqlmock" |
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/store/entity/db" |
||||||
|
"github.com/grafana/grafana/pkg/services/store/entity/db/dbimpl" |
||||||
|
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate" |
||||||
|
sqltemplateMocks "github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate/mocks" |
||||||
|
"github.com/grafana/grafana/pkg/util/testutil" |
||||||
|
) |
||||||
|
|
||||||
|
// newMockDBNopSQL returns a db.DB and a sqlmock.Sqlmock that doesn't validates
|
||||||
|
// SQL. This is only meant to be used to test wrapping utilities exec, query and
|
||||||
|
// queryRow, where the actual SQL is not relevant to the unit tests, but rather
|
||||||
|
// how the possible derived error conditions handled.
|
||||||
|
func newMockDBNopSQL(t *testing.T) (db.DB, sqlmock.Sqlmock) { |
||||||
|
t.Helper() |
||||||
|
|
||||||
|
db, mock, err := sqlmock.New( |
||||||
|
sqlmock.MonitorPingsOption(true), |
||||||
|
sqlmock.QueryMatcherOption(sqlmock.QueryMatcherFunc( |
||||||
|
func(expectedSQL, actualSQL string) error { |
||||||
|
return nil |
||||||
|
}, |
||||||
|
)), |
||||||
|
) |
||||||
|
|
||||||
|
return newUnitTestDB(t, db, mock, err) |
||||||
|
} |
||||||
|
|
||||||
|
// newMockDBMatchWords returns a db.DB and a sqlmock.Sqlmock that will match SQL
|
||||||
|
// by splitting the expected SQL string into words, and then try to find all of
|
||||||
|
// them in the actual SQL, in the given order, case insensitively. Prepend a
|
||||||
|
// word with a `!` to say that word should not be found.
|
||||||
|
func newMockDBMatchWords(t *testing.T) (db.DB, sqlmock.Sqlmock) { |
||||||
|
t.Helper() |
||||||
|
|
||||||
|
db, mock, err := sqlmock.New( |
||||||
|
sqlmock.MonitorPingsOption(true), |
||||||
|
sqlmock.QueryMatcherOption( |
||||||
|
sqlmock.QueryMatcherFunc(func(expectedSQL, actualSQL string) error { |
||||||
|
actualSQL = strings.ToLower(sqltemplate.FormatSQL(actualSQL)) |
||||||
|
expectedSQL = strings.ToLower(expectedSQL) |
||||||
|
|
||||||
|
var offset int |
||||||
|
for _, vv := range mockDBMatchWordsRE.FindAllStringSubmatch(expectedSQL, -1) { |
||||||
|
v := vv[1] |
||||||
|
|
||||||
|
var shouldNotMatch bool |
||||||
|
if v != "" && v[0] == '!' { |
||||||
|
v = v[1:] |
||||||
|
shouldNotMatch = true |
||||||
|
} |
||||||
|
if v == "" { |
||||||
|
return fmt.Errorf("invalid expected word %q in %q", v, |
||||||
|
expectedSQL) |
||||||
|
} |
||||||
|
|
||||||
|
reWord, err := regexp.Compile(`\b` + regexp.QuoteMeta(v) + `\b`) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("compile word %q from expected SQL: %s", v, |
||||||
|
expectedSQL) |
||||||
|
} |
||||||
|
|
||||||
|
if shouldNotMatch { |
||||||
|
if reWord.MatchString(actualSQL[offset:]) { |
||||||
|
return fmt.Errorf("actual SQL fragent should not cont"+ |
||||||
|
"ain %q but it does\n\tFragment: %s\n\tFull SQL: %s", |
||||||
|
v, actualSQL[offset:], actualSQL) |
||||||
|
} |
||||||
|
} else { |
||||||
|
loc := reWord.FindStringIndex(actualSQL[offset:]) |
||||||
|
if len(loc) == 0 { |
||||||
|
return fmt.Errorf("actual SQL fragment should contain "+ |
||||||
|
"%q but it doesn't\n\tFragment: %s\n\tFull SQL: %s", |
||||||
|
v, actualSQL[offset:], actualSQL) |
||||||
|
} |
||||||
|
offset = loc[1] // advance the offset
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
}, |
||||||
|
), |
||||||
|
), |
||||||
|
) |
||||||
|
|
||||||
|
return newUnitTestDB(t, db, mock, err) |
||||||
|
} |
||||||
|
|
||||||
|
var mockDBMatchWordsRE = regexp.MustCompile(`(?:\W|\A)(!?\w+)\b`) |
||||||
|
|
||||||
|
func newUnitTestDB(t *testing.T, db *sql.DB, mock sqlmock.Sqlmock, err error) (db.DB, sqlmock.Sqlmock) { |
||||||
|
t.Helper() |
||||||
|
|
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
return dbimpl.NewDB(db, "sqlmock"), mock |
||||||
|
} |
||||||
|
|
||||||
|
// mockResults aids in testing code paths with queries returning large number of
|
||||||
|
// values, like those returning *entity.Entity. This is because we want to
|
||||||
|
// emulate returning the same row columns and row values the same as a real
|
||||||
|
// database would do. This utility the same template SQL that is expected to be
|
||||||
|
// used to help populate all the expected fields.
|
||||||
|
// fileds
|
||||||
|
type mockResults[T any] struct { |
||||||
|
t *testing.T |
||||||
|
tmpl *template.Template |
||||||
|
data sqltemplate.WithResults[T] |
||||||
|
rows *sqlmock.Rows |
||||||
|
} |
||||||
|
|
||||||
|
// newMockResults returns a new *mockResults. If you want to emulate a call
|
||||||
|
// returning zero rows, then immediately call the Row method afterward.
|
||||||
|
func newMockResults[T any](t *testing.T, mock sqlmock.Sqlmock, tmpl *template.Template, data sqltemplate.WithResults[T]) *mockResults[T] { |
||||||
|
t.Helper() |
||||||
|
|
||||||
|
data.Reset() |
||||||
|
err := tmpl.Execute(io.Discard, data) |
||||||
|
require.NoError(t, err) |
||||||
|
rows := mock.NewRows(data.GetColNames()) |
||||||
|
|
||||||
|
return &mockResults[T]{ |
||||||
|
t: t, |
||||||
|
tmpl: tmpl, |
||||||
|
data: data, |
||||||
|
rows: rows, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// AddCurrentData uses the values contained in the `data` argument used during
|
||||||
|
// creation to populate a new expected row. It will access `data` with pointers,
|
||||||
|
// so you should replace the internal values of `data` with freshly allocated
|
||||||
|
// results to return different rows.
|
||||||
|
func (r *mockResults[T]) AddCurrentData() *mockResults[T] { |
||||||
|
r.t.Helper() |
||||||
|
|
||||||
|
r.data.Reset() |
||||||
|
err := r.tmpl.Execute(io.Discard, r.data) |
||||||
|
require.NoError(r.t, err) |
||||||
|
|
||||||
|
d := r.data.GetScanDest() |
||||||
|
dv := make([]driver.Value, len(d)) |
||||||
|
for i, v := range d { |
||||||
|
dv[i] = v |
||||||
|
} |
||||||
|
r.rows.AddRow(dv...) |
||||||
|
|
||||||
|
return r |
||||||
|
} |
||||||
|
|
||||||
|
// Rows returns the *sqlmock.Rows object built.
|
||||||
|
func (r *mockResults[T]) Rows() *sqlmock.Rows { |
||||||
|
return r.rows |
||||||
|
} |
||||||
|
|
||||||
|
func TestCreateETag(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
v := createETag(nil, nil, nil) |
||||||
|
require.Equal(t, "d41d8cd98f00b204e9800998ecf8427e", v) |
||||||
|
} |
||||||
|
|
||||||
|
func TestGetCurrentUser(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
username, err := getCurrentUser(ctx) |
||||||
|
require.NotEmpty(t, username) |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
ctx = ctx.WithUser(nil) |
||||||
|
username, err = getCurrentUser(ctx) |
||||||
|
require.Empty(t, username) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorIs(t, err, ErrUserNotFoundInContext) |
||||||
|
} |
||||||
|
|
||||||
|
func TestPtrOr(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
p := ptrOr[*int]() |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Zero(t, *p) |
||||||
|
|
||||||
|
p = ptrOr[*int](nil, nil, nil, nil, nil, nil) |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Zero(t, *p) |
||||||
|
|
||||||
|
v := 42 |
||||||
|
v2 := 5 |
||||||
|
p = ptrOr(nil, nil, nil, &v, nil, &v2, nil, nil) |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Equal(t, v, *p) |
||||||
|
|
||||||
|
p = ptrOr(nil, nil, nil, &v) |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Equal(t, v, *p) |
||||||
|
} |
||||||
|
|
||||||
|
func TestSliceOr(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
p := sliceOr[[]int]() |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Len(t, p, 0) |
||||||
|
|
||||||
|
p = sliceOr[[]int](nil, nil, nil, nil) |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Len(t, p, 0) |
||||||
|
|
||||||
|
p = sliceOr([]int{}, []int{}, []int{}, []int{}) |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Len(t, p, 0) |
||||||
|
|
||||||
|
v := []int{1, 2} |
||||||
|
p = sliceOr([]int{}, nil, []int{}, v, nil, []int{}, []int{10}, nil) |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Equal(t, v, p) |
||||||
|
|
||||||
|
p = sliceOr([]int{}, nil, []int{}, v) |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Equal(t, v, p) |
||||||
|
} |
||||||
|
|
||||||
|
func TestMapOr(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
p := mapOr[map[string]int]() |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Len(t, p, 0) |
||||||
|
|
||||||
|
p = mapOr(nil, map[string]int(nil), nil, map[string]int{}, nil) |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Len(t, p, 0) |
||||||
|
|
||||||
|
v := map[string]int{"a": 0, "b": 1} |
||||||
|
v2 := map[string]int{"c": 2, "d": 3} |
||||||
|
|
||||||
|
p = mapOr(nil, map[string]int(nil), v, v2, nil, map[string]int{}, nil) |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Equal(t, v, p) |
||||||
|
|
||||||
|
p = mapOr(nil, map[string]int(nil), v) |
||||||
|
require.NotNil(t, p) |
||||||
|
require.Equal(t, v, p) |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
validTestTmpl = template.Must(template.New("test").Parse("nothing special")) |
||||||
|
invalidTestTmpl = template.New("no definition should fail to exec") |
||||||
|
errTest = errors.New("because of reasons") |
||||||
|
) |
||||||
|
|
||||||
|
// expectRows is a testing helper to keep mocks in sync when adding rows to a
|
||||||
|
// mocked SQL result. This is a helper to test `query` and `queryRow`.
|
||||||
|
type expectRows[T any] struct { |
||||||
|
*sqlmock.Rows |
||||||
|
ExpectedResults []T |
||||||
|
|
||||||
|
req *sqltemplateMocks.WithResults[T] |
||||||
|
} |
||||||
|
|
||||||
|
func newReturnsRow[T any](dbmock sqlmock.Sqlmock, req *sqltemplateMocks.WithResults[T]) *expectRows[T] { |
||||||
|
return &expectRows[T]{ |
||||||
|
Rows: dbmock.NewRows(nil), |
||||||
|
req: req, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Add adds a new value that should be returned by the `query` or `queryRow`
|
||||||
|
// operation.
|
||||||
|
func (r *expectRows[T]) Add(value T, err error) *expectRows[T] { |
||||||
|
r.req.EXPECT().GetScanDest().Return(nil).Once() |
||||||
|
r.req.EXPECT().Results().Return(value, err).Once() |
||||||
|
r.Rows.AddRow() |
||||||
|
r.ExpectedResults = append(r.ExpectedResults, value) |
||||||
|
|
||||||
|
return r |
||||||
|
} |
||||||
|
|
||||||
|
func TestQueryRow(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
t.Run("happy path", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
req := sqltemplateMocks.NewWithResults[int64](t) |
||||||
|
db, dbmock := newMockDBNopSQL(t) |
||||||
|
rows := newReturnsRow(dbmock, req) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
req.EXPECT().Validate().Return(nil).Once() |
||||||
|
req.EXPECT().GetArgs().Return(nil).Once() |
||||||
|
rows.Add(1, nil) |
||||||
|
dbmock.ExpectQuery("").WillReturnRows(rows.Rows) |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
res, err := queryRow(ctx, db, validTestTmpl, req) |
||||||
|
require.NoError(t, err) |
||||||
|
require.Equal(t, rows.ExpectedResults[0], res) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("invalid request", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
req := sqltemplateMocks.NewWithResults[int64](t) |
||||||
|
db, _ := newMockDBNopSQL(t) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
req.EXPECT().Validate().Return(errTest).Once() |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
res, err := queryRow(ctx, db, invalidTestTmpl, req) |
||||||
|
require.Zero(t, res) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorContains(t, err, "invalid request") |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("error executing template", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
req := sqltemplateMocks.NewWithResults[int64](t) |
||||||
|
db, _ := newMockDBNopSQL(t) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
req.EXPECT().Validate().Return(nil).Once() |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
res, err := queryRow(ctx, db, invalidTestTmpl, req) |
||||||
|
require.Zero(t, res) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorContains(t, err, "execute template") |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("error executing query", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
req := sqltemplateMocks.NewWithResults[int64](t) |
||||||
|
db, dbmock := newMockDBNopSQL(t) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
req.EXPECT().Validate().Return(nil).Once() |
||||||
|
req.EXPECT().GetArgs().Return(nil) |
||||||
|
req.EXPECT().GetScanDest().Return(nil).Maybe() |
||||||
|
dbmock.ExpectQuery("").WillReturnError(errTest) |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
res, err := queryRow(ctx, db, validTestTmpl, req) |
||||||
|
require.Zero(t, res) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorAs(t, err, new(SQLError)) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// scannerFunc is an adapter for the `scanner` interface.
|
||||||
|
type scannerFunc func(dest ...any) error |
||||||
|
|
||||||
|
func (f scannerFunc) Scan(dest ...any) error { |
||||||
|
return f(dest...) |
||||||
|
} |
||||||
|
|
||||||
|
func TestScanRow(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
const value int64 = 1 |
||||||
|
|
||||||
|
t.Run("happy path", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
req := sqltemplateMocks.NewWithResults[int64](t) |
||||||
|
sc := scannerFunc(func(dest ...any) error { |
||||||
|
return nil |
||||||
|
}) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
req.EXPECT().GetScanDest().Return(nil).Once() |
||||||
|
req.EXPECT().Results().Return(value, nil).Once() |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
res, err := scanRow(sc, req) |
||||||
|
require.NoError(t, err) |
||||||
|
require.Equal(t, value, res) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("scan error", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
req := sqltemplateMocks.NewWithResults[int64](t) |
||||||
|
sc := scannerFunc(func(dest ...any) error { |
||||||
|
return errTest |
||||||
|
}) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
req.EXPECT().GetScanDest().Return(nil).Once() |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
res, err := scanRow(sc, req) |
||||||
|
require.Zero(t, res) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorIs(t, err, errTest) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("results error", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
req := sqltemplateMocks.NewWithResults[int64](t) |
||||||
|
sc := scannerFunc(func(dest ...any) error { |
||||||
|
return nil |
||||||
|
}) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
req.EXPECT().GetScanDest().Return(nil).Once() |
||||||
|
req.EXPECT().Results().Return(0, errTest).Once() |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
res, err := scanRow(sc, req) |
||||||
|
require.Zero(t, res) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorIs(t, err, errTest) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestExec(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
t.Run("happy path", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
req := sqltemplateMocks.NewSQLTemplateIface(t) |
||||||
|
db, dbmock := newMockDBNopSQL(t) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
req.EXPECT().Validate().Return(nil).Once() |
||||||
|
req.EXPECT().GetArgs().Return(nil).Once() |
||||||
|
dbmock.ExpectExec("").WillReturnResult(sqlmock.NewResult(0, 0)) |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
res, err := exec(ctx, db, validTestTmpl, req) |
||||||
|
require.NoError(t, err) |
||||||
|
require.NotNil(t, res) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("invalid request", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
req := sqltemplateMocks.NewSQLTemplateIface(t) |
||||||
|
db, _ := newMockDBNopSQL(t) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
req.EXPECT().Validate().Return(errTest).Once() |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
res, err := exec(ctx, db, invalidTestTmpl, req) |
||||||
|
require.Nil(t, res) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorContains(t, err, "invalid request") |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("error executing template", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
req := sqltemplateMocks.NewSQLTemplateIface(t) |
||||||
|
db, _ := newMockDBNopSQL(t) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
req.EXPECT().Validate().Return(nil).Once() |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
res, err := exec(ctx, db, invalidTestTmpl, req) |
||||||
|
require.Nil(t, res) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorContains(t, err, "execute template") |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("error executing SQL", func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
// test declarations
|
||||||
|
ctx := testutil.NewDefaultTestContext(t) |
||||||
|
req := sqltemplateMocks.NewSQLTemplateIface(t) |
||||||
|
db, dbmock := newMockDBNopSQL(t) |
||||||
|
|
||||||
|
// setup expectations
|
||||||
|
req.EXPECT().Validate().Return(nil).Once() |
||||||
|
req.EXPECT().GetArgs().Return(nil) |
||||||
|
dbmock.ExpectExec("").WillReturnError(errTest) |
||||||
|
|
||||||
|
// execute and assert
|
||||||
|
res, err := exec(ctx, db, validTestTmpl, req) |
||||||
|
require.Nil(t, res) |
||||||
|
require.Error(t, err) |
||||||
|
require.ErrorAs(t, err, new(SQLError)) |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
package sqlstash |
||||||
|
|
||||||
|
import "github.com/grafana/grafana/pkg/services/store/entity" |
||||||
|
|
||||||
|
// validateEntity validates a fully loaded *entity.Entity model, and should be
|
||||||
|
// used before storing an entity to the database and before returning it to the
|
||||||
|
// user.
|
||||||
|
func validateEntity(*entity.Entity) error { |
||||||
|
return nil // TODO
|
||||||
|
} |
||||||
|
|
||||||
|
// validateLabels validates the given map of label names to their values.
|
||||||
|
func validateLabels(map[string]string) error { |
||||||
|
// this should be called by validateEntity
|
||||||
|
return nil // TODO
|
||||||
|
} |
||||||
|
|
||||||
|
// validateFields validates the given map of fields names to their values.
|
||||||
|
func validateFields(map[string]string) error { |
||||||
|
// this should be called by validateEntity
|
||||||
|
return nil // TODO
|
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
package sqlstash |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
) |
||||||
|
|
||||||
|
func TestValidateEntity(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
err := validateEntity(newEmptyEntity()) |
||||||
|
require.NoError(t, err) |
||||||
|
} |
||||||
|
|
||||||
|
func TestValidateLabels(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
err := validateLabels(map[string]string{}) |
||||||
|
require.NoError(t, err) |
||||||
|
} |
||||||
|
|
||||||
|
func TestValidateFields(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
err := validateFields(map[string]string{}) |
||||||
|
require.NoError(t, err) |
||||||
|
} |
||||||
|
|
||||||
|
// Silence the `unused` linter until we implement and use these validations.
|
||||||
|
var ( |
||||||
|
_ = validateEntity |
||||||
|
_ = validateLabels |
||||||
|
_ = validateFields |
||||||
|
) |
@ -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) |
||||||
|
} |
Loading…
Reference in new issue