diff --git a/pkg/handler/cors_test.go b/pkg/handler/cors_test.go index 1979f546a..39b8c1722 100644 --- a/pkg/handler/cors_test.go +++ b/pkg/handler/cors_test.go @@ -21,7 +21,7 @@ func TestCORS(t *testing.T) { }, Code: http.StatusOK, ResHeader: map[string]string{ - "Access-Control-Allow-Headers": "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat", + "Access-Control-Allow-Headers": "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete", "Access-Control-Allow-Methods": "POST, HEAD, PATCH, OPTIONS, GET, DELETE", "Access-Control-Max-Age": "86400", "Access-Control-Allow-Origin": "tus.io", @@ -43,7 +43,7 @@ func TestCORS(t *testing.T) { }, Code: http.StatusOK, ResHeader: map[string]string{ - "Access-Control-Allow-Headers": "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat", + "Access-Control-Allow-Headers": "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete", "Access-Control-Allow-Methods": "POST, HEAD, PATCH, OPTIONS", "Access-Control-Max-Age": "86400", "Access-Control-Allow-Origin": "tus.io", @@ -64,7 +64,7 @@ func TestCORS(t *testing.T) { }, Code: http.StatusMethodNotAllowed, ResHeader: map[string]string{ - "Access-Control-Expose-Headers": "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat", + "Access-Control-Expose-Headers": "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete", "Access-Control-Allow-Origin": "tus.io", }, }).Run(handler, t) diff --git a/pkg/handler/head_test.go b/pkg/handler/head_test.go index b5c83bba2..c055400ec 100644 --- a/pkg/handler/head_test.go +++ b/pkg/handler/head_test.go @@ -146,4 +146,107 @@ func TestHead(t *testing.T) { }, }).Run(handler, t) }) + + SubTest(t, "ExperimentalProtocol", func(t *testing.T, _ *MockFullDataStore, _ *StoreComposer) { + SubTest(t, "IncompleteUpload", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(context.Background(), "yes").Return(upload, nil), + upload.EXPECT().GetInfo(context.Background()).Return(FileInfo{ + SizeIsDeferred: false, + Size: 10, + Offset: 5, + }, nil), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + EnableExperimentalProtocol: true, + }) + + (&httpTest{ + Method: "HEAD", + URL: "yes", + ReqHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + }, + Code: http.StatusNoContent, + ResHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + "Upload-Incomplete": "?1", + "Upload-Offset": "5", + }, + }).Run(handler, t) + }) + + SubTest(t, "CompleteUpload", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(context.Background(), "yes").Return(upload, nil), + upload.EXPECT().GetInfo(context.Background()).Return(FileInfo{ + SizeIsDeferred: false, + Size: 10, + Offset: 10, + }, nil), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + EnableExperimentalProtocol: true, + }) + + (&httpTest{ + Method: "HEAD", + URL: "yes", + ReqHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + }, + Code: http.StatusNoContent, + ResHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + "Upload-Incomplete": "?0", + "Upload-Offset": "10", + }, + }).Run(handler, t) + }) + + SubTest(t, "DeferredLength", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(context.Background(), "yes").Return(upload, nil), + upload.EXPECT().GetInfo(context.Background()).Return(FileInfo{ + SizeIsDeferred: true, + Offset: 5, + }, nil), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + EnableExperimentalProtocol: true, + }) + + (&httpTest{ + Method: "HEAD", + URL: "yes", + ReqHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + }, + Code: http.StatusNoContent, + ResHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + "Upload-Incomplete": "?1", + "Upload-Offset": "5", + }, + }).Run(handler, t) + }) + }) } diff --git a/pkg/handler/patch_test.go b/pkg/handler/patch_test.go index 3329a4524..6dd1690b0 100644 --- a/pkg/handler/patch_test.go +++ b/pkg/handler/patch_test.go @@ -683,4 +683,161 @@ func TestPatch(t *testing.T) { ResBody: "an error while reading the body\n", }).Run(handler, t) }) + + SubTest(t, "ExperimentalProtocol", func(t *testing.T, _ *MockFullDataStore, _ *StoreComposer) { + SubTest(t, "CompleteUploadWithKnownSize", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(context.Background(), "yes").Return(upload, nil), + upload.EXPECT().GetInfo(context.Background()).Return(FileInfo{ + ID: "yes", + Offset: 5, + Size: 10, + SizeIsDeferred: false, + }, nil), + upload.EXPECT().WriteChunk(context.Background(), int64(5), NewReaderMatcher("hello")).Return(int64(5), nil), + upload.EXPECT().FinishUpload(context.Background()), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + EnableExperimentalProtocol: true, + }) + + (&httpTest{ + Method: "PATCH", + URL: "yes", + ReqHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + "Upload-Offset": "5", + "Upload-Incomplete": "?0", + }, + ReqBody: strings.NewReader("hello"), + Code: http.StatusNoContent, + ResHeader: map[string]string{ + "Upload-Offset": "10", + }, + }).Run(handler, t) + }) + SubTest(t, "CompleteUploadWithUnknownSize", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(context.Background(), "yes").Return(upload, nil), + upload.EXPECT().GetInfo(context.Background()).Return(FileInfo{ + ID: "yes", + Offset: 5, + Size: 0, + SizeIsDeferred: true, + }, nil), + upload.EXPECT().WriteChunk(context.Background(), int64(5), NewReaderMatcher("hello")).Return(int64(5), nil), + upload.EXPECT().GetInfo(context.Background()).Return(FileInfo{ + ID: "yes", + Offset: 10, + Size: 0, + SizeIsDeferred: true, + }, nil), + store.EXPECT().AsLengthDeclarableUpload(upload).Return(upload), + upload.EXPECT().DeclareLength(context.Background(), int64(10)), + upload.EXPECT().FinishUpload(context.Background()), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + EnableExperimentalProtocol: true, + }) + + (&httpTest{ + Method: "PATCH", + URL: "yes", + ReqHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + "Upload-Offset": "5", + "Upload-Incomplete": "?0", + }, + ReqBody: strings.NewReader("hello"), + Code: http.StatusNoContent, + ResHeader: map[string]string{ + "Upload-Offset": "10", + }, + }).Run(handler, t) + }) + SubTest(t, "ContinueUploadWithKnownSize", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(context.Background(), "yes").Return(upload, nil), + upload.EXPECT().GetInfo(context.Background()).Return(FileInfo{ + ID: "yes", + Offset: 5, + Size: 10, + SizeIsDeferred: false, + }, nil), + upload.EXPECT().WriteChunk(context.Background(), int64(5), NewReaderMatcher("hel")).Return(int64(3), nil), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + EnableExperimentalProtocol: true, + }) + + (&httpTest{ + Method: "PATCH", + URL: "yes", + ReqHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + "Upload-Offset": "5", + "Upload-Incomplete": "?1", + }, + ReqBody: strings.NewReader("hel"), + Code: http.StatusNoContent, + ResHeader: map[string]string{ + "Upload-Offset": "8", + }, + }).Run(handler, t) + }) + SubTest(t, "ContinueUploadWithUnknownSize", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(context.Background(), "yes").Return(upload, nil), + upload.EXPECT().GetInfo(context.Background()).Return(FileInfo{ + ID: "yes", + Offset: 5, + Size: 0, + SizeIsDeferred: true, + }, nil), + upload.EXPECT().WriteChunk(context.Background(), int64(5), NewReaderMatcher("hel")).Return(int64(3), nil), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + EnableExperimentalProtocol: true, + }) + + (&httpTest{ + Method: "PATCH", + URL: "yes", + ReqHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + "Upload-Offset": "5", + "Upload-Incomplete": "?1", + }, + ReqBody: strings.NewReader("hel"), + Code: http.StatusNoContent, + ResHeader: map[string]string{ + "Upload-Offset": "8", + }, + }).Run(handler, t) + }) + }) } diff --git a/pkg/handler/post_test.go b/pkg/handler/post_test.go index 12425c276..43f5f70d2 100644 --- a/pkg/handler/post_test.go +++ b/pkg/handler/post_test.go @@ -544,4 +544,117 @@ func TestPost(t *testing.T) { }).Run(handler, t) }) }) + + SubTest(t, "ExperimentalProtocol", func(t *testing.T, _ *MockFullDataStore, _ *StoreComposer) { + SubTest(t, "CompleteUpload", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + locker := NewMockFullLocker(ctrl) + lock := NewMockFullLock(ctrl) + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().NewUpload(context.Background(), FileInfo{ + SizeIsDeferred: false, + Size: 11, + MetaData: map[string]string{ + "filename": "hello.txt", + "filetype": "text/plain", + }, + }).Return(upload, nil), + upload.EXPECT().GetInfo(context.Background()).Return(FileInfo{ + ID: "foo", + SizeIsDeferred: false, + Size: 11, + MetaData: map[string]string{ + "filename": "hello.txt", + "filetype": "text/plain", + }, + }, nil), + locker.EXPECT().NewLock("foo").Return(lock, nil), + lock.EXPECT().Lock().Return(nil), + upload.EXPECT().WriteChunk(context.Background(), int64(0), NewReaderMatcher("hello world")).Return(int64(11), nil), + upload.EXPECT().FinishUpload(context.Background()).Return(nil), + lock.EXPECT().Unlock().Return(nil), + ) + + composer = NewStoreComposer() + composer.UseCore(store) + composer.UseLocker(locker) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + BasePath: "/files/", + EnableExperimentalProtocol: true, + }) + + (&httpTest{ + Method: "POST", + ReqHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + "Upload-Incomplete": "?0", + "Content-Type": "text/plain; charset=utf-8", + "Content-Disposition": "attachment; filename=hello.txt", + }, + ReqBody: strings.NewReader("hello world"), + // TODO: httptest.Recorder only captures the first informational response, so must expect a 104 and not a 201 here. + Code: 104, + ResHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + "Location": "http://tus.io/files/foo", + "Upload-Offset": "11", + }, + }).Run(handler, t) + }) + + SubTest(t, "IncompleteUpload", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + locker := NewMockFullLocker(ctrl) + lock := NewMockFullLock(ctrl) + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().NewUpload(context.Background(), FileInfo{ + SizeIsDeferred: true, + MetaData: map[string]string{}, + }).Return(upload, nil), + upload.EXPECT().GetInfo(context.Background()).Return(FileInfo{ + ID: "foo", + SizeIsDeferred: true, + }, nil), + locker.EXPECT().NewLock("foo").Return(lock, nil), + lock.EXPECT().Lock().Return(nil), + upload.EXPECT().WriteChunk(context.Background(), int64(0), NewReaderMatcher("hello world")).Return(int64(11), nil), + lock.EXPECT().Unlock().Return(nil), + ) + + composer = NewStoreComposer() + composer.UseCore(store) + composer.UseLocker(locker) + composer.UseLengthDeferrer(store) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + BasePath: "/files/", + EnableExperimentalProtocol: true, + }) + + (&httpTest{ + Method: "POST", + ReqHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + "Upload-Incomplete": "?1", + }, + ReqBody: strings.NewReader("hello world"), + // TODO: httptest.Recorder only captures the first informational response, so must expect a 104 and not a 201 here. + Code: 104, + ResHeader: map[string]string{ + "Upload-Draft-Interop-Version": "3", + "Location": "http://tus.io/files/foo", + "Upload-Offset": "11", + }, + }).Run(handler, t) + }) + }) }