github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/api/controller/crossmodelrelations/crossmodelrelations_test.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package crossmodelrelations_test
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  
    11  	"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery"
    12  	"github.com/juju/clock"
    13  	"github.com/juju/errors"
    14  	jc "github.com/juju/testing/checkers"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/macaroon.v2"
    17  
    18  	"github.com/juju/juju/api/base"
    19  	"github.com/juju/juju/api/base/testing"
    20  	"github.com/juju/juju/api/controller/crossmodelrelations"
    21  	apitesting "github.com/juju/juju/api/testing"
    22  	"github.com/juju/juju/rpc/params"
    23  	coretesting "github.com/juju/juju/testing"
    24  )
    25  
    26  var _ = gc.Suite(&CrossModelRelationsSuite{})
    27  
    28  type CrossModelRelationsSuite struct {
    29  	coretesting.BaseSuite
    30  
    31  	cache *crossmodelrelations.MacaroonCache
    32  }
    33  
    34  func (s *CrossModelRelationsSuite) SetUpTest(c *gc.C) {
    35  	s.cache = crossmodelrelations.NewMacaroonCache(clock.WallClock)
    36  	s.BaseSuite.SetUpTest(c)
    37  }
    38  
    39  func (s *CrossModelRelationsSuite) TestNewClient(c *gc.C) {
    40  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
    41  		return nil
    42  	})
    43  	client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache)
    44  	c.Assert(client, gc.NotNil)
    45  }
    46  
    47  type mockDischargeAcquirer struct {
    48  	base.MacaroonDischarger
    49  }
    50  
    51  func (m *mockDischargeAcquirer) DischargeAll(ctx context.Context, b *bakery.Macaroon) (macaroon.Slice, error) {
    52  	if !bytes.Equal(b.M().Caveats()[0].Id, []byte("third party caveat")) {
    53  		return nil, errors.New("permission denied")
    54  	}
    55  	mac, err := apitesting.NewMacaroon("discharge mac")
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	return macaroon.Slice{mac}, nil
    60  }
    61  
    62  func (s *CrossModelRelationsSuite) newDischargeMacaroon(c *gc.C) *macaroon.Macaroon {
    63  	mac, err := apitesting.NewMacaroon("id")
    64  	c.Assert(err, jc.ErrorIsNil)
    65  	err = mac.AddThirdPartyCaveat(nil, []byte("third party caveat"), "third party location")
    66  	c.Assert(err, jc.ErrorIsNil)
    67  	return mac
    68  }
    69  
    70  func (s *CrossModelRelationsSuite) fillResponse(c *gc.C, resp interface{}, value interface{}) {
    71  	b, err := json.Marshal(value)
    72  	c.Assert(err, jc.ErrorIsNil)
    73  	err = json.Unmarshal(b, resp)
    74  	c.Assert(err, jc.ErrorIsNil)
    75  }
    76  
    77  func (s *CrossModelRelationsSuite) TestPublishRelationChange(c *gc.C) {
    78  	var callCount int
    79  	mac, err := apitesting.NewMacaroon("id")
    80  	c.Assert(err, jc.ErrorIsNil)
    81  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
    82  		c.Check(objType, gc.Equals, "CrossModelRelations")
    83  		c.Check(version, gc.Equals, 0)
    84  		c.Check(id, gc.Equals, "")
    85  		c.Check(request, gc.Equals, "PublishRelationChanges")
    86  		c.Check(arg, gc.DeepEquals, params.RemoteRelationsChanges{
    87  			Changes: []params.RemoteRelationChangeEvent{{
    88  				RelationToken: "token",
    89  				DepartedUnits: []int{1}, Macaroons: macaroon.Slice{mac},
    90  				BakeryVersion: bakery.LatestVersion,
    91  			}},
    92  		})
    93  		c.Assert(result, gc.FitsTypeOf, &params.ErrorResults{})
    94  		*(result.(*params.ErrorResults)) = params.ErrorResults{
    95  			Results: []params.ErrorResult{{
    96  				Error: &params.Error{Message: "FAIL"},
    97  			}},
    98  		}
    99  		callCount++
   100  		return nil
   101  	})
   102  	client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache)
   103  	err = client.PublishRelationChange(params.RemoteRelationChangeEvent{
   104  		RelationToken: "token",
   105  		DepartedUnits: []int{1},
   106  		Macaroons:     macaroon.Slice{mac},
   107  		BakeryVersion: bakery.LatestVersion,
   108  	})
   109  	c.Check(err, gc.ErrorMatches, "FAIL")
   110  	// Call again with a different macaroon but the first one will be
   111  	// cached and override the passed in macaroon.
   112  	different, err := apitesting.NewMacaroon("different")
   113  	c.Assert(err, jc.ErrorIsNil)
   114  	s.cache.Upsert("token", macaroon.Slice{mac})
   115  	err = client.PublishRelationChange(params.RemoteRelationChangeEvent{
   116  		RelationToken: "token",
   117  		DepartedUnits: []int{1},
   118  		Macaroons:     macaroon.Slice{different},
   119  		BakeryVersion: bakery.LatestVersion,
   120  	})
   121  	c.Check(err, gc.ErrorMatches, "FAIL")
   122  	c.Check(callCount, gc.Equals, 2)
   123  }
   124  
   125  func (s *CrossModelRelationsSuite) TestPublishRelationChangeDischargeRequired(c *gc.C) {
   126  	var (
   127  		callCount    int
   128  		dischargeMac macaroon.Slice
   129  	)
   130  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   131  		var resultErr *params.Error
   132  		if callCount == 0 {
   133  			mac := s.newDischargeMacaroon(c)
   134  			resultErr = &params.Error{
   135  				Code: params.CodeDischargeRequired,
   136  				Info: params.DischargeRequiredErrorInfo{
   137  					Macaroon: mac,
   138  				}.AsMap(),
   139  			}
   140  		}
   141  		argParam := arg.(params.RemoteRelationsChanges)
   142  		dischargeMac = argParam.Changes[0].Macaroons
   143  		resp := params.ErrorResults{
   144  			Results: []params.ErrorResult{{Error: resultErr}},
   145  		}
   146  		s.fillResponse(c, result, resp)
   147  		callCount++
   148  		return nil
   149  	})
   150  	acquirer := &mockDischargeAcquirer{}
   151  	callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer)
   152  	client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache)
   153  	err := client.PublishRelationChange(params.RemoteRelationChangeEvent{
   154  		RelationToken: "token",
   155  		DepartedUnits: []int{1},
   156  	})
   157  	c.Check(callCount, gc.Equals, 2)
   158  	c.Check(err, jc.ErrorIsNil)
   159  	c.Check(dischargeMac, gc.HasLen, 1)
   160  	c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac"))
   161  	// Macaroon has been cached.
   162  	ms, ok := s.cache.Get("token")
   163  	c.Assert(ok, jc.IsTrue)
   164  	apitesting.MacaroonEquals(c, ms[0], dischargeMac[0])
   165  }
   166  
   167  func (s *CrossModelRelationsSuite) TestRegisterRemoteRelations(c *gc.C) {
   168  	var callCount int
   169  	mac, err := apitesting.NewMacaroon("id")
   170  	c.Assert(err, jc.ErrorIsNil)
   171  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   172  		c.Check(objType, gc.Equals, "CrossModelRelations")
   173  		c.Check(version, gc.Equals, 0)
   174  		c.Check(id, gc.Equals, "")
   175  		c.Check(request, gc.Equals, "RegisterRemoteRelations")
   176  		c.Check(arg, gc.DeepEquals, params.RegisterRemoteRelationArgs{
   177  			Relations: []params.RegisterRemoteRelationArg{{
   178  				RelationToken: "token",
   179  				OfferUUID:     "offer-uuid",
   180  				Macaroons:     macaroon.Slice{mac},
   181  				BakeryVersion: bakery.LatestVersion,
   182  			}}})
   183  		c.Assert(result, gc.FitsTypeOf, &params.RegisterRemoteRelationResults{})
   184  		*(result.(*params.RegisterRemoteRelationResults)) = params.RegisterRemoteRelationResults{
   185  			Results: []params.RegisterRemoteRelationResult{{
   186  				Error: &params.Error{Message: "FAIL"},
   187  			}},
   188  		}
   189  		callCount++
   190  		return nil
   191  	})
   192  	client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache)
   193  	result, err := client.RegisterRemoteRelations(params.RegisterRemoteRelationArg{
   194  		RelationToken: "token",
   195  		OfferUUID:     "offer-uuid",
   196  		Macaroons:     macaroon.Slice{mac},
   197  		BakeryVersion: bakery.LatestVersion,
   198  	})
   199  	c.Check(err, jc.ErrorIsNil)
   200  	c.Assert(result, gc.HasLen, 1)
   201  	c.Check(result[0].Error, gc.ErrorMatches, "FAIL")
   202  	// Call again with a different macaroon but the first one will be
   203  	// cached and override the passed in macaroon.
   204  	different, err := apitesting.NewMacaroon("different")
   205  	c.Assert(err, jc.ErrorIsNil)
   206  	s.cache.Upsert("token", macaroon.Slice{mac})
   207  	result, err = client.RegisterRemoteRelations(params.RegisterRemoteRelationArg{
   208  		RelationToken: "token",
   209  		OfferUUID:     "offer-uuid",
   210  		Macaroons:     macaroon.Slice{different},
   211  		BakeryVersion: bakery.LatestVersion,
   212  	})
   213  	c.Check(err, jc.ErrorIsNil)
   214  	c.Assert(result, gc.HasLen, 1)
   215  	c.Check(result[0].Error, gc.ErrorMatches, "FAIL")
   216  	c.Check(callCount, gc.Equals, 2)
   217  }
   218  
   219  func (s *CrossModelRelationsSuite) TestRegisterRemoteRelationCount(c *gc.C) {
   220  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   221  		*(result.(*params.RegisterRemoteRelationResults)) = params.RegisterRemoteRelationResults{
   222  			Results: []params.RegisterRemoteRelationResult{
   223  				{Error: &params.Error{Message: "FAIL"}},
   224  				{Error: &params.Error{Message: "FAIL"}},
   225  			},
   226  		}
   227  		return nil
   228  	})
   229  	client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache)
   230  	_, err := client.RegisterRemoteRelations(params.RegisterRemoteRelationArg{})
   231  	c.Check(err, gc.ErrorMatches, `expected 1 result\(s\), got 2`)
   232  }
   233  
   234  func (s *CrossModelRelationsSuite) TestRegisterRemoteRelationDischargeRequired(c *gc.C) {
   235  	var (
   236  		callCount    int
   237  		dischargeMac macaroon.Slice
   238  	)
   239  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   240  		var resultErr *params.Error
   241  		if callCount == 0 {
   242  			mac := s.newDischargeMacaroon(c)
   243  			resultErr = &params.Error{
   244  				Code: params.CodeDischargeRequired,
   245  				Info: params.DischargeRequiredErrorInfo{
   246  					Macaroon: mac,
   247  				}.AsMap(),
   248  			}
   249  		}
   250  		argParam := arg.(params.RegisterRemoteRelationArgs)
   251  		dischargeMac = argParam.Relations[0].Macaroons
   252  		resp := params.RegisterRemoteRelationResults{
   253  			Results: []params.RegisterRemoteRelationResult{{Error: resultErr}},
   254  		}
   255  		s.fillResponse(c, result, resp)
   256  		callCount++
   257  		return nil
   258  	})
   259  	acquirer := &mockDischargeAcquirer{}
   260  	callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer)
   261  	client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache)
   262  	result, err := client.RegisterRemoteRelations(params.RegisterRemoteRelationArg{
   263  		RelationToken: "token",
   264  		OfferUUID:     "offer-uuid"})
   265  	c.Check(err, jc.ErrorIsNil)
   266  	c.Check(callCount, gc.Equals, 2)
   267  	c.Assert(result, gc.HasLen, 1)
   268  	c.Check(result[0].Error, gc.IsNil)
   269  	c.Assert(dischargeMac, gc.HasLen, 1)
   270  	c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac"))
   271  	// Macaroon has been cached.
   272  	ms, ok := s.cache.Get("token")
   273  	c.Assert(ok, jc.IsTrue)
   274  	apitesting.MacaroonEquals(c, ms[0], dischargeMac[0])
   275  }
   276  
   277  func (s *CrossModelRelationsSuite) TestWatchRelationChanges(c *gc.C) {
   278  	remoteRelationToken := "token"
   279  	mac, err := apitesting.NewMacaroon("id")
   280  	c.Assert(err, jc.ErrorIsNil)
   281  	var callCount int
   282  	apiCaller := testing.BestVersionCaller{
   283  		APICallerFunc: testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   284  			c.Check(objType, gc.Equals, "CrossModelRelations")
   285  			c.Check(version, gc.Equals, 2)
   286  			c.Check(id, gc.Equals, "")
   287  			c.Check(arg, jc.DeepEquals, params.RemoteEntityArgs{Args: []params.RemoteEntityArg{{
   288  				Token: remoteRelationToken, Macaroons: macaroon.Slice{mac},
   289  				BakeryVersion: bakery.LatestVersion,
   290  			}}})
   291  			c.Check(request, gc.Equals, "WatchRelationChanges")
   292  			c.Assert(result, gc.FitsTypeOf, &params.RemoteRelationWatchResults{})
   293  			*(result.(*params.RemoteRelationWatchResults)) = params.RemoteRelationWatchResults{
   294  				Results: []params.RemoteRelationWatchResult{{
   295  					Error: &params.Error{Message: "FAIL"},
   296  				}},
   297  			}
   298  			callCount++
   299  			return nil
   300  		}),
   301  		BestVersion: 2,
   302  	}
   303  	client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache)
   304  	_, err = client.WatchRelationChanges(
   305  		remoteRelationToken,
   306  		"app-token",
   307  		macaroon.Slice{mac},
   308  	)
   309  	c.Check(err, gc.ErrorMatches, "FAIL")
   310  	// Call again with a different macaroon but the first one will be
   311  	// cached and override the passed in macaroon.
   312  	different, err := apitesting.NewMacaroon("different")
   313  	c.Assert(err, jc.ErrorIsNil)
   314  	s.cache.Upsert("token", macaroon.Slice{mac})
   315  	_, err = client.WatchRelationChanges(
   316  		remoteRelationToken,
   317  		"app-token",
   318  		macaroon.Slice{different},
   319  	)
   320  	c.Check(err, gc.ErrorMatches, "FAIL")
   321  	c.Check(callCount, gc.Equals, 2)
   322  }
   323  
   324  func (s *CrossModelRelationsSuite) TestWatchRelationChangesDischargeRequired(c *gc.C) {
   325  	var (
   326  		callCount    int
   327  		dischargeMac macaroon.Slice
   328  	)
   329  	apiCaller := testing.BestVersionCaller{
   330  		BestVersion: 2,
   331  		APICallerFunc: testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   332  			var resultErr *params.Error
   333  			c.Logf("called")
   334  			switch callCount {
   335  			case 2, 3: //Watcher Next, Stop
   336  				return nil
   337  			case 0:
   338  				mac := s.newDischargeMacaroon(c)
   339  				resultErr = &params.Error{
   340  					Code: params.CodeDischargeRequired,
   341  					Info: params.DischargeRequiredErrorInfo{
   342  						Macaroon: mac,
   343  					}.AsMap(),
   344  				}
   345  			case 1:
   346  				argParam := arg.(params.RemoteEntityArgs)
   347  				dischargeMac = argParam.Args[0].Macaroons
   348  			}
   349  			resp := params.RemoteRelationWatchResults{
   350  				Results: []params.RemoteRelationWatchResult{{Error: resultErr}},
   351  			}
   352  			s.fillResponse(c, result, resp)
   353  			callCount++
   354  			return nil
   355  		}),
   356  	}
   357  	acquirer := &mockDischargeAcquirer{}
   358  	callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer)
   359  	client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache)
   360  	_, err := client.WatchRelationChanges("token", "app-token", nil)
   361  	c.Check(callCount, gc.Equals, 2)
   362  	c.Check(err, jc.ErrorIsNil)
   363  	c.Assert(dischargeMac, gc.HasLen, 1)
   364  	c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac"))
   365  	// Macaroon has been cached.
   366  	ms, ok := s.cache.Get("token")
   367  	c.Assert(ok, jc.IsTrue)
   368  	apitesting.MacaroonEquals(c, ms[0], dischargeMac[0])
   369  }
   370  
   371  func (s *CrossModelRelationsSuite) TestWatchRelationStatus(c *gc.C) {
   372  	remoteRelationToken := "token"
   373  	mac, err := apitesting.NewMacaroon("id")
   374  	c.Assert(err, jc.ErrorIsNil)
   375  	var callCount int
   376  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   377  		c.Check(objType, gc.Equals, "CrossModelRelations")
   378  		c.Check(version, gc.Equals, 0)
   379  		c.Check(id, gc.Equals, "")
   380  		c.Check(arg, jc.DeepEquals, params.RemoteEntityArgs{Args: []params.RemoteEntityArg{{
   381  			Token: remoteRelationToken, Macaroons: macaroon.Slice{mac},
   382  			BakeryVersion: bakery.LatestVersion,
   383  		}}})
   384  		c.Check(request, gc.Equals, "WatchRelationsSuspendedStatus")
   385  		c.Assert(result, gc.FitsTypeOf, &params.RelationStatusWatchResults{})
   386  		*(result.(*params.RelationStatusWatchResults)) = params.RelationStatusWatchResults{
   387  			Results: []params.RelationLifeSuspendedStatusWatchResult{{
   388  				Error: &params.Error{Message: "FAIL"},
   389  			}},
   390  		}
   391  		callCount++
   392  		return nil
   393  	})
   394  	client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache)
   395  	_, err = client.WatchRelationSuspendedStatus(params.RemoteEntityArg{
   396  		Token:         remoteRelationToken,
   397  		Macaroons:     macaroon.Slice{mac},
   398  		BakeryVersion: bakery.LatestVersion,
   399  	})
   400  	c.Check(err, gc.ErrorMatches, "FAIL")
   401  	// Call again with a different macaroon but the first one will be
   402  	// cached and override the passed in macaroon.
   403  	different, err := apitesting.NewMacaroon("different")
   404  	c.Assert(err, jc.ErrorIsNil)
   405  	s.cache.Upsert("token", macaroon.Slice{mac})
   406  	_, err = client.WatchRelationSuspendedStatus(params.RemoteEntityArg{
   407  		Token:         remoteRelationToken,
   408  		Macaroons:     macaroon.Slice{different},
   409  		BakeryVersion: bakery.LatestVersion,
   410  	})
   411  	c.Check(err, gc.ErrorMatches, "FAIL")
   412  	c.Check(callCount, gc.Equals, 2)
   413  }
   414  
   415  func (s *CrossModelRelationsSuite) TestWatchRelationStatusDischargeRequired(c *gc.C) {
   416  	var (
   417  		callCount    int
   418  		dischargeMac macaroon.Slice
   419  	)
   420  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   421  		var resultErr *params.Error
   422  		switch callCount {
   423  		case 2, 3: //Watcher Next, Stop
   424  			return nil
   425  		case 0:
   426  			mac := s.newDischargeMacaroon(c)
   427  			resultErr = &params.Error{
   428  				Code: params.CodeDischargeRequired,
   429  				Info: params.DischargeRequiredErrorInfo{
   430  					Macaroon: mac,
   431  				}.AsMap(),
   432  			}
   433  		case 1:
   434  			argParam := arg.(params.RemoteEntityArgs)
   435  			dischargeMac = argParam.Args[0].Macaroons
   436  		}
   437  		resp := params.RelationStatusWatchResults{
   438  			Results: []params.RelationLifeSuspendedStatusWatchResult{{Error: resultErr}},
   439  		}
   440  		s.fillResponse(c, result, resp)
   441  		callCount++
   442  		return nil
   443  	})
   444  	acquirer := &mockDischargeAcquirer{}
   445  	callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer)
   446  	client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache)
   447  	_, err := client.WatchRelationSuspendedStatus(params.RemoteEntityArg{Token: "token"})
   448  	c.Check(callCount, gc.Equals, 2)
   449  	c.Check(err, jc.ErrorIsNil)
   450  	c.Assert(dischargeMac, gc.HasLen, 1)
   451  	c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac"))
   452  	// Macaroon has been cached.
   453  	ms, ok := s.cache.Get("token")
   454  	c.Assert(ok, jc.IsTrue)
   455  	apitesting.MacaroonEquals(c, ms[0], dischargeMac[0])
   456  }
   457  
   458  func (s *CrossModelRelationsSuite) TestPublishIngressNetworkChange(c *gc.C) {
   459  	mac, err := apitesting.NewMacaroon("id")
   460  	c.Assert(err, jc.ErrorIsNil)
   461  	var callCount int
   462  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   463  		c.Check(objType, gc.Equals, "CrossModelRelations")
   464  		c.Check(version, gc.Equals, 0)
   465  		c.Check(id, gc.Equals, "")
   466  		c.Check(request, gc.Equals, "PublishIngressNetworkChanges")
   467  		c.Check(arg, gc.DeepEquals, params.IngressNetworksChanges{
   468  			Changes: []params.IngressNetworksChangeEvent{{
   469  				RelationToken: "token",
   470  				Networks:      []string{"1.2.3.4/32"}, Macaroons: macaroon.Slice{mac},
   471  				BakeryVersion: bakery.LatestVersion,
   472  			}},
   473  		})
   474  		c.Assert(result, gc.FitsTypeOf, &params.ErrorResults{})
   475  		*(result.(*params.ErrorResults)) = params.ErrorResults{
   476  			Results: []params.ErrorResult{{
   477  				Error: &params.Error{Message: "FAIL"},
   478  			}},
   479  		}
   480  		callCount++
   481  		return nil
   482  	})
   483  	client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache)
   484  	err = client.PublishIngressNetworkChange(params.IngressNetworksChangeEvent{
   485  		RelationToken: "token",
   486  		Networks:      []string{"1.2.3.4/32"}, Macaroons: macaroon.Slice{mac},
   487  		BakeryVersion: bakery.LatestVersion,
   488  	})
   489  	c.Check(err, gc.ErrorMatches, "FAIL")
   490  	// Call again with a different macaroon but the first one will be
   491  	// cached and override the passed in macaroon.
   492  	different, err := apitesting.NewMacaroon("different")
   493  	c.Assert(err, jc.ErrorIsNil)
   494  	s.cache.Upsert("token", macaroon.Slice{mac})
   495  	err = client.PublishIngressNetworkChange(params.IngressNetworksChangeEvent{
   496  		RelationToken: "token",
   497  		Networks:      []string{"1.2.3.4/32"}, Macaroons: macaroon.Slice{different},
   498  		BakeryVersion: bakery.LatestVersion,
   499  	})
   500  	c.Check(err, gc.ErrorMatches, "FAIL")
   501  	c.Check(callCount, gc.Equals, 2)
   502  }
   503  
   504  func (s *CrossModelRelationsSuite) TestPublishIngressNetworkChangeDischargeRequired(c *gc.C) {
   505  	var (
   506  		callCount    int
   507  		dischargeMac macaroon.Slice
   508  	)
   509  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   510  		var resultErr *params.Error
   511  		if callCount == 0 {
   512  			mac := s.newDischargeMacaroon(c)
   513  			resultErr = &params.Error{
   514  				Code: params.CodeDischargeRequired,
   515  				Info: params.DischargeRequiredErrorInfo{
   516  					Macaroon: mac,
   517  				}.AsMap(),
   518  			}
   519  		}
   520  		argParam := arg.(params.IngressNetworksChanges)
   521  		dischargeMac = argParam.Changes[0].Macaroons
   522  		resp := params.ErrorResults{
   523  			Results: []params.ErrorResult{{Error: resultErr}},
   524  		}
   525  		s.fillResponse(c, result, resp)
   526  		callCount++
   527  		return nil
   528  	})
   529  	acquirer := &mockDischargeAcquirer{}
   530  	callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer)
   531  	client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache)
   532  	err := client.PublishIngressNetworkChange(params.IngressNetworksChangeEvent{
   533  		RelationToken: "token",
   534  		Networks:      []string{"1.2.3.4/32"}})
   535  	c.Check(callCount, gc.Equals, 2)
   536  	c.Check(err, jc.ErrorIsNil)
   537  	c.Assert(dischargeMac, gc.HasLen, 1)
   538  	c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac"))
   539  	// Macaroon has been cached.
   540  	ms, ok := s.cache.Get("token")
   541  	c.Assert(ok, jc.IsTrue)
   542  	apitesting.MacaroonEquals(c, ms[0], dischargeMac[0])
   543  }
   544  
   545  func (s *CrossModelRelationsSuite) TestWatchEgressAddressesForRelation(c *gc.C) {
   546  	var callCount int
   547  	remoteRelationToken := "token"
   548  	mac, err := apitesting.NewMacaroon("id")
   549  	relation := params.RemoteEntityArg{
   550  		Token: remoteRelationToken, Macaroons: macaroon.Slice{mac},
   551  		BakeryVersion: bakery.LatestVersion,
   552  	}
   553  	c.Check(err, jc.ErrorIsNil)
   554  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   555  		c.Check(objType, gc.Equals, "CrossModelRelations")
   556  		c.Check(version, gc.Equals, 0)
   557  		c.Check(id, gc.Equals, "")
   558  		c.Check(request, gc.Equals, "WatchEgressAddressesForRelations")
   559  		c.Check(arg, gc.DeepEquals, params.RemoteEntityArgs{
   560  			Args: []params.RemoteEntityArg{relation}})
   561  		c.Assert(result, gc.FitsTypeOf, &params.StringsWatchResults{})
   562  		*(result.(*params.StringsWatchResults)) = params.StringsWatchResults{
   563  			Results: []params.StringsWatchResult{{
   564  				Error: &params.Error{Message: "FAIL"},
   565  			}},
   566  		}
   567  		callCount++
   568  		return nil
   569  	})
   570  	client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache)
   571  	_, err = client.WatchEgressAddressesForRelation(relation)
   572  	c.Check(err, gc.ErrorMatches, "FAIL")
   573  	// Call again with a different macaroon but the first one will be
   574  	// cached and override the passed in macaroon.
   575  	different, err := apitesting.NewMacaroon("different")
   576  	c.Assert(err, jc.ErrorIsNil)
   577  	s.cache.Upsert("token", macaroon.Slice{mac})
   578  	rel2 := relation
   579  	rel2.Macaroons = macaroon.Slice{different}
   580  	rel2.BakeryVersion = bakery.LatestVersion
   581  	_, err = client.WatchEgressAddressesForRelation(rel2)
   582  	c.Check(err, gc.ErrorMatches, "FAIL")
   583  	c.Check(callCount, gc.Equals, 2)
   584  }
   585  
   586  func (s *CrossModelRelationsSuite) TestWatchEgressAddressesForRelationDischargeRequired(c *gc.C) {
   587  	var (
   588  		callCount    int
   589  		mac          *macaroon.Macaroon
   590  		dischargeMac macaroon.Slice
   591  	)
   592  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   593  		var resultErr *params.Error
   594  		switch callCount {
   595  		case 2, 3: //Watcher Next, Stop
   596  			return nil
   597  		case 0:
   598  			mac = s.newDischargeMacaroon(c)
   599  			resultErr = &params.Error{
   600  				Code: params.CodeDischargeRequired,
   601  				Info: params.DischargeRequiredErrorInfo{
   602  					Macaroon: mac,
   603  				}.AsMap(),
   604  			}
   605  		case 1:
   606  			argParam := arg.(params.RemoteEntityArgs)
   607  			dischargeMac = argParam.Args[0].Macaroons
   608  		}
   609  		resp := params.StringsWatchResults{
   610  			Results: []params.StringsWatchResult{{Error: resultErr}},
   611  		}
   612  		s.fillResponse(c, result, resp)
   613  		callCount++
   614  		return nil
   615  	})
   616  	acquirer := &mockDischargeAcquirer{}
   617  	callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer)
   618  	client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache)
   619  	_, err := client.WatchEgressAddressesForRelation(params.RemoteEntityArg{Token: "token"})
   620  	c.Check(callCount, gc.Equals, 2)
   621  	c.Check(err, jc.ErrorIsNil)
   622  	c.Assert(dischargeMac, gc.HasLen, 1)
   623  	c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac"))
   624  	// Macaroon has been cached.
   625  	ms, ok := s.cache.Get("token")
   626  	c.Assert(ok, jc.IsTrue)
   627  	apitesting.MacaroonEquals(c, ms[0], dischargeMac[0])
   628  }
   629  
   630  func (s *CrossModelRelationsSuite) TestWatchOfferStatus(c *gc.C) {
   631  	offerUUID := "offer-uuid"
   632  	mac, err := apitesting.NewMacaroon("id")
   633  	c.Assert(err, jc.ErrorIsNil)
   634  	var callCount int
   635  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   636  		c.Check(objType, gc.Equals, "CrossModelRelations")
   637  		c.Check(version, gc.Equals, 0)
   638  		c.Check(id, gc.Equals, "")
   639  		c.Check(arg, jc.DeepEquals, params.OfferArgs{Args: []params.OfferArg{{
   640  			OfferUUID: offerUUID, Macaroons: macaroon.Slice{mac},
   641  			BakeryVersion: bakery.LatestVersion,
   642  		}}})
   643  		c.Check(request, gc.Equals, "WatchOfferStatus")
   644  		c.Assert(result, gc.FitsTypeOf, &params.OfferStatusWatchResults{})
   645  		*(result.(*params.OfferStatusWatchResults)) = params.OfferStatusWatchResults{
   646  			Results: []params.OfferStatusWatchResult{{
   647  				Error: &params.Error{Message: "FAIL"},
   648  			}},
   649  		}
   650  		callCount++
   651  		return nil
   652  	})
   653  	client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache)
   654  	_, err = client.WatchOfferStatus(params.OfferArg{
   655  		OfferUUID:     offerUUID,
   656  		Macaroons:     macaroon.Slice{mac},
   657  		BakeryVersion: bakery.LatestVersion,
   658  	})
   659  	c.Check(err, gc.ErrorMatches, "FAIL")
   660  	// Call again with a different macaroon but the first one will be
   661  	// cached and override the passed in macaroon.
   662  	different, err := apitesting.NewMacaroon("different")
   663  	c.Assert(err, jc.ErrorIsNil)
   664  	s.cache.Upsert("offer-uuid", macaroon.Slice{mac})
   665  	_, err = client.WatchOfferStatus(params.OfferArg{
   666  		OfferUUID:     offerUUID,
   667  		Macaroons:     macaroon.Slice{different},
   668  		BakeryVersion: bakery.LatestVersion,
   669  	})
   670  	c.Check(err, gc.ErrorMatches, "FAIL")
   671  	c.Check(callCount, gc.Equals, 2)
   672  }
   673  
   674  func (s *CrossModelRelationsSuite) TestWatchOfferStatusDischargeRequired(c *gc.C) {
   675  	var (
   676  		callCount    int
   677  		dischargeMac macaroon.Slice
   678  	)
   679  	apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   680  		var resultErr *params.Error
   681  		switch callCount {
   682  		case 2, 3: //Watcher Next, Stop
   683  			return nil
   684  		case 0:
   685  			mac := s.newDischargeMacaroon(c)
   686  			resultErr = &params.Error{
   687  				Code: params.CodeDischargeRequired,
   688  				Info: params.DischargeRequiredErrorInfo{
   689  					Macaroon: mac,
   690  				}.AsMap(),
   691  			}
   692  		case 1:
   693  			argParam := arg.(params.OfferArgs)
   694  			dischargeMac = argParam.Args[0].Macaroons
   695  		}
   696  		resp := params.OfferStatusWatchResults{
   697  			Results: []params.OfferStatusWatchResult{{Error: resultErr}},
   698  		}
   699  		s.fillResponse(c, result, resp)
   700  		callCount++
   701  		return nil
   702  	})
   703  	acquirer := &mockDischargeAcquirer{}
   704  	callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer)
   705  	client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache)
   706  	_, err := client.WatchOfferStatus(params.OfferArg{OfferUUID: "offer-uuid"})
   707  	c.Check(callCount, gc.Equals, 2)
   708  	c.Check(err, jc.ErrorIsNil)
   709  	c.Assert(dischargeMac, gc.HasLen, 1)
   710  	c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac"))
   711  	// Macaroon has been cached.
   712  	ms, ok := s.cache.Get("offer-uuid")
   713  	c.Assert(ok, jc.IsTrue)
   714  	apitesting.MacaroonEquals(c, ms[0], dischargeMac[0])
   715  }
   716  
   717  func (s *CrossModelRelationsSuite) TestWatchConsumedSecretsChanges(c *gc.C) {
   718  	appToken := "app-token"
   719  	relToken := "rel-token"
   720  	mac, err := apitesting.NewMacaroon("id")
   721  	c.Assert(err, jc.ErrorIsNil)
   722  	var callCount int
   723  	apiCaller := testing.BestVersionCaller{APICallerFunc: testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   724  		c.Check(objType, gc.Equals, "CrossModelRelations")
   725  		c.Check(version, gc.Equals, 3)
   726  		c.Check(id, gc.Equals, "")
   727  		c.Check(arg, jc.DeepEquals, params.WatchRemoteSecretChangesArgs{Args: []params.WatchRemoteSecretChangesArg{{
   728  			ApplicationToken: appToken, RelationToken: relToken, Macaroons: macaroon.Slice{mac},
   729  			BakeryVersion: bakery.LatestVersion,
   730  		}}})
   731  		c.Check(request, gc.Equals, "WatchConsumedSecretsChanges")
   732  		c.Assert(result, gc.FitsTypeOf, &params.SecretRevisionWatchResults{})
   733  		*(result.(*params.SecretRevisionWatchResults)) = params.SecretRevisionWatchResults{
   734  			Results: []params.SecretRevisionWatchResult{{
   735  				Error: &params.Error{Message: "FAIL"},
   736  			}},
   737  		}
   738  		callCount++
   739  		return nil
   740  	}), BestVersion: 3}
   741  	client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache)
   742  	_, err = client.WatchConsumedSecretsChanges(appToken, relToken, mac)
   743  	c.Check(err, gc.ErrorMatches, "FAIL")
   744  	// Call again with a different macaroon but the first one will be
   745  	// cached and override the passed in macaroon.
   746  	different, err := apitesting.NewMacaroon("different")
   747  	c.Assert(err, jc.ErrorIsNil)
   748  	s.cache.Upsert(relToken, macaroon.Slice{mac})
   749  	_, err = client.WatchConsumedSecretsChanges(appToken, relToken, different)
   750  	c.Check(err, gc.ErrorMatches, "FAIL")
   751  	c.Check(callCount, gc.Equals, 2)
   752  }
   753  
   754  func (s *CrossModelRelationsSuite) TestWatchConsumedSecretsChangesDischargeRequired(c *gc.C) {
   755  	var (
   756  		callCount    int
   757  		dischargeMac macaroon.Slice
   758  	)
   759  	apiCaller := testing.BestVersionCaller{APICallerFunc: testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   760  		var resultErr *params.Error
   761  		switch callCount {
   762  		case 2, 3: //Watcher Next, Stop
   763  			return nil
   764  		case 0:
   765  			mac := s.newDischargeMacaroon(c)
   766  			resultErr = &params.Error{
   767  				Code: params.CodeDischargeRequired,
   768  				Info: params.DischargeRequiredErrorInfo{
   769  					Macaroon: mac,
   770  				}.AsMap(),
   771  			}
   772  		case 1:
   773  			argParam := arg.(params.WatchRemoteSecretChangesArgs)
   774  			dischargeMac = argParam.Args[0].Macaroons
   775  		}
   776  		resp := params.SecretRevisionWatchResults{
   777  			Results: []params.SecretRevisionWatchResult{{Error: resultErr}},
   778  		}
   779  		s.fillResponse(c, result, resp)
   780  		callCount++
   781  		return nil
   782  	}), BestVersion: 3}
   783  	acquirer := &mockDischargeAcquirer{}
   784  	callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer)
   785  	client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache)
   786  	_, err := client.WatchConsumedSecretsChanges("app-token", "rel-token", nil)
   787  	c.Check(callCount, gc.Equals, 2)
   788  	c.Check(err, jc.ErrorIsNil)
   789  	c.Assert(dischargeMac, gc.HasLen, 1)
   790  	c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac"))
   791  	// Macaroon has been cached.
   792  	ms, ok := s.cache.Get("rel-token")
   793  	c.Assert(ok, jc.IsTrue)
   794  	apitesting.MacaroonEquals(c, ms[0], dischargeMac[0])
   795  }