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 }