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(&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{
    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(&registrationPost)
   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  }