github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/pkg/storage/url/urldownload/storage_test.go (about)

     1  //go:build unit || !integration
     2  
     3  package urldownload
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"os"
    11  	"path/filepath"
    12  	"testing"
    13  
    14  	"github.com/filecoin-project/bacalhau/pkg/logger"
    15  	"github.com/filecoin-project/bacalhau/pkg/model"
    16  	"github.com/filecoin-project/bacalhau/pkg/system"
    17  	"github.com/spf13/cobra"
    18  	"github.com/stretchr/testify/suite"
    19  )
    20  
    21  // Define the suite, and absorb the built-in basic suite
    22  // functionality from testify - including a T() method which
    23  // returns the current testing context
    24  type StorageSuite struct {
    25  	suite.Suite
    26  	RootCmd *cobra.Command
    27  }
    28  
    29  func TestStorageSuite(t *testing.T) {
    30  	suite.Run(t, new(StorageSuite))
    31  }
    32  
    33  // Before each test
    34  func (s *StorageSuite) SetupTest() {
    35  	logger.ConfigureTestLogging(s.T())
    36  	s.Require().NoError(system.InitConfigForTesting(s.T()))
    37  }
    38  
    39  func (s *StorageSuite) TestNewStorageProvider() {
    40  	cm := system.NewCleanupManager()
    41  
    42  	sp, err := NewStorage(cm)
    43  	s.Require().NoError(err, "failed to create storage provider")
    44  
    45  	// is dir writable?
    46  	f, err := os.Create(filepath.Join(sp.localDir, "data.txt"))
    47  	s.Require().NoError(err, "failed to create file")
    48  
    49  	_, err = f.WriteString("test\n")
    50  	s.Require().NoError(err, "failed to write to file")
    51  
    52  	s.NoError(f.Close())
    53  	s.Require().NotNil(sp.client, "HTTPClient is nil")
    54  }
    55  
    56  func (s *StorageSuite) TestHasStorageLocally() {
    57  	sp := newStorage(s.T().TempDir())
    58  
    59  	spec := model.StorageSpec{
    60  		StorageSource: model.StorageSourceURLDownload,
    61  		URL:           "foo",
    62  		Path:          "foo",
    63  	}
    64  	// files are not cached thus shall never return true
    65  	locally, err := sp.HasStorageLocally(context.Background(), spec)
    66  	s.Require().NoError(err, "failed to check if storage is locally available")
    67  
    68  	s.False(locally, "storage should not be locally available")
    69  }
    70  
    71  func (s *StorageSuite) TestPrepareStorageURL() {
    72  	type dummyRequest struct {
    73  		path    string
    74  		code    int
    75  		content string
    76  	}
    77  	tests := []struct {
    78  		name             string
    79  		requests         []dummyRequest
    80  		expectedContent  string
    81  		expectedFilename string
    82  	}{
    83  		{
    84  			name: "follows-redirect",
    85  			requests: []dummyRequest{
    86  				{
    87  					path:    "/initial",
    88  					code:    302,
    89  					content: "/second.png",
    90  				},
    91  				{
    92  					path:    "/second.png",
    93  					code:    302,
    94  					content: "/third.txt",
    95  				},
    96  				{
    97  					path:    "/third.txt",
    98  					code:    200,
    99  					content: "this is from the final redirect",
   100  				},
   101  			},
   102  			expectedContent:  "this is from the final redirect",
   103  			expectedFilename: "third.txt",
   104  		},
   105  		{
   106  			name: "retries",
   107  			requests: []dummyRequest{
   108  				{
   109  					path:    "/initial",
   110  					code:    500,
   111  					content: "",
   112  				},
   113  				{
   114  					path:    "/initial",
   115  					code:    500,
   116  					content: "",
   117  				},
   118  				{
   119  					path:    "/initial",
   120  					code:    200,
   121  					content: "got there eventually",
   122  				},
   123  			},
   124  			expectedContent:  "got there eventually",
   125  			expectedFilename: "initial",
   126  		},
   127  		{
   128  			name: "retry-anything",
   129  			requests: []dummyRequest{
   130  				{
   131  					path:    "/initial",
   132  					code:    401,
   133  					content: "not allowed",
   134  				},
   135  				{
   136  					path:    "/initial",
   137  					code:    401,
   138  					content: "not allowed",
   139  				},
   140  				{
   141  					path:    "/initial",
   142  					code:    200,
   143  					content: "changed my mind",
   144  				},
   145  			},
   146  			expectedContent:  "changed my mind",
   147  			expectedFilename: "initial",
   148  		},
   149  		{
   150  			name: "generates-name",
   151  			requests: []dummyRequest{
   152  				{
   153  					path:    "/",
   154  					code:    200,
   155  					content: "name should be a UUID",
   156  				},
   157  			},
   158  			expectedContent:  "name should be a UUID",
   159  			expectedFilename: "",
   160  		},
   161  		{
   162  			name: "no-content",
   163  			requests: []dummyRequest{
   164  				{
   165  					path:    "/nothing.txt",
   166  					code:    204,
   167  					content: "",
   168  				},
   169  			},
   170  			expectedContent:  "",
   171  			expectedFilename: "nothing.txt",
   172  		},
   173  		{
   174  			name: "picsum.photos",
   175  			requests: []dummyRequest{
   176  				{
   177  					path:    "/200/300",
   178  					code:    302,
   179  					content: "/id/568/200/300.jpg",
   180  				},
   181  				{
   182  					path:    "/id/568/200/300.jpg",
   183  					code:    200,
   184  					content: "i'm not putting an image here",
   185  				},
   186  			},
   187  			expectedContent:  "i'm not putting an image here",
   188  			expectedFilename: "300.jpg",
   189  		},
   190  	}
   191  
   192  	for _, test := range tests {
   193  		s.Run(test.name, func() {
   194  			responseCount := 0
   195  			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   196  				response := test.requests[responseCount]
   197  				responseCount++
   198  
   199  				if r.URL.Path != response.path {
   200  					http.Error(w, fmt.Sprintf("invalid path: %s should be %s", r.URL.Path, response.path), 999)
   201  					return
   202  				}
   203  
   204  				if response.code == http.StatusFound {
   205  					http.Redirect(w, r, response.content, http.StatusFound)
   206  					return
   207  				}
   208  
   209  				w.WriteHeader(response.code)
   210  				_, err := w.Write([]byte(response.content))
   211  				s.NoError(err)
   212  			}))
   213  			s.T().Cleanup(ts.Close)
   214  
   215  			subject := newStorage(s.T().TempDir())
   216  
   217  			vol, err := subject.PrepareStorage(context.Background(), model.StorageSpec{
   218  				URL:  fmt.Sprintf("%s%s", ts.URL, test.requests[0].path),
   219  				Path: "/inputs",
   220  			})
   221  			s.Require().NoError(err)
   222  
   223  			actualFilename := filepath.Base(vol.Source)
   224  			if test.expectedFilename == "" {
   225  				s.Regexp(`[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`, actualFilename,
   226  					"Filename should be a UUID if it can't come from the HTTP response ")
   227  				s.Regexp(`[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`, filepath.Base(vol.Target))
   228  				s.Equal(fmt.Sprintf("%s%s", string(os.PathSeparator), "inputs"), filepath.Dir(vol.Target))
   229  			} else {
   230  				s.Equal(test.expectedFilename, actualFilename)
   231  				s.Equal(filepath.Join("/inputs", test.expectedFilename), vol.Target)
   232  			}
   233  
   234  			s.FileExists(vol.Source)
   235  			actualContent, err := os.ReadFile(vol.Source)
   236  			s.Require().NoError(err)
   237  
   238  			s.Equal(test.expectedContent, string(actualContent))
   239  		})
   240  	}
   241  }