package app import ( "context" "errors" "testing" "time" "homekey.local/provisioner/drivers/sesame" "homekey.local/provisioner/internal/physical" ) // A failed preparation owns no Link. Retiring it must not short-circuit the // actual BlueZ disconnect, which was the reason repeated clicks were needed. func TestDisconnectFailedPreparationReleasesBlueZLink(t *testing.T) { m, err := physical.Open(context.Background(), t.TempDir(), func(context.Context, sesame.Target) (sesame.Link, sesame.Target, error) { return nil, sesame.Target{}, errors.New("Sesame is already connected through this adapter") }) if err != nil { t.Fatal(err) } defer m.Close() target := sesame.Target{UUID: "00112233-4455-6677-8899-aabbccddeeff", Model: "sesame_6_pro", Adapter: "hci0", Address: "AA:BB:CC:DD:EE:FF"} m.Prepare([]sesame.Target{target}) deadline := time.Now().Add(time.Second) for time.Now().Before(deadline) { states := m.RegistrationConnections() if len(states) == 1 && states[0].Phase == "failed" { break } time.Sleep(time.Millisecond) } if states := m.RegistrationConnections(); len(states) != 1 || states[0].Phase != "failed" { t.Fatal("preparation did not fail", states) } connected := true calls := 0 release := func(ctx context.Context, adapter, address string) error { if adapter != target.Adapter || address != target.Address { t.Fatal("released wrong lock", adapter, address) } if len(m.RegistrationConnections()) != 0 { t.Fatal("released before retiring preparation") } calls++ connected = false return nil } if err = disconnectSesame(context.Background(), m, target.Adapter, target.Address, release); err != nil { t.Fatal(err) } if connected || calls != 1 { t.Fatal("failed preparation hid the live BlueZ link", connected, calls) } transportError := errors.New("disconnect failed") if err = disconnectSesame(context.Background(), m, target.Adapter, target.Address, func(context.Context, string, string) error { return transportError }); !errors.Is(err, transportError) { t.Fatal("hid transport disconnect failure", err) } }