github.com/oskarth/go-ethereum@v1.6.8-0.20191013093314-dac24a9d3494/cmd/swarm/manifest_test.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // go-ethereum is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package main
    18  
    19  import (
    20  	"bytes"
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  	"runtime"
    25  	"testing"
    26  
    27  	"github.com/ethereum/go-ethereum/swarm/api"
    28  	swarm "github.com/ethereum/go-ethereum/swarm/api/client"
    29  	"github.com/ethereum/go-ethereum/swarm/testutil"
    30  )
    31  
    32  // TestManifestChange tests manifest add, update and remove
    33  // cli commands without encryption.
    34  func TestManifestChange(t *testing.T) {
    35  	if runtime.GOOS == "windows" {
    36  		t.Skip()
    37  	}
    38  
    39  	testManifestChange(t, false)
    40  }
    41  
    42  // TestManifestChange tests manifest add, update and remove
    43  // cli commands with encryption enabled.
    44  func TestManifestChangeEncrypted(t *testing.T) {
    45  	if runtime.GOOS == "windows" {
    46  		t.Skip()
    47  	}
    48  
    49  	testManifestChange(t, true)
    50  }
    51  
    52  // testManifestChange performs cli commands:
    53  // - manifest add
    54  // - manifest update
    55  // - manifest remove
    56  // on a manifest, testing the functionality of this
    57  // comands on paths that are in root manifest or a nested one.
    58  // Argument encrypt controls whether to use encryption or not.
    59  func testManifestChange(t *testing.T, encrypt bool) {
    60  	t.Parallel()
    61  	srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
    62  	defer srv.Close()
    63  
    64  	tmp, err := ioutil.TempDir("", "swarm-manifest-test")
    65  	if err != nil {
    66  		t.Fatal(err)
    67  	}
    68  	defer os.RemoveAll(tmp)
    69  
    70  	origDir := filepath.Join(tmp, "orig")
    71  	if err := os.Mkdir(origDir, 0777); err != nil {
    72  		t.Fatal(err)
    73  	}
    74  
    75  	indexDataFilename := filepath.Join(origDir, "index.html")
    76  	err = ioutil.WriteFile(indexDataFilename, []byte("<h1>Test</h1>"), 0666)
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  	// Files paths robots.txt and robots.html share the same prefix "robots."
    81  	// which will result a manifest with a nested manifest under path "robots.".
    82  	// This will allow testing manifest changes on both root and nested manifest.
    83  	err = ioutil.WriteFile(filepath.Join(origDir, "robots.txt"), []byte("Disallow: /"), 0666)
    84  	if err != nil {
    85  		t.Fatal(err)
    86  	}
    87  	err = ioutil.WriteFile(filepath.Join(origDir, "robots.html"), []byte("<strong>No Robots Allowed</strong>"), 0666)
    88  	if err != nil {
    89  		t.Fatal(err)
    90  	}
    91  	err = ioutil.WriteFile(filepath.Join(origDir, "mutants.txt"), []byte("Frank\nMarcus"), 0666)
    92  	if err != nil {
    93  		t.Fatal(err)
    94  	}
    95  
    96  	args := []string{
    97  		"--bzzapi",
    98  		srv.URL,
    99  		"--recursive",
   100  		"--defaultpath",
   101  		indexDataFilename,
   102  		"up",
   103  		origDir,
   104  	}
   105  	if encrypt {
   106  		args = append(args, "--encrypt")
   107  	}
   108  
   109  	origManifestHash := runSwarmExpectHash(t, args...)
   110  
   111  	checkHashLength(t, origManifestHash, encrypt)
   112  
   113  	client := swarm.NewClient(srv.URL)
   114  
   115  	// upload a new file and use its manifest to add it the original manifest.
   116  	t.Run("add", func(t *testing.T) {
   117  		humansData := []byte("Ann\nBob")
   118  		humansDataFilename := filepath.Join(tmp, "humans.txt")
   119  		err = ioutil.WriteFile(humansDataFilename, humansData, 0666)
   120  		if err != nil {
   121  			t.Fatal(err)
   122  		}
   123  
   124  		humansManifestHash := runSwarmExpectHash(t,
   125  			"--bzzapi",
   126  			srv.URL,
   127  			"up",
   128  			humansDataFilename,
   129  		)
   130  
   131  		newManifestHash := runSwarmExpectHash(t,
   132  			"--bzzapi",
   133  			srv.URL,
   134  			"manifest",
   135  			"add",
   136  			origManifestHash,
   137  			"humans.txt",
   138  			humansManifestHash,
   139  		)
   140  
   141  		checkHashLength(t, newManifestHash, encrypt)
   142  
   143  		newManifest := downloadManifest(t, client, newManifestHash, encrypt)
   144  
   145  		var found bool
   146  		for _, e := range newManifest.Entries {
   147  			if e.Path == "humans.txt" {
   148  				found = true
   149  				if e.Size != int64(len(humansData)) {
   150  					t.Errorf("expected humans.txt size %v, got %v", len(humansData), e.Size)
   151  				}
   152  				if e.ModTime.IsZero() {
   153  					t.Errorf("got zero mod time for humans.txt")
   154  				}
   155  				ct := "text/plain; charset=utf-8"
   156  				if e.ContentType != ct {
   157  					t.Errorf("expected content type %q, got %q", ct, e.ContentType)
   158  				}
   159  				break
   160  			}
   161  		}
   162  		if !found {
   163  			t.Fatal("no humans.txt in new manifest")
   164  		}
   165  
   166  		checkFile(t, client, newManifestHash, "humans.txt", humansData)
   167  	})
   168  
   169  	// upload a new file and use its manifest to add it the original manifest,
   170  	// but ensure that the file will be in the nested manifest of the original one.
   171  	t.Run("add nested", func(t *testing.T) {
   172  		robotsData := []byte(`{"disallow": "/"}`)
   173  		robotsDataFilename := filepath.Join(tmp, "robots.json")
   174  		err = ioutil.WriteFile(robotsDataFilename, robotsData, 0666)
   175  		if err != nil {
   176  			t.Fatal(err)
   177  		}
   178  
   179  		robotsManifestHash := runSwarmExpectHash(t,
   180  			"--bzzapi",
   181  			srv.URL,
   182  			"up",
   183  			robotsDataFilename,
   184  		)
   185  
   186  		newManifestHash := runSwarmExpectHash(t,
   187  			"--bzzapi",
   188  			srv.URL,
   189  			"manifest",
   190  			"add",
   191  			origManifestHash,
   192  			"robots.json",
   193  			robotsManifestHash,
   194  		)
   195  
   196  		checkHashLength(t, newManifestHash, encrypt)
   197  
   198  		newManifest := downloadManifest(t, client, newManifestHash, encrypt)
   199  
   200  		var found bool
   201  	loop:
   202  		for _, e := range newManifest.Entries {
   203  			if e.Path == "robots." {
   204  				nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
   205  				for _, e := range nestedManifest.Entries {
   206  					if e.Path == "json" {
   207  						found = true
   208  						if e.Size != int64(len(robotsData)) {
   209  							t.Errorf("expected robots.json size %v, got %v", len(robotsData), e.Size)
   210  						}
   211  						if e.ModTime.IsZero() {
   212  							t.Errorf("got zero mod time for robots.json")
   213  						}
   214  						ct := "application/json"
   215  						if e.ContentType != ct {
   216  							t.Errorf("expected content type %q, got %q", ct, e.ContentType)
   217  						}
   218  						break loop
   219  					}
   220  				}
   221  			}
   222  		}
   223  		if !found {
   224  			t.Fatal("no robots.json in new manifest")
   225  		}
   226  
   227  		checkFile(t, client, newManifestHash, "robots.json", robotsData)
   228  	})
   229  
   230  	// upload a new file and use its manifest to change the file it the original manifest.
   231  	t.Run("update", func(t *testing.T) {
   232  		indexData := []byte("<h1>Ethereum Swarm</h1>")
   233  		indexDataFilename := filepath.Join(tmp, "index.html")
   234  		err = ioutil.WriteFile(indexDataFilename, indexData, 0666)
   235  		if err != nil {
   236  			t.Fatal(err)
   237  		}
   238  
   239  		indexManifestHash := runSwarmExpectHash(t,
   240  			"--bzzapi",
   241  			srv.URL,
   242  			"up",
   243  			indexDataFilename,
   244  		)
   245  
   246  		newManifestHash := runSwarmExpectHash(t,
   247  			"--bzzapi",
   248  			srv.URL,
   249  			"manifest",
   250  			"update",
   251  			origManifestHash,
   252  			"index.html",
   253  			indexManifestHash,
   254  		)
   255  
   256  		checkHashLength(t, newManifestHash, encrypt)
   257  
   258  		newManifest := downloadManifest(t, client, newManifestHash, encrypt)
   259  
   260  		var found bool
   261  		for _, e := range newManifest.Entries {
   262  			if e.Path == "index.html" {
   263  				found = true
   264  				if e.Size != int64(len(indexData)) {
   265  					t.Errorf("expected index.html size %v, got %v", len(indexData), e.Size)
   266  				}
   267  				if e.ModTime.IsZero() {
   268  					t.Errorf("got zero mod time for index.html")
   269  				}
   270  				ct := "text/html; charset=utf-8"
   271  				if e.ContentType != ct {
   272  					t.Errorf("expected content type %q, got %q", ct, e.ContentType)
   273  				}
   274  				break
   275  			}
   276  		}
   277  		if !found {
   278  			t.Fatal("no index.html in new manifest")
   279  		}
   280  
   281  		checkFile(t, client, newManifestHash, "index.html", indexData)
   282  
   283  		// check default entry change
   284  		checkFile(t, client, newManifestHash, "", indexData)
   285  	})
   286  
   287  	// upload a new file and use its manifest to change the file it the original manifest,
   288  	// but ensure that the file is in the nested manifest of the original one.
   289  	t.Run("update nested", func(t *testing.T) {
   290  		robotsData := []byte(`<string>Only humans allowed!!!</strong>`)
   291  		robotsDataFilename := filepath.Join(tmp, "robots.html")
   292  		err = ioutil.WriteFile(robotsDataFilename, robotsData, 0666)
   293  		if err != nil {
   294  			t.Fatal(err)
   295  		}
   296  
   297  		humansManifestHash := runSwarmExpectHash(t,
   298  			"--bzzapi",
   299  			srv.URL,
   300  			"up",
   301  			robotsDataFilename,
   302  		)
   303  
   304  		newManifestHash := runSwarmExpectHash(t,
   305  			"--bzzapi",
   306  			srv.URL,
   307  			"manifest",
   308  			"update",
   309  			origManifestHash,
   310  			"robots.html",
   311  			humansManifestHash,
   312  		)
   313  
   314  		checkHashLength(t, newManifestHash, encrypt)
   315  
   316  		newManifest := downloadManifest(t, client, newManifestHash, encrypt)
   317  
   318  		var found bool
   319  	loop:
   320  		for _, e := range newManifest.Entries {
   321  			if e.Path == "robots." {
   322  				nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
   323  				for _, e := range nestedManifest.Entries {
   324  					if e.Path == "html" {
   325  						found = true
   326  						if e.Size != int64(len(robotsData)) {
   327  							t.Errorf("expected robots.html size %v, got %v", len(robotsData), e.Size)
   328  						}
   329  						if e.ModTime.IsZero() {
   330  							t.Errorf("got zero mod time for robots.html")
   331  						}
   332  						ct := "text/html; charset=utf-8"
   333  						if e.ContentType != ct {
   334  							t.Errorf("expected content type %q, got %q", ct, e.ContentType)
   335  						}
   336  						break loop
   337  					}
   338  				}
   339  			}
   340  		}
   341  		if !found {
   342  			t.Fatal("no robots.html in new manifest")
   343  		}
   344  
   345  		checkFile(t, client, newManifestHash, "robots.html", robotsData)
   346  	})
   347  
   348  	// remove a file from the manifest.
   349  	t.Run("remove", func(t *testing.T) {
   350  		newManifestHash := runSwarmExpectHash(t,
   351  			"--bzzapi",
   352  			srv.URL,
   353  			"manifest",
   354  			"remove",
   355  			origManifestHash,
   356  			"mutants.txt",
   357  		)
   358  
   359  		checkHashLength(t, newManifestHash, encrypt)
   360  
   361  		newManifest := downloadManifest(t, client, newManifestHash, encrypt)
   362  
   363  		var found bool
   364  		for _, e := range newManifest.Entries {
   365  			if e.Path == "mutants.txt" {
   366  				found = true
   367  				break
   368  			}
   369  		}
   370  		if found {
   371  			t.Fatal("mutants.txt is not removed")
   372  		}
   373  	})
   374  
   375  	// remove a file from the manifest, but ensure that the file is in
   376  	// the nested manifest of the original one.
   377  	t.Run("remove nested", func(t *testing.T) {
   378  		newManifestHash := runSwarmExpectHash(t,
   379  			"--bzzapi",
   380  			srv.URL,
   381  			"manifest",
   382  			"remove",
   383  			origManifestHash,
   384  			"robots.html",
   385  		)
   386  
   387  		checkHashLength(t, newManifestHash, encrypt)
   388  
   389  		newManifest := downloadManifest(t, client, newManifestHash, encrypt)
   390  
   391  		var found bool
   392  	loop:
   393  		for _, e := range newManifest.Entries {
   394  			if e.Path == "robots." {
   395  				nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
   396  				for _, e := range nestedManifest.Entries {
   397  					if e.Path == "html" {
   398  						found = true
   399  						break loop
   400  					}
   401  				}
   402  			}
   403  		}
   404  		if found {
   405  			t.Fatal("robots.html in not removed")
   406  		}
   407  	})
   408  }
   409  
   410  // TestNestedDefaultEntryUpdate tests if the default entry is updated
   411  // if the file in nested manifest used for it is also updated.
   412  func TestNestedDefaultEntryUpdate(t *testing.T) {
   413  	if runtime.GOOS == "windows" {
   414  		t.Skip()
   415  	}
   416  
   417  	testNestedDefaultEntryUpdate(t, false)
   418  }
   419  
   420  // TestNestedDefaultEntryUpdateEncrypted tests if the default entry
   421  // of encrypted upload is updated if the file in nested manifest
   422  // used for it is also updated.
   423  func TestNestedDefaultEntryUpdateEncrypted(t *testing.T) {
   424  	if runtime.GOOS == "windows" {
   425  		t.Skip()
   426  	}
   427  
   428  	testNestedDefaultEntryUpdate(t, true)
   429  }
   430  
   431  func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) {
   432  	t.Parallel()
   433  	srv := testutil.NewTestSwarmServer(t, serverFunc, nil)
   434  	defer srv.Close()
   435  
   436  	tmp, err := ioutil.TempDir("", "swarm-manifest-test")
   437  	if err != nil {
   438  		t.Fatal(err)
   439  	}
   440  	defer os.RemoveAll(tmp)
   441  
   442  	origDir := filepath.Join(tmp, "orig")
   443  	if err := os.Mkdir(origDir, 0777); err != nil {
   444  		t.Fatal(err)
   445  	}
   446  
   447  	indexData := []byte("<h1>Test</h1>")
   448  	indexDataFilename := filepath.Join(origDir, "index.html")
   449  	err = ioutil.WriteFile(indexDataFilename, indexData, 0666)
   450  	if err != nil {
   451  		t.Fatal(err)
   452  	}
   453  	// Add another file with common prefix as the default entry to test updates of
   454  	// default entry with nested manifests.
   455  	err = ioutil.WriteFile(filepath.Join(origDir, "index.txt"), []byte("Test"), 0666)
   456  	if err != nil {
   457  		t.Fatal(err)
   458  	}
   459  
   460  	args := []string{
   461  		"--bzzapi",
   462  		srv.URL,
   463  		"--recursive",
   464  		"--defaultpath",
   465  		indexDataFilename,
   466  		"up",
   467  		origDir,
   468  	}
   469  	if encrypt {
   470  		args = append(args, "--encrypt")
   471  	}
   472  
   473  	origManifestHash := runSwarmExpectHash(t, args...)
   474  
   475  	checkHashLength(t, origManifestHash, encrypt)
   476  
   477  	client := swarm.NewClient(srv.URL)
   478  
   479  	newIndexData := []byte("<h1>Ethereum Swarm</h1>")
   480  	newIndexDataFilename := filepath.Join(tmp, "index.html")
   481  	err = ioutil.WriteFile(newIndexDataFilename, newIndexData, 0666)
   482  	if err != nil {
   483  		t.Fatal(err)
   484  	}
   485  
   486  	newIndexManifestHash := runSwarmExpectHash(t,
   487  		"--bzzapi",
   488  		srv.URL,
   489  		"up",
   490  		newIndexDataFilename,
   491  	)
   492  
   493  	newManifestHash := runSwarmExpectHash(t,
   494  		"--bzzapi",
   495  		srv.URL,
   496  		"manifest",
   497  		"update",
   498  		origManifestHash,
   499  		"index.html",
   500  		newIndexManifestHash,
   501  	)
   502  
   503  	checkHashLength(t, newManifestHash, encrypt)
   504  
   505  	newManifest := downloadManifest(t, client, newManifestHash, encrypt)
   506  
   507  	var found bool
   508  	for _, e := range newManifest.Entries {
   509  		if e.Path == "index." {
   510  			found = true
   511  			newManifest = downloadManifest(t, client, e.Hash, encrypt)
   512  			break
   513  		}
   514  	}
   515  	if !found {
   516  		t.Fatal("no index. path in new manifest")
   517  	}
   518  
   519  	found = false
   520  	for _, e := range newManifest.Entries {
   521  		if e.Path == "html" {
   522  			found = true
   523  			if e.Size != int64(len(newIndexData)) {
   524  				t.Errorf("expected index.html size %v, got %v", len(newIndexData), e.Size)
   525  			}
   526  			if e.ModTime.IsZero() {
   527  				t.Errorf("got zero mod time for index.html")
   528  			}
   529  			ct := "text/html; charset=utf-8"
   530  			if e.ContentType != ct {
   531  				t.Errorf("expected content type %q, got %q", ct, e.ContentType)
   532  			}
   533  			break
   534  		}
   535  	}
   536  	if !found {
   537  		t.Fatal("no html in new manifest")
   538  	}
   539  
   540  	checkFile(t, client, newManifestHash, "index.html", newIndexData)
   541  
   542  	// check default entry change
   543  	checkFile(t, client, newManifestHash, "", newIndexData)
   544  }
   545  
   546  func runSwarmExpectHash(t *testing.T, args ...string) (hash string) {
   547  	t.Helper()
   548  	hashRegexp := `[a-f\d]{64,128}`
   549  	up := runSwarm(t, args...)
   550  	_, matches := up.ExpectRegexp(hashRegexp)
   551  	up.ExpectExit()
   552  
   553  	if len(matches) < 1 {
   554  		t.Fatal("no matches found")
   555  	}
   556  	return matches[0]
   557  }
   558  
   559  func checkHashLength(t *testing.T, hash string, encrypted bool) {
   560  	t.Helper()
   561  	l := len(hash)
   562  	if encrypted && l != 128 {
   563  		t.Errorf("expected hash length 128, got %v", l)
   564  	}
   565  	if !encrypted && l != 64 {
   566  		t.Errorf("expected hash length 64, got %v", l)
   567  	}
   568  }
   569  
   570  func downloadManifest(t *testing.T, client *swarm.Client, hash string, encrypted bool) (manifest *api.Manifest) {
   571  	t.Helper()
   572  	m, isEncrypted, err := client.DownloadManifest(hash)
   573  	if err != nil {
   574  		t.Fatal(err)
   575  	}
   576  
   577  	if encrypted != isEncrypted {
   578  		t.Error("new manifest encryption flag is not correct")
   579  	}
   580  	return m
   581  }
   582  
   583  func checkFile(t *testing.T, client *swarm.Client, hash, path string, expected []byte) {
   584  	t.Helper()
   585  	f, err := client.Download(hash, path)
   586  	if err != nil {
   587  		t.Fatal(err)
   588  	}
   589  
   590  	got, err := ioutil.ReadAll(f)
   591  	if err != nil {
   592  		t.Fatal(err)
   593  	}
   594  	if !bytes.Equal(got, expected) {
   595  		t.Errorf("expected file content %q, got %q", expected, got)
   596  	}
   597  }