package cooler import ( "context" "fmt" "strings" "testing" "time" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) // Smoke tests only; plan 05 of phase 19.3 owns full coverage. func TestOpenRedisRedactsSmoke(t *testing.T) { ctx := t.Context() _, err := OpenRedis(ctx, "redis://alice:s3cretpw@cache.example.com:6379/0\x7f") if err == nil { t.Fatal("a URL with a control character must not parse") } msg := err.Error() if strings.Contains(msg, "alice") || strings.Contains(msg, "s3cretpw") { t.Fatalf("error leaks credentials: %q", msg) } if !strings.Contains(msg, RedisURLKey) { t.Fatalf("error should name %s: %q", RedisURLKey, msg) } c, err := OpenRedis(ctx, "rediss://user:pw@cache.example.com:6380/1") if err != nil { t.Fatalf("rediss URL: %v", err) } defer c.Close() opt := c.Options() if opt.TLSConfig == nil || opt.Addr != "cache.example.com:6380" || opt.DB != 1 { t.Fatalf("rediss options = addr %q db %d tls %v", opt.Addr, opt.DB, opt.TLSConfig != nil) } if !opt.DisableIdentity { t.Fatal("OpenRedis must disable the CLIENT SETINFO handshake") } } // cacheRoundTrip runs the shared smoke sequence against one driver. func cacheRoundTrip(t *testing.T, c Cache) { t.Helper() ctx := t.Context() if err := c.Set(ctx, "greeting", []byte("hello"), 0); err != nil { t.Fatal(err) } if v, ok, err := c.Get(ctx, "greeting"); err != nil || !ok || string(v) != "hello" { t.Fatalf("Get = %q %v %v", v, ok, err) } if ok, err := c.Has(ctx, "greeting"); err != nil || !ok { t.Fatalf("Has = %v %v", ok, err) } if n, err := c.Increment(ctx, "hits", 2); err != nil || n != 2 { t.Fatalf("Increment from missing = %d %v", n, err) } if n, err := c.Increment(ctx, "hits", 3); err != nil || n != 5 { t.Fatalf("Increment existing = %d %v", n, err) } calls := 0 fn := func(context.Context) ([]byte, error) { calls++; return []byte("computed"), nil } for range 2 { v, err := c.Remember(ctx, "remembered", time.Minute, fn) if err != nil || string(v) != "computed" { t.Fatalf("Remember = %q %v", v, err) } } if calls != 1 { t.Fatalf("Remember called the callback %d times, want 1", calls) } if err := c.Forget(ctx, "greeting"); err != nil { t.Fatal(err) } if ok, _ := c.Has(ctx, "greeting"); ok { t.Fatal("Forget left the key") } if err := c.Set(ctx, "short", []byte("x"), time.Millisecond); err != nil { t.Fatal(err) } time.Sleep(20 * time.Millisecond) if _, ok, _ := c.Get(ctx, "short"); ok { t.Fatal("a 1 ms TTL did not expire") } if err := c.Flush(ctx); err != nil { t.Fatal(err) } for _, k := range []string{"hits", "remembered"} { if ok, _ := c.Has(ctx, k); ok { t.Fatalf("Flush left %q", k) } } } func TestMemoryCacheSmoke(t *testing.T) { c, err := NewMemoryCache("acme:", DefaultMaxEntries) if err != nil { t.Fatal(err) } cacheRoundTrip(t, c) // Stored bytes are copies. ctx := t.Context() in := []byte("abc") _ = c.Set(ctx, "copy", in, 0) in[0] = 'X' out, _, _ := c.Get(ctx, "copy") out[1] = 'Y' if again, _, _ := c.Get(ctx, "copy"); string(again) != "abc" { t.Fatalf("stored value mutated: %q", again) } if _, err := NewMemoryCache("acme:", 0); err == nil || !strings.Contains(err.Error(), CacheMaxEntriesKey) { t.Fatalf("max entries 0: %v", err) } } func TestRedisCacheSmoke(t *testing.T) { if testing.Short() { t.Skip("requires testcontainers redis") } ctx, cancel := context.WithTimeout(t.Context(), 2*time.Minute) defer cancel() ctr, err := testcontainers.Run(ctx, "redis:7-alpine", testcontainers.WithExposedPorts("6379/tcp"), testcontainers.WithWaitStrategy(wait.ForListeningPort("6379/tcp")), ) if err != nil { t.Fatalf("redis: testcontainers: %v", err) } t.Cleanup(func() { _ = testcontainers.TerminateContainer(ctr) }) host, err := ctr.Host(ctx) if err != nil { t.Fatal(err) } port, err := ctr.MappedPort(ctx, "6379/tcp") if err != nil { t.Fatal(err) } client, err := OpenRedis(ctx, fmt.Sprintf("redis://%s:%s/0", host, port.Port())) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = client.Close() }) // A rate-limit counter of this app and another app's cache key survive Flush. if err := client.Set(ctx, "acme:rl:login|192.0.2.1", 3, 0).Err(); err != nil { t.Fatal(err) } if err := client.Set(ctx, "other:cache:greeting", "theirs", 0).Err(); err != nil { t.Fatal(err) } cacheRoundTrip(t, NewRedisCache(client, "acme:")) for _, k := range []string{"acme:rl:login|192.0.2.1", "other:cache:greeting"} { if n, err := client.Exists(ctx, k).Result(); err != nil || n != 1 { t.Fatalf("Flush removed %q (exists=%d, err=%v)", k, n, err) } } }