Skip to content

Commit

Permalink
handler: Support IETF draft -04 and -05 (#1213)
Browse files Browse the repository at this point in the history
* `Upload-Draft-Interop-Version: 6`

* `Content-Type: application/partial-upload`

* `Upload-Length` in POST requests

* `Upload-Length` in HEAD responses

* `Upload-Limit` in OPTIONS responses

* `Upload-Limit` in POST responses

* `Upload-Limit` in HEAD responses
  • Loading branch information
Acconut authored Nov 2, 2024
1 parent b6cc786 commit c28e1fa
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 20 deletions.
9 changes: 8 additions & 1 deletion pkg/handler/head_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func TestHead(t *testing.T) {
})

SubTest(t, "ExperimentalProtocol", func(t *testing.T, _ *MockFullDataStore, _ *StoreComposer) {
for _, interopVersion := range []string{"3", "4", "5"} {
for _, interopVersion := range []string{"3", "4", "5", "6"} {
SubTest(t, "InteropVersion"+interopVersion, func(t *testing.T, _ *MockFullDataStore, _ *StoreComposer) {
SubTest(t, "IncompleteUpload", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
ctrl := gomock.NewController(t)
Expand Down Expand Up @@ -176,6 +176,8 @@ func TestHead(t *testing.T) {
ResHeader: addIETFUploadCompleteHeader(map[string]string{
"Upload-Draft-Interop-Version": interopVersion,
"Upload-Offset": "5",
"Upload-Length": "10",
"Upload-Limit": "min-size=0,max-size=10",
}, false, interopVersion),
}).Run(handler, t)
})
Expand Down Expand Up @@ -209,6 +211,8 @@ func TestHead(t *testing.T) {
ResHeader: addIETFUploadCompleteHeader(map[string]string{
"Upload-Draft-Interop-Version": interopVersion,
"Upload-Offset": "10",
"Upload-Length": "10",
"Upload-Limit": "min-size=0,max-size=10",
}, true, interopVersion),
}).Run(handler, t)
})
Expand All @@ -229,6 +233,7 @@ func TestHead(t *testing.T) {
handler, _ := NewHandler(Config{
StoreComposer: composer,
EnableExperimentalProtocol: true,
MaxSize: 400,
})

(&httpTest{
Expand All @@ -241,6 +246,8 @@ func TestHead(t *testing.T) {
ResHeader: addIETFUploadCompleteHeader(map[string]string{
"Upload-Draft-Interop-Version": interopVersion,
"Upload-Offset": "5",
"Upload-Length": "",
"Upload-Limit": "min-size=0,max-size=400",
}, false, interopVersion),
}).Run(handler, t)
})
Expand Down
22 changes: 22 additions & 0 deletions pkg/handler/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,26 @@ func TestOptions(t *testing.T) {
Code: http.StatusPreconditionFailed,
}).Run(handler, t)
})

SubTest(t, "ExperimentalProtocol", func(t *testing.T, store *MockFullDataStore, _ *StoreComposer) {
composer := NewStoreComposer()
composer.UseCore(store)

handler, _ := NewHandler(Config{
StoreComposer: composer,
EnableExperimentalProtocol: true,
MaxSize: 400,
})

(&httpTest{
Method: "OPTIONS",
ReqHeader: map[string]string{
"Upload-Draft-Interop-Version": "6",
},
ResHeader: map[string]string{
"Upload-Limit": "min-size=0,max-size=400",
},
Code: http.StatusOK,
}).Run(handler, t)
})
}
40 changes: 31 additions & 9 deletions pkg/handler/patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ func TestPatch(t *testing.T) {
})

