Files
go-omni/storage/local/read_test.go

126 lines
4.0 KiB
Go

package local
import (
"context"
"io"
"mime"
"mime/multipart"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"git.yuntuo.hk/k2safe/go-omni/storage"
)
func TestSignedPayloadRanges(t *testing.T) {
store, err := New(t.TempDir(), 1<<20)
if err != nil {
t.Fatal(err)
}
data := "%PDF-1.4\n" + strings.Repeat("0123456789", 100) + "\n%%EOF\n"
ctx := context.Background()
if _, err = store.Put(ctx, "private/document", strings.NewReader(data), int64(len(data)), "application/pdf"); err != nil {
t.Fatal(err)
}
signer, _ := storage.NewSigner(store, "https://files.example/read", []byte(strings.Repeat("s", 32)))
link, err := signer.PresignGetWithOptions(ctx, "private/document", 30*time.Minute, storage.ReadOptions{Disposition: storage.DispositionInline, FileName: "示例.pdf"})
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
method, span string
status int
want string
}{
{"GET", "", 200, data}, {"HEAD", "", 200, ""}, {"HEAD", "bytes=0-3", 200, ""},
{"GET", "bytes=0-7", 206, data[:8]}, {"GET", "bytes=20-", 206, data[20:]}, {"GET", "bytes=-6", 206, data[len(data)-6:]},
{"GET", "bytes=1000-999999", 206, data[1000:]}, {"GET", "bytes=999999-", 416, ""}, {"GET", "bytes=bad", 416, ""},
} {
t.Run(tc.method+tc.span, func(t *testing.T) {
r := httptest.NewRequest(tc.method, link.URL, nil)
if tc.span != "" {
r.Header.Set("Range", tc.span)
}
w := httptest.NewRecorder()
signer.ServeHTTP(w, r)
if w.Code != tc.status {
t.Fatal(w.Code)
}
if tc.status != 416 && w.Body.String() != tc.want {
t.Fatal("wrong payload slice")
}
if tc.status == 206 && (w.Header().Get("Content-Length") != strconv.Itoa(len(tc.want)) || !strings.HasSuffix(w.Header().Get("Content-Range"), "/"+strconv.Itoa(len(data)))) {
t.Fatal(w.Header())
}
if tc.status == 200 && w.Header().Get("Content-Length") != strconv.Itoa(len(data)) {
t.Fatal("metadata included in length")
}
if tc.span == "bytes=999999-" && w.Header().Get("Content-Range") != "bytes */"+strconv.Itoa(len(data)) {
t.Fatal(w.Header())
}
if w.Header().Get("Cache-Control") != "private, no-store" {
t.Fatal("cache policy")
}
if tc.status != 416 {
kind, params, err := mime.ParseMediaType(w.Header().Get("Content-Disposition"))
if err != nil || kind != "inline" || params["filename"] != "示例.pdf" || w.Header().Get("Content-Type") != "application/pdf" {
t.Fatal(w.Header(), err)
}
}
})
}
r := httptest.NewRequest("GET", link.URL, nil)
r.Header.Set("Range", "bytes=0-3,10-13")
w := httptest.NewRecorder()
signer.ServeHTTP(w, r)
kind, params, err := mime.ParseMediaType(w.Header().Get("Content-Type"))
if err != nil || w.Code != 206 || kind != "multipart/byteranges" {
t.Fatal(w.Header(), err)
}
reader := multipart.NewReader(w.Body, params["boundary"])
for _, want := range []string{data[:4], data[10:14]} {
p, err := reader.NextPart()
if err != nil {
t.Fatal(err)
}
got, err := io.ReadAll(p)
if err != nil || string(got) != want {
t.Fatal("multipart bytes", err)
}
}
if _, err := reader.NextPart(); err != io.EOF {
t.Fatal(err)
}
// Random access cannot expose the managed header and still observes context.
ctx, cancel := context.WithCancel(ctx)
defer cancel()
object, err := store.Get(ctx, "private/document")
if err != nil {
t.Fatal(err)
}
defer object.Body.Close()
seeker := object.Body.(io.ReadSeeker)
if size, err := seeker.Seek(0, io.SeekEnd); err != nil || size != int64(len(data)) {
t.Fatal(size, err)
}
if _, err := seeker.Seek(-1, io.SeekStart); err == nil {
t.Fatal("seek into header")
}
if _, err := seeker.Seek(0, io.SeekStart); err != nil {
t.Fatal(err)
}
prefix := make([]byte, 8)
if _, err := io.ReadFull(seeker, prefix); err != nil || string(prefix) != data[:8] {
t.Fatal("metadata exposed", err)
}
cancel()
if _, err := seeker.Seek(0, io.SeekStart); err != context.Canceled {
t.Fatal(err)
}
if _, err := seeker.Read(prefix); err != context.Canceled {
t.Fatal(err)
}
}