Files
athens/pkg/stash/with_redis_test.go
2023-06-23 15:41:27 +02:00

154 lines
3.7 KiB
Go

package stash
import (
"context"
"fmt"
"os"
"strings"
"sync"
"testing"
"time"
"github.com/gomods/athens/pkg/config"
"github.com/gomods/athens/pkg/storage"
"github.com/gomods/athens/pkg/storage/mem"
"golang.org/x/sync/errgroup"
)
// testingRedisLogger implements pkg/stash.RedisLogger.
type testingRedisLogger struct {
t *testing.T
}
func (l *testingRedisLogger) Printf(ctx context.Context, format string, v ...any) {
l.t.Logf(format, v...)
}
// WithRedisLock will ensure that 5 concurrent requests will all get the first request's
// response. We can ensure that because only the first response does not return an error
// and therefore all 5 responses should have no error.
func TestWithRedisLock(t *testing.T) {
endpoint := os.Getenv("REDIS_TEST_ENDPOINT")
password := os.Getenv("ATHENS_REDIS_PASSWORD")
if len(endpoint) == 0 {
t.SkipNow()
}
strg, err := mem.NewStorage()
if err != nil {
t.Fatal(err)
}
ms := &mockRedisStasher{strg: strg}
l := &testingRedisLogger{t: t}
wrapper, err := WithRedisLock(l, endpoint, password, storage.WithChecker(strg), config.DefaultRedisLockConfig())
if err != nil {
t.Fatal(err)
}
s := wrapper(ms)
var eg errgroup.Group
for i := 0; i < 5; i++ {
eg.Go(func() error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
_, err := s.Stash(ctx, "mod", "ver")
return err
})
}
err = eg.Wait()
if err != nil {
t.Fatal(err)
}
}
// Verify with WithRedisLock working with password protected redis
// Same logic as the TestWithRedisLock test.
func TestWithRedisLockWithPassword(t *testing.T) {
endpoint := os.Getenv("PROTECTED_REDIS_TEST_ENDPOINT")
password := os.Getenv("ATHENS_PROTECTED_REDIS_PASSWORD")
if len(endpoint) == 0 {
t.SkipNow()
}
strg, err := mem.NewStorage()
if err != nil {
t.Fatal(err)
}
ms := &mockRedisStasher{strg: strg}
l := &testingRedisLogger{t: t}
wrapper, err := WithRedisLock(l, endpoint, password, storage.WithChecker(strg), config.DefaultRedisLockConfig())
if err != nil {
t.Fatal(err)
}
s := wrapper(ms)
var eg errgroup.Group
for i := 0; i < 5; i++ {
eg.Go(func() error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
_, err := s.Stash(ctx, "mod", "ver")
return err
})
}
err = eg.Wait()
if err != nil {
t.Fatal(err)
}
}
// Verify the WithRedisLock fails with the correct error when trying
// to connect with the wrong password.
func TestWithRedisLockWithWrongPassword(t *testing.T) {
endpoint := os.Getenv("PROTECTED_REDIS_TEST_ENDPOINT")
password := ""
if len(endpoint) == 0 {
t.SkipNow()
}
strg, err := mem.NewStorage()
if err != nil {
t.Fatal(err)
}
l := &testingRedisLogger{t: t}
_, err = WithRedisLock(l, endpoint, password, storage.WithChecker(strg), config.DefaultRedisLockConfig())
if err == nil {
t.Fatal("Expected Connection Error")
}
if !strings.Contains(err.Error(), "NOAUTH Authentication required.") {
t.Fatalf("Wrong error was thrown %q\n", err.Error())
}
}
// mockRedisStasher is like mockStasher
// but leverages in memory storage
// so that redis can determine
// whether to call the underlying stasher or not.
type mockRedisStasher struct {
strg storage.Backend
mu sync.Mutex
num int
}
func (ms *mockRedisStasher) Stash(ctx context.Context, mod, ver string) (string, error) {
time.Sleep(time.Millisecond * 100) // allow for second requests to come in.
ms.mu.Lock()
defer ms.mu.Unlock()
if ms.num == 0 {
err := ms.strg.Save(
ctx,
mod,
ver,
[]byte("mod file"),
strings.NewReader("zip file"),
[]byte("info file"),
)
if err != nil {
return "", err
}
ms.num++
return "", nil
}
return "", fmt.Errorf("second time error")
}