package uploads import ( "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "os" "path/filepath" "regexp" "testing" "time" ) func TestStore_SaveAndRetrieve(t *testing.T) { store, err := New(t.TempDir()) if err != nil { t.Fatalf("new store: %v", err) } data := []byte("hello uploads") sum := sha256.Sum256(data) wantSHA := hex.EncodeToString(sum[:]) fileID, name, size, sha256Hex, absPath, err := store.Save(data, "hello.txt") if err != nil { t.Fatalf("save: %v", err) } if fileID == "" || !regexp.MustCompile(`^[0-9a-f]{32}$`).MatchString(fileID) { t.Errorf("fileID=%q, want 32 lowercase hex chars", fileID) } if name != "hello.txt" { t.Errorf("name=%q, want hello.txt", name) } if size != int64(len(data)) { t.Errorf("size=%d, want %d", size, len(data)) } if sha256Hex != wantSHA { t.Errorf("sha256=%q, want %q", sha256Hex, wantSHA) } if !filepath.IsAbs(absPath) { t.Errorf("absPath=%q is not absolute", absPath) } if filepath.Base(absPath) != fileID { t.Errorf("absPath base=%q, want fileID %q", filepath.Base(absPath), fileID) } gotData, err := os.ReadFile(absPath) if err != nil { t.Fatalf("read data file: %v", err) } if string(gotData) != string(data) { t.Errorf("data=%q, want %q", gotData, data) } info, err := os.Stat(absPath) if err != nil { t.Fatalf("stat data file: %v", err) } if info.Mode().Perm() != 0o640 { t.Errorf("data mode=%o, want %o", info.Mode().Perm(), 0o640) } metaPath := absPath + ".meta.json" metaBytes, err := os.ReadFile(metaPath) if err != nil { t.Fatalf("read sidecar: %v", err) } var sc sidecar if err := json.Unmarshal(metaBytes, &sc); err != nil { t.Fatalf("unmarshal sidecar: %v", err) } if sc.Name != "hello.txt" { t.Errorf("sidecar name=%q, want hello.txt", sc.Name) } if sc.Size != int64(len(data)) { t.Errorf("sidecar size=%d, want %d", sc.Size, len(data)) } if sc.Sha256 != wantSHA { t.Errorf("sidecar sha256=%q, want %q", sc.Sha256, wantSHA) } if sc.UploadedAt.IsZero() { t.Errorf("sidecar uploaded_at is zero") } metaInfo, err := os.Stat(metaPath) if err != nil { t.Fatalf("stat sidecar: %v", err) } if metaInfo.Mode().Perm() != 0o600 { t.Errorf("sidecar mode=%o, want %o", metaInfo.Mode().Perm(), 0o600) } validatedID, err := store.Validate(absPath) if err != nil { t.Fatalf("validate: %v", err) } if validatedID != fileID { t.Errorf("validatedID=%q, want %q", validatedID, fileID) } } func TestStore_AbsPath_RejectsMalformed(t *testing.T) { store, err := New(t.TempDir()) if err != nil { t.Fatalf("new store: %v", err) } validID := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" if _, err := store.AbsPath(validID); err != nil { t.Errorf("valid fileID rejected: %v", err) } cases := []string{ "", "abc", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "0123456789abcdef0123456789abcdefg", "0123456789abcdef0123456789abcde ", } for _, id := range cases { t.Run(fmt.Sprintf("id=%q", id), func(t *testing.T) { _, err := store.AbsPath(id) if err == nil { t.Errorf("AbsPath(%q) succeeded, want error", id) } }) } } func TestStore_Validate_RejectsOutsideRoot(t *testing.T) { if os.PathSeparator != '/' { t.Skip("Unix-style path test") } store, err := New(t.TempDir()) if err != nil { t.Fatalf("new store: %v", err) } fileID := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" if err := os.WriteFile(filepath.Join(store.Root, fileID), []byte("x"), 0o640); err != nil { t.Fatalf("create data file: %v", err) } if err := os.WriteFile(filepath.Join(store.Root, fileID+".meta.json"), []byte(`{"uploaded_at":"`+time.Now().Format(time.RFC3339Nano)+`"}`), 0o600); err != nil { t.Fatalf("create sidecar: %v", err) } cases := []struct { name string path string }{ {"parent", filepath.Join(store.Root, "..", "other", fileID)}, {"dotdot", filepath.Join(store.Root, "..", "..", "tmp", fileID)}, {"different_volume", "/other/volume/" + fileID}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { _, err := store.Validate(c.path) if err == nil { t.Errorf("Validate(%q) succeeded, want error", c.path) } }) } } func TestStore_Sweep_DeletesExpiredKeepsRecent(t *testing.T) { store, err := New(t.TempDir()) if err != nil { t.Fatalf("new store: %v", err) } now := time.Now() expiredID := "00000000000000000000000000000001" freshID := "00000000000000000000000000000002" noMetaID := "00000000000000000000000000000003" create := func(id string, uploadedAt *time.Time, withData bool) { if withData { if err := os.WriteFile(filepath.Join(store.Root, id), []byte("x"), 0o640); err != nil { t.Fatalf("create data file %s: %v", id, err) } } if uploadedAt != nil { sc := map[string]any{ "name": "x", "size": 1, "sha256": "abc", "uploaded_at": uploadedAt.Format(time.RFC3339Nano), } b, _ := json.Marshal(sc) if err := os.WriteFile(filepath.Join(store.Root, id+".meta.json"), b, 0o600); err != nil { t.Fatalf("create sidecar %s: %v", id, err) } } } create(expiredID, timePtr(now.Add(-2*time.Hour)), true) create(freshID, timePtr(now), true) create(noMetaID, nil, true) deleted, err := store.Sweep(1 * time.Hour) if err != nil { t.Fatalf("sweep: %v", err) } if deleted != 1 { t.Errorf("deleted=%d, want 1", deleted) } if _, err := os.Stat(filepath.Join(store.Root, expiredID)); !os.IsNotExist(err) { t.Errorf("expired data file still exists") } if _, err := os.Stat(filepath.Join(store.Root, expiredID+".meta.json")); !os.IsNotExist(err) { t.Errorf("expired sidecar still exists") } if _, err := os.Stat(filepath.Join(store.Root, freshID)); err != nil { t.Errorf("fresh data file missing: %v", err) } if _, err := os.Stat(filepath.Join(store.Root, noMetaID)); err != nil { t.Errorf("no-sidecar data file missing: %v", err) } } func TestStore_Sweep_OrphanSidecarReaped(t *testing.T) { store, err := New(t.TempDir()) if err != nil { t.Fatalf("new store: %v", err) } id := "00000000000000000000000000000004" sc := map[string]any{ "name": "orphan", "size": 1, "sha256": "abc", "uploaded_at": time.Now().Add(-2 * time.Hour).Format(time.RFC3339Nano), } b, _ := json.Marshal(sc) if err := os.WriteFile(filepath.Join(store.Root, id+".meta.json"), b, 0o600); err != nil { t.Fatalf("create orphan sidecar: %v", err) } deleted, err := store.Sweep(1 * time.Hour) if err != nil { t.Fatalf("sweep: %v", err) } if deleted != 1 { t.Errorf("deleted=%d, want 1", deleted) } if _, err := os.Stat(filepath.Join(store.Root, id+".meta.json")); !os.IsNotExist(err) { t.Errorf("orphan sidecar still exists") } } func timePtr(t time.Time) *time.Time { return &t }