Implemented shared S3 audio caching for prepare and restore --include-audio
This commit is contained in:
145
internal/audio/s3_audio_test.go
Normal file
145
internal/audio/s3_audio_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package audio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/adapters/storage"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
||||
)
|
||||
|
||||
func TestMaterializeS3AudioCacheMissDownloadsAndPopulatesCache(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")))
|
||||
|
||||
result, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("MaterializeS3Audio() error = %v", err)
|
||||
}
|
||||
if !result.Downloaded || result.CacheHit {
|
||||
t.Fatalf("result = %#v, want downloaded miss", result)
|
||||
}
|
||||
if len(fake.Downloads) != 1 {
|
||||
t.Fatalf("downloads = %d, want 1", len(fake.Downloads))
|
||||
}
|
||||
assertFileEquals(t, req.DestPath, "audio")
|
||||
assertFileEquals(t, req.SpoolPath, "audio")
|
||||
assertFileEquals(t, result.CachePath, "audio")
|
||||
if result.Checksum == "" {
|
||||
t.Fatalf("checksum is empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaterializeS3AudioCacheHitSkipsDownload(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := &storage.FakeBackend{}
|
||||
key := "dnd/campaigns/forsaken/sessions/2026-04-19/audio/alice.flac"
|
||||
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)
|
||||
}
|
||||
writeAudioTestFile(t, cachePath, "audio")
|
||||
|
||||
result, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("MaterializeS3Audio() error = %v", err)
|
||||
}
|
||||
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))
|
||||
}
|
||||
assertFileEquals(t, req.DestPath, "audio")
|
||||
if result.SpoolPath != "" {
|
||||
t.Fatalf("spool path = %q, want empty on cache hit", result.SpoolPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaterializeS3AudioInvalidCacheRefreshesFromS3(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")})
|
||||
req := testMaterializeRequest(t, root, fake, key, int64(len("fresh-audio")))
|
||||
cachePath, err := artifacts.S3AudioCachePath(req.CacheRoot, req.Bucket, key)
|
||||
if err != nil {
|
||||
t.Fatalf("cache path: %v", err)
|
||||
}
|
||||
writeAudioTestFile(t, cachePath, "stale")
|
||||
|
||||
result, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("MaterializeS3Audio() error = %v", err)
|
||||
}
|
||||
if !result.Downloaded || result.CacheHit {
|
||||
t.Fatalf("result = %#v, want refreshed miss", result)
|
||||
}
|
||||
if len(fake.Downloads) != 1 {
|
||||
t.Fatalf("downloads = %d, want 1", len(fake.Downloads))
|
||||
}
|
||||
assertFileEquals(t, req.DestPath, "fresh-audio")
|
||||
assertFileEquals(t, cachePath, "fresh-audio")
|
||||
}
|
||||
|
||||
func TestMaterializeS3AudioDownloadFailureLeavesDestinationMissing(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := &storage.FakeBackend{DownloadErr: os.ErrPermission}
|
||||
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")))
|
||||
|
||||
_, err := MaterializeS3Audio(context.Background(), req)
|
||||
if err == nil || !strings.Contains(err.Error(), "download s3 audio object") {
|
||||
t.Fatalf("error = %v, want download error", err)
|
||||
}
|
||||
assertMissing(t, req.DestPath)
|
||||
}
|
||||
|
||||
func testMaterializeRequest(t *testing.T, root string, fake *storage.FakeBackend, key string, size int64) S3MaterializeRequest {
|
||||
t.Helper()
|
||||
return S3MaterializeRequest{
|
||||
Store: fake,
|
||||
Object: storage.ObjectInfo{Key: key, Size: size, ETag: "etag"},
|
||||
Bucket: "my-dnd-archive",
|
||||
CacheRoot: filepath.Join(root, "cache"),
|
||||
CacheEnabled: true,
|
||||
SpoolPath: filepath.Join(root, "spool", "alice.flac"),
|
||||
DestPath: filepath.Join(root, "work", "audio", "alice.flac"),
|
||||
}
|
||||
}
|
||||
|
||||
func writeAudioTestFile(t *testing.T, path, contents string) {
|
||||
t.Helper()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatalf("mkdir %q: %v", path, err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
|
||||
t.Fatalf("write %q: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertFileEquals(t *testing.T, path, want string) {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read %q: %v", path, err)
|
||||
}
|
||||
if string(data) != want {
|
||||
t.Fatalf("%q = %q, want %q", path, string(data), want)
|
||||
}
|
||||
}
|
||||
|
||||
func assertMissing(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
t.Fatalf("stat %q = %v, want not exists", path, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user