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