github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/healthcheck/healthcheck_test.go (about) 1 package healthcheck_test 2 3 import ( 4 "bytes" 5 "errors" 6 "io" 7 "strings" 8 "testing" 9 10 "github.com/fastly/go-fastly/v9/fastly" 11 12 "github.com/fastly/cli/pkg/app" 13 "github.com/fastly/cli/pkg/global" 14 "github.com/fastly/cli/pkg/mock" 15 "github.com/fastly/cli/pkg/testutil" 16 ) 17 18 func TestHealthCheckCreate(t *testing.T) { 19 args := testutil.Args 20 scenarios := []struct { 21 args []string 22 api mock.API 23 wantError string 24 wantOutput string 25 }{ 26 { 27 args: args("healthcheck create --version 1"), 28 wantError: "error reading service: no service ID found", 29 }, 30 { 31 args: args("healthcheck create --service-id 123 --version 1 --name www.test.com --autoclone"), 32 api: mock.API{ 33 ListVersionsFn: testutil.ListVersions, 34 CloneVersionFn: testutil.CloneVersionResult(4), 35 CreateHealthCheckFn: createHealthCheckError, 36 }, 37 wantError: errTest.Error(), 38 }, 39 // NOTE: Added --timeout flag to validate that a nil pointer dereference is 40 // not triggered at runtime when parsing the arguments. 41 { 42 args: args("healthcheck create --service-id 123 --version 1 --name www.test.com --autoclone --timeout 10"), 43 api: mock.API{ 44 ListVersionsFn: testutil.ListVersions, 45 CloneVersionFn: testutil.CloneVersionResult(4), 46 CreateHealthCheckFn: createHealthCheckOK, 47 }, 48 wantOutput: "Created healthcheck www.test.com (service 123 version 4)", 49 }, 50 } 51 for testcaseIdx := range scenarios { 52 testcase := &scenarios[testcaseIdx] 53 t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { 54 var stdout bytes.Buffer 55 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 56 opts := testutil.MockGlobalData(testcase.args, &stdout) 57 opts.APIClientFactory = mock.APIClient(testcase.api) 58 return opts, nil 59 } 60 err := app.Run(testcase.args, nil) 61 testutil.AssertErrorContains(t, err, testcase.wantError) 62 testutil.AssertStringContains(t, stdout.String(), testcase.wantOutput) 63 }) 64 } 65 } 66 67 func TestHealthCheckList(t *testing.T) { 68 args := testutil.Args 69 scenarios := []struct { 70 args []string 71 api mock.API 72 wantError string 73 wantOutput string 74 }{ 75 { 76 args: args("healthcheck list --service-id 123 --version 1"), 77 api: mock.API{ 78 ListVersionsFn: testutil.ListVersions, 79 ListHealthChecksFn: listHealthChecksOK, 80 }, 81 wantOutput: listHealthChecksShortOutput, 82 }, 83 { 84 args: args("healthcheck list --service-id 123 --version 1 --verbose"), 85 api: mock.API{ 86 ListVersionsFn: testutil.ListVersions, 87 ListHealthChecksFn: listHealthChecksOK, 88 }, 89 wantOutput: listHealthChecksVerboseOutput, 90 }, 91 { 92 args: args("healthcheck list --service-id 123 --version 1 -v"), 93 api: mock.API{ 94 ListVersionsFn: testutil.ListVersions, 95 ListHealthChecksFn: listHealthChecksOK, 96 }, 97 wantOutput: listHealthChecksVerboseOutput, 98 }, 99 { 100 args: args("healthcheck --verbose list --service-id 123 --version 1"), 101 api: mock.API{ 102 ListVersionsFn: testutil.ListVersions, 103 ListHealthChecksFn: listHealthChecksOK, 104 }, 105 wantOutput: listHealthChecksVerboseOutput, 106 }, 107 { 108 args: args("-v healthcheck list --service-id 123 --version 1"), 109 api: mock.API{ 110 ListVersionsFn: testutil.ListVersions, 111 ListHealthChecksFn: listHealthChecksOK, 112 }, 113 wantOutput: listHealthChecksVerboseOutput, 114 }, 115 { 116 args: args("healthcheck list --service-id 123 --version 1"), 117 api: mock.API{ 118 ListVersionsFn: testutil.ListVersions, 119 ListHealthChecksFn: listHealthChecksError, 120 }, 121 wantError: errTest.Error(), 122 }, 123 } 124 for testcaseIdx := range scenarios { 125 testcase := &scenarios[testcaseIdx] 126 t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { 127 var stdout bytes.Buffer 128 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 129 opts := testutil.MockGlobalData(testcase.args, &stdout) 130 opts.APIClientFactory = mock.APIClient(testcase.api) 131 return opts, nil 132 } 133 err := app.Run(testcase.args, nil) 134 testutil.AssertErrorContains(t, err, testcase.wantError) 135 testutil.AssertString(t, testcase.wantOutput, stdout.String()) 136 }) 137 } 138 } 139 140 func TestHealthCheckDescribe(t *testing.T) { 141 args := testutil.Args 142 scenarios := []struct { 143 args []string 144 api mock.API 145 wantError string 146 wantOutput string 147 }{ 148 { 149 args: args("healthcheck describe --service-id 123 --version 1"), 150 wantError: "error parsing arguments: required flag --name not provided", 151 }, 152 { 153 args: args("healthcheck describe --service-id 123 --version 1 --name www.test.com"), 154 api: mock.API{ 155 ListVersionsFn: testutil.ListVersions, 156 GetHealthCheckFn: getHealthCheckError, 157 }, 158 wantError: errTest.Error(), 159 }, 160 { 161 args: args("healthcheck describe --service-id 123 --version 1 --name www.test.com"), 162 api: mock.API{ 163 ListVersionsFn: testutil.ListVersions, 164 GetHealthCheckFn: getHealthCheckOK, 165 }, 166 wantOutput: describeHealthCheckOutput, 167 }, 168 } 169 for testcaseIdx := range scenarios { 170 testcase := &scenarios[testcaseIdx] 171 t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { 172 var stdout bytes.Buffer 173 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 174 opts := testutil.MockGlobalData(testcase.args, &stdout) 175 opts.APIClientFactory = mock.APIClient(testcase.api) 176 return opts, nil 177 } 178 err := app.Run(testcase.args, nil) 179 testutil.AssertErrorContains(t, err, testcase.wantError) 180 testutil.AssertString(t, testcase.wantOutput, stdout.String()) 181 }) 182 } 183 } 184 185 func TestHealthCheckUpdate(t *testing.T) { 186 args := testutil.Args 187 scenarios := []struct { 188 args []string 189 api mock.API 190 wantError string 191 wantOutput string 192 }{ 193 { 194 args: args("healthcheck update --service-id 123 --version 1 --new-name www.test.com --comment "), 195 wantError: "error parsing arguments: required flag --name not provided", 196 }, 197 { 198 args: args("healthcheck update --service-id 123 --version 1 --name www.test.com --new-name www.example.com --autoclone"), 199 api: mock.API{ 200 ListVersionsFn: testutil.ListVersions, 201 CloneVersionFn: testutil.CloneVersionResult(4), 202 UpdateHealthCheckFn: updateHealthCheckOK, 203 }, 204 }, 205 { 206 args: args("healthcheck update --service-id 123 --version 1 --name www.test.com --new-name www.example.com --autoclone"), 207 api: mock.API{ 208 ListVersionsFn: testutil.ListVersions, 209 CloneVersionFn: testutil.CloneVersionResult(4), 210 UpdateHealthCheckFn: updateHealthCheckError, 211 }, 212 wantError: errTest.Error(), 213 }, 214 { 215 args: args("healthcheck update --service-id 123 --version 1 --name www.test.com --new-name www.example.com --autoclone"), 216 api: mock.API{ 217 ListVersionsFn: testutil.ListVersions, 218 CloneVersionFn: testutil.CloneVersionResult(4), 219 UpdateHealthCheckFn: updateHealthCheckOK, 220 }, 221 wantOutput: "Updated healthcheck www.example.com (service 123 version 4)", 222 }, 223 } 224 for testcaseIdx := range scenarios { 225 testcase := &scenarios[testcaseIdx] 226 t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { 227 var stdout bytes.Buffer 228 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 229 opts := testutil.MockGlobalData(testcase.args, &stdout) 230 opts.APIClientFactory = mock.APIClient(testcase.api) 231 return opts, nil 232 } 233 err := app.Run(testcase.args, nil) 234 testutil.AssertErrorContains(t, err, testcase.wantError) 235 testutil.AssertStringContains(t, stdout.String(), testcase.wantOutput) 236 }) 237 } 238 } 239 240 func TestHealthCheckDelete(t *testing.T) { 241 args := testutil.Args 242 scenarios := []struct { 243 args []string 244 api mock.API 245 wantError string 246 wantOutput string 247 }{ 248 { 249 args: args("healthcheck delete --service-id 123 --version 1"), 250 wantError: "error parsing arguments: required flag --name not provided", 251 }, 252 { 253 args: args("healthcheck delete --service-id 123 --version 1 --name www.test.com --autoclone"), 254 api: mock.API{ 255 ListVersionsFn: testutil.ListVersions, 256 CloneVersionFn: testutil.CloneVersionResult(4), 257 DeleteHealthCheckFn: deleteHealthCheckError, 258 }, 259 wantError: errTest.Error(), 260 }, 261 { 262 args: args("healthcheck delete --service-id 123 --version 1 --name www.test.com --autoclone"), 263 api: mock.API{ 264 ListVersionsFn: testutil.ListVersions, 265 CloneVersionFn: testutil.CloneVersionResult(4), 266 DeleteHealthCheckFn: deleteHealthCheckOK, 267 }, 268 wantOutput: "Deleted healthcheck www.test.com (service 123 version 4)", 269 }, 270 } 271 for testcaseIdx := range scenarios { 272 testcase := &scenarios[testcaseIdx] 273 t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { 274 var stdout bytes.Buffer 275 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 276 opts := testutil.MockGlobalData(testcase.args, &stdout) 277 opts.APIClientFactory = mock.APIClient(testcase.api) 278 return opts, nil 279 } 280 err := app.Run(testcase.args, nil) 281 testutil.AssertErrorContains(t, err, testcase.wantError) 282 testutil.AssertStringContains(t, stdout.String(), testcase.wantOutput) 283 }) 284 } 285 } 286 287 var errTest = errors.New("fixture error") 288 289 func createHealthCheckOK(i *fastly.CreateHealthCheckInput) (*fastly.HealthCheck, error) { 290 return &fastly.HealthCheck{ 291 ServiceID: fastly.ToPointer(i.ServiceID), 292 ServiceVersion: fastly.ToPointer(i.ServiceVersion), 293 Name: i.Name, 294 Host: fastly.ToPointer("www.test.com"), 295 Path: fastly.ToPointer("/health"), 296 }, nil 297 } 298 299 func createHealthCheckError(_ *fastly.CreateHealthCheckInput) (*fastly.HealthCheck, error) { 300 return nil, errTest 301 } 302 303 func listHealthChecksOK(i *fastly.ListHealthChecksInput) ([]*fastly.HealthCheck, error) { 304 return []*fastly.HealthCheck{ 305 { 306 ServiceID: fastly.ToPointer(i.ServiceID), 307 ServiceVersion: fastly.ToPointer(i.ServiceVersion), 308 Name: fastly.ToPointer("test"), 309 Comment: fastly.ToPointer("test"), 310 Method: fastly.ToPointer("HEAD"), 311 Host: fastly.ToPointer("www.test.com"), 312 Path: fastly.ToPointer("/health"), 313 }, 314 { 315 ServiceID: fastly.ToPointer(i.ServiceID), 316 ServiceVersion: fastly.ToPointer(i.ServiceVersion), 317 Name: fastly.ToPointer("example"), 318 Comment: fastly.ToPointer("example"), 319 Method: fastly.ToPointer("HEAD"), 320 Host: fastly.ToPointer("www.example.com"), 321 Path: fastly.ToPointer("/health"), 322 }, 323 }, nil 324 } 325 326 func listHealthChecksError(_ *fastly.ListHealthChecksInput) ([]*fastly.HealthCheck, error) { 327 return nil, errTest 328 } 329 330 var listHealthChecksShortOutput = strings.TrimSpace(` 331 SERVICE VERSION NAME METHOD HOST PATH 332 123 1 test HEAD www.test.com /health 333 123 1 example HEAD www.example.com /health 334 `) + "\n" 335 336 var listHealthChecksVerboseOutput = strings.Join([]string{ 337 "Fastly API endpoint: https://api.fastly.com", 338 "Fastly API token provided via config file (profile: user)", 339 "", 340 "Service ID (via --service-id): 123", 341 "", 342 "Version: 1", 343 " Healthcheck 1/2", 344 " Name: test", 345 " Comment: test", 346 " Method: HEAD", 347 " Host: www.test.com", 348 " Path: /health", 349 " HTTP version: ", 350 " Timeout: 0", 351 " Check interval: 0", 352 " Expected response: 0", 353 " Window: 0", 354 " Threshold: 0", 355 " Initial: 0", 356 " Healthcheck 2/2", 357 " Name: example", 358 " Comment: example", 359 " Method: HEAD", 360 " Host: www.example.com", 361 " Path: /health", 362 " HTTP version: ", 363 " Timeout: 0", 364 " Check interval: 0", 365 " Expected response: 0", 366 " Window: 0", 367 " Threshold: 0", 368 " Initial: 0", 369 }, "\n") + "\n\n" 370 371 func getHealthCheckOK(i *fastly.GetHealthCheckInput) (*fastly.HealthCheck, error) { 372 return &fastly.HealthCheck{ 373 ServiceID: fastly.ToPointer(i.ServiceID), 374 ServiceVersion: fastly.ToPointer(i.ServiceVersion), 375 Name: fastly.ToPointer("test"), 376 Method: fastly.ToPointer("HEAD"), 377 Host: fastly.ToPointer("www.test.com"), 378 Path: fastly.ToPointer("/healthcheck"), 379 Comment: fastly.ToPointer("test"), 380 }, nil 381 } 382 383 func getHealthCheckError(_ *fastly.GetHealthCheckInput) (*fastly.HealthCheck, error) { 384 return nil, errTest 385 } 386 387 var describeHealthCheckOutput = "\n" + strings.Join([]string{ 388 "Service ID: 123", 389 "Version: 1", 390 "Name: test", 391 "Comment: test", 392 "Method: HEAD", 393 "Host: www.test.com", 394 "Path: /healthcheck", 395 "HTTP version: ", 396 "Timeout: 0", 397 "Check interval: 0", 398 "Expected response: 0", 399 "Window: 0", 400 "Threshold: 0", 401 "Initial: 0", 402 }, "\n") + "\n" 403 404 func updateHealthCheckOK(i *fastly.UpdateHealthCheckInput) (*fastly.HealthCheck, error) { 405 return &fastly.HealthCheck{ 406 ServiceID: fastly.ToPointer(i.ServiceID), 407 ServiceVersion: fastly.ToPointer(i.ServiceVersion), 408 Name: i.NewName, 409 }, nil 410 } 411 412 func updateHealthCheckError(_ *fastly.UpdateHealthCheckInput) (*fastly.HealthCheck, error) { 413 return nil, errTest 414 } 415 416 func deleteHealthCheckOK(_ *fastly.DeleteHealthCheckInput) error { 417 return nil 418 } 419 420 func deleteHealthCheckError(_ *fastly.DeleteHealthCheckInput) error { 421 return errTest 422 }