diff --git a/pkg/ranger/content.go b/pkg/ranger/content.go index 1f108df03..6f01a0939 100644 --- a/pkg/ranger/content.go +++ b/pkg/ranger/content.go @@ -126,7 +126,7 @@ func ServeContent(ctx context.Context, w http.ResponseWriter, r *http.Request, pr, pw := io.Pipe() mw := multipart.NewWriter(pw) w.Header().Set("Content-Type", - "multipart/byteranges; boundary="+mw.Boundary()) + "multipart/byteranges; boundary=" + mw.Boundary()) sendContent = func() (io.ReadCloser, error) { return ioutil.NopCloser(pr), nil } // cause writing goroutine to fail and exit if CopyN doesn't finish. defer func() { @@ -319,6 +319,7 @@ func checkIfNoneMatch(w http.ResponseWriter, r *http.Request) condResult { } if buf[0] == ',' { buf = buf[1:] + continue } if buf[0] == '*' { return condFalse diff --git a/pkg/ranger/content_test.go b/pkg/ranger/content_test.go new file mode 100644 index 000000000..c74688313 --- /dev/null +++ b/pkg/ranger/content_test.go @@ -0,0 +1,948 @@ +// Copyright (C) 2018 Storj Labs, Inc. +// See LICENSE for copying information. + +package ranger + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "net/http/httptest" +) + +func TestServeContent(t *testing.T) { + type RangerArgs struct { + a string + } + for _, tt := range []struct { + testName string + requestMethod string + requestHeaderMap map[string]string + writerHeaderMap map[string]string + name string + modtime time.Time + content Ranger + }{ + { + name: "True preconditions", + requestHeaderMap: map[string]string{"If-Match": "\t\t"}, + }, + } { + t.Run(tt.testName, func(t *testing.T) { + req := httptest.NewRequest(tt.requestMethod, "/", nil) + for k, v := range tt.requestHeaderMap { + req.Header.Add(k, v) + } + + writer := httptest.NewRecorder() + for k, v := range tt.writerHeaderMap { + writer.Header().Add(k, v) + } + + ServeContent(context.Background(), writer, req, tt.name, tt.modtime, tt.content) + }) + } +} + +func TestServeContentContentSize(t *testing.T) { + req := httptest.NewRequest("", "/", nil) + writer := httptest.NewRecorder() + ranger := ByteRanger([]byte("")) + + ServeContent(context.Background(), writer, req, "", time.Now().UTC(), ranger) + + assert.Equal(t, http.StatusOK, writer.Code) +} + +func TestServeContentParseRange(t *testing.T) { + req := httptest.NewRequest("", "/", nil) + for k, v := range map[string]string{"If-Range": "\"abcde\""} { + req.Header.Add(k, v) + } + + writer := httptest.NewRecorder() + for k, v := range map[string]string{"Etag": "\"abcde\""} { + writer.Header().Add(k, v) + } + ranger := ByteRanger([]byte("bytes=1-5/0,bytes=1-5/8",)) + + ServeContent(context.Background(), writer, req, "", time.Now().UTC(), ranger) + + assert.Equal(t, http.StatusOK, writer.Code) + assert.Equal(t, "23", writer.HeaderMap.Get("Content-Length")) +} + +func Test_isZeroTime(t *testing.T) { + for _, tt := range []struct { + name string + t time.Time + expected bool + }{ + { + name: "Valid", + t: time.Now().UTC(), + expected: false, + }, + + { + name: "Zero time", + t: time.Unix(0, 0).UTC(), + expected: true, + }, + } { + t.Run(tt.name, func(t *testing.T) { + got := isZeroTime(tt.t) + + assert.Equal(t, tt.expected, got) + }) + } +} + +func Test_setLastModified(t *testing.T) { + for _, tt := range []struct { + name string + modtime time.Time + expected string + }{ + { + name: "Zero time", + modtime: time.Unix(0, 0).UTC(), + expected: "", + }, + { + name: "Valid time", + modtime: time.Unix(1531836358, 0).UTC(), + expected: "Tue, 17 Jul 2018 14:05:58 GMT", + }, + } { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRecorder() + + setLastModified(req, tt.modtime) + + assert.Equal(t, tt.expected, req.HeaderMap.Get("Last-Modified")) + }) + } +} + +//TODO This code block is commented until PR #158 acceptance +//func Test_setLastModifiedNilWriter(t *testing.T) { +// req := httptest.NewRecorder() +// +// setLastModified(nil, time.Now().UTC()) +// +// assert.Equal(t, "", req.HeaderMap.Get("Last-Modified")) +//} + +func Test_checkPreconditions(t *testing.T) { + for _, tt := range []struct { + name string + requestHeaderMap map[string]string + writerHeaderMap map[string]string + requestMethod string + modtime time.Time + expectedDone bool + expectedRangeHeader string + }{ + { + name: "Empty If-Match with trailing spaces", + requestHeaderMap: map[string]string{"If-Match": "\t\t"}, + expectedDone: true, + expectedRangeHeader: "", + }, + + { + name: "No If-Match header", + requestHeaderMap: map[string]string{"If-Unmodified-Since": "Thursday, 18-Jul-18 12:20:25 EEST"}, + expectedDone: true, + modtime: time.Unix(1531999477, 0).UTC(), + expectedRangeHeader: "", + }, + + { + name: "Any If-Match header with GET request", + requestMethod: http.MethodGet, //By default method is GET. Wrote for clarity + requestHeaderMap: map[string]string{"If-None-Match": "*"}, + expectedDone: true, + expectedRangeHeader: "", + }, + + { + name: "Any If-Match header with HEAD request", + requestHeaderMap: map[string]string{"If-None-Match": "*"}, + requestMethod: http.MethodHead, + expectedDone: true, + expectedRangeHeader: "", + }, + + { + name: "Any If-Match header with PUT request", + requestHeaderMap: map[string]string{"If-None-Match": "*"}, + requestMethod: http.MethodPut, + expectedDone: true, + expectedRangeHeader: "", + }, + + { + name: "Empty request", + requestHeaderMap: map[string]string{}, + expectedDone: false, + expectedRangeHeader: "", + }, + + { + name: "Empty modified request", + requestHeaderMap: map[string]string{"If-Modified-Since": "Thursday, 20-Jul-18 12:20:25 EEST"}, + modtime: time.Unix(1531999477, 0).UTC(), + expectedDone: true, + expectedRangeHeader: "", + }, + + { + name: "Empty unmodified request", + requestHeaderMap: map[string]string{"Range": "aaa", "If-Range": "\"abcde\""}, + expectedDone: false, + expectedRangeHeader: "", + }, + } { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest(tt.requestMethod, "/", nil) + for k, v := range tt.requestHeaderMap { + req.Header.Add(k, v) + } + + writer := httptest.NewRecorder() + for k, v := range tt.writerHeaderMap { + writer.Header().Add(k, v) + } + + gotDone, gotRangeHeader := checkPreconditions(writer, req, tt.modtime) + + assert.Equal(t, tt.expectedDone, gotDone) + assert.Equal(t, tt.expectedRangeHeader, gotRangeHeader) + }) + } +} + +func Test_checkIfMatch(t *testing.T) { + for _, tt := range []struct { + name string + requestHeaderMap map[string]string + writerHeaderMap map[string]string + expectedResult condResult + }{ + { + name: "No If-Match header", + requestHeaderMap: map[string]string{}, + expectedResult: condNone, + }, + + { + name: "Empty If-Match with trailing spaces", + requestHeaderMap: map[string]string{"If-Match": "\t\t"}, + expectedResult: condFalse, + }, + + { + name: "Starts with coma", + requestHeaderMap: map[string]string{"If-Match": ","}, + expectedResult: condFalse, + }, + + { + name: "Anything match", + requestHeaderMap: map[string]string{"If-Match": "*"}, + expectedResult: condTrue, + }, + + { + name: "Empty If-Match ETag", + requestHeaderMap: map[string]string{"If-Match": "abcd"}, + expectedResult: condFalse, + }, + + { + name: "First ETag match", + requestHeaderMap: map[string]string{"If-Match": "\"testETag\", \"testETag\""}, + writerHeaderMap: map[string]string{"Etag": "\"testETag\""}, + expectedResult: condTrue, + }, + + { + name: "Separated ETag match", + requestHeaderMap: map[string]string{"If-Match": "\"wrongETag\", \"correctETag\""}, + writerHeaderMap: map[string]string{"Etag": "\"correctETag\""}, + expectedResult: condTrue, + }, + } { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest("", "/", nil) + for k, v := range tt.requestHeaderMap { + req.Header.Add(k, v) + } + + writer := httptest.NewRecorder() + for k, v := range tt.writerHeaderMap { + writer.Header().Add(k, v) + } + + gotResult := checkIfMatch(writer, req) + + assert.Equal(t, tt.expectedResult, gotResult) + }) + } +} + +func Test_checkIfUnmodifiedSince(t *testing.T) { + for _, tt := range []struct { + name string + headerMap map[string]string + modtime time.Time + expectedResult condResult + }{ + { + name: "No modified flag", + headerMap: map[string]string{}, + modtime: time.Now().UTC(), + expectedResult: condNone, + }, + + { + name: "Zero time", + headerMap: map[string]string{"If-Unmodified-Since": "Thursday, 18-Jul-18 12:20:25 EEST"}, + modtime: time.Unix(0, 0).UTC(), + expectedResult: condNone, + }, + + { + name: "Is modified", + headerMap: map[string]string{"If-Unmodified-Since": "Thursday, 20-Jul-18 12:20:25 EEST"}, + modtime: time.Unix(1531999477, 0).UTC(), + expectedResult: condTrue, + }, + + { + name: "Is not modified", + headerMap: map[string]string{"If-Unmodified-Since": "Thursday, 18-Jul-18 12:20:25 EEST"}, + modtime: time.Unix(1531999477, 0).UTC(), + expectedResult: condFalse, + }, + + { + name: "Malformed RFC time", + headerMap: map[string]string{"If-Unmodified-Since": "abcdefg"}, + modtime: time.Unix(1531999477, 0).UTC(), + expectedResult: condNone, + }, + } { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest("", "/", nil) + for k, v := range tt.headerMap { + req.Header.Add(k, v) + } + + gotResult := checkIfUnmodifiedSince(req, tt.modtime) + + assert.Equal(t, tt.expectedResult, gotResult) + }) + } +} + +func Test_checkIfNoneMatch(t *testing.T) { + for _, tt := range []struct { + name string + requestHeaderMap map[string]string + writerHeaderMap map[string]string + expectedResult condResult + }{ + { + name: "No If-None-Match header", + requestHeaderMap: map[string]string{}, + expectedResult: condNone, + }, + + { + name: "Empty If-None-Match with trailing spaces", + requestHeaderMap: map[string]string{"If-None-Match": "\t\t"}, + expectedResult: condTrue, + }, + + { + name: "Starts with coma", + requestHeaderMap: map[string]string{"If-None-Match": ","}, + expectedResult: condTrue, + }, + + { + name: "Anything match", + requestHeaderMap: map[string]string{"If-None-Match": "*"}, + expectedResult: condFalse, + }, + + { + name: "Empty If-None-Match ETag", + requestHeaderMap: map[string]string{"If-None-Match": "abcd"}, + expectedResult: condTrue, + }, + + { + name: "First ETag match", + requestHeaderMap: map[string]string{"If-None-Match": "\"testETag\", \"testETag\""}, + writerHeaderMap: map[string]string{"Etag": "\"testETag\""}, + expectedResult: condFalse, + }, + + { + name: "Separated ETag match", + requestHeaderMap: map[string]string{"If-None-Match": "\"wrongETag\", \"correctETag\""}, + writerHeaderMap: map[string]string{"Etag": "\"correctETag\""}, + expectedResult: condFalse, + }, + } { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest("", "/", nil) + for k, v := range tt.requestHeaderMap { + req.Header.Add(k, v) + } + + writer := httptest.NewRecorder() + for k, v := range tt.writerHeaderMap { + writer.Header().Add(k, v) + } + + gotResult := checkIfNoneMatch(writer, req) + + assert.Equal(t, tt.expectedResult, gotResult) + }) + } +} + +func Test_checkIfModifiedSince(t *testing.T) { + for _, tt := range []struct { + name string + requestMethod string + headerMap map[string]string + modtime time.Time + expectedResult condResult + }{ + { + name: "Unacceptable Method", + requestMethod: "PUT", + headerMap: map[string]string{}, + expectedResult: condNone, + }, + + { + name: "No If-Modified-Since header", + requestMethod: "GET", + expectedResult: condNone, + }, + + { + name: "Malformed If-Modified-Since header", + requestMethod: "GET", + headerMap: map[string]string{"If-Modified-Since": "aaaa"}, + modtime: time.Unix(1531999477, 0).UTC(), + expectedResult: condNone, + }, + + { + name: "Is not modified before", + requestMethod: "GET", + headerMap: map[string]string{"If-Modified-Since": "Thursday, 20-Jul-18 12:20:25 EEST"}, + modtime: time.Unix(1531999477, 0).UTC(), + expectedResult: condFalse, + }, + + { + name: "Modified before", + requestMethod: "GET", + headerMap: map[string]string{"If-Modified-Since": "Thursday, 18-Jul-18 12:20:25 EEST"}, + modtime: time.Unix(1531999477, 0).UTC(), + expectedResult: condTrue, + }, + } { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest(tt.requestMethod, "/", nil) + for k, v := range tt.headerMap { + req.Header.Add(k, v) + } + + gotResult := checkIfModifiedSince(req, tt.modtime) + + assert.Equal(t, tt.expectedResult, gotResult) + }) + } +} + +func Test_checkIfRange(t *testing.T) { + for _, tt := range []struct { + name string + requestMethod string + requestHeaderMap map[string]string + writerHeaderMap map[string]string + modtime time.Time + expectedResult condResult + }{ + { + name: "Unacceptable Method", + requestMethod: "PUT", + expectedResult: condNone, + }, + + { + name: "No If-Range header", + requestMethod: "GET", + expectedResult: condNone, + }, + + { + name: "Not matching ETags", + requestMethod: "GET", + requestHeaderMap: map[string]string{"If-Range": "\"abcde\""}, + expectedResult: condFalse, + }, + + { + name: "Matching ETags", + requestMethod: "GET", + requestHeaderMap: map[string]string{"If-Range": "\"abcde\""}, + writerHeaderMap: map[string]string{"Etag": "\"abcde\""}, + expectedResult: condTrue, + }, + + //TODO This test case increases test coverage of 'checkIfRange' function to 100% + //Waiting PR #158 to be accepted + { + name: "Zero modtime", + requestMethod: "GET", + requestHeaderMap: map[string]string{"If-Range": "a"}, + modtime: time.Unix(0, 0).UTC(), + expectedResult: condFalse, + }, + + { + name: "Malformed header time", + requestMethod: "GET", + requestHeaderMap: map[string]string{"If-Range": "aaa"}, + modtime: time.Unix(1531999477, 0).UTC(), + expectedResult: condFalse, + }, + + { + name: "Equal time", + requestMethod: "GET", + requestHeaderMap: map[string]string{"If-Range": "Thu, 19 Jul 2018 14:12:03 GMT"}, + modtime: time.Unix(1532009523, 0).UTC(), + expectedResult: condTrue, + }, + + { + name: "Equal time", + requestMethod: "GET", + requestHeaderMap: map[string]string{"If-Range": "Thu, 18 Jul 2018 14:12:03 GMT"}, + modtime: time.Unix(1532009523, 0).UTC(), + expectedResult: condFalse, + }, + } { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest(tt.requestMethod, "/", nil) + for k, v := range tt.requestHeaderMap { + req.Header.Add(k, v) + } + + writer := httptest.NewRecorder() + for k, v := range tt.writerHeaderMap { + writer.Header().Add(k, v) + } + + gotResult := checkIfRange(writer, req, tt.modtime) + + assert.Equal(t, tt.expectedResult, gotResult) + }) + } +} + +func Test_writeNotModified(t *testing.T) { + + for _, tt := range []struct { + name string + writerHeaderMap map[string]string + }{ + { + name: "Empty ETag", + writerHeaderMap: map[string]string{ + "Content-Type": "a", + "Content-Length": "23", + "Etag": "", + }, + }, + + { + name: "Existing ETag", + writerHeaderMap: map[string]string{ + "Content-Type": "a", + "Content-Length": "23", + "Etag": "asdf", + "Last-Modified": "aaa", + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + writer := httptest.NewRecorder() + for k, v := range tt.writerHeaderMap { + writer.Header().Add(k, v) + } + + writeNotModified(writer) + + assert.Equal(t, http.StatusNotModified, writer.Code) + assert.Equal(t, "", writer.Header().Get("Content-Type")) + assert.Equal(t, "", writer.Header().Get("Content-Length")) + assert.Equal(t, "", writer.Header().Get("Last-Modified")) + }) + } +} + +//TODO Waiting PR #158 to be accepted for 100% coverage +func Test_scanETag(t *testing.T) { + for _, tt := range []struct { + name string + s string + expectedEtag string + expectedRemain string + }{ + { + name: "Empty ETag", s: "", + expectedEtag: "", + expectedRemain: "", + }, + + { + name: "Empty ETag with W", s: "W/", + expectedEtag: "", + expectedRemain: "", + }, + + { + name: "Malformed ETag", + s: "asdf", + expectedEtag: "", + expectedRemain: "", + }, + + { + name: "Valid ETag", + s: "\"abcdef\"", + expectedEtag: "\"abcdef\"", + expectedRemain: "", + }, + + { + name: "Valid ETag with W/", + s: "W/\"aaaa\"", + expectedEtag: "W/\"aaaa\"", + expectedRemain: "", + }, + + //{ + // name: "Valid ETag with special character", + // s: "\"{aa\"", + // expectedEtag: "", + // expectedRemain: "", + //}, + } { + t.Run(tt.name, func(t *testing.T) { + + gotEtag, gotRemain := scanETag(tt.s) + + assert.Equal(t, tt.expectedEtag, gotEtag) + assert.Equal(t, tt.expectedRemain, gotRemain) + }) + } +} + +func Test_etagStrongMatch(t *testing.T) { + for _, tt := range []struct { + name string + a, b string + expectedMatch bool + }{ + { + name: "Not equal arguments", + a: "a", + b: "b", + expectedMatch: false, + }, + { + name: "Empty string", + a: "", + b: "", + expectedMatch: false, + }, + { + name: "Does not start with required char", + a: "a", + b: "a", + expectedMatch: false, + }, + { + name: "Valid test case", + a: "\"", + b: "\"", + expectedMatch: true, + }, + } { + t.Run(tt.name, func(t *testing.T) { + gotMatch := etagStrongMatch(tt.a, tt.b) + + assert.Equal(t, tt.expectedMatch, gotMatch) + }) + } +} + +func Test_etagWeakMatch(t *testing.T) { + for _, tt := range []struct { + name string + a, b string + expectedMatch bool + }{ + { + name: "Empty ETag", + a: "", + b: "", + expectedMatch: true, + }, + + { + name: "Not equal ETags", + a: "W/a", + b: "W/b", + expectedMatch: false, + }, + + { + name: "Equal ETags", + a: "W/a", + b: "W/a", + expectedMatch: true, + }, + } { + t.Run(tt.name, func(t *testing.T) { + gotMatch := etagWeakMatch(tt.a, tt.b) + + assert.Equal(t, tt.expectedMatch, gotMatch) + }) + } +} + +func Test_httpRange_contentRange(t *testing.T) { + for _, tt := range []struct { + name string + start int64 + length int64 + size int64 + expectedRangeSize string + }{ + { + name: "Valid case", + start: 1, + length: 5, + size: 8, + expectedRangeSize: "bytes 1-5/8", + }, + } { + t.Run(tt.name, func(t *testing.T) { + r := httpRange{ + start: tt.start, + length: tt.length, + } + + gotRangeSize := r.contentRange(tt.size) + + assert.Equal(t, tt.expectedRangeSize, gotRangeSize) + }) + } +} + +func Test_httpRange_mimeHeader(t *testing.T) { + for _, tt := range []struct { + name string + contentType string + size int64 + expected string + }{ + { + name: "Valid", + contentType: "text", + size: 8, + expected: "bytes 1-5/8", + }, + } { + t.Run(tt.name, func(t *testing.T) { + r := httpRange{ + start: 1, + length: 5, + } + + gotMimeHeader := r.mimeHeader(tt.contentType, tt.size) + + assert.Equal(t, tt.contentType, gotMimeHeader.Get("Content-Type")) + assert.Equal(t, tt.expected, gotMimeHeader.Get("Content-Range")) + }) + } +} + +func Test_parseRange(t *testing.T) { + for _, tt := range []struct { + name string + s string + size int64 + expectedRange []httpRange + expectedError bool + }{ + { + name: "Header not present", + s: "", + size: 0, + expectedRange: nil, + expectedError: false, + }, + { + name: "invalid range", + s: "a", + size: 0, + expectedRange: nil, + expectedError: true, + }, + { + name: "Empty Bytes", + s: "bytes=", + size: 0, + expectedRange: nil, + expectedError: false, + }, + { + name: "invalid Range", + s: "bytes=1-5/0,bytes=1-5/8", + size: 0, + expectedRange: nil, + expectedError: true, + }, + { + name: "invalid Range", + s: "bytes=-", + size: 0, + expectedRange: nil, + expectedError: true, + }, + { + name: "invalid Range", + s: "bytes=111,bytes=111,bytes=111", + size: 0, + expectedRange: nil, + expectedError: true, + }, + { + name: "invalid Range", + s: "bytes=1-5/0,bytes=1-5/8", + size: 3, + expectedRange: nil, + expectedError: true, + }, + } { + t.Run(tt.name, func(t *testing.T) { + gotRange, err := parseRange(tt.s, tt.size) + + assert.Equal(t, err != nil, tt.expectedError) + assert.Equal(t, gotRange, tt.expectedRange) + }) + + } +} + +func Test_countingWriter_Write(t *testing.T) { + for _, tt := range []struct { + name string + arrayHolder string + expectedLength int + expectingError bool + }{ + { + name: "", + arrayHolder: "abcd", + expectedLength: 4, + expectingError: false, + }, + + { + name: "", + arrayHolder: "", + expectedLength: 0, + expectingError: false, + }, + } { + t.Run(tt.name, func(t *testing.T) { + var a countingWriter + + gotN, err := a.Write([]byte(tt.arrayHolder)) + + assert.Equal(t, tt.expectedLength, gotN) + assert.Equal(t, tt.expectingError, err != nil) + }) + } +} + +func Test_rangesMIMESize(t *testing.T) { + for _, tt := range []struct { + name string + ranges []httpRange + expectedEncSize int64 + }{ + { + name: "Valid case", + ranges: []httpRange{ + {start: 0, length: 5}, + }, + expectedEncSize: 187, + }, + } { + t.Run(tt.name, func(t *testing.T) { + gotEncSize := rangesMIMESize(tt.ranges, "text", 3) + + assert.Equal(t, tt.expectedEncSize, gotEncSize) + }) + } +} + +func Test_sumRangesSize(t *testing.T) { + for _, tt := range []struct { + ranges []httpRange + expectedSize int64 + }{ + { + ranges: []httpRange{ + {length: 5}, + {length: 5}, + {length: 5}, + }, + expectedSize: 15, + }, + + { + ranges: []httpRange{ + {length: 0}, + {length: 0}, + {length: 0}, + }, + expectedSize: 0, + }, + } { + gotSize := sumRangesSize(tt.ranges) + + assert.Equal(t, tt.expectedSize, gotSize) + } +} diff --git a/pkg/ranger/file_test.go b/pkg/ranger/file_test.go index f247f0710..ce9006ebf 100644 --- a/pkg/ranger/file_test.go +++ b/pkg/ranger/file_test.go @@ -67,3 +67,23 @@ func TestFileRanger(t *testing.T) { } } } + +func TestFileRangerOpenFileError(t *testing.T) { + rr, err := FileRanger("") + if rr != nil { + t.Fatal("Ranger expected to be nil") + } + if err == nil { + t.Fatal("Error expected") + } +} + +func TestFileRangerHandlerFileStatError(t *testing.T) { + rr, err := FileHandleRanger(nil) + if rr != nil { + t.Fatal("Ranger expected to be nil") + } + if err == nil { + t.Fatal("Error expected") + } +} diff --git a/pkg/ranger/http_test.go b/pkg/ranger/http_test.go index d22f712c3..9e46ccf21 100644 --- a/pkg/ranger/http_test.go +++ b/pkg/ranger/http_test.go @@ -61,6 +61,24 @@ func TestHTTPRanger(t *testing.T) { } } } + +func TestHTTPRangerURLError(t *testing.T) { + rr, err := HTTPRanger("") + assert.Nil(t, rr) + assert.NotNil(t, err) +} + +func TestHTTPRangeStatusCodeOk(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + http.ServeContent(w, r, "test", time.Now(), strings.NewReader("")) + })) + rr, err := HTTPRanger(ts.URL) + assert.Nil(t, rr) + assert.NotNil(t, err) +} + func TestHTTPRangerSize(t *testing.T) { var content string ts := httptest.NewServer(http.HandlerFunc( diff --git a/pkg/ranger/reader_test.go b/pkg/ranger/reader_test.go index da2ee35b4..016a7bef7 100644 --- a/pkg/ranger/reader_test.go +++ b/pkg/ranger/reader_test.go @@ -8,6 +8,7 @@ import ( "context" "io/ioutil" "testing" + "github.com/stretchr/testify/assert" ) func TestByteRanger(t *testing.T) { @@ -134,3 +135,22 @@ func TestSubranger(t *testing.T) { } } } + +func TestSubrangerError(t *testing.T) { + for _, tt := range []struct { + name string + data string + offset, length int64 + }{ + {name: "Negative offset", data: "abcd", offset: -1}, + {name: "Offset is bigger than DataSize", data: "abcd", offset: 5}, + {name: "Length and offset is bigger than DataSize", data: "abcd", offset: 4, length: 1}, + } { + t.Run(tt.name, func(t *testing.T) { + rr, err := Subrange(ByteRanger([]byte(tt.data)), tt.offset, tt.length) + assert.Nil(t, rr) + assert.NotNil(t, err) + }) + + } +} diff --git a/pkg/ranger/readerat_test.go b/pkg/ranger/readerat_test.go new file mode 100644 index 000000000..70b21aa0a --- /dev/null +++ b/pkg/ranger/readerat_test.go @@ -0,0 +1,42 @@ +// Copyright (C) 2018 Storj Labs, Inc. +// See LICENSE for copying information. + +package ranger + +import ( + "testing" + "github.com/stretchr/testify/assert" + "context" +) + +func TestRange(t *testing.T) { + for _, tt := range []struct { + name string + offset int64 + length int64 + size int64 + }{ + { + name: "Negative offset", + offset: -2, + }, + + { + name: "Negative length", + offset: 2, + length: -1, + }, + } { + t.Run(tt.name, func(t *testing.T) { + rr := readerAtRanger{size: tt.size} + closer, err := rr.Range(context.Background(), tt.offset, tt.length) + assert.Nil(t, closer) + assert.NotNil(t, err) + }) + } +} + +func TestClose(t *testing.T) { + rr := readerAtReader{length:0} + assert.Nil(t, rr.Close()) +} \ No newline at end of file