gitee.com/mysnapcore/mysnapd@v0.1.0/daemon/api_interfaces_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2021 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package daemon_test
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  	"net/http"
    27  	"net/http/httptest"
    28  	"strings"
    29  
    30  	"gopkg.in/check.v1"
    31  
    32  	"gitee.com/mysnapcore/mysnapd/client"
    33  	"gitee.com/mysnapcore/mysnapd/daemon"
    34  	"gitee.com/mysnapcore/mysnapd/interfaces"
    35  	"gitee.com/mysnapcore/mysnapd/interfaces/builtin"
    36  	"gitee.com/mysnapcore/mysnapd/interfaces/ifacetest"
    37  	"gitee.com/mysnapcore/mysnapd/overlord/ifacestate"
    38  	"gitee.com/mysnapcore/mysnapd/overlord/state"
    39  )
    40  
    41  var _ = check.Suite(&interfacesSuite{})
    42  
    43  type interfacesSuite struct {
    44  	apiBaseSuite
    45  }
    46  
    47  func (s *interfacesSuite) SetUpTest(c *check.C) {
    48  	s.apiBaseSuite.SetUpTest(c)
    49  
    50  	s.expectWriteAccess(daemon.AuthenticatedAccess{Polkit: "io.snapcraft.snapd.manage-interfaces"})
    51  }
    52  
    53  func mockIface(c *check.C, d *daemon.Daemon, iface interfaces.Interface) {
    54  	err := d.Overlord().InterfaceManager().Repository().AddInterface(iface)
    55  	c.Assert(err, check.IsNil)
    56  }
    57  
    58  // inverseCaseMapper implements SnapMapper to use lower case internally and upper case externally.
    59  type inverseCaseMapper struct {
    60  	ifacestate.IdentityMapper // Embed the identity mapper to reuse empty state mapping functions.
    61  }
    62  
    63  func (m *inverseCaseMapper) RemapSnapFromRequest(snapName string) string {
    64  	return strings.ToLower(snapName)
    65  }
    66  
    67  func (m *inverseCaseMapper) RemapSnapToResponse(snapName string) string {
    68  	return strings.ToUpper(snapName)
    69  }
    70  
    71  func (m *inverseCaseMapper) SystemSnapName() string {
    72  	return "core"
    73  }
    74  
    75  // Tests for POST /v2/interfaces
    76  
    77  const (
    78  	consumerYaml = `
    79  name: consumer
    80  version: 1
    81  apps:
    82   app:
    83  plugs:
    84   plug:
    85    interface: test
    86    key: value
    87    label: label
    88  `
    89  
    90  	producerYaml = `
    91  name: producer
    92  version: 1
    93  apps:
    94   app:
    95  slots:
    96   slot:
    97    interface: test
    98    key: value
    99    label: label
   100  `
   101  
   102  	coreProducerYaml = `
   103  name: core
   104  version: 1
   105  slots:
   106   slot:
   107    interface: test
   108    key: value
   109    label: label
   110  `
   111  
   112  	differentProducerYaml = `
   113  name: producer
   114  version: 1
   115  apps:
   116   app:
   117  slots:
   118   slot:
   119    interface: different
   120    key: value
   121    label: label
   122  `
   123  )
   124  
   125  func (s *interfacesSuite) TestConnectPlugSuccess(c *check.C) {
   126  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   127  	defer restore()
   128  	// Install an inverse case mapper to exercise the interface mapping at the same time.
   129  	restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
   130  	defer restore()
   131  
   132  	d := s.daemon(c)
   133  
   134  	s.mockSnap(c, consumerYaml)
   135  	s.mockSnap(c, producerYaml)
   136  
   137  	d.Overlord().Loop()
   138  	defer d.Overlord().Stop()
   139  
   140  	action := &client.InterfaceAction{
   141  		Action: "connect",
   142  		Plugs:  []client.Plug{{Snap: "CONSUMER", Name: "plug"}},
   143  		Slots:  []client.Slot{{Snap: "PRODUCER", Name: "slot"}},
   144  	}
   145  	text, err := json.Marshal(action)
   146  	c.Assert(err, check.IsNil)
   147  	buf := bytes.NewBuffer(text)
   148  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   149  	c.Assert(err, check.IsNil)
   150  	rec := httptest.NewRecorder()
   151  	s.req(c, req, nil).ServeHTTP(rec, req)
   152  	c.Check(rec.Code, check.Equals, 202)
   153  	var body map[string]interface{}
   154  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   155  	c.Check(err, check.IsNil)
   156  	id := body["change"].(string)
   157  
   158  	st := d.Overlord().State()
   159  	st.Lock()
   160  	chg := st.Change(id)
   161  	st.Unlock()
   162  	c.Assert(chg, check.NotNil)
   163  
   164  	<-chg.Ready()
   165  
   166  	st.Lock()
   167  	err = chg.Err()
   168  	st.Unlock()
   169  	c.Assert(err, check.IsNil)
   170  
   171  	repo := d.Overlord().InterfaceManager().Repository()
   172  	ifaces := repo.Interfaces()
   173  	c.Assert(ifaces.Connections, check.HasLen, 1)
   174  	c.Check(ifaces.Connections, check.DeepEquals, []*interfaces.ConnRef{{
   175  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   176  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
   177  	}})
   178  }
   179  
   180  func (s *interfacesSuite) TestConnectPlugFailureInterfaceMismatch(c *check.C) {
   181  	d := s.daemon(c)
   182  
   183  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
   184  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "different"})
   185  	s.mockSnap(c, consumerYaml)
   186  	s.mockSnap(c, differentProducerYaml)
   187  
   188  	action := &client.InterfaceAction{
   189  		Action: "connect",
   190  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   191  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   192  	}
   193  	text, err := json.Marshal(action)
   194  	c.Assert(err, check.IsNil)
   195  	buf := bytes.NewBuffer(text)
   196  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   197  	c.Assert(err, check.IsNil)
   198  	rec := httptest.NewRecorder()
   199  	s.req(c, req, nil).ServeHTTP(rec, req)
   200  	c.Check(rec.Code, check.Equals, 400)
   201  	var body map[string]interface{}
   202  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   203  	c.Check(err, check.IsNil)
   204  	c.Check(body, check.DeepEquals, map[string]interface{}{
   205  		"result": map[string]interface{}{
   206  			"message": "cannot connect consumer:plug (\"test\" interface) to producer:slot (\"different\" interface)",
   207  		},
   208  		"status":      "Bad Request",
   209  		"status-code": 400.0,
   210  		"type":        "error",
   211  	})
   212  	repo := d.Overlord().InterfaceManager().Repository()
   213  	ifaces := repo.Interfaces()
   214  	c.Assert(ifaces.Connections, check.HasLen, 0)
   215  }
   216  
   217  func (s *interfacesSuite) TestConnectPlugFailureNoSuchPlug(c *check.C) {
   218  	d := s.daemon(c)
   219  
   220  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
   221  	// there is no consumer, no plug defined
   222  	s.mockSnap(c, producerYaml)
   223  	s.mockSnap(c, consumerYaml)
   224  
   225  	action := &client.InterfaceAction{
   226  		Action: "connect",
   227  		Plugs:  []client.Plug{{Snap: "consumer", Name: "missingplug"}},
   228  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   229  	}
   230  	text, err := json.Marshal(action)
   231  	c.Assert(err, check.IsNil)
   232  	buf := bytes.NewBuffer(text)
   233  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   234  	c.Assert(err, check.IsNil)
   235  	rec := httptest.NewRecorder()
   236  	s.req(c, req, nil).ServeHTTP(rec, req)
   237  	c.Check(rec.Code, check.Equals, 400)
   238  
   239  	var body map[string]interface{}
   240  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   241  	c.Check(err, check.IsNil)
   242  	c.Check(body, check.DeepEquals, map[string]interface{}{
   243  		"result": map[string]interface{}{
   244  			"message": "snap \"consumer\" has no plug named \"missingplug\"",
   245  		},
   246  		"status":      "Bad Request",
   247  		"status-code": 400.0,
   248  		"type":        "error",
   249  	})
   250  
   251  	repo := d.Overlord().InterfaceManager().Repository()
   252  	ifaces := repo.Interfaces()
   253  	c.Assert(ifaces.Connections, check.HasLen, 0)
   254  }
   255  
   256  func (s *interfacesSuite) TestConnectAlreadyConnected(c *check.C) {
   257  	d := s.daemon(c)
   258  
   259  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
   260  	// there is no consumer, no plug defined
   261  	s.mockSnap(c, producerYaml)
   262  	s.mockSnap(c, consumerYaml)
   263  
   264  	repo := d.Overlord().InterfaceManager().Repository()
   265  	connRef := &interfaces.ConnRef{
   266  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   267  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
   268  	}
   269  
   270  	d.Overlord().Loop()
   271  	defer d.Overlord().Stop()
   272  
   273  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
   274  	c.Assert(err, check.IsNil)
   275  	conns := map[string]interface{}{
   276  		"consumer:plug producer:slot": map[string]interface{}{
   277  			"auto": false,
   278  		},
   279  	}
   280  	st := d.Overlord().State()
   281  	st.Lock()
   282  	st.Set("conns", conns)
   283  	st.Unlock()
   284  
   285  	action := &client.InterfaceAction{
   286  		Action: "connect",
   287  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   288  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   289  	}
   290  	text, err := json.Marshal(action)
   291  	c.Assert(err, check.IsNil)
   292  	buf := bytes.NewBuffer(text)
   293  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   294  	c.Assert(err, check.IsNil)
   295  	rec := httptest.NewRecorder()
   296  	s.req(c, req, nil).ServeHTTP(rec, req)
   297  	c.Check(rec.Code, check.Equals, 202)
   298  	var body map[string]interface{}
   299  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   300  	c.Check(err, check.IsNil)
   301  	id := body["change"].(string)
   302  
   303  	st.Lock()
   304  	chg := st.Change(id)
   305  	c.Assert(chg.Tasks(), check.HasLen, 0)
   306  	c.Assert(chg.Status(), check.Equals, state.DoneStatus)
   307  	st.Unlock()
   308  }
   309  
   310  func (s *interfacesSuite) TestConnectPlugFailureNoSuchSlot(c *check.C) {
   311  	d := s.daemon(c)
   312  
   313  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
   314  	s.mockSnap(c, consumerYaml)
   315  	s.mockSnap(c, producerYaml)
   316  	// there is no producer, no slot defined
   317  
   318  	action := &client.InterfaceAction{
   319  		Action: "connect",
   320  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   321  		Slots:  []client.Slot{{Snap: "producer", Name: "missingslot"}},
   322  	}
   323  	text, err := json.Marshal(action)
   324  	c.Assert(err, check.IsNil)
   325  	buf := bytes.NewBuffer(text)
   326  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   327  	c.Assert(err, check.IsNil)
   328  	rec := httptest.NewRecorder()
   329  	s.req(c, req, nil).ServeHTTP(rec, req)
   330  	c.Check(rec.Code, check.Equals, 400)
   331  
   332  	var body map[string]interface{}
   333  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   334  	c.Check(err, check.IsNil)
   335  	c.Check(body, check.DeepEquals, map[string]interface{}{
   336  		"result": map[string]interface{}{
   337  			"message": "snap \"producer\" has no slot named \"missingslot\"",
   338  		},
   339  		"status":      "Bad Request",
   340  		"status-code": 400.0,
   341  		"type":        "error",
   342  	})
   343  
   344  	repo := d.Overlord().InterfaceManager().Repository()
   345  	ifaces := repo.Interfaces()
   346  	c.Assert(ifaces.Connections, check.HasLen, 0)
   347  }
   348  
   349  func (s *interfacesSuite) testConnectFailureNoSnap(c *check.C, installedSnap string) {
   350  	// validity, either consumer or producer needs to be enabled
   351  	consumer := installedSnap == "consumer"
   352  	producer := installedSnap == "producer"
   353  	c.Assert(consumer || producer, check.Equals, true, check.Commentf("installed snap must be consumer or producer"))
   354  
   355  	d := s.daemon(c)
   356  
   357  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
   358  
   359  	if consumer {
   360  		s.mockSnap(c, consumerYaml)
   361  	}
   362  	if producer {
   363  		s.mockSnap(c, producerYaml)
   364  	}
   365  
   366  	action := &client.InterfaceAction{
   367  		Action: "connect",
   368  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   369  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   370  	}
   371  	text, err := json.Marshal(action)
   372  	c.Assert(err, check.IsNil)
   373  	buf := bytes.NewBuffer(text)
   374  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   375  	c.Assert(err, check.IsNil)
   376  	rec := httptest.NewRecorder()
   377  	s.req(c, req, nil).ServeHTTP(rec, req)
   378  	c.Check(rec.Code, check.Equals, 400)
   379  
   380  	var body map[string]interface{}
   381  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   382  	c.Check(err, check.IsNil)
   383  	if producer {
   384  		c.Check(body, check.DeepEquals, map[string]interface{}{
   385  			"result": map[string]interface{}{
   386  				"message": "snap \"consumer\" is not installed",
   387  			},
   388  			"status":      "Bad Request",
   389  			"status-code": 400.0,
   390  			"type":        "error",
   391  		})
   392  	} else {
   393  		c.Check(body, check.DeepEquals, map[string]interface{}{
   394  			"result": map[string]interface{}{
   395  				"message": "snap \"producer\" is not installed",
   396  			},
   397  			"status":      "Bad Request",
   398  			"status-code": 400.0,
   399  			"type":        "error",
   400  		})
   401  	}
   402  }
   403  
   404  func (s *interfacesSuite) TestConnectPlugFailureNoPlugSnap(c *check.C) {
   405  	s.testConnectFailureNoSnap(c, "producer")
   406  }
   407  
   408  func (s *interfacesSuite) TestConnectPlugFailureNoSlotSnap(c *check.C) {
   409  	s.testConnectFailureNoSnap(c, "consumer")
   410  }
   411  
   412  func (s *interfacesSuite) TestConnectPlugChangeConflict(c *check.C) {
   413  	d := s.daemon(c)
   414  
   415  	mockIface(c, d, &ifacetest.TestInterface{InterfaceName: "test"})
   416  	s.mockSnap(c, consumerYaml)
   417  	s.mockSnap(c, producerYaml)
   418  	// there is no producer, no slot defined
   419  
   420  	s.simulateConflict("consumer")
   421  
   422  	action := &client.InterfaceAction{
   423  		Action: "connect",
   424  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   425  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   426  	}
   427  	text, err := json.Marshal(action)
   428  	c.Assert(err, check.IsNil)
   429  	buf := bytes.NewBuffer(text)
   430  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   431  	c.Assert(err, check.IsNil)
   432  	rec := httptest.NewRecorder()
   433  	s.req(c, req, nil).ServeHTTP(rec, req)
   434  	c.Check(rec.Code, check.Equals, 409)
   435  
   436  	var body map[string]interface{}
   437  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   438  	c.Check(err, check.IsNil)
   439  	c.Check(body, check.DeepEquals, map[string]interface{}{
   440  		"status-code": 409.,
   441  		"status":      "Conflict",
   442  		"result": map[string]interface{}{
   443  			"message": `snap "consumer" has "manip" change in progress`,
   444  			"kind":    "snap-change-conflict",
   445  			"value": map[string]interface{}{
   446  				"change-kind": "manip",
   447  				"snap-name":   "consumer",
   448  			},
   449  		},
   450  		"type": "error"})
   451  }
   452  
   453  func (s *interfacesSuite) TestConnectCoreSystemAlias(c *check.C) {
   454  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   455  	defer revert()
   456  	d := s.daemon(c)
   457  
   458  	s.mockSnap(c, consumerYaml)
   459  	s.mockSnap(c, coreProducerYaml)
   460  
   461  	d.Overlord().Loop()
   462  	defer d.Overlord().Stop()
   463  
   464  	action := &client.InterfaceAction{
   465  		Action: "connect",
   466  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   467  		Slots:  []client.Slot{{Snap: "system", Name: "slot"}},
   468  	}
   469  	text, err := json.Marshal(action)
   470  	c.Assert(err, check.IsNil)
   471  	buf := bytes.NewBuffer(text)
   472  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   473  	c.Assert(err, check.IsNil)
   474  	rec := httptest.NewRecorder()
   475  	s.req(c, req, nil).ServeHTTP(rec, req)
   476  	c.Check(rec.Code, check.Equals, 202)
   477  	var body map[string]interface{}
   478  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   479  	c.Check(err, check.IsNil)
   480  	id := body["change"].(string)
   481  
   482  	st := d.Overlord().State()
   483  	st.Lock()
   484  	chg := st.Change(id)
   485  	st.Unlock()
   486  	c.Assert(chg, check.NotNil)
   487  
   488  	<-chg.Ready()
   489  
   490  	st.Lock()
   491  	err = chg.Err()
   492  	st.Unlock()
   493  	c.Assert(err, check.IsNil)
   494  
   495  	repo := d.Overlord().InterfaceManager().Repository()
   496  	ifaces := repo.Interfaces()
   497  	c.Assert(ifaces.Connections, check.HasLen, 1)
   498  	c.Check(ifaces.Connections, check.DeepEquals, []*interfaces.ConnRef{{
   499  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   500  		SlotRef: interfaces.SlotRef{Snap: "core", Name: "slot"}}})
   501  }
   502  
   503  func (s *interfacesSuite) testDisconnect(c *check.C, plugSnap, plugName, slotSnap, slotName string) {
   504  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   505  	defer restore()
   506  	// Install an inverse case mapper to exercise the interface mapping at the same time.
   507  	restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
   508  	defer restore()
   509  	d := s.daemon(c)
   510  
   511  	s.mockSnap(c, consumerYaml)
   512  	s.mockSnap(c, producerYaml)
   513  
   514  	repo := d.Overlord().InterfaceManager().Repository()
   515  	connRef := &interfaces.ConnRef{
   516  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   517  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
   518  	}
   519  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
   520  	c.Assert(err, check.IsNil)
   521  
   522  	st := d.Overlord().State()
   523  	st.Lock()
   524  	st.Set("conns", map[string]interface{}{
   525  		"consumer:plug producer:slot": map[string]interface{}{
   526  			"interface": "test",
   527  		},
   528  	})
   529  	st.Unlock()
   530  
   531  	d.Overlord().Loop()
   532  	defer d.Overlord().Stop()
   533  
   534  	action := &client.InterfaceAction{
   535  		Action: "disconnect",
   536  		Plugs:  []client.Plug{{Snap: plugSnap, Name: plugName}},
   537  		Slots:  []client.Slot{{Snap: slotSnap, Name: slotName}},
   538  	}
   539  	text, err := json.Marshal(action)
   540  	c.Assert(err, check.IsNil)
   541  	buf := bytes.NewBuffer(text)
   542  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   543  	c.Assert(err, check.IsNil)
   544  	rec := httptest.NewRecorder()
   545  	s.req(c, req, nil).ServeHTTP(rec, req)
   546  	c.Check(rec.Code, check.Equals, 202)
   547  	var body map[string]interface{}
   548  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   549  	c.Check(err, check.IsNil)
   550  	id := body["change"].(string)
   551  
   552  	st.Lock()
   553  	chg := st.Change(id)
   554  	st.Unlock()
   555  	c.Assert(chg, check.NotNil)
   556  
   557  	<-chg.Ready()
   558  
   559  	st.Lock()
   560  	err = chg.Err()
   561  	st.Unlock()
   562  	c.Assert(err, check.IsNil)
   563  
   564  	ifaces := repo.Interfaces()
   565  	c.Assert(ifaces.Connections, check.HasLen, 0)
   566  }
   567  
   568  func (s *interfacesSuite) TestDisconnectPlugSuccess(c *check.C) {
   569  	s.testDisconnect(c, "CONSUMER", "plug", "PRODUCER", "slot")
   570  }
   571  
   572  func (s *interfacesSuite) TestDisconnectPlugSuccessWithEmptyPlug(c *check.C) {
   573  	s.testDisconnect(c, "", "", "PRODUCER", "slot")
   574  }
   575  
   576  func (s *interfacesSuite) TestDisconnectPlugSuccessWithEmptySlot(c *check.C) {
   577  	s.testDisconnect(c, "CONSUMER", "plug", "", "")
   578  }
   579  
   580  func (s *interfacesSuite) TestDisconnectPlugFailureNoSuchPlug(c *check.C) {
   581  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   582  	defer revert()
   583  	s.daemon(c)
   584  
   585  	s.mockSnap(c, consumerYaml)
   586  	s.mockSnap(c, producerYaml)
   587  
   588  	action := &client.InterfaceAction{
   589  		Action: "disconnect",
   590  		Plugs:  []client.Plug{{Snap: "consumer", Name: "missingplug"}},
   591  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   592  	}
   593  	text, err := json.Marshal(action)
   594  	c.Assert(err, check.IsNil)
   595  	buf := bytes.NewBuffer(text)
   596  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   597  	c.Assert(err, check.IsNil)
   598  	rec := httptest.NewRecorder()
   599  	s.req(c, req, nil).ServeHTTP(rec, req)
   600  	c.Check(rec.Code, check.Equals, 400)
   601  	var body map[string]interface{}
   602  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   603  	c.Check(err, check.IsNil)
   604  	c.Check(body, check.DeepEquals, map[string]interface{}{
   605  		"result": map[string]interface{}{
   606  			"message": "snap \"consumer\" has no plug named \"missingplug\"",
   607  		},
   608  		"status":      "Bad Request",
   609  		"status-code": 400.0,
   610  		"type":        "error",
   611  	})
   612  }
   613  
   614  func (s *interfacesSuite) testDisconnectFailureNoSnap(c *check.C, installedSnap string) {
   615  	// validity, either consumer or producer needs to be enabled
   616  	consumer := installedSnap == "consumer"
   617  	producer := installedSnap == "producer"
   618  	c.Assert(consumer || producer, check.Equals, true, check.Commentf("installed snap must be consumer or producer"))
   619  
   620  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   621  	defer revert()
   622  	s.daemon(c)
   623  
   624  	if consumer {
   625  		s.mockSnap(c, consumerYaml)
   626  	}
   627  	if producer {
   628  		s.mockSnap(c, producerYaml)
   629  	}
   630  
   631  	action := &client.InterfaceAction{
   632  		Action: "disconnect",
   633  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   634  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   635  	}
   636  	text, err := json.Marshal(action)
   637  	c.Assert(err, check.IsNil)
   638  	buf := bytes.NewBuffer(text)
   639  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   640  	c.Assert(err, check.IsNil)
   641  	rec := httptest.NewRecorder()
   642  	s.req(c, req, nil).ServeHTTP(rec, req)
   643  	c.Check(rec.Code, check.Equals, 400)
   644  	var body map[string]interface{}
   645  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   646  	c.Check(err, check.IsNil)
   647  
   648  	if producer {
   649  		c.Check(body, check.DeepEquals, map[string]interface{}{
   650  			"result": map[string]interface{}{
   651  				"message": "snap \"consumer\" is not installed",
   652  			},
   653  			"status":      "Bad Request",
   654  			"status-code": 400.0,
   655  			"type":        "error",
   656  		})
   657  	} else {
   658  		c.Check(body, check.DeepEquals, map[string]interface{}{
   659  			"result": map[string]interface{}{
   660  				"message": "snap \"producer\" is not installed",
   661  			},
   662  			"status":      "Bad Request",
   663  			"status-code": 400.0,
   664  			"type":        "error",
   665  		})
   666  	}
   667  }
   668  
   669  func (s *interfacesSuite) TestDisconnectPlugFailureNoPlugSnap(c *check.C) {
   670  	s.testDisconnectFailureNoSnap(c, "producer")
   671  }
   672  
   673  func (s *interfacesSuite) TestDisconnectPlugFailureNoSlotSnap(c *check.C) {
   674  	s.testDisconnectFailureNoSnap(c, "consumer")
   675  }
   676  
   677  func (s *interfacesSuite) TestDisconnectPlugNothingToDo(c *check.C) {
   678  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   679  	defer revert()
   680  	s.daemon(c)
   681  
   682  	s.mockSnap(c, consumerYaml)
   683  	s.mockSnap(c, producerYaml)
   684  
   685  	action := &client.InterfaceAction{
   686  		Action: "disconnect",
   687  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   688  		Slots:  []client.Slot{{Snap: "", Name: ""}},
   689  	}
   690  	text, err := json.Marshal(action)
   691  	c.Assert(err, check.IsNil)
   692  	buf := bytes.NewBuffer(text)
   693  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   694  	c.Assert(err, check.IsNil)
   695  	rec := httptest.NewRecorder()
   696  	s.req(c, req, nil).ServeHTTP(rec, req)
   697  	c.Check(rec.Code, check.Equals, 400)
   698  	var body map[string]interface{}
   699  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   700  	c.Check(err, check.IsNil)
   701  	c.Check(body, check.DeepEquals, map[string]interface{}{
   702  		"result": map[string]interface{}{
   703  			"message": "nothing to do",
   704  			"kind":    "interfaces-unchanged",
   705  		},
   706  		"status":      "Bad Request",
   707  		"status-code": 400.0,
   708  		"type":        "error",
   709  	})
   710  }
   711  
   712  func (s *interfacesSuite) TestDisconnectPlugFailureNoSuchSlot(c *check.C) {
   713  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   714  	defer revert()
   715  	s.daemon(c)
   716  
   717  	s.mockSnap(c, consumerYaml)
   718  	s.mockSnap(c, producerYaml)
   719  
   720  	action := &client.InterfaceAction{
   721  		Action: "disconnect",
   722  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   723  		Slots:  []client.Slot{{Snap: "producer", Name: "missingslot"}},
   724  	}
   725  	text, err := json.Marshal(action)
   726  	c.Assert(err, check.IsNil)
   727  	buf := bytes.NewBuffer(text)
   728  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   729  	c.Assert(err, check.IsNil)
   730  	rec := httptest.NewRecorder()
   731  	s.req(c, req, nil).ServeHTTP(rec, req)
   732  
   733  	c.Check(rec.Code, check.Equals, 400)
   734  	var body map[string]interface{}
   735  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   736  	c.Check(err, check.IsNil)
   737  	c.Check(body, check.DeepEquals, map[string]interface{}{
   738  		"result": map[string]interface{}{
   739  			"message": "snap \"producer\" has no slot named \"missingslot\"",
   740  		},
   741  		"status":      "Bad Request",
   742  		"status-code": 400.0,
   743  		"type":        "error",
   744  	})
   745  }
   746  
   747  func (s *interfacesSuite) TestDisconnectPlugFailureNotConnected(c *check.C) {
   748  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   749  	defer revert()
   750  	s.daemon(c)
   751  
   752  	s.mockSnap(c, consumerYaml)
   753  	s.mockSnap(c, producerYaml)
   754  
   755  	action := &client.InterfaceAction{
   756  		Action: "disconnect",
   757  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   758  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   759  	}
   760  	text, err := json.Marshal(action)
   761  	c.Assert(err, check.IsNil)
   762  	buf := bytes.NewBuffer(text)
   763  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   764  	c.Assert(err, check.IsNil)
   765  	rec := httptest.NewRecorder()
   766  	s.req(c, req, nil).ServeHTTP(rec, req)
   767  
   768  	c.Check(rec.Code, check.Equals, 400)
   769  	var body map[string]interface{}
   770  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   771  	c.Check(err, check.IsNil)
   772  	c.Check(body, check.DeepEquals, map[string]interface{}{
   773  		"result": map[string]interface{}{
   774  			"message": "cannot disconnect consumer:plug from producer:slot, it is not connected",
   775  		},
   776  		"status":      "Bad Request",
   777  		"status-code": 400.0,
   778  		"type":        "error",
   779  	})
   780  }
   781  
   782  func (s *interfacesSuite) TestDisconnectForgetPlugFailureNotConnected(c *check.C) {
   783  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   784  	defer revert()
   785  	s.daemon(c)
   786  
   787  	s.mockSnap(c, consumerYaml)
   788  	s.mockSnap(c, producerYaml)
   789  
   790  	action := &client.InterfaceAction{
   791  		Action: "disconnect",
   792  		Forget: true,
   793  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   794  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   795  	}
   796  	text, err := json.Marshal(action)
   797  	c.Assert(err, check.IsNil)
   798  	buf := bytes.NewBuffer(text)
   799  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   800  	c.Assert(err, check.IsNil)
   801  	rec := httptest.NewRecorder()
   802  	s.req(c, req, nil).ServeHTTP(rec, req)
   803  
   804  	c.Check(rec.Code, check.Equals, 400)
   805  	var body map[string]interface{}
   806  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   807  	c.Check(err, check.IsNil)
   808  	c.Check(body, check.DeepEquals, map[string]interface{}{
   809  		"result": map[string]interface{}{
   810  			"message": "cannot forget connection consumer:plug from producer:slot, it was not connected",
   811  		},
   812  		"status":      "Bad Request",
   813  		"status-code": 400.0,
   814  		"type":        "error",
   815  	})
   816  }
   817  
   818  func (s *interfacesSuite) TestDisconnectConflict(c *check.C) {
   819  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   820  	defer revert()
   821  	d := s.daemon(c)
   822  
   823  	s.mockSnap(c, consumerYaml)
   824  	s.mockSnap(c, producerYaml)
   825  
   826  	repo := d.Overlord().InterfaceManager().Repository()
   827  	connRef := &interfaces.ConnRef{
   828  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   829  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
   830  	}
   831  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
   832  	c.Assert(err, check.IsNil)
   833  
   834  	st := d.Overlord().State()
   835  	st.Lock()
   836  	st.Set("conns", map[string]interface{}{
   837  		"consumer:plug producer:slot": map[string]interface{}{
   838  			"interface": "test",
   839  		},
   840  	})
   841  	st.Unlock()
   842  
   843  	s.simulateConflict("consumer")
   844  
   845  	action := &client.InterfaceAction{
   846  		Action: "disconnect",
   847  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   848  		Slots:  []client.Slot{{Snap: "producer", Name: "slot"}},
   849  	}
   850  	text, err := json.Marshal(action)
   851  	c.Assert(err, check.IsNil)
   852  	buf := bytes.NewBuffer(text)
   853  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   854  	c.Assert(err, check.IsNil)
   855  	rec := httptest.NewRecorder()
   856  	s.req(c, req, nil).ServeHTTP(rec, req)
   857  
   858  	c.Check(rec.Code, check.Equals, 409)
   859  
   860  	var body map[string]interface{}
   861  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   862  	c.Check(err, check.IsNil)
   863  	c.Check(body, check.DeepEquals, map[string]interface{}{
   864  		"status-code": 409.,
   865  		"status":      "Conflict",
   866  		"result": map[string]interface{}{
   867  			"message": `snap "consumer" has "manip" change in progress`,
   868  			"kind":    "snap-change-conflict",
   869  			"value": map[string]interface{}{
   870  				"change-kind": "manip",
   871  				"snap-name":   "consumer",
   872  			},
   873  		},
   874  		"type": "error"})
   875  }
   876  
   877  func (s *interfacesSuite) TestDisconnectCoreSystemAlias(c *check.C) {
   878  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   879  	defer revert()
   880  	d := s.daemon(c)
   881  
   882  	s.mockSnap(c, consumerYaml)
   883  	s.mockSnap(c, coreProducerYaml)
   884  
   885  	repo := d.Overlord().InterfaceManager().Repository()
   886  	connRef := &interfaces.ConnRef{
   887  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
   888  		SlotRef: interfaces.SlotRef{Snap: "core", Name: "slot"},
   889  	}
   890  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
   891  	c.Assert(err, check.IsNil)
   892  
   893  	st := d.Overlord().State()
   894  	st.Lock()
   895  	st.Set("conns", map[string]interface{}{
   896  		"consumer:plug core:slot": map[string]interface{}{
   897  			"interface": "test",
   898  		},
   899  	})
   900  	st.Unlock()
   901  
   902  	d.Overlord().Loop()
   903  	defer d.Overlord().Stop()
   904  
   905  	action := &client.InterfaceAction{
   906  		Action: "disconnect",
   907  		Plugs:  []client.Plug{{Snap: "consumer", Name: "plug"}},
   908  		Slots:  []client.Slot{{Snap: "system", Name: "slot"}},
   909  	}
   910  	text, err := json.Marshal(action)
   911  	c.Assert(err, check.IsNil)
   912  	buf := bytes.NewBuffer(text)
   913  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   914  	c.Assert(err, check.IsNil)
   915  	rec := httptest.NewRecorder()
   916  	s.req(c, req, nil).ServeHTTP(rec, req)
   917  	c.Check(rec.Code, check.Equals, 202)
   918  	var body map[string]interface{}
   919  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   920  	c.Check(err, check.IsNil)
   921  	id := body["change"].(string)
   922  
   923  	st.Lock()
   924  	chg := st.Change(id)
   925  	st.Unlock()
   926  	c.Assert(chg, check.NotNil)
   927  
   928  	<-chg.Ready()
   929  
   930  	st.Lock()
   931  	err = chg.Err()
   932  	st.Unlock()
   933  	c.Assert(err, check.IsNil)
   934  
   935  	ifaces := repo.Interfaces()
   936  	c.Assert(ifaces.Connections, check.HasLen, 0)
   937  }
   938  
   939  func (s *interfacesSuite) TestUnsupportedInterfaceRequest(c *check.C) {
   940  	s.daemon(c)
   941  	buf := bytes.NewBuffer([]byte(`garbage`))
   942  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   943  	c.Assert(err, check.IsNil)
   944  	rec := httptest.NewRecorder()
   945  	s.req(c, req, nil).ServeHTTP(rec, req)
   946  	c.Check(rec.Code, check.Equals, 400)
   947  	var body map[string]interface{}
   948  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   949  	c.Check(err, check.IsNil)
   950  	c.Check(body, check.DeepEquals, map[string]interface{}{
   951  		"result": map[string]interface{}{
   952  			"message": "cannot decode request body into an interface action: invalid character 'g' looking for beginning of value",
   953  		},
   954  		"status":      "Bad Request",
   955  		"status-code": 400.0,
   956  		"type":        "error",
   957  	})
   958  }
   959  
   960  func (s *interfacesSuite) TestMissingInterfaceAction(c *check.C) {
   961  	s.daemon(c)
   962  	action := &client.InterfaceAction{}
   963  	text, err := json.Marshal(action)
   964  	c.Assert(err, check.IsNil)
   965  	buf := bytes.NewBuffer(text)
   966  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   967  	c.Assert(err, check.IsNil)
   968  	rec := httptest.NewRecorder()
   969  	s.req(c, req, nil).ServeHTTP(rec, req)
   970  	c.Check(rec.Code, check.Equals, 400)
   971  	var body map[string]interface{}
   972  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   973  	c.Check(err, check.IsNil)
   974  	c.Check(body, check.DeepEquals, map[string]interface{}{
   975  		"result": map[string]interface{}{
   976  			"message": "interface action not specified",
   977  		},
   978  		"status":      "Bad Request",
   979  		"status-code": 400.0,
   980  		"type":        "error",
   981  	})
   982  }
   983  
   984  func (s *interfacesSuite) TestUnsupportedInterfaceAction(c *check.C) {
   985  	s.daemon(c)
   986  	action := &client.InterfaceAction{Action: "foo"}
   987  	text, err := json.Marshal(action)
   988  	c.Assert(err, check.IsNil)
   989  	buf := bytes.NewBuffer(text)
   990  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
   991  	c.Assert(err, check.IsNil)
   992  	rec := httptest.NewRecorder()
   993  	s.req(c, req, nil).ServeHTTP(rec, req)
   994  	c.Check(rec.Code, check.Equals, 400)
   995  	var body map[string]interface{}
   996  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   997  	c.Check(err, check.IsNil)
   998  	c.Check(body, check.DeepEquals, map[string]interface{}{
   999  		"result": map[string]interface{}{
  1000  			"message": "unsupported interface action: \"foo\"",
  1001  		},
  1002  		"status":      "Bad Request",
  1003  		"status-code": 400.0,
  1004  		"type":        "error",
  1005  	})
  1006  }
  1007  
  1008  // Tests for GET /v2/interfaces
  1009  
  1010  func (s *interfacesSuite) TestInterfacesLegacy(c *check.C) {
  1011  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  1012  	defer restore()
  1013  	// Install an inverse case mapper to exercise the interface mapping at the same time.
  1014  	restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
  1015  	defer restore()
  1016  
  1017  	d := s.daemon(c)
  1018  
  1019  	var anotherConsumerYaml = `
  1020  name: another-consumer-%s
  1021  version: 1
  1022  apps:
  1023   app:
  1024  plugs:
  1025   plug:
  1026    interface: test
  1027    key: value
  1028    label: label
  1029  `
  1030  	s.mockSnap(c, consumerYaml)
  1031  	s.mockSnap(c, fmt.Sprintf(anotherConsumerYaml, "def"))
  1032  	s.mockSnap(c, fmt.Sprintf(anotherConsumerYaml, "abc"))
  1033  	s.mockSnap(c, producerYaml)
  1034  
  1035  	repo := d.Overlord().InterfaceManager().Repository()
  1036  	connRef := &interfaces.ConnRef{
  1037  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  1038  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
  1039  	}
  1040  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
  1041  	c.Assert(err, check.IsNil)
  1042  
  1043  	st := d.Overlord().State()
  1044  	st.Lock()
  1045  	st.Set("conns", map[string]interface{}{
  1046  		"consumer:plug producer:slot": map[string]interface{}{
  1047  			"interface": "test",
  1048  			"auto":      true,
  1049  		},
  1050  		"another-consumer-def:plug producer:slot": map[string]interface{}{
  1051  			"interface": "test",
  1052  			"by-gadget": true,
  1053  			"auto":      true,
  1054  		},
  1055  		"another-consumer-abc:plug producer:slot": map[string]interface{}{
  1056  			"interface": "test",
  1057  			"by-gadget": true,
  1058  			"auto":      true,
  1059  		},
  1060  	})
  1061  	st.Unlock()
  1062  
  1063  	req, err := http.NewRequest("GET", "/v2/interfaces", nil)
  1064  	c.Assert(err, check.IsNil)
  1065  	rec := httptest.NewRecorder()
  1066  	s.req(c, req, nil).ServeHTTP(rec, req)
  1067  	c.Check(rec.Code, check.Equals, 200)
  1068  	var body map[string]interface{}
  1069  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  1070  	c.Check(err, check.IsNil)
  1071  	c.Check(body, check.DeepEquals, map[string]interface{}{
  1072  		"result": map[string]interface{}{
  1073  			"plugs": []interface{}{
  1074  				map[string]interface{}{
  1075  					"snap":      "another-consumer-abc",
  1076  					"plug":      "plug",
  1077  					"interface": "test",
  1078  					"attrs":     map[string]interface{}{"key": "value"},
  1079  					"apps":      []interface{}{"app"},
  1080  					"label":     "label",
  1081  					"connections": []interface{}{
  1082  						map[string]interface{}{"snap": "producer", "slot": "slot"},
  1083  					},
  1084  				},
  1085  				map[string]interface{}{
  1086  					"snap":      "another-consumer-def",
  1087  					"plug":      "plug",
  1088  					"interface": "test",
  1089  					"attrs":     map[string]interface{}{"key": "value"},
  1090  					"apps":      []interface{}{"app"},
  1091  					"label":     "label",
  1092  					"connections": []interface{}{
  1093  						map[string]interface{}{"snap": "producer", "slot": "slot"},
  1094  					},
  1095  				},
  1096  				map[string]interface{}{
  1097  					"snap":      "consumer",
  1098  					"plug":      "plug",
  1099  					"interface": "test",
  1100  					"attrs":     map[string]interface{}{"key": "value"},
  1101  					"apps":      []interface{}{"app"},
  1102  					"label":     "label",
  1103  					"connections": []interface{}{
  1104  						map[string]interface{}{"snap": "producer", "slot": "slot"},
  1105  					},
  1106  				},
  1107  			},
  1108  			"slots": []interface{}{
  1109  				map[string]interface{}{
  1110  					"snap":      "producer",
  1111  					"slot":      "slot",
  1112  					"interface": "test",
  1113  					"attrs":     map[string]interface{}{"key": "value"},
  1114  					"apps":      []interface{}{"app"},
  1115  					"label":     "label",
  1116  					"connections": []interface{}{
  1117  						map[string]interface{}{"snap": "another-consumer-abc", "plug": "plug"},
  1118  						map[string]interface{}{"snap": "another-consumer-def", "plug": "plug"},
  1119  						map[string]interface{}{"snap": "consumer", "plug": "plug"},
  1120  					},
  1121  				},
  1122  			},
  1123  		},
  1124  		"status":      "OK",
  1125  		"status-code": 200.0,
  1126  		"type":        "sync",
  1127  	})
  1128  }
  1129  
  1130  func (s *interfacesSuite) TestInterfacesModern(c *check.C) {
  1131  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  1132  	defer restore()
  1133  	// Install an inverse case mapper to exercise the interface mapping at the same time.
  1134  	restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
  1135  	defer restore()
  1136  
  1137  	d := s.daemon(c)
  1138  
  1139  	s.mockSnap(c, consumerYaml)
  1140  	s.mockSnap(c, producerYaml)
  1141  
  1142  	repo := d.Overlord().InterfaceManager().Repository()
  1143  	connRef := &interfaces.ConnRef{
  1144  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  1145  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
  1146  	}
  1147  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
  1148  	c.Assert(err, check.IsNil)
  1149  
  1150  	req, err := http.NewRequest("GET", "/v2/interfaces?select=connected&doc=true&plugs=true&slots=true", nil)
  1151  	c.Assert(err, check.IsNil)
  1152  	rec := httptest.NewRecorder()
  1153  	s.req(c, req, nil).ServeHTTP(rec, req)
  1154  	c.Check(rec.Code, check.Equals, 200)
  1155  	var body map[string]interface{}
  1156  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  1157  	c.Check(err, check.IsNil)
  1158  	c.Check(body, check.DeepEquals, map[string]interface{}{
  1159  		"result": []interface{}{
  1160  			map[string]interface{}{
  1161  				"name": "test",
  1162  				"plugs": []interface{}{
  1163  					map[string]interface{}{
  1164  						"snap":  "consumer",
  1165  						"plug":  "plug",
  1166  						"label": "label",
  1167  						"attrs": map[string]interface{}{
  1168  							"key": "value",
  1169  						},
  1170  					}},
  1171  				"slots": []interface{}{
  1172  					map[string]interface{}{
  1173  						"snap":  "producer",
  1174  						"slot":  "slot",
  1175  						"label": "label",
  1176  						"attrs": map[string]interface{}{
  1177  							"key": "value",
  1178  						},
  1179  					},
  1180  				},
  1181  			},
  1182  		},
  1183  		"status":      "OK",
  1184  		"status-code": 200.0,
  1185  		"type":        "sync",
  1186  	})
  1187  }