github.com/DeltaLaboratory/entcache@v0.1.1/driver_test.go (about) 1 package entcache_test 2 3 import ( 4 "context" 5 "database/sql/driver" 6 "testing" 7 "time" 8 9 "github.com/redis/rueidis" 10 "go.uber.org/mock/gomock" 11 12 "github.com/DeltaLaboratory/entcache" 13 14 "entgo.io/ent/dialect" 15 "entgo.io/ent/dialect/sql" 16 "github.com/DATA-DOG/go-sqlmock" 17 ruemock "github.com/redis/rueidis/mock" 18 ) 19 20 func TestDriver_ContextLevel(t *testing.T) { 21 db, mock, err := sqlmock.New() 22 if err != nil { 23 t.Fatal(err) 24 } 25 drv := sql.OpenDB(dialect.MySQL, db) 26 27 t.Run("One", func(t *testing.T) { 28 drv := entcache.NewDriver(drv, entcache.ContextLevel()) 29 mock.ExpectQuery("SELECT id FROM users"). 30 WillReturnRows( 31 sqlmock.NewRows([]string{"id"}). 32 AddRow(1). 33 AddRow(2). 34 AddRow(3), 35 ) 36 ctx := entcache.NewContext(context.Background()) 37 expectQuery(ctx, t, drv, "SELECT id FROM users", []interface{}{int64(1), int64(2), int64(3)}) 38 expectQuery(ctx, t, drv, "SELECT id FROM users", []interface{}{int64(1), int64(2), int64(3)}) 39 if err := mock.ExpectationsWereMet(); err != nil { 40 t.Fatal(err) 41 } 42 }) 43 44 t.Run("Multi", func(t *testing.T) { 45 drv := entcache.NewDriver(drv, entcache.ContextLevel()) 46 mock.ExpectQuery("SELECT name FROM users"). 47 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 48 ctx1 := entcache.NewContext(context.Background()) 49 expectQuery(ctx1, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 50 ctx2 := entcache.NewContext(context.Background()) 51 mock.ExpectQuery("SELECT name FROM users"). 52 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 53 expectQuery(ctx2, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 54 if err := mock.ExpectationsWereMet(); err != nil { 55 t.Fatal(err) 56 } 57 }) 58 59 t.Run("TTL", func(t *testing.T) { 60 drv := entcache.NewDriver(drv, entcache.ContextLevel(), entcache.TTL(-1)) 61 mock.ExpectQuery("SELECT name FROM users"). 62 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 63 mock.ExpectQuery("SELECT name FROM users"). 64 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 65 ctx := entcache.NewContext(context.Background()) 66 expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 67 expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 68 if err := mock.ExpectationsWereMet(); err != nil { 69 t.Fatal(err) 70 } 71 }) 72 } 73 74 func TestDriver_Levels(t *testing.T) { 75 db, mock, err := sqlmock.New() 76 if err != nil { 77 t.Fatal(err) 78 } 79 drv := sql.OpenDB(dialect.Postgres, db) 80 81 t.Run("One", func(t *testing.T) { 82 drv := entcache.NewDriver(drv, entcache.TTL(time.Second)) 83 mock.ExpectQuery("SELECT age FROM users"). 84 WillReturnRows( 85 sqlmock.NewRows([]string{"age"}). 86 AddRow(20.1). 87 AddRow(30.2). 88 AddRow(40.5), 89 ) 90 expectQuery(context.Background(), t, drv, "SELECT age FROM users", []interface{}{20.1, 30.2, 40.5}) 91 expectQuery(context.Background(), t, drv, "SELECT age FROM users", []interface{}{20.1, 30.2, 40.5}) 92 if err := mock.ExpectationsWereMet(); err != nil { 93 t.Fatal(err) 94 } 95 }) 96 97 t.Run("Multi", func(t *testing.T) { 98 drv := entcache.NewDriver( 99 drv, 100 entcache.Levels( 101 entcache.NewLRU(-1), // Nop. 102 entcache.NewLRU(0), // No limit. 103 ), 104 ) 105 mock.ExpectQuery("SELECT age FROM users"). 106 WillReturnRows( 107 sqlmock.NewRows([]string{"age"}). 108 AddRow(20.1). 109 AddRow(30.2). 110 AddRow(40.5), 111 ) 112 expectQuery(context.Background(), t, drv, "SELECT age FROM users", []interface{}{20.1, 30.2, 40.5}) 113 expectQuery(context.Background(), t, drv, "SELECT age FROM users", []interface{}{20.1, 30.2, 40.5}) 114 if err := mock.ExpectationsWereMet(); err != nil { 115 t.Fatal(err) 116 } 117 }) 118 119 t.Run("Redis", func(t *testing.T) { 120 var ( 121 rdb = ruemock.NewClient(gomock.NewController(t)) 122 drv = entcache.NewDriver( 123 drv, 124 entcache.Levels( 125 entcache.NewLRU(-1), 126 entcache.NewRedis(rdb), 127 ), 128 entcache.Hash(func(string, []interface{}) (entcache.Key, error) { 129 return 1, nil 130 }), 131 ) 132 ) 133 rdb.EXPECT().Do(context.Background(), ruemock.Match("GET", "1")).Return(ruemock.Result(ruemock.RedisNil())) 134 mock.ExpectQuery("SELECT active FROM users"). 135 WillReturnRows(sqlmock.NewRows([]string{"active"}).AddRow(true).AddRow(false)) 136 137 buf, _ := entcache.Entry{Values: [][]driver.Value{{true}, {false}}}.MarshalBinary() 138 rdb.EXPECT().Do(context.Background(), ruemock.Match("SET", "1", rueidis.BinaryString(buf), "EX", "0")).Return(ruemock.Result(ruemock.RedisNil())) 139 expectQuery(context.Background(), t, drv, "SELECT active FROM users", []interface{}{true, false}) 140 141 rdb.EXPECT().Do(context.Background(), ruemock.Match("GET", "1")).Return(ruemock.Result(ruemock.RedisString(rueidis.BinaryString(buf)))) 142 expectQuery(context.Background(), t, drv, "SELECT active FROM users", []interface{}{true, false}) 143 144 expected := entcache.Stats{Gets: 2, Hits: 1} 145 if s := drv.Stats(); s != expected { 146 t.Errorf("unexpected stats: %v != %v", s, expected) 147 } 148 }) 149 } 150 151 func TestDriver_ContextOptions(t *testing.T) { 152 db, mock, err := sqlmock.New() 153 if err != nil { 154 t.Fatal(err) 155 } 156 drv := sql.OpenDB(dialect.MySQL, db) 157 158 t.Run("Skip", func(t *testing.T) { 159 drv := entcache.NewDriver(drv) 160 mock.ExpectQuery("SELECT name FROM users"). 161 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 162 ctx := context.Background() 163 expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 164 expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 165 mock.ExpectQuery("SELECT name FROM users"). 166 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 167 skipCtx := entcache.Skip(ctx) 168 expectQuery(skipCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 169 expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 170 if err := mock.ExpectationsWereMet(); err != nil { 171 t.Fatal(err) 172 } 173 }) 174 175 t.Run("Evict", func(t *testing.T) { 176 drv := entcache.NewDriver(drv) 177 mock.ExpectQuery("SELECT name FROM users"). 178 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 179 ctx := context.Background() 180 expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 181 expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 182 mock.ExpectQuery("SELECT name FROM users"). 183 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 184 evictCtx := entcache.Evict(ctx) 185 expectQuery(evictCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 186 mock.ExpectQuery("SELECT name FROM users"). 187 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 188 expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 189 if err := mock.ExpectationsWereMet(); err != nil { 190 t.Fatal(err) 191 } 192 }) 193 194 t.Run("WithTTL", func(t *testing.T) { 195 drv := entcache.NewDriver(drv) 196 mock.ExpectQuery("SELECT name FROM users"). 197 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 198 ttlCtx := entcache.WithTTL(context.Background(), -1) 199 expectQuery(ttlCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 200 mock.ExpectQuery("SELECT name FROM users"). 201 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 202 expectQuery(ttlCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 203 if err := mock.ExpectationsWereMet(); err != nil { 204 t.Fatal(err) 205 } 206 }) 207 208 t.Run("WithKey", func(t *testing.T) { 209 drv := entcache.NewDriver(drv) 210 mock.ExpectQuery("SELECT name FROM users"). 211 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 212 ctx := context.Background() 213 keyCtx := entcache.WithKey(ctx, "cache-key") 214 expectQuery(keyCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 215 expectQuery(keyCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 216 mock.ExpectQuery("SELECT name FROM users"). 217 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 218 expectQuery(ctx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 219 if err := drv.Cache.Del(ctx, "cache-key"); err != nil { 220 t.Fatal(err) 221 } 222 mock.ExpectQuery("SELECT name FROM users"). 223 WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("a8m")) 224 expectQuery(keyCtx, t, drv, "SELECT name FROM users", []interface{}{"a8m"}) 225 if err := mock.ExpectationsWereMet(); err != nil { 226 t.Fatal(err) 227 } 228 expected := entcache.Stats{Gets: 4, Hits: 1} 229 if s := drv.Stats(); s != expected { 230 t.Errorf("unexpected stats: %v != %v", s, expected) 231 } 232 }) 233 } 234 235 func TestDriver_SkipInsert(t *testing.T) { 236 db, mock, err := sqlmock.New() 237 if err != nil { 238 t.Fatal(err) 239 } 240 drv := entcache.NewDriver(sql.OpenDB(dialect.Postgres, db), entcache.Hash(func(string, []interface{}) (entcache.Key, error) { 241 t.Fatal("Driver.Query should not be called for INSERT statements") 242 return nil, nil 243 })) 244 mock.ExpectQuery("INSERT INTO users DEFAULT VALUES RETURNING id"). 245 WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) 246 expectQuery(context.Background(), t, drv, "INSERT INTO users DEFAULT VALUES RETURNING id", []interface{}{int64(1)}) 247 if err := mock.ExpectationsWereMet(); err != nil { 248 t.Fatal(err) 249 } 250 var expected entcache.Stats 251 if s := drv.Stats(); s != expected { 252 t.Errorf("unexpected stats: %v != %v", s, expected) 253 } 254 } 255 256 func expectQuery(ctx context.Context, t *testing.T, drv dialect.Driver, query string, args []interface{}) { 257 rows := &sql.Rows{} 258 if err := drv.Query(ctx, query, []interface{}{}, rows); err != nil { 259 t.Fatalf("unexpected query failure: %q: %v", query, err) 260 } 261 var dest []interface{} 262 for rows.Next() { 263 var v interface{} 264 if err := rows.Scan(&v); err != nil { 265 t.Fatal("unexpected Rows.Scan failure:", err) 266 } 267 dest = append(dest, v) 268 } 269 if len(dest) != len(args) { 270 t.Fatalf("mismatch rows length: %d != %d", len(dest), len(args)) 271 } 272 for i := range dest { 273 if dest[i] != args[i] { 274 t.Fatalf("mismatch values: %v %T != %v %T", dest[i], dest[i], args[i], args[i]) 275 } 276 } 277 if err := rows.Close(); err != nil { 278 t.Fatal(err) 279 } 280 }