github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/daemon/api_validate_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 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  	"fmt"
    24  	"net/http"
    25  	"net/url"
    26  	"strings"
    27  
    28  	"gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/daemon"
    31  	"github.com/snapcore/snapd/overlord/assertstate"
    32  	"github.com/snapcore/snapd/overlord/state"
    33  )
    34  
    35  var _ = check.Suite(&apiValidationSetsSuite{})
    36  
    37  type apiValidationSetsSuite struct {
    38  	apiBaseSuite
    39  }
    40  
    41  func (s *apiValidationSetsSuite) SetUpTest(c *check.C) {
    42  	s.apiBaseSuite.SetUpTest(c)
    43  	d := s.daemon(c)
    44  	d.Overlord().Loop()
    45  	s.AddCleanup(func() { d.Overlord().Stop() })
    46  }
    47  
    48  func mockValidationSetsTracking(st *state.State) {
    49  	st.Set("validation-sets", map[string]interface{}{
    50  		"foo/bar": map[string]interface{}{
    51  			"account-id": "foo",
    52  			"name":       "bar",
    53  			"mode":       assertstate.Enforce,
    54  			"pinned-at":  9,
    55  			"current":    12,
    56  		},
    57  		"foo/baz": map[string]interface{}{
    58  			"account-id": "foo",
    59  			"name":       "baz",
    60  			"mode":       assertstate.Monitor,
    61  			"pinned-at":  0,
    62  			"current":    2,
    63  		},
    64  	})
    65  }
    66  
    67  func (s *apiValidationSetsSuite) TestQueryValidationSetsErrors(c *check.C) {
    68  	st := s.d.Overlord().State()
    69  	st.Lock()
    70  	mockValidationSetsTracking(st)
    71  	st.Unlock()
    72  
    73  	for i, tc := range []struct {
    74  		validationSet string
    75  		// sequence is normally an int, use string for passing invalid ones.
    76  		sequence string
    77  		message  string
    78  		status   int
    79  	}{
    80  		{
    81  			validationSet: "abc/Xfoo",
    82  			message:       `invalid name "Xfoo"`,
    83  			status:        400,
    84  		},
    85  		{
    86  			validationSet: "Xfoo/bar",
    87  			message:       `invalid account ID "Xfoo"`,
    88  			status:        400,
    89  		},
    90  		{
    91  			validationSet: "foo/foo",
    92  			message:       "validation set not found",
    93  			status:        404,
    94  		},
    95  		{
    96  			validationSet: "foo/bar",
    97  			sequence:      "1999",
    98  			message:       "validation set not found",
    99  			status:        404,
   100  		},
   101  		{
   102  			validationSet: "foo/bar",
   103  			sequence:      "x",
   104  			message:       "invalid sequence argument",
   105  			status:        400,
   106  		},
   107  		{
   108  			validationSet: "foo/bar",
   109  			sequence:      "-2",
   110  			message:       "invalid sequence argument: -2",
   111  			status:        400,
   112  		},
   113  	} {
   114  		q := url.Values{}
   115  		if tc.sequence != "" {
   116  			q.Set("sequence", tc.sequence)
   117  		}
   118  		req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s?%s", tc.validationSet, q.Encode()), nil)
   119  		c.Assert(err, check.IsNil)
   120  		rsp := s.req(c, req, nil).(*daemon.Resp)
   121  		c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError, check.Commentf("case #%d", i))
   122  		c.Check(rsp.Status, check.Equals, tc.status, check.Commentf("case #%d", i))
   123  		c.Check(rsp.ErrorResult().Message, check.Matches, tc.message)
   124  	}
   125  }
   126  
   127  func (s *apiValidationSetsSuite) TestGetValidationSetsNone(c *check.C) {
   128  	req, err := http.NewRequest("GET", "/v2/validation-sets", nil)
   129  	c.Assert(err, check.IsNil)
   130  
   131  	rsp := s.req(c, req, nil).(*daemon.Resp)
   132  	c.Assert(rsp.Status, check.Equals, 200)
   133  	res := rsp.Result.([]daemon.ValidationSetResult)
   134  	c.Check(res, check.HasLen, 0)
   135  }
   136  
   137  func (s *apiValidationSetsSuite) TestListValidationSets(c *check.C) {
   138  	req, err := http.NewRequest("GET", "/v2/validation-sets", nil)
   139  	c.Assert(err, check.IsNil)
   140  
   141  	st := s.d.Overlord().State()
   142  	st.Lock()
   143  	mockValidationSetsTracking(st)
   144  	st.Unlock()
   145  
   146  	rsp := s.req(c, req, nil).(*daemon.Resp)
   147  	c.Assert(rsp.Status, check.Equals, 200)
   148  	res := rsp.Result.([]daemon.ValidationSetResult)
   149  	c.Check(res, check.DeepEquals, []daemon.ValidationSetResult{
   150  		{
   151  			AccountID: "foo",
   152  			Name:      "bar",
   153  			PinnedAt:  9,
   154  			Mode:      "enforce",
   155  			Sequence:  12,
   156  			Valid:     false,
   157  		},
   158  		{
   159  			AccountID: "foo",
   160  			Name:      "baz",
   161  			Mode:      "monitor",
   162  			Sequence:  2,
   163  			Valid:     false,
   164  		},
   165  	})
   166  }
   167  
   168  func (s *apiValidationSetsSuite) TestGetValidationSetOne(c *check.C) {
   169  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/bar", nil)
   170  	c.Assert(err, check.IsNil)
   171  
   172  	st := s.d.Overlord().State()
   173  	st.Lock()
   174  	mockValidationSetsTracking(st)
   175  	st.Unlock()
   176  
   177  	rsp := s.req(c, req, nil).(*daemon.Resp)
   178  	c.Assert(rsp.Status, check.Equals, 200)
   179  	res := rsp.Result.(daemon.ValidationSetResult)
   180  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   181  		AccountID: "foo",
   182  		Name:      "bar",
   183  		PinnedAt:  9,
   184  		Mode:      "enforce",
   185  		Sequence:  12,
   186  		Valid:     false,
   187  	})
   188  }
   189  
   190  func (s *apiValidationSetsSuite) TestGetValidationSetPinned(c *check.C) {
   191  	q := url.Values{}
   192  	q.Set("sequence", "9")
   193  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/bar?"+q.Encode(), nil)
   194  	c.Assert(err, check.IsNil)
   195  
   196  	st := s.d.Overlord().State()
   197  	st.Lock()
   198  	mockValidationSetsTracking(st)
   199  	st.Unlock()
   200  
   201  	rsp := s.req(c, req, nil).(*daemon.Resp)
   202  	c.Assert(rsp.Status, check.Equals, 200)
   203  	res := rsp.Result.(daemon.ValidationSetResult)
   204  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   205  		AccountID: "foo",
   206  		Name:      "bar",
   207  		PinnedAt:  9,
   208  		Mode:      "enforce",
   209  		Sequence:  12,
   210  		Valid:     false,
   211  	})
   212  }
   213  
   214  func (s *apiValidationSetsSuite) TestGetValidationSetNotFound(c *check.C) {
   215  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil)
   216  	c.Assert(err, check.IsNil)
   217  
   218  	st := s.d.Overlord().State()
   219  	st.Lock()
   220  	mockValidationSetsTracking(st)
   221  	st.Unlock()
   222  
   223  	rsp := s.req(c, req, nil).(*daemon.Resp)
   224  	c.Assert(rsp.Status, check.Equals, 404)
   225  	res := rsp.Result.(*daemon.ErrorResult)
   226  	c.Assert(res, check.NotNil)
   227  	c.Check(string(res.Kind), check.Equals, "validation-set-not-found")
   228  	c.Check(res.Value, check.DeepEquals, map[string]interface{}{
   229  		"account-id": "foo",
   230  		"name":       "other",
   231  	})
   232  }
   233  
   234  func (s *apiValidationSetsSuite) TestGetValidationSetPinnedNotFound(c *check.C) {
   235  	q := url.Values{}
   236  	q.Set("sequence", "333")
   237  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/bar?"+q.Encode(), nil)
   238  	c.Assert(err, check.IsNil)
   239  
   240  	st := s.d.Overlord().State()
   241  	st.Lock()
   242  	mockValidationSetsTracking(st)
   243  	st.Unlock()
   244  
   245  	rsp := s.req(c, req, nil).(*daemon.Resp)
   246  	c.Assert(rsp.Status, check.Equals, 404)
   247  	res := rsp.Result.(*daemon.ErrorResult)
   248  	c.Assert(res, check.NotNil)
   249  	c.Check(string(res.Kind), check.Equals, "validation-set-not-found")
   250  	c.Check(res.Value, check.DeepEquals, map[string]interface{}{
   251  		"account-id": "foo",
   252  		"name":       "bar",
   253  		"sequence":   333,
   254  	})
   255  }
   256  
   257  func (s *apiValidationSetsSuite) TestApplyValidationSet(c *check.C) {
   258  	st := s.d.Overlord().State()
   259  
   260  	for _, tc := range []struct {
   261  		mode         string
   262  		sequence     int
   263  		expectedMode assertstate.ValidationSetMode
   264  	}{
   265  		{
   266  			mode:         "enforce",
   267  			sequence:     12,
   268  			expectedMode: assertstate.Enforce,
   269  		},
   270  		{
   271  			mode:         "monitor",
   272  			sequence:     99,
   273  			expectedMode: assertstate.Monitor,
   274  		},
   275  		{
   276  			mode:         "enforce",
   277  			expectedMode: assertstate.Enforce,
   278  		},
   279  		{
   280  			mode:         "monitor",
   281  			expectedMode: assertstate.Monitor,
   282  		},
   283  	} {
   284  		var body string
   285  		if tc.sequence != 0 {
   286  			body = fmt.Sprintf(`{"action":"apply","mode":"%s", "sequence":%d}`, tc.mode, tc.sequence)
   287  		} else {
   288  			body = fmt.Sprintf(`{"action":"apply","mode":"%s"}`, tc.mode)
   289  		}
   290  
   291  		req, err := http.NewRequest("POST", "/v2/validation-sets/foo/bar", strings.NewReader(body))
   292  		c.Assert(err, check.IsNil)
   293  
   294  		rsp := s.req(c, req, nil).(*daemon.Resp)
   295  		c.Assert(rsp.Status, check.Equals, 200)
   296  
   297  		var tr assertstate.ValidationSetTracking
   298  
   299  		st.Lock()
   300  		err = assertstate.GetValidationSet(st, "foo", "bar", &tr)
   301  		st.Unlock()
   302  		c.Assert(err, check.IsNil)
   303  		c.Check(tr, check.DeepEquals, assertstate.ValidationSetTracking{
   304  			AccountID: "foo",
   305  			Name:      "bar",
   306  			PinnedAt:  tc.sequence,
   307  			Mode:      tc.expectedMode,
   308  		})
   309  	}
   310  }
   311  
   312  func (s *apiValidationSetsSuite) TestForgetValidationSet(c *check.C) {
   313  	st := s.d.Overlord().State()
   314  
   315  	for i, sequence := range []int{0, 9} {
   316  		st.Lock()
   317  		mockValidationSetsTracking(st)
   318  		st.Unlock()
   319  
   320  		var body string
   321  		if sequence != 0 {
   322  			body = fmt.Sprintf(`{"action":"forget", "sequence":%d}`, sequence)
   323  		} else {
   324  			body = fmt.Sprintf(`{"action":"forget"}`)
   325  		}
   326  
   327  		var tr assertstate.ValidationSetTracking
   328  
   329  		st.Lock()
   330  		// sanity, it exists before removing
   331  		err := assertstate.GetValidationSet(st, "foo", "bar", &tr)
   332  		st.Unlock()
   333  		c.Assert(err, check.IsNil)
   334  		c.Check(tr.AccountID, check.Equals, "foo")
   335  		c.Check(tr.Name, check.Equals, "bar")
   336  
   337  		req, err := http.NewRequest("POST", "/v2/validation-sets/foo/bar", strings.NewReader(body))
   338  		c.Assert(err, check.IsNil)
   339  		rsp := s.req(c, req, nil).(*daemon.Resp)
   340  		c.Assert(rsp.Status, check.Equals, 200, check.Commentf("case #%d", i))
   341  
   342  		// after forget it's removed
   343  		st.Lock()
   344  		err = assertstate.GetValidationSet(st, "foo", "bar", &tr)
   345  		st.Unlock()
   346  		c.Assert(err, check.Equals, state.ErrNoState)
   347  
   348  		// and forget again fails
   349  		req, err = http.NewRequest("POST", "/v2/validation-sets/foo/bar", strings.NewReader(body))
   350  		c.Assert(err, check.IsNil)
   351  		rsp = s.req(c, req, nil).(*daemon.Resp)
   352  		c.Assert(rsp.Status, check.Equals, 404, check.Commentf("case #%d", i))
   353  	}
   354  }
   355  
   356  func (s *apiValidationSetsSuite) TestApplyValidationSetsErrors(c *check.C) {
   357  	st := s.d.Overlord().State()
   358  	st.Lock()
   359  	mockValidationSetsTracking(st)
   360  	st.Unlock()
   361  
   362  	for i, tc := range []struct {
   363  		validationSet string
   364  		mode          string
   365  		// sequence is normally an int, use string for passing invalid ones.
   366  		sequence string
   367  		message  string
   368  		status   int
   369  	}{
   370  		{
   371  			validationSet: "0/zzz",
   372  			mode:          "monitor",
   373  			message:       `invalid account ID "0"`,
   374  			status:        400,
   375  		},
   376  		{
   377  			validationSet: "Xfoo/bar",
   378  			mode:          "monitor",
   379  			message:       `invalid account ID "Xfoo"`,
   380  			status:        400,
   381  		},
   382  		{
   383  			validationSet: "foo/Xabc",
   384  			mode:          "monitor",
   385  			message:       `invalid name "Xabc"`,
   386  			status:        400,
   387  		},
   388  		{
   389  			validationSet: "foo/bar",
   390  			sequence:      "x",
   391  			message:       "cannot decode request body into validation set action: invalid character 'x' looking for beginning of value",
   392  			status:        400,
   393  		},
   394  		{
   395  			validationSet: "foo/bar",
   396  			mode:          "bad",
   397  			message:       `invalid mode "bad"`,
   398  			status:        400,
   399  		},
   400  		{
   401  			validationSet: "foo/bar",
   402  			sequence:      "-1",
   403  			mode:          "monitor",
   404  			message:       `invalid sequence argument: -1`,
   405  			status:        400,
   406  		},
   407  	} {
   408  		var body string
   409  		if tc.sequence != "" {
   410  			body = fmt.Sprintf(`{"action":"apply","mode":"%s", "sequence":%s}`, tc.mode, tc.sequence)
   411  		} else {
   412  			body = fmt.Sprintf(`{"action":"apply","mode":"%s"}`, tc.mode)
   413  		}
   414  		req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s", tc.validationSet), strings.NewReader(body))
   415  		c.Assert(err, check.IsNil)
   416  		rsp := s.req(c, req, nil).(*daemon.Resp)
   417  
   418  		c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError, check.Commentf("case #%d", i))
   419  		c.Check(rsp.Status, check.Equals, tc.status, check.Commentf("case #%d", i))
   420  		c.Check(rsp.ErrorResult().Message, check.Matches, tc.message)
   421  	}
   422  }
   423  
   424  func (s *apiValidationSetsSuite) TestApplyValidationSetUnsupportedAction(c *check.C) {
   425  	body := fmt.Sprintf(`{"action":"baz","mode":"monitor"}`)
   426  
   427  	req, err := http.NewRequest("POST", "/v2/validation-sets/foo/bar", strings.NewReader(body))
   428  	c.Assert(err, check.IsNil)
   429  
   430  	rsp := s.req(c, req, nil).(*daemon.Resp)
   431  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError)
   432  	c.Check(rsp.Status, check.Equals, 400)
   433  	c.Check(rsp.ErrorResult().Message, check.Matches, `unsupported action "baz"`)
   434  }