github.com/vmware/govmomi@v0.43.0/vim25/soap/json_client_test.go (about)

     1  /*
     2  Copyright (c) 2023-2023 VMware, Inc. All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package soap
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  	"net/url"
    25  	"reflect"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  
    31  	"github.com/vmware/govmomi/vim25/types"
    32  )
    33  
    34  type AcquireMksTicketBody struct {
    35  	Req    *types.AcquireMksTicket         `xml:"urn:vim25 AcquireMksTicket,omitempty"`
    36  	Res    *types.AcquireMksTicketResponse `xml:"AcquireMksTicketResponse,omitempty"`
    37  	Fault_ *Fault                          `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"`
    38  }
    39  
    40  func (b *AcquireMksTicketBody) Fault() *Fault { return b.Fault_ }
    41  
    42  func TestUnpack(t *testing.T) {
    43  	req := &types.AcquireMksTicket{
    44  		This: types.ManagedObjectReference{
    45  			Type:  "Folder",
    46  			Value: "group-d1",
    47  		},
    48  	}
    49  	var reqBody HasFault = &AcquireMksTicketBody{
    50  		Req: req,
    51  	}
    52  	this, method, params, err := unpackSOAPRequest(reqBody)
    53  	if err != nil {
    54  		t.Fatalf("Cannot unpack request %v", err)
    55  	}
    56  	if method != "AcquireMksTicket" {
    57  		t.Fatalf("Expected 'AcquireMksTicket' methods but got: %v", method)
    58  	}
    59  	if this.Type != "Folder" {
    60  		t.Fatalf("Expected 'Folder' type in this but got: %v", this.Type)
    61  	}
    62  	if this.Value != "group-d1" {
    63  		t.Fatalf("Expected 'Folder' type in this but got: %v", this.Type)
    64  	}
    65  	if params != req {
    66  		t.Fatal("Expected pointer to the req struct")
    67  	}
    68  }
    69  
    70  func TestGetResultPtr(t *testing.T) {
    71  	var respBody = &AcquireMksTicketBody{
    72  		Res: &types.AcquireMksTicketResponse{
    73  			Returnval: types.VirtualMachineMksTicket{},
    74  		},
    75  	}
    76  	resPtr2, err := getSOAPResultPtr(respBody)
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  	if &respBody.Res.Returnval != resPtr2 {
    81  		t.Fatal("Expected the original pointer to the result value but got different one.")
    82  	}
    83  }
    84  
    85  func TestErrorUnmarshal(t *testing.T) {
    86  	headers := http.Header{}
    87  	headers.Set("content-type", "application/json")
    88  	body := io.NopCloser(strings.NewReader(`{
    89  		"_typeName": "InvalidLogin",
    90  		"faultstring": "Cannot complete login due to an incorrect user name or password."
    91  	}`))
    92  	resp := &http.Response{
    93  		StatusCode: 500,
    94  		Header:     headers,
    95  		Body:       body,
    96  	}
    97  
    98  	addr, _ := url.Parse("https://localhost/test")
    99  	c := NewClient(addr, true)
   100  	c.Namespace = "urn:vim25"
   101  
   102  	var result interface{}
   103  	unmarshaler := c.responseUnmarshaler(&result)
   104  
   105  	err := unmarshaler(resp)
   106  	if err == nil {
   107  		t.Error("Expected non nil error")
   108  	}
   109  	if !strings.HasPrefix(err.Error(), "ServerFaultCode: Cannot complete login") {
   110  		t.Error("Unexpected error:", err)
   111  	}
   112  	if !IsSoapFault(err) {
   113  		t.Error("Expected SOAP fault.")
   114  	}
   115  	fault := ToSoapFault(err)
   116  
   117  	if fault.String != "Cannot complete login due to an incorrect user name or password." {
   118  		t.Error("Unexpected faultstring message. Got:", fault.String)
   119  	}
   120  	if fault.Code != "ServerFaultCode" {
   121  		t.Error("Unexpected faultcode. Got:", fault.Code)
   122  
   123  	}
   124  	// This may be incorrect. It is more of a speculation
   125  	if fault.XMLName.Space != "urn:vim25" {
   126  		t.Error("Expected vim25 namespace obtained from the client. Got:",
   127  			fault.XMLName.Space)
   128  	}
   129  	if fault.XMLName.Local != "InvalidLoginFault" {
   130  		t.Error("ExpectedInvalidLoginFault type. Got:", fault.XMLName.Local)
   131  	}
   132  	if _, ok := fault.Detail.Fault.(types.InvalidLogin); !ok {
   133  		t.Error("Expected InvalidLogin nested fault type. Got:",
   134  			reflect.TypeOf(fault.Detail.Fault).Name())
   135  	}
   136  
   137  }
   138  
   139  func TestSuccessUnmarshal(t *testing.T) {
   140  	headers := http.Header{}
   141  	headers.Set("content-type", "application/json")
   142  	body := io.NopCloser(strings.NewReader(`{
   143  		"_typeName": "UserSession",
   144  		"key": "527025b6-f0f4-144e-0d36-a7aaf2eb21da",
   145  		"userName": "VSPHERE.LOCAL\\Administrator",
   146  		"fullName": "Administrator vsphere.local",
   147  		"loginTime": "2023-02-14T22:12:48.753169Z",
   148  		"lastActiveTime": "2023-02-14T22:12:48.753169Z",
   149  		"locale": "en",
   150  		"messageLocale": "en",
   151  		"extensionSession": false,
   152  		"ipAddress": "10.93.153.94",
   153  		"userAgent": "curl/7.86.0",
   154  		"callCount": 0
   155  	}`))
   156  	resp := &http.Response{
   157  		StatusCode:    200,
   158  		Header:        headers,
   159  		Body:          body,
   160  		ContentLength: 100, // Fake length to avoid check for empty body
   161  	}
   162  
   163  	addr, _ := url.Parse("https://localhost/test")
   164  	c := NewClient(addr, true)
   165  	c.Namespace = "urn:vim25"
   166  
   167  	var result interface{}
   168  	unmarshaler := c.responseUnmarshaler(&result)
   169  
   170  	err := unmarshaler(resp)
   171  	if err != nil {
   172  		t.Error("Expected nil error")
   173  	}
   174  	var us types.UserSession
   175  	var ok bool
   176  	if us, ok = result.(types.UserSession); !ok {
   177  		t.Error("Expected user session")
   178  	}
   179  	if us.UserName != "VSPHERE.LOCAL\\Administrator" {
   180  		t.Error("Unexpected user name", us.UserName)
   181  	}
   182  }
   183  
   184  type mockHTTP struct {
   185  	impl func(*http.Request) (*http.Response, error)
   186  }
   187  
   188  func (m *mockHTTP) RoundTrip(req *http.Request) (*http.Response, error) {
   189  	if m.impl == nil {
   190  		return nil, fmt.Errorf("mock HTTP implementation is not set")
   191  	}
   192  	return m.impl(req)
   193  }
   194  
   195  func TestFullRequestCycle(t *testing.T) {
   196  	ctx := context.Background()
   197  	req := AcquireMksTicketBody{
   198  		Req: &types.AcquireMksTicket{
   199  			This: types.ManagedObjectReference{
   200  				Type:  "VirtualMachine",
   201  				Value: "vm-42",
   202  			},
   203  		},
   204  	}
   205  	addr, _ := url.Parse("https://localhost/test")
   206  	c := NewClient(addr, true)
   207  	c.Namespace = "urn:vim25"
   208  	c.Version = "8.0.0.1"
   209  	c.cookie = "(original)"
   210  	c.UseJSON(true)
   211  
   212  	c.Transport = &mockHTTP{
   213  		impl: func(r *http.Request) (*http.Response, error) {
   214  			if r.URL.String() != "https://localhost/test/vim25/8.0.0.1/VirtualMachine/vm-42/AcquireMksTicket" {
   215  				t.Error("Unexpected url value:", r.URL)
   216  			}
   217  
   218  			if r.Method != "POST" {
   219  				t.Error("Unexpected method value:", r.Method)
   220  			}
   221  			if r.Header.Get("vmware-api-session-id") != "(original)" {
   222  				t.Error("Unexpected authentication value:", r.Header.Get("vmware-api-session-id"))
   223  			}
   224  			reqBytes, _ := io.ReadAll(r.Body)
   225  			reqBody := string(reqBytes)
   226  			assert.JSONEq(t, `{"_typeName":"AcquireMksTicket"}`, reqBody)
   227  
   228  			if t.Failed() {
   229  				t.FailNow()
   230  			}
   231  			body := `{
   232  				"_typeName": "VirtualMachineMksTicket",
   233  				"ticket": "ticket_value",
   234  				"cfgFile": "file.ini",
   235  				"host": "localhost",
   236  				"port": 8080
   237  			}`
   238  			headers := http.Header{}
   239  			headers.Set("content-type", "application/json")
   240  
   241  			return &http.Response{
   242  				StatusCode:    200,
   243  				Header:        headers,
   244  				Body:          io.NopCloser(strings.NewReader(body)),
   245  				ContentLength: 100, // Fake length to avoid check for empty body
   246  			}, nil
   247  		},
   248  	}
   249  
   250  	res := AcquireMksTicketBody{}
   251  
   252  	e := c.RoundTrip(ctx, &req, &res)
   253  
   254  	if e != nil {
   255  		t.Fatal(e)
   256  	}
   257  	if res.Fault_ != nil {
   258  		t.Fatal(res.Fault_)
   259  	}
   260  	if res.Res.Returnval.Ticket != "ticket_value" || res.Res.Returnval.Port != 8080 {
   261  		t.Fatalf("Return value not unmarshaled correctly '%#v'", res.Res.Returnval)
   262  	}
   263  }