Import source: handler/fs/fs
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path"
|
||||
"reactor-crw"
|
||||
)
|
||||
|
||||
// PathResolver defines a simple interface to resolve path for content
|
||||
// before saving it.
|
||||
type pathResolver interface {
|
||||
// CreateFolder creates a new folder in the "current" dir. The current
|
||||
// directory is specified in Config.SavePath provided by a user. If dir
|
||||
// with such name already exists it'll skip creation step.
|
||||
CreateFolder(name string) error
|
||||
|
||||
// CreateFile creates a new file for downloaded content in the directory
|
||||
// created by the CreateFolder function and returns the corresponding
|
||||
// io.WriteCloser. If a file with such a name already exists then it'll
|
||||
// be overwritten.
|
||||
CreateFile(name string) (io.WriteCloser, error)
|
||||
|
||||
// Remove removes a file or folder by its name. If provided path does not
|
||||
// exist it'll be skipped.
|
||||
Remove(name string)
|
||||
}
|
||||
|
||||
// FileSaver defines ContentHandler implementation that will download content
|
||||
// and save it to the host's file system. It resolves the corresponding file
|
||||
// path with PathResolver.
|
||||
type FileSaver struct {
|
||||
pr pathResolver
|
||||
t reactor_crw.Transport
|
||||
}
|
||||
|
||||
// NewFileSaver creates a new FileSaver instance along with a new folder that
|
||||
// will act as a context for a new instance. All files will be processed within
|
||||
// this new folder.
|
||||
func NewFileSaver(pr pathResolver, t reactor_crw.Transport, baseFolder string) (*FileSaver, error) {
|
||||
err := pr.CreateFolder(baseFolder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FileSaver{
|
||||
pr: pr,
|
||||
t: t,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Process downloads content by the corresponding URL by making an HTTP request
|
||||
// and saves the result to the file system. In case of error during saving the
|
||||
// content the corresponding file will be deleted from the file system.
|
||||
func (f *FileSaver) Process(url string, progress chan<- int, e chan<- error) {
|
||||
defer func() {
|
||||
progress <- 1
|
||||
}()
|
||||
|
||||
data, err := f.t.FetchData(url)
|
||||
if err != nil {
|
||||
e <- err
|
||||
return
|
||||
}
|
||||
|
||||
defer func(b io.ReadCloser) {
|
||||
_ = b.Close()
|
||||
}(data)
|
||||
|
||||
file, err := f.pr.CreateFile(path.Base(url))
|
||||
if err != nil {
|
||||
e <- err
|
||||
return
|
||||
}
|
||||
|
||||
defer func(f io.WriteCloser) {
|
||||
_ = f.Close()
|
||||
}(file)
|
||||
|
||||
_, err = io.Copy(file, data)
|
||||
if err != nil {
|
||||
f.pr.Remove(path.Base(url))
|
||||
e <- err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
//go:build unit
|
||||
// +build unit
|
||||
|
||||
package fs_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reactor-crw/handler/fs"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type pathResolverMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *pathResolverMock) CreateFolder(name string) error {
|
||||
args := m.Called(name)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *pathResolverMock) CreateFile(name string) (io.WriteCloser, error) {
|
||||
args := m.Called(name)
|
||||
return args.Get(0).(io.WriteCloser), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *pathResolverMock) Remove(name string) {
|
||||
_ = m.Called(name)
|
||||
}
|
||||
|
||||
type transportMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *transportMock) FetchData(url string) (io.ReadCloser, error) {
|
||||
args := m.Called(url)
|
||||
return args.Get(0).(io.ReadCloser), args.Error(1)
|
||||
}
|
||||
|
||||
func TestNewFileSaver(t *testing.T) {
|
||||
pr := pathResolverMock{}
|
||||
trp := transportMock{}
|
||||
|
||||
t.Log("Given the need to create a file saver handler.")
|
||||
{
|
||||
t.Log("When baseFolder is incorrect.")
|
||||
{
|
||||
pr.On("CreateFolder", "baseFolder").Return(errors.New("error"))
|
||||
_, err := fs.NewFileSaver(&pr, &trp, "baseFolder")
|
||||
require.Error(t, err, "Expected an error during create folder")
|
||||
pr.ExpectedCalls = []*mock.Call{}
|
||||
}
|
||||
|
||||
t.Log("When all data correct.")
|
||||
{
|
||||
pr.On("CreateFolder", "baseFolder").Return(nil)
|
||||
_, err := fs.NewFileSaver(&pr, &trp, "baseFolder")
|
||||
require.NoError(t, err, "Wasn't expected an error on new creating file saver")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSaver_Process(t *testing.T) {
|
||||
tmlFile, _ := ioutil.TempFile(os.TempDir(), "file-title.txt")
|
||||
|
||||
pr := pathResolverMock{}
|
||||
pr.On("CreateFolder", "baseFolder").Return(nil)
|
||||
pr.On("Remove", mock.Anything)
|
||||
|
||||
trp := transportMock{}
|
||||
|
||||
p := make(chan int, 1)
|
||||
e := make(chan error, 1)
|
||||
|
||||
fileSaver, _ := fs.NewFileSaver(&pr, &trp, "baseFolder")
|
||||
|
||||
t.Log("Given the need to process data.")
|
||||
{
|
||||
t.Log("When transport returns an error.")
|
||||
{
|
||||
trp.On("FetchData", "file-title.txt").Return(tmlFile, errors.New("error")).Once()
|
||||
|
||||
fileSaver.Process("file-title.txt", p, e)
|
||||
require.Error(t, <-e, "Expected an error during create file")
|
||||
<-p
|
||||
}
|
||||
|
||||
t.Log("When path resolver returns an error.")
|
||||
{
|
||||
trp.On("FetchData", "file-title.txt").Return(tmlFile, nil).Once()
|
||||
pr.On("CreateFile", "file-title.txt").Return(tmlFile, errors.New("error")).Once()
|
||||
|
||||
fileSaver.Process("file-title.txt", p, e)
|
||||
require.Error(t, <-e, "Expected an error during create file")
|
||||
<-p
|
||||
}
|
||||
|
||||
t.Log("When cannot save the data.")
|
||||
{
|
||||
trp.On("FetchData", "file-title.txt").Return(tmlFile, nil).Once()
|
||||
pr.On("CreateFile", "file-title.txt").Return(tmlFile, nil).Once()
|
||||
pr.On("Remove", mock.Anything)
|
||||
|
||||
fileSaver.Process("file-title.txt", p, e)
|
||||
require.Error(t, <-e, "Expected an error file copying")
|
||||
<-p
|
||||
}
|
||||
|
||||
t.Log("When all data correct.")
|
||||
{
|
||||
tmlFile, _ = ioutil.TempFile(os.TempDir(), "new-file-title.txt")
|
||||
|
||||
trp.On("FetchData", "new-file-title.txt").Return(tmlFile, nil).Once()
|
||||
pr.On("CreateFile", "new-file-title.txt").Return(tmlFile, nil).Once()
|
||||
|
||||
fileSaver.Process("new-file-title.txt", p, e)
|
||||
select {
|
||||
case err := <-e:
|
||||
require.NoError(t, err, "Wasn't expected an error on new file process")
|
||||
case <-p:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user