github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/application/register_test.go (about) 1 // Copyright 2015 Canonical Ltd. All rights reserved. 2 3 package application 4 5 import ( 6 "encoding/json" 7 "net/http" 8 "net/http/httptest" 9 10 "github.com/juju/cmd" 11 "github.com/juju/cmd/cmdtesting" 12 "github.com/juju/errors" 13 "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/charm.v6" 17 "gopkg.in/macaroon-bakery.v2-unstable/httpbakery" 18 19 apicharms "github.com/juju/juju/api/charms" 20 "github.com/juju/juju/charmstore" 21 ) 22 23 var _ = gc.Suite(®istrationSuite{}) 24 25 type registrationSuite struct { 26 testing.CleanupSuite 27 stub *testing.Stub 28 handler *testMetricsRegistrationHandler 29 server *httptest.Server 30 register DeployStep 31 ctx *cmd.Context 32 } 33 34 func (s *registrationSuite) SetUpTest(c *gc.C) { 35 s.CleanupSuite.SetUpTest(c) 36 s.stub = &testing.Stub{} 37 s.handler = &testMetricsRegistrationHandler{ 38 Stub: s.stub, 39 availablePlans: []availablePlanURL{ 40 {URL: "thisplan"}, 41 {URL: "thisotherplan"}, 42 }, 43 } 44 s.server = httptest.NewServer(s.handler) 45 s.register = &RegisterMeteredCharm{ 46 Plan: "someplan", 47 PlanURL: s.server.URL, 48 IncreaseBudget: 100, 49 } 50 s.ctx = cmdtesting.Context(c) 51 } 52 53 func (s *registrationSuite) TearDownTest(c *gc.C) { 54 s.CleanupSuite.TearDownTest(c) 55 s.server.Close() 56 } 57 58 func (s *registrationSuite) TestMeteredCharm(c *gc.C) { 59 client := httpbakery.NewClient() 60 d := DeploymentInfo{ 61 CharmID: charmstore.CharmID{ 62 URL: charm.MustParseURL("cs:quantal/metered-1"), 63 }, 64 ApplicationName: "application name", 65 ModelUUID: "model uuid", 66 CharmInfo: &apicharms.CharmInfo{ 67 Metrics: &charm.Metrics{ 68 Plan: &charm.Plan{Required: true}, 69 }, 70 }, 71 } 72 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 73 c.Assert(err, jc.ErrorIsNil) 74 err = s.register.RunPost(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d, nil) 75 c.Assert(err, jc.ErrorIsNil) 76 authorization, err := json.Marshal([]byte("hello registration")) 77 authorization = append(authorization, byte(0xa)) 78 s.stub.CheckCalls(c, []testing.StubCall{{ 79 "IsMetered", []interface{}{"cs:quantal/metered-1"}, 80 }, { 81 "Authorize", []interface{}{metricRegistrationPost{ 82 ModelUUID: "model uuid", 83 CharmURL: "cs:quantal/metered-1", 84 ApplicationName: "application name", 85 PlanURL: "someplan", 86 IncreaseBudget: 100, 87 }}, 88 }, { 89 "SetMetricCredentials", []interface{}{ 90 "application name", 91 authorization, 92 }}, 93 }) 94 } 95 96 func (s *registrationSuite) TestOptionalPlanMeteredCharm(c *gc.C) { 97 client := httpbakery.NewClient() 98 d := DeploymentInfo{ 99 CharmID: charmstore.CharmID{ 100 URL: charm.MustParseURL("cs:quantal/metered-1"), 101 }, 102 ApplicationName: "application name", 103 ModelUUID: "model uuid", 104 CharmInfo: &apicharms.CharmInfo{ 105 Metrics: &charm.Metrics{ 106 Plan: &charm.Plan{Required: false}, 107 }, 108 }, 109 } 110 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 111 c.Assert(err, jc.ErrorIsNil) 112 err = s.register.RunPost(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d, nil) 113 c.Assert(err, jc.ErrorIsNil) 114 authorization, err := json.Marshal([]byte("hello registration")) 115 authorization = append(authorization, byte(0xa)) 116 s.stub.CheckCalls(c, []testing.StubCall{{ 117 "IsMetered", []interface{}{"cs:quantal/metered-1"}, 118 }, { 119 "Authorize", []interface{}{metricRegistrationPost{ 120 ModelUUID: "model uuid", 121 CharmURL: "cs:quantal/metered-1", 122 ApplicationName: "application name", 123 PlanURL: "someplan", 124 IncreaseBudget: 100, 125 }}, 126 }, { 127 "SetMetricCredentials", []interface{}{ 128 "application name", 129 authorization, 130 }}, 131 }) 132 } 133 134 func (s *registrationSuite) TestPlanNotSpecifiedCharm(c *gc.C) { 135 client := httpbakery.NewClient() 136 d := DeploymentInfo{ 137 CharmID: charmstore.CharmID{ 138 URL: charm.MustParseURL("cs:quantal/metered-1"), 139 }, 140 ApplicationName: "application name", 141 ModelUUID: "model uuid", 142 CharmInfo: &apicharms.CharmInfo{ 143 Metrics: &charm.Metrics{ 144 Plan: nil, 145 }, 146 }, 147 } 148 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 149 c.Assert(err, jc.ErrorIsNil) 150 err = s.register.RunPost(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d, nil) 151 c.Assert(err, jc.ErrorIsNil) 152 authorization, err := json.Marshal([]byte("hello registration")) 153 authorization = append(authorization, byte(0xa)) 154 s.stub.CheckCalls(c, []testing.StubCall{{ 155 "IsMetered", []interface{}{"cs:quantal/metered-1"}, 156 }, { 157 "Authorize", []interface{}{metricRegistrationPost{ 158 ModelUUID: "model uuid", 159 CharmURL: "cs:quantal/metered-1", 160 ApplicationName: "application name", 161 PlanURL: "someplan", 162 IncreaseBudget: 100, 163 }}, 164 }, { 165 "SetMetricCredentials", []interface{}{ 166 "application name", 167 authorization, 168 }}, 169 }) 170 } 171 172 func (s *registrationSuite) TestMeteredCharmAPIError(c *gc.C) { 173 s.stub.SetErrors(nil, errors.New("something failed")) 174 client := httpbakery.NewClient() 175 d := DeploymentInfo{ 176 CharmID: charmstore.CharmID{ 177 URL: charm.MustParseURL("cs:quantal/metered-1"), 178 }, 179 ApplicationName: "application name", 180 ModelUUID: "model uuid", 181 CharmInfo: &apicharms.CharmInfo{ 182 Metrics: &charm.Metrics{ 183 Plan: &charm.Plan{Required: true}, 184 }, 185 }, 186 } 187 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 188 c.Assert(err, gc.ErrorMatches, `authorization failed: something failed`) 189 s.stub.CheckCalls(c, []testing.StubCall{{ 190 "IsMetered", []interface{}{"cs:quantal/metered-1"}, 191 }, { 192 "Authorize", []interface{}{metricRegistrationPost{ 193 ModelUUID: "model uuid", 194 CharmURL: "cs:quantal/metered-1", 195 ApplicationName: "application name", 196 PlanURL: "someplan", 197 IncreaseBudget: 100, 198 }}, 199 }}) 200 } 201 202 func (s *registrationSuite) TestMeteredCharmInvalidAllocation(c *gc.C) { 203 client := httpbakery.NewClient() 204 d := DeploymentInfo{ 205 CharmID: charmstore.CharmID{ 206 URL: charm.MustParseURL("cs:quantal/metered-1"), 207 }, 208 ApplicationName: "application name", 209 ModelUUID: "model uuid", 210 CharmInfo: &apicharms.CharmInfo{ 211 Metrics: &charm.Metrics{ 212 Plan: &charm.Plan{Required: true}, 213 }, 214 }, 215 } 216 s.register = &RegisterMeteredCharm{ 217 Plan: "someplan", 218 PlanURL: s.server.URL, 219 IncreaseBudget: -1000, 220 } 221 222 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 223 c.Assert(err, gc.ErrorMatches, `invalid budget increase -1000`) 224 s.stub.CheckNoCalls(c) 225 } 226 227 func (s *registrationSuite) TestMeteredCharmDefaultBudgetAllocation(c *gc.C) { 228 client := httpbakery.NewClient() 229 d := DeploymentInfo{ 230 CharmID: charmstore.CharmID{ 231 URL: charm.MustParseURL("cs:quantal/metered-1"), 232 }, 233 ApplicationName: "application name", 234 ModelUUID: "model uuid", 235 CharmInfo: &apicharms.CharmInfo{ 236 Metrics: &charm.Metrics{ 237 Plan: &charm.Plan{Required: true}, 238 }, 239 }, 240 } 241 s.register = &RegisterMeteredCharm{ 242 Plan: "someplan", 243 PlanURL: s.server.URL, 244 IncreaseBudget: 20, 245 } 246 247 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 248 c.Assert(err, jc.ErrorIsNil) 249 s.stub.CheckCalls(c, []testing.StubCall{{ 250 FuncName: "IsMetered", 251 Args: []interface{}{"cs:quantal/metered-1"}, 252 }, { 253 FuncName: "Authorize", 254 Args: []interface{}{metricRegistrationPost{ 255 ModelUUID: "model uuid", 256 CharmURL: "cs:quantal/metered-1", 257 ApplicationName: "application name", 258 PlanURL: "someplan", 259 IncreaseBudget: 20, 260 }, 261 }, 262 }, 263 }) 264 } 265 266 func (s *registrationSuite) TestMeteredCharmDeployError(c *gc.C) { 267 client := httpbakery.NewClient() 268 d := DeploymentInfo{ 269 CharmID: charmstore.CharmID{ 270 URL: charm.MustParseURL("cs:quantal/metered-1"), 271 }, 272 ApplicationName: "application name", 273 ModelUUID: "model uuid", 274 CharmInfo: &apicharms.CharmInfo{ 275 Metrics: &charm.Metrics{ 276 Plan: &charm.Plan{Required: true}, 277 }, 278 }, 279 } 280 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 281 c.Assert(err, jc.ErrorIsNil) 282 deployError := errors.New("deployment failed") 283 err = s.register.RunPost(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d, deployError) 284 c.Assert(err, jc.ErrorIsNil) 285 authorization, err := json.Marshal([]byte("hello registration")) 286 authorization = append(authorization, byte(0xa)) 287 c.Assert(err, jc.ErrorIsNil) 288 s.stub.CheckCalls(c, []testing.StubCall{{ 289 "IsMetered", []interface{}{"cs:quantal/metered-1"}, 290 }, { 291 "Authorize", []interface{}{metricRegistrationPost{ 292 ModelUUID: "model uuid", 293 CharmURL: "cs:quantal/metered-1", 294 ApplicationName: "application name", 295 PlanURL: "someplan", 296 IncreaseBudget: 100, 297 }}, 298 }}) 299 } 300 301 func (s *registrationSuite) TestMeteredLocalCharmWithPlan(c *gc.C) { 302 client := httpbakery.NewClient() 303 d := DeploymentInfo{ 304 CharmID: charmstore.CharmID{ 305 URL: charm.MustParseURL("local:quantal/metered-1"), 306 }, 307 ApplicationName: "application name", 308 ModelUUID: "model uuid", 309 CharmInfo: &apicharms.CharmInfo{ 310 Metrics: &charm.Metrics{ 311 Plan: &charm.Plan{Required: true}, 312 }, 313 }, 314 } 315 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 316 c.Assert(err, jc.ErrorIsNil) 317 err = s.register.RunPost(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d, nil) 318 c.Assert(err, jc.ErrorIsNil) 319 authorization, err := json.Marshal([]byte("hello registration")) 320 authorization = append(authorization, byte(0xa)) 321 s.stub.CheckCalls(c, []testing.StubCall{{ 322 "IsMetered", []interface{}{"local:quantal/metered-1"}, 323 }, { 324 "Authorize", []interface{}{metricRegistrationPost{ 325 ModelUUID: "model uuid", 326 CharmURL: "local:quantal/metered-1", 327 ApplicationName: "application name", 328 PlanURL: "someplan", 329 IncreaseBudget: 100, 330 }}, 331 }, { 332 "SetMetricCredentials", []interface{}{ 333 "application name", 334 authorization, 335 }, 336 }}) 337 } 338 339 func (s *registrationSuite) TestMeteredLocalCharmNoPlan(c *gc.C) { 340 s.register = &RegisterMeteredCharm{ 341 PlanURL: s.server.URL, 342 IncreaseBudget: 100, 343 } 344 client := httpbakery.NewClient() 345 d := DeploymentInfo{ 346 CharmID: charmstore.CharmID{ 347 URL: charm.MustParseURL("local:quantal/metered-1"), 348 }, 349 ApplicationName: "application name", 350 ModelUUID: "model uuid", 351 CharmInfo: &apicharms.CharmInfo{ 352 Metrics: &charm.Metrics{ 353 Plan: &charm.Plan{Required: true}, 354 }, 355 }, 356 } 357 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 358 c.Assert(err, jc.ErrorIsNil) 359 err = s.register.RunPost(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d, nil) 360 c.Assert(err, jc.ErrorIsNil) 361 authorization, err := json.Marshal([]byte("hello registration")) 362 authorization = append(authorization, byte(0xa)) 363 s.stub.CheckCalls(c, []testing.StubCall{{ 364 "IsMetered", []interface{}{"local:quantal/metered-1"}, 365 }, { 366 "Authorize", []interface{}{metricRegistrationPost{ 367 ModelUUID: "model uuid", 368 CharmURL: "local:quantal/metered-1", 369 ApplicationName: "application name", 370 PlanURL: "", 371 IncreaseBudget: 100, 372 }}, 373 }, { 374 "SetMetricCredentials", []interface{}{ 375 "application name", 376 authorization, 377 }}, 378 }) 379 } 380 381 func (s *registrationSuite) TestMeteredCharmNoPlanSet(c *gc.C) { 382 s.register = &RegisterMeteredCharm{ 383 IncreaseBudget: 100, 384 PlanURL: s.server.URL} 385 client := httpbakery.NewClient() 386 d := DeploymentInfo{ 387 CharmID: charmstore.CharmID{ 388 URL: charm.MustParseURL("cs:quantal/metered-1"), 389 }, 390 ApplicationName: "application name", 391 ModelUUID: "model uuid", 392 CharmInfo: &apicharms.CharmInfo{ 393 Metrics: &charm.Metrics{ 394 Plan: &charm.Plan{Required: true}, 395 }, 396 }, 397 } 398 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 399 c.Assert(err, jc.ErrorIsNil) 400 err = s.register.RunPost(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d, nil) 401 c.Assert(err, jc.ErrorIsNil) 402 authorization, err := json.Marshal([]byte("hello registration")) 403 authorization = append(authorization, byte(0xa)) 404 c.Assert(err, jc.ErrorIsNil) 405 s.stub.CheckCalls(c, []testing.StubCall{{ 406 "IsMetered", []interface{}{"cs:quantal/metered-1"}, 407 }, { 408 "DefaultPlan", []interface{}{"cs:quantal/metered-1"}, 409 }, { 410 "Authorize", []interface{}{metricRegistrationPost{ 411 ModelUUID: "model uuid", 412 CharmURL: "cs:quantal/metered-1", 413 ApplicationName: "application name", 414 PlanURL: "thisplan", 415 IncreaseBudget: 100, 416 }}, 417 }, { 418 "SetMetricCredentials", []interface{}{ 419 "application name", 420 authorization, 421 }, 422 }}) 423 } 424 425 func (s *registrationSuite) TestMeteredCharmNoDefaultPlan(c *gc.C) { 426 s.stub.SetErrors(nil, errors.NotFoundf("default plan")) 427 s.register = &RegisterMeteredCharm{ 428 IncreaseBudget: 100, 429 PlanURL: s.server.URL} 430 client := httpbakery.NewClient() 431 d := DeploymentInfo{ 432 CharmID: charmstore.CharmID{ 433 URL: charm.MustParseURL("cs:quantal/metered-1"), 434 }, 435 ApplicationName: "application name", 436 ModelUUID: "model uuid", 437 CharmInfo: &apicharms.CharmInfo{ 438 Metrics: &charm.Metrics{ 439 Plan: &charm.Plan{Required: true}, 440 }, 441 }, 442 } 443 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 444 c.Assert(err, gc.ErrorMatches, `cs:quantal/metered-1 has no default plan. Try "juju deploy --plan <plan-name> with one of thisplan, thisotherplan"`) 445 s.stub.CheckCalls(c, []testing.StubCall{{ 446 "IsMetered", []interface{}{"cs:quantal/metered-1"}, 447 }, { 448 "DefaultPlan", []interface{}{"cs:quantal/metered-1"}, 449 }, { 450 "ListPlans", []interface{}{"cs:quantal/metered-1"}, 451 }}) 452 } 453 454 func (s *registrationSuite) TestMeteredCharmNoAvailablePlan(c *gc.C) { 455 s.stub.SetErrors(nil, errors.NotFoundf("default plan")) 456 s.handler.availablePlans = []availablePlanURL{} 457 s.register = &RegisterMeteredCharm{ 458 IncreaseBudget: 100, 459 PlanURL: s.server.URL} 460 client := httpbakery.NewClient() 461 d := DeploymentInfo{ 462 CharmID: charmstore.CharmID{ 463 URL: charm.MustParseURL("cs:quantal/metered-1"), 464 }, 465 ApplicationName: "application name", 466 ModelUUID: "model uuid", 467 CharmInfo: &apicharms.CharmInfo{ 468 Metrics: &charm.Metrics{ 469 Plan: &charm.Plan{Required: true}, 470 }, 471 }, 472 } 473 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 474 c.Assert(err, gc.ErrorMatches, `no plans available for cs:quantal/metered-1.`) 475 s.stub.CheckCalls(c, []testing.StubCall{{ 476 "IsMetered", []interface{}{"cs:quantal/metered-1"}, 477 }, { 478 "DefaultPlan", []interface{}{"cs:quantal/metered-1"}, 479 }, { 480 "ListPlans", []interface{}{"cs:quantal/metered-1"}, 481 }}) 482 } 483 484 func (s *registrationSuite) TestMeteredCharmFailToQueryDefaultCharm(c *gc.C) { 485 s.stub.SetErrors(nil, errors.New("something failed")) 486 s.register = &RegisterMeteredCharm{ 487 IncreaseBudget: 100, 488 PlanURL: s.server.URL} 489 client := httpbakery.NewClient() 490 d := DeploymentInfo{ 491 CharmID: charmstore.CharmID{ 492 URL: charm.MustParseURL("cs:quantal/metered-1"), 493 }, 494 ApplicationName: "application name", 495 ModelUUID: "model uuid", 496 CharmInfo: &apicharms.CharmInfo{ 497 Metrics: &charm.Metrics{ 498 Plan: &charm.Plan{Required: true}, 499 }, 500 }, 501 } 502 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 503 c.Assert(err, gc.ErrorMatches, `failed to query default plan:.*`) 504 s.stub.CheckCalls(c, []testing.StubCall{{ 505 "IsMetered", []interface{}{"cs:quantal/metered-1"}, 506 }, { 507 "DefaultPlan", []interface{}{"cs:quantal/metered-1"}, 508 }}) 509 } 510 511 func (s *registrationSuite) TestUnmeteredCharm(c *gc.C) { 512 client := httpbakery.NewClient() 513 d := DeploymentInfo{ 514 CharmID: charmstore.CharmID{ 515 URL: charm.MustParseURL("cs:quantal/unmetered-1"), 516 }, 517 ApplicationName: "application name", 518 ModelUUID: "model uuid", 519 CharmInfo: &apicharms.CharmInfo{ 520 Metrics: &charm.Metrics{ 521 Plan: &charm.Plan{Required: true}, 522 }, 523 }, 524 } 525 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 526 c.Assert(err, jc.ErrorIsNil) 527 s.stub.CheckCalls(c, []testing.StubCall{{ 528 "IsMetered", []interface{}{"cs:quantal/unmetered-1"}, 529 }}) 530 s.stub.ResetCalls() 531 err = s.register.RunPost(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d, nil) 532 c.Assert(err, jc.ErrorIsNil) 533 s.stub.CheckCalls(c, []testing.StubCall{}) 534 } 535 536 func (s *registrationSuite) TestFailedAuth(c *gc.C) { 537 s.stub.SetErrors(nil, errors.Errorf("could not authorize")) 538 client := httpbakery.NewClient() 539 d := DeploymentInfo{ 540 CharmID: charmstore.CharmID{ 541 URL: charm.MustParseURL("cs:quantal/metered-1"), 542 }, 543 ApplicationName: "application name", 544 ModelUUID: "model uuid", 545 CharmInfo: &apicharms.CharmInfo{ 546 Metrics: &charm.Metrics{ 547 Plan: &charm.Plan{Required: true}, 548 }, 549 }, 550 } 551 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 552 c.Assert(err, gc.ErrorMatches, `authorization failed:.*`) 553 authorization, err := json.Marshal([]byte("hello registration")) 554 authorization = append(authorization, byte(0xa)) 555 c.Assert(err, jc.ErrorIsNil) 556 s.stub.CheckCalls(c, []testing.StubCall{{ 557 "IsMetered", []interface{}{"cs:quantal/metered-1"}, 558 }, { 559 "Authorize", []interface{}{metricRegistrationPost{ 560 ModelUUID: "model uuid", 561 CharmURL: "cs:quantal/metered-1", 562 ApplicationName: "application name", 563 PlanURL: "someplan", 564 IncreaseBudget: 100, 565 }}, 566 }}) 567 } 568 569 func (s *registrationSuite) TestPlanArgumentPlanRequiredInteraction(c *gc.C) { 570 tests := []struct { 571 about string 572 planArgument string 573 planRequired bool 574 noDefaultPlan bool 575 apiCalls []string 576 err string 577 }{{ 578 about: "deploy with --plan, required false", 579 planArgument: "plan", 580 planRequired: false, 581 apiCalls: []string{"IsMetered", "Authorize"}, 582 err: "", 583 }, { 584 about: "deploy with --plan, required true", 585 planArgument: "plan", 586 planRequired: true, 587 apiCalls: []string{"IsMetered", "Authorize"}, 588 err: "", 589 }, { 590 about: "deploy without --plan, required false with default plan", 591 planRequired: false, 592 apiCalls: []string{"IsMetered"}, 593 err: "", 594 }, { 595 about: "deploy without --plan, required true with default plan", 596 planRequired: true, 597 apiCalls: []string{"IsMetered", "DefaultPlan", "Authorize"}, 598 err: "", 599 }, { 600 about: "deploy without --plan, required false with no default plan", 601 planRequired: false, 602 noDefaultPlan: true, 603 apiCalls: []string{"IsMetered"}, 604 err: "", 605 }, { 606 about: "deploy without --plan, required true with no default plan", 607 planRequired: true, 608 noDefaultPlan: true, 609 apiCalls: []string{"IsMetered", "DefaultPlan", "ListPlans"}, 610 err: `cs:quantal/metered-1 has no default plan. Try "juju deploy --plan <plan-name> with one of thisplan, thisotherplan"`, 611 }, 612 } 613 for i, test := range tests { 614 s.stub.ResetCalls() 615 c.Logf("running test %d: %s", i, test.about) 616 if test.noDefaultPlan { 617 s.stub.SetErrors(nil, errors.NotFoundf("default plan")) 618 } else { 619 s.stub.SetErrors(nil) 620 } 621 s.register = &RegisterMeteredCharm{ 622 Plan: test.planArgument, 623 IncreaseBudget: 100, 624 PlanURL: s.server.URL, 625 } 626 client := httpbakery.NewClient() 627 d := DeploymentInfo{ 628 CharmID: charmstore.CharmID{ 629 URL: charm.MustParseURL("cs:quantal/metered-1"), 630 }, 631 ApplicationName: "application name", 632 ModelUUID: "model uuid", 633 CharmInfo: &apicharms.CharmInfo{ 634 Metrics: &charm.Metrics{ 635 Plan: &charm.Plan{Required: test.planRequired}, 636 }, 637 }, 638 } 639 640 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 641 if test.err != "" { 642 c.Assert(err, gc.ErrorMatches, test.err) 643 } else { 644 c.Assert(err, jc.ErrorIsNil) 645 } 646 647 s.stub.CheckCallNames(c, test.apiCalls...) 648 } 649 } 650 651 type availablePlanURL struct { 652 URL string `json:"url"` 653 } 654 655 type testMetricsRegistrationHandler struct { 656 *testing.Stub 657 availablePlans []availablePlanURL 658 } 659 660 type respErr struct { 661 Error string `json:"error"` 662 } 663 664 // ServeHTTP implements http.Handler. 665 func (c *testMetricsRegistrationHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 666 if req.Method == "POST" { 667 var registrationPost metricRegistrationPost 668 decoder := json.NewDecoder(req.Body) 669 err := decoder.Decode(®istrationPost) 670 if err != nil { 671 http.Error(w, "bad request", http.StatusBadRequest) 672 return 673 } 674 c.AddCall("Authorize", registrationPost) 675 rErr := c.NextErr() 676 if rErr != nil { 677 w.WriteHeader(http.StatusInternalServerError) 678 err = json.NewEncoder(w).Encode(respErr{Error: rErr.Error()}) 679 if err != nil { 680 panic(err) 681 } 682 return 683 } 684 err = json.NewEncoder(w).Encode([]byte("hello registration")) 685 if err != nil { 686 panic(err) 687 } 688 } else if req.Method == "GET" { 689 if req.URL.Path == "/default" { 690 cURL := req.URL.Query().Get("charm-url") 691 c.AddCall("DefaultPlan", cURL) 692 rErr := c.NextErr() 693 if rErr != nil { 694 if errors.IsNotFound(rErr) { 695 http.Error(w, rErr.Error(), http.StatusNotFound) 696 return 697 } 698 http.Error(w, rErr.Error(), http.StatusInternalServerError) 699 return 700 } 701 result := struct { 702 URL string `json:"url"` 703 }{"thisplan"} 704 err := json.NewEncoder(w).Encode(result) 705 if err != nil { 706 panic(err) 707 } 708 return 709 } 710 cURL := req.URL.Query().Get("charm-url") 711 c.AddCall("ListPlans", cURL) 712 rErr := c.NextErr() 713 if rErr != nil { 714 http.Error(w, rErr.Error(), http.StatusInternalServerError) 715 return 716 } 717 err := json.NewEncoder(w).Encode(c.availablePlans) 718 if err != nil { 719 panic(err) 720 } 721 } else { 722 http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 723 return 724 } 725 } 726 727 var _ = gc.Suite(&noPlanRegistrationSuite{}) 728 729 type noPlanRegistrationSuite struct { 730 testing.CleanupSuite 731 stub *testing.Stub 732 handler *testMetricsRegistrationHandler 733 server *httptest.Server 734 register DeployStep 735 ctx *cmd.Context 736 } 737 738 func (s *noPlanRegistrationSuite) SetUpTest(c *gc.C) { 739 s.CleanupSuite.SetUpTest(c) 740 s.stub = &testing.Stub{} 741 s.handler = &testMetricsRegistrationHandler{Stub: s.stub} 742 s.server = httptest.NewServer(s.handler) 743 s.register = &RegisterMeteredCharm{ 744 Plan: "", 745 PlanURL: s.server.URL, 746 IncreaseBudget: 100, 747 } 748 s.ctx = cmdtesting.Context(c) 749 } 750 751 func (s *noPlanRegistrationSuite) TearDownTest(c *gc.C) { 752 s.CleanupSuite.TearDownTest(c) 753 s.server.Close() 754 } 755 func (s *noPlanRegistrationSuite) TestOptionalPlanMeteredCharm(c *gc.C) { 756 client := httpbakery.NewClient() 757 d := DeploymentInfo{ 758 CharmID: charmstore.CharmID{ 759 URL: charm.MustParseURL("cs:quantal/metered-1"), 760 }, 761 ApplicationName: "application name", 762 ModelUUID: "model uuid", 763 CharmInfo: &apicharms.CharmInfo{ 764 Metrics: &charm.Metrics{ 765 Plan: &charm.Plan{Required: false}, 766 }, 767 }, 768 } 769 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 770 c.Assert(err, jc.ErrorIsNil) 771 err = s.register.RunPost(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d, nil) 772 c.Assert(err, jc.ErrorIsNil) 773 s.stub.CheckCalls(c, []testing.StubCall{{ 774 "IsMetered", []interface{}{"cs:quantal/metered-1"}, 775 }}) 776 } 777 778 func (s *noPlanRegistrationSuite) TestPlanNotSpecifiedCharm(c *gc.C) { 779 client := httpbakery.NewClient() 780 d := DeploymentInfo{ 781 CharmID: charmstore.CharmID{ 782 URL: charm.MustParseURL("cs:quantal/metered-1"), 783 }, 784 ApplicationName: "application name", 785 ModelUUID: "model uuid", 786 CharmInfo: &apicharms.CharmInfo{ 787 Metrics: &charm.Metrics{ 788 Plan: nil, 789 }, 790 }, 791 } 792 err := s.register.RunPre(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d) 793 c.Assert(err, jc.ErrorIsNil) 794 err = s.register.RunPost(&mockMeteredDeployAPI{Stub: s.stub}, client, s.ctx, d, nil) 795 c.Assert(err, jc.ErrorIsNil) 796 s.stub.CheckCalls(c, []testing.StubCall{{ 797 "IsMetered", []interface{}{"cs:quantal/metered-1"}, 798 }}) 799 } 800 801 type mockMeteredDeployAPI struct { 802 MeteredDeployAPI 803 *testing.Stub 804 } 805 806 func (m *mockMeteredDeployAPI) IsMetered(charmURL string) (bool, error) { 807 m.AddCall("IsMetered", charmURL) 808 if charmURL == "cs:quantal/metered-1" || charmURL == "local:quantal/metered-1" { 809 return true, m.NextErr() 810 } 811 return false, m.NextErr() 812 813 } 814 func (m *mockMeteredDeployAPI) SetMetricCredentials(application string, credentials []byte) error { 815 m.AddCall("SetMetricCredentials", application, credentials) 816 return m.NextErr() 817 }