2015-12-25 21:33:27 +00:00
|
|
|
package tusd_test
|
2015-02-09 18:37:06 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
2015-12-25 21:33:27 +00:00
|
|
|
|
2016-01-20 15:40:13 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
2015-12-25 21:33:27 +00:00
|
|
|
. "github.com/tus/tusd"
|
2015-02-09 18:37:06 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type patchStore struct {
|
|
|
|
zeroStore
|
2016-01-20 15:40:13 +00:00
|
|
|
t *assert.Assertions
|
2015-02-09 18:37:06 +00:00
|
|
|
called bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s patchStore) GetInfo(id string) (FileInfo, error) {
|
|
|
|
if id != "yes" {
|
|
|
|
return FileInfo{}, os.ErrNotExist
|
|
|
|
}
|
|
|
|
|
|
|
|
return FileInfo{
|
2016-03-12 21:44:54 +00:00
|
|
|
ID: id,
|
2015-02-09 18:37:06 +00:00
|
|
|
Offset: 5,
|
2016-03-12 21:44:54 +00:00
|
|
|
Size: 10,
|
2015-02-09 18:37:06 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2015-03-23 18:02:12 +00:00
|
|
|
func (s patchStore) WriteChunk(id string, offset int64, src io.Reader) (int64, error) {
|
2016-01-20 15:40:13 +00:00
|
|
|
s.t.False(s.called, "WriteChunk must be called only once")
|
2015-02-09 18:37:06 +00:00
|
|
|
s.called = true
|
|
|
|
|
2016-01-20 15:40:13 +00:00
|
|
|
s.t.Equal(int64(5), offset)
|
2015-02-09 18:37:06 +00:00
|
|
|
|
|
|
|
data, err := ioutil.ReadAll(src)
|
2016-01-20 15:40:13 +00:00
|
|
|
s.t.Nil(err)
|
|
|
|
s.t.Equal("hello", string(data))
|
2015-02-09 18:37:06 +00:00
|
|
|
|
2015-03-23 18:02:12 +00:00
|
|
|
return 5, nil
|
2015-02-09 18:37:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestPatch(t *testing.T) {
|
2016-03-12 21:44:54 +00:00
|
|
|
a := assert.New(t)
|
|
|
|
|
2015-02-09 18:37:06 +00:00
|
|
|
handler, _ := NewHandler(Config{
|
|
|
|
MaxSize: 100,
|
|
|
|
DataStore: patchStore{
|
2016-03-12 21:44:54 +00:00
|
|
|
t: a,
|
2015-02-09 18:37:06 +00:00
|
|
|
},
|
2016-03-12 21:44:54 +00:00
|
|
|
NotifyCompleteUploads: true,
|
2015-02-09 18:37:06 +00:00
|
|
|
})
|
|
|
|
|
2016-03-12 21:44:54 +00:00
|
|
|
c := make(chan FileInfo, 1)
|
|
|
|
handler.CompleteUploads = c
|
|
|
|
|
2015-02-17 14:44:12 +00:00
|
|
|
(&httpTest{
|
|
|
|
Name: "Successful request",
|
|
|
|
Method: "PATCH",
|
|
|
|
URL: "yes",
|
|
|
|
ReqHeader: map[string]string{
|
2015-03-23 17:15:05 +00:00
|
|
|
"Tus-Resumable": "1.0.0",
|
2015-11-04 09:56:32 +00:00
|
|
|
"Content-Type": "application/offset+octet-stream",
|
2015-03-23 17:15:05 +00:00
|
|
|
"Upload-Offset": "5",
|
2015-02-17 14:44:12 +00:00
|
|
|
},
|
|
|
|
ReqBody: strings.NewReader("hello"),
|
|
|
|
Code: http.StatusNoContent,
|
2015-03-23 18:02:12 +00:00
|
|
|
ResHeader: map[string]string{
|
|
|
|
"Upload-Offset": "10",
|
|
|
|
},
|
2015-02-17 14:44:12 +00:00
|
|
|
}).Run(handler, t)
|
|
|
|
|
2016-03-12 21:44:54 +00:00
|
|
|
info := <-c
|
|
|
|
a.Equal("yes", info.ID)
|
|
|
|
a.Equal(int64(10), info.Size)
|
|
|
|
a.Equal(int64(10), info.Offset)
|
|
|
|
|
2015-02-17 14:44:12 +00:00
|
|
|
(&httpTest{
|
|
|
|
Name: "Non-existing file",
|
|
|
|
Method: "PATCH",
|
|
|
|
URL: "no",
|
|
|
|
ReqHeader: map[string]string{
|
2015-03-23 17:15:05 +00:00
|
|
|
"Tus-Resumable": "1.0.0",
|
2015-11-04 09:56:32 +00:00
|
|
|
"Content-Type": "application/offset+octet-stream",
|
2015-03-23 17:15:05 +00:00
|
|
|
"Upload-Offset": "5",
|
2015-02-17 14:44:12 +00:00
|
|
|
},
|
|
|
|
Code: http.StatusNotFound,
|
|
|
|
}).Run(handler, t)
|
|
|
|
|
|
|
|
(&httpTest{
|
|
|
|
Name: "Wrong offset",
|
|
|
|
Method: "PATCH",
|
|
|
|
URL: "yes",
|
|
|
|
ReqHeader: map[string]string{
|
2015-03-23 17:15:05 +00:00
|
|
|
"Tus-Resumable": "1.0.0",
|
2015-11-04 09:56:32 +00:00
|
|
|
"Content-Type": "application/offset+octet-stream",
|
2015-03-23 17:15:05 +00:00
|
|
|
"Upload-Offset": "4",
|
2015-02-17 14:44:12 +00:00
|
|
|
},
|
|
|
|
Code: http.StatusConflict,
|
|
|
|
}).Run(handler, t)
|
|
|
|
|
|
|
|
(&httpTest{
|
|
|
|
Name: "Exceeding file size",
|
|
|
|
Method: "PATCH",
|
|
|
|
URL: "yes",
|
|
|
|
ReqHeader: map[string]string{
|
2015-03-23 17:15:05 +00:00
|
|
|
"Tus-Resumable": "1.0.0",
|
2015-11-04 09:56:32 +00:00
|
|
|
"Content-Type": "application/offset+octet-stream",
|
2015-03-23 17:15:05 +00:00
|
|
|
"Upload-Offset": "5",
|
2015-02-17 14:44:12 +00:00
|
|
|
},
|
|
|
|
ReqBody: strings.NewReader("hellothisismorethan15bytes"),
|
|
|
|
Code: http.StatusRequestEntityTooLarge,
|
|
|
|
}).Run(handler, t)
|
2015-02-09 18:37:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type overflowPatchStore struct {
|
|
|
|
zeroStore
|
2016-01-20 15:40:13 +00:00
|
|
|
t *assert.Assertions
|
2015-02-09 18:37:06 +00:00
|
|
|
called bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s overflowPatchStore) GetInfo(id string) (FileInfo, error) {
|
|
|
|
if id != "yes" {
|
|
|
|
return FileInfo{}, os.ErrNotExist
|
|
|
|
}
|
|
|
|
|
|
|
|
return FileInfo{
|
|
|
|
Offset: 5,
|
|
|
|
Size: 20,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2015-03-23 18:02:12 +00:00
|
|
|
func (s overflowPatchStore) WriteChunk(id string, offset int64, src io.Reader) (int64, error) {
|
2016-01-20 15:40:13 +00:00
|
|
|
s.t.False(s.called, "WriteChunk must be called only once")
|
2015-02-09 18:37:06 +00:00
|
|
|
s.called = true
|
|
|
|
|
2016-01-20 15:40:13 +00:00
|
|
|
s.t.Equal(int64(5), offset)
|
2015-02-09 18:37:06 +00:00
|
|
|
|
|
|
|
data, err := ioutil.ReadAll(src)
|
2016-01-20 15:40:13 +00:00
|
|
|
s.t.Nil(err)
|
|
|
|
s.t.Equal("hellothisismore", string(data))
|
2015-02-09 18:37:06 +00:00
|
|
|
|
2015-03-23 18:02:12 +00:00
|
|
|
return 15, nil
|
2015-02-09 18:37:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// noEOFReader implements io.Reader, io.Writer, io.Closer but does not return
|
|
|
|
// an io.EOF when the internal buffer is empty. This way we can simulate slow
|
|
|
|
// networks.
|
|
|
|
type noEOFReader struct {
|
|
|
|
closed bool
|
|
|
|
buffer []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *noEOFReader) Read(dst []byte) (int, error) {
|
|
|
|
if r.closed && len(r.buffer) == 0 {
|
|
|
|
return 0, io.EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
n := copy(dst, r.buffer)
|
|
|
|
r.buffer = r.buffer[n:]
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *noEOFReader) Close() error {
|
|
|
|
r.closed = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *noEOFReader) Write(src []byte) (int, error) {
|
|
|
|
r.buffer = append(r.buffer, src...)
|
|
|
|
return len(src), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPatchOverflow(t *testing.T) {
|
|
|
|
handler, _ := NewHandler(Config{
|
|
|
|
MaxSize: 100,
|
|
|
|
DataStore: overflowPatchStore{
|
2016-01-20 15:40:13 +00:00
|
|
|
t: assert.New(t),
|
2015-02-09 18:37:06 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
body := &noEOFReader{}
|
|
|
|
|
2015-11-11 05:13:56 +00:00
|
|
|
body.Write([]byte("hellothisismorethan15bytes"))
|
|
|
|
body.Close()
|
2015-02-09 18:37:06 +00:00
|
|
|
|
2015-02-17 14:44:12 +00:00
|
|
|
(&httpTest{
|
|
|
|
Name: "Too big body exceeding file size",
|
|
|
|
Method: "PATCH",
|
|
|
|
URL: "yes",
|
|
|
|
ReqHeader: map[string]string{
|
2015-03-23 17:15:05 +00:00
|
|
|
"Tus-Resumable": "1.0.0",
|
2015-11-04 09:56:32 +00:00
|
|
|
"Content-Type": "application/offset+octet-stream",
|
2015-03-23 17:15:05 +00:00
|
|
|
"Upload-Offset": "5",
|
2015-02-17 14:44:12 +00:00
|
|
|
"Content-Length": "3",
|
|
|
|
},
|
|
|
|
ReqBody: body,
|
|
|
|
Code: http.StatusNoContent,
|
|
|
|
}).Run(handler, t)
|
2015-02-09 18:37:06 +00:00
|
|
|
}
|
2015-12-26 20:23:09 +00:00
|
|
|
|
|
|
|
const (
|
|
|
|
LOCK = iota
|
|
|
|
INFO
|
|
|
|
WRITE
|
|
|
|
UNLOCK
|
|
|
|
END
|
|
|
|
)
|
|
|
|
|
|
|
|
type lockingPatchStore struct {
|
|
|
|
zeroStore
|
|
|
|
callOrder chan int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s lockingPatchStore) GetInfo(id string) (FileInfo, error) {
|
|
|
|
s.callOrder <- INFO
|
|
|
|
|
|
|
|
return FileInfo{
|
|
|
|
Offset: 0,
|
|
|
|
Size: 20,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s lockingPatchStore) WriteChunk(id string, offset int64, src io.Reader) (int64, error) {
|
|
|
|
s.callOrder <- WRITE
|
|
|
|
|
|
|
|
return 5, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s lockingPatchStore) LockUpload(id string) error {
|
|
|
|
s.callOrder <- LOCK
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s lockingPatchStore) UnlockUpload(id string) error {
|
|
|
|
s.callOrder <- UNLOCK
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLockingPatch(t *testing.T) {
|
|
|
|
callOrder := make(chan int, 10)
|
|
|
|
|
|
|
|
handler, _ := NewHandler(Config{
|
|
|
|
DataStore: lockingPatchStore{
|
|
|
|
callOrder: callOrder,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
(&httpTest{
|
|
|
|
Name: "Uploading to locking store",
|
|
|
|
Method: "PATCH",
|
|
|
|
URL: "yes",
|
|
|
|
ReqHeader: map[string]string{
|
|
|
|
"Tus-Resumable": "1.0.0",
|
|
|
|
"Content-Type": "application/offset+octet-stream",
|
|
|
|
"Upload-Offset": "0",
|
|
|
|
},
|
|
|
|
ReqBody: strings.NewReader("hello"),
|
|
|
|
Code: http.StatusNoContent,
|
|
|
|
}).Run(handler, t)
|
|
|
|
|
|
|
|
callOrder <- END
|
|
|
|
close(callOrder)
|
|
|
|
|
|
|
|
if <-callOrder != LOCK {
|
|
|
|
t.Error("expected call to LockUpload")
|
|
|
|
}
|
|
|
|
|
|
|
|
if <-callOrder != INFO {
|
|
|
|
t.Error("expected call to GetInfo")
|
|
|
|
}
|
|
|
|
|
|
|
|
if <-callOrder != WRITE {
|
|
|
|
t.Error("expected call to WriteChunk")
|
|
|
|
}
|
|
|
|
|
|
|
|
if <-callOrder != UNLOCK {
|
|
|
|
t.Error("expected call to UnlockUpload")
|
|
|
|
}
|
|
|
|
|
|
|
|
if <-callOrder != END {
|
|
|
|
t.Error("expected no more calls to happen")
|
|
|
|
}
|
|
|
|
}
|
2016-04-09 20:09:22 +00:00
|
|
|
|
|
|
|
type finishedPatchStore struct {
|
|
|
|
zeroStore
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s finishedPatchStore) GetInfo(id string) (FileInfo, error) {
|
|
|
|
return FileInfo{
|
|
|
|
Offset: 20,
|
|
|
|
Size: 20,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s finishedPatchStore) WriteChunk(id string, offset int64, src io.Reader) (int64, error) {
|
|
|
|
panic("WriteChunk must not be called")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestFinishedPatch(t *testing.T) {
|
|
|
|
|
|
|
|
handler, _ := NewHandler(Config{
|
|
|
|
DataStore: finishedPatchStore{},
|
|
|
|
})
|
|
|
|
|
|
|
|
(&httpTest{
|
|
|
|
Name: "Uploading to finished upload",
|
|
|
|
Method: "PATCH",
|
|
|
|
URL: "yes",
|
|
|
|
ReqHeader: map[string]string{
|
|
|
|
"Tus-Resumable": "1.0.0",
|
|
|
|
"Content-Type": "application/offset+octet-stream",
|
|
|
|
"Upload-Offset": "20",
|
|
|
|
},
|
|
|
|
ReqBody: strings.NewReader(""),
|
|
|
|
Code: http.StatusNoContent,
|
|
|
|
ResHeader: map[string]string{
|
|
|
|
"Upload-Offset": "20",
|
|
|
|
},
|
|
|
|
}).Run(handler, t)
|
|
|
|
}
|