github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap/cmd_model_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 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 main_test
    21  
    22  import (
    23  	"fmt"
    24  	"net/http"
    25  
    26  	"gopkg.in/check.v1"
    27  
    28  	snap "github.com/snapcore/snapd/cmd/snap"
    29  )
    30  
    31  const happyModelAssertionResponse = `type: model
    32  authority-id: mememe
    33  series: 16
    34  brand-id: mememe
    35  model: test-model
    36  architecture: amd64
    37  base: core18
    38  gadget: pc=18
    39  kernel: pc-kernel=18
    40  store: mememestore
    41  system-user-authority:
    42    - youyouyou
    43    - mememe
    44  required-snaps:
    45    - core
    46    - hello-world
    47  timestamp: 2017-07-27T00:00:00.0Z
    48  sign-key-sha3-384: 8B3Wmemeu3H6i4dEV4Q85Q4gIUCHIBCNMHq49e085QeLGHi7v27l3Cqmemer4__t
    49  
    50  AcLBcwQAAQoAHRYhBMbX+t6MbKGH5C3nnLZW7+q0g6ELBQJdTdwTAAoJELZW7+q0g6ELEvgQAI3j
    51  jXTqR6kKOqvw94pArwdMDUaZ++tebASAZgso8ejrW2DQGWSc0Q7SQICIR8bvHxqS1GtupQswOzwS
    52  U8hjDTv7WEchH1jylyTj/1W1GernmitTKycecRlEkSOE+EpuqBFgTtj6PdA1Fj3CiCRi1rLMhgF2
    53  luCOitBLaP+E8P3fuATsLqqDLYzt1VY4Y14MU75hMn+CxAQdnOZTI+NzGMasPsldmOYCPNaN/b3N
    54  6/fDLU47RtNlMJ3K0Tz8kj0bqRbegKlD0RdNbAgo9iZwNmrr5E9WCu9f/0rUor/NIxO77H2ExIll
    55  zhmsZ7E6qlxvAgBmzKgAXrn68gGrBkIb0eXKiCaKy/i2ApvjVZ9HkOzA6Ldd+SwNJv/iA8rdiMsq
    56  p2BfKV5f3ju5b6+WktHxAakJ8iqQmj9Yh7piHjsOAUf1PEJd2s2nqQ+pEEn1F0B23gVCY/Fa9YRQ
    57  iKtWVeL3rBw4dSAaK9rpTMqlNcr+yrdXfTK5YzkCC6RU4yzc5MW0hKeseeSiEDSaRYxvftjFfVNa
    58  ZaVXKg8Lu+cHtCJDeYXEkPIDQzXswdBO1M8Mb9D0mYxQwHxwvsWv1DByB+Otq08EYgPh4kyHo7ag
    59  85yK2e/NQ/fxSwQJMhBF74jM1z9arq6RMiE/KOleFAOraKn2hcROKnEeinABW+sOn6vNuMVv
    60  `
    61  
    62  const happyUC20ModelAssertionResponse = `type: model
    63  authority-id: testrootorg
    64  series: 16
    65  brand-id: testrootorg
    66  model: test-snapd-core-20-amd64
    67  architecture: amd64
    68  base: core20
    69  grade: secured
    70  snaps:
    71    -
    72      default-channel: 20/edge
    73      id: UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH
    74      name: pc
    75      type: gadget
    76    -
    77      default-channel: 20/edge
    78      id: pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza
    79      name: pc-kernel
    80      type: kernel
    81    -
    82      default-channel: latest/stable
    83      id: DLqre5XGLbDqg9jPtiAhRRjDuPVa5X1q
    84      name: core20
    85      type: base
    86    -
    87      default-channel: latest/stable
    88      id: PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4
    89      name: snapd
    90      type: snapd
    91  timestamp: 2018-09-11T22:00:00+00:00
    92  sign-key-sha3-384: hIedp1AvrWlcDI4uS_qjoFLzjKl5enu4G2FYJpgB3Pj-tUzGlTQBxMBsBmi-tnJR
    93  
    94  AcLBUgQAAQoABgUCX06qogAAv10QAFaqQ0NDDvIB7LqM0xNIz+5Y6PB5wJaRk0HqVsg2LlNgS0PQ
    95  uJf0uFMV4GjQMraL7ZYv9BGyUoA+cz8Nbiz85m1g2ADt0ugqR/x2bAojii9lbFLmWpDMJcZhrtB1
    96  3k32lEUwqTMvzYTGiZ6TVug0KYbdmf2+5IGxsayAS3EwdrfbuGRHZOv6XGV7bmm1GEwCRAFvgHCk
    97  BHKoLZ+rfbNclF4l6G+biWJTdyc5jCMpMQ6X/INnx2hXaMRf9Jfrpl6s2bGCfsxW6HVf7AWZ8qHK
    98  jtWPQqJ6NFu2Kw1lYIA202ReK8DC3gfAlOeNzUG5dTPor3KwAoDJJI8ZaQypOazEhO9SHERIutbP
    99  eqPxPmEoB2+E0/o0+g0o5jK4qww3Yd7b8FTDkqm2xfuuldWAiAA4x6ZOQb2So9OLT6ovqHnD3D2r
   100  pLW/lhqwfKp3xzIVUrLi0sjGOVXu5xFDDRyFICZ6kwC7JynRGfHoa5E2y7rv8ehnOZQJ+esz9sgY
   101  lCJcyJ8vhabDlVHg0msSeNKMVBwhQnOSakEwlcfVyaSnapArkF+OCAMl8cuGpMTKO7vJLIJo2c2P
   102  jcVE0ftsTGs9eBi2HmdDhu3e3fmxHt9VcC4uRSOnYNVcJnMh0yVmG8RGS/Dqcz04II7llww6JJYG
   103  KKjQ3RU/TduXa8VJsoWiRRUYAv3H
   104  `
   105  
   106  const happyModelWithDisplayNameAssertionResponse = `type: model
   107  authority-id: mememe
   108  series: 16
   109  brand-id: mememe
   110  model: test-model
   111  architecture: amd64
   112  display-name: Model Name
   113  base: core18
   114  gadget: pc=18
   115  kernel: pc-kernel=18
   116  store: mememestore
   117  system-user-authority:
   118    - youyouyou
   119    - mememe
   120  required-snaps:
   121    - core
   122    - hello-world
   123  timestamp: 2017-07-27T00:00:00.0Z
   124  sign-key-sha3-384: 8B3Wmemeu3H6i4dEV4Q85Q4gIUCHIBCNMHq49e085QeLGHi7v27l3Cqmemer4__t
   125  
   126  AcLBcwQAAQoAHRYhBMbX+t6MbKGH5C3nnLZW7+q0g6ELBQJdTdwTAAoJELZW7+q0g6ELEvgQAI3j
   127  jXTqR6kKOqvw94pArwdMDUaZ++tebASAZgso8ejrW2DQGWSc0Q7SQICIR8bvHxqS1GtupQswOzwS
   128  U8hjDTv7WEchH1jylyTj/1W1GernmitTKycecRlEkSOE+EpuqBFgTtj6PdA1Fj3CiCRi1rLMhgF2
   129  luCOitBLaP+E8P3fuATsLqqDLYzt1VY4Y14MU75hMn+CxAQdnOZTI+NzGMasPsldmOYCPNaN/b3N
   130  6/fDLU47RtNlMJ3K0Tz8kj0bqRbegKlD0RdNbAgo9iZwNmrr5E9WCu9f/0rUor/NIxO77H2ExIll
   131  zhmsZ7E6qlxvAgBmzKgAXrn68gGrBkIb0eXKiCaKy/i2ApvjVZ9HkOzA6Ldd+SwNJv/iA8rdiMsq
   132  p2BfKV5f3ju5b6+WktHxAakJ8iqQmj9Yh7piHjsOAUf1PEJd2s2nqQ+pEEn1F0B23gVCY/Fa9YRQ
   133  iKtWVeL3rBw4dSAaK9rpTMqlNcr+yrdXfTK5YzkCC6RU4yzc5MW0hKeseeSiEDSaRYxvftjFfVNa
   134  ZaVXKg8Lu+cHtCJDeYXEkPIDQzXswdBO1M8Mb9D0mYxQwHxwvsWv1DByB+Otq08EYgPh4kyHo7ag
   135  85yK2e/NQ/fxSwQJMhBF74jM1z9arq6RMiE/KOleFAOraKn2hcROKnEeinABW+sOn6vNuMVv
   136  `
   137  
   138  const happyAccountAssertionResponse = `type: account
   139  authority-id: canonical
   140  account-id: mememe
   141  display-name: MeMeMe
   142  timestamp: 2016-04-01T00:00:00.0Z
   143  username: meuser
   144  validation: certified
   145  sign-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswHNiEB9Lxk
   146  
   147  AcLDXAQAAQoABgUCV7UYzwAKCRDUpVvql9g3IK7uH/4udqNOurx5WYVknzXdwekp0ovHCQJ0iBPw
   148  TSFxEVr9faZSzb7eqJ1WicHsShf97PYS3ClRYAiluFsjRA8Y03kkSVJHjC+sIwGFubsnkmgflt6D
   149  WEmYIl0UBmeaEDS8uY4Xvp9NsLTzNEj2kvzy/52gKaTc1ZSl5RDL9ppMav+0V9iBYpiDPBWH2rJ+
   150  aDSD8Rkyygm0UscfAKyDKH4lrvZ0WkYyi1YVNPrjQ/AtBySh6Q4iJ3LifzKa9woIyAuJET/4/FPY
   151  oirqHAfuvNod36yNQIyNqEc20AvTvZNH0PSsg4rq3DLjIPzv5KbJO9lhsasNJK1OdL6x8Yqrdsbk
   152  ldZp4qkzfjV7VOMQKaadfcZPRaVVeJWOBnBiaukzkhoNlQi1sdCdkBB/AJHZF8QXw6c7vPDcfnCV
   153  1lW7ddQ2p8IsJbT6LzpJu3GW/P4xhNgCjtCJ1AJm9a9RqLwQYgdLZwwDa9iCRtqTbRXBlfy3apps
   154  1VjbQ3h5iCd0hNfwDBnGVm1rhLKHCD1DUdNE43oN2ZlE7XGyh0HFV6vKlpqoW3eoXCIxWu+HBY96
   155  +LSl/jQgCkb0nxYyzEYK4Reb31D0mYw1Nji5W+MIF5E09+DYZoOT0UvR05YMwMEOeSdI/hLWg/5P
   156  k+GDK+/KopMmpd4D1+jjtF7ZvqDpmAV98jJGB2F88RyVb4gcjmFFyTi4Kv6vzz/oLpbm0qrizC0W
   157  HLGDN/ymGA5sHzEgEx7U540vz/q9VX60FKqL2YZr/DcyY9GKX5kCG4sNqIIHbcJneZ4frM99oVDu
   158  7Jv+DIx/Di6D1ULXol2XjxbbJLKHFtHksR97ceaFvcZwTogC61IYUBJCvvMoqdXAWMhEXCr0QfQ5
   159  Xbi31XW2d4/lF/zWlAkRnGTzufIXFni7+nEuOK0SQEzO3/WaRedK1SGOOtTDjB8/3OJeW96AUYK5
   160  oTIynkYkEyHWMNCXALg+WQW6L4/YO7aUjZ97zOWIugd7Xy63aT3r/EHafqaY2nacOhLfkeKZ830b
   161  o/ezjoZQAxbh6ce7JnXRgE9ELxjdAhBTpGjmmmN2sYrJ7zP9bOgly0BnEPXGSQfFA+NNNw1FADx1
   162  MUY8q9DBjmVtgqY+1KGTV5X8KvQCBMODZIf/XJPHdCRAHxMd8COypcwgL2vDIIXpOFbi1J/B0GF+
   163  eklxk9wzBA8AecBMCwCzIRHDNpD1oa2we38bVFrOug6e/VId1k1jYFJjiLyLCDmV8IMYwEllHSXp
   164  LQAdm3xZ7t4WnxYC8YSCk9mXf3CZg59SpmnV5Q5Z6A5Pl7Nc3sj7hcsMBZEsOMPzNC9dPsBnZvjs
   165  WpPUffJzEdhHBFhvYMuD4Vqj6ejUv9l3oTrjQWVC`
   166  
   167  // note: this serial assertion was generated by adding print statements to the
   168  // test in api_model_test.go that generate a fake serial assertion
   169  const happySerialAssertionResponse = `type: serial
   170  authority-id: my-brand
   171  brand-id: my-brand
   172  model: my-old-model
   173  serial: serialserial
   174  device-key:
   175      AcZrBFaFwYABAvCgEOrrLA6FKcreHxCcOoTgBUZ+IRG7Nb8tzmEAklaQPGpv7skapUjwD1luE2go
   176      mTcoTssVHrfLpBoSDV1aBs44rg3NK40ZKPJP7d2zkds1GxUo1Ea5vfet3SJ4h3aRABEBAAE=
   177  device-key-sha3-384: iqLo9doLzK8De9925UrdUyuvPbBad72OTWVE9YJXqd6nz9dKvwJ_lHP5bVxrl3VO
   178  timestamp: 2019-08-26T16:34:21-05:00
   179  sign-key-sha3-384: anCEGC2NYq7DzDEi6y7OafQCVeVLS90XlLt9PNjrRl9sim5rmRHDDNFNO7ODcWQW
   180  
   181  AcJwBAABCgAGBQJdZFBdAADCLALwR6Sy24wm9PffwbvUhOEXneyY3BnxKC0+NgdHu1gU8go9vEP1
   182  i+Flh5uoS70+MBIO+nmF8T+9JWIx2QWFDDxvcuFosnIhvUajCEQohauys5FMz/H/WvB0vrbTBpvK
   183  eg==
   184  `
   185  
   186  const happySerialUC20AssertionResponse = `type: serial
   187  authority-id: testrootorg
   188  brand-id: testrootorg
   189  model: test-snapd-core-20-amd64
   190  serial: 7777
   191  device-key:
   192      AcbBTQRWhcGAARAAuKf9n7WvZDI7u3NzMkD8WN+dxCYrb0UE9XIaHcbrj0i2zJpxCtUtpzoEo7Uk
   193      Cvxuhr2uBpzAa8fScwzOd77MGHIZQDpS7sFSkhYsSSN0m4sy8vRevsj0roN31fugCjRnhtLTkgxo
   194      KSoAsK87vYnC+m5V5AHaRER7q1KgpUoVD7eLOJZyrd/tWecsLL9OK87yAQHdF/cVlQupOP6OU3fK
   195      DllER6V2TD4jADK2Gyj2lDhy3F0+rE0a+zsGpmQQBorvzbozUHgBE3z/XjTTMrHYP4m+4V5HeWdn
   196      rHt/x1LZ8wMTCMT1eeruclC82UPRgF0zWI+P7WgBqogJpCbfadhAj1zvKW+5vJ385n0BU7PoAZtA
   197      KddBbsmEnfK/gWIxgFemIrYcYGhIBxYY6iNcygTYRFo4R9xm3bELHLG+viHggih4Lrjnb4sLHOdC
   198      h3C4/45bY+6hSno8GQGlp4kYQQM8mrF9st51jIM6oyB84NtoySLYYE1wMeGNzDHSuI+1IiRmaTgy
   199      Q2ImXTuqOhclhNA1sOi3R4H+oOBxe6GmoM5ATBPBqJeqUEvK8GpSRCig0QH4qMNF/abNKwvKhGMZ
   200      LqtpFp5LNx7xYuAwoVkcq0nxQTsXctl3gJqY+lRx7mIeoXLZPKZyJees+5v96oa9lMdNX3f5UUpX
   201      zq0cNhdgHrXZfcsAEQEAAQ==
   202  device-key-sha3-384: CZeO_5nJm_Rg0izosNfcQRoQj9nFtAmK2Y_tz4YjlKlvS93b_9gTDHuby5HHwi7d
   203  timestamp: 2020-09-03T14:42:47-05:00
   204  sign-key-sha3-384: hIedp1AvrWlcDI4uS_qjoFLzjKl5enu4G2FYJpgB3Pj-tUzGlTQBxMBsBmi-tnJR
   205  
   206  AcLBUgQAAQoABgUCX1FHNwAAqFoQABFiyzipoTYAuYN0Wd7cXuPPD7z+z+E+LoZZ+j4vUKqvnGX8
   207  tksb2nEEOQhjSvVof5pPOswWgq8Nj52dtYA20R5Zgfy0MZHHcCCfgxaRj6EiFyrG5h9l5wWMnzdb
   208  pXo9SJ3hxw6lKdj3n9RAAY0mACvw6f/trcyLeSxQ7EBm6X9c4ohJSjlHkKj0TlKkNTrFflko5aQH
   209  uJUk/YgsvMTZUHbgj6QKHlODUH8iRvOHxzn/Y9BlnzBsb/SyzvNTPeQyzFtd9QkESI2sWghviys2
   210  fGeEZPeXU6xts6Ht+xhr3mj5npZwkkL/6YxSzm9owQ0zGrfaFTswN+xoDKZ5498qRtSY3mCK/5xx
   211  kvWpOTHHhfvuS3GGyvRZOih7IAffDEwQsUNh8V9IjQNNTIkCYTPZz4WBM42mI8UgeDsnDImmcoc0
   212  GlqBeCxUigszJlEdUAHQklwW7Sgp13mceR3zB7BHgp4Sk7n0RyPuTQUA94ys6SeesK5YphwmhVed
   213  V02lkdeqRbGt3yZ/T5Zg8CIUIM0RKDSqoHgvoCMZh98dRGv6LPRj/P0RSWmjYWotjdK+lXK1fySM
   214  RXMNJIInZoC0x8qEwGLXVl5V3z8motLG71ie7PQ677W0dE9XM5LRnZHEKXP41jfaOO9vu12TtBsh
   215  pe/pnYDfIzU6OyOsdmkGWaWD+nbD
   216  `
   217  
   218  const noModelAssertionYetResponse = `
   219  {
   220  	"type": "error",
   221  	"status-code": 404,
   222  	"status": "Not Found",
   223  	"result": {
   224  	  "message": "no model assertion yet",
   225  	  "kind": "assertion-not-found",
   226  	  "value": "model"
   227  	}
   228  }`
   229  
   230  const noSerialAssertionYetResponse = `
   231  {
   232  	"type": "error",
   233  	"status-code": 404,
   234  	"status": "Not Found",
   235  	"result": {
   236  	  "message": "no serial assertion yet",
   237  	  "kind": "assertion-not-found",
   238  	  "value": "serial"
   239  	}
   240  }`
   241  
   242  // helper for constructing different types of responses to the client
   243  type checkResponder func(c *check.C, w http.ResponseWriter, r *http.Request)
   244  
   245  func simpleHappyResponder(body string) checkResponder {
   246  	return func(c *check.C, w http.ResponseWriter, r *http.Request) {
   247  		c.Check(r.Method, check.Equals, "GET")
   248  		c.Check(r.URL.RawQuery, check.Equals, "")
   249  		fmt.Fprintln(w, body)
   250  	}
   251  }
   252  
   253  func simpleUnhappyResponder(errBody string) checkResponder {
   254  	return func(c *check.C, w http.ResponseWriter, r *http.Request) {
   255  		c.Check(r.Method, check.Equals, "GET")
   256  		c.Check(r.URL.RawQuery, check.Equals, "")
   257  		w.Header().Set("Content-Type", "application/json")
   258  		w.WriteHeader(404)
   259  		fmt.Fprintln(w, errBody)
   260  	}
   261  }
   262  
   263  func simpleAssertionAccountResponder(body string) checkResponder {
   264  	return func(c *check.C, w http.ResponseWriter, r *http.Request) {
   265  		c.Check(r.Method, check.Equals, "GET")
   266  		w.Header().Set("X-Ubuntu-Assertions-Count", "1")
   267  		fmt.Fprintln(w, body)
   268  	}
   269  }
   270  
   271  func makeHappyTestServerHandler(c *check.C, modelResp, serialResp, accountResp checkResponder) func(w http.ResponseWriter, r *http.Request) {
   272  	var nModelSerial, nModel, nKnown int
   273  	return func(w http.ResponseWriter, r *http.Request) {
   274  		switch r.URL.Path {
   275  		case "/v2/model":
   276  			switch nModel {
   277  			case 0:
   278  				modelResp(c, w, r)
   279  			default:
   280  				c.Fatalf("expected to get 1 request for /v2/model, now on %d", nModel+1)
   281  			}
   282  			nModel++
   283  		case "/v2/model/serial":
   284  			switch nModelSerial {
   285  			case 0:
   286  				serialResp(c, w, r)
   287  			default:
   288  				c.Fatalf("expected to get 1 request for /v2/model, now on %d", nModelSerial+1)
   289  			}
   290  			nModelSerial++
   291  		case "/v2/assertions/account":
   292  			switch nKnown {
   293  			case 0:
   294  				accountResp(c, w, r)
   295  			default:
   296  				c.Fatalf("expected to get 1 request for /v2/model, now on %d", nKnown+1)
   297  			}
   298  			nKnown++
   299  		default:
   300  			c.Fatalf("unexpected request to %s", r.URL.Path)
   301  		}
   302  	}
   303  }
   304  
   305  func (s *SnapSuite) TestNoModelYet(c *check.C) {
   306  	s.RedirectClientToTestServer(
   307  		makeHappyTestServerHandler(
   308  			c,
   309  			simpleUnhappyResponder(noModelAssertionYetResponse),
   310  			simpleUnhappyResponder(noSerialAssertionYetResponse),
   311  			simpleAssertionAccountResponder(happyAccountAssertionResponse),
   312  		))
   313  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"model"})
   314  	c.Assert(err, check.ErrorMatches, `device not ready yet \(no assertions found\)`)
   315  }
   316  
   317  func (s *SnapSuite) TestNoSerialYet(c *check.C) {
   318  	s.RedirectClientToTestServer(
   319  		makeHappyTestServerHandler(
   320  			c,
   321  			simpleHappyResponder(happyModelAssertionResponse),
   322  			simpleUnhappyResponder(noSerialAssertionYetResponse),
   323  			simpleAssertionAccountResponder(happyAccountAssertionResponse),
   324  		))
   325  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--serial"})
   326  	c.Assert(err, check.ErrorMatches, `device not registered yet \(no serial assertion found\)`)
   327  	c.Check(s.Stderr(), check.Equals, "")
   328  	c.Check(s.Stdout(), check.Equals, `
   329  brand-id:  mememe
   330  model:     test-model
   331  `[1:])
   332  }
   333  
   334  func (s *SnapSuite) TestModel(c *check.C) {
   335  
   336  	for _, tt := range []struct {
   337  		comment string
   338  		modelF  checkResponder
   339  		serialF checkResponder
   340  		outText string
   341  	}{
   342  		{
   343  			comment: "normal serial and model asserts",
   344  			modelF:  simpleHappyResponder(happyModelAssertionResponse),
   345  			serialF: simpleHappyResponder(happySerialAssertionResponse),
   346  			outText: `
   347  brand   MeMeMe (meuser*)
   348  model   test-model
   349  serial  serialserial
   350  `[1:],
   351  		},
   352  		{
   353  			comment: "normal uc20 serial and model asserts",
   354  			modelF:  simpleHappyResponder(happyUC20ModelAssertionResponse),
   355  			serialF: simpleHappyResponder(happySerialUC20AssertionResponse),
   356  			outText: `
   357  brand   MeMeMe (meuser*)
   358  model   test-snapd-core-20-amd64
   359  grade   secured
   360  serial  7777
   361  `[1:],
   362  		},
   363  		{
   364  			comment: "model assert has display-name",
   365  			modelF:  simpleHappyResponder(happyModelWithDisplayNameAssertionResponse),
   366  			serialF: simpleHappyResponder(happySerialAssertionResponse),
   367  			outText: `
   368  brand   MeMeMe (meuser*)
   369  model   Model Name (test-model)
   370  serial  serialserial
   371  `[1:],
   372  		},
   373  		{
   374  			comment: "missing serial assert",
   375  			modelF:  simpleHappyResponder(happyModelAssertionResponse),
   376  			serialF: simpleUnhappyResponder(noSerialAssertionYetResponse),
   377  			outText: `
   378  brand   MeMeMe (meuser*)
   379  model   test-model
   380  serial  - (device not registered yet)
   381  `[1:],
   382  		},
   383  	} {
   384  		s.RedirectClientToTestServer(
   385  			makeHappyTestServerHandler(
   386  				c,
   387  				tt.modelF,
   388  				tt.serialF,
   389  				simpleAssertionAccountResponder(happyAccountAssertionResponse),
   390  			))
   391  		rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model"})
   392  		c.Assert(err, check.IsNil)
   393  		c.Assert(rest, check.DeepEquals, []string{})
   394  		c.Check(s.Stdout(), check.Equals, tt.outText, check.Commentf("\n%s\n", tt.outText))
   395  		c.Check(s.Stderr(), check.Equals, "")
   396  		s.ResetStdStreams()
   397  	}
   398  }
   399  
   400  func (s *SnapSuite) TestModelVerbose(c *check.C) {
   401  	s.RedirectClientToTestServer(
   402  		makeHappyTestServerHandler(
   403  			c,
   404  			simpleHappyResponder(happyModelAssertionResponse),
   405  			simpleHappyResponder(happySerialAssertionResponse),
   406  			simpleAssertionAccountResponder(happyAccountAssertionResponse),
   407  		))
   408  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--verbose", "--abs-time"})
   409  	c.Assert(err, check.IsNil)
   410  	c.Assert(rest, check.DeepEquals, []string{})
   411  	c.Check(s.Stdout(), check.Equals, `
   412  brand-id:               mememe
   413  model:                  test-model
   414  serial:                 serialserial
   415  architecture:           amd64
   416  base:                   core18
   417  gadget:                 pc=18
   418  kernel:                 pc-kernel=18
   419  store:                  mememestore
   420  system-user-authority:  
   421    - youyouyou
   422    - mememe
   423  timestamp:       2017-07-27T00:00:00Z
   424  required-snaps:  
   425    - core
   426    - hello-world
   427  `[1:])
   428  	c.Check(s.Stderr(), check.Equals, "")
   429  }
   430  
   431  func (s *SnapSuite) TestModelVerboseDisplayName(c *check.C) {
   432  	s.RedirectClientToTestServer(
   433  		makeHappyTestServerHandler(
   434  			c,
   435  			simpleHappyResponder(happyModelWithDisplayNameAssertionResponse),
   436  			simpleHappyResponder(happySerialAssertionResponse),
   437  			simpleAssertionAccountResponder(happyAccountAssertionResponse),
   438  		))
   439  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--verbose", "--abs-time"})
   440  	c.Assert(err, check.IsNil)
   441  	c.Assert(rest, check.DeepEquals, []string{})
   442  	c.Check(s.Stdout(), check.Equals, `
   443  brand-id:               mememe
   444  model:                  test-model
   445  serial:                 serialserial
   446  architecture:           amd64
   447  base:                   core18
   448  display-name:           Model Name
   449  gadget:                 pc=18
   450  kernel:                 pc-kernel=18
   451  store:                  mememestore
   452  system-user-authority:  
   453    - youyouyou
   454    - mememe
   455  timestamp:       2017-07-27T00:00:00Z
   456  required-snaps:  
   457    - core
   458    - hello-world
   459  `[1:])
   460  	c.Check(s.Stderr(), check.Equals, "")
   461  }
   462  
   463  func (s *SnapSuite) TestModelVerboseNoSerialYet(c *check.C) {
   464  	s.RedirectClientToTestServer(
   465  		makeHappyTestServerHandler(
   466  			c,
   467  			simpleHappyResponder(happyModelAssertionResponse),
   468  			simpleUnhappyResponder(noSerialAssertionYetResponse),
   469  			simpleAssertionAccountResponder(happyAccountAssertionResponse),
   470  		))
   471  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--verbose", "--abs-time"})
   472  	c.Assert(err, check.IsNil)
   473  	c.Assert(rest, check.DeepEquals, []string{})
   474  	c.Check(s.Stdout(), check.Equals, `
   475  brand-id:               mememe
   476  model:                  test-model
   477  serial:                 -- (device not registered yet)
   478  architecture:           amd64
   479  base:                   core18
   480  gadget:                 pc=18
   481  kernel:                 pc-kernel=18
   482  store:                  mememestore
   483  system-user-authority:  
   484    - youyouyou
   485    - mememe
   486  timestamp:       2017-07-27T00:00:00Z
   487  required-snaps:  
   488    - core
   489    - hello-world
   490  `[1:])
   491  	c.Check(s.Stderr(), check.Equals, "")
   492  }
   493  
   494  func (s *SnapSuite) TestModelAssertion(c *check.C) {
   495  	s.RedirectClientToTestServer(
   496  		makeHappyTestServerHandler(
   497  			c,
   498  			simpleHappyResponder(happyModelAssertionResponse),
   499  			simpleHappyResponder(happySerialAssertionResponse),
   500  			simpleAssertionAccountResponder(happyAccountAssertionResponse),
   501  		))
   502  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--assertion"})
   503  	c.Assert(err, check.IsNil)
   504  	c.Assert(rest, check.DeepEquals, []string{})
   505  	c.Check(s.Stdout(), check.Equals, happyModelAssertionResponse)
   506  	c.Check(s.Stderr(), check.Equals, "")
   507  }
   508  
   509  func (s *SnapSuite) TestModelAssertionVerbose(c *check.C) {
   510  	// check that no calls to the server happen
   511  	s.RedirectClientToTestServer(
   512  		func(w http.ResponseWriter, r *http.Request) {
   513  			c.Fatalf("unexpected request to %s", r.URL.Path)
   514  		},
   515  	)
   516  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--assertion", "--verbose"})
   517  	c.Assert(err, check.ErrorMatches, "cannot use --verbose with --assertion")
   518  	c.Check(s.Stdout(), check.Equals, "")
   519  	c.Check(s.Stderr(), check.Equals, "")
   520  }
   521  
   522  func (s *SnapSuite) TestSerial(c *check.C) {
   523  	s.RedirectClientToTestServer(
   524  		makeHappyTestServerHandler(
   525  			c,
   526  			simpleHappyResponder(happyModelAssertionResponse),
   527  			simpleHappyResponder(happySerialAssertionResponse),
   528  			simpleAssertionAccountResponder(happyAccountAssertionResponse),
   529  		))
   530  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--serial"})
   531  	c.Assert(err, check.IsNil)
   532  	c.Assert(rest, check.DeepEquals, []string{})
   533  	c.Check(s.Stdout(), check.Equals, `
   534  brand-id:  my-brand
   535  model:     my-old-model
   536  serial:    serialserial
   537  `[1:])
   538  	c.Check(s.Stderr(), check.Equals, "")
   539  }
   540  
   541  func (s *SnapSuite) TestSerialVerbose(c *check.C) {
   542  	s.RedirectClientToTestServer(
   543  		makeHappyTestServerHandler(
   544  			c,
   545  			simpleHappyResponder(happyModelAssertionResponse),
   546  			simpleHappyResponder(happySerialAssertionResponse),
   547  			simpleAssertionAccountResponder(happyAccountAssertionResponse),
   548  		))
   549  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--serial", "--verbose", "--abs-time"})
   550  	c.Assert(err, check.IsNil)
   551  	c.Assert(rest, check.DeepEquals, []string{})
   552  	c.Check(s.Stdout(), check.Equals, `
   553  brand-id:   my-brand
   554  model:      my-old-model
   555  serial:     serialserial
   556  timestamp:  2019-08-26T16:34:21-05:00
   557  device-key-sha3-384: |
   558    iqLo9doLzK8De9925UrdUyuvPbBad72OTWVE9YJXqd6nz9dKvwJ_lHP5bVxrl3VO
   559  device-key: |
   560    AcZrBFaFwYABAvCgEOrrLA6FKcreHxCcOoTgBUZ+IRG7Nb8tzmEAklaQPGpv7skapUjwD1luE2g
   561    omTcoTssVHrfLpBoSDV1aBs44rg3NK40ZKPJP7d2zkds1GxUo1Ea5vfet3SJ4h3aRABEBAAE=
   562  `[1:])
   563  	c.Check(s.Stderr(), check.Equals, "")
   564  }
   565  
   566  func (s *SnapSuite) TestSerialAssertion(c *check.C) {
   567  	s.RedirectClientToTestServer(
   568  		makeHappyTestServerHandler(
   569  			c,
   570  			simpleHappyResponder(happyModelAssertionResponse),
   571  			simpleHappyResponder(happySerialAssertionResponse),
   572  			simpleAssertionAccountResponder(happyAccountAssertionResponse),
   573  		))
   574  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--serial", "--assertion"})
   575  	c.Assert(err, check.IsNil)
   576  	c.Assert(rest, check.DeepEquals, []string{})
   577  	c.Check(s.Stdout(), check.Equals, happySerialAssertionResponse)
   578  	c.Check(s.Stderr(), check.Equals, "")
   579  }
   580  
   581  func (s *SnapSuite) TestSerialAssertionSerialAssertionMissing(c *check.C) {
   582  	s.RedirectClientToTestServer(
   583  		makeHappyTestServerHandler(
   584  			c,
   585  			simpleHappyResponder(happyModelAssertionResponse),
   586  			simpleUnhappyResponder(noSerialAssertionYetResponse),
   587  			simpleAssertionAccountResponder(happyAccountAssertionResponse),
   588  		))
   589  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--serial", "--assertion"})
   590  	c.Assert(err, check.ErrorMatches, `device not ready yet \(no assertions found\)`)
   591  	c.Assert(s.Stdout(), check.Equals, "")
   592  	c.Assert(s.Stderr(), check.Equals, "")
   593  }