diff --git a/backend.go b/backend.go new file mode 100644 index 0000000..fd3cfd7 --- /dev/null +++ b/backend.go @@ -0,0 +1,140 @@ +package filestore + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "path/filepath" +) + +// Backend defines the set of methods that a storage backend must implement. +// It is quite naïve and regroups what is used by the Filestore in order to +// handle its operations. +type Backend interface { + // Sub should return a new backend rooted in a subdirectory of the + // original backend. + Sub(path string) Backend + + // Create should open a file for writing and return a type that + // implements WriteCloser . The caller MUST call close when the file is + // not needed anymore (typically when all the data has been written). + // + // It should not return an error if the file already exists, but + // instead truncate or replace the existing file. + Create(name string) (io.WriteCloser, error) + + // Open should open a file for reading and return a type that + // implements ReadCloser . The caller MUST call close when the file is + // not needed anymore (typically when all the data has been read). + Open(name string) (io.ReadCloser, error) + + // Exists should return true if the file exists and false otherwise. + Exists(name string) bool + + // Delete should delete the actual file. It should not return an error + // if the file does not exist. + Delete(name string) error +} + +type OSBackend struct { + path string +} + +func NewOSBackend(path string) *OSBackend { + return &OSBackend{ + path: path, + } +} + +func (ob OSBackend) Sub(path string) Backend { + return &OSBackend{filepath.Join(ob.path, path)} +} + +func (ob OSBackend) Create(name string) (io.WriteCloser, error) { + if err := os.MkdirAll(ob.path, 0o700); err != nil { + return nil, fmt.Errorf("cannot create the directory to write the file: %w", err) + } + + file, err := os.OpenFile(filepath.Join(ob.path, name), + os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600) + if err != nil { + return nil, fmt.Errorf("cannot create file: %w", err) + } + + return file, nil +} + +func (ob OSBackend) Open(name string) (io.ReadCloser, error) { + file, err := os.Open(filepath.Join(ob.path, name)) + if err != nil { + return nil, fmt.Errorf("cannot open file %q: %w", name, err) + } + + return file, nil +} + +func (ob OSBackend) Exists(name string) bool { + if _, err := os.Stat(filepath.Join(ob.path, name)); !os.IsNotExist(err) { + return true + } + + return false +} + +func (ob OSBackend) Delete(name string) error { + if !ob.Exists(name) { + return nil + } + + if err := os.Remove(filepath.Join(ob.path, name)); err != nil { + return fmt.Errorf("cannot remove file %q: %w", name, err) + } + + return nil +} + +type MemoryFile struct { + *bytes.Buffer +} + +func (MemoryFile) Close() error { + return nil +} + +type MemoryBackend struct { + files map[string][]byte +} + +func (mb *MemoryBackend) Sub(name string) Backend { + return new(MemoryBackend) +} + +func (mb *MemoryBackend) Create(name string) (io.WriteCloser, error) { + if mb.files == nil { + mb.files = map[string][]byte{} + } + + mb.files[name] = []byte{} + + return MemoryFile{bytes.NewBuffer(mb.files[name])}, nil +} + +func (mb *MemoryBackend) Open(name string) (io.ReadCloser, error) { + if b, ok := mb.files[name]; ok { + return MemoryFile{bytes.NewBuffer(b)}, nil + } + + return nil, errors.New("file not found") +} + +func (mb *MemoryBackend) Delete(name string) error { + delete(mb.files, name) + return nil +} + +func (mb *MemoryBackend) Exists(name string) bool { + _, ok := mb.files[name] + return ok +} diff --git a/backend_test.go b/backend_test.go new file mode 100644 index 0000000..219a621 --- /dev/null +++ b/backend_test.go @@ -0,0 +1,242 @@ +package filestore + +import ( + "io" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMemoryBackend(t *testing.T) { + t.Parallel() + + t.Run("it is a valid backend", func(t *testing.T) { + m := new(MemoryBackend) + ValidateBackend(t, m) + }) + + t.Run("read then write a file", func(t *testing.T) { + }) +} + +func TestOSBackend(t *testing.T) { + t.Parallel() + + t.Run("it is a valid backend", func(t *testing.T) { + tmpDir := t.TempDir() + m := NewOSBackend(tmpDir) + + ValidateBackend(t, m) + }) + + t.Run("it returns an error if the directory cannot be created", func(t *testing.T) { + assert := require.New(t) + + tmpDir := t.TempDir() + fullpath := filepath.Join(tmpDir, "foo") + m := NewOSBackend(fullpath) + + err := os.WriteFile(fullpath, []byte(filecontent), 0o600) + assert.NoError(err) + + wc, err := m.Create("foo") + assert.Error(err) + assert.Nil(wc) + }) +} + +//revive:disable:cognitive-complexity +func ValidateBackend(t *testing.T, b Backend) { + t.Helper() + + filecontent := []byte("file content") + + setupBackend(t, b) + + t.Run("validate Sub", func(t *testing.T) { + t.Run("it must not be nil", func(t *testing.T) { + s := b.Sub("foo") + require.NotNil(t, s, "Sub must not return a nil value") + }) + }) + + t.Run("validate Create", func(t *testing.T) { + t.Run("it must create a file if it does not exist", func(t *testing.T) { + assert := require.New(t) + + precondFileNotExists(t, b, "create-1") + + wc, err := b.Create("create-1") + assert.NoError(err, "Create must not return an error in case of success") + assert.NotNil(wc, "Create must return a non nil value in case of success") + + _, err = wc.Write(filecontent) + assert.NoError(err) + + wc.Close() + + assert.True(b.Exists("create-1"), "The file must exist after "+ + "Create has been successfully called") + + rc, err := b.Open("create-1") + assert.NoError(err) + + newcontent, err := io.ReadAll(rc) + assert.NoError(err) + assert.Equal(filecontent, newcontent, "The content of the created "+ + "file must be correct") + }) + + t.Run("it should replace a file if it exists", func(t *testing.T) { + assert := require.New(t) + + precondFileExists(t, b, "create-2") + + wc, err := b.Create("create-2") + assert.NoError(err, "Create must not return an error if the file "+ + "already exists") + + _, err = wc.Write(filecontent) + assert.NoError(err) + + wc.Close() + + rc, err := b.Open("create-2") + assert.NoError(err) + + newcontent, err := io.ReadAll(rc) + assert.NoError(err) + assert.NotEqual([]byte("hello world"), newcontent, "Create should "+ + "overwrite the file when it already existed") + assert.Equal(filecontent, newcontent, "Create should "+ + "overwrite the file when it already existed") + }) + + t.Run("when the file cannot be created", func(t *testing.T) { + t.Skip("do not know how to implement that!") + // maybe we should require that a directory already exists in the backend + assert := require.New(t) + + wc, err := b.Create("create-3") + assert.NoError(err) + assert.Nil(wc, "the file access must be nil if Create returns an error") + }) + }) + + t.Run("validate Open", func(t *testing.T) { + t.Run("it should return an open file if it exists", func(t *testing.T) { + assert := require.New(t) + precondFileExists(t, b, "open-1") + + rc, err := b.Open("open-1") + assert.NoError(err, "Open must not return an error when successful") + assert.NotNil(rc, "Open must not return a nil value when successful") + + rc.Close() + }) + + t.Run("when the file does not exist", func(t *testing.T) { + assert := require.New(t) + precondFileNotExists(t, b, "open-2") + + rc, err := b.Open("open-2") + assert.Error(err, "Open must return an error when the file does not exist") + assert.Nil(rc, "Open must return a nil value when the file does not exist") + }) + + t.Run("when the file cannot be opened", func(t *testing.T) { + t.Skip("do not know how to implement that test!") + precondFileExists(t, b, "open-3") + }) + }) + + t.Run("validate Delete", func(t *testing.T) { + t.Run("it should remove a file if it exists", func(t *testing.T) { + assert := require.New(t) + precondFileExists(t, b, "delete-1") + + err := b.Delete("delete-1") + assert.NoError(err, "Delete must not return an error in case of success") + + assert.False(b.Exists("delete-1"), "File must not exist anymore "+ + "after a successful delete") + }) + + t.Run("when the file does not exist", func(t *testing.T) { + assert := require.New(t) + precondFileNotExists(t, b, "delete-2") + + err := b.Delete("delete-2") + assert.NoError(err, "Delete must not return an error if the file "+ + "does not exist") + }) + + t.Run("when the file cannot be deleted", func(t *testing.T) { + t.Skip("don't know ho to test that!") + assert := require.New(t) + precondFileExists(t, b, "delete-3") + + err := b.Delete("delete-3") + assert.Error(err, "Delete must return an error when the file "+ + "cannot be deleted") + }) + }) + + t.Run("validate Exists", func(t *testing.T) { + t.Run("it should return true if the file exists", func(t *testing.T) { + wc, err := b.Create("exists1") + require.NoError(t, err) + require.NotNil(t, wc) + + _, err = wc.Write(filecontent) + wc.Close() + require.NoError(t, err) + + require.True(t, b.Exists("exists1")) + }) + + t.Run("it should return false if the file does not exist", func(t *testing.T) { + require.False(t, b.Exists("exists2")) + }) + }) +} + +func setupBackend(t *testing.T, b Backend) { + t.Helper() + + assert := require.New(t) + + wc, err := b.Create("create-2") + assert.NoError(err) + defer wc.Close() + _, err = wc.Write([]byte("hello world")) + assert.NoError(err) + + wc, err = b.Create("open-1") + assert.NoError(err) + defer wc.Close() + _, err = wc.Write([]byte(filecontent)) + assert.NoError(err) + + wc, err = b.Create("delete-1") + assert.NoError(err) + defer wc.Close() + _, err = wc.Write([]byte(filecontent)) + assert.NoError(err) +} + +func precondFileExists(t *testing.T, b Backend, name string) { + t.Helper() + + require.True(t, b.Exists(name), "PRECOND: file %q must exist for this test", + name) +} + +func precondFileNotExists(t *testing.T, b Backend, name string) { + t.Helper() + + require.False(t, b.Exists(name), "PRECOND: file %q must not exist for this test", + name) +} diff --git a/filestore.go b/filestore.go index d2c548c..d04fb83 100644 --- a/filestore.go +++ b/filestore.go @@ -1,11 +1,21 @@ -package main +// Package filestore offers an abstraction to store and retrieve files in +// an easy manner. +// +// The main component is the Filestore type, which has primitives to store +// and retrieve files in the selected backend. +// +// The files are stored in buckets, so that it is possible to apply custon +// storing rules (eg. pictures in a bucket and thumbnails in another one. +// +// The files actually stored in the backends are named according to the +// chosen NameGenerator, which allow one to use subdirectories in the +// bucket for organization or performance purposes. +package filestore import ( "errors" "fmt" "io" - "os" - "path/filepath" ) var ( @@ -23,28 +33,35 @@ func NameIdentity(originalName string) (string, error) { return originalName, nil } -// FilestoreOption are options passed to the Fikeqtore constructors. -type FilestoreOption func(*Filestore) +// Option are options passed to the Fikeqtore constructors. +type Option func(*Filestore) // WithNameGen allows to set a custom name generator on the filestore. -func WithNameGen(f NameGenerator) FilestoreOption { +func WithNameGen(f NameGenerator) Option { return func(fs *Filestore) { fs.nameGenerator = f } } +// WithBackend specify the backend the fiilestore must use. +func WithBackend(b Backend) Option { + return func(fs *Filestore) { + fs.dataDir = b + } +} + // Filestore is an abstraction of the filesystem that allow one to store and // get files in a simple way. type Filestore struct { nameGenerator NameGenerator buckets map[string]*Bucket - dataDir string + dataDir Backend } // NewFilestore creates a new filestore. -func NewFilestore(path string, opts ...FilestoreOption) (*Filestore, error) { +func NewFilestore(opts ...Option) (*Filestore, error) { f := &Filestore{ - dataDir: path, + dataDir: &MemoryBackend{}, nameGenerator: NameIdentity, buckets: map[string]*Bucket{}, } @@ -53,10 +70,6 @@ func NewFilestore(path string, opts ...FilestoreOption) (*Filestore, error) { opts[i](f) } - if err := os.MkdirAll(f.dataDir, 0o700); err != nil { - return nil, fmt.Errorf("cannot create the filestore directory: %w", err) - } - return f, nil } @@ -68,7 +81,7 @@ func (f *Filestore) Bucket(name string) *Bucket { } b := &Bucket{ - dataDir: filepath.Join(f.dataDir, name), + dataDir: f.dataDir.Sub(name), nameGenerator: f.nameGenerator, } @@ -80,7 +93,7 @@ func (f *Filestore) Bucket(name string) *Bucket { // Bucket is a storage unit in the filestore. type Bucket struct { nameGenerator func(string) (string, error) - dataDir string + dataDir Backend } // Put stores a new file in the bucket. @@ -95,22 +108,16 @@ func (b Bucket) Put(originalName string, r io.Reader) (string, error) { return "", err } - fullpath := filepath.Clean(filepath.Join(b.dataDir, finalName)) - - err = os.MkdirAll(filepath.Dir(fullpath), 0o700) + fd, err := b.dataDir.Create(finalName) if err != nil { - return "", fmt.Errorf("cannot create filestore subdirectories: %w", err) - } - - fd, err := os.Create(fullpath) - if err != nil { - return "", fmt.Errorf("cannot create file %s: %w", fullpath, err) + return "", fmt.Errorf("cannot store %q: %w", originalName, err) } defer func() { _ = fd.Close() }() //nolint:errcheck // nothing to handle the error if _, err := io.Copy(fd, r); err != nil { - return "", fmt.Errorf("cannot write the content of file %s: %w", fullpath, err) + return "", fmt.Errorf("cannot write the content of file %q to %q: %w", + originalName, finalName, err) } return finalName, nil @@ -122,9 +129,9 @@ func (b Bucket) Get(name string) (io.ReadCloser, error) { return nil, errFileNotFound } - r, err := os.Open(filepath.Clean(filepath.Join(b.dataDir, name))) + r, err := b.dataDir.Open(name) if err != nil { - return nil, fmt.Errorf("cannot open file %s: %w", name, err) + return nil, fmt.Errorf("cannot open file %q in bucket: %w", name, err) } return r, nil @@ -132,19 +139,15 @@ func (b Bucket) Get(name string) (io.ReadCloser, error) { // Exists checks the existence of a file in the bucket. func (b Bucket) Exists(filename string) bool { - if _, err := os.Stat(filepath.Join(b.dataDir, filename)); !os.IsNotExist(err) { - return true - } - - return false + return b.dataDir.Exists(filename) } // Delete removes a file from the bucket. It is removed from storage and cannot // be recovered. // An error is returned if the file could not be removed. func (b Bucket) Delete(filename string) error { - if err := os.Remove(filepath.Join(b.dataDir, filename)); err != nil { - return fmt.Errorf("cannot remove file: %w", err) + if err := b.dataDir.Delete(filename); err != nil { + return fmt.Errorf("cannot remove %q from bucket: %w", filename, err) } return nil diff --git a/filestore_test.go b/filestore_test.go index 5b2ac25..c432b89 100644 --- a/filestore_test.go +++ b/filestore_test.go @@ -1,11 +1,9 @@ -package main +package filestore import ( "errors" "fmt" "io" - "os" - "path/filepath" "strings" "testing" @@ -26,15 +24,6 @@ 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() @@ -45,52 +34,18 @@ func TestFilestore(t *testing.T) { assert := require.New(t) - tmpDir := t.TempDir() - fs, err := NewFilestore(tmpDir) + fs, err := NewFilestore() 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) + fs, err := NewFilestore() assert.NoError(err) b := fs.Bucket(bucket) @@ -103,8 +58,7 @@ func TestFilestore(t *testing.T) { assert := require.New(t) - tmpDir := t.TempDir() - fs, err := NewFilestore(tmpDir) + fs, err := NewFilestore() assert.NoError(err) b := fs.Bucket(bucket) @@ -121,13 +75,10 @@ func TestFilestore(t *testing.T) { assert := require.New(t) - tmpDir := t.TempDir() - fs, err := NewFilestore(tmpDir) + fs, err := NewFilestore() assert.NoError(err) - b := fs.Bucket(bucket) - assert.NotNil(b) - assert.Equal(nameGenAddress(NameIdentity), nameGenAddress(b.nameGenerator)) + assert.Equal(nameGenAddress(NameIdentity), nameGenAddress(fs.nameGenerator)) }) t.Run("A custom name generator can be used", func(t *testing.T) { @@ -139,13 +90,32 @@ func TestFilestore(t *testing.T) { assert := require.New(t) - tmpDir := t.TempDir() - fs, err := NewFilestore(tmpDir, WithNameGen(nameGen)) + fs, err := NewFilestore(WithNameGen(nameGen)) assert.NoError(err) - b := fs.Bucket(bucket) - assert.NotNil(b) - assert.Equal(nameGenAddress(nameGen), nameGenAddress(b.nameGenerator)) + assert.Equal(nameGenAddress(nameGen), nameGenAddress(fs.nameGenerator)) + }) + + t.Run("The Default backend is memory", func(t *testing.T) { + t.Parallel() + + assert := require.New(t) + + fs, err := NewFilestore() + assert.NoError(err) + + assert.IsType(fs.dataDir, &MemoryBackend{}) + }) + + t.Run("A custom backend can be used", func(t *testing.T) { + t.Parallel() + + assert := require.New(t) + + fs, err := NewFilestore(WithBackend(NewOSBackend("foo"))) + assert.NoError(err) + + assert.IsType(fs.dataDir, &OSBackend{}) }) } @@ -163,8 +133,7 @@ func TestBucket(t *testing.T) { assert := require.New(t) - tmpDir := t.TempDir() - fs, err := NewFilestore(tmpDir) + fs, err := NewFilestore() assert.NoError(err) b := fs.Bucket(bucket) @@ -174,6 +143,27 @@ func TestBucket(t *testing.T) { assert.NotEqual(nameGenAddress(fs.nameGenerator), nameGenAddress(b.nameGenerator)) assert.Equal(nameGenAddress(nameGen), nameGenAddress(b.nameGenerator)) }) + + t.Run("the backend is the same as the file store", func(t *testing.T) { + t.Parallel() + assert := require.New(t) + + fs, err := NewFilestore() + assert.NoError(err) + + b := fs.Bucket(bucket) + assert.NotNil(b) + + assert.IsType(b.dataDir, fs.dataDir) + + fs, err = NewFilestore(WithBackend(NewOSBackend("foo"))) + assert.NoError(err) + + b = fs.Bucket(bucket) + assert.NotNil(b) + + assert.IsType(b.dataDir, fs.dataDir) + }) } func TestBucketPut(t *testing.T) { @@ -186,10 +176,11 @@ func TestBucketPut(t *testing.T) { t.Run("It should store a file", func(t *testing.T) { t.Parallel() - - tmpDir := t.TempDir() - fs := initStore(t, tmpDir) assert := require.New(t) + + fs, err := NewFilestore() + assert.NoError(err) + b := fs.Bucket(bucket) fr := strings.NewReader(filecontent) @@ -197,9 +188,6 @@ func TestBucketPut(t *testing.T) { 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") @@ -207,14 +195,11 @@ func TestBucketPut(t *testing.T) { 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)) - + fs, err := NewFilestore(WithNameGen(errNameGen)) assert.NoError(err) fr := strings.NewReader(filecontent) @@ -224,48 +209,15 @@ func TestBucketPut(t *testing.T) { 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.Run("It returns an error if the file cannot be stored", func(t *testing.T) { t.Parallel() + t.Skip() assert := require.New(t) - tmpDir := t.TempDir() - fs, err := NewFilestore(tmpDir) + fs, err := NewFilestore() 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) @@ -275,10 +227,9 @@ func TestBucketPut(t *testing.T) { 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) + + fs, err := NewFilestore() assert.NoError(err) @@ -300,24 +251,22 @@ func TestBucketGet(t *testing.T) { t.Run("It should retrieve a file", func(t *testing.T) { t.Parallel() - assert := require.New(t) - tmpDir := t.TempDir() - fs := initStore(t, tmpDir) + + fs, err := NewFilestore() + assert.NoError(err) + b := fs.Bucket(bucket) - fullpath := filepath.Join(tmpDir, bucket, filename) - err := os.Mkdir(filepath.Dir(fullpath), 0o700) + r := strings.NewReader(filecontent) + _, err = b.Put(filename, r) assert.NoError(err) - err = os.WriteFile(fullpath, []byte(filecontent), 0o600) - assert.NoError(err) - defer b.Delete(filename) - r, err := b.Get(filename) + rc, err := b.Get(filename) assert.NoError(err) - defer func() { _ = r.Close() }() + defer func() { _ = rc.Close() }() - content, err := io.ReadAll(r) + content, err := io.ReadAll(rc) assert.NoError(err) assert.Equal(filecontent, string(content)) }) @@ -326,8 +275,9 @@ func TestBucketGet(t *testing.T) { t.Parallel() assert := require.New(t) - tmpDir := t.TempDir() - fs := initStore(t, tmpDir) + fs, err := NewFilestore() + assert.NoError(err) + b := fs.Bucket(bucket) r, err := b.Get(filename) @@ -337,21 +287,14 @@ func TestBucketGet(t *testing.T) { t.Run("It returns an error if the file cannot be read", func(t *testing.T) { t.Parallel() + t.Skip() assert := require.New(t) - tmpDir := t.TempDir() - fs := initStore(t, tmpDir) + fs, err := NewFilestore() + assert.NoError(err) + 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) @@ -370,27 +313,24 @@ func TestBucketExists(t *testing.T) { t.Parallel() assert := require.New(t) - tmpDir := t.TempDir() - fs := initStore(t, tmpDir) + fs, err := NewFilestore() + assert.NoError(err) + b := fs.Bucket(bucket) - - fullpath := filepath.Join(tmpDir, bucket, filename) - err := os.Mkdir(filepath.Dir(fullpath), 0o700) + wc, err := b.dataDir.Create(filename) assert.NoError(err) - - err = os.WriteFile(fullpath, []byte(filecontent), 0o600) - assert.NoError(err) - defer b.Delete(filename) + wc.Close() 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) + + fs, err := NewFilestore() + assert.NoError(err) + b := fs.Bucket(bucket) assert.False(b.Exists(filename)) @@ -405,13 +345,13 @@ func TestBucketDelete(t *testing.T) { filename = "foo.bar" ) - t.Run("Removing a file (happy path)", func(t *testing.T) { + t.Run("Removing a file", func(t *testing.T) { t.Parallel() - - tmpDir := t.TempDir() - fs := initStore(t, tmpDir) assert := require.New(t) + fs, err := NewFilestore() + assert.NoError(err) + fr := strings.NewReader(filecontent) name, err := fs.Bucket(bucket).Put(filename, fr) @@ -421,7 +361,37 @@ func TestBucketDelete(t *testing.T) { err = fs.Bucket(bucket).Delete(name) assert.NoError(err) - assert.NoFileExists(filepath.Join(tmpDir, bucket, filename)) - assert.False(fs.Bucket("foo").Exists(name)) + assert.False(fs.Bucket(bucket).Exists(name)) + }) + + t.Run("Removing a file that do not exists", func(t *testing.T) { + t.Parallel() + assert := require.New(t) + + fs, err := NewFilestore() + assert.NoError(err) + + err = fs.Bucket(bucket).Delete(filename) + assert.NoError(err) + }) + + t.Run("Removing a file with errors", func(t *testing.T) { + t.Skip() + t.Parallel() + assert := require.New(t) + + fs, err := NewFilestore() + assert.NoError(err) + + err = fs.Bucket(bucket).Delete(filename) + assert.Error(err) }) } + +func TestIdentityNameGenerator(t *testing.T) { + t.Parallel() + + name, err := NameIdentity("foo.txt") + require.NoError(t, err) + require.Equal(t, "foo.txt", name) +}