github.com/sleungcy/cli@v7.1.0+incompatible/api/cloudcontroller/ccv3/relationship_test.go (about) 1 package ccv3_test 2 3 import ( 4 "encoding/json" 5 "net/http" 6 7 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 8 . "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3" 9 "code.cloudfoundry.org/cli/resources" 10 . "github.com/onsi/ginkgo" 11 . "github.com/onsi/gomega" 12 . "github.com/onsi/gomega/ghttp" 13 ) 14 15 var _ = Describe("Relationship", func() { 16 var ( 17 client *Client 18 ) 19 20 BeforeEach(func() { 21 client, _ = NewTestClient() 22 }) 23 24 Describe("Relationship", func() { 25 Describe("MarshalJSON", func() { 26 When("the isolation segment is specified by name", func() { 27 It("contains the name in the marshaled JSON", func() { 28 body, err := json.Marshal(resources.Relationship{GUID: "some-iso-guid"}) 29 expectedJSON := `{ 30 "data": { 31 "guid": "some-iso-guid" 32 } 33 }` 34 35 Expect(err).NotTo(HaveOccurred()) 36 Expect(body).To(MatchJSON(expectedJSON)) 37 }) 38 }) 39 40 When("the isolation segment is the empty string", func() { 41 It("contains null in the marshaled JSON", func() { 42 body, err := json.Marshal(resources.Relationship{GUID: ""}) 43 expectedJSON := `{ 44 "data": null 45 }` 46 47 Expect(err).NotTo(HaveOccurred()) 48 Expect(body).To(MatchJSON(expectedJSON)) 49 }) 50 }) 51 }) 52 }) 53 54 Describe("UpdateSpaceIsolationSegmentRelationship", func() { 55 When("the assignment is successful", func() { 56 BeforeEach(func() { 57 response := `{ 58 "data": { 59 "guid": "some-isolation-segment-guid" 60 } 61 }` 62 63 requestBody := map[string]map[string]string{ 64 "data": {"guid": "some-iso-guid"}, 65 } 66 server.AppendHandlers( 67 CombineHandlers( 68 VerifyRequest(http.MethodPatch, "/v3/spaces/some-space-guid/relationships/isolation_segment"), 69 VerifyJSONRepresenting(requestBody), 70 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 71 ), 72 ) 73 }) 74 75 It("returns all relationships and warnings", func() { 76 relationship, warnings, err := client.UpdateSpaceIsolationSegmentRelationship("some-space-guid", "some-iso-guid") 77 Expect(err).NotTo(HaveOccurred()) 78 Expect(warnings).To(ConsistOf("this is a warning")) 79 Expect(relationship).To(Equal(resources.Relationship{ 80 GUID: "some-isolation-segment-guid", 81 })) 82 }) 83 }) 84 }) 85 86 Describe("DeleteServiceInstanceRelationshipsSharedSpace", func() { 87 var ( 88 serviceInstanceGUID string 89 spaceGUID string 90 91 warnings Warnings 92 executeErr error 93 ) 94 95 BeforeEach(func() { 96 serviceInstanceGUID = "some-service-instance-guid" 97 spaceGUID = "some-space-guid" 98 }) 99 100 JustBeforeEach(func() { 101 warnings, executeErr = client.DeleteServiceInstanceRelationshipsSharedSpace(serviceInstanceGUID, spaceGUID) 102 }) 103 104 When("no errors occur deleting the shared space relationship", func() { 105 BeforeEach(func() { 106 server.AppendHandlers( 107 CombineHandlers( 108 VerifyRequest(http.MethodDelete, "/v3/service_instances/some-service-instance-guid/relationships/shared_spaces/some-space-guid"), 109 RespondWith(http.StatusNoContent, "{}", http.Header{"X-Cf-Warnings": {"this is a warning"}}), 110 ), 111 ) 112 }) 113 114 It("does not return any errors and returns all warnings", func() { 115 Expect(executeErr).NotTo(HaveOccurred()) 116 Expect(warnings).To(ConsistOf("this is a warning")) 117 }) 118 }) 119 120 When("an error occurs deleting the shared space relationship", func() { 121 BeforeEach(func() { 122 response := `{ 123 "errors": [ 124 { 125 "code": 10008, 126 "detail": "The request is semantically invalid: command presence", 127 "title": "CF-UnprocessableEntity" 128 }, 129 { 130 "code": 10008, 131 "detail": "The request is semantically invalid: command presence", 132 "title": "CF-UnprocessableEntity" 133 } 134 ] 135 }` 136 server.AppendHandlers( 137 CombineHandlers( 138 VerifyRequest(http.MethodDelete, "/v3/service_instances/some-service-instance-guid/relationships/shared_spaces/some-space-guid"), 139 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 140 ), 141 ) 142 }) 143 144 It("returns the errors and all warnings", func() { 145 Expect(executeErr).To(MatchError(ccerror.MultiError{ 146 ResponseCode: http.StatusTeapot, 147 Errors: []ccerror.V3Error{ 148 { 149 Code: 10008, 150 Detail: "The request is semantically invalid: command presence", 151 Title: "CF-UnprocessableEntity", 152 }, 153 { 154 Code: 10008, 155 Detail: "The request is semantically invalid: command presence", 156 Title: "CF-UnprocessableEntity", 157 }, 158 }, 159 })) 160 Expect(warnings).To(ConsistOf("this is a warning")) 161 }) 162 }) 163 }) 164 165 Describe("GetOrganizationDefaultIsolationSegment", func() { 166 When("getting the isolation segment is successful", func() { 167 BeforeEach(func() { 168 response := `{ 169 "data": { 170 "guid": "some-isolation-segment-guid" 171 } 172 }` 173 174 server.AppendHandlers( 175 CombineHandlers( 176 VerifyRequest(http.MethodGet, "/v3/organizations/some-org-guid/relationships/default_isolation_segment"), 177 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 178 ), 179 ) 180 }) 181 182 It("returns the relationship and warnings", func() { 183 relationship, warnings, err := client.GetOrganizationDefaultIsolationSegment("some-org-guid") 184 Expect(err).NotTo(HaveOccurred()) 185 Expect(warnings).To(ConsistOf("this is a warning")) 186 Expect(relationship).To(Equal(resources.Relationship{ 187 GUID: "some-isolation-segment-guid", 188 })) 189 }) 190 }) 191 192 When("getting the isolation segment fails with an error", func() { 193 BeforeEach(func() { 194 response := `{ 195 "errors": [ 196 { 197 "detail": "Organization not found", 198 "title": "CF-ResourceNotFound", 199 "code": 10010 200 } 201 ] 202 }` 203 server.AppendHandlers( 204 CombineHandlers( 205 VerifyRequest(http.MethodGet, "/v3/organizations/some-org-guid/relationships/default_isolation_segment"), 206 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 207 ), 208 ) 209 }) 210 211 It("returns an error and warnings", func() { 212 _, warnings, err := client.GetOrganizationDefaultIsolationSegment("some-org-guid") 213 Expect(err).To(MatchError(ccerror.ResourceNotFoundError{ 214 Message: "Organization not found", 215 })) 216 Expect(warnings).To(ConsistOf("this is a warning")) 217 }) 218 }) 219 }) 220 221 Describe("GetSpaceIsolationSegment", func() { 222 When("getting the isolation segment is successful", func() { 223 BeforeEach(func() { 224 response := `{ 225 "data": { 226 "guid": "some-isolation-segment-guid" 227 } 228 }` 229 230 server.AppendHandlers( 231 CombineHandlers( 232 VerifyRequest(http.MethodGet, "/v3/spaces/some-space-guid/relationships/isolation_segment"), 233 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 234 ), 235 ) 236 }) 237 238 It("returns the relationship and warnings", func() { 239 relationship, warnings, err := client.GetSpaceIsolationSegment("some-space-guid") 240 Expect(err).NotTo(HaveOccurred()) 241 Expect(warnings).To(ConsistOf("this is a warning")) 242 Expect(relationship).To(Equal(resources.Relationship{ 243 GUID: "some-isolation-segment-guid", 244 })) 245 }) 246 }) 247 }) 248 249 Describe("UpdateOrganizationDefaultIsolationSegmentRelationship", func() { 250 When("patching the default organization isolation segment with non-empty isolation segment guid", func() { 251 BeforeEach(func() { 252 expectedBody := `{ 253 "data": { 254 "guid": "some-isolation-segment-guid" 255 } 256 }` 257 responseBody := `{ 258 "data": { 259 "guid": "some-isolation-segment-guid" 260 } 261 }` 262 263 server.AppendHandlers( 264 CombineHandlers( 265 VerifyRequest(http.MethodPatch, "/v3/organizations/some-org-guid/relationships/default_isolation_segment"), 266 VerifyJSON(expectedBody), 267 RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 268 ), 269 ) 270 }) 271 272 It("patches the organization's default isolation segment", func() { 273 relationship, warnings, err := client.UpdateOrganizationDefaultIsolationSegmentRelationship("some-org-guid", "some-isolation-segment-guid") 274 Expect(relationship).To(Equal(resources.Relationship{GUID: "some-isolation-segment-guid"})) 275 Expect(err).ToNot(HaveOccurred()) 276 Expect(warnings).To(ConsistOf("this is a warning")) 277 }) 278 }) 279 280 When("patching the default organization isolation segment with empty isolation segment guid", func() { 281 BeforeEach(func() { 282 expectedBody := `{ 283 "data": null 284 }` 285 responseBody := `{ 286 "data": null 287 }` 288 server.AppendHandlers( 289 CombineHandlers( 290 VerifyRequest(http.MethodPatch, "/v3/organizations/some-org-guid/relationships/default_isolation_segment"), 291 VerifyJSON(expectedBody), 292 RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 293 ), 294 ) 295 }) 296 297 It("patches the organization's default isolation segment with nil guid", func() { 298 relationship, warnings, err := client.UpdateOrganizationDefaultIsolationSegmentRelationship("some-org-guid", "") 299 Expect(relationship).To(BeZero()) 300 Expect(err).ToNot(HaveOccurred()) 301 Expect(warnings).To(ConsistOf("this is a warning")) 302 }) 303 }) 304 305 When("patching the isolation segment fails with an error", func() { 306 BeforeEach(func() { 307 response := `{ 308 "errors": [ 309 { 310 "detail": "Organization not found", 311 "title": "CF-ResourceNotFound", 312 "code": 10010 313 } 314 ] 315 }` 316 317 server.AppendHandlers( 318 CombineHandlers( 319 VerifyRequest(http.MethodPatch, "/v3/organizations/some-org-guid/relationships/default_isolation_segment"), 320 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 321 ), 322 ) 323 }) 324 325 It("returns the empty relationship, an error and warnings", func() { 326 relationship, warnings, err := client.UpdateOrganizationDefaultIsolationSegmentRelationship("some-org-guid", "some-isolation-segment-guid") 327 Expect(relationship).To(BeZero()) 328 Expect(err).To(MatchError(ccerror.ResourceNotFoundError{ 329 Message: "Organization not found", 330 })) 331 Expect(warnings).To(ConsistOf("this is a warning")) 332 }) 333 }) 334 }) 335 336 Describe("DeleteIsolationSegmentOrganization", func() { 337 When("relationship exists", func() { 338 BeforeEach(func() { 339 server.AppendHandlers( 340 CombineHandlers( 341 VerifyRequest(http.MethodDelete, "/v3/isolation_segments/segment-guid/relationships/organizations/org-guid"), 342 RespondWith(http.StatusOK, "", http.Header{"X-Cf-Warnings": {"this is a warning"}}), 343 ), 344 ) 345 }) 346 347 It("revoke the relationship", func() { 348 warnings, err := client.DeleteIsolationSegmentOrganization("segment-guid", "org-guid") 349 Expect(err).ToNot(HaveOccurred()) 350 Expect(warnings).To(ConsistOf("this is a warning")) 351 352 Expect(server.ReceivedRequests()).To(HaveLen(3)) 353 }) 354 }) 355 356 When("an error occurs", func() { 357 BeforeEach(func() { 358 response := `{ 359 "errors": [ 360 { 361 "code": 10008, 362 "detail": "The request is semantically invalid: command presence", 363 "title": "CF-UnprocessableEntity" 364 }, 365 { 366 "code": 10008, 367 "detail": "The request is semantically invalid: command presence", 368 "title": "CF-UnprocessableEntity" 369 } 370 ] 371 }` 372 373 server.AppendHandlers( 374 CombineHandlers( 375 VerifyRequest(http.MethodDelete, "/v3/isolation_segments/segment-guid/relationships/organizations/org-guid"), 376 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 377 ), 378 ) 379 }) 380 381 It("returns the error and warnings", func() { 382 warnings, err := client.DeleteIsolationSegmentOrganization("segment-guid", "org-guid") 383 Expect(err).To(MatchError(ccerror.MultiError{ 384 ResponseCode: http.StatusTeapot, 385 Errors: []ccerror.V3Error{ 386 { 387 Code: 10008, 388 Detail: "The request is semantically invalid: command presence", 389 Title: "CF-UnprocessableEntity", 390 }, 391 { 392 Code: 10008, 393 Detail: "The request is semantically invalid: command presence", 394 Title: "CF-UnprocessableEntity", 395 }, 396 }, 397 })) 398 Expect(warnings).To(ConsistOf("this is a warning")) 399 }) 400 }) 401 }) 402 403 Describe("SetApplicationDroplet", func() { 404 Context("it sets the droplet", func() { 405 BeforeEach(func() { 406 response := ` 407 { 408 "data": { 409 "guid": "some-droplet-guid" 410 }, 411 "links": { 412 "self": { 413 "href": "https://api.example.org/v3/apps/some-app-guid/relationships/current_droplet" 414 }, 415 "related": { 416 "href": "https://api.example.org/v3/apps/some-app-guid/droplets/current" 417 } 418 } 419 }` 420 requestBody := map[string]interface{}{ 421 "data": map[string]string{ 422 "guid": "some-droplet-guid", 423 }, 424 } 425 426 server.AppendHandlers( 427 CombineHandlers( 428 VerifyRequest(http.MethodPatch, "/v3/apps/some-app-guid/relationships/current_droplet"), 429 VerifyJSONRepresenting(requestBody), 430 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 431 ), 432 ) 433 }) 434 435 It("returns warnings and no error", func() { 436 relationship, warnings, err := client.SetApplicationDroplet("some-app-guid", "some-droplet-guid") 437 Expect(err).ToNot(HaveOccurred()) 438 Expect(warnings).To(ConsistOf("this is a warning")) 439 Expect(relationship.GUID).To(Equal("some-droplet-guid")) 440 }) 441 }) 442 443 When("the CC returns an error", func() { 444 BeforeEach(func() { 445 response := `{ 446 "errors": [ 447 { 448 "code": 10008, 449 "detail": "The request is semantically invalid: command presence", 450 "title": "CF-UnprocessableEntity" 451 }, 452 { 453 "code": 10010, 454 "detail": "App not found", 455 "title": "CF-ResourceNotFound" 456 } 457 ] 458 }` 459 requestBody := map[string]interface{}{ 460 "data": map[string]string{ 461 "guid": "some-droplet-guid", 462 }, 463 } 464 465 server.AppendHandlers( 466 CombineHandlers( 467 VerifyRequest(http.MethodPatch, "/v3/apps/no-such-app-guid/relationships/current_droplet"), 468 VerifyJSONRepresenting(requestBody), 469 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 470 ), 471 ) 472 473 }) 474 475 It("returns the error and all warnings", func() { 476 _, warnings, err := client.SetApplicationDroplet("no-such-app-guid", "some-droplet-guid") 477 Expect(err).To(MatchError(ccerror.MultiError{ 478 ResponseCode: http.StatusTeapot, 479 Errors: []ccerror.V3Error{ 480 { 481 Code: 10008, 482 Detail: "The request is semantically invalid: command presence", 483 Title: "CF-UnprocessableEntity", 484 }, 485 { 486 Code: 10010, 487 Detail: "App not found", 488 Title: "CF-ResourceNotFound", 489 }, 490 }, 491 })) 492 Expect(warnings).To(ConsistOf("this is a warning")) 493 }) 494 }) 495 }) 496 })