components-contrib/state/mysql/mysql_test.go

932 lines
23 KiB
Go

/*
Copyright 2021 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mysql
import (
"database/sql"
"encoding/base64"
"encoding/json"
"errors"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/dapr/components-contrib/metadata"
"github.com/dapr/components-contrib/state"
"github.com/dapr/components-contrib/state/utils"
"github.com/dapr/kit/logger"
)
const (
fakeConnectionString = "not a real connection"
keyTableName = "tableName"
keyConnectionString = "connectionString"
keySchemaName = "schemaName"
)
func TestEnsureStateSchemaHandlesShortConnectionString(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
m.mySQL.schemaName = "theSchema"
m.mySQL.connectionString = "theUser:thePassword@/"
rows := sqlmock.NewRows([]string{"exists"}).AddRow(1)
m.mock1.ExpectQuery("SELECT EXISTS").WillReturnRows(rows)
// Act
m.mySQL.ensureStateSchema(t.Context())
// Assert
assert.Equal(t, "theUser:thePassword@/theSchema", m.mySQL.connectionString)
}
func TestFinishInitHandlesSchemaExistsError(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
expectedErr := errors.New("existsError")
m.mock1.ExpectQuery("SELECT EXISTS").WillReturnError(expectedErr)
// Act
actualErr := m.mySQL.finishInit(t.Context(), m.mySQL.db)
// Assert
require.Error(t, actualErr, "now error returned")
assert.Equal(t, "existsError", actualErr.Error(), "wrong error")
}
func TestFinishInitHandlesDatabaseCreateError(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
rows := sqlmock.NewRows([]string{"exists"}).AddRow(0)
m.mock1.ExpectQuery("SELECT EXISTS").WillReturnRows(rows)
expectedErr := errors.New("createDatabaseError")
m.mock1.ExpectExec("CREATE DATABASE").WillReturnError(expectedErr)
// Act
actualErr := m.mySQL.finishInit(t.Context(), m.mySQL.db)
// Assert
require.Error(t, actualErr, "now error returned")
assert.Equal(t, "createDatabaseError", actualErr.Error(), "wrong error")
}
func TestFinishInitHandlesPingError(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
m.factory.openCount = 1
// See if Schema exists
rows := sqlmock.NewRows([]string{"exists"}).AddRow(1)
m.mock1.ExpectQuery("SELECT EXISTS").WillReturnRows(rows)
m.mock1.ExpectClose()
expectedErr := errors.New("pingError")
m.mock2.ExpectPing().WillReturnError(expectedErr)
// Act
actualErr := m.mySQL.finishInit(t.Context(), m.mySQL.db)
// Assert
require.Error(t, actualErr, "now error returned")
assert.Equal(t, "pingError", actualErr.Error(), "wrong error")
}
// Verifies that finishInit can handle an error from its call to
// ensureStateTable. The code should not attempt to create the table. Because
// there is no m.mock1.ExpectExec if the code attempts to execute the create
// table commnad this test will fail.
func TestFinishInitHandlesTableExistsError(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
m.factory.openCount = 1
// See if Schema exists
rows := sqlmock.NewRows([]string{"exists"}).AddRow(1)
m.mock1.ExpectQuery("SELECT EXISTS").WillReturnRows(rows)
m.mock1.ExpectClose()
// Execute use command
m.mock2.ExpectPing()
m.mock2.ExpectQuery("SELECT EXISTS").WillReturnError(errors.New("tableExistsError"))
// Act
err := m.mySQL.finishInit(t.Context(), m.mySQL.db)
// Assert
require.Error(t, err, "no error returned")
assert.Equal(t, "tableExistsError", err.Error(), "tableExists did not return err")
}
func TestClosingDatabaseTwiceReturnsNil(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
m.mySQL.Close()
m.mySQL.db = nil
// Act
err := m.mySQL.Close()
// Assert
require.NoError(t, err, "error returned")
}
func TestMultiCommitSetsAndDeletes(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
m.mock1.ExpectBegin()
m.mock1.ExpectExec("REPLACE INTO").WillReturnResult(sqlmock.NewResult(0, 1))
m.mock1.ExpectExec("DELETE FROM").WillReturnResult(sqlmock.NewResult(0, 1))
m.mock1.ExpectCommit()
request := state.TransactionalStateRequest{
Operations: []state.TransactionalStateOperation{
createSetRequest(),
createDeleteRequest(),
},
Metadata: map[string]string{},
}
// Act
err := m.mySQL.Multi(t.Context(), &request)
// Assert
require.NoError(t, err, "error returned")
}
func TestSetHandlesOptionsError(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
request := createSetRequest()
request.Options.Consistency = "Invalid"
// Act
err := m.mySQL.Set(t.Context(), &request)
// Assert
require.Error(t, err)
}
func TestSetHandlesNoKey(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
request := createSetRequest()
request.Key = ""
// Act
err := m.mySQL.Set(t.Context(), &request)
// Assert
require.Error(t, err)
assert.Equal(t, "missing key in set operation", err.Error(), "wrong error returned")
}
func TestSetHandlesUpdate(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
m.mock1.ExpectExec("UPDATE state").WillReturnResult(sqlmock.NewResult(1, 1))
eTag := "946af56e"
request := createSetRequest()
request.ETag = &eTag
// Act
err := m.mySQL.Set(t.Context(), &request)
// Assert
require.NoError(t, err)
}
func TestSetHandlesErr(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
t.Run("error occurs when insert", func(t *testing.T) {
m.mock1.ExpectExec("REPLACE INTO state").WillReturnError(errors.New("error"))
request := createSetRequest()
// Act
err := m.mySQL.Set(t.Context(), &request)
// Assert
require.Error(t, err)
assert.Equal(t, "error", err.Error())
})
t.Run("insert on conflict", func(t *testing.T) {
m.mock1.ExpectExec("REPLACE INTO state").WillReturnResult(sqlmock.NewResult(1, 2))
request := createSetRequest()
// Act
err := m.mySQL.Set(t.Context(), &request)
// Assert
require.NoError(t, err)
})
t.Run("no rows effected error", func(t *testing.T) {
m.mock1.ExpectExec("UPDATE state").WillReturnResult(sqlmock.NewResult(1, 0))
eTag := "illegal etag"
request := createSetRequest()
request.ETag = &eTag
// Act
err := m.mySQL.Set(t.Context(), &request)
// Assert
require.Error(t, err)
assert.IsType(t, &state.ETagError{}, err)
assert.Equal(t, state.ETagMismatch, err.(*state.ETagError).Kind())
})
}
// Verifies that MySQL passes through to myDBAccess.
func TestMySQLDeleteHandlesNoKey(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
request := createDeleteRequest()
request.Key = ""
// Act
err := m.mySQL.Delete(t.Context(), &request)
// Asset
require.Error(t, err)
assert.Equal(t, "missing key in delete operation", err.Error(), "wrong error returned")
}
func TestDeleteWithETag(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
m.mock1.ExpectExec("DELETE FROM").WillReturnResult(sqlmock.NewResult(0, 1))
eTag := "946af562"
request := createDeleteRequest()
request.ETag = &eTag
// Act
err := m.mySQL.Delete(t.Context(), &request)
// Assert
require.NoError(t, err)
}
func TestDeleteWithErr(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
t.Run("error occurs when delete", func(t *testing.T) {
m.mock1.ExpectExec("DELETE FROM").WillReturnError(errors.New("error"))
request := createDeleteRequest()
// Act
err := m.mySQL.Delete(t.Context(), &request)
// Assert
require.Error(t, err)
assert.Equal(t, "error", err.Error())
})
t.Run("etag mismatch", func(t *testing.T) {
m.mock1.ExpectExec("DELETE FROM").WillReturnResult(sqlmock.NewResult(0, 0))
eTag := "946af563"
request := createDeleteRequest()
request.ETag = &eTag
// Act
err := m.mySQL.Delete(t.Context(), &request)
// Assert
require.Error(t, err)
assert.IsType(t, &state.ETagError{}, err)
assert.Equal(t, state.ETagMismatch, err.(*state.ETagError).Kind())
})
}
func TestGetHandlesNoRows(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
m.mock1.ExpectQuery("SELECT id").WillReturnRows(sqlmock.NewRows([]string{"UnitTest", "value", "eTag"}))
request := &state.GetRequest{
Key: "UnitTest",
}
// Act
response, err := m.mySQL.Get(t.Context(), request)
// Assert
require.NoError(t, err, "returned error")
assert.NotNil(t, response, "did not return empty response")
}
func TestGetHandlesNoKey(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
request := &state.GetRequest{
Key: "",
}
// Act
response, err := m.mySQL.Get(t.Context(), request)
// Assert
require.Error(t, err, "returned error")
assert.Equal(t, "missing key in get operation", err.Error(), "wrong error returned")
assert.Nil(t, response, "returned response")
}
func TestGetHandlesGenericError(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
m.mock1.ExpectQuery("").WillReturnError(errors.New("generic error"))
request := &state.GetRequest{
Key: "UnitTest",
}
// Act
response, err := m.mySQL.Get(t.Context(), request)
// Assert
require.Error(t, err)
assert.Nil(t, response)
}
func TestGetSucceeds(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
t.Run("has json type", func(t *testing.T) {
rows := sqlmock.NewRows([]string{"id", "value", "eTag", "isbinary", "expiredate"}).AddRow("UnitTest", "{}", "946af56e", false, "")
m.mock1.ExpectQuery(`SELECT id, value, eTag, isbinary, IFNULL\(expiredate, ""\) FROM state WHERE id = ?`).WillReturnRows(rows)
request := &state.GetRequest{
Key: "UnitTest",
}
// Act
response, err := m.mySQL.Get(t.Context(), request)
// Assert
require.NoError(t, err)
assert.NotNil(t, response)
assert.Equal(t, "{}", string(response.Data))
assert.NotContains(t, response.Metadata, state.GetRespMetaKeyTTLExpireTime)
})
t.Run("has binary type and expiredate", func(t *testing.T) {
now := time.UnixMilli(20001).UTC()
value, _ := utils.Marshal(base64.StdEncoding.EncodeToString([]byte("abcdefg")), json.Marshal)
rows := sqlmock.NewRows([]string{"id", "value", "eTag", "isbinary", "expiredate"}).AddRow("UnitTest", value, "946af56e", true, now.Format(time.DateTime))
m.mock1.ExpectQuery(`SELECT id, value, eTag, isbinary, IFNULL\(expiredate, ""\) FROM state WHERE id = ?`).WillReturnRows(rows)
request := &state.GetRequest{
Key: "UnitTest",
}
// Act
response, err := m.mySQL.Get(t.Context(), request)
// Assert
require.NoError(t, err)
assert.NotNil(t, response)
assert.Equal(t, "abcdefg", string(response.Data))
assert.Contains(t, response.Metadata, state.GetRespMetaKeyTTLExpireTime)
assert.Equal(t, "1970-01-01T00:00:20Z", response.Metadata[state.GetRespMetaKeyTTLExpireTime])
})
}
// Verifies that the correct query is executed to test if the table
// already exists in the database or not.
func TestTableExists(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
// Return a result that indicates that the table already exists in the
// database.
rows := sqlmock.NewRows([]string{"exists"}).AddRow(1)
m.mock1.ExpectQuery("SELECT EXISTS").WillReturnRows(rows)
// Act
actual, err := tableExists(t.Context(), m.mySQL.db, "dapr_state_store", "store", 10*time.Second)
// Assert
require.NoError(t, err, `error was returned`)
assert.True(t, actual, `table does not exists`)
}
// Verifies that the code returns an error if the create table command fails.
func TestEnsureStateTableHandlesCreateTableError(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
rows := sqlmock.NewRows([]string{"exists"}).AddRow(0)
m.mock1.ExpectQuery("SELECT EXISTS").WillReturnRows(rows)
m.mock1.ExpectExec("CREATE TABLE").WillReturnError(errors.New("CreateTableError"))
// Act
err := m.mySQL.ensureStateTable(t.Context(), "dapr_state_store", "state")
// Assert
require.Error(t, err, "no error returned")
assert.Equal(t, "CreateTableError", err.Error(), "wrong error returned")
}
// Verifies that ensureStateTable creates the table when tableExists returns
// false.
func TestEnsureStateTableCreatesTable(t *testing.T) {
// Arrange
m, _ := mockDatabase(t)
defer m.mySQL.Close()
// Return exists = 0 when Select Exists is called to indicate the table
// does not already exist.
rows := sqlmock.NewRows([]string{"exists"}).AddRow(0)
m.mock1.ExpectQuery("SELECT EXISTS").WillReturnRows(rows)
m.mock1.ExpectExec("CREATE TABLE").WillReturnResult(sqlmock.NewResult(1, 1))
rows = sqlmock.NewRows([]string{"exists"}).AddRow(1)
m.mock1.ExpectQuery("SELECT count(/*)").WillReturnRows(rows)
m.mock1.ExpectExec("CREATE PROCEDURE").WillReturnResult(sqlmock.NewResult(1, 1))
// Act
err := m.mySQL.ensureStateTable(t.Context(), "dapr_state_store", "state")
// Assert
require.NoError(t, err)
}
// Verify that the call to MySQL init get passed through
// to the DbAccess instance.
func TestInitReturnsErrorOnNoConnectionString(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
metadata := &state.Metadata{
Base: metadata.Base{Properties: map[string]string{keyConnectionString: ""}},
}
// Act
err := m.mySQL.Init(t.Context(), *metadata)
// Assert
require.Error(t, err)
assert.Equal(t, defaultTableName, m.mySQL.tableName, "table name did not default")
}
func TestInitReturnsErrorOnFailOpen(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
metadata := &state.Metadata{
Base: metadata.Base{Properties: map[string]string{keyConnectionString: fakeConnectionString}},
}
m.mock1.ExpectQuery("SELECT EXISTS").WillReturnError(sql.ErrConnDone)
// Act
err := m.mySQL.Init(t.Context(), *metadata)
// Assert
require.Error(t, err)
}
func TestInitHandlesRegisterTLSConfigError(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
m.factory.registerErr = errors.New("registerTLSConfigError")
metadata := &state.Metadata{
Base: metadata.Base{
Properties: map[string]string{
keyPemPath: "./ssl.pem",
keyTableName: "stateStore",
keyConnectionString: fakeConnectionString,
},
},
}
// Act
err := m.mySQL.Init(t.Context(), *metadata)
// Assert
require.Error(t, err)
assert.Equal(t, "registerTLSConfigError", err.Error(), "wrong error")
}
func TestInitSetsTableName(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
metadata := &state.Metadata{
Base: metadata.Base{Properties: map[string]string{keyConnectionString: "", keyTableName: "stateStore"}},
}
// Act
err := m.mySQL.Init(t.Context(), *metadata)
// Assert
require.Error(t, err)
assert.Equal(t, "stateStore", m.mySQL.tableName, "table name did not default")
}
func TestInitInvalidTableName(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
metadata := &state.Metadata{
Base: metadata.Base{Properties: map[string]string{keyConnectionString: "", keyTableName: "🙃"}},
}
// Act
err := m.mySQL.Init(t.Context(), *metadata)
// Assert
require.ErrorContains(t, err, "table name '🙃' is not valid")
}
func TestInitSetsSchemaName(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
metadata := &state.Metadata{
Base: metadata.Base{Properties: map[string]string{keyConnectionString: "", keySchemaName: "stateStoreSchema"}},
}
// Act
err := m.mySQL.Init(t.Context(), *metadata)
// Assert
require.Error(t, err)
assert.Equal(t, "stateStoreSchema", m.mySQL.schemaName, "table name did not default")
}
func TestInitInvalidSchemaName(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
metadata := &state.Metadata{
Base: metadata.Base{Properties: map[string]string{keyConnectionString: "", keySchemaName: "?"}},
}
// Act
err := m.mySQL.Init(t.Context(), *metadata)
// Assert
require.ErrorContains(t, err, "schema name '?' is not valid")
}
func TestMultiWithNoRequestsDoesNothing(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
var ops []state.TransactionalStateOperation
// no operations expected
m.mock1.ExpectBegin()
m.mock1.ExpectCommit()
// Act
err := m.mySQL.Multi(t.Context(), &state.TransactionalStateRequest{
Operations: ops,
})
// Assert
require.NoError(t, err)
}
func TestClosingMySQLWithNilDba(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
m.mySQL.Close()
m.mySQL.db = nil
// Act
err := m.mySQL.Close()
// Assert
require.NoError(t, err)
}
func TestValidSetRequest(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
t.Run("single op", func(t *testing.T) {
ops := []state.TransactionalStateOperation{
createSetRequest(),
}
m.mock1.ExpectExec("REPLACE INTO").WillReturnResult(sqlmock.NewResult(0, 1))
// Act
err := m.mySQL.Multi(t.Context(), &state.TransactionalStateRequest{
Operations: ops,
})
// Assert
require.NoError(t, err)
})
t.Run("multiple ops", func(t *testing.T) {
ops := []state.TransactionalStateOperation{
createSetRequest(),
createSetRequest(),
}
m.mock1.ExpectBegin()
m.mock1.ExpectExec("REPLACE INTO").WillReturnResult(sqlmock.NewResult(0, 1))
m.mock1.ExpectExec("REPLACE INTO").WillReturnResult(sqlmock.NewResult(0, 1))
m.mock1.ExpectCommit()
// Act
err := m.mySQL.Multi(t.Context(), &state.TransactionalStateRequest{
Operations: ops,
})
// Assert
require.NoError(t, err)
})
}
func TestInvalidMultiSetRequestNoKey(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
ops := []state.TransactionalStateOperation{
state.SetRequest{
// empty key is not valid for Upsert operation
Key: "",
Value: "value1",
},
}
// Act
err := m.mySQL.Multi(t.Context(), &state.TransactionalStateRequest{
Operations: ops,
})
// Assert
require.Error(t, err)
}
func TestValidMultiDeleteRequest(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
t.Run("single op", func(t *testing.T) {
ops := []state.TransactionalStateOperation{
createDeleteRequest(),
}
m.mock1.ExpectExec("DELETE FROM").WillReturnResult(sqlmock.NewResult(0, 1))
// Act
err := m.mySQL.Multi(t.Context(), &state.TransactionalStateRequest{
Operations: ops,
})
// Assert
require.NoError(t, err)
})
t.Run("multiple ops", func(t *testing.T) {
ops := []state.TransactionalStateOperation{
createDeleteRequest(),
createDeleteRequest(),
}
m.mock1.ExpectBegin()
m.mock1.ExpectExec("DELETE FROM").WillReturnResult(sqlmock.NewResult(0, 1))
m.mock1.ExpectExec("DELETE FROM").WillReturnResult(sqlmock.NewResult(0, 1))
m.mock1.ExpectCommit()
// Act
err := m.mySQL.Multi(t.Context(), &state.TransactionalStateRequest{
Operations: ops,
})
// Assert
require.NoError(t, err)
})
}
func TestInvalidMultiDeleteRequestNoKey(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
ops := []state.TransactionalStateOperation{
state.DeleteRequest{
// empty key is not valid for Delete operation
Key: "",
},
}
// Act
err := m.mySQL.Multi(t.Context(), &state.TransactionalStateRequest{
Operations: ops,
})
// Assert
require.Error(t, err)
}
func TestMultiOperationOrder(t *testing.T) {
// Arrange
t.Parallel()
m, _ := mockDatabase(t)
// In a transaction with multiple operations,
// the order of operations must be respected.
ops := []state.TransactionalStateOperation{
state.SetRequest{Key: "k1", Value: "v1"},
state.DeleteRequest{Key: "k1"},
state.SetRequest{Key: "k2", Value: "v2"},
}
// expected to run the operations in sequence
m.mock1.ExpectBegin()
m.mock1.ExpectExec("REPLACE INTO").WillReturnResult(sqlmock.NewResult(0, 1))
m.mock1.ExpectExec("DELETE FROM").WithArgs("k1").WillReturnResult(sqlmock.NewResult(0, 1))
m.mock1.ExpectExec("REPLACE INTO").WillReturnResult(sqlmock.NewResult(0, 1))
m.mock1.ExpectCommit()
// Act
err := m.mySQL.Multi(t.Context(), &state.TransactionalStateRequest{
Operations: ops,
})
// Assert
require.NoError(t, err)
err = m.mock1.ExpectationsWereMet()
require.NoError(t, err)
}
func createSetRequest() state.SetRequest {
return state.SetRequest{
Key: randomKey(),
Value: randomJSON(),
}
}
func createDeleteRequest() state.DeleteRequest {
return state.DeleteRequest{
Key: randomKey(),
}
}
type mocks struct {
mySQL *MySQL
db *sql.DB
mock1 sqlmock.Sqlmock
mock2 sqlmock.Sqlmock
factory *fakeMySQLFactory
}
// Returns a MySQL and an extra sql.DB for test that have to close the first
// db returned in the MySQL instance.
func mockDatabase(t *testing.T) (*mocks, error) {
db1, mock1, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
db2, mock2, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
fake := newFakeMySQLFactory(db1, db2, nil, nil)
logger := logger.NewLogger("test")
mys := newMySQLStateStore(logger, fake)
mys.db = db1
mys.tableName = "state"
mys.connectionString = "theUser:thePassword@/theDBName"
return &mocks{
db: db2,
mySQL: mys,
mock1: mock1,
mock2: mock2,
factory: fake,
}, err
}
// Fake for unit testings the part that uses the factory
// to open the database and register the pem file.
type fakeMySQLFactory struct {
openCount int
openErr error
registerErr error
db1 *sql.DB
db2 *sql.DB
}
// startCount is used to set openCount. openCount is used to determine
// which fake to open. In the normal flow of code this would go from 1
// to 2. However, in some tests they assume the factory open has already
// been called so for those test set startCount to 1.
func newFakeMySQLFactory(db1 *sql.DB, db2 *sql.DB, registerErr, openErr error) *fakeMySQLFactory {
return &fakeMySQLFactory{
db1: db1,
db2: db2,
openErr: openErr,
registerErr: registerErr,
}
}
func (f *fakeMySQLFactory) Open(connectionString string) (*sql.DB, error) {
f.openCount++
if f.openCount == 1 {
return f.db1, f.openErr
}
return f.db2, f.openErr
}
func (f *fakeMySQLFactory) RegisterTLSConfig(pemPath string) error {
return f.registerErr
}
func TestValidIdentifier(t *testing.T) {
tests := []struct {
name string
arg string
want bool
}{
{name: "empty string", arg: "", want: false},
{name: "valid characters only", arg: "acz_039_AZS", want: true},
{name: "invalid ASCII characters 1", arg: "$", want: false},
{name: "invalid ASCII characters 2", arg: "*", want: false},
{name: "invalid ASCII characters 3", arg: "hello world", want: false},
{name: "non-ASCII characters", arg: "🙃", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := validIdentifier(tt.arg); got != tt.want {
t.Errorf("validIdentifier() = %v, want %v", got, tt.want)
}
})
}
}