Bind audio cache reuse to remote identity
This commit is contained in:
@@ -40,12 +40,11 @@ func TestMaterializeS3AudioCacheHitSkipsDownload(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := &storage.FakeBackend{}
|
||||
key := "dnd/campaigns/forsaken/sessions/2026-04-19/audio/alice.flac"
|
||||
fake.SeedObject(storage.FakeObject{Key: key, Data: []byte("audio")})
|
||||
req := testMaterializeRequest(t, root, fake, key, int64(len("audio")))
|
||||
cachePath, err := artifacts.S3AudioCachePath(req.CacheRoot, req.Bucket, key)
|
||||
if err != nil {
|
||||
t.Fatalf("cache path: %v", err)
|
||||
if _, err := MaterializeS3Audio(context.Background(), req); err != nil {
|
||||
t.Fatalf("populate cache: %v", err)
|
||||
}
|
||||
writeAudioTestFile(t, cachePath, "audio")
|
||||
|
||||
result, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err != nil {
|
||||
@@ -54,8 +53,8 @@ func TestMaterializeS3AudioCacheHitSkipsDownload(t *testing.T) {
|
||||
if !result.CacheHit || result.Downloaded {
|
||||
t.Fatalf("result = %#v, want cache hit", result)
|
||||
}
|
||||
if len(fake.Downloads) != 0 {
|
||||
t.Fatalf("downloads = %d, want 0", len(fake.Downloads))
|
||||
if len(fake.Downloads) != 1 {
|
||||
t.Fatalf("downloads = %d, want only the initial population download", len(fake.Downloads))
|
||||
}
|
||||
assertFileEquals(t, req.DestPath, "audio")
|
||||
if result.SpoolPath != "" {
|
||||
@@ -63,6 +62,192 @@ func TestMaterializeS3AudioCacheHitSkipsDownload(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaterializeS3AudioRefreshesSameSizeChangedGeneration(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := &storage.FakeBackend{}
|
||||
key := "dnd/campaigns/forsaken/sessions/2026-04-19/audio/alice.flac"
|
||||
fake.SeedObject(storage.FakeObject{Key: key, Data: []byte("audio-one"), ETag: "generation-one"})
|
||||
req := testMaterializeRequest(t, root, fake, key, int64(len("audio-one")))
|
||||
req.Object.ETag = "generation-one"
|
||||
if _, err := MaterializeS3Audio(context.Background(), req); err != nil {
|
||||
t.Fatalf("initial MaterializeS3Audio() error = %v", err)
|
||||
}
|
||||
|
||||
fake.SeedObject(storage.FakeObject{Key: key, Data: []byte("audio-two"), ETag: "generation-two"})
|
||||
req.Object.ETag = "generation-two"
|
||||
result, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("refreshed MaterializeS3Audio() error = %v", err)
|
||||
}
|
||||
if !result.Downloaded || result.CacheHit || len(fake.Downloads) != 2 {
|
||||
t.Fatalf("result=%#v downloads=%d, want refreshed download", result, len(fake.Downloads))
|
||||
}
|
||||
assertFileEquals(t, req.DestPath, "audio-two")
|
||||
}
|
||||
|
||||
func TestMaterializeS3AudioRefreshesCorruptedSameSizeCache(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := &storage.FakeBackend{}
|
||||
key := "dnd/campaigns/forsaken/sessions/2026-04-19/audio/alice.flac"
|
||||
fake.SeedObject(storage.FakeObject{Key: key, Data: []byte("fresh-audio"), ETag: "generation"})
|
||||
req := testMaterializeRequest(t, root, fake, key, int64(len("fresh-audio")))
|
||||
req.Object.ETag = "generation"
|
||||
if _, err := MaterializeS3Audio(context.Background(), req); err != nil {
|
||||
t.Fatalf("initial MaterializeS3Audio() error = %v", err)
|
||||
}
|
||||
writeAudioTestFile(t, cachePathForTest(t, req), "stale-audio")
|
||||
|
||||
result, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("refreshed MaterializeS3Audio() error = %v", err)
|
||||
}
|
||||
if !result.Downloaded || result.CacheHit || len(fake.Downloads) != 2 {
|
||||
t.Fatalf("result=%#v downloads=%d, want refreshed download", result, len(fake.Downloads))
|
||||
}
|
||||
assertFileEquals(t, req.DestPath, "fresh-audio")
|
||||
}
|
||||
|
||||
func TestMaterializeS3AudioMissingIdentityDoesNotReuseCache(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := &storage.FakeBackend{}
|
||||
key := "dnd/campaigns/forsaken/sessions/2026-04-19/audio/alice.flac"
|
||||
fake.SeedObject(storage.FakeObject{Key: key, Data: []byte("fresh-audio"), ETag: "generation"})
|
||||
req := testMaterializeRequest(t, root, fake, key, int64(len("fresh-audio")))
|
||||
req.Object.ETag = "generation"
|
||||
writeAudioTestFile(t, cachePathForTest(t, req), "stale-audio")
|
||||
|
||||
result, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("MaterializeS3Audio() error = %v", err)
|
||||
}
|
||||
if !result.Downloaded || result.CacheHit || len(fake.Downloads) != 1 {
|
||||
t.Fatalf("result=%#v downloads=%d, want cache refresh", result, len(fake.Downloads))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaterializeS3AudioTreatsMultipartETagAsGeneration(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := &storage.FakeBackend{}
|
||||
key := "dnd/campaigns/forsaken/sessions/2026-04-19/audio/alice.flac"
|
||||
fake.SeedObject(storage.FakeObject{Key: key, Data: []byte("fresh-audio"), ETag: "not-a-checksum-2"})
|
||||
req := testMaterializeRequest(t, root, fake, key, int64(len("fresh-audio")))
|
||||
req.Object.ETag = "not-a-checksum-2"
|
||||
if _, err := MaterializeS3Audio(context.Background(), req); err != nil {
|
||||
t.Fatalf("populate cache: %v", err)
|
||||
}
|
||||
result, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("reuse cache: %v", err)
|
||||
}
|
||||
if !result.CacheHit || result.Downloaded || len(fake.Downloads) != 1 {
|
||||
t.Fatalf("result=%#v downloads=%d, want verified cache hit", result, len(fake.Downloads))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaterializeS3AudioUnknownRemoteSizeDoesNotReuseCache(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := &storage.FakeBackend{}
|
||||
key := "dnd/campaigns/forsaken/sessions/2026-04-19/audio/alice.flac"
|
||||
fake.SeedObject(storage.FakeObject{Key: key, Data: []byte("fresh-audio"), ETag: "generation"})
|
||||
req := testMaterializeRequest(t, root, fake, key, 0)
|
||||
req.Object.ETag = "generation"
|
||||
if _, err := MaterializeS3Audio(context.Background(), req); err != nil {
|
||||
t.Fatalf("first MaterializeS3Audio() error = %v", err)
|
||||
}
|
||||
result, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("second MaterializeS3Audio() error = %v", err)
|
||||
}
|
||||
if !result.Downloaded || result.CacheHit || len(fake.Downloads) != 2 {
|
||||
t.Fatalf("result=%#v downloads=%d, want refresh without identity evidence", result, len(fake.Downloads))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaterializeS3AudioSymlinkedCacheRefreshesWithoutFollowingLink(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := &storage.FakeBackend{}
|
||||
key := "dnd/campaigns/forsaken/sessions/2026-04-19/audio/alice.flac"
|
||||
fake.SeedObject(storage.FakeObject{Key: key, Data: []byte("fresh-audio"), ETag: "generation"})
|
||||
req := testMaterializeRequest(t, root, fake, key, int64(len("fresh-audio")))
|
||||
req.Object.ETag = "generation"
|
||||
if _, err := MaterializeS3Audio(context.Background(), req); err != nil {
|
||||
t.Fatalf("populate cache: %v", err)
|
||||
}
|
||||
|
||||
cachePath := cachePathForTest(t, req)
|
||||
outsidePath := filepath.Join(root, "outside-audio")
|
||||
writeAudioTestFile(t, outsidePath, "untrusted-audio")
|
||||
if err := os.Remove(cachePath); err != nil {
|
||||
t.Fatalf("remove cache file: %v", err)
|
||||
}
|
||||
if err := os.Symlink(outsidePath, cachePath); err != nil {
|
||||
t.Fatalf("create cache symlink: %v", err)
|
||||
}
|
||||
|
||||
result, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("refresh symlinked cache: %v", err)
|
||||
}
|
||||
if !result.Downloaded || result.CacheHit || len(fake.Downloads) != 2 {
|
||||
t.Fatalf("result=%#v downloads=%d, want safe refresh", result, len(fake.Downloads))
|
||||
}
|
||||
assertFileEquals(t, outsidePath, "untrusted-audio")
|
||||
assertFileEquals(t, req.DestPath, "fresh-audio")
|
||||
}
|
||||
|
||||
func TestMaterializeS3AudioNonRegularCacheFailsClearly(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := &storage.FakeBackend{}
|
||||
key := "dnd/campaigns/forsaken/sessions/2026-04-19/audio/alice.flac"
|
||||
fake.SeedObject(storage.FakeObject{Key: key, Data: []byte("fresh-audio"), ETag: "generation"})
|
||||
req := testMaterializeRequest(t, root, fake, key, int64(len("fresh-audio")))
|
||||
req.Object.ETag = "generation"
|
||||
if _, err := MaterializeS3Audio(context.Background(), req); err != nil {
|
||||
t.Fatalf("populate cache: %v", err)
|
||||
}
|
||||
|
||||
cachePath := cachePathForTest(t, req)
|
||||
if err := os.Remove(cachePath); err != nil {
|
||||
t.Fatalf("remove cache file: %v", err)
|
||||
}
|
||||
if err := os.Mkdir(cachePath, 0o755); err != nil {
|
||||
t.Fatalf("create cache directory: %v", err)
|
||||
}
|
||||
_, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err == nil || !strings.Contains(err.Error(), "populate audio cache") {
|
||||
t.Fatalf("error = %v, want clear cache refresh error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaterializeS3AudioInterruptedRefreshCanRetry(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := &storage.FakeBackend{}
|
||||
key := "dnd/campaigns/forsaken/sessions/2026-04-19/audio/alice.flac"
|
||||
fake.SeedObject(storage.FakeObject{Key: key, Data: []byte("audio-one"), ETag: "generation-one"})
|
||||
req := testMaterializeRequest(t, root, fake, key, int64(len("audio-one")))
|
||||
req.Object.ETag = "generation-one"
|
||||
if _, err := MaterializeS3Audio(context.Background(), req); err != nil {
|
||||
t.Fatalf("populate cache: %v", err)
|
||||
}
|
||||
|
||||
fake.SeedObject(storage.FakeObject{Key: key, Data: []byte("audio-two"), ETag: "generation-two"})
|
||||
req.Object.ETag = "generation-two"
|
||||
fake.DownloadErr = os.ErrPermission
|
||||
if _, err := MaterializeS3Audio(context.Background(), req); err == nil {
|
||||
t.Fatal("MaterializeS3Audio() error = nil, want interrupted refresh failure")
|
||||
}
|
||||
fake.DownloadErr = nil
|
||||
|
||||
result, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("retry refresh: %v", err)
|
||||
}
|
||||
if !result.Downloaded || result.CacheHit {
|
||||
t.Fatalf("result = %#v, want refreshed download after retry", result)
|
||||
}
|
||||
assertFileEquals(t, req.DestPath, "audio-two")
|
||||
}
|
||||
|
||||
func TestMaterializeS3AudioInvalidCacheRefreshesFromS3(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := &storage.FakeBackend{}
|
||||
@@ -116,6 +301,15 @@ func testMaterializeRequest(t *testing.T, root string, fake *storage.FakeBackend
|
||||
}
|
||||
}
|
||||
|
||||
func cachePathForTest(t *testing.T, req S3MaterializeRequest) string {
|
||||
t.Helper()
|
||||
cachePath, err := artifacts.S3AudioCachePath(req.CacheRoot, req.Bucket, req.Object.Key)
|
||||
if err != nil {
|
||||
t.Fatalf("cache path: %v", err)
|
||||
}
|
||||
return cachePath
|
||||
}
|
||||
|
||||
func writeAudioTestFile(t *testing.T, path, contents string) {
|
||||
t.Helper()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user