Files
wehub-resource-sync 1b8708893a
Security Scan / tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:12:26 +08:00

313 lines
8.3 KiB
Go

package storage
import (
"bytes"
"context"
"io"
"os"
"path/filepath"
"sync"
"sync/atomic"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
// mockObjectStore is a test double that counts Get calls and adds a small
// delay to simulate real I/O. This lets us verify singleflight deduplication.
type mockObjectStore struct {
getCalls atomic.Int64
delay time.Duration
data map[string][]byte
}
func newMockObjectStore(delay time.Duration) *mockObjectStore {
return &mockObjectStore{
delay: delay,
data: make(map[string][]byte),
}
}
func (m *mockObjectStore) Put(_ context.Context, key string, r io.Reader) error {
b, err := io.ReadAll(r)
if err != nil {
return err
}
m.data[key] = b
return nil
}
func (m *mockObjectStore) Get(_ context.Context, key string) (io.ReadCloser, error) {
m.getCalls.Add(1)
time.Sleep(m.delay)
b, ok := m.data[key]
if !ok {
return nil, io.EOF
}
return io.NopCloser(bytes.NewReader(b)), nil
}
func (m *mockObjectStore) Head(_ context.Context, key string) (*ObjectMeta, error) {
b, ok := m.data[key]
if !ok {
return nil, io.EOF
}
return &ObjectMeta{
Key: key,
Size: int64(len(b)),
}, nil
}
func (m *mockObjectStore) Exists(_ context.Context, key string) (bool, error) {
_, ok := m.data[key]
return ok, nil
}
func (m *mockObjectStore) Delete(_ context.Context, key string) error {
delete(m.data, key)
return nil
}
func (m *mockObjectStore) List(_ context.Context, prefix string) ([]string, error) {
var keys []string
for k := range m.data {
if len(k) >= len(prefix) && k[:len(prefix)] == prefix {
keys = append(keys, k)
}
}
return keys, nil
}
var _ = Describe("FileManager", func() {
Describe("Upload", func() {
It("sends file data to the object store", func() {
store := newMockObjectStore(0)
cacheDir := GinkgoT().TempDir()
fm, err := NewFileManager(store, cacheDir)
Expect(err).ToNot(HaveOccurred())
// Write a temp file to upload
tmpFile := filepath.Join(GinkgoT().TempDir(), "upload-test.txt")
Expect(os.WriteFile(tmpFile, []byte("upload-content"), 0644)).To(Succeed())
Expect(fm.Upload(context.Background(), "my/key", tmpFile)).To(Succeed())
// Verify the mock store received the data
Expect(store.data).To(HaveKey("my/key"))
Expect(store.data["my/key"]).To(Equal([]byte("upload-content")))
})
It("returns error when local file does not exist", func() {
store := newMockObjectStore(0)
cacheDir := GinkgoT().TempDir()
fm, err := NewFileManager(store, cacheDir)
Expect(err).ToNot(HaveOccurred())
err = fm.Upload(context.Background(), "key", "/nonexistent/path")
Expect(err).To(HaveOccurred())
})
})
Describe("Download cache hit", func() {
It("serves from cache on second download without calling store again", func() {
store := newMockObjectStore(0)
store.data["cached-key"] = []byte("cached-content")
cacheDir := GinkgoT().TempDir()
fm, err := NewFileManager(store, cacheDir)
Expect(err).ToNot(HaveOccurred())
// First download populates cache
path1, err := fm.Download(context.Background(), "cached-key")
Expect(err).ToNot(HaveOccurred())
Expect(path1).ToNot(BeEmpty())
content, err := os.ReadFile(path1)
Expect(err).ToNot(HaveOccurred())
Expect(content).To(Equal([]byte("cached-content")))
Expect(store.getCalls.Load()).To(BeNumerically("==", 1))
// Second download should hit cache
path2, err := fm.Download(context.Background(), "cached-key")
Expect(err).ToNot(HaveOccurred())
Expect(path2).To(Equal(path1))
// store.Get should NOT have been called again
Expect(store.getCalls.Load()).To(BeNumerically("==", 1))
})
})
Describe("Delete", func() {
It("removes from both local cache and remote store", func() {
store := newMockObjectStore(0)
store.data["del-key"] = []byte("delete-me")
cacheDir := GinkgoT().TempDir()
fm, err := NewFileManager(store, cacheDir)
Expect(err).ToNot(HaveOccurred())
// Download to populate cache
localPath, err := fm.Download(context.Background(), "del-key")
Expect(err).ToNot(HaveOccurred())
Expect(localPath).ToNot(BeEmpty())
// Verify cache file exists
_, err = os.Stat(localPath)
Expect(err).ToNot(HaveOccurred())
// Delete
Expect(fm.Delete(context.Background(), "del-key")).To(Succeed())
// Local cache should be gone
_, err = os.Stat(localPath)
Expect(os.IsNotExist(err)).To(BeTrue())
// Remote store should be gone
Expect(store.data).ToNot(HaveKey("del-key"))
})
})
Describe("nil store (single-node mode)", func() {
var fm *FileManager
BeforeEach(func() {
cacheDir := GinkgoT().TempDir()
var err error
fm, err = NewFileManager(nil, cacheDir)
Expect(err).ToNot(HaveOccurred())
})
It("Upload is a no-op", func() {
tmpFile := filepath.Join(GinkgoT().TempDir(), "nil-upload.txt")
Expect(os.WriteFile(tmpFile, []byte("data"), 0644)).To(Succeed())
err := fm.Upload(context.Background(), "key", tmpFile)
Expect(err).ToNot(HaveOccurred())
})
It("Download returns an error", func() {
_, err := fm.Download(context.Background(), "key")
Expect(err).To(HaveOccurred())
})
It("Delete is a no-op", func() {
err := fm.Delete(context.Background(), "key")
Expect(err).ToNot(HaveOccurred())
})
It("IsConfigured returns false", func() {
Expect(fm.IsConfigured()).To(BeFalse())
})
})
Describe("singleflight deduplication", func() {
It("deduplicates concurrent downloads for the same key", func() {
store := newMockObjectStore(50 * time.Millisecond)
store.data["same-key"] = []byte("test-content")
cacheDir := GinkgoT().TempDir()
fm, err := NewFileManager(store, cacheDir)
Expect(err).ToNot(HaveOccurred())
const numGoroutines = 10
var wg sync.WaitGroup
errs := make([]error, numGoroutines)
paths := make([]string, numGoroutines)
for i := range numGoroutines {
wg.Go(func() {
defer GinkgoRecover()
p, e := fm.Download(context.Background(), "same-key")
paths[i] = p
errs[i] = e
})
}
wg.Wait()
for i, e := range errs {
Expect(e).ToNot(HaveOccurred(), "goroutine %d", i)
}
for i, p := range paths {
Expect(p).To(Equal(paths[0]), "goroutine %d path differs", i)
}
Expect(store.getCalls.Load()).To(BeNumerically("==", 1))
})
})
Describe("cachePath directory traversal prevention", func() {
var fm *FileManager
var cacheDir string
BeforeEach(func() {
cacheDir = GinkgoT().TempDir()
var err error
fm, err = NewFileManager(nil, cacheDir)
Expect(err).ToNot(HaveOccurred())
})
It("rejects keys with .. that escape the cache directory", func() {
_, err := fm.CachePath("../escape")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("escapes cache directory"))
})
It("rejects keys with nested .. traversal", func() {
_, err := fm.CachePath("foo/../../bar")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("escapes cache directory"))
})
It("allows normal keys", func() {
p, err := fm.CachePath("models/llama/weights.bin")
Expect(err).ToNot(HaveOccurred())
Expect(p).To(HavePrefix(cacheDir))
})
It("allows simple keys without slashes", func() {
p, err := fm.CachePath("myfile.txt")
Expect(err).ToNot(HaveOccurred())
Expect(p).To(Equal(filepath.Join(cacheDir, "myfile.txt")))
})
It("CacheExists returns false for traversal keys", func() {
Expect(fm.CacheExists("../etc/passwd")).To(BeFalse())
})
It("EvictCache returns error for traversal keys", func() {
err := fm.EvictCache("../../etc/shadow")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("escapes cache directory"))
})
})
Describe("different keys", func() {
It("does not serialize downloads for different keys", func() {
store := newMockObjectStore(50 * time.Millisecond)
store.data["key-a"] = []byte("content-a")
store.data["key-b"] = []byte("content-b")
cacheDir := GinkgoT().TempDir()
fm, err := NewFileManager(store, cacheDir)
Expect(err).ToNot(HaveOccurred())
var wg sync.WaitGroup
var errA, errB error
wg.Go(func() {
defer GinkgoRecover()
_, errA = fm.Download(context.Background(), "key-a")
})
wg.Go(func() {
defer GinkgoRecover()
_, errB = fm.Download(context.Background(), "key-b")
})
wg.Wait()
Expect(errA).ToNot(HaveOccurred())
Expect(errB).ToNot(HaveOccurred())
Expect(store.getCalls.Load()).To(BeNumerically("==", 2))
})
})
})