1package blob23import (4 "context"5 "errors"6 "net/http"7 "net/http/httptest"8 "strings"9 "testing"10)1112func TestUploadStoresClaimScopedNameAndPath(t *testing.T) {13 s := testService(t)14 body := "same bytes"15 request := httptest.NewRequest(http.MethodPut, "https://relay.test/upload?filename=note.txt&path=docs%2Fnote.txt", strings.NewReader(body))16 request.Header.Set("Content-Type", "text/plain")17 if response := record(s.Handler(), request); response.Code != http.StatusOK {18 t.Fatalf("upload status = %d: %s", response.Code, response.Body.String())19 }20 entries, err := s.ListClaimMetadata(context.Background(), "uploader")21 if err != nil || len(entries) != 1 || entries[0].Name != "note.txt" || entries[0].Path != "docs/note.txt" {22 t.Fatalf("metadata = %+v, %v", entries, err)23 }24 other := strings.Repeat("b", 64)25 if _, err := s.Put(context.Background(), PutOptions{Reader: strings.NewReader(body), Type: "text/plain", Uploader: other, Name: "other.txt", Path: "other.txt"}); err != nil {26 t.Fatal(err)27 }28 entries, err = s.ListClaimMetadata(context.Background(), other)29 if err != nil || len(entries) != 1 || entries[0].Name != "other.txt" {30 t.Fatalf("deduplicated metadata = %+v, %v", entries, err)31 }32}3334func TestBlobAccessIsDurableAndCannotChangeOnDeduplication(t *testing.T) {35 s := testService(t)36 ctx := context.Background()37 entry, err := s.Put(ctx, PutOptions{Reader: strings.NewReader("member bytes"), Type: "text/plain", Uploader: "uploader", Access: AccessMembers})38 if err != nil {39 t.Fatal(err)40 }41 if access, err := s.BlobAccess(ctx, entry.SHA256); err != nil || access != AccessMembers {42 t.Fatalf("access = %q, %v", access, err)43 }44 if _, err := s.Put(ctx, PutOptions{Reader: strings.NewReader("member bytes"), Type: "text/plain", Uploader: "other", Access: AccessPublic}); !errors.Is(err, ErrAccessConflict) {45 t.Fatalf("public deduplication error = %v", err)46 }47 if access, err := s.BlobAccess(ctx, entry.SHA256); err != nil || access != AccessMembers {48 t.Fatalf("access changed after rejected deduplication = %q, %v", access, err)49 }50}5152func TestValidateClaimPath(t *testing.T) {53 for _, path := range []string{"", "file.txt", "dir/file.txt"} {54 if _, err := validateClaimPath(path); err != nil {55 t.Errorf("validateClaimPath(%q): %v", path, err)56 }57 }58 for _, path := range []string{"/file.txt", "../file.txt", "dir/../../file.txt", "dir/../secret.txt", "dir/./file.txt", "dir//file.txt", "dir\\file.txt", "dir/", "a\x00b"} {59 if _, err := validateClaimPath(path); err == nil {60 t.Errorf("validateClaimPath(%q) succeeded", path)61 }62 }63 if !errors.Is(validateClaimName("dir/file.txt"), errInvalidClaimName) {64 t.Fatal("path was accepted as a filename")65 }66 if _, _, err := claimMetadata("other.txt", "dir/file.txt"); !errors.Is(err, errMismatchedClaimName) {67 t.Fatalf("mismatched filename accepted: %v", err)68 }69 if name, cleanPath, err := claimMetadata("", "dir/file.txt"); err != nil || name != "file.txt" || cleanPath != "dir/file.txt" {70 t.Fatalf("path basename metadata = %q, %q, %v", name, cleanPath, err)71 }72 if err := validateClaimName(string([]byte{0xff})); !errors.Is(err, errInvalidClaimName) {73 t.Fatalf("invalid UTF-8 filename accepted: %v", err)74 }75}7677func TestIdenticalFilesPreserveDistinctFolderPaths(t *testing.T) {78 s := testService(t)79 ctx := context.Background()80 for _, filePath := range []string{"one/note.txt", "two/note.txt", "one/note.txt"} {81 if _, err := s.Put(ctx, PutOptions{Reader: strings.NewReader("same bytes"), Type: "text/plain", Uploader: "uploader", Name: "note.txt", Path: filePath}); err != nil {82 t.Fatal(err)83 }84 }85 entries, err := s.ListClaimMetadata(ctx, "uploader")86 if err != nil || len(entries) != 2 {87 t.Fatalf("distinct paths lost or repeated: %#v, %v", entries, err)88 }89 if entries[0].SHA256 != entries[1].SHA256 || entries[0].Path == entries[1].Path {90 t.Fatalf("same content should retain both paths: %#v", entries)91 }92}