1package factory
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "testing"
8
9 "github.com/bnema/dumber/internal/application/port"
10 "github.com/bnema/dumber/internal/infrastructure/audio/null"
11)
12
13// failingFactory is a factory that always returns an error
14type failingFactory struct {
15 err error
16}
17
18func (f *failingFactory) NewStream(_ context.Context, _ port.AudioStreamFormat) (port.AudioOutputStream, error) {
19 return nil, f.err
20}
21
22func TestSelector_PrimaryFails_FallsBackToFallback(t *testing.T) {
23 // Arrange
24 primaryErr := errors.New("primary backend unavailable")
25 primary := &failingFactory{err: primaryErr}
26 fallback := &null.Factory{}
27
28 selector := &Selector{
29 Primary: primary,
30 Fallback: fallback,
31 }
32
33 format := port.AudioStreamFormat{
34 SampleRate: 48000,
35 ChannelCount: 2,
36 FramesPerBuffer: 512,
37 }
38 ctx := context.Background()
39
40 // Act
41 stream, err := selector.NewStream(ctx, format)
42
43 // Assert
44 if err != nil {
45 t.Fatalf("Expected no error when fallback succeeds, got: %v", err)
46 }
47 if stream == nil {
48 t.Fatal("Expected non-nil stream from fallback")
49 }
50
51 // Verify the stream works (it's the null stream)
52 samples := make([][]float32, format.ChannelCount)
53 for i := range samples {
54 samples[i] = make([]float32, format.FramesPerBuffer)
55 }
56
57 if err := stream.Write(samples); err != nil {
58 t.Errorf("Write failed: %v", err)
59 }
60 if err := stream.Close(); err != nil {
61 t.Errorf("Close failed: %v", err)
62 }
63}
64
65func TestSelector_PrimarySucceeds_UsesPrimary(t *testing.T) {
66 // Arrange
67 primary := &null.Factory{} // Using null as primary for simplicity
68 fallback := &failingFactory{err: errors.New("fallback should not be called")}
69
70 selector := &Selector{
71 Primary: primary,
72 Fallback: fallback,
73 }
74
75 format := port.AudioStreamFormat{
76 SampleRate: 48000,
77 ChannelCount: 2,
78 FramesPerBuffer: 512,
79 }
80 ctx := context.Background()
81
82 // Act
83 stream, err := selector.NewStream(ctx, format)
84
85 // Assert
86 if err != nil {
87 t.Fatalf("Expected no error, got: %v", err)
88 }
89 if stream == nil {
90 t.Fatal("Expected non-nil stream from primary")
91 }
92
93 // Verify the stream works
94 samples := make([][]float32, format.ChannelCount)
95 for i := range samples {
96 samples[i] = make([]float32, format.FramesPerBuffer)
97 }
98
99 if err := stream.Write(samples); err != nil {
100 t.Errorf("Write failed: %v", err)
101 }
102 if err := stream.Close(); err != nil {
103 t.Errorf("Close failed: %v", err)
104 }
105}
106
107func TestSelector_BothFail_ReturnsError(t *testing.T) {
108 // Arrange
109 primaryErr := errors.New("primary backend unavailable")
110 fallbackErr := errors.New("fallback backend unavailable")
111
112 selector := &Selector{
113 Primary: &failingFactory{err: primaryErr},
114 Fallback: &failingFactory{err: fallbackErr},
115 }
116
117 format := port.AudioStreamFormat{
118 SampleRate: 48000,
119 ChannelCount: 2,
120 FramesPerBuffer: 512,
121 }
122 ctx := context.Background()
123
124 // Act
125 stream, err := selector.NewStream(ctx, format)
126
127 // Assert
128 if err == nil {
129 t.Fatal("Expected error when both backends fail")
130 }
131 if stream != nil {
132 t.Error("Expected nil stream when both backends fail")
133 }
134}
135
136// TestNewAudioOutputFactory_ReturnsWorkingFactory verifies that
137// NewAudioOutputFactory wires PipeWire as primary and null as fallback without
138// requiring a real PipeWire session.
139func TestNewAudioOutputFactory_ReturnsWorkingFactory(t *testing.T) {
140 factory := NewAudioOutputFactory()
141 if factory == nil {
142 t.Fatal("Expected non-nil factory")
143 }
144
145 selector, ok := factory.(*Selector)
146 if !ok {
147 t.Fatalf("Expected *Selector, got %T", factory)
148 }
149 if selector.Primary == nil {
150 t.Fatal("Expected non-nil primary backend")
151 }
152 if selector.Fallback == nil {
153 t.Fatal("Expected non-nil fallback backend")
154 }
155 if got := fmt.Sprintf("%T", selector.Primary); got != "*pipewire.Factory" {
156 t.Fatalf("Expected PipeWire primary backend, got %s", got)
157 }
158 if got := fmt.Sprintf("%T", selector.Fallback); got != "*null.Factory" {
159 t.Fatalf("Expected null fallback backend, got %s", got)
160 }
161}