package capture import ( "encoding/json" "testing" ) // TestPWParseAudioNodes parses a representative pw-dump payload into the // node shape used by ListAudioSources. func TestPWParseAudioNodes(t *testing.T) { payload := `[ {"id": 51, "info": {"props": {"media.class": "Audio/Sink", "node.name": "alsa_out_speaker", "node.description": "Speaker", "object.serial": 1179}}}, {"id": 56, "info": {"props": {"media.class": "Audio/Source", "node.name": "alsa_in_mic", "node.description": "Stereo Mic", "object.serial": 1183}}}, {"id": 64, "info": {"props": {"media.class": "Audio/Device", "node.description": "Not capturable", "object.serial": 1172}}} ]` var nodes []pwDumpNode if err := json.Unmarshal([]byte(payload), &nodes); err != nil { t.Fatalf("unmarshal: %v", err) } var devs []AudioDevice for _, n := range nodes { props := n.Info.Props mc, _ := props["media.class"].(string) if !audioNodeClass(mc) { continue } devs = append(devs, AudioDevice{ Serial: toUint64(props["object.serial"]), ID: n.ID, Name: props["node.name"].(string), Desc: props["node.description"].(string), IsOutput: mc == "Audio/Sink", }) } if len(devs) != 2 { t.Fatalf("got %d capturable devices, want 2", len(devs)) } // Sink serial parsed and flagged as output. if devs[0].Serial != 1179 || !devs[0].IsOutput { t.Errorf("sink wrong: %+v", devs[0]) } // Source serial parsed and flagged as input. if devs[1].Serial != 1183 || devs[1].IsOutput { t.Errorf("source wrong: %+v", devs[1]) } } // TestAudioNodeClass verifies which media classes are capturable. func TestAudioNodeClass(t *testing.T) { cases := map[string]bool{ "Audio/Sink": true, "Audio/Source": true, "Audio/Device": false, "Video/Source": false, } for cls, want := range cases { if got := audioNodeClass(cls); got != want { t.Errorf("audioNodeClass(%q) = %v, want %v", cls, got, want) } } }