Added tests for metadata properties schemaName/tableName

Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
ItalyPaleAle 2022-11-01 16:27:16 +00:00
parent f240581313
commit 576c62b0f1
9 changed files with 190 additions and 33 deletions

View File

@ -249,7 +249,7 @@ jobs:
set +e
gotestsum --jsonfile ${{ env.TEST_OUTPUT_FILE_PREFIX }}_certification.json \
--junitfile ${{ env.TEST_OUTPUT_FILE_PREFIX }}_certification.xml --format standard-quiet -- \
-coverprofile=cover.out -covermode=set -coverpkg=${{ env.SOURCE_PATH }}
-coverprofile=cover.out -covermode=set -tags=certtests -coverpkg=${{ env.SOURCE_PATH }}
status=$?
echo "Completed certification tests for ${{ matrix.component }} ... "
if test $status -ne 0; then

View File

@ -13,8 +13,8 @@ run:
tests: true
# list of build tags, all linters use it. Default is empty list.
#build-tags:
# - mytag
build-tags:
- certtests
# which dirs to skip: they won't be analyzed;
# can use regexp here: generated.*, regexp is applied on full path;

View File

@ -75,8 +75,6 @@ type MySQL struct {
// Instance of the database to issue commands to
db *sql.DB
features []state.Feature
// Logger used in a functions
logger logger.Logger
@ -97,9 +95,8 @@ func newMySQLStateStore(logger logger.Logger, factory iMySQLFactory) *MySQL {
// Store the provided logger and return the object. The rest of the
// properties will be populated in the Init function
return &MySQL{
features: []state.Feature{state.FeatureETag, state.FeatureTransactional},
logger: logger,
factory: factory,
logger: logger,
factory: factory,
}
}
@ -163,7 +160,15 @@ func (m *MySQL) Init(metadata state.Metadata) error {
// Features returns the features available in this state store.
func (m *MySQL) Features() []state.Feature {
return m.features
return []state.Feature{state.FeatureETag, state.FeatureTransactional}
}
// Ping the database.
func (m *MySQL) Ping() error {
if m.db == nil {
return sql.ErrConnDone
}
return m.db.Ping()
}
// Separated out to make this portion of code testable.
@ -176,7 +181,7 @@ func (m *MySQL) finishInit(db *sql.DB) error {
return err
}
err = m.db.Ping()
err = m.Ping()
if err != nil {
m.logger.Error(err)
return err
@ -258,23 +263,23 @@ func (m *MySQL) ensureStateTable(stateTableName string) error {
}
func schemaExists(db *sql.DB, schemaName string) (bool, error) {
// Returns 1 or 0 as a string if the table exists or not
exists := ""
// Returns 1 or 0 if the table exists or not
var exists int
query := `SELECT EXISTS (
SELECT SCHEMA_NAME FROM information_schema.schemata WHERE SCHEMA_NAME = ?
) AS 'exists'`
err := db.QueryRow(query, schemaName).Scan(&exists)
return exists == "1", err
return exists == 1, err
}
func tableExists(db *sql.DB, tableName string) (bool, error) {
// Returns 1 or 0 as a string if the table exists or not
exists := ""
// Returns 1 or 0 if the table exists or not
var exists int
query := `SELECT EXISTS (
SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_NAME = ?
) AS 'exists'`
err := db.QueryRow(query, tableName).Scan(&exists)
return exists == "1", err
return exists == 1, err
}
// Delete removes an entity from the store
@ -612,11 +617,13 @@ func (m *MySQL) BulkGet(req []state.GetRequest) (bool, []state.BulkGetResponse,
// Close implements io.Closer.
func (m *MySQL) Close() error {
if m.db != nil {
return m.db.Close()
if m.db == nil {
return nil
}
return nil
err := m.db.Close()
m.db = nil
return err
}
// Validates an identifier, such as table or DB name.

View File

@ -0,0 +1,37 @@
//go:build certtests
// +build certtests
/*
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.
*/
// This file is only built for certification tests and it exposes internal properties of the object
package mysql
import (
"database/sql"
)
// GetConnection returns the database connection.
func (m *MySQL) GetConnection() *sql.DB {
return m.db
}
// SchemaName returns the value of the schemaName property.
func (m *MySQL) SchemaName() string {
return m.schemaName
}
// TableName returns the value of the tableName property.
func (m *MySQL) TableName() string {
return m.tableName
}

View File

@ -29,4 +29,15 @@ d. Get and validate eTag, which should not have changed.
## Transactions
Upsert in Multi function, using 3 keys with updating values and TTL for 2 of the keys, down in the order.
1. Upsert in Multi function, using 3 keys with updating values and TTL for 2 of the keys, down in the order.
## Close component
1. Ensure the database connection is closed when the component is closed.
## Metadata options
1. Without `schemaName`, check that the default one is used
2. Without `tableName`, check that the default one is used
3. Instantiate a component with a custom `schemaName` and validate it's used
4. Instantiate a component with a custom `tableName` and validate it's used

View File

@ -8,5 +8,3 @@ spec:
metadata:
- name: connectionString
value: "dapr:example@tcp(localhost:3307)/"
- name: schemaName
value: dapr_state_store

View File

@ -8,5 +8,3 @@ spec:
metadata:
- name: connectionString
value: "dapr:example@tcp(localhost:3306)/?allowNativePasswords=true"
- name: schemaName
value: dapr_state_store

View File

@ -1,6 +1,6 @@
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: keyvaultconfig
name: testconfig
spec:
features:

View File

@ -14,22 +14,26 @@ limitations under the License.
package main
import (
"database/sql"
"errors"
"strconv"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/dapr/components-contrib/metadata"
"github.com/dapr/components-contrib/state"
state_mysql "github.com/dapr/components-contrib/state/mysql"
stateMysql "github.com/dapr/components-contrib/state/mysql"
"github.com/dapr/components-contrib/tests/certification/embedded"
"github.com/dapr/components-contrib/tests/certification/flow"
"github.com/dapr/components-contrib/tests/certification/flow/dockercompose"
"github.com/dapr/components-contrib/tests/certification/flow/sidecar"
state_loader "github.com/dapr/dapr/pkg/components/state"
stateLoader "github.com/dapr/dapr/pkg/components/state"
"github.com/dapr/dapr/pkg/runtime"
dapr_testing "github.com/dapr/dapr/pkg/testing"
daprTesting "github.com/dapr/dapr/pkg/testing"
daprClient "github.com/dapr/go-sdk/client"
"github.com/dapr/kit/logger"
)
@ -38,19 +42,29 @@ const (
sidecarNamePrefix = "mysql-sidecar-"
dockerComposeYAML = "docker-compose.yaml"
certificationTestPrefix = "stable-certification-"
defaultSchemaName = "dapr_state_store"
defaultTableName = "state"
mysqlConnString = "root:root@tcp(localhost:3306)/?allowNativePasswords=true"
mariadbConnString = "root:root@tcp(localhost:3307)/"
)
func TestMySQL(t *testing.T) {
log := logger.NewLogger("dapr.components")
ports, err := dapr_testing.GetFreePorts(1)
ports, err := daprTesting.GetFreePorts(1)
require.NoError(t, err)
currentGrpcPort := ports[0]
stateRegistry := state_loader.NewRegistry()
registeredComponents := [2]*stateMysql.MySQL{}
stateRegistry := stateLoader.NewRegistry()
stateRegistry.Logger = log
stateRegistry.RegisterComponent(func(l logger.Logger) state.Store {
return state_mysql.NewMySQLStateStore(log)
n := atomic.Int32{}
stateRegistry.RegisterComponent(func(_ logger.Logger) state.Store {
component := stateMysql.NewMySQLStateStore(log).(*stateMysql.MySQL)
registeredComponents[n.Add(1)-1] = component
return component
}, "mysql")
basicTest := func(stateStoreName string) func(ctx flow.Context) error {
@ -177,9 +191,11 @@ func TestMySQL(t *testing.T) {
require.NoError(t, err)
resp1, err := client.GetState(ctx, stateStoreName, "reqKey1", nil)
require.NoError(t, err)
assert.Equal(t, "reqVal101", string(resp1.Value))
resp3, err := client.GetState(ctx, stateStoreName, "reqKey3", nil)
require.NoError(t, err)
assert.Equal(t, "reqVal103", string(resp3.Value))
return nil
}
@ -235,9 +251,90 @@ func TestMySQL(t *testing.T) {
}
}
// checks that the connection is closed when the component is closed
closeTest := func(idx int) func(ctx flow.Context) error {
return func(ctx flow.Context) (err error) {
component := registeredComponents[idx]
// Check connection is active
err = component.Ping()
require.NoError(t, err)
// Close the component
err = component.Close()
require.NoError(t, err)
// Ensure the connection is closed
err = component.Ping()
require.Error(t, err)
assert.Truef(t, errors.Is(err, sql.ErrConnDone), "expected sql.ErrConnDone but got %v", err)
return nil
}
}
// checks that metadata options schemaName and tableName behave correctly
metadataTest := func(connString string, schemaName string, tableName string) func(ctx flow.Context) error {
return func(ctx flow.Context) (err error) {
properties := map[string]string{
"connectionString": connString,
}
// Check if schemaName and tableName are set to custom values
if schemaName != "" {
properties["schemaName"] = schemaName
} else {
schemaName = defaultSchemaName
}
if tableName != "" {
properties["tableName"] = tableName
} else {
tableName = defaultTableName
}
// Init the component
component := stateMysql.NewMySQLStateStore(log).(*stateMysql.MySQL)
component.Init(state.Metadata{
Base: metadata.Base{
Properties: properties,
},
})
// Check connection is active
err = component.Ping()
require.NoError(t, err)
var exists int
conn := component.GetConnection()
require.NotNil(t, conn)
// Check that the database exists
query := `SELECT EXISTS (
SELECT SCHEMA_NAME FROM information_schema.schemata WHERE SCHEMA_NAME = ?
) AS 'exists'`
err = conn.QueryRow(query, schemaName).Scan(&exists)
require.NoError(t, err)
assert.Equal(t, 1, exists)
// Check that the table exists
query = `SELECT EXISTS (
SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_NAME = ?
) AS 'exists'`
err = conn.QueryRow(query, tableName).Scan(&exists)
require.NoError(t, err)
assert.Equal(t, 1, exists)
// Close the component
err = component.Close()
require.NoError(t, err)
return nil
}
}
flow.New(t, "Run tests").
Step(dockercompose.Run("db", dockerComposeYAML)).
Step("wait for component to start", flow.Sleep(20*time.Second)).
Step("wait for component to start", flow.Sleep(30*time.Second)).
Step(sidecar.Run(sidecarNamePrefix+"dockerDefault",
embedded.WithoutApp(),
embedded.WithDaprGRPCPort(currentGrpcPort),
@ -264,6 +361,15 @@ func TestMySQL(t *testing.T) {
Step("start mariadb", dockercompose.Start("db", dockerComposeYAML, "mariadb")).
Step("wait for component to start", flow.Sleep(10*time.Second)).
Step("Run connection test on mariadb", testGetAfterDBRestart("mariadb")).
// Test closing the connection
// We don't know exactly which database is which (since init order isn't deterministic), so we'll just close both
Step("Close database connection 1", closeTest(0)).
Step("Close database connection 2", closeTest(1)).
// Metadata
Step("Default schemaName and tableName on mysql", metadataTest(mysqlConnString, "", "")).
Step("Custom schemaName and tableName on mysql", metadataTest(mysqlConnString, "mydaprdb", "mytable")).
Step("Default schemaName and tableName on mariadb", metadataTest(mariadbConnString, "", "")).
Step("Custom schemaName and tableName on mariadb", metadataTest(mariadbConnString, "mydaprdb", "mytable")).
// Run tests
Run()
}