filestore/filestore_test.go

427 lines
8.7 KiB
Go

package main
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
const filecontent = "the content of my file"
type errorReader struct{}
func (errorReader) Read(_ []byte) (int, error) {
return 0, errors.New("reading error")
}
var _ io.Reader = errorReader{}
func nameGenAddress(f NameGenerator) string {
return fmt.Sprintf("%p", f)
}
func initStore(t *testing.T, path string) *Filestore {
t.Helper()
fs, err := NewFilestore(path)
require.NoError(t, err, "cannot initialize filestore")
return fs
}
func TestFilestore(t *testing.T) {
t.Parallel()
const bucket = "foo"
t.Run("Initialize a store", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
fs, err := NewFilestore(tmpDir)
assert.NoError(err)
assert.NotNil(fs)
})
t.Run("It should create its directories if they do not exist", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
fs, err := NewFilestore(filepath.Join(tmpDir, "foo", "bar", "baz"))
assert.NoError(err)
assert.NotNil(fs)
})
t.Run("Initialize a store in a directory that can't be created", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
tmpDir = filepath.Join(tmpDir, "unwritable")
err := os.Mkdir(tmpDir, 0o000)
assert.NoError(err)
tmpDir = filepath.Join(tmpDir, "subdir")
os.WriteFile(tmpDir, []byte("foo"), 0o644)
fs, err := NewFilestore(tmpDir)
assert.Error(err)
assert.Nil(fs)
})
t.Run("Selecting a new bucket", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
fs, err := NewFilestore(tmpDir)
assert.NoError(err)
b := fs.Bucket(bucket)
assert.NotNil(b)
})
t.Run("Select again a bucket", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
fs, err := NewFilestore(tmpDir)
assert.NoError(err)
b := fs.Bucket(bucket)
assert.NotNil(b)
b2 := fs.Bucket(bucket)
assert.NotNil(b2)
assert.Same(b, b2)
})
t.Run("The Default name generator is identity", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
fs, err := NewFilestore(tmpDir)
assert.NoError(err)
b := fs.Bucket(bucket)
assert.NotNil(b)
assert.Equal(nameGenAddress(NameIdentity), nameGenAddress(b.nameGenerator))
})
t.Run("A custom name generator can be used", func(t *testing.T) {
t.Parallel()
nameGen := func(s string) (string, error) {
return s, nil
}
assert := require.New(t)
tmpDir := t.TempDir()
fs, err := NewFilestore(tmpDir, WithNameGen(nameGen))
assert.NoError(err)
b := fs.Bucket(bucket)
assert.NotNil(b)
assert.Equal(nameGenAddress(nameGen), nameGenAddress(b.nameGenerator))
})
}
func TestBucket(t *testing.T) {
t.Parallel()
const bucket = "foo"
t.Run("the name generator of a bucket can be overloaded", func(t *testing.T) {
t.Parallel()
nameGen := func(s string) (string, error) {
return s, nil
}
assert := require.New(t)
tmpDir := t.TempDir()
fs, err := NewFilestore(tmpDir)
assert.NoError(err)
b := fs.Bucket(bucket)
assert.NotNil(b)
b.SetNameGenerator(nameGen)
assert.NotEqual(nameGenAddress(fs.nameGenerator), nameGenAddress(b.nameGenerator))
assert.Equal(nameGenAddress(nameGen), nameGenAddress(b.nameGenerator))
})
}
func TestBucketPut(t *testing.T) {
t.Parallel()
const (
bucket = "foo"
filename = "foo.bar"
)
t.Run("It should store a file", func(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
fs := initStore(t, tmpDir)
assert := require.New(t)
b := fs.Bucket(bucket)
fr := strings.NewReader(filecontent)
name, err := b.Put(filename, fr)
assert.NoError(err, "cannot store file")
assert.Equal("foo.bar", name)
assert.FileExists(filepath.Join(tmpDir, bucket, filename),
"the file does not exist on disk")
defer b.Delete(filename)
assert.True(b.Exists(name),
"the file does not exist in the store")
})
t.Run("It returns an error if the name cannot be generated", func(t *testing.T) {
t.Parallel()
errNameGen := func(_ string) (string, error) {
return "", errFilenameGeneration
}
tmpDir := t.TempDir()
assert := require.New(t)
fs, err := NewFilestore(tmpDir, WithNameGen(errNameGen))
assert.NoError(err)
fr := strings.NewReader(filecontent)
name, err := fs.Bucket(bucket).Put(filename, fr)
assert.Error(err)
assert.Equal("", name)
})
t.Run("It returns an error if the bucket dir cannot be created", func(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
assert := require.New(t)
fs, err := NewFilestore(tmpDir)
assert.NoError(err)
err = os.WriteFile(filepath.Join(tmpDir, bucket), []byte("foo"), 0o644)
assert.NoError(err)
err = os.Chmod(tmpDir, 0o400)
assert.NoError(err)
defer func() {
err = os.Chmod(tmpDir, 0o700)
assert.NoError(err)
}()
fr := strings.NewReader(filecontent)
name, err := fs.Bucket(bucket).Put(filename, fr)
assert.Error(err)
assert.Equal("", name)
})
t.Run("It returns an error if the file cannot be opened", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
fs, err := NewFilestore(tmpDir)
assert.NoError(err)
bucketDir := filepath.Join(tmpDir, bucket)
err = os.Mkdir(bucketDir, 0o700)
assert.NoError(err)
err = os.Mkdir(filepath.Join(bucketDir, filename), 0o700)
assert.NoError(err)
fr := strings.NewReader(filecontent)
name, err := fs.Bucket(bucket).Put(filename, fr)
assert.Error(err)
assert.Equal("", name)
})
t.Run("It returns an error if the file cannot be written", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
fs, err := NewFilestore(tmpDir)
assert.NoError(err)
fr := errorReader{}
name, err := fs.Bucket(bucket).Put(filename, fr)
assert.Error(err)
assert.Equal("", name)
})
}
func TestBucketGet(t *testing.T) {
t.Parallel()
const (
bucket = "foo"
filename = "foo.bar"
)
t.Run("It should retrieve a file", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
fs := initStore(t, tmpDir)
b := fs.Bucket(bucket)
fullpath := filepath.Join(tmpDir, bucket, filename)
err := os.Mkdir(filepath.Dir(fullpath), 0o700)
assert.NoError(err)
err = os.WriteFile(fullpath, []byte(filecontent), 0o600)
assert.NoError(err)
defer b.Delete(filename)
r, err := b.Get(filename)
assert.NoError(err)
defer func() { _ = r.Close() }()
content, err := io.ReadAll(r)
assert.NoError(err)
assert.Equal(filecontent, string(content))
})
t.Run("It returns an error if the file is not found", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
fs := initStore(t, tmpDir)
b := fs.Bucket(bucket)
r, err := b.Get(filename)
assert.ErrorIs(err, errFileNotFound)
assert.Nil(r)
})
t.Run("It returns an error if the file cannot be read", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
fs := initStore(t, tmpDir)
b := fs.Bucket(bucket)
fullpath := filepath.Join(tmpDir, bucket, filename)
err := os.Mkdir(filepath.Dir(fullpath), 0o700)
assert.NoError(err)
//revive:disable-next-line:add-constant // Only used here for tests
err = os.WriteFile(fullpath, []byte(filecontent), 0o100)
assert.NoError(err)
defer b.Delete(filename)
r, err := b.Get(filename)
assert.Error(err)
assert.Nil(r)
})
}
func TestBucketExists(t *testing.T) {
t.Parallel()
const (
bucket = "foo"
filename = "foo.bar"
)
t.Run("It returns true if the file exists", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
fs := initStore(t, tmpDir)
b := fs.Bucket(bucket)
fullpath := filepath.Join(tmpDir, bucket, filename)
err := os.Mkdir(filepath.Dir(fullpath), 0o700)
assert.NoError(err)
err = os.WriteFile(fullpath, []byte(filecontent), 0o600)
assert.NoError(err)
defer b.Delete(filename)
assert.True(b.Exists(filename))
})
t.Run("It returns false if the file does not exist", func(t *testing.T) {
t.Parallel()
assert := require.New(t)
tmpDir := t.TempDir()
fs := initStore(t, tmpDir)
b := fs.Bucket(bucket)
assert.False(b.Exists(filename))
})
}
func TestBucketDelete(t *testing.T) {
t.Parallel()
const (
bucket = "foo"
filename = "foo.bar"
)
t.Run("Removing a file (happy path)", func(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
fs := initStore(t, tmpDir)
assert := require.New(t)
fr := strings.NewReader(filecontent)
name, err := fs.Bucket(bucket).Put(filename, fr)
assert.NoError(err)
defer fs.Bucket(bucket).Delete(filename)
err = fs.Bucket(bucket).Delete(name)
assert.NoError(err)
assert.NoFileExists(filepath.Join(tmpDir, bucket, filename))
assert.False(fs.Bucket("foo").Exists(name))
})
}