github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/daemon/api_connections_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-2020 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  	"encoding/json"
    24  	"fmt"
    25  	"net/http"
    26  	"net/http/httptest"
    27  
    28  	"gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/daemon"
    31  	"github.com/snapcore/snapd/interfaces"
    32  	"github.com/snapcore/snapd/interfaces/builtin"
    33  	"github.com/snapcore/snapd/interfaces/ifacetest"
    34  	"github.com/snapcore/snapd/strutil"
    35  )
    36  
    37  // Tests for GET /v2/connections
    38  
    39  func (s *interfacesSuite) testConnectionsConnected(c *check.C, d *daemon.Daemon, query string, connsState map[string]interface{}, repoConnected []string, expected map[string]interface{}) {
    40  	repo := d.Overlord().InterfaceManager().Repository()
    41  	for crefStr, cstate := range connsState {
    42  		// if repoConnected is defined, then given connection must be on
    43  		// list, otherwise it's not going to be connected in the repository
    44  		// to simulate missing plugs/slots.
    45  		if repoConnected != nil && !strutil.ListContains(repoConnected, crefStr) {
    46  			continue
    47  		}
    48  		cref, err := interfaces.ParseConnRef(crefStr)
    49  		c.Assert(err, check.IsNil)
    50  		conn := cstate.(map[string]interface{})
    51  		if undesiredRaw, ok := conn["undesired"]; ok {
    52  			undesired, ok := undesiredRaw.(bool)
    53  			c.Assert(ok, check.Equals, true, check.Commentf("unexpected value for key 'undesired': %v", cstate))
    54  			if undesired {
    55  				// do not add connections that are undesired
    56  				continue
    57  			}
    58  		}
    59  		staticPlugAttrs, _ := conn["plug-static"].(map[string]interface{})
    60  		dynamicPlugAttrs, _ := conn["plug-dynamic"].(map[string]interface{})
    61  		staticSlotAttrs, _ := conn["slot-static"].(map[string]interface{})
    62  		dynamicSlotAttrs, _ := conn["slot-dynamic"].(map[string]interface{})
    63  		_, err = repo.Connect(cref, staticPlugAttrs, dynamicPlugAttrs, staticSlotAttrs, dynamicSlotAttrs, nil)
    64  		c.Assert(err, check.IsNil)
    65  	}
    66  
    67  	st := d.Overlord().State()
    68  	st.Lock()
    69  	st.Set("conns", connsState)
    70  	st.Unlock()
    71  
    72  	s.testConnections(c, query, expected)
    73  }
    74  
    75  func (s *interfacesSuite) testConnections(c *check.C, query string, expected map[string]interface{}) {
    76  	req, err := http.NewRequest("GET", query, nil)
    77  	c.Assert(err, check.IsNil)
    78  	rec := httptest.NewRecorder()
    79  	s.req(c, req, nil).ServeHTTP(rec, req)
    80  	c.Check(rec.Code, check.Equals, 200)
    81  	var body map[string]interface{}
    82  	err = json.Unmarshal(rec.Body.Bytes(), &body)
    83  	c.Check(err, check.IsNil)
    84  	c.Check(body, check.DeepEquals, expected)
    85  }
    86  
    87  func (s *interfacesSuite) TestConnectionsUnhappy(c *check.C) {
    88  	s.daemon(c)
    89  	req, err := http.NewRequest("GET", "/v2/connections?select=bad", nil)
    90  	c.Assert(err, check.IsNil)
    91  	rec := httptest.NewRecorder()
    92  	s.req(c, req, nil).ServeHTTP(rec, req)
    93  	c.Check(rec.Code, check.Equals, 400)
    94  	var body map[string]interface{}
    95  	err = json.Unmarshal(rec.Body.Bytes(), &body)
    96  	c.Check(err, check.IsNil)
    97  	c.Check(body, check.DeepEquals, map[string]interface{}{
    98  		"result": map[string]interface{}{
    99  			"message": "unsupported select qualifier",
   100  		},
   101  		"status":      "Bad Request",
   102  		"status-code": 400.0,
   103  		"type":        "error",
   104  	})
   105  }
   106  
   107  func (s *interfacesSuite) TestConnectionsEmpty(c *check.C) {
   108  	s.daemon(c)
   109  	s.testConnections(c, "/v2/connections", map[string]interface{}{
   110  		"result": map[string]interface{}{
   111  			"established": []interface{}{},
   112  			"plugs":       []interface{}{},
   113  			"slots":       []interface{}{},
   114  		},
   115  		"status":      "OK",
   116  		"status-code": 200.0,
   117  		"type":        "sync",
   118  	})
   119  	s.testConnections(c, "/v2/connections?select=all", map[string]interface{}{
   120  		"result": map[string]interface{}{
   121  			"established": []interface{}{},
   122  			"plugs":       []interface{}{},
   123  			"slots":       []interface{}{},
   124  		},
   125  		"status":      "OK",
   126  		"status-code": 200.0,
   127  		"type":        "sync",
   128  	})
   129  }
   130  
   131  func (s *interfacesSuite) TestConnectionsNotFound(c *check.C) {
   132  	s.daemon(c)
   133  	req, err := http.NewRequest("GET", "/v2/connections?snap=not-found", nil)
   134  	c.Assert(err, check.IsNil)
   135  	rec := httptest.NewRecorder()
   136  	s.req(c, req, nil).ServeHTTP(rec, req)
   137  	c.Check(rec.Code, check.Equals, 404)
   138  	var body map[string]interface{}
   139  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   140  	c.Check(err, check.IsNil)
   141  	c.Check(body, check.DeepEquals, map[string]interface{}{
   142  		"result": map[string]interface{}{
   143  			"message": "no state entry for key",
   144  			"kind":    "snap-not-found",
   145  			"value":   "not-found",
   146  		},
   147  		"status":      "Not Found",
   148  		"status-code": 404.0,
   149  		"type":        "error",
   150  	})
   151  }
   152  
   153  func (s *interfacesSuite) TestConnectionsUnconnected(c *check.C) {
   154  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   155  	defer restore()
   156  
   157  	s.daemon(c)
   158  
   159  	s.mockSnap(c, consumerYaml)
   160  	s.mockSnap(c, producerYaml)
   161  
   162  	s.testConnections(c, "/v2/connections?select=all", map[string]interface{}{
   163  		"result": map[string]interface{}{
   164  			"established": []interface{}{},
   165  			"plugs": []interface{}{
   166  				map[string]interface{}{
   167  					"snap":      "consumer",
   168  					"plug":      "plug",
   169  					"interface": "test",
   170  					"attrs":     map[string]interface{}{"key": "value"},
   171  					"apps":      []interface{}{"app"},
   172  					"label":     "label",
   173  				},
   174  			},
   175  			"slots": []interface{}{
   176  				map[string]interface{}{
   177  					"snap":      "producer",
   178  					"slot":      "slot",
   179  					"interface": "test",
   180  					"attrs":     map[string]interface{}{"key": "value"},
   181  					"apps":      []interface{}{"app"},
   182  					"label":     "label",
   183  				},
   184  			},
   185  		},
   186  		"status":      "OK",
   187  		"status-code": 200.0,
   188  		"type":        "sync",
   189  	})
   190  }
   191  
   192  func (s *interfacesSuite) TestConnectionsBySnapName(c *check.C) {
   193  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   194  	defer restore()
   195  
   196  	d := s.daemon(c)
   197  
   198  	s.mockSnap(c, consumerYaml)
   199  	s.mockSnap(c, producerYaml)
   200  
   201  	s.testConnections(c, "/v2/connections?select=all&snap=producer", map[string]interface{}{
   202  		"result": map[string]interface{}{
   203  			"established": []interface{}{},
   204  			"slots": []interface{}{
   205  				map[string]interface{}{
   206  					"snap":      "producer",
   207  					"slot":      "slot",
   208  					"interface": "test",
   209  					"attrs":     map[string]interface{}{"key": "value"},
   210  					"apps":      []interface{}{"app"},
   211  					"label":     "label",
   212  				},
   213  			},
   214  			"plugs": []interface{}{},
   215  		},
   216  		"status":      "OK",
   217  		"status-code": 200.0,
   218  		"type":        "sync",
   219  	})
   220  
   221  	s.testConnections(c, "/v2/connections?select=all&snap=consumer", map[string]interface{}{
   222  		"result": map[string]interface{}{
   223  			"established": []interface{}{},
   224  			"plugs": []interface{}{
   225  				map[string]interface{}{
   226  					"snap":      "consumer",
   227  					"plug":      "plug",
   228  					"interface": "test",
   229  					"attrs":     map[string]interface{}{"key": "value"},
   230  					"apps":      []interface{}{"app"},
   231  					"label":     "label",
   232  				},
   233  			},
   234  			"slots": []interface{}{},
   235  		},
   236  		"status":      "OK",
   237  		"status-code": 200.0,
   238  		"type":        "sync",
   239  	})
   240  
   241  	s.testConnectionsConnected(c, d, "/v2/connections?snap=producer", map[string]interface{}{
   242  		"consumer:plug producer:slot": map[string]interface{}{
   243  			"interface": "test",
   244  		},
   245  	}, nil, map[string]interface{}{
   246  		"result": map[string]interface{}{
   247  			"plugs": []interface{}{
   248  				map[string]interface{}{
   249  					"snap":      "consumer",
   250  					"plug":      "plug",
   251  					"interface": "test",
   252  					"attrs":     map[string]interface{}{"key": "value"},
   253  					"apps":      []interface{}{"app"},
   254  					"label":     "label",
   255  					"connections": []interface{}{
   256  						map[string]interface{}{"snap": "producer", "slot": "slot"},
   257  					},
   258  				},
   259  			},
   260  			"slots": []interface{}{
   261  				map[string]interface{}{
   262  					"snap":      "producer",
   263  					"slot":      "slot",
   264  					"interface": "test",
   265  					"attrs":     map[string]interface{}{"key": "value"},
   266  					"apps":      []interface{}{"app"},
   267  					"label":     "label",
   268  					"connections": []interface{}{
   269  						map[string]interface{}{"snap": "consumer", "plug": "plug"},
   270  					},
   271  				},
   272  			},
   273  			"established": []interface{}{
   274  				map[string]interface{}{
   275  					"plug":      map[string]interface{}{"snap": "consumer", "plug": "plug"},
   276  					"slot":      map[string]interface{}{"snap": "producer", "slot": "slot"},
   277  					"manual":    true,
   278  					"interface": "test",
   279  				},
   280  			},
   281  		},
   282  		"status":      "OK",
   283  		"status-code": 200.0,
   284  		"type":        "sync",
   285  	})
   286  }
   287  
   288  func (s *interfacesSuite) TestConnectionsMissingPlugSlotFilteredOut(c *check.C) {
   289  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   290  	defer restore()
   291  
   292  	d := s.daemon(c)
   293  
   294  	s.mockSnap(c, consumerYaml)
   295  	s.mockSnap(c, producerYaml)
   296  
   297  	for _, missingPlugOrSlot := range []string{"consumer:plug2 producer:slot", "consumer:plug producer:slot2"} {
   298  		s.testConnectionsConnected(c, d, "/v2/connections?snap=producer", map[string]interface{}{
   299  			"consumer:plug producer:slot": map[string]interface{}{
   300  				"interface": "test",
   301  			},
   302  			missingPlugOrSlot: map[string]interface{}{
   303  				"interface": "test",
   304  			},
   305  		},
   306  			[]string{"consumer:plug producer:slot"},
   307  			map[string]interface{}{
   308  				"result": map[string]interface{}{
   309  					"plugs": []interface{}{
   310  						map[string]interface{}{
   311  							"snap":      "consumer",
   312  							"plug":      "plug",
   313  							"interface": "test",
   314  							"attrs":     map[string]interface{}{"key": "value"},
   315  							"apps":      []interface{}{"app"},
   316  							"label":     "label",
   317  							"connections": []interface{}{
   318  								map[string]interface{}{"snap": "producer", "slot": "slot"},
   319  							},
   320  						},
   321  					},
   322  					"slots": []interface{}{
   323  						map[string]interface{}{
   324  							"snap":      "producer",
   325  							"slot":      "slot",
   326  							"interface": "test",
   327  							"attrs":     map[string]interface{}{"key": "value"},
   328  							"apps":      []interface{}{"app"},
   329  							"label":     "label",
   330  							"connections": []interface{}{
   331  								map[string]interface{}{"snap": "consumer", "plug": "plug"},
   332  							},
   333  						},
   334  					},
   335  					"established": []interface{}{
   336  						map[string]interface{}{
   337  							"plug":      map[string]interface{}{"snap": "consumer", "plug": "plug"},
   338  							"slot":      map[string]interface{}{"snap": "producer", "slot": "slot"},
   339  							"manual":    true,
   340  							"interface": "test",
   341  						},
   342  					},
   343  				},
   344  				"status":      "OK",
   345  				"status-code": 200.0,
   346  				"type":        "sync",
   347  			})
   348  	}
   349  }
   350  
   351  func (s *interfacesSuite) TestConnectionsBySnapAlias(c *check.C) {
   352  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   353  	defer restore()
   354  
   355  	d := s.daemon(c)
   356  
   357  	s.mockSnap(c, consumerYaml)
   358  	s.mockSnap(c, coreProducerYaml)
   359  
   360  	expectedUnconnected := map[string]interface{}{
   361  		"established": []interface{}{},
   362  		"slots": []interface{}{
   363  			map[string]interface{}{
   364  				"snap":      "core",
   365  				"slot":      "slot",
   366  				"interface": "test",
   367  				"attrs":     map[string]interface{}{"key": "value"},
   368  				"label":     "label",
   369  			},
   370  		},
   371  		"plugs": []interface{}{},
   372  	}
   373  	s.testConnections(c, "/v2/connections?select=all&snap=core", map[string]interface{}{
   374  		"result":      expectedUnconnected,
   375  		"status":      "OK",
   376  		"status-code": 200.0,
   377  		"type":        "sync",
   378  	})
   379  	// try using a well know alias
   380  	s.testConnections(c, "/v2/connections?select=all&snap=system", map[string]interface{}{
   381  		"result":      expectedUnconnected,
   382  		"status":      "OK",
   383  		"status-code": 200.0,
   384  		"type":        "sync",
   385  	})
   386  
   387  	expectedConnmected := map[string]interface{}{
   388  		"plugs": []interface{}{
   389  			map[string]interface{}{
   390  				"snap":      "consumer",
   391  				"plug":      "plug",
   392  				"interface": "test",
   393  				"attrs":     map[string]interface{}{"key": "value"},
   394  				"apps":      []interface{}{"app"},
   395  				"label":     "label",
   396  				"connections": []interface{}{
   397  					map[string]interface{}{"snap": "core", "slot": "slot"},
   398  				},
   399  			},
   400  		},
   401  		"slots": []interface{}{
   402  			map[string]interface{}{
   403  				"snap":      "core",
   404  				"slot":      "slot",
   405  				"interface": "test",
   406  				"attrs":     map[string]interface{}{"key": "value"},
   407  				"label":     "label",
   408  				"connections": []interface{}{
   409  					map[string]interface{}{"snap": "consumer", "plug": "plug"},
   410  				},
   411  			},
   412  		},
   413  		"established": []interface{}{
   414  			map[string]interface{}{
   415  				"plug":      map[string]interface{}{"snap": "consumer", "plug": "plug"},
   416  				"slot":      map[string]interface{}{"snap": "core", "slot": "slot"},
   417  				"manual":    true,
   418  				"interface": "test",
   419  			},
   420  		},
   421  	}
   422  
   423  	s.testConnectionsConnected(c, d, "/v2/connections?snap=core", map[string]interface{}{
   424  		"consumer:plug core:slot": map[string]interface{}{
   425  			"interface": "test",
   426  		},
   427  	}, nil, map[string]interface{}{
   428  		"result":      expectedConnmected,
   429  		"status":      "OK",
   430  		"status-code": 200.0,
   431  		"type":        "sync",
   432  	})
   433  	// connection was already established
   434  	s.testConnections(c, "/v2/connections?snap=system", map[string]interface{}{
   435  		"result":      expectedConnmected,
   436  		"status":      "OK",
   437  		"status-code": 200.0,
   438  		"type":        "sync",
   439  	})
   440  }
   441  
   442  func (s *interfacesSuite) TestConnectionsByIfaceName(c *check.C) {
   443  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   444  	defer restore()
   445  	restore = builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "different"})
   446  	defer restore()
   447  
   448  	d := s.daemon(c)
   449  
   450  	s.mockSnap(c, consumerYaml)
   451  	s.mockSnap(c, producerYaml)
   452  	var differentProducerYaml = `
   453  name: different-producer
   454  version: 1
   455  apps:
   456   app:
   457  slots:
   458   slot:
   459    interface: different
   460    key: value
   461    label: label
   462  `
   463  	var differentConsumerYaml = `
   464  name: different-consumer
   465  version: 1
   466  apps:
   467   app:
   468  plugs:
   469   plug:
   470    interface: different
   471    key: value
   472    label: label
   473  `
   474  	s.mockSnap(c, differentProducerYaml)
   475  	s.mockSnap(c, differentConsumerYaml)
   476  
   477  	s.testConnections(c, "/v2/connections?select=all&interface=test", map[string]interface{}{
   478  		"result": map[string]interface{}{
   479  			"established": []interface{}{},
   480  			"plugs": []interface{}{
   481  				map[string]interface{}{
   482  					"snap":      "consumer",
   483  					"plug":      "plug",
   484  					"interface": "test",
   485  					"attrs":     map[string]interface{}{"key": "value"},
   486  					"apps":      []interface{}{"app"},
   487  					"label":     "label",
   488  				},
   489  			},
   490  			"slots": []interface{}{
   491  				map[string]interface{}{
   492  					"snap":      "producer",
   493  					"slot":      "slot",
   494  					"interface": "test",
   495  					"attrs":     map[string]interface{}{"key": "value"},
   496  					"apps":      []interface{}{"app"},
   497  					"label":     "label",
   498  				},
   499  			},
   500  		},
   501  		"status":      "OK",
   502  		"status-code": 200.0,
   503  		"type":        "sync",
   504  	})
   505  	s.testConnections(c, "/v2/connections?select=all&interface=different", map[string]interface{}{
   506  		"result": map[string]interface{}{
   507  			"established": []interface{}{},
   508  			"plugs": []interface{}{
   509  				map[string]interface{}{
   510  					"snap":      "different-consumer",
   511  					"plug":      "plug",
   512  					"interface": "different",
   513  					"attrs":     map[string]interface{}{"key": "value"},
   514  					"apps":      []interface{}{"app"},
   515  					"label":     "label",
   516  				},
   517  			},
   518  			"slots": []interface{}{
   519  				map[string]interface{}{
   520  					"snap":      "different-producer",
   521  					"slot":      "slot",
   522  					"interface": "different",
   523  					"attrs":     map[string]interface{}{"key": "value"},
   524  					"apps":      []interface{}{"app"},
   525  					"label":     "label",
   526  				},
   527  			},
   528  		},
   529  		"status":      "OK",
   530  		"status-code": 200.0,
   531  		"type":        "sync",
   532  	})
   533  
   534  	// modifies state internally
   535  	s.testConnectionsConnected(c, d, "/v2/connections?interfaces=test", map[string]interface{}{
   536  		"consumer:plug producer:slot": map[string]interface{}{
   537  			"interface": "test",
   538  		},
   539  	}, nil, map[string]interface{}{
   540  		"result": map[string]interface{}{
   541  			"plugs": []interface{}{
   542  				map[string]interface{}{
   543  					"snap":      "consumer",
   544  					"plug":      "plug",
   545  					"interface": "test",
   546  					"attrs":     map[string]interface{}{"key": "value"},
   547  					"apps":      []interface{}{"app"},
   548  					"label":     "label",
   549  					"connections": []interface{}{
   550  						map[string]interface{}{"snap": "producer", "slot": "slot"},
   551  					},
   552  				},
   553  			},
   554  			"slots": []interface{}{
   555  				map[string]interface{}{
   556  					"snap":      "producer",
   557  					"slot":      "slot",
   558  					"interface": "test",
   559  					"attrs":     map[string]interface{}{"key": "value"},
   560  					"apps":      []interface{}{"app"},
   561  					"label":     "label",
   562  					"connections": []interface{}{
   563  						map[string]interface{}{"snap": "consumer", "plug": "plug"},
   564  					},
   565  				},
   566  			},
   567  			"established": []interface{}{
   568  				map[string]interface{}{
   569  					"plug":      map[string]interface{}{"snap": "consumer", "plug": "plug"},
   570  					"slot":      map[string]interface{}{"snap": "producer", "slot": "slot"},
   571  					"manual":    true,
   572  					"interface": "test",
   573  				},
   574  			},
   575  		},
   576  		"status":      "OK",
   577  		"status-code": 200.0,
   578  		"type":        "sync",
   579  	})
   580  	// use state modified by previous cal
   581  	s.testConnections(c, "/v2/connections?interface=different", map[string]interface{}{
   582  		"result": map[string]interface{}{
   583  			"established": []interface{}{},
   584  			"slots":       []interface{}{},
   585  			"plugs":       []interface{}{},
   586  		},
   587  		"status":      "OK",
   588  		"status-code": 200.0,
   589  		"type":        "sync",
   590  	})
   591  }
   592  
   593  func (s *interfacesSuite) TestConnectionsDefaultManual(c *check.C) {
   594  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   595  	defer restore()
   596  
   597  	d := s.daemon(c)
   598  
   599  	s.mockSnap(c, consumerYaml)
   600  	s.mockSnap(c, producerYaml)
   601  
   602  	s.testConnectionsConnected(c, d, "/v2/connections", map[string]interface{}{
   603  		"consumer:plug producer:slot": map[string]interface{}{
   604  			"interface": "test",
   605  		},
   606  	}, nil, map[string]interface{}{
   607  		"result": map[string]interface{}{
   608  			"plugs": []interface{}{
   609  				map[string]interface{}{
   610  					"snap":      "consumer",
   611  					"plug":      "plug",
   612  					"interface": "test",
   613  					"attrs":     map[string]interface{}{"key": "value"},
   614  					"apps":      []interface{}{"app"},
   615  					"label":     "label",
   616  					"connections": []interface{}{
   617  						map[string]interface{}{"snap": "producer", "slot": "slot"},
   618  					},
   619  				},
   620  			},
   621  			"slots": []interface{}{
   622  				map[string]interface{}{
   623  					"snap":      "producer",
   624  					"slot":      "slot",
   625  					"interface": "test",
   626  					"attrs":     map[string]interface{}{"key": "value"},
   627  					"apps":      []interface{}{"app"},
   628  					"label":     "label",
   629  					"connections": []interface{}{
   630  						map[string]interface{}{"snap": "consumer", "plug": "plug"},
   631  					},
   632  				},
   633  			},
   634  			"established": []interface{}{
   635  				map[string]interface{}{
   636  					"plug":      map[string]interface{}{"snap": "consumer", "plug": "plug"},
   637  					"slot":      map[string]interface{}{"snap": "producer", "slot": "slot"},
   638  					"manual":    true,
   639  					"interface": "test",
   640  				},
   641  			},
   642  		},
   643  		"status":      "OK",
   644  		"status-code": 200.0,
   645  		"type":        "sync",
   646  	})
   647  }
   648  
   649  func (s *interfacesSuite) TestConnectionsDefaultAuto(c *check.C) {
   650  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   651  	defer restore()
   652  
   653  	d := s.daemon(c)
   654  
   655  	s.mockSnap(c, consumerYaml)
   656  	s.mockSnap(c, producerYaml)
   657  
   658  	s.testConnectionsConnected(c, d, "/v2/connections", map[string]interface{}{
   659  		"consumer:plug producer:slot": map[string]interface{}{
   660  			"interface": "test",
   661  			"auto":      true,
   662  			"plug-static": map[string]interface{}{
   663  				"key": "value",
   664  			},
   665  			"plug-dynamic": map[string]interface{}{
   666  				"foo-plug-dynamic": "bar-dynamic",
   667  			},
   668  			"slot-static": map[string]interface{}{
   669  				"key": "value",
   670  			},
   671  			"slot-dynamic": map[string]interface{}{
   672  				"foo-slot-dynamic": "bar-dynamic",
   673  			},
   674  		},
   675  	}, nil, map[string]interface{}{
   676  		"result": map[string]interface{}{
   677  			"plugs": []interface{}{
   678  				map[string]interface{}{
   679  					"snap":      "consumer",
   680  					"plug":      "plug",
   681  					"interface": "test",
   682  					"attrs":     map[string]interface{}{"key": "value"},
   683  					"apps":      []interface{}{"app"},
   684  					"label":     "label",
   685  					"connections": []interface{}{
   686  						map[string]interface{}{"snap": "producer", "slot": "slot"},
   687  					},
   688  				},
   689  			},
   690  			"slots": []interface{}{
   691  				map[string]interface{}{
   692  					"snap":      "producer",
   693  					"slot":      "slot",
   694  					"interface": "test",
   695  					"attrs":     map[string]interface{}{"key": "value"},
   696  					"apps":      []interface{}{"app"},
   697  					"label":     "label",
   698  					"connections": []interface{}{
   699  						map[string]interface{}{"snap": "consumer", "plug": "plug"},
   700  					},
   701  				},
   702  			},
   703  			"established": []interface{}{
   704  				map[string]interface{}{
   705  					"plug":      map[string]interface{}{"snap": "consumer", "plug": "plug"},
   706  					"slot":      map[string]interface{}{"snap": "producer", "slot": "slot"},
   707  					"interface": "test",
   708  					"plug-attrs": map[string]interface{}{
   709  						"key":              "value",
   710  						"foo-plug-dynamic": "bar-dynamic",
   711  					},
   712  					"slot-attrs": map[string]interface{}{
   713  						"key":              "value",
   714  						"foo-slot-dynamic": "bar-dynamic",
   715  					},
   716  				},
   717  			},
   718  		},
   719  		"status":      "OK",
   720  		"status-code": 200.0,
   721  		"type":        "sync",
   722  	})
   723  }
   724  
   725  func (s *interfacesSuite) TestConnectionsDefaultGadget(c *check.C) {
   726  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   727  	defer restore()
   728  
   729  	d := s.daemon(c)
   730  
   731  	s.mockSnap(c, consumerYaml)
   732  	s.mockSnap(c, producerYaml)
   733  
   734  	s.testConnectionsConnected(c, d, "/v2/connections", map[string]interface{}{
   735  		"consumer:plug producer:slot": map[string]interface{}{
   736  			"interface": "test",
   737  			"by-gadget": true,
   738  			"auto":      true,
   739  		},
   740  	}, nil, map[string]interface{}{
   741  		"result": map[string]interface{}{
   742  			"plugs": []interface{}{
   743  				map[string]interface{}{
   744  					"snap":      "consumer",
   745  					"plug":      "plug",
   746  					"interface": "test",
   747  					"attrs":     map[string]interface{}{"key": "value"},
   748  					"apps":      []interface{}{"app"},
   749  					"label":     "label",
   750  					"connections": []interface{}{
   751  						map[string]interface{}{"snap": "producer", "slot": "slot"},
   752  					},
   753  				},
   754  			},
   755  			"slots": []interface{}{
   756  				map[string]interface{}{
   757  					"snap":      "producer",
   758  					"slot":      "slot",
   759  					"interface": "test",
   760  					"attrs":     map[string]interface{}{"key": "value"},
   761  					"apps":      []interface{}{"app"},
   762  					"label":     "label",
   763  					"connections": []interface{}{
   764  						map[string]interface{}{"snap": "consumer", "plug": "plug"},
   765  					},
   766  				},
   767  			},
   768  			"established": []interface{}{
   769  				map[string]interface{}{
   770  					"plug":      map[string]interface{}{"snap": "consumer", "plug": "plug"},
   771  					"slot":      map[string]interface{}{"snap": "producer", "slot": "slot"},
   772  					"gadget":    true,
   773  					"interface": "test",
   774  				},
   775  			},
   776  		},
   777  		"status":      "OK",
   778  		"status-code": 200.0,
   779  		"type":        "sync",
   780  	})
   781  }
   782  
   783  func (s *interfacesSuite) TestConnectionsAll(c *check.C) {
   784  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   785  	defer restore()
   786  
   787  	d := s.daemon(c)
   788  
   789  	s.mockSnap(c, consumerYaml)
   790  	s.mockSnap(c, producerYaml)
   791  
   792  	s.testConnectionsConnected(c, d, "/v2/connections?select=all", map[string]interface{}{
   793  		"consumer:plug producer:slot": map[string]interface{}{
   794  			"interface": "test",
   795  			"by-gadget": true,
   796  			"auto":      true,
   797  			"undesired": true,
   798  		},
   799  	}, nil, map[string]interface{}{
   800  		"result": map[string]interface{}{
   801  			"established": []interface{}{},
   802  			"plugs": []interface{}{
   803  				map[string]interface{}{
   804  					"snap":      "consumer",
   805  					"plug":      "plug",
   806  					"interface": "test",
   807  					"attrs":     map[string]interface{}{"key": "value"},
   808  					"apps":      []interface{}{"app"},
   809  					"label":     "label",
   810  				},
   811  			},
   812  			"slots": []interface{}{
   813  				map[string]interface{}{
   814  					"snap":      "producer",
   815  					"slot":      "slot",
   816  					"interface": "test",
   817  					"attrs":     map[string]interface{}{"key": "value"},
   818  					"apps":      []interface{}{"app"},
   819  					"label":     "label",
   820  				},
   821  			},
   822  			"undesired": []interface{}{
   823  				map[string]interface{}{
   824  					"plug":      map[string]interface{}{"snap": "consumer", "plug": "plug"},
   825  					"slot":      map[string]interface{}{"snap": "producer", "slot": "slot"},
   826  					"gadget":    true,
   827  					"manual":    true,
   828  					"interface": "test",
   829  				},
   830  			},
   831  		},
   832  		"status":      "OK",
   833  		"status-code": 200.0,
   834  		"type":        "sync",
   835  	})
   836  }
   837  
   838  func (s *interfacesSuite) TestConnectionsOnlyUndesired(c *check.C) {
   839  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   840  	defer restore()
   841  
   842  	d := s.daemon(c)
   843  
   844  	s.mockSnap(c, consumerYaml)
   845  	s.mockSnap(c, producerYaml)
   846  
   847  	s.testConnectionsConnected(c, d, "/v2/connections", map[string]interface{}{
   848  		"consumer:plug producer:slot": map[string]interface{}{
   849  			"interface": "test",
   850  			"by-gadget": true,
   851  			"auto":      true,
   852  			"undesired": true,
   853  		},
   854  	}, nil, map[string]interface{}{
   855  		"result": map[string]interface{}{
   856  			"established": []interface{}{},
   857  			"plugs":       []interface{}{},
   858  			"slots":       []interface{}{},
   859  		},
   860  		"status":      "OK",
   861  		"status-code": 200.0,
   862  		"type":        "sync",
   863  	})
   864  }
   865  
   866  func (s *interfacesSuite) TestConnectionsHotplugGone(c *check.C) {
   867  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   868  	defer restore()
   869  
   870  	d := s.daemon(c)
   871  
   872  	s.mockSnap(c, consumerYaml)
   873  	s.mockSnap(c, producerYaml)
   874  
   875  	s.testConnectionsConnected(c, d, "/v2/connections", map[string]interface{}{
   876  		"consumer:plug producer:slot": map[string]interface{}{
   877  			"interface":    "test",
   878  			"hotplug-gone": true,
   879  		},
   880  	}, nil, map[string]interface{}{
   881  		"result": map[string]interface{}{
   882  			"established": []interface{}{},
   883  			"plugs":       []interface{}{},
   884  			"slots":       []interface{}{},
   885  		},
   886  		"status":      "OK",
   887  		"status-code": 200.0,
   888  		"type":        "sync",
   889  	})
   890  }
   891  
   892  func (s *interfacesSuite) TestConnectionsSorted(c *check.C) {
   893  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
   894  	defer restore()
   895  
   896  	d := s.daemon(c)
   897  
   898  	var anotherConsumerYaml = `
   899  name: another-consumer-%s
   900  version: 1
   901  apps:
   902   app:
   903  plugs:
   904   plug:
   905    interface: test
   906    key: value
   907    label: label
   908  `
   909  	var anotherProducerYaml = `
   910  name: another-producer
   911  version: 1
   912  apps:
   913   app:
   914  slots:
   915   slot:
   916    interface: test
   917    key: value
   918    label: label
   919  `
   920  
   921  	s.mockSnap(c, consumerYaml)
   922  	s.mockSnap(c, fmt.Sprintf(anotherConsumerYaml, "def"))
   923  	s.mockSnap(c, fmt.Sprintf(anotherConsumerYaml, "abc"))
   924  
   925  	s.mockSnap(c, producerYaml)
   926  	s.mockSnap(c, anotherProducerYaml)
   927  
   928  	s.testConnectionsConnected(c, d, "/v2/connections", map[string]interface{}{
   929  		"consumer:plug producer:slot": map[string]interface{}{
   930  			"interface": "test",
   931  			"by-gadget": true,
   932  			"auto":      true,
   933  		},
   934  		"another-consumer-def:plug producer:slot": map[string]interface{}{
   935  			"interface": "test",
   936  			"by-gadget": true,
   937  			"auto":      true,
   938  		},
   939  		"another-consumer-abc:plug producer:slot": map[string]interface{}{
   940  			"interface": "test",
   941  			"by-gadget": true,
   942  			"auto":      true,
   943  		},
   944  		"another-consumer-def:plug another-producer:slot": map[string]interface{}{
   945  			"interface": "test",
   946  			"by-gadget": true,
   947  			"auto":      true,
   948  		},
   949  	}, nil, map[string]interface{}{
   950  		"result": map[string]interface{}{
   951  			"plugs": []interface{}{
   952  				map[string]interface{}{
   953  					"snap":      "another-consumer-abc",
   954  					"plug":      "plug",
   955  					"interface": "test",
   956  					"attrs":     map[string]interface{}{"key": "value"},
   957  					"apps":      []interface{}{"app"},
   958  					"label":     "label",
   959  					"connections": []interface{}{
   960  						map[string]interface{}{"snap": "producer", "slot": "slot"},
   961  					},
   962  				},
   963  				map[string]interface{}{
   964  					"snap":      "another-consumer-def",
   965  					"plug":      "plug",
   966  					"interface": "test",
   967  					"attrs":     map[string]interface{}{"key": "value"},
   968  					"apps":      []interface{}{"app"},
   969  					"label":     "label",
   970  					"connections": []interface{}{
   971  						map[string]interface{}{"snap": "another-producer", "slot": "slot"},
   972  						map[string]interface{}{"snap": "producer", "slot": "slot"},
   973  					},
   974  				},
   975  				map[string]interface{}{
   976  					"snap":      "consumer",
   977  					"plug":      "plug",
   978  					"interface": "test",
   979  					"attrs":     map[string]interface{}{"key": "value"},
   980  					"apps":      []interface{}{"app"},
   981  					"label":     "label",
   982  					"connections": []interface{}{
   983  						map[string]interface{}{"snap": "producer", "slot": "slot"},
   984  					},
   985  				},
   986  			},
   987  			"slots": []interface{}{
   988  				map[string]interface{}{
   989  					"snap":      "another-producer",
   990  					"slot":      "slot",
   991  					"interface": "test",
   992  					"attrs":     map[string]interface{}{"key": "value"},
   993  					"apps":      []interface{}{"app"},
   994  					"label":     "label",
   995  					"connections": []interface{}{
   996  						map[string]interface{}{"snap": "another-consumer-def", "plug": "plug"},
   997  					},
   998  				},
   999  				map[string]interface{}{
  1000  					"snap":      "producer",
  1001  					"slot":      "slot",
  1002  					"interface": "test",
  1003  					"attrs":     map[string]interface{}{"key": "value"},
  1004  					"apps":      []interface{}{"app"},
  1005  					"label":     "label",
  1006  					"connections": []interface{}{
  1007  						map[string]interface{}{"snap": "another-consumer-abc", "plug": "plug"},
  1008  						map[string]interface{}{"snap": "another-consumer-def", "plug": "plug"},
  1009  						map[string]interface{}{"snap": "consumer", "plug": "plug"},
  1010  					},
  1011  				},
  1012  			},
  1013  			"established": []interface{}{
  1014  				map[string]interface{}{
  1015  					"plug":      map[string]interface{}{"snap": "another-consumer-abc", "plug": "plug"},
  1016  					"slot":      map[string]interface{}{"snap": "producer", "slot": "slot"},
  1017  					"interface": "test",
  1018  					"gadget":    true,
  1019  				},
  1020  				map[string]interface{}{
  1021  					"plug":      map[string]interface{}{"snap": "another-consumer-def", "plug": "plug"},
  1022  					"slot":      map[string]interface{}{"snap": "another-producer", "slot": "slot"},
  1023  					"interface": "test",
  1024  					"gadget":    true,
  1025  				},
  1026  				map[string]interface{}{
  1027  					"plug":      map[string]interface{}{"snap": "another-consumer-def", "plug": "plug"},
  1028  					"slot":      map[string]interface{}{"snap": "producer", "slot": "slot"},
  1029  					"interface": "test",
  1030  					"gadget":    true,
  1031  				},
  1032  				map[string]interface{}{
  1033  					"plug":      map[string]interface{}{"snap": "consumer", "plug": "plug"},
  1034  					"slot":      map[string]interface{}{"snap": "producer", "slot": "slot"},
  1035  					"interface": "test",
  1036  					"gadget":    true,
  1037  				},
  1038  			},
  1039  		},
  1040  		"status":      "OK",
  1041  		"status-code": 200.0,
  1042  		"type":        "sync",
  1043  	})
  1044  }