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 }