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