github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/client/snapshot_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018 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 client_test
    21  
    22  import (
    23  	"crypto/sha256"
    24  	"io/ioutil"
    25  	"net/http"
    26  	"net/url"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  
    31  	"gopkg.in/check.v1"
    32  
    33  	"github.com/snapcore/snapd/client"
    34  	"github.com/snapcore/snapd/snap"
    35  )
    36  
    37  func (cs *clientSuite) TestClientSnapshotIsValid(c *check.C) {
    38  	now := time.Now()
    39  	revno := snap.R(1)
    40  	sums := map[string]string{"user/foo.tgz": "some long hash"}
    41  	c.Check((&client.Snapshot{
    42  		SetID:    42,
    43  		Time:     now,
    44  		Snap:     "asnap",
    45  		Revision: revno,
    46  		SHA3_384: sums,
    47  	}).IsValid(), check.Equals, true)
    48  
    49  	for desc, snapshot := range map[string]*client.Snapshot{
    50  		"nil":     nil,
    51  		"empty":   {},
    52  		"no id":   { /*SetID: 42,*/ Time: now, Snap: "asnap", Revision: revno, SHA3_384: sums},
    53  		"no time": {SetID: 42 /*Time: now,*/, Snap: "asnap", Revision: revno, SHA3_384: sums},
    54  		"no snap": {SetID: 42, Time: now /*Snap: "asnap",*/, Revision: revno, SHA3_384: sums},
    55  		"no rev":  {SetID: 42, Time: now, Snap: "asnap" /*Revision: revno,*/, SHA3_384: sums},
    56  		"no sums": {SetID: 42, Time: now, Snap: "asnap", Revision: revno /*SHA3_384: sums*/},
    57  	} {
    58  		c.Check(snapshot.IsValid(), check.Equals, false, check.Commentf("%s", desc))
    59  	}
    60  
    61  }
    62  
    63  func (cs *clientSuite) TestClientSnapshotSetTime(c *check.C) {
    64  	// if set is empty, it doesn't explode (and returns the zero time)
    65  	c.Check(client.SnapshotSet{}.Time().IsZero(), check.Equals, true)
    66  	// if not empty, returns the earliest one
    67  	c.Check(client.SnapshotSet{Snapshots: []*client.Snapshot{
    68  		{Time: time.Unix(3, 0)},
    69  		{Time: time.Unix(1, 0)},
    70  		{Time: time.Unix(2, 0)},
    71  	}}.Time(), check.DeepEquals, time.Unix(1, 0))
    72  }
    73  
    74  func (cs *clientSuite) TestClientSnapshotSetSize(c *check.C) {
    75  	// if set is empty, doesn't explode (and returns 0)
    76  	c.Check(client.SnapshotSet{}.Size(), check.Equals, int64(0))
    77  	// if not empty, returns the sum
    78  	c.Check(client.SnapshotSet{Snapshots: []*client.Snapshot{
    79  		{Size: 1},
    80  		{Size: 2},
    81  		{Size: 3},
    82  	}}.Size(), check.DeepEquals, int64(6))
    83  }
    84  
    85  func (cs *clientSuite) TestClientSnapshotSets(c *check.C) {
    86  	cs.rsp = `{
    87  		"type": "sync",
    88  		"result": [{"id": 1}, {"id":2}]
    89  }`
    90  	sets, err := cs.cli.SnapshotSets(42, []string{"foo", "bar"})
    91  	c.Assert(err, check.IsNil)
    92  	c.Check(sets, check.DeepEquals, []client.SnapshotSet{{ID: 1}, {ID: 2}})
    93  	c.Check(cs.req.Method, check.Equals, "GET")
    94  	c.Check(cs.req.URL.Path, check.Equals, "/v2/snapshots")
    95  	c.Check(cs.req.URL.Query(), check.DeepEquals, url.Values{
    96  		"set":   []string{"42"},
    97  		"snaps": []string{"foo,bar"},
    98  	})
    99  }
   100  
   101  func (cs *clientSuite) testClientSnapshotActionFull(c *check.C, action string, users []string, f func() (string, error)) {
   102  	cs.status = 202
   103  	cs.rsp = `{
   104  		"status-code": 202,
   105  		"type": "async",
   106  		"change": "1too3"
   107  	}`
   108  	id, err := f()
   109  	c.Assert(err, check.IsNil)
   110  	c.Check(id, check.Equals, "1too3")
   111  
   112  	c.Assert(cs.req.Header.Get("Content-Type"), check.Equals, "application/json")
   113  
   114  	act, err := client.UnmarshalSnapshotAction(cs.req.Body)
   115  	c.Assert(err, check.IsNil)
   116  	c.Check(act.SetID, check.Equals, uint64(42))
   117  	c.Check(act.Action, check.Equals, action)
   118  	c.Check(act.Snaps, check.DeepEquals, []string{"asnap", "bsnap"})
   119  	c.Check(act.Users, check.DeepEquals, users)
   120  
   121  	c.Check(cs.req.Method, check.Equals, "POST")
   122  	c.Check(cs.req.URL.Path, check.Equals, "/v2/snapshots")
   123  	c.Check(cs.req.URL.Query(), check.HasLen, 0)
   124  }
   125  
   126  func (cs *clientSuite) TestClientForgetSnapshot(c *check.C) {
   127  	cs.testClientSnapshotActionFull(c, "forget", nil, func() (string, error) {
   128  		return cs.cli.ForgetSnapshots(42, []string{"asnap", "bsnap"})
   129  	})
   130  }
   131  
   132  func (cs *clientSuite) testClientSnapshotAction(c *check.C, action string, f func(uint64, []string, []string) (string, error)) {
   133  	cs.testClientSnapshotActionFull(c, action, []string{"auser", "buser"}, func() (string, error) {
   134  		return f(42, []string{"asnap", "bsnap"}, []string{"auser", "buser"})
   135  	})
   136  }
   137  
   138  func (cs *clientSuite) TestClientCheckSnapshots(c *check.C) {
   139  	cs.testClientSnapshotAction(c, "check", cs.cli.CheckSnapshots)
   140  }
   141  
   142  func (cs *clientSuite) TestClientRestoreSnapshots(c *check.C) {
   143  	cs.testClientSnapshotAction(c, "restore", cs.cli.RestoreSnapshots)
   144  }
   145  
   146  func (cs *clientSuite) TestClientExportSnapshot(c *check.C) {
   147  	type tableT struct {
   148  		content     string
   149  		contentType string
   150  		status      int
   151  	}
   152  
   153  	table := []tableT{
   154  		{"dummy-export", client.SnapshotExportMediaType, 200},
   155  		{"dummy-export", "application/x-tar", 400},
   156  		{"", "", 400},
   157  	}
   158  
   159  	for i, t := range table {
   160  		comm := check.Commentf("%d: %q", i, t.content)
   161  
   162  		cs.contentLength = int64(len(t.content))
   163  		cs.header = http.Header{"Content-Type": []string{t.contentType}}
   164  		cs.rsp = t.content
   165  		cs.status = t.status
   166  
   167  		r, size, err := cs.cli.SnapshotExport(42)
   168  		if t.status == 200 {
   169  			c.Assert(err, check.IsNil, comm)
   170  			c.Assert(cs.countingCloser.closeCalled, check.Equals, 0)
   171  			c.Assert(size, check.Equals, int64(len(t.content)), comm)
   172  		} else {
   173  			c.Assert(err.Error(), check.Equals, "unexpected status code: ")
   174  			c.Assert(cs.countingCloser.closeCalled, check.Equals, 1)
   175  		}
   176  
   177  		if t.status == 200 {
   178  			buf, err := ioutil.ReadAll(r)
   179  			c.Assert(err, check.IsNil)
   180  			c.Assert(string(buf), check.Equals, t.content)
   181  		}
   182  	}
   183  }
   184  
   185  func (cs *clientSuite) TestClientSnapshotImport(c *check.C) {
   186  	type tableT struct {
   187  		rsp    string
   188  		status int
   189  		setID  uint64
   190  		error  string
   191  	}
   192  	table := []tableT{
   193  		{`{"type": "sync", "result": {"set-id": 42, "snaps": ["baz", "bar", "foo"]}}`, 200, 42, ""},
   194  		{`{"type": "error"}`, 400, 0, "server error: \"Bad Request\""},
   195  	}
   196  
   197  	for i, t := range table {
   198  		comm := check.Commentf("%d: %s", i, t.rsp)
   199  
   200  		cs.rsp = t.rsp
   201  		cs.status = t.status
   202  
   203  		fakeSnapshotData := "fake"
   204  		r := strings.NewReader(fakeSnapshotData)
   205  		importSet, err := cs.cli.SnapshotImport(r, int64(len(fakeSnapshotData)))
   206  		if t.error != "" {
   207  			c.Assert(err, check.NotNil, comm)
   208  			c.Check(err.Error(), check.Equals, t.error, comm)
   209  			continue
   210  		}
   211  		c.Assert(err, check.IsNil, comm)
   212  		c.Assert(cs.req.Header.Get("Content-Type"), check.Equals, client.SnapshotExportMediaType)
   213  		c.Assert(cs.req.Header.Get("Content-Length"), check.Equals, strconv.Itoa(len(fakeSnapshotData)))
   214  		c.Check(importSet.ID, check.Equals, t.setID, comm)
   215  		c.Check(importSet.Snaps, check.DeepEquals, []string{"baz", "bar", "foo"}, comm)
   216  		d, err := ioutil.ReadAll(cs.req.Body)
   217  		c.Assert(err, check.IsNil)
   218  		c.Check(string(d), check.Equals, fakeSnapshotData)
   219  	}
   220  }
   221  
   222  func (cs *clientSuite) TestClientSnapshotContentHash(c *check.C) {
   223  	now := time.Now()
   224  	revno := snap.R(1)
   225  	sums := map[string]string{"user/foo.tgz": "some long hash"}
   226  
   227  	sh1 := &client.Snapshot{SetID: 1, Time: now, Snap: "asnap", Revision: revno, SHA3_384: sums}
   228  	// sh1, sh1_1 are the same except time
   229  	sh1_1 := &client.Snapshot{SetID: 1, Time: now.Add(10), Snap: "asnap", Revision: revno, SHA3_384: sums}
   230  	// sh1, sh2 are the same except setID
   231  	sh2 := &client.Snapshot{SetID: 2, Time: now, Snap: "asnap", Revision: revno, SHA3_384: sums}
   232  
   233  	h1, err := sh1.ContentHash()
   234  	c.Assert(err, check.IsNil)
   235  	// content hash uses sha256 internally
   236  	c.Check(h1, check.HasLen, sha256.Size)
   237  
   238  	// same except time means same hash
   239  	h1_1, err := sh1_1.ContentHash()
   240  	c.Assert(err, check.IsNil)
   241  	c.Check(h1, check.DeepEquals, h1_1)
   242  
   243  	// same except set means same hash
   244  	h2, err := sh2.ContentHash()
   245  	c.Assert(err, check.IsNil)
   246  	c.Check(h1, check.DeepEquals, h2)
   247  
   248  	// sh3 is actually different
   249  	sh3 := &client.Snapshot{SetID: 1, Time: now, Snap: "other-snap", Revision: revno, SHA3_384: sums}
   250  	h3, err := sh3.ContentHash()
   251  	c.Assert(err, check.IsNil)
   252  	c.Check(h1, check.Not(check.DeepEquals), h3)
   253  
   254  	// identical to sh1 except for sha3_384 sums
   255  	sums4 := map[string]string{"user/foo.tgz": "some other hash"}
   256  	sh4 := &client.Snapshot{SetID: 1, Time: now, Snap: "asnap", Revision: revno, SHA3_384: sums4}
   257  	// same except sha3_384 means different hash
   258  	h4, err := sh4.ContentHash()
   259  	c.Assert(err, check.IsNil)
   260  	c.Check(h4, check.Not(check.DeepEquals), h1)
   261  }
   262  
   263  func (cs *clientSuite) TestClientSnapshotSetContentHash(c *check.C) {
   264  	sums := map[string]string{"user/foo.tgz": "some long hash"}
   265  	ss1 := client.SnapshotSet{Snapshots: []*client.Snapshot{
   266  		{SetID: 1, Snap: "snap2", Size: 2, SHA3_384: sums},
   267  		{SetID: 1, Snap: "snap1", Size: 1, SHA3_384: sums},
   268  		{SetID: 1, Snap: "snap3", Size: 3, SHA3_384: sums},
   269  	}}
   270  	// ss2 is the same ss1 but in a different order with different setID
   271  	// (but that does not matter for the content hash)
   272  	ss2 := client.SnapshotSet{Snapshots: []*client.Snapshot{
   273  		{SetID: 2, Snap: "snap3", Size: 3, SHA3_384: sums},
   274  		{SetID: 2, Snap: "snap2", Size: 2, SHA3_384: sums},
   275  		{SetID: 2, Snap: "snap1", Size: 1, SHA3_384: sums},
   276  	}}
   277  
   278  	h1, err := ss1.ContentHash()
   279  	c.Assert(err, check.IsNil)
   280  	// content hash uses sha256 internally
   281  	c.Check(h1, check.HasLen, sha256.Size)
   282  
   283  	// h1 and h2 have the same hash
   284  	h2, err := ss2.ContentHash()
   285  	c.Assert(err, check.IsNil)
   286  	c.Check(h2, check.DeepEquals, h1)
   287  
   288  	// ss3 is different because the size of snap3 is different
   289  	ss3 := client.SnapshotSet{Snapshots: []*client.Snapshot{
   290  		{SetID: 1, Snap: "snap2", Size: 2},
   291  		{SetID: 1, Snap: "snap3", Size: 666666666},
   292  		{SetID: 1, Snap: "snap1", Size: 1},
   293  	}}
   294  	// h1 and h3 are different
   295  	h3, err := ss3.ContentHash()
   296  	c.Assert(err, check.IsNil)
   297  	c.Check(h3, check.Not(check.DeepEquals), h1)
   298  
   299  }