package icecast import ( "bytes" "context" "errors" "io" "testing" "github.com/jan/fm-rds-tx/internal/ingest" "github.com/jan/fm-rds-tx/internal/ingest/decoder" ) type testDecoder struct { name string err error called int } func (d *testDecoder) Name() string { return d.name } func (d *testDecoder) DecodeStream(_ context.Context, _ io.Reader, _ decoder.StreamMeta, _ func(ingest.PCMChunk) error) error { d.called++ return d.err } func TestDecodeWithPreferenceAutoFallsBackFromNativeUnsupported(t *testing.T) { native := &testDecoder{name: "native", err: decoder.ErrUnsupported} fallback := &testDecoder{name: "ffmpeg"} reg := decoder.NewRegistry() reg.Register("mp3", func() decoder.Decoder { return native }) reg.Register("ffmpeg", func() decoder.Decoder { return fallback }) src := New("ice-test", "http://example", nil, ReconnectConfig{}, WithDecoderRegistry(reg), WithDecoderPreference("auto"), ) err := src.decodeWithPreference(context.Background(), bytes.NewReader(nil), decoder.StreamMeta{ ContentType: "audio/mpeg", SourceID: "ice-test", }) if err != nil { t.Fatalf("decode: %v", err) } if native.called != 1 { t.Fatalf("native called %d times", native.called) } if fallback.called != 1 { t.Fatalf("fallback called %d times", fallback.called) } } func TestDecodeWithPreferenceNativeDoesNotFallback(t *testing.T) { nativeErr := errors.New("decode failed") native := &testDecoder{name: "native", err: nativeErr} fallback := &testDecoder{name: "ffmpeg"} reg := decoder.NewRegistry() reg.Register("mp3", func() decoder.Decoder { return native }) reg.Register("ffmpeg", func() decoder.Decoder { return fallback }) src := New("ice-test", "http://example", nil, ReconnectConfig{}, WithDecoderRegistry(reg), WithDecoderPreference("native"), ) err := src.decodeWithPreference(context.Background(), bytes.NewReader(nil), decoder.StreamMeta{ ContentType: "audio/mpeg", SourceID: "ice-test", }) if !errors.Is(err, nativeErr) { t.Fatalf("expected native error, got %v", err) } if fallback.called != 0 { t.Fatalf("fallback should not be called, got %d", fallback.called) } } func TestDecodeWithPreferenceFFmpegOnly(t *testing.T) { native := &testDecoder{name: "native"} fallback := &testDecoder{name: "ffmpeg"} reg := decoder.NewRegistry() reg.Register("mp3", func() decoder.Decoder { return native }) reg.Register("ffmpeg", func() decoder.Decoder { return fallback }) src := New("ice-test", "http://example", nil, ReconnectConfig{}, WithDecoderRegistry(reg), WithDecoderPreference("ffmpeg"), ) err := src.decodeWithPreference(context.Background(), bytes.NewReader(nil), decoder.StreamMeta{ ContentType: "audio/mpeg", SourceID: "ice-test", }) if err != nil { t.Fatalf("decode: %v", err) } if native.called != 0 { t.Fatalf("native should not be called in ffmpeg mode, got %d", native.called) } if fallback.called != 1 { t.Fatalf("fallback called %d times", fallback.called) } } func TestDecodeWithPreferenceAutoUnsupportedContentTypeFallsBack(t *testing.T) { fallback := &testDecoder{name: "ffmpeg"} reg := decoder.NewRegistry() reg.Register("ffmpeg", func() decoder.Decoder { return fallback }) src := New("ice-test", "http://example", nil, ReconnectConfig{}, WithDecoderRegistry(reg), WithDecoderPreference("auto"), ) err := src.decodeWithPreference(context.Background(), bytes.NewReader(nil), decoder.StreamMeta{ ContentType: "application/octet-stream", SourceID: "ice-test", }) if err != nil { t.Fatalf("decode: %v", err) } if fallback.called != 1 { t.Fatalf("fallback called %d times", fallback.called) } } func TestWithDecoderPreferenceFallbackAliasNormalizesToFFmpeg(t *testing.T) { src := New("ice-test", "http://example", nil, ReconnectConfig{}, WithDecoderPreference("fallback")) if got := src.Descriptor().Codec; got != "ffmpeg" { t.Fatalf("codec=%s want ffmpeg", got) } }