SubTest(t, "ExperimentalProtocol", func(t *testing.T, _ *MockFullDataStore, _ *StoreComposer) {
for _, interopVersion := range []string{"3", "4", "5"} {
for _, interopVersion := range []string{"3", "4", "5", "6"} {
SubTest(t, "InteropVersion"+interopVersion, func(t *testing.T, _ *MockFullDataStore, _ *StoreComposer) {
SubTest(t, "CompleteUploadWithKnownSize", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
ctrl := gomock.NewController(t)
Expand All @@ -843,10 +843,10 @@ func TestPatch(t *testing.T) {
(&httpTest{
Method: "PATCH",
URL: "yes",
ReqHeader: addIETFUploadCompleteHeader(map[string]string{
ReqHeader: addIETFContentTypeHeader(addIETFUploadCompleteHeader(map[string]string{
"Upload-Draft-Interop-Version": interopVersion,
"Upload-Offset": "5",
}, true, interopVersion),
}, true, interopVersion), interopVersion),
ReqBody: strings.NewReader("hello"),
Code: http.StatusNoContent,
ResHeader: map[string]string{
Expand Down Expand Up @@ -887,10 +887,10 @@ func TestPatch(t *testing.T) {
(&httpTest{
Method: "PATCH",
URL: "yes",
ReqHeader: addIETFUploadCompleteHeader(map[string]string{
ReqHeader: addIETFContentTypeHeader(addIETFUploadCompleteHeader(map[string]string{
"Upload-Draft-Interop-Version": interopVersion,
"Upload-Offset": "5",
}, true, interopVersion),
}, true, interopVersion), interopVersion),
ReqBody: strings.NewReader("hello"),
Code: http.StatusNoContent,
ResHeader: map[string]string{
Expand Down Expand Up @@ -922,10 +922,10 @@ func TestPatch(t *testing.T) {
(&httpTest{
Method: "PATCH",
URL: "yes",
ReqHeader: addIETFUploadCompleteHeader(map[string]string{
ReqHeader: addIETFContentTypeHeader(addIETFUploadCompleteHeader(map[string]string{
"Upload-Draft-Interop-Version": interopVersion,
"Upload-Offset": "5",
}, false, interopVersion),
}, false, interopVersion), interopVersion),
ReqBody: strings.NewReader("hel"),
Code: http.StatusNoContent,
ResHeader: map[string]string{
Expand Down Expand Up @@ -957,17 +957,39 @@ func TestPatch(t *testing.T) {
(&httpTest{
Method: "PATCH",
URL: "yes",
ReqHeader: addIETFUploadCompleteHeader(map[string]string{
ReqHeader: addIETFContentTypeHeader(addIETFUploadCompleteHeader(map[string]string{
"Upload-Draft-Interop-Version": interopVersion,
"Upload-Offset": "5",
}, false, interopVersion),
}, false, interopVersion), interopVersion),
ReqBody: strings.NewReader("hel"),
Code: http.StatusNoContent,
ResHeader: map[string]string{
"Upload-Offset": "8",
},
}).Run(handler, t)
})

if interopVersion != "3" && interopVersion != "4" && interopVersion != "5" {
SubTest(t, "InvalidContentType", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
handler, _ := NewHandler(Config{
StoreComposer: composer,
EnableExperimentalProtocol: true,
})

(&httpTest{
Method: "PATCH",
URL: "yes",
ReqHeader: map[string]string{
"Upload-Draft-Interop-Version": interopVersion,
"Content-Type": "application/not-partial-upload",
"Upload-Offset": "0",
},
ReqBody: strings.NewReader("test"),
Code: http.StatusBadRequest,
ResBody: "ERR_INVALID_CONTENT_TYPE: missing or invalid Content-Type header\n",
}).Run(handler, t)
})
}
})
}
})
Expand Down
137 changes: 136 additions & 1 deletion pkg/handler/post_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ func TestPost(t *testing.T) {
})

SubTest(t, "ExperimentalProtocol", func(t *testing.T, _ *MockFullDataStore, _ *StoreComposer) {
for _, interopVersion := range []string{"3", "4", "5"} {
for _, interopVersion := range []string{"3", "4", "5", "6"} {
SubTest(t, "InteropVersion"+interopVersion, func(t *testing.T, _ *MockFullDataStore, _ *StoreComposer) {
SubTest(t, "CompleteUpload", func(t *testing.T, store *MockFullDataStore, _ *StoreComposer) {
ctrl := gomock.NewController(t)
Expand Down Expand Up @@ -603,6 +603,7 @@ func TestPost(t *testing.T) {
"Upload-Draft-Interop-Version": interopVersion,
"Location": "http://tus.io/files/foo",
"Upload-Offset": "11",
"Upload-Limit": "min-size=0,max-size=11",
},
}).Run(handler, t)

Expand All @@ -614,6 +615,7 @@ func TestPost(t *testing.T) {
"Upload-Draft-Interop-Version": []string{interopVersion},
"Location": []string{"http://tus.io/files/foo"},
"X-Content-Type-Options": []string{"nosniff"},
"Upload-Limit": []string{"min-size=0,max-size=11"},
},
},
}, res.InformationalResponses)
Expand Down Expand Up @@ -650,6 +652,7 @@ func TestPost(t *testing.T) {
StoreComposer: composer,
BasePath: "/files/",
EnableExperimentalProtocol: true,
MaxSize: 400,
})

res := (&httpTest{
Expand All @@ -663,6 +666,7 @@ func TestPost(t *testing.T) {
"Upload-Draft-Interop-Version": interopVersion,
"Location": "http://tus.io/files/foo",
"Upload-Offset": "11",
"Upload-Limit": "min-size=0,max-size=400",
},
}).Run(handler, t)

Expand All @@ -674,10 +678,141 @@ func TestPost(t *testing.T) {
"Upload-Draft-Interop-Version": []string{interopVersion},
"Location": []string{"http://tus.io/files/foo"},
"X-Content-Type-Options": []string{"nosniff"},
"Upload-Limit": []string{"min-size=0,max-size=400"},
},
},
}, res.InformationalResponses)
})

if interopVersion != "3" && interopVersion != "4" && interopVersion != "5" {
SubTest(t, "UploadLengthAndContentLengthMatch", func(t *testing.T, store *MockFullDataStore, _ *StoreComposer) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
locker := NewMockFullLocker(ctrl)
lock := NewMockFullLock(ctrl)
upload := NewMockFullUpload(ctrl)

gomock.InOrder(
store.EXPECT().NewUpload(gomock.Any(), FileInfo{
SizeIsDeferred: false,
Size: 11,
MetaData: map[string]string{},
}).Return(upload, nil),
upload.EXPECT().GetInfo(gomock.Any()).Return(FileInfo{
ID: "foo",
SizeIsDeferred: false,
Size: 11,
}, nil),
locker.EXPECT().NewLock("foo").Return(lock, nil),
lock.EXPECT().Lock(gomock.Any(), gomock.Any()).Return(nil),
upload.EXPECT().WriteChunk(gomock.Any(), int64(0), NewReaderMatcher("hello world")).Return(int64(11), nil),
upload.EXPECT().FinishUpload(gomock.Any()).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": interopVersion,
"Upload-Length": "11",
"Upload-Complete": "?1",
},
ReqBody: strings.NewReader("hello world"),
Code: http.StatusCreated,
ResHeader: map[string]string{
"Upload-Draft-Interop-Version": interopVersion,
"Location": "http://tus.io/files/foo",
"Upload-Offset": "11",
"Upload-Limit": "min-size=0,max-size=11",
},
}).Run(handler, t)
})

SubTest(t, "UploadLengthAndContentLengthMismatch", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

handler, _ := NewHandler(Config{
StoreComposer: composer,
BasePath: "/files/",
EnableExperimentalProtocol: true,
})

(&httpTest{
Method: "POST",
ReqHeader: map[string]string{
"Upload-Draft-Interop-Version": interopVersion,
"Upload-Length": "999999",
"Upload-Complete": "?1",
},
ReqBody: strings.NewReader("hello world"),
Code: http.StatusBadRequest,
ResBody: "ERR_INVALID_UPLOAD_LENGTH: missing or invalid Upload-Length header\n",
}).Run(handler, t)
})

SubTest(t, "OnlyUploadLength", func(t *testing.T, store *MockFullDataStore, _ *StoreComposer) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
locker := NewMockFullLocker(ctrl)
lock := NewMockFullLock(ctrl)
upload := NewMockFullUpload(ctrl)

gomock.InOrder(
store.EXPECT().NewUpload(gomock.Any(), FileInfo{
SizeIsDeferred: false,
Size: 11,
MetaData: map[string]string{},
}).Return(upload, nil),
upload.EXPECT().GetInfo(gomock.Any()).Return(FileInfo{
ID: "foo",
SizeIsDeferred: false,
Size: 11,
}, nil),
locker.EXPECT().NewLock("foo").Return(lock, nil),
lock.EXPECT().Lock(gomock.Any(), gomock.Any()).Return(nil),
upload.EXPECT().WriteChunk(gomock.Any(), int64(0), NewReaderMatcher("hello ")).Return(int64(6), 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": interopVersion,
"Upload-Length": "11",
"Upload-Complete": "?0",
},
ReqBody: strings.NewReader("hello "),
Code: http.StatusCreated,
ResHeader: map[string]string{
"Upload-Draft-Interop-Version": interopVersion,
"Location": "http://tus.io/files/foo",
"Upload-Offset": "6",
"Upload-Limit": "min-size=0,max-size=11",
},
}).Run(handler, t)
})
}
})
}
})
Expand Down
Loading

0 comments on commit c28e1fa

Please sign in to comment.