github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/feature_flag_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/resources" 10 . "github.com/onsi/ginkgo" 11 . "github.com/onsi/gomega" 12 . "github.com/onsi/gomega/ghttp" 13 ) 14 15 var _ = Describe("Feature Flags", func() { 16 var client *Client 17 var executeErr error 18 var warnings Warnings 19 var featureFlags []resources.FeatureFlag 20 21 BeforeEach(func() { 22 client, _ = NewTestClient() 23 }) 24 Describe("GetFeatureFlags", func() { 25 26 JustBeforeEach(func() { 27 featureFlags, warnings, executeErr = client.GetFeatureFlags() 28 }) 29 30 When("the cloud controller returns without errors", func() { 31 BeforeEach(func() { 32 response1 := fmt.Sprintf(`{ 33 "pagination": { 34 "next": { 35 "href": "%s/v3/feature_flags?page=2&per_page=2" 36 } 37 }, 38 "resources": [ 39 { 40 "name": "flag1", 41 "enabled": true, 42 "updated_at": "2016-10-17T20:00:42Z", 43 "custom_error_message": null, 44 "links": { 45 "self": { 46 "href": "https://api.example.org/v3/feature_flags/flag1" 47 } 48 } 49 }, 50 { 51 "name": "flag2", 52 "enabled": false, 53 "updated_at": "2016-10-17T20:00:42Z", 54 "custom_error_message": null, 55 "links": { 56 "self": { 57 "href": "https://api.example.org/v3/feature_flags/flag2" 58 } 59 } 60 } 61 ] 62 }`, server.URL()) 63 response2 := `{ 64 "pagination": { 65 "next": null 66 }, 67 "resources": [ 68 { 69 "name": "flag3", 70 "enabled": true, 71 "updated_at": "2016-10-17T20:00:42Z", 72 "custom_error_message": "error message the user sees", 73 "links": { 74 "self": { 75 "href": "https://api.example.org/v3/feature_flags/flag3" 76 } 77 } 78 } 79 ] 80 }` 81 server.AppendHandlers( 82 CombineHandlers( 83 VerifyRequest(http.MethodGet, "/v3/feature_flags"), 84 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"page1 warning"}}), 85 ), 86 ) 87 server.AppendHandlers( 88 CombineHandlers( 89 VerifyRequest(http.MethodGet, "/v3/feature_flags", "page=2&per_page=2"), 90 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"page2 warning"}}), 91 ), 92 ) 93 }) 94 95 It("returns feature flags and warnings", func() { 96 Expect(executeErr).NotTo(HaveOccurred()) 97 Expect(warnings).To(ConsistOf("page1 warning", "page2 warning")) 98 Expect(featureFlags).To(ConsistOf( 99 resources.FeatureFlag{ 100 Name: "flag1", 101 Enabled: true, 102 }, 103 resources.FeatureFlag{ 104 Name: "flag2", 105 Enabled: false, 106 }, 107 resources.FeatureFlag{ 108 Name: "flag3", 109 Enabled: true, 110 }, 111 )) 112 }) 113 }) 114 115 When("the cloud controller returns errors and warnings", func() { 116 BeforeEach(func() { 117 errResponse := `{ 118 "errors": [ 119 { 120 "detail": "Feature flag not found", 121 "title": "CF-ResourceNotFound", 122 "code": 10010 123 } 124 ] 125 }` 126 server.AppendHandlers( 127 CombineHandlers( 128 VerifyRequest(http.MethodGet, "/v3/feature_flags"), 129 RespondWith(http.StatusNotFound, errResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 130 ), 131 ) 132 }) 133 134 It("returns a flag not found error", func() { 135 Expect(executeErr).To(MatchError(ccerror.FeatureFlagNotFoundError{})) 136 Expect(warnings).To(Equal(Warnings{"this is a warning"})) 137 }) 138 139 }) 140 141 }) 142 143 Describe("GetFeatureFlag", func() { 144 var ( 145 flagName string 146 flag resources.FeatureFlag 147 warnings Warnings 148 executeError error 149 ) 150 151 JustBeforeEach(func() { 152 flag, warnings, executeError = client.GetFeatureFlag(flagName) 153 }) 154 155 When("The flag exists", func() { 156 BeforeEach(func() { 157 flagName = "flag1" 158 response := `{ 159 "updated_at": "2016-06-08T16:41:39Z", 160 "name": "flag1", 161 "enabled": true, 162 "custom_error_message": "This is wonky", 163 "links": { 164 "self": { "href": "https://example.com/v3/config/feature_flags/flag1" } 165 } 166 }` 167 server.AppendHandlers( 168 CombineHandlers( 169 VerifyRequest(http.MethodGet, fmt.Sprintf("/v3/feature_flags/%s", flagName)), 170 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 171 ), 172 ) 173 }) 174 175 It("returns the requested flag", func() { 176 Expect(executeError).ToNot(HaveOccurred()) 177 Expect(warnings).To(Equal(Warnings{"this is a warning"})) 178 Expect(flag.Name).To(Equal(flagName)) 179 Expect(flag.Enabled).To(Equal(true)) 180 }) 181 }) 182 When("The flag does not exist", func() { 183 BeforeEach(func() { 184 flagName = "flag1" 185 response := `{ 186 "errors": [ 187 { 188 "detail": "Feature flag not found", 189 "title": "CF-ResourceNotFound", 190 "code": 10010 191 } 192 ] 193 }` 194 195 server.AppendHandlers( 196 CombineHandlers( 197 VerifyRequest(http.MethodGet, fmt.Sprintf("/v3/feature_flags/%s", flagName)), 198 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 199 ), 200 ) 201 }) 202 203 It("returns a flag not found error", func() { 204 Expect(executeError).To(MatchError(ccerror.FeatureFlagNotFoundError{})) 205 Expect(warnings).To(Equal(Warnings{"this is a warning"})) 206 }) 207 }) 208 When("some other error occurs", func() { 209 BeforeEach(func() { 210 flagName = "flag1" 211 response := `{ 212 "errors": [ 213 { 214 "code": 10008, 215 "detail": "The request is semantically invalid: command presence", 216 "title": "CF-UnprocessableEntity" 217 } 218 ] 219 }` 220 221 server.AppendHandlers( 222 CombineHandlers( 223 VerifyRequest(http.MethodGet, fmt.Sprintf("/v3/feature_flags/%s", flagName)), 224 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 225 ), 226 ) 227 }) 228 229 It("returns the error", func() { 230 Expect(executeError).To(MatchError(ccerror.V3UnexpectedResponseError{ 231 V3ErrorResponse: ccerror.V3ErrorResponse{ 232 Errors: []ccerror.V3Error{ 233 { 234 Code: 10008, 235 Detail: "The request is semantically invalid: command presence", 236 Title: "CF-UnprocessableEntity", 237 }, 238 }, 239 }, 240 ResponseCode: http.StatusTeapot, 241 }, 242 )) 243 Expect(warnings).To(Equal(Warnings{"this is a warning"})) 244 }) 245 }) 246 }) 247 248 Describe("UpdateFeatureFlag", func() { 249 var ( 250 argFlag resources.FeatureFlag 251 expectedBody string 252 ccFlag resources.FeatureFlag 253 warnings Warnings 254 executeError error 255 ) 256 257 BeforeEach(func() { 258 expectedBody = `{"enabled":false}` 259 argFlag = resources.FeatureFlag{ 260 Name: "flag1", 261 Enabled: false, 262 } 263 }) 264 265 JustBeforeEach(func() { 266 ccFlag, warnings, executeError = client.UpdateFeatureFlag(argFlag) 267 }) 268 269 When("The flag exists", func() { 270 BeforeEach(func() { 271 response := `{ 272 "updated_at": "2016-06-08T16:41:39Z", 273 "name": "flag1", 274 "enabled": false, 275 "custom_error_message": "This is wonky", 276 "links": { 277 "self": { "href": "https://example.com/v3/config/feature_flags/flag1" } 278 } 279 }` 280 server.AppendHandlers( 281 CombineHandlers( 282 VerifyRequest(http.MethodPatch, fmt.Sprintf("/v3/feature_flags/%s", argFlag.Name)), 283 VerifyJSON(expectedBody), 284 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 285 ), 286 ) 287 }) 288 289 It("Successfully updates the flag", func() { 290 Expect(executeError).ToNot(HaveOccurred()) 291 Expect(warnings).To(Equal(Warnings{"this is a warning"})) 292 Expect(ccFlag).To(Equal(argFlag)) 293 }) 294 }) 295 296 When("The flag does not exist", func() { 297 BeforeEach(func() { 298 response := `{ 299 "errors": [ 300 { 301 "detail": "Feature flag not found", 302 "title": "CF-ResourceNotFound", 303 "code": 10010 304 } 305 ] 306 }` 307 server.AppendHandlers( 308 CombineHandlers( 309 VerifyRequest(http.MethodPatch, fmt.Sprintf("/v3/feature_flags/%s", argFlag.Name)), 310 VerifyJSON(expectedBody), 311 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 312 ), 313 ) 314 }) 315 316 It("returns a flag not found error", func() { 317 Expect(executeError).To(MatchError(ccerror.FeatureFlagNotFoundError{})) 318 Expect(warnings).To(Equal(Warnings{"this is a warning"})) 319 }) 320 }) 321 When("some other error occurs", func() { 322 BeforeEach(func() { 323 response := `{ 324 "errors": [ 325 { 326 "code": 10008, 327 "detail": "The request is semantically invalid: command presence", 328 "title": "CF-UnprocessableEntity" 329 } 330 ] 331 }` 332 333 server.AppendHandlers( 334 CombineHandlers( 335 VerifyRequest(http.MethodPatch, fmt.Sprintf("/v3/feature_flags/%s", argFlag.Name)), 336 VerifyJSON(expectedBody), 337 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 338 ), 339 ) 340 }) 341 342 It("returns the error", func() { 343 Expect(executeError).To(MatchError(ccerror.V3UnexpectedResponseError{ 344 V3ErrorResponse: ccerror.V3ErrorResponse{ 345 Errors: []ccerror.V3Error{ 346 { 347 Code: 10008, 348 Detail: "The request is semantically invalid: command presence", 349 Title: "CF-UnprocessableEntity", 350 }, 351 }, 352 }, 353 ResponseCode: http.StatusTeapot, 354 }, 355 )) 356 Expect(warnings).To(Equal(Warnings{"this is a warning"})) 357 }) 358 }) 359 }) 360 })