tusd/patch_test.go

327 lines
6.3 KiB
Go
Raw Permalink Normal View History

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{
ID: id,
2015-02-09 18:37:06 +00:00
Offset: 5,
Size: 10,
2015-02-09 18:37:06 +00:00
}, nil
}
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
return 5, nil
2015-02-09 18:37:06 +00:00
}
func TestPatch(t *testing.T) {
a := assert.New(t)
2015-02-09 18:37:06 +00:00
handler, _ := NewHandler(Config{
MaxSize: 100,
DataStore: patchStore{
t: a,
2015-02-09 18:37:06 +00:00
},
NotifyCompleteUploads: true,
2015-02-09 18:37:06 +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,
ResHeader: map[string]string{
"Upload-Offset": "10",
},
2015-02-17 14:44:12 +00:00
}).Run(handler, t)
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
}
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
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
}
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")
}
}
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)
}