package keyframe import ( "os" "testing" "time" ) func TestGetCacheKey(t *testing.T) { // Create a temporary file tmpFile, err := os.CreateTemp("", "test-video-*.mp4") if err != nil { t.Fatal(err) } defer os.Remove(tmpFile.Name()) tmpFile.Close() key1, err := GetCacheKey(tmpFile.Name()) if err != nil { t.Fatalf("GetCacheKey failed: %v", err) } if key1 == "" { t.Error("cache key should not be empty") } // Get key again - should be same key2, err := GetCacheKey(tmpFile.Name()) if err != nil { t.Fatalf("GetCacheKey failed: %v", err) } if key1 != key2 { t.Errorf("cache keys should match: %s != %s", key1, key2) } // Modify file - key should change // Need at least 1 second for mod time to change time.Sleep(1100 * time.Millisecond) if err := os.WriteFile(tmpFile.Name(), []byte("modified"), 0644); err != nil { t.Fatal(err) } key3, err := GetCacheKey(tmpFile.Name()) if err != nil { t.Fatalf("GetCacheKey failed: %v", err) } if key1 == key3 { t.Error("cache key should change when file is modified") } } func TestGetCacheDir(t *testing.T) { dir, err := GetCacheDir() if err != nil { t.Fatalf("GetCacheDir failed: %v", err) } if dir == "" { t.Error("cache dir should not be empty") } // Verify directory exists info, err := os.Stat(dir) if err != nil { t.Fatalf("cache directory does not exist: %v", err) } if !info.IsDir() { t.Error("cache path is not a directory") } } func TestIndexSaveLoad(t *testing.T) { // Create a temporary file for testing tmpFile, err := os.CreateTemp("", "test-video-*.mp4") if err != nil { t.Fatal(err) } defer os.Remove(tmpFile.Name()) tmpFile.Close() // Create a test index idx := &Index{ Keyframes: []Keyframe{ {FrameNum: 0, Timestamp: 0.0}, {FrameNum: 60, Timestamp: 2.0}, {FrameNum: 120, Timestamp: 4.0}, {FrameNum: 180, Timestamp: 6.0}, }, TotalFrames: 300, Duration: 10.0, FrameRate: 30.0, VideoPath: tmpFile.Name(), CreatedAt: time.Now(), } // Save to cache if err := idx.SaveToCache(); err != nil { t.Fatalf("SaveToCache failed: %v", err) } // Load from cache loaded, err := LoadFromCache(tmpFile.Name()) if err != nil { t.Fatalf("LoadFromCache failed: %v", err) } // Verify data if len(loaded.Keyframes) != len(idx.Keyframes) { t.Errorf("keyframe count mismatch: %d != %d", len(loaded.Keyframes), len(idx.Keyframes)) } if loaded.Duration != idx.Duration { t.Errorf("duration mismatch: %f != %f", loaded.Duration, idx.Duration) } if loaded.FrameRate != idx.FrameRate { t.Errorf("framerate mismatch: %f != %f", loaded.FrameRate, idx.FrameRate) } for i, kf := range loaded.Keyframes { if kf.FrameNum != idx.Keyframes[i].FrameNum { t.Errorf("keyframe %d frame num mismatch: %d != %d", i, kf.FrameNum, idx.Keyframes[i].FrameNum) } if kf.Timestamp != idx.Keyframes[i].Timestamp { t.Errorf("keyframe %d timestamp mismatch: %f != %f", i, kf.Timestamp, idx.Keyframes[i].Timestamp) } } } func TestFindNearestKeyframe(t *testing.T) { idx := &Index{ Keyframes: []Keyframe{ {FrameNum: 0, Timestamp: 0.0}, {FrameNum: 60, Timestamp: 2.0}, {FrameNum: 120, Timestamp: 4.0}, {FrameNum: 180, Timestamp: 6.0}, {FrameNum: 240, Timestamp: 8.0}, }, FrameRate: 30.0, } tests := []struct { timestamp float64 direction string expected float64 }{ {1.0, "before", 0.0}, {1.0, "after", 2.0}, {1.0, "nearest", 0.0}, // Closer to 0.0 than 2.0 {3.0, "before", 2.0}, {3.0, "after", 4.0}, {3.0, "nearest", 2.0}, // Equidistant, picks before (closer by <=) {5.0, "nearest", 4.0}, // Exactly between 4.0 and 6.0, should pick 4.0 {7.0, "before", 6.0}, {7.0, "after", 8.0}, {100.0, "before", 8.0}, // Beyond end {100.0, "after", 8.0}, // Beyond end } for _, tt := range tests { kf := idx.FindNearestKeyframe(tt.timestamp, tt.direction) if kf == nil { t.Errorf("FindNearestKeyframe(%f, %s) returned nil", tt.timestamp, tt.direction) continue } if kf.Timestamp != tt.expected { t.Errorf("FindNearestKeyframe(%f, %s) = %f, want %f", tt.timestamp, tt.direction, kf.Timestamp, tt.expected) } } } func TestEstimateFrameNumber(t *testing.T) { idx := &Index{ FrameRate: 30.0, } tests := []struct { timestamp float64 expected int }{ {0.0, 0}, {1.0, 30}, {2.0, 60}, {0.5, 15}, {1.5, 45}, } for _, tt := range tests { result := idx.EstimateFrameNumber(tt.timestamp) if result != tt.expected { t.Errorf("EstimateFrameNumber(%f) = %d, want %d", tt.timestamp, result, tt.expected) } } } func TestParseFrameRate(t *testing.T) { tests := []struct { input string expected float64 }{ {"30/1", 30.0}, {"30000/1001", 29.97002997002997}, {"25/1", 25.0}, {"60/1", 60.0}, {"invalid", 0}, {"", 0}, } for _, tt := range tests { result := parseFrameRate(tt.input) if tt.expected == 0 { if result != 0 { t.Errorf("parseFrameRate(%q) = %f, want 0", tt.input, result) } } else { diff := result - tt.expected if diff < -0.0001 || diff > 0.0001 { t.Errorf("parseFrameRate(%q) = %f, want %f", tt.input, result, tt.expected) } } } } func BenchmarkFindNearestKeyframe(b *testing.B) { // Create index with 1000 keyframes (typical for 1-hour video @ 2s GOP) keyframes := make([]Keyframe, 1000) for i := range keyframes { keyframes[i] = Keyframe{ FrameNum: i * 60, Timestamp: float64(i) * 2.0, } } idx := &Index{ Keyframes: keyframes, FrameRate: 30.0, } b.ResetTimer() for i := 0; i < b.N; i++ { // Search for random timestamp ts := float64(i%2000) + 0.5 idx.FindNearestKeyframe(ts, "nearest") } }