164 lines
6.3 KiB
Go
164 lines
6.3 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestTheManifestIsTheStackItsToolsAndTheDaemon(t *testing.T) {
|
|
m := readManifest(t)
|
|
holdsTheBundle(t, m, "bluetooth")
|
|
if got := strings.Join(m.packages(), ","); got != "bluez,bluez-utils" {
|
|
t.Errorf("packages %s: the applet and the TUI are the operator's", got)
|
|
}
|
|
s := m.services()["bluetooth.service"]
|
|
if s == nil || s["state"] != "running" || s["boot"] != "enabled" {
|
|
t.Errorf("%v", s)
|
|
}
|
|
if len(m.Resources) != 3 {
|
|
t.Errorf("no configuration file: /etc/bluetooth/main.conf is the package's, unchanged on both workstations: %v", m.Resources)
|
|
}
|
|
}
|
|
|
|
const show = "Controller 4C:82:A9:97:01:8E (public)\n\tName: node-b\n\tAlias: node-b\n\tPowered: yes\n\tPowerState: on\n\tDiscoverable: no\n\tPairable: yes\n\tUUID: Headset (00001108-0000-1000-8000-00805f9b34fb)\n\tDiscovering: no\n"
|
|
|
|
func headphones(connected bool) string {
|
|
c := "no"
|
|
extra := ""
|
|
if connected {
|
|
c, extra = "yes", "\tBattery Percentage: 0x50 (80)\n"
|
|
}
|
|
return "Device 80:99:E7:C2:29:DA (public)\n\tName: WH-1000XM4\n\tAlias: WH-1000XM4\n\tIcon: audio-headset\n\tPaired: yes\n\tBonded: yes\n\tTrusted: yes\n\tBlocked: no\n\tConnected: " + c + "\n" + extra + "\tUUID: Headset (00001108-0000-1000-8000-00805f9b34fb)\n"
|
|
}
|
|
|
|
func TestControllerReadsShow(t *testing.T) {
|
|
using(t, func(string, Cmd) Result { return ok("\x1b[0;94m" + show) })
|
|
c, err := Controller()
|
|
if err != nil || c.Address != "4C:82:A9:97:01:8E" || c.Name != "node-b" || !c.Powered || c.Discoverable || !c.Pairable {
|
|
t.Fatalf("%+v %v", c, err)
|
|
}
|
|
using(t, func(string, Cmd) Result { return ok("No default controller available\n") })
|
|
if _, err := Controller(); err == nil || !strings.Contains(err.Error(), "no Bluetooth controller") {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
}
|
|
|
|
func TestDevicesAskEachOneAndReportBatteryWhereGiven(t *testing.T) {
|
|
f := using(t, func(line string, c Cmd) Result {
|
|
switch line {
|
|
case "bluetoothctl devices Paired":
|
|
return ok("Device 80:99:E7:C2:29:DA WH-1000XM4\nDevice 2C:41:A1:E4:EC:86 Earmuffs\n")
|
|
case "bluetoothctl info 80:99:E7:C2:29:DA":
|
|
return ok(headphones(true))
|
|
}
|
|
return ok("Device 2C:41:A1:E4:EC:86 (public)\n\tName: Earmuffs\n\tPaired: yes\n\tConnected: no\n")
|
|
})
|
|
got, err := Devices("paired")
|
|
devices := got["devices"].([]Device)
|
|
if err != nil || got["count"] != 2 || !devices[0].Connected || devices[0].Battery == nil || *devices[0].Battery != 80 || devices[1].Battery != nil {
|
|
t.Fatalf("%+v %v", got, err)
|
|
}
|
|
if f.lines()[0] != "bluetoothctl devices Paired" {
|
|
t.Errorf("%v", f.lines())
|
|
}
|
|
if _, err := Devices("nearby"); err == nil {
|
|
t.Error("an unknown which")
|
|
}
|
|
}
|
|
|
|
func TestAnActThatFailsIsAnErrorWhateverTheExitStatus(t *testing.T) {
|
|
using(t, func(line string, c Cmd) Result {
|
|
return ok("Attempting to connect to 80:99:E7:C2:29:DA\nFailed to connect: org.bluez.Error.Failed br-connection-page-timeout\n")
|
|
})
|
|
if _, err := Act("connect", "80:99:E7:C2:29:DA"); err == nil || !strings.Contains(err.Error(), "page-timeout") {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
using(t, func(string, Cmd) Result { return Result{Status: 1, Stdout: "Device 00:11:22:33:44:55 not available\n"} })
|
|
if _, err := Act("trust", "00:11:22:33:44:55"); err == nil || !strings.Contains(err.Error(), "not available") {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
}
|
|
|
|
func TestConnectWaitsWithBluetoothctlsOwnTimeoutAndAnswersTheState(t *testing.T) {
|
|
f := using(t, func(line string, c Cmd) Result {
|
|
if strings.Contains(line, "connect") {
|
|
return ok("Attempting to connect\n[CHG] Device Connected: yes\nConnection successful\n")
|
|
}
|
|
return ok(headphones(true))
|
|
})
|
|
got, err := Act("connect", "80:99:E7:C2:29:DA")
|
|
if err != nil || !got["device"].(Device).Connected {
|
|
t.Fatalf("%v %v", got, err)
|
|
}
|
|
if f.lines()[0] != "bluetoothctl --timeout 15 connect 80:99:E7:C2:29:DA" || f.asked[0].Timeout != CallTimeout {
|
|
t.Errorf("%v", f.lines())
|
|
}
|
|
}
|
|
|
|
func TestAnActBluezRefusesTheAccountIsRetriedThroughSudo(t *testing.T) {
|
|
f := using(t, func(line string, c Cmd) Result {
|
|
if strings.HasPrefix(line, "sudo") {
|
|
return ok("Changing power off succeeded\n" + show)
|
|
}
|
|
return ok("Failed to set power off: org.freedesktop.DBus.Error.AccessDenied\n")
|
|
})
|
|
if _, err := Power(false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if l := f.lines(); l[0] != "bluetoothctl power off" || l[1] != "sudo -n bluetoothctl power off" {
|
|
t.Errorf("%v", l)
|
|
}
|
|
}
|
|
|
|
func TestScanIsBoundedAndAnswersUnpairedDevicesStrongestFirst(t *testing.T) {
|
|
f := using(t, func(line string, c Cmd) Result {
|
|
switch {
|
|
case strings.Contains(line, "scan on"):
|
|
return ok("Discovery started\n[NEW] Device AA:BB:CC:DD:EE:01 Speaker\n")
|
|
case line == "bluetoothctl devices":
|
|
return ok("Device 80:99:E7:C2:29:DA WH-1000XM4\nDevice AA:BB:CC:DD:EE:01 Speaker\nDevice AA:BB:CC:DD:EE:02 Phone\nDevice AA:BB:CC:DD:EE:03 Gone\n")
|
|
case strings.HasSuffix(line, "EE:01"):
|
|
return ok("Device AA:BB:CC:DD:EE:01\n\tName: Speaker\n\tPaired: no\n\tRSSI: 0xffffffc4 (-60)\n")
|
|
case strings.HasSuffix(line, "EE:02"):
|
|
return ok("Device AA:BB:CC:DD:EE:02\n\tName: Phone\n\tPaired: no\n\tRSSI: -40\n")
|
|
case strings.HasSuffix(line, "EE:03"):
|
|
return Result{Status: 1, Stdout: "Device AA:BB:CC:DD:EE:03 not available\n"}
|
|
}
|
|
return ok(headphones(false))
|
|
})
|
|
got, err := Scan(8)
|
|
found := got["found"].([]Device)
|
|
if err != nil || len(found) != 2 || found[0].Name != "Phone" || *found[1].RSSI != -60 {
|
|
t.Fatalf("%+v %v", got, err)
|
|
}
|
|
if f.lines()[0] != "bluetoothctl --timeout 8 scan on" || f.asked[0].Timeout.Seconds() != 12 {
|
|
t.Errorf("%v %v", f.lines()[0], f.asked[0].Timeout)
|
|
}
|
|
}
|
|
|
|
func TestPairUsesAnAgentThatConfirmsNothingAndThenTrusts(t *testing.T) {
|
|
f := using(t, func(line string, c Cmd) Result {
|
|
if strings.Contains(line, " pair ") {
|
|
return ok("Pairing successful\n")
|
|
}
|
|
return ok(headphones(false))
|
|
})
|
|
if _, err := Pair("80:99:e7:c2:29:da"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if l := f.lines(); l[0] != "bluetoothctl --agent NoInputNoOutput --timeout 15 pair 80:99:e7:c2:29:da" || l[1] != "bluetoothctl trust 80:99:e7:c2:29:da" {
|
|
t.Errorf("%v", l)
|
|
}
|
|
}
|
|
|
|
func TestAnAddressIsSixHexPairs(t *testing.T) {
|
|
for _, bad := range []string{"", "80:99:E7:C2:29", "80:99:E7:C2:29:DA; rm", "--help", "GG:99:E7:C2:29:DA"} {
|
|
if _, err := addressOf(map[string]any{"address": bad}); err == nil {
|
|
t.Errorf("%q accepted", bad)
|
|
}
|
|
}
|
|
if a, err := addressOf(map[string]any{"address": "80:99:e7:c2:29:da"}); err != nil || a != "80:99:E7:C2:29:DA" {
|
|
t.Errorf("%s %v", a, err)
|
|
}
|
|
}
|