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 }