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

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