Add code
This commit is contained in:
parent
616a5d9aba
commit
73e0b22fb4
7 changed files with 2302 additions and 0 deletions
72
.gitlab-ci.yml
Normal file
72
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,72 @@
|
|||
image: golang:latest
|
||||
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- release
|
||||
|
||||
variables:
|
||||
GOPATH: "${CI_PROJECT_DIR}/.gocache"
|
||||
GOLANGCI_LINT_CACHE: "${GOPATH}/golangci-lint_cache"
|
||||
GOCACHE: "${GOPATH}/go-build"
|
||||
|
||||
.cache: &depscache
|
||||
key: $CI_COMMIT_REF_SLUG
|
||||
paths:
|
||||
- $CI_PROJECT_DIR/.gocache
|
||||
|
||||
code_navigation:
|
||||
stage: test
|
||||
image: sourcegraph/lsif-go:latest
|
||||
allow_failure: true # recommended
|
||||
script:
|
||||
- lsif-go --no-animation
|
||||
- ls -lh dump.lsif
|
||||
artifacts:
|
||||
reports:
|
||||
lsif: dump.lsif
|
||||
|
||||
lint:
|
||||
stage: test
|
||||
image: golangci/golangci-lint:latest
|
||||
script:
|
||||
- golangci-lint cache status
|
||||
- golangci-lint run --timeout 5m --out-format junit-xml > lint.junit.xml
|
||||
cache:
|
||||
<<: *depscache
|
||||
policy: pull-push
|
||||
artifacts:
|
||||
reports:
|
||||
junit: lint.junit.xml
|
||||
|
||||
tests:
|
||||
stage: test
|
||||
script:
|
||||
- export PATH="$PATH:$GOPATH/bin"
|
||||
- go install gotest.tools/gotestsum@latest
|
||||
- gotestsum --junitfile tests.junit.xml -- -coverprofile=coverage.txt -covermode atomic -race ./...
|
||||
after_script:
|
||||
- export PATH="$PATH:$GOPATH/bin"
|
||||
- go install github.com/boumenot/gocover-cobertura@latest
|
||||
- gocover-cobertura < coverage.txt > coverage.xml
|
||||
- go tool cover -func=coverage.txt | grep "total:"
|
||||
coverage: '/total:\s+\(statements\)\s+(\d+.\d+\%)/'
|
||||
cache:
|
||||
<<: *depscache
|
||||
policy: pull-push
|
||||
artifacts:
|
||||
reports:
|
||||
cobertura: coverage.xml
|
||||
junit: tests.junit.xml
|
||||
|
||||
release:
|
||||
stage: release
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
script:
|
||||
- |
|
||||
release-cli create \
|
||||
--name "${CI_COMMIT_TAG}" \
|
||||
--tag-name "${CI_COMMIT_TAG}" \
|
||||
--milestone "${CI_COMMIT_TAG}"
|
1626
.golangci.yml
Normal file
1626
.golangci.yml
Normal file
File diff suppressed because it is too large
Load diff
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 go
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
156
filestore.go
Normal file
156
filestore.go
Normal file
|
@ -0,0 +1,156 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
errFileNotFound = errors.New("file not found")
|
||||
errFilenameGeneration = errors.New("cannot generate filename")
|
||||
)
|
||||
|
||||
// NameGenerator is the type of the functions that generate the filenames
|
||||
// under which the files are stored on disk from thz name the user passed
|
||||
// as argument.
|
||||
type NameGenerator func(string) (string, error)
|
||||
|
||||
// NameIdentity returns the original filename unchanged.
|
||||
func NameIdentity(originalName string) (string, error) {
|
||||
return originalName, nil
|
||||
}
|
||||
|
||||
// FilestoreOption are options passed to the Fikeqtore constructors.
|
||||
type FilestoreOption func(*Filestore)
|
||||
|
||||
// WithNameGen allows to set a custom name generator on the filestore.
|
||||
func WithNameGen(f NameGenerator) FilestoreOption {
|
||||
return func(fs *Filestore) {
|
||||
fs.nameGenerator = f
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// NewFilestore creates a new filestore.
|
||||
func NewFilestore(path string, opts ...FilestoreOption) (*Filestore, error) {
|
||||
f := &Filestore{
|
||||
dataDir: path,
|
||||
nameGenerator: NameIdentity,
|
||||
buckets: map[string]*Bucket{},
|
||||
}
|
||||
|
||||
for i := range opts {
|
||||
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
|
||||
}
|
||||
|
||||
// Bucket gets a bucket from the filestore. If the asked bucket does not exist,
|
||||
// it is created.
|
||||
func (f *Filestore) Bucket(name string) *Bucket {
|
||||
if b, ok := f.buckets[name]; ok {
|
||||
return b
|
||||
}
|
||||
|
||||
b := &Bucket{
|
||||
dataDir: filepath.Join(f.dataDir, name),
|
||||
nameGenerator: f.nameGenerator,
|
||||
}
|
||||
|
||||
f.buckets[name] = b
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// Bucket is a storage unit in the filestore.
|
||||
type Bucket struct {
|
||||
nameGenerator func(string) (string, error)
|
||||
dataDir string
|
||||
}
|
||||
|
||||
// Put stores a new file in the bucket.
|
||||
func (b Bucket) Put(originalName string, r io.Reader) (string, error) {
|
||||
var (
|
||||
finalName string
|
||||
err error
|
||||
)
|
||||
|
||||
finalName, err = b.nameGenerator(originalName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fullpath := filepath.Clean(filepath.Join(b.dataDir, finalName))
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(fullpath), 0o700)
|
||||
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)
|
||||
}
|
||||
|
||||
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 finalName, nil
|
||||
}
|
||||
|
||||
// Get retrieves a file from the bucket.
|
||||
func (b Bucket) Get(name string) (io.ReadCloser, error) {
|
||||
if !b.Exists(name) {
|
||||
return nil, errFileNotFound
|
||||
}
|
||||
|
||||
r, err := os.Open(filepath.Clean(filepath.Join(b.dataDir, name)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open file %s: %w", name, err)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetNameGenerator sets the nameGenerator for this Bucket.
|
||||
func (b *Bucket) SetNameGenerator(g func(string) (string, error)) {
|
||||
b.nameGenerator = g
|
||||
}
|
403
filestore_test.go
Normal file
403
filestore_test.go
Normal file
|
@ -0,0 +1,403 @@
|
|||
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("Initialize a store in a directory that can't be written", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := require.New(t)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
tmpDir = filepath.Join(tmpDir, "unwritable")
|
||||
|
||||
err := os.Mkdir(tmpDir, 0o400)
|
||||
assert.NoError(err)
|
||||
|
||||
tmpDir = filepath.Join(tmpDir, "subdir")
|
||||
|
||||
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.Chmod(tmpDir, 0o400)
|
||||
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, 0o400)
|
||||
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))
|
||||
})
|
||||
}
|
11
go.mod
Normal file
11
go.mod
Normal file
|
@ -0,0 +1,11 @@
|
|||
module code.bcarlin.xyz/go/filestore
|
||||
|
||||
go 1.18
|
||||
|
||||
require github.com/stretchr/testify v1.7.1
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 // indirect
|
||||
)
|
13
go.sum
Normal file
13
go.sum
Normal file
|
@ -0,0 +1,13 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 h1:dbuHpmKjkDzSOMKAWl10QNlgaZUd3V1q99xc81tt2Kc=
|
||||
gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
Loading…
Reference in a new issue