github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/space_test.go (about) 1 package ccv3_test 2 3 import ( 4 "fmt" 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/api/cloudcontroller/ccv3/constant" 10 "code.cloudfoundry.org/cli/resources" 11 . "code.cloudfoundry.org/cli/resources" 12 "code.cloudfoundry.org/cli/types" 13 . "github.com/onsi/ginkgo" 14 . "github.com/onsi/gomega" 15 . "github.com/onsi/gomega/ghttp" 16 ) 17 18 var _ = Describe("Spaces", func() { 19 var client *Client 20 21 BeforeEach(func() { 22 client, _ = NewTestClient() 23 }) 24 25 Describe("CreateSpace", func() { 26 var ( 27 space Space 28 spaceToCreate Space 29 warnings Warnings 30 executeErr error 31 ) 32 33 JustBeforeEach(func() { 34 spaceToCreate = Space{Name: "some-space", Relationships: resources.Relationships{constant.RelationshipTypeOrganization: resources.Relationship{GUID: "some-org-guid"}}} 35 space, warnings, executeErr = client.CreateSpace(spaceToCreate) 36 }) 37 38 When("spaces exist", func() { 39 BeforeEach(func() { 40 response := `{ 41 "name": "some-space", 42 "guid": "some-space-guid" 43 }` 44 45 expectedBody := `{ 46 "name": "some-space", 47 "relationships": { 48 "organization": { 49 "data": { "guid": "some-org-guid" } 50 } 51 } 52 }` 53 server.AppendHandlers( 54 CombineHandlers( 55 VerifyRequest(http.MethodPost, "/v3/spaces"), 56 VerifyJSON(expectedBody), 57 RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 58 ), 59 ) 60 }) 61 62 It("returns the given space and all warnings", func() { 63 Expect(executeErr).ToNot(HaveOccurred()) 64 Expect(warnings).To(ConsistOf("this is a warning")) 65 66 Expect(space).To(Equal(Space{ 67 GUID: "some-space-guid", 68 Name: "some-space", 69 })) 70 }) 71 }) 72 73 When("the cloud controller returns errors and warnings", func() { 74 BeforeEach(func() { 75 response := `{ 76 "errors": [ 77 { 78 "code": 10008, 79 "detail": "The request is semantically invalid: command presence", 80 "title": "CF-UnprocessableEntity" 81 }, 82 { 83 "code": 10010, 84 "detail": "Isolation segment not found", 85 "title": "CF-ResourceNotFound" 86 } 87 ] 88 }` 89 server.AppendHandlers( 90 CombineHandlers( 91 VerifyRequest(http.MethodPost, "/v3/spaces"), 92 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 93 ), 94 ) 95 }) 96 97 It("returns the error and all warnings", func() { 98 Expect(executeErr).To(MatchError(ccerror.MultiError{ 99 ResponseCode: http.StatusTeapot, 100 Errors: []ccerror.V3Error{ 101 { 102 Code: 10008, 103 Detail: "The request is semantically invalid: command presence", 104 Title: "CF-UnprocessableEntity", 105 }, 106 { 107 Code: 10010, 108 Detail: "Isolation segment not found", 109 Title: "CF-ResourceNotFound", 110 }, 111 }, 112 })) 113 Expect(warnings).To(ConsistOf("this is a warning")) 114 }) 115 }) 116 117 }) 118 119 Describe("GetSpaces", func() { 120 var ( 121 query []Query 122 123 includes IncludedResources 124 spaces []Space 125 warnings Warnings 126 executeErr error 127 ) 128 129 JustBeforeEach(func() { 130 spaces, includes, warnings, executeErr = client.GetSpaces(query...) 131 }) 132 133 When("spaces exist", func() { 134 BeforeEach(func() { 135 response1 := fmt.Sprintf(`{ 136 "pagination": { 137 "next": { 138 "href": "%s/v3/spaces?names=some-space-name&page=2&per_page=2" 139 } 140 }, 141 "resources": [ 142 { 143 "name": "space-name-1", 144 "guid": "space-guid-1", 145 "relationships": { 146 "organization": { 147 "data": { "guid": "org-guid-1" } 148 } 149 } 150 }, 151 { 152 "name": "space-name-2", 153 "guid": "space-guid-2", 154 "relationships": { 155 "organization": { 156 "data": { "guid": "org-guid-2" } 157 } 158 } 159 } 160 ] 161 }`, server.URL()) 162 response2 := `{ 163 "pagination": { 164 "next": null 165 }, 166 "resources": [ 167 { 168 "name": "space-name-3", 169 "guid": "space-guid-3", 170 "relationships": { 171 "organization": { 172 "data": { "guid": "org-guid-3" } 173 } 174 } 175 } 176 ] 177 }` 178 179 server.AppendHandlers( 180 CombineHandlers( 181 VerifyRequest(http.MethodGet, "/v3/spaces", "names=some-space-name"), 182 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 183 ), 184 ) 185 server.AppendHandlers( 186 CombineHandlers( 187 VerifyRequest(http.MethodGet, "/v3/spaces", "names=some-space-name&page=2&per_page=2"), 188 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"this is another warning"}}), 189 ), 190 ) 191 192 query = []Query{{ 193 Key: NameFilter, 194 Values: []string{"some-space-name"}, 195 }} 196 }) 197 198 It("returns the queried spaces and all warnings", func() { 199 Expect(executeErr).NotTo(HaveOccurred()) 200 201 Expect(spaces).To(ConsistOf( 202 Space{Name: "space-name-1", GUID: "space-guid-1", Relationships: resources.Relationships{ 203 constant.RelationshipTypeOrganization: resources.Relationship{GUID: "org-guid-1"}, 204 }}, 205 Space{Name: "space-name-2", GUID: "space-guid-2", Relationships: resources.Relationships{ 206 constant.RelationshipTypeOrganization: resources.Relationship{GUID: "org-guid-2"}, 207 }}, 208 Space{Name: "space-name-3", GUID: "space-guid-3", Relationships: resources.Relationships{ 209 constant.RelationshipTypeOrganization: resources.Relationship{GUID: "org-guid-3"}, 210 }}, 211 )) 212 Expect(warnings).To(ConsistOf("this is a warning", "this is another warning")) 213 }) 214 }) 215 216 When("the request uses the `include` query key", func() { 217 BeforeEach(func() { 218 response1 := fmt.Sprintf(`{ 219 "pagination": { 220 "next": { 221 "href": "%s/v3/spaces?names=some-space-name&include=organizations&page=2&per_page=2" 222 } 223 }, 224 "resources": [ 225 { 226 "name": "space-name-1", 227 "guid": "space-guid-1", 228 "relationships": { 229 "organization": { 230 "data": { "guid": "org-guid-1" } 231 } 232 } 233 }, 234 { 235 "name": "space-name-2", 236 "guid": "space-guid-2", 237 "relationships": { 238 "organization": { 239 "data": { "guid": "org-guid-2" } 240 } 241 } 242 } 243 ], 244 "included": { 245 "organizations": [ 246 { 247 "guid": "org-guid-1", 248 "name": "org-name-1" 249 } 250 ] 251 } 252 }`, server.URL()) 253 response2 := `{ 254 "pagination": { 255 "next": null 256 }, 257 "resources": [ 258 { 259 "name": "space-name-3", 260 "guid": "space-guid-3", 261 "relationships": { 262 "organization": { 263 "data": { "guid": "org-guid-3" } 264 } 265 } 266 } 267 ], 268 "included": { 269 "organizations": [ 270 { 271 "guid": "org-guid-2", 272 "name": "org-name-2" 273 } 274 ] 275 } 276 }` 277 278 server.AppendHandlers( 279 CombineHandlers( 280 VerifyRequest(http.MethodGet, "/v3/spaces", "names=some-space-name&include=organizations"), 281 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}), 282 ), 283 ) 284 server.AppendHandlers( 285 CombineHandlers( 286 VerifyRequest(http.MethodGet, "/v3/spaces", "names=some-space-name&page=2&per_page=2&include=organizations"), 287 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}), 288 ), 289 ) 290 291 query = []Query{ 292 { 293 Key: NameFilter, 294 Values: []string{"some-space-name"}, 295 }, 296 { 297 Key: Include, 298 Values: []string{"organizations"}, 299 }, 300 } 301 }) 302 303 It("returns the given route and all warnings", func() { 304 Expect(executeErr).ToNot(HaveOccurred()) 305 Expect(warnings).To(ConsistOf("warning-1", "warning-2")) 306 307 Expect(spaces).To(ConsistOf( 308 Space{Name: "space-name-1", GUID: "space-guid-1", Relationships: resources.Relationships{ 309 constant.RelationshipTypeOrganization: resources.Relationship{GUID: "org-guid-1"}, 310 }}, 311 Space{Name: "space-name-2", GUID: "space-guid-2", Relationships: resources.Relationships{ 312 constant.RelationshipTypeOrganization: resources.Relationship{GUID: "org-guid-2"}, 313 }}, 314 Space{Name: "space-name-3", GUID: "space-guid-3", Relationships: resources.Relationships{ 315 constant.RelationshipTypeOrganization: resources.Relationship{GUID: "org-guid-3"}, 316 }}, 317 )) 318 319 Expect(includes).To(Equal(IncludedResources{ 320 Organizations: []Organization{ 321 {GUID: "org-guid-1", Name: "org-name-1"}, 322 {GUID: "org-guid-2", Name: "org-name-2"}, 323 }, 324 })) 325 }) 326 }) 327 328 When("the cloud controller returns errors and warnings", func() { 329 BeforeEach(func() { 330 response := `{ 331 "errors": [ 332 { 333 "code": 10008, 334 "detail": "The request is semantically invalid: command presence", 335 "title": "CF-UnprocessableEntity" 336 }, 337 { 338 "code": 10010, 339 "detail": "Space not found", 340 "title": "CF-SpaceNotFound" 341 } 342 ] 343 }` 344 server.AppendHandlers( 345 CombineHandlers( 346 VerifyRequest(http.MethodGet, "/v3/spaces"), 347 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 348 ), 349 ) 350 }) 351 352 It("returns the error and all warnings", func() { 353 Expect(executeErr).To(MatchError(ccerror.MultiError{ 354 ResponseCode: http.StatusTeapot, 355 Errors: []ccerror.V3Error{ 356 { 357 Code: 10008, 358 Detail: "The request is semantically invalid: command presence", 359 Title: "CF-UnprocessableEntity", 360 }, 361 { 362 Code: 10010, 363 Detail: "Space not found", 364 Title: "CF-SpaceNotFound", 365 }, 366 }, 367 })) 368 Expect(warnings).To(ConsistOf("this is a warning")) 369 }) 370 }) 371 }) 372 373 Describe("UpdateSpace", func() { 374 var ( 375 spaceToUpdate Space 376 updatedSpace Space 377 warnings Warnings 378 executeErr error 379 ) 380 381 JustBeforeEach(func() { 382 updatedSpace, warnings, executeErr = client.UpdateSpace(spaceToUpdate) 383 }) 384 385 When("the organization is updated successfully", func() { 386 BeforeEach(func() { 387 response := `{ 388 "guid": "some-space-guid", 389 "name": "some-space-name", 390 "metadata": { 391 "labels": { 392 "k1":"v1", 393 "k2":"v2" 394 } 395 } 396 }` 397 398 expectedBody := map[string]interface{}{ 399 "name": "some-space-name", 400 "metadata": map[string]interface{}{ 401 "labels": map[string]string{ 402 "k1": "v1", 403 "k2": "v2", 404 }, 405 }, 406 } 407 408 server.AppendHandlers( 409 CombineHandlers( 410 VerifyRequest(http.MethodPatch, "/v3/spaces/some-guid"), 411 VerifyJSONRepresenting(expectedBody), 412 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 413 ), 414 ) 415 416 spaceToUpdate = Space{ 417 Name: "some-space-name", 418 GUID: "some-guid", 419 Metadata: &Metadata{ 420 Labels: map[string]types.NullString{ 421 "k1": types.NewNullString("v1"), 422 "k2": types.NewNullString("v2"), 423 }, 424 }, 425 } 426 }) 427 428 It("should include the labels in the JSON", func() { 429 Expect(executeErr).ToNot(HaveOccurred()) 430 Expect(server.ReceivedRequests()).To(HaveLen(1)) 431 Expect(len(warnings)).To(Equal(1)) 432 Expect(warnings).To(ConsistOf("this is a warning")) 433 Expect(updatedSpace.Metadata.Labels).To(BeEquivalentTo( 434 map[string]types.NullString{ 435 "k1": types.NewNullString("v1"), 436 "k2": types.NewNullString("v2"), 437 })) 438 }) 439 440 }) 441 442 }) 443 444 Describe("DeleteSpace", func() { 445 var ( 446 jobURL JobURL 447 warnings Warnings 448 executeErr error 449 ) 450 451 JustBeforeEach(func() { 452 jobURL, warnings, executeErr = client.DeleteSpace("space-guid") 453 }) 454 455 When("no errors are encountered", func() { 456 BeforeEach(func() { 457 server.AppendHandlers( 458 CombineHandlers( 459 VerifyRequest(http.MethodDelete, "/v3/spaces/space-guid"), 460 RespondWith(http.StatusAccepted, nil, http.Header{"X-Cf-Warnings": {"warning-1, warning-2"}, "Location": []string{"job-url"}}), 461 )) 462 }) 463 464 It("deletes the resources.Space and returns all warnings", func() { 465 Expect(executeErr).NotTo(HaveOccurred()) 466 Expect(warnings).To(ConsistOf(Warnings{"warning-1", "warning-2"})) 467 Expect(jobURL).To(Equal(JobURL("job-url"))) 468 }) 469 }) 470 471 When("an error is encountered", func() { 472 BeforeEach(func() { 473 response := `{ 474 "errors": [ 475 { 476 "detail": "Space not found", 477 "title": "CF-ResourceNotFound", 478 "code": 10010 479 } 480 ] 481 }` 482 server.AppendHandlers( 483 CombineHandlers( 484 VerifyRequest(http.MethodDelete, "/v3/spaces/space-guid"), 485 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"warning-1, warning-2"}}), 486 )) 487 }) 488 489 It("returns an error and all warnings", func() { 490 Expect(executeErr).To(MatchError(ccerror.ResourceNotFoundError{ 491 Message: "Space not found", 492 })) 493 Expect(warnings).To(ConsistOf(Warnings{"warning-1", "warning-2"})) 494 }) 495 }) 496 }) 497 })