feat: complete API-driven projects with infrastructure and business acceptance
This commit is contained in:
@@ -9,11 +9,13 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Run(t *testing.T, store storage.Store) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
key := "contract/文件 +%.txt"
|
||||
defer store.Delete(ctx, key)
|
||||
if _, err := store.Get(ctx, key); !errors.Is(err, storage.ErrNotFound) {
|
||||
@@ -37,13 +39,19 @@ func Run(t *testing.T, store storage.Store) {
|
||||
if _, err := store.Put(ctx, "tooBig", strings.NewReader(strings.Repeat("a", 65)), 65, "text/plain"); !errors.Is(err, storage.ErrTooLarge) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, size := range []int64{0, 2, 4} {
|
||||
if _, err := store.Put(ctx, "sizeMismatch", strings.NewReader("abc"), size, "text/plain"); err == nil {
|
||||
t.Fatalf("declared size %d accepted for 3 bytes", size)
|
||||
}
|
||||
}
|
||||
defer store.Delete(context.Background(), "sizeMismatch")
|
||||
for _, key := range []string{"../bad", "a/../b", "/absolute", "a//b", "a\\b"} {
|
||||
if _, err := store.Put(ctx, key, strings.NewReader("a"), 1, "text/plain"); err == nil {
|
||||
t.Fatal(key)
|
||||
}
|
||||
}
|
||||
canceled, cancel := context.WithCancel(ctx)
|
||||
cancel()
|
||||
canceled, cancelRequest := context.WithCancel(ctx)
|
||||
cancelRequest()
|
||||
if _, err := store.Get(canceled, key); !errors.Is(err, context.Canceled) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
127
storage/storagetest/uploads.go
Normal file
127
storage/storagetest/uploads.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package storagetest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.yuntuo.hk/k2safe/go-omni/storage"
|
||||
)
|
||||
|
||||
type UploadStore interface {
|
||||
storage.Store
|
||||
storage.UploadPresigner
|
||||
storage.MultipartStore
|
||||
storage.PartPresigner
|
||||
}
|
||||
|
||||
func sendUpload(t *testing.T, client *http.Client, link storage.UploadRequest, body string) *http.Response {
|
||||
t.Helper()
|
||||
req, err := http.NewRequest(link.Method, link.URL, strings.NewReader(body))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header = link.Headers.Clone()
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
||||
resp.Body.Close()
|
||||
t.Fatalf("upload rejected: %d %s", resp.StatusCode, raw)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// RunUploads exercises both SDK upload and direct HTTP requests against a
|
||||
// caller-owned server with maxBytes >=6 MiB. The caller cleans its bucket.
|
||||
func RunUploads(t *testing.T, s UploadStore, client *http.Client) {
|
||||
t.Helper()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
link, err := s.PresignPut(ctx, "direct", 5, "text/plain", time.Minute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if link.Method != "PUT" || link.Headers.Get("Content-Length") != "5" || link.Headers.Get("Content-Type") != "text/plain" {
|
||||
t.Fatal("missing constraints", link.Headers)
|
||||
}
|
||||
resp := sendUpload(t, client, link, "hello")
|
||||
resp.Body.Close()
|
||||
defer s.Delete(context.Background(), "direct")
|
||||
object, err := s.Get(ctx, "direct")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, _ := io.ReadAll(object.Body)
|
||||
object.Body.Close()
|
||||
if string(raw) != "hello" {
|
||||
t.Fatal("bad direct body")
|
||||
}
|
||||
if _, err = s.PresignPut(ctx, "direct", -1, "text/plain", time.Minute); err == nil {
|
||||
t.Fatal("negative size accepted")
|
||||
}
|
||||
if _, err = s.PresignPut(ctx, "direct", 1, "text/plain", 2*time.Hour); err == nil {
|
||||
t.Fatal("long TTL accepted")
|
||||
}
|
||||
upload, err := s.CreateMultipart(ctx, "multipart", "text/plain")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.AbortMultipart(context.Background(), upload)
|
||||
data := strings.Repeat("a", 5<<20)
|
||||
first, err := s.UploadPart(ctx, upload, 1, strings.NewReader(data), int64(len(data)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
link, err = s.PresignPart(ctx, upload, 2, 5, time.Minute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp = sendUpload(t, client, link, "world")
|
||||
etag := resp.Header.Get("ETag")
|
||||
resp.Body.Close()
|
||||
if etag == "" {
|
||||
t.Fatal("no part ETag")
|
||||
}
|
||||
page, err := s.ListParts(ctx, upload, 0, 1)
|
||||
if err != nil || len(page.Parts) != 1 || page.NextMarker != 1 {
|
||||
t.Fatal(page, err)
|
||||
}
|
||||
next, err := s.ListParts(ctx, upload, page.NextMarker, 1)
|
||||
if err != nil || len(next.Parts) != 1 || next.Parts[0].Number != 2 || next.Parts[0].Size != 5 || next.NextMarker != 0 {
|
||||
t.Fatal(next, err)
|
||||
}
|
||||
first.Size = 1 // The provider's size must prevail over caller metadata.
|
||||
info, err := s.CompleteMultipart(ctx, upload, []storage.Part{first, {Number: 2, ETag: etag, Size: 1}})
|
||||
if err != nil || info.Size != int64(len(data)+5) {
|
||||
t.Fatal(info, err)
|
||||
}
|
||||
defer s.Delete(context.Background(), "multipart")
|
||||
object, err = s.Get(ctx, "multipart")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, err = io.ReadAll(object.Body)
|
||||
object.Body.Close()
|
||||
if err != nil || string(raw) != data+"world" {
|
||||
t.Fatal("multipart bytes mismatch", len(raw), err)
|
||||
}
|
||||
canceled, err := s.CreateMultipart(ctx, "canceled", "text/plain")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
if err = s.AbortMultipart(ctx, canceled); err != nil {
|
||||
t.Fatal("abort "+strconv.Itoa(i), err)
|
||||
}
|
||||
}
|
||||
if _, err = s.ListParts(ctx, canceled, 0, 1); err == nil {
|
||||
t.Fatal("aborted upload remains")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user