2025-01-17 14:34:48 +00:00
/ *
Copyright 2023 SUSE LLC
Adapted from client - go , Copyright 2014 The Kubernetes Authors .
* /
package store
// Mocks for this test are generated with the following command.
2025-02-05 09:05:52 +00:00
//go:generate mockgen --build_flags=--mod=mod -package store -destination ./db_mocks_test.go github.com/rancher/steve/pkg/sqlcache/db Rows,Client
//go:generate mockgen --build_flags=--mod=mod -package store -destination ./transaction_mocks_test.go -mock_names Client=MockTXClient github.com/rancher/steve/pkg/sqlcache/db/transaction Stmt,Client
2025-01-17 14:34:48 +00:00
import (
"context"
"database/sql"
"fmt"
"reflect"
"testing"
"github.com/rancher/steve/pkg/sqlcache/db"
2025-02-05 09:05:52 +00:00
"github.com/rancher/steve/pkg/sqlcache/db/transaction"
2025-01-17 14:34:48 +00:00
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)
func testStoreKeyFunc ( obj interface { } ) ( string , error ) {
return obj . ( testStoreObject ) . Id , nil
}
type testStoreObject struct {
Id string
Val string
}
func TestAdd ( t * testing . T ) {
type testCase struct {
description string
test func ( t * testing . T , shouldEncrypt bool )
}
testObject := testStoreObject { Id : "something" , Val : "a" }
var tests [ ] testCase
// Tests with shouldEncryptSet to false
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Add with no DB client errors" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
2025-02-05 09:05:52 +00:00
2025-01-17 14:34:48 +00:00
c . EXPECT ( ) . Upsert ( txC , store . upsertStmt , "something" , testObject , store . shouldEncrypt ) . Return ( nil )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( nil ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err != nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Add ( testObject )
assert . Nil ( t , err )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Add with no DB client errors and an afterUpsert function" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
2025-02-05 09:05:52 +00:00
2025-01-17 14:34:48 +00:00
c . EXPECT ( ) . Upsert ( txC , store . upsertStmt , "something" , testObject , store . shouldEncrypt ) . Return ( nil )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( nil ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err != nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
var count int
2025-02-05 09:05:52 +00:00
store . afterUpsert = append ( store . afterUpsert , func ( key string , object any , tx transaction . Client ) error {
2025-01-17 14:34:48 +00:00
count ++
return nil
} )
err := store . Add ( testObject )
assert . Nil ( t , err )
assert . Equal ( t , count , 1 )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Add with no DB client errors and an afterUpsert function that returns error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
2025-02-05 09:05:52 +00:00
2025-01-17 14:34:48 +00:00
c . EXPECT ( ) . Upsert ( txC , store . upsertStmt , "something" , testObject , store . shouldEncrypt ) . Return ( nil )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "error" ) ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err == nil {
t . Fail ( )
}
} )
store . afterUpsert = append ( store . afterUpsert , func ( key string , object any , txC transaction . Client ) error {
2025-01-17 14:34:48 +00:00
return fmt . Errorf ( "error" )
} )
err := store . Add ( testObject )
assert . NotNil ( t , err )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Add with DB client WithTransaction error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "failed" ) )
2025-01-17 14:34:48 +00:00
store := SetupStore ( t , c , shouldEncrypt )
err := store . Add ( testObject )
assert . NotNil ( t , err )
} } )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Add with DB client Upsert() error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
c . EXPECT ( ) . Upsert ( txC , store . upsertStmt , "something" , testObject , store . shouldEncrypt ) . Return ( fmt . Errorf ( "failed" ) )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "failed" ) ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err == nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Add ( testObject )
assert . NotNil ( t , err )
} } )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Add with DB client Commit() error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
c . EXPECT ( ) . Upsert ( txC , store . upsertStmt , "something" , testObject , store . shouldEncrypt ) . Return ( nil )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "failed" ) ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err != nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Add ( testObject )
assert . NotNil ( t , err )
} } )
t . Parallel ( )
for _ , test := range tests {
t . Run ( test . description , func ( t * testing . T ) { test . test ( t , false ) } )
t . Run ( fmt . Sprintf ( "%s with encryption" , test . description ) , func ( t * testing . T ) { test . test ( t , true ) } )
}
}
// Update updates the given object in the accumulator associated with the given object's key
func TestUpdate ( t * testing . T ) {
type testCase struct {
description string
test func ( t * testing . T , shouldEncrypt bool )
}
testObject := testStoreObject { Id : "something" , Val : "a" }
var tests [ ] testCase
// Tests with shouldEncryptSet to false
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Update with no DB client errors" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
2025-02-05 09:05:52 +00:00
2025-01-17 14:34:48 +00:00
c . EXPECT ( ) . Upsert ( txC , store . upsertStmt , "something" , testObject , store . shouldEncrypt ) . Return ( nil )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( nil ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err != nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Update ( testObject )
assert . Nil ( t , err )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Update with no DB client errors and an afterUpsert function" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
2025-02-05 09:05:52 +00:00
2025-01-17 14:34:48 +00:00
c . EXPECT ( ) . Upsert ( txC , store . upsertStmt , "something" , testObject , store . shouldEncrypt ) . Return ( nil )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( nil ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err != nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
var count int
2025-02-05 09:05:52 +00:00
store . afterUpsert = append ( store . afterUpsert , func ( key string , object any , txC transaction . Client ) error {
2025-01-17 14:34:48 +00:00
count ++
return nil
} )
err := store . Update ( testObject )
assert . Nil ( t , err )
assert . Equal ( t , count , 1 )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Update with no DB client errors and an afterUpsert function that returns error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
2025-02-05 09:05:52 +00:00
2025-01-17 14:34:48 +00:00
c . EXPECT ( ) . Upsert ( txC , store . upsertStmt , "something" , testObject , store . shouldEncrypt ) . Return ( nil )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "error" ) ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err == nil {
t . Fail ( )
}
} )
store . afterUpsert = append ( store . afterUpsert , func ( key string , object any , txC transaction . Client ) error {
2025-01-17 14:34:48 +00:00
return fmt . Errorf ( "error" )
} )
err := store . Update ( testObject )
assert . NotNil ( t , err )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Update with DB client WithTransaction returning error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "error" ) )
2025-01-17 14:34:48 +00:00
store := SetupStore ( t , c , shouldEncrypt )
err := store . Update ( testObject )
assert . NotNil ( t , err )
} } )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Update with DB client Upsert() error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . Upsert ( txC , store . upsertStmt , "something" , testObject , store . shouldEncrypt ) . Return ( fmt . Errorf ( "failed" ) )
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "error" ) ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err == nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Update ( testObject )
assert . NotNil ( t , err )
} } )
t . Parallel ( )
for _ , test := range tests {
t . Run ( test . description , func ( t * testing . T ) { test . test ( t , false ) } )
t . Run ( fmt . Sprintf ( "%s with encryption" , test . description ) , func ( t * testing . T ) { test . test ( t , true ) } )
}
}
// Delete deletes the given object from the accumulator associated with the given object's key
func TestDelete ( t * testing . T ) {
type testCase struct {
description string
test func ( t * testing . T , shouldEncrypt bool )
}
testObject := testStoreObject { Id : "something" , Val : "a" }
var tests [ ] testCase
// Tests with shouldEncryptSet to false
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Delete with no DB client errors" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
2025-02-05 09:05:52 +00:00
stmt := NewMockStmt ( gomock . NewController ( t ) )
txC . EXPECT ( ) . Stmt ( store . deleteStmt ) . Return ( stmt )
stmt . EXPECT ( ) . Exec ( testObject . Id ) . Return ( nil , nil )
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( nil ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err != nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Delete ( testObject )
assert . Nil ( t , err )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Delete with DB client WithTransaction returning error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "error" ) )
2025-01-17 14:34:48 +00:00
err := store . Delete ( testObject )
assert . NotNil ( t , err )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Delete with TX client Exec() error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
2025-02-05 09:05:52 +00:00
stmt := NewMockStmt ( gomock . NewController ( t ) )
txC . EXPECT ( ) . Stmt ( store . deleteStmt ) . Return ( stmt )
stmt . EXPECT ( ) . Exec ( testObject . Id ) . Return ( nil , fmt . Errorf ( "error" ) )
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "error" ) ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err == nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Delete ( testObject )
assert . NotNil ( t , err )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Delete with DB client Commit() error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
2025-02-05 09:05:52 +00:00
stmt := NewMockStmt ( gomock . NewController ( t ) )
txC . EXPECT ( ) . Stmt ( store . deleteStmt ) . Return ( stmt )
stmt . EXPECT ( ) . Exec ( testObject . Id ) . Return ( nil , nil )
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "error" ) ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err != nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Delete ( testObject )
assert . NotNil ( t , err )
} ,
} )
t . Parallel ( )
for _ , test := range tests {
t . Run ( test . description , func ( t * testing . T ) { test . test ( t , false ) } )
t . Run ( fmt . Sprintf ( "%s with encryption" , test . description ) , func ( t * testing . T ) { test . test ( t , true ) } )
}
}
// List returns a list of all the currently non-empty accumulators
func TestList ( t * testing . T ) {
type testCase struct {
description string
test func ( t * testing . T , shouldEncrypt bool )
}
testObject := testStoreObject { Id : "something" , Val : "a" }
var tests [ ] testCase
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "List with no DB client errors and no items" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . listStmt ) . Return ( r , nil )
c . EXPECT ( ) . ReadObjects ( r , reflect . TypeOf ( testObject ) , store . shouldEncrypt ) . Return ( [ ] any { } , nil )
items := store . List ( )
assert . Len ( t , items , 0 )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "List with no DB client errors and some items" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
fakeItemsToReturn := [ ] any { "something1" , 2 , false }
r := & sql . Rows { }
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . listStmt ) . Return ( r , nil )
c . EXPECT ( ) . ReadObjects ( r , reflect . TypeOf ( testObject ) , store . shouldEncrypt ) . Return ( fakeItemsToReturn , nil )
items := store . List ( )
assert . Equal ( t , fakeItemsToReturn , items )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "List with DB client ReadObjects() error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . listStmt ) . Return ( r , nil )
c . EXPECT ( ) . ReadObjects ( r , reflect . TypeOf ( testObject ) , store . shouldEncrypt ) . Return ( nil , fmt . Errorf ( "error" ) )
defer func ( ) {
recover ( )
} ( )
_ = store . List ( )
assert . Fail ( t , "Store list should panic when ReadObjects returns an error" )
} ,
} )
t . Parallel ( )
for _ , test := range tests {
t . Run ( test . description , func ( t * testing . T ) { test . test ( t , false ) } )
t . Run ( fmt . Sprintf ( "%s with encryption" , test . description ) , func ( t * testing . T ) { test . test ( t , true ) } )
}
}
// ListKeys returns a list of all the keys currently associated with non-empty accumulators
func TestListKeys ( t * testing . T ) {
type testCase struct {
description string
test func ( t * testing . T , shouldEncrypt bool )
}
var tests [ ] testCase
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "ListKeys with no DB client errors and some items" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . listKeysStmt ) . Return ( r , nil )
c . EXPECT ( ) . ReadStrings ( r ) . Return ( [ ] string { "a" , "b" , "c" } , nil )
keys := store . ListKeys ( )
assert . Len ( t , keys , 3 )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "ListKeys with DB client ReadStrings() error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . listKeysStmt ) . Return ( r , nil )
c . EXPECT ( ) . ReadStrings ( r ) . Return ( nil , fmt . Errorf ( "error" ) )
keys := store . ListKeys ( )
assert . Len ( t , keys , 0 )
} ,
} )
t . Parallel ( )
for _ , test := range tests {
t . Run ( test . description , func ( t * testing . T ) { test . test ( t , false ) } )
t . Run ( fmt . Sprintf ( "%s with encryption" , test . description ) , func ( t * testing . T ) { test . test ( t , true ) } )
}
}
// Get returns the accumulator associated with the given object's key
func TestGet ( t * testing . T ) {
type testCase struct {
description string
test func ( t * testing . T , shouldEncrypt bool )
}
var tests [ ] testCase
testObject := testStoreObject { Id : "something" , Val : "a" }
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Get with no DB client errors and object exists" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . getStmt , testObject . Id ) . Return ( r , nil )
c . EXPECT ( ) . ReadObjects ( r , reflect . TypeOf ( testObject ) , store . shouldEncrypt ) . Return ( [ ] any { testObject } , nil )
item , exists , err := store . Get ( testObject )
assert . Nil ( t , err )
assert . Equal ( t , item , testObject )
assert . True ( t , exists )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Get with no DB client errors and object does not exist" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . getStmt , testObject . Id ) . Return ( r , nil )
c . EXPECT ( ) . ReadObjects ( r , reflect . TypeOf ( testObject ) , store . shouldEncrypt ) . Return ( [ ] any { } , nil )
item , exists , err := store . Get ( testObject )
assert . Nil ( t , err )
assert . Equal ( t , item , nil )
assert . False ( t , exists )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Get with DB client ReadObjects() error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . getStmt , testObject . Id ) . Return ( r , nil )
c . EXPECT ( ) . ReadObjects ( r , reflect . TypeOf ( testObject ) , store . shouldEncrypt ) . Return ( nil , fmt . Errorf ( "error" ) )
_ , _ , err := store . Get ( testObject )
assert . NotNil ( t , err )
} ,
} )
t . Parallel ( )
for _ , test := range tests {
t . Run ( test . description , func ( t * testing . T ) { test . test ( t , false ) } )
t . Run ( fmt . Sprintf ( "%s with encryption" , test . description ) , func ( t * testing . T ) { test . test ( t , true ) } )
}
}
// GetByKey returns the accumulator associated with the given key
func TestGetByKey ( t * testing . T ) {
type testCase struct {
description string
test func ( t * testing . T , shouldEncrypt bool )
}
var tests [ ] testCase
testObject := testStoreObject { Id : "something" , Val : "a" }
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "GetByKey with no DB client errors and item exists" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . getStmt , testObject . Id ) . Return ( r , nil )
c . EXPECT ( ) . ReadObjects ( r , reflect . TypeOf ( testObject ) , store . shouldEncrypt ) . Return ( [ ] any { testObject } , nil )
item , exists , err := store . GetByKey ( testObject . Id )
assert . Nil ( t , err )
assert . Equal ( t , item , testObject )
assert . True ( t , exists )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "GetByKey with no DB client errors and item does not exist" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . getStmt , testObject . Id ) . Return ( r , nil )
c . EXPECT ( ) . ReadObjects ( r , reflect . TypeOf ( testObject ) , store . shouldEncrypt ) . Return ( [ ] any { } , nil )
item , exists , err := store . GetByKey ( testObject . Id )
assert . Nil ( t , err )
assert . Equal ( t , nil , item )
assert . False ( t , exists )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "GetByKey with DB client ReadObjects() error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . getStmt , testObject . Id ) . Return ( r , nil )
c . EXPECT ( ) . ReadObjects ( r , reflect . TypeOf ( testObject ) , store . shouldEncrypt ) . Return ( nil , fmt . Errorf ( "error" ) )
_ , _ , err := store . GetByKey ( testObject . Id )
assert . NotNil ( t , err )
} ,
} )
t . Parallel ( )
for _ , test := range tests {
t . Run ( test . description , func ( t * testing . T ) { test . test ( t , false ) } )
t . Run ( fmt . Sprintf ( "%s with encryption" , test . description ) , func ( t * testing . T ) { test . test ( t , true ) } )
}
}
// Replace will delete the contents of the store, using instead the
// given list. Store takes ownership of the list, you should not reference
// it after calling this function.
func TestReplace ( t * testing . T ) {
type testCase struct {
description string
test func ( t * testing . T , shouldEncrypt bool )
}
var tests [ ] testCase
testObject := testStoreObject { Id : "something" , Val : "a" }
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Replace with no DB client errors and some items" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
2025-02-05 09:05:52 +00:00
stmt := NewMockStmt ( gomock . NewController ( t ) )
txC . EXPECT ( ) . Stmt ( store . listKeysStmt ) . Return ( stmt )
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , stmt ) . Return ( r , nil )
2025-01-17 14:34:48 +00:00
c . EXPECT ( ) . ReadStrings ( r ) . Return ( [ ] string { testObject . Id } , nil )
2025-02-05 09:05:52 +00:00
txC . EXPECT ( ) . Stmt ( store . deleteStmt ) . Return ( stmt )
stmt . EXPECT ( ) . Exec ( testObject . Id )
2025-01-17 14:34:48 +00:00
c . EXPECT ( ) . Upsert ( txC , store . upsertStmt , testObject . Id , testObject , store . shouldEncrypt )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( nil ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err != nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Replace ( [ ] any { testObject } , testObject . Id )
assert . Nil ( t , err )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Replace with no DB client errors and no items" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
2025-02-05 09:05:52 +00:00
2025-01-17 14:34:48 +00:00
txC . EXPECT ( ) . Stmt ( store . listKeysStmt ) . Return ( store . listKeysStmt )
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . listKeysStmt ) . Return ( r , nil )
c . EXPECT ( ) . ReadStrings ( r ) . Return ( [ ] string { } , nil )
c . EXPECT ( ) . Upsert ( txC , store . upsertStmt , testObject . Id , testObject , store . shouldEncrypt )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( nil ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err != nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Replace ( [ ] any { testObject } , testObject . Id )
assert . Nil ( t , err )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Replace with DB client WithTransaction returning error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "error" ) )
2025-01-17 14:34:48 +00:00
err := store . Replace ( [ ] any { testObject } , testObject . Id )
assert . NotNil ( t , err )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Replace with DB client ReadStrings() error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
2025-02-05 09:05:52 +00:00
2025-01-17 14:34:48 +00:00
txC . EXPECT ( ) . Stmt ( store . listKeysStmt ) . Return ( store . listKeysStmt )
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , store . listKeysStmt ) . Return ( r , nil )
c . EXPECT ( ) . ReadStrings ( r ) . Return ( nil , fmt . Errorf ( "error" ) )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "error" ) ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err == nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Replace ( [ ] any { testObject } , testObject . Id )
assert . NotNil ( t , err )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Replace with TX client StmtExec() error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
2025-02-05 09:05:52 +00:00
listKeysStmt := NewMockStmt ( gomock . NewController ( t ) )
deleteStmt := NewMockStmt ( gomock . NewController ( t ) )
txC . EXPECT ( ) . Stmt ( store . listKeysStmt ) . Return ( listKeysStmt )
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , listKeysStmt ) . Return ( r , nil )
2025-01-17 14:34:48 +00:00
c . EXPECT ( ) . ReadStrings ( r ) . Return ( [ ] string { testObject . Id } , nil )
2025-02-05 09:05:52 +00:00
txC . EXPECT ( ) . Stmt ( store . deleteStmt ) . Return ( deleteStmt )
deleteStmt . EXPECT ( ) . Exec ( testObject . Id ) . Return ( nil , fmt . Errorf ( "error" ) )
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "error" ) ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err == nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Replace ( [ ] any { testObject } , testObject . Id )
assert . NotNil ( t , err )
} ,
} )
2025-02-05 09:05:52 +00:00
tests = append ( tests , testCase { description : "Replace with DB client Upsert() error" , test : func ( t * testing . T , shouldEncrypt bool ) {
2025-01-17 14:34:48 +00:00
c , txC := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
r := & sql . Rows { }
2025-02-05 09:05:52 +00:00
listKeysStmt := NewMockStmt ( gomock . NewController ( t ) )
deleteStmt := NewMockStmt ( gomock . NewController ( t ) )
txC . EXPECT ( ) . Stmt ( store . listKeysStmt ) . Return ( listKeysStmt )
c . EXPECT ( ) . QueryForRows ( context . TODO ( ) , listKeysStmt ) . Return ( r , nil )
2025-01-17 14:34:48 +00:00
c . EXPECT ( ) . ReadStrings ( r ) . Return ( [ ] string { testObject . Id } , nil )
2025-02-05 09:05:52 +00:00
txC . EXPECT ( ) . Stmt ( store . deleteStmt ) . Return ( deleteStmt )
deleteStmt . EXPECT ( ) . Exec ( testObject . Id ) . Return ( nil , nil )
2025-01-17 14:34:48 +00:00
c . EXPECT ( ) . Upsert ( txC , store . upsertStmt , testObject . Id , testObject , store . shouldEncrypt ) . Return ( fmt . Errorf ( "error" ) )
2025-02-05 09:05:52 +00:00
c . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( fmt . Errorf ( "error" ) ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err == nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
err := store . Replace ( [ ] any { testObject } , testObject . Id )
assert . NotNil ( t , err )
} ,
} )
t . Parallel ( )
for _ , test := range tests {
t . Run ( test . description , func ( t * testing . T ) { test . test ( t , false ) } )
t . Run ( fmt . Sprintf ( "%s with encryption" , test . description ) , func ( t * testing . T ) { test . test ( t , true ) } )
}
}
// Resync is meaningless in the terms appearing here but has
// meaning in some implementations that have non-trivial
// additional behavior (e.g., DeltaFIFO).
func TestResync ( t * testing . T ) {
type testCase struct {
description string
test func ( t * testing . T , shouldEncrypt bool )
}
var tests [ ] testCase
tests = append ( tests , testCase { description : "Resync shouldn't call the client, panic, or do anything else" , test : func ( t * testing . T , shouldEncrypt bool ) {
c , _ := SetupMockDB ( t )
store := SetupStore ( t , c , shouldEncrypt )
err := store . Resync ( )
assert . Nil ( t , err )
} ,
} )
t . Parallel ( )
for _ , test := range tests {
t . Run ( test . description , func ( t * testing . T ) { test . test ( t , false ) } )
t . Run ( fmt . Sprintf ( "%s with encryption" , test . description ) , func ( t * testing . T ) { test . test ( t , true ) } )
}
}
2025-02-05 09:05:52 +00:00
func SetupMockDB ( t * testing . T ) ( * MockClient , * MockTXClient ) {
dbC := NewMockClient ( gomock . NewController ( t ) ) // add functionality once store expectation are known
2025-01-17 14:34:48 +00:00
txC := NewMockTXClient ( gomock . NewController ( t ) )
2025-02-05 09:05:52 +00:00
txC . EXPECT ( ) . Exec ( fmt . Sprintf ( createTableFmt , "testStoreObject" ) ) . Return ( nil , nil )
dbC . EXPECT ( ) . WithTransaction ( gomock . Any ( ) , true , gomock . Any ( ) ) . Return ( nil ) . Do (
func ( ctx context . Context , shouldEncrypt bool , f db . WithTransactionFunction ) {
err := f ( txC )
if err != nil {
t . Fail ( )
}
} )
2025-01-17 14:34:48 +00:00
// use stmt mock here
dbC . EXPECT ( ) . Prepare ( fmt . Sprintf ( upsertStmtFmt , "testStoreObject" ) ) . Return ( & sql . Stmt { } )
dbC . EXPECT ( ) . Prepare ( fmt . Sprintf ( deleteStmtFmt , "testStoreObject" ) ) . Return ( & sql . Stmt { } )
dbC . EXPECT ( ) . Prepare ( fmt . Sprintf ( getStmtFmt , "testStoreObject" ) ) . Return ( & sql . Stmt { } )
dbC . EXPECT ( ) . Prepare ( fmt . Sprintf ( listStmtFmt , "testStoreObject" ) ) . Return ( & sql . Stmt { } )
dbC . EXPECT ( ) . Prepare ( fmt . Sprintf ( listKeysStmtFmt , "testStoreObject" ) ) . Return ( & sql . Stmt { } )
return dbC , txC
}
2025-02-05 09:05:52 +00:00
func SetupStore ( t * testing . T , client * MockClient , shouldEncrypt bool ) * Store {
2025-01-17 14:34:48 +00:00
store , err := NewStore ( testStoreObject { } , testStoreKeyFunc , client , shouldEncrypt , "testStoreObject" )
if err != nil {
t . Error ( err )
}
return store
}