From cf30414c2a0696cc75c615f4da66ea85d0522c42 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Wed, 29 Apr 2026 19:43:05 +0300 Subject: feat: implement filesystem scanner --- internal/probe/probe_test.go | 126 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 internal/probe/probe_test.go (limited to 'internal/probe/probe_test.go') diff --git a/internal/probe/probe_test.go b/internal/probe/probe_test.go new file mode 100644 index 0000000..9c05846 --- /dev/null +++ b/internal/probe/probe_test.go @@ -0,0 +1,126 @@ +package probe + +import ( + "context" + "testing" + "time" + + "github.com/paul/kiss-media-player/internal/model" +) + +func TestParseFFprobeOutput(t *testing.T) { + cases := []struct { + name string + input string + want *model.Metadata + wantErr bool + }{ + { + name: "video with all fields", + input: `{ + "format": {"duration": "123.45", "bit_rate": "500000"}, + "streams": [ + {"codec_name": "h264", "width": 1920, "height": 1080, "codec_type": "video"}, + {"codec_name": "aac", "codec_type": "audio"} + ] + }`, + want: &model.Metadata{ + Duration: 123.45, + Codec: "h264", + Resolution: "1920x1080", + Bitrate: 500000, + }, + }, + { + name: "audio only no video stream", + input: `{ + "format": {"duration": "200.1", "bit_rate": "128000"}, + "streams": [ + {"codec_name": "mp3", "codec_type": "audio"} + ] + }`, + want: &model.Metadata{ + Duration: 200.1, + Codec: "mp3", + Bitrate: 128000, + }, + }, + { + name: "invalid json", + input: `{bad json`, + wantErr: true, + }, + { + name: "empty streams uses first stream fallback", + input: `{ + "format": {}, + "streams": [ + {"codec_name": "vp9", "codec_type": "video", "width": 0, "height": 0} + ] + }`, + want: &model.Metadata{Codec: "vp9"}, + }, + { + name: "no format or streams", + input: `{"format":{},"streams":[]}`, + want: &model.Metadata{}, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got, err := parseFFprobeOutput([]byte(c.input)) + if (err != nil) != c.wantErr { + t.Fatalf("unexpected error: %v", err) + } + if c.wantErr { + return + } + if got.Duration != c.want.Duration { + t.Errorf("Duration = %v, want %v", got.Duration, c.want.Duration) + } + if got.Codec != c.want.Codec { + t.Errorf("Codec = %v, want %v", got.Codec, c.want.Codec) + } + if got.Resolution != c.want.Resolution { + t.Errorf("Resolution = %v, want %v", got.Resolution, c.want.Resolution) + } + if got.Bitrate != c.want.Bitrate { + t.Errorf("Bitrate = %v, want %v", got.Bitrate, c.want.Bitrate) + } + }) + } +} + +func TestMockProber(t *testing.T) { + ctx := context.Background() + m := &MockProber{} + meta, err := m.Probe(ctx, "any") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if meta == nil { + t.Fatal("expected non-nil metadata") + } + + m.ProbeFunc = func(context.Context, string) (*model.Metadata, error) { + return &model.Metadata{Duration: 42}, nil + } + meta, err = m.Probe(ctx, "any") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if meta.Duration != 42 { + t.Errorf("duration = %v, want 42", meta.Duration) + } +} + +func TestFFProber_ContextCancellation(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) + defer cancel() + p := NewFFProber() + _, err := p.Probe(ctx, "nonexistent_path_should_fail") + if err == nil { + t.Fatal("expected error when context is cancelled or ffprobe fails") + } +} -- cgit v1.2.